Skip to content

Commit

Permalink
Add TX to fee. Parse fees that were not found through event parsing (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pharr117 authored Jan 5, 2024
1 parent f6a3b55 commit 96faf11
Show file tree
Hide file tree
Showing 13 changed files with 114 additions and 23 deletions.
9 changes: 6 additions & 3 deletions csv/accounting_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,10 @@ func TestAccointingIbcMsgTransferSelf(t *testing.T) {

// make transactions for this user entering and leaving LPs
transferTxs := getTestIbcTransferTXs(t, sourceAddress, destAddress, sourceChain, targetChain)
emptyFees := []db.Fee{}

// attempt to parse
err := parser.ProcessTaxableTx(sourceAddress.Address, transferTxs)
err := parser.ProcessTaxableTx(sourceAddress.Address, transferTxs, emptyFees)
assert.Nil(t, err, "should not get error from parsing these transactions")

// validate output
Expand Down Expand Up @@ -83,9 +84,10 @@ func TestAccointingIbcMsgTransferExternal(t *testing.T) {

// make transactions for this user entering and leaving LPs
transferTxs := getTestIbcTransferTXs(t, sourceAddress, destAddress, sourceChain, targetChain)
emptyFees := []db.Fee{}

// attempt to parse
err := parser.ProcessTaxableTx(sourceAddress.Address, transferTxs)
err := parser.ProcessTaxableTx(sourceAddress.Address, transferTxs, emptyFees)
assert.Nil(t, err, "should not get error from parsing these transactions")

// validate output
Expand Down Expand Up @@ -115,9 +117,10 @@ func TestAccointingOsmoLPParsing(t *testing.T) {

// make transactions for this user entering and leaving LPs
transferTxs := getTestSwapTXs(t, targetAddress, chain)
emptyFees := []db.Fee{}

// attempt to parse
err := parser.ProcessTaxableTx(targetAddress.Address, transferTxs)
err := parser.ProcessTaxableTx(targetAddress.Address, transferTxs, emptyFees)
assert.Nil(t, err, "should not get error from parsing these transactions")

// validate output
Expand Down
4 changes: 3 additions & 1 deletion csv/koinly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/DefiantLabs/cosmos-tax-cli/config"
"github.com/DefiantLabs/cosmos-tax-cli/csv/parsers/koinly"
"github.com/DefiantLabs/cosmos-tax-cli/db"
"github.com/DefiantLabs/cosmos-tax-cli/osmosis"
"github.com/stretchr/testify/assert"
)
Expand All @@ -23,9 +24,10 @@ func TestKoinlyOsmoLPParsing(t *testing.T) {

// make transactions for this user entering and leaving LPs
transferTxs := getTestSwapTXs(t, targetAddress, chain)
emptyFees := []db.Fee{}

// attempt to parse
err := parser.ProcessTaxableTx(targetAddress.Address, transferTxs)
err := parser.ProcessTaxableTx(targetAddress.Address, transferTxs, emptyFees)
assert.Nil(t, err, "should not get error from parsing these transactions")

// validate output
Expand Down
10 changes: 9 additions & 1 deletion csv/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,15 @@ func ParseForAddress(addresses []string, startDate, endDate *time.Time, pgSQL *g
return nil, nil, err
}

err = parser.ProcessTaxableTx(address, taxableTxs)
// Some TXs may have fees while the address had no taxable TXs
// We gather all fees and pass them to the parser
taxableFees, err := db.GetTaxableFees(address, pgSQL)
if err != nil {
config.Log.Error("Error getting taxable fees.", err)
return nil, nil, err
}

err = parser.ProcessTaxableTx(address, taxableTxs, taxableFees)
if err != nil {
config.Log.Error("Error processing taxable transaction.", err)
return nil, nil, err
Expand Down
17 changes: 13 additions & 4 deletions csv/parsers/accointing/accointing.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (p *Parser) TimeLayout() string {
return TimeLayout
}

func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error {
func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error {
// Build a map, so we know which TX go with which messages
txMap := parsers.MakeTXMap(taxableTxs)

Expand Down Expand Up @@ -55,7 +55,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac
// Handle fees on all taxableTxs at once, we don't do this in the regular parser or in the parsing groups
// This requires HandleFees to process the fees into unique mappings of tx -> fees (since we gather Taxable Messages in the taxableTxs)
// If we move it into the ParseTx function or into the ParseGroup function, we may be able to reduce the logic in the HandleFees func
feeRows, err := HandleFees(address, taxableTxs)
feeRows, err := HandleFees(address, taxableTxs, taxableFees)
if err != nil {
return err
}
Expand Down Expand Up @@ -166,9 +166,9 @@ func (p Parser) GetHeaders() []string {
// If the transaction lists the same amount of fees as there are rows in the CSV,
// then we spread the fees out one per row. Otherwise we add a line for the fees,
// where each fee has a separate line.
func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err error) {
func HandleFees(address string, events []db.TaxableTransaction, allFees []db.Fee) (rows []Row, err error) {
// No events -- This address didn't pay any fees
if len(events) == 0 {
if len(events) == 0 && len(allFees) == 0 {
return rows, nil
}

Expand All @@ -183,6 +183,15 @@ func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err
txIdsToTx[txID] = event.Message.Tx
}

// Due to the way we are parsing, we may have fees for TX that we don't have events for
for _, fee := range allFees {
txID := fee.Tx.ID
if _, ok := txToFeesMap[txID]; !ok {
txToFeesMap[txID] = []db.Fee{fee}
txIdsToTx[txID] = fee.Tx
}
}

for id, txFees := range txToFeesMap {
for _, fee := range txFees {
if fee.PayerAddress.Address == address {
Expand Down
17 changes: 13 additions & 4 deletions csv/parsers/cointracker/cointracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (p *Parser) TimeLayout() string {
return TimeLayout
}

func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error {
func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error {
// Build a map, so we know which TX go with which messages
txMap := parsers.MakeTXMap(taxableTxs)

Expand Down Expand Up @@ -54,7 +54,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac
// Handle fees on all taxableTxs at once, we don't do this in the regular parser or in the parsing groups
// This requires HandleFees to process the fees into unique mappings of tx -> fees (since we gather Taxable Messages in the taxableTxs)
// If we move it into the ParseTx function or into the ParseGroup function, we may be able to reduce the logic in the HandleFees func
feeRows, err := HandleFees(address, taxableTxs)
feeRows, err := HandleFees(address, taxableTxs, taxableFees)
if err != nil {
return err
}
Expand Down Expand Up @@ -161,9 +161,9 @@ func (p Parser) GetHeaders() []string {
// If the transaction lists the same amount of fees as there are rows in the CSV,
// then we spread the fees out one per row. Otherwise we add a line for the fees,
// where each fee has a separate line.
func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err error) {
func HandleFees(address string, events []db.TaxableTransaction, allFees []db.Fee) (rows []Row, err error) {
// No events -- This address didn't pay any fees
if len(events) == 0 {
if len(events) == 0 && len(allFees) == 0 {
return rows, nil
}

Expand All @@ -178,6 +178,15 @@ func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err
txIdsToTx[txID] = event.Message.Tx
}

// Due to the way we are parsing, we may have fees for TX that we don't have events for
for _, fee := range allFees {
txID := fee.Tx.ID
if _, ok := txToFeesMap[txID]; !ok {
txToFeesMap[txID] = []db.Fee{fee}
txIdsToTx[txID] = fee.Tx
}
}

for id, txFees := range txToFeesMap {
for _, fee := range txFees {
if fee.PayerAddress.Address == address {
Expand Down
19 changes: 18 additions & 1 deletion csv/parsers/cryptotaxcalculator/cryptotaxcalculator.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ func (p *Parser) TimeLayout() string {
return TimeLayout
}

func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error {
func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error {
// Build a map, so we know which TX go with which messages
txMap := parsers.MakeTXMap(taxableTxs)

feesWithoutTx := []db.Fee{}
for _, fee := range taxableFees {
if _, ok := txMap[fee.Tx.ID]; !ok {
feesWithoutTx = append(feesWithoutTx, fee)
}
}

// Pull messages out of txMap that must be grouped together
parsers.SeparateParsingGroups(txMap, p.ParsingGroups)

Expand Down Expand Up @@ -51,6 +58,16 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac
}
}

for _, fee := range feesWithoutTx {
row := Row{}
err := row.ParseFee(address, fee)
if err != nil {
return err
}

p.Rows = append(p.Rows, row)
}

return nil
}

Expand Down
16 changes: 16 additions & 0 deletions csv/parsers/cryptotaxcalculator/rows.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,22 @@ func (row *Row) ParseBasic(address string, event db.TaxableTransaction) error {
return nil
}

func (row *Row) ParseFee(address string, fee db.Fee) error {
row.Date = fee.Tx.Block.TimeStamp
row.ID = fee.Tx.Hash
row.Type = Fee

sentConversionAmount, sentConversionSymbol, err := db.ConvertUnits(util.FromNumeric(fee.Amount), fee.Denomination)
if err != nil {
return fmt.Errorf("cannot parse denom units for TX %s (classification: swap sent)", row.ID)
}

row.BaseAmount = sentConversionAmount.Text('f', -1)
row.BaseCurrency = sentConversionSymbol

return nil
}

func (row *Row) ParseSwap(event db.TaxableTransaction, address, eventType string) error {
row.Date = event.Message.Tx.Block.TimeStamp
row.ID = event.Message.Tx.Hash
Expand Down
1 change: 1 addition & 0 deletions csv/parsers/cryptotaxcalculator/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ const (
FlatWithdrawal = "flat-withdrawal"
Receive = "receive"
Sell = "sell"
Fee = "fee"
)
17 changes: 13 additions & 4 deletions csv/parsers/koinly/koinly.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func (p *Parser) TimeLayout() string {
return TimeLayout
}

func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error {
func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error {
// Build a map, so we know which TX go with which messages
txMap := parsers.MakeTXMap(taxableTxs)

Expand Down Expand Up @@ -80,7 +80,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac
// Handle fees on all taxableTxs at once, we don't do this in the regular parser or in the parsing groups
// This requires HandleFees to process the fees into unique mappings of tx -> fees (since we gather Taxable Messages in the taxableTxs)
// If we move it into the ParseTx function or into the ParseGroup function, we may be able to reduce the logic in the HandleFees func
feeRows, err := HandleFees(address, taxableTxs)
feeRows, err := HandleFees(address, taxableTxs, taxableFees)
if err != nil {
return err
}
Expand Down Expand Up @@ -235,9 +235,9 @@ func (p Parser) GetHeaders() []string {
// If the transaction lists the same amount of fees as there are rows in the CSV,
// then we spread the fees out one per row. Otherwise we add a line for the fees,
// where each fee has a separate line.
func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err error) {
func HandleFees(address string, events []db.TaxableTransaction, allFees []db.Fee) (rows []Row, err error) {
// No events -- This address didn't pay any fees
if len(events) == 0 {
if len(events) == 0 && len(allFees) == 0 {
return rows, nil
}

Expand All @@ -252,6 +252,15 @@ func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err
txIdsToTx[txID] = event.Message.Tx
}

// Due to the way we are parsing, we may have fees for TX that we don't have events for
for _, fee := range allFees {
txID := fee.Tx.ID
if _, ok := txToFeesMap[txID]; !ok {
txToFeesMap[txID] = []db.Fee{fee}
txIdsToTx[txID] = fee.Tx
}
}

for id, txFees := range txToFeesMap {
for _, fee := range txFees {
if fee.PayerAddress.Address == address {
Expand Down
17 changes: 13 additions & 4 deletions csv/parsers/taxbit/taxbit.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func (p *Parser) TimeLayout() string {
return TimeLayout
}

func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error {
func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error {
// Build a map, so we know which TX go with which messages
txMap := parsers.MakeTXMap(taxableTxs)

Expand Down Expand Up @@ -54,7 +54,7 @@ func (p *Parser) ProcessTaxableTx(address string, taxableTxs []db.TaxableTransac
// Handle fees on all taxableTxs at once, we don't do this in the regular parser or in the parsing groups
// This requires HandleFees to process the fees into unique mappings of tx -> fees (since we gather Taxable Messages in the taxableTxs)
// If we move it into the ParseTx function or into the ParseGroup function, we may be able to reduce the logic in the HandleFees func
feeRows, err := HandleFees(address, taxableTxs)
feeRows, err := HandleFees(address, taxableTxs, taxableFees)
if err != nil {
return err
}
Expand Down Expand Up @@ -165,9 +165,9 @@ func (p Parser) GetHeaders() []string {
// If the transaction lists the same amount of fees as there are rows in the CSV,
// then we spread the fees out one per row. Otherwise we add a line for the fees,
// where each fee has a separate line.
func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err error) {
func HandleFees(address string, events []db.TaxableTransaction, allFees []db.Fee) (rows []Row, err error) {
// No events -- This address didn't pay any fees
if len(events) == 0 {
if len(events) == 0 && len(allFees) == 0 {
return rows, nil
}

Expand All @@ -182,6 +182,15 @@ func HandleFees(address string, events []db.TaxableTransaction) (rows []Row, err
txIdsToTx[txID] = event.Message.Tx
}

// Due to the way we are parsing, we may have fees for TX that we don't have events for
for _, fee := range allFees {
txID := fee.Tx.ID
if _, ok := txToFeesMap[txID]; !ok {
txToFeesMap[txID] = []db.Fee{fee}
txIdsToTx[txID] = fee.Tx
}
}

for id, txFees := range txToFeesMap {
for _, fee := range txFees {
if fee.PayerAddress.Address == address {
Expand Down
2 changes: 1 addition & 1 deletion csv/parsers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type Parser interface {
InitializeParsingGroups()
ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction) error
ProcessTaxableTx(address string, taxableTxs []db.TaxableTransaction, taxableFees []db.Fee) error
ProcessTaxableEvent(taxableEvents []db.TaxableEvent) error
GetHeaders() []string
GetRows(address string, startDate, endDate *time.Time) ([]CsvRow, error)
Expand Down
1 change: 1 addition & 0 deletions db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Tx struct {
type Fee struct {
ID uint `gorm:"primaryKey"`
TxID uint `gorm:"uniqueIndex:txDenomFee"`
Tx Tx `gorm:"foreignKey:tx_id"`
Amount decimal.Decimal `gorm:"type:decimal(78,0);"`
DenominationID uint `gorm:"uniqueIndex:txDenomFee"`
Denomination Denom `gorm:"foreignKey:DenominationID"`
Expand Down
7 changes: 7 additions & 0 deletions db/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ func GetTaxableTransactions(address string, db *gorm.DB) ([]TaxableTransaction,
return taxableTransactions, result.Error
}

func GetTaxableFees(address string, db *gorm.DB) ([]Fee, error) {
var fees []Fee
result := db.Joins("JOIN addresses ON addresses.id = fees.payer_address_id").
Where("addresses.address = ?", address).Preload("PayerAddress").Preload("Denomination").Preload("Tx").Find(&fees)
return fees, result.Error
}

func GetTaxableEvents(address string, db *gorm.DB) ([]TaxableEvent, error) {
// Look up all TaxableEvents for the addresses
var taxableEvents []TaxableEvent
Expand Down

0 comments on commit 96faf11

Please sign in to comment.