Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TX to fee. Parse fees that were not found through event parsing #518

Merged
merged 1 commit into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading