From 57e370e7b953c941de1722e643ed6d40ac5bd732 Mon Sep 17 00:00:00 2001 From: Ivan Belyakov Date: Fri, 22 Sep 2023 18:09:14 +0200 Subject: [PATCH] feat(wallet): Added detection of ERC1155 SingleTransfer events --- services/wallet/common/log_parser.go | 89 +++++-- services/wallet/transfer/commands.go | 9 +- .../wallet/transfer/commands_sequential.go | 1 + .../transfer/commands_sequential_test.go | 231 ++++++++++++++---- services/wallet/transfer/downloader.go | 83 ++++++- 5 files changed, 342 insertions(+), 71 deletions(-) diff --git a/services/wallet/common/log_parser.go b/services/wallet/common/log_parser.go index a52362bf1..b6dbc4f12 100644 --- a/services/wallet/common/log_parser.go +++ b/services/wallet/common/log_parser.go @@ -20,20 +20,24 @@ type EventType string const ( // Transaction types - EthTransfer Type = "eth" - Erc20Transfer Type = "erc20" - Erc721Transfer Type = "erc721" - UniswapV2Swap Type = "uniswapV2Swap" - UniswapV3Swap Type = "uniswapV3Swap" - HopBridgeFrom Type = "HopBridgeFrom" - HopBridgeTo Type = "HopBridgeTo" - unknownTransaction Type = "unknown" + EthTransfer Type = "eth" + Erc20Transfer Type = "erc20" + Erc721Transfer Type = "erc721" + Erc1155SingleTransfer Type = "erc1155" + Erc1155BatchTransfer Type = "erc1155" + UniswapV2Swap Type = "uniswapV2Swap" + UniswapV3Swap Type = "uniswapV3Swap" + HopBridgeFrom Type = "HopBridgeFrom" + HopBridgeTo Type = "HopBridgeTo" + unknownTransaction Type = "unknown" // Event types WETHDepositEventType EventType = "wethDepositEvent" WETHWithdrawalEventType EventType = "wethWithdrawalEvent" Erc20TransferEventType EventType = "erc20Event" Erc721TransferEventType EventType = "erc721Event" + Erc1155TransferSingleEventType EventType = "erc1155SingleEvent" + Erc1155TransferBatchEventType EventType = "erc1155BatchEvent" UniswapV2SwapEventType EventType = "uniswapV2SwapEvent" UniswapV3SwapEventType EventType = "uniswapV3SwapEvent" HopBridgeTransferSentToL2EventType EventType = "hopBridgeTransferSentToL2Event" @@ -49,10 +53,13 @@ const ( // Transfer (index_topic_1 address from, index_topic_2 address to, uint256 value) // Transfer (index_topic_1 address from, index_topic_2 address to, index_topic_3 uint256 tokenId) - Erc20_721TransferEventSignature = "Transfer(address,address,uint256)" + Erc20_721TransferEventSignature = "Transfer(address,address,uint256)" + Erc1155TransferSingleEventSignature = "TransferSingle(address,address,address,uint256,uint256)" // operator, from, to, id, value + Erc1155TransferBatchEventSignature = "TransferBatch(address,address,address,uint256[],uint256[])" // operator, from, to, ids, values - erc20TransferEventIndexedParameters = 3 // signature, from, to - erc721TransferEventIndexedParameters = 4 // signature, from, to, tokenId + erc20TransferEventIndexedParameters = 3 // signature, from, to + erc721TransferEventIndexedParameters = 4 // signature, from, to, tokenId + erc1155TransferEventIndexedParameters = 4 // signature, operator, from, to (id, value are not indexed) // Swap (index_topic_1 address sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, index_topic_2 address to) uniswapV2SwapEventSignature = "Swap(address,uint256,uint256,uint256,uint256,address)" // also used by SushiSwap @@ -79,6 +86,8 @@ func GetEventType(log *types.Log) EventType { wethDepositEventSignatureHash := GetEventSignatureHash(wethDepositEventSignature) wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature) erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature) + erc1155TransferSingleEventSignatureHash := GetEventSignatureHash(Erc1155TransferSingleEventSignature) + erc1155TransferBatchEventSignatureHash := GetEventSignatureHash(Erc1155TransferBatchEventSignature) uniswapV2SwapEventSignatureHash := GetEventSignatureHash(uniswapV2SwapEventSignature) uniswapV3SwapEventSignatureHash := GetEventSignatureHash(uniswapV3SwapEventSignature) hopBridgeTransferSentToL2EventSignatureHash := GetEventSignatureHash(hopBridgeTransferSentToL2EventSignature) @@ -99,6 +108,10 @@ func GetEventType(log *types.Log) EventType { case erc721TransferEventIndexedParameters: return Erc721TransferEventType } + case erc1155TransferSingleEventSignatureHash: + return Erc1155TransferSingleEventType + case erc1155TransferBatchEventSignatureHash: + return Erc1155TransferBatchEventType case uniswapV2SwapEventSignatureHash: return UniswapV2SwapEventType case uniswapV3SwapEventSignatureHash: @@ -123,6 +136,10 @@ func EventTypeToSubtransactionType(eventType EventType) Type { return Erc20Transfer case Erc721TransferEventType: return Erc721Transfer + case Erc1155TransferSingleEventType: + return Erc1155SingleTransfer + case Erc1155TransferBatchEventType: + return Erc1155BatchTransfer case UniswapV2SwapEventType: return UniswapV2Swap case UniswapV3SwapEventType: @@ -149,7 +166,12 @@ func GetFirstEvent(logs []*types.Log) (EventType, *types.Log) { func IsTokenTransfer(logs []*types.Log) bool { eventType, _ := GetFirstEvent(logs) - return eventType == Erc20TransferEventType + switch eventType { + case Erc20TransferEventType, Erc721TransferEventType, Erc1155TransferSingleEventType, Erc1155TransferBatchEventType: + return true + } + + return false } func ParseWETHDepositLog(ethlog *types.Log) (src common.Address, amount *big.Int) { @@ -200,7 +222,7 @@ func ParseWETHWithdrawLog(ethlog *types.Log) (dst common.Address, amount *big.In func ParseErc20TransferLog(ethlog *types.Log) (from, to common.Address, amount *big.Int) { amount = new(big.Int) - if len(ethlog.Topics) < 3 { + if len(ethlog.Topics) < erc20TransferEventIndexedParameters { log.Warn("not enough topics for erc20 transfer", "topics", ethlog.Topics) return } @@ -225,7 +247,7 @@ func ParseErc20TransferLog(ethlog *types.Log) (from, to common.Address, amount * func ParseErc721TransferLog(ethlog *types.Log) (from, to common.Address, tokenID *big.Int) { tokenID = new(big.Int) - if len(ethlog.Topics) < 4 { + if len(ethlog.Topics) < erc721TransferEventIndexedParameters { log.Warn("not enough topics for erc721 transfer", "topics", ethlog.Topics) return } @@ -248,6 +270,37 @@ func ParseErc721TransferLog(ethlog *types.Log) (from, to common.Address, tokenID return } +func ParseErc1155TransferSingleLog(ethlog *types.Log) (operator, from, to common.Address, id, amount *big.Int) { + if len(ethlog.Topics) < erc1155TransferEventIndexedParameters { + log.Warn("not enough topics for erc1155 transfer single", "topics", ethlog.Topics) + return + } + + amount = new(big.Int) + id = new(big.Int) + + for i := 1; i < erc1155TransferEventIndexedParameters; i++ { + if len(ethlog.Topics[i]) != common.HashLength { + log.Warn(fmt.Sprintf("topic %d is not padded to %d byte address, topic=%s", i, common.HashLength, ethlog.Topics[1])) + return + } + } + addressIdx := common.HashLength - common.AddressLength + copy(operator[:], ethlog.Topics[1][addressIdx:]) + copy(from[:], ethlog.Topics[2][addressIdx:]) + copy(to[:], ethlog.Topics[3][addressIdx:]) + + if len(ethlog.Data) != common.HashLength*2 { + log.Warn("data is not padded to 64 bytes", "data", ethlog.Data) + return + } + + id.SetBytes(ethlog.Data[:common.HashLength]) + amount.SetBytes(ethlog.Data[common.HashLength:]) + + return +} + func ParseUniswapV2Log(ethlog *types.Log) (pairAddress common.Address, from common.Address, to common.Address, amount0In *big.Int, amount1In *big.Int, amount0Out *big.Int, amount1Out *big.Int, err error) { amount0In = new(big.Int) amount1In = new(big.Int) @@ -502,6 +555,14 @@ func ExtractTokenIdentity(dbEntryType Type, log *types.Log, tx *types.Transactio txTokenID = tokenID txFrom = &from txTo = &to + case Erc1155SingleTransfer: + tokenAddress = new(common.Address) + *tokenAddress = log.Address + _, from, to, tokenID, value := ParseErc1155TransferSingleLog(log) + txTokenID = tokenID + txFrom = &from + txTo = &to + txValue = value } return diff --git a/services/wallet/transfer/commands.go b/services/wallet/transfer/commands.go index ff72fb002..8256c099e 100644 --- a/services/wallet/transfer/commands.go +++ b/services/wallet/transfer/commands.go @@ -116,9 +116,10 @@ type erc20HistoricalCommand struct { chainClient chain.ClientInterface feed *event.Feed - iterator *IterativeDownloader - to *big.Int - from *big.Int + iterator *IterativeDownloader + to *big.Int + from *big.Int + foundHeaders []*DBHeader } @@ -166,7 +167,7 @@ func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) { for !c.iterator.Finished() { headers, _, _, err := c.iterator.Next(ctx) if err != nil { - log.Error("failed to get next batch", "error", err) + log.Error("failed to get next batch", "error", err, "chainID", c.chainClient.NetworkID()) // TODO: stop inifinite command in case of an error that we can't fix like missing trie node return err } c.foundHeaders = append(c.foundHeaders, headers...) diff --git a/services/wallet/transfer/commands_sequential.go b/services/wallet/transfer/commands_sequential.go index d14ee7049..8374223f3 100644 --- a/services/wallet/transfer/commands_sequential.go +++ b/services/wallet/transfer/commands_sequential.go @@ -281,6 +281,7 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) { break } + // if we have found first ETH block and we have not reached the start of ETH history yet if c.startBlockNumber != nil && c.fromBlockNumber.Cmp(from) == -1 { log.Debug("ERC20 tail should be checked", "initial from", c.fromBlockNumber, "actual from", from, "first ETH block", c.startBlockNumber) c.reachedETHHistoryStart = true diff --git a/services/wallet/transfer/commands_sequential_test.go b/services/wallet/transfer/commands_sequential_test.go index 890b4e2da..fe79d47c1 100644 --- a/services/wallet/transfer/commands_sequential_test.go +++ b/services/wallet/transfer/commands_sequential_test.go @@ -11,6 +11,8 @@ import ( "time" "github.com/stretchr/testify/mock" + "golang.org/x/exp/slices" // since 1.21, this is in the standard library + "github.com/stretchr/testify/require" "github.com/ethereum/go-ethereum" @@ -25,6 +27,7 @@ import ( "github.com/status-im/status-go/rpc/chain" "github.com/status-im/status-go/services/wallet/async" "github.com/status-im/status-go/services/wallet/balance" + w_common "github.com/status-im/status-go/services/wallet/common" "github.com/status-im/status-go/t/helpers" "github.com/status-im/status-go/params" @@ -39,17 +42,19 @@ import ( type TestClient struct { t *testing.T // [][block, newBalance, nonceDiff] - balances [][]int - outgoingERC20Transfers []testERC20Transfer - incomingERC20Transfers []testERC20Transfer - balanceHistory map[uint64]*big.Int - tokenBalanceHistory map[common.Address]map[uint64]*big.Int - nonceHistory map[uint64]uint64 - traceAPICalls bool - printPreparedData bool - rw sync.RWMutex - callsCounter map[string]int - currentBlock uint64 + balances [][]int + outgoingERC20Transfers []testERC20Transfer + incomingERC20Transfers []testERC20Transfer + outgoingERC1155SingleTransfers []testERC20Transfer + incomingERC1155SingleTransfers []testERC20Transfer + balanceHistory map[uint64]*big.Int + tokenBalanceHistory map[common.Address]map[uint64]*big.Int + nonceHistory map[uint64]uint64 + traceAPICalls bool + printPreparedData bool + rw sync.RWMutex + callsCounter map[string]int + currentBlock uint64 } func (tc *TestClient) incCounter(method string) { @@ -126,11 +131,36 @@ func (tc *TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([ if tc.traceAPICalls { tc.t.Log("FilterLogs") } - //checking only ERC20 for now - incomingAddress := q.Topics[len(q.Topics)-1] - allTransfers := tc.incomingERC20Transfers - if len(incomingAddress) == 0 { - allTransfers = tc.outgoingERC20Transfers + + // We do not verify addresses for now + allTransfers := []testERC20Transfer{} + signatures := q.Topics[0] + erc20TransferSignature := w_common.GetEventSignatureHash(w_common.Erc20_721TransferEventSignature) + erc1155TransferSingleSignature := w_common.GetEventSignatureHash(w_common.Erc1155TransferSingleEventSignature) + if slices.Contains(signatures, erc1155TransferSingleSignature) { + from := q.Topics[2] + var to []common.Hash + if len(q.Topics) > 3 { + to = q.Topics[3] + } + + if len(to) > 0 { + allTransfers = append(allTransfers, tc.incomingERC1155SingleTransfers...) + } + if len(from) > 0 { + allTransfers = append(allTransfers, tc.outgoingERC1155SingleTransfers...) + } + } + + if slices.Contains(signatures, erc20TransferSignature) { + from := q.Topics[1] + to := q.Topics[2] + if len(to) > 0 { + allTransfers = append(allTransfers, tc.incomingERC20Transfers...) + } + if len(from) > 0 { + allTransfers = append(allTransfers, tc.outgoingERC20Transfers...) + } } logs := []types.Log{} @@ -341,6 +371,15 @@ func (tc *TestClient) prepareTokenBalanceHistory(toBlock int) { transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer) } + for _, transfer := range tc.outgoingERC1155SingleTransfers { + transfer.amount = new(big.Int).Neg(transfer.amount) + transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer) + } + + for _, transfer := range tc.incomingERC1155SingleTransfers { + transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer) + } + tc.tokenBalanceHistory = map[common.Address]map[uint64]*big.Int{} for token, transfers := range transfersPerToken { @@ -539,16 +578,18 @@ type testERC20Transfer struct { } type findBlockCase struct { - balanceChanges [][]int - ERC20BalanceChanges [][]int - fromBlock int64 - toBlock int64 - rangeSize int - expectedBlocksFound int - outgoingERC20Transfers []testERC20Transfer - incomingERC20Transfers []testERC20Transfer - label string - expectedCalls map[string]int + balanceChanges [][]int + ERC20BalanceChanges [][]int + fromBlock int64 + toBlock int64 + rangeSize int + expectedBlocksFound int + outgoingERC20Transfers []testERC20Transfer + incomingERC20Transfers []testERC20Transfer + outgoingERC1155SingleTransfers []testERC20Transfer + incomingERC1155SingleTransfers []testERC20Transfer + label string + expectedCalls map[string]int } func transferInEachBlock() [][]int { @@ -577,11 +618,7 @@ func getCases() []findBlockCase { toBlock: 100, expectedBlocksFound: 6, expectedCalls: map[string]int{ - "BalanceAt": 27, - //TODO(rasom) NonceAt is flaky, sometimes it's called 18 times, sometimes 17 - //to be investigated - //"NonceAt": 18, - "FilterLogs": 10, + "FilterLogs": 15, "HeaderByNumber": 5, }, } @@ -593,7 +630,7 @@ func getCases() []findBlockCase { expectedCalls: map[string]int{ "BalanceAt": 101, "NonceAt": 0, - "FilterLogs": 10, + "FilterLogs": 15, "HeaderByNumber": 100, }, } @@ -660,7 +697,7 @@ func getCases() []findBlockCase { {big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, }, expectedCalls: map[string]int{ - "FilterLogs": 3, + "FilterLogs": 5, "CallContract": 3, }, } @@ -677,8 +714,7 @@ func getCases() []findBlockCase { {big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, }, expectedCalls: map[string]int{ - "FilterLogs": 3, - "CallContract": 5, + "FilterLogs": 5, }, } @@ -690,12 +726,115 @@ func getCases() []findBlockCase { label: "single block range, no transactions", expectedCalls: map[string]int{ // only two requests to check the range for incoming ERC20 - "FilterLogs": 2, + "FilterLogs": 3, // no contract calls as ERC20 is not checked "CallContract": 0, }, } + case11IncomingERC1155SingleTransfers := findBlockCase{ + balanceChanges: [][]int{}, + toBlock: 100, + rangeSize: 20, + // we expect only a single eth_getLogs to be executed here for both erc20 transfers, + // thus only 2 blocks found + expectedBlocksFound: 2, + incomingERC1155SingleTransfers: []testERC20Transfer{ + {big.NewInt(7), tokenTXYAddress, big.NewInt(1)}, + {big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, + }, + expectedCalls: map[string]int{ + "FilterLogs": 5, + "CallContract": 5, + }, + } + + case12OutgoingERC1155SingleTransfers := findBlockCase{ + balanceChanges: [][]int{ + {6, 1, 0}, + }, + toBlock: 100, + rangeSize: 20, + expectedBlocksFound: 3, + outgoingERC1155SingleTransfers: []testERC20Transfer{ + {big.NewInt(80), tokenTXYAddress, big.NewInt(1)}, + {big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, + }, + expectedCalls: map[string]int{ + "FilterLogs": 15, // 3 for each range + }, + } + + case13outgoingERC20ERC1155SingleTransfers := findBlockCase{ + balanceChanges: [][]int{ + {63, 1, 0}, + }, + toBlock: 100, + rangeSize: 20, + expectedBlocksFound: 3, + outgoingERC1155SingleTransfers: []testERC20Transfer{ + {big.NewInt(80), tokenTXYAddress, big.NewInt(1)}, + }, + outgoingERC20Transfers: []testERC20Transfer{ + {big.NewInt(63), tokenTXYAddress, big.NewInt(1)}, + }, + expectedCalls: map[string]int{ + "FilterLogs": 6, // 3 for each range, 0 for tail check becauseERC20ScanByBalance returns no ranges + }, + } + + case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs := findBlockCase{ + balanceChanges: [][]int{ + {61, 1, 0}, + }, + toBlock: 100, + rangeSize: 20, + expectedBlocksFound: 3, + outgoingERC1155SingleTransfers: []testERC20Transfer{ + {big.NewInt(80), tokenTXYAddress, big.NewInt(1)}, + }, + outgoingERC20Transfers: []testERC20Transfer{ + {big.NewInt(61), tokenTXYAddress, big.NewInt(1)}, + }, + expectedCalls: map[string]int{ + "FilterLogs": 9, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges + }, + label: "outgoing ERC20 and ERC1155 transfers but more FilterLogs calls because startFromBlock is not detected at range [60-80] as it is in the first subrange", + } + + case15incomingERC20outgoingERC1155SingleTransfers := findBlockCase{ + balanceChanges: [][]int{ + {85, 1, 0}, + }, + toBlock: 100, + rangeSize: 20, + expectedBlocksFound: 2, + outgoingERC1155SingleTransfers: []testERC20Transfer{ + {big.NewInt(85), tokenTXYAddress, big.NewInt(1)}, + }, + incomingERC20Transfers: []testERC20Transfer{ + {big.NewInt(88), tokenTXYAddress, big.NewInt(1)}, + }, + expectedCalls: map[string]int{ + "FilterLogs": 3, // 3 for each range of [40-100], 0 for tail check because ERC20ScanByBalance returns no ranges + }, + label: "incoming ERC20 and outgoing ERC1155 transfers are fetched with same topic", + } + + case16 := findBlockCase{ + balanceChanges: [][]int{ + {75, 0, 1}, + }, + outgoingERC20Transfers: []testERC20Transfer{ + {big.NewInt(80), tokenTXXAddress, big.NewInt(4)}, + }, + toBlock: 100, + rangeSize: 20, + expectedBlocksFound: 3, // ideally we should find 2 blocks, but we will find 3 and this test shows that we are ok with that + label: `duplicate blocks detected but we wont fix it because we want to save requests on the edges of the ranges, + taking balance and nonce from cache while ETH and tokens ranges searching are tightly coupled`, + } + cases = append(cases, case1) cases = append(cases, case100transfers) cases = append(cases, case3) @@ -707,6 +846,12 @@ func getCases() []findBlockCase { cases = append(cases, case8emptyHistoryWithERC20Transfers) cases = append(cases, case9emptyHistoryWithERC20Transfers) cases = append(cases, case10) + cases = append(cases, case11IncomingERC1155SingleTransfers) + cases = append(cases, case12OutgoingERC1155SingleTransfers) + cases = append(cases, case13outgoingERC20ERC1155SingleTransfers) + cases = append(cases, case14outgoingERC20ERC1155SingleTransfersMoreFilterLogs) + cases = append(cases, case15incomingERC20outgoingERC1155SingleTransfers) + cases = append(cases, case16) //cases = append([]findBlockCase{}, case10) @@ -718,7 +863,7 @@ var tokenTXYAddress = common.HexToAddress("0x73211") func TestFindBlocksCommand(t *testing.T) { for idx, testCase := range getCases() { - t.Log("case #", idx) + t.Log("case #", idx+1) ctx := context.Background() group := async.NewGroup(ctx) @@ -728,11 +873,13 @@ func TestFindBlocksCommand(t *testing.T) { wdb := NewDB(db) tc := &TestClient{ - t: t, - balances: testCase.balanceChanges, - outgoingERC20Transfers: testCase.outgoingERC20Transfers, - incomingERC20Transfers: testCase.incomingERC20Transfers, - callsCounter: map[string]int{}, + t: t, + balances: testCase.balanceChanges, + outgoingERC20Transfers: testCase.outgoingERC20Transfers, + incomingERC20Transfers: testCase.incomingERC20Transfers, + outgoingERC1155SingleTransfers: testCase.outgoingERC1155SingleTransfers, + incomingERC1155SingleTransfers: testCase.incomingERC1155SingleTransfers, + callsCounter: map[string]int{}, } //tc.traceAPICalls = true //tc.printPreparedData = true diff --git a/services/wallet/transfer/downloader.go b/services/wallet/transfer/downloader.go index a87b29970..bf55da7e3 100644 --- a/services/wallet/transfer/downloader.go +++ b/services/wallet/transfer/downloader.go @@ -240,11 +240,13 @@ func NewERC20TransfersDownloader(client chain.ClientInterface, accounts []common signature := w_common.GetEventSignatureHash(w_common.Erc20_721TransferEventSignature) return &ERC20TransfersDownloader{ - client: client, - accounts: accounts, - incomingOnly: incomingOnly, - signature: signature, - signer: signer, + client: client, + accounts: accounts, + signature: signature, + incomingOnly: incomingOnly, + signatureErc1155Single: w_common.GetEventSignatureHash(w_common.Erc1155TransferSingleEventSignature), + signatureErc1155Batch: w_common.GetEventSignatureHash(w_common.Erc1155TransferBatchEventSignature), + signer: signer, } } @@ -259,7 +261,9 @@ type ERC20TransfersDownloader struct { incomingOnly bool // hash of the Transfer event signature - signature common.Hash + signature common.Hash + signatureErc1155Single common.Hash + signatureErc1155Batch common.Hash // signer is used to derive tx sender from tx signature signer types.Signer @@ -279,6 +283,14 @@ func (d *ERC20TransfersDownloader) outboundTopics(address common.Address) [][]co return [][]common.Hash{{d.signature}, {d.paddedAddress(address)}, {}} } +func (d *ERC20TransfersDownloader) inboundERC20OutboundERC1155Topics(address common.Address) [][]common.Hash { + return [][]common.Hash{{d.signature, d.signatureErc1155Single}, {}, {d.paddedAddress(address)}} +} + +func (d *ERC20TransfersDownloader) inboundTopicsERC1155Single(address common.Address) [][]common.Hash { + return [][]common.Hash{{d.signatureErc1155Single}, {}, {}, {d.paddedAddress(address)}} +} + func (d *ETHDownloader) subTransactionsFromTransactionHash(parent context.Context, txHash common.Hash, address common.Address) ([]Transfer, error) { ctx, cancel := context.WithTimeout(parent, 3*time.Second) tx, _, err := d.chainClient.TransactionByHash(ctx, txHash) @@ -321,7 +333,7 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction rst := make([]Transfer, 0, len(receipt.Logs)) for _, log := range receipt.Logs { eventType := w_common.GetEventType(log) - // Only add ERC20/ERC721 transfers from/to the given account + // Only add ERC20/ERC721/ERC1155 transfers from/to the given account // Other types of events get always added mustAppend := false switch eventType { @@ -335,6 +347,11 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction if trFrom == address || trTo == address { mustAppend = true } + case w_common.Erc1155TransferSingleEventType: + _, trFrom, trTo, _, _ := w_common.ParseErc1155TransferSingleLog(log) + if trFrom == address || trTo == address { + mustAppend = true + } case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType: mustAppend = true case w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType: @@ -382,6 +399,13 @@ func (d *ERC20TransfersDownloader) blocksFromLogs(parent context.Context, logs [ return nil, err } + logType := w_common.EventTypeToSubtransactionType(w_common.GetEventType(&l)) + log.Debug("block from logs", "block", l.BlockNumber, "log", l, "logType", logType, "address", address, "id", id) + + if logType != w_common.Erc1155SingleTransfer && logType != w_common.Erc1155BatchTransfer { + logType = w_common.Erc20Transfer + } + header := &DBHeader{ Number: big.NewInt(int64(l.BlockNumber)), Hash: l.BlockHash, @@ -392,7 +416,7 @@ func (d *ERC20TransfersDownloader) blocksFromLogs(parent context.Context, logs [ ID: id, From: address, Loaded: false, - Type: w_common.Erc20Transfer, + Type: logType, Log: &l, BaseGasFees: baseGasFee, }}, @@ -422,6 +446,7 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro var err error for _, address := range d.accounts { outbound := []types.Log{} + var inboundOrMixed []types.Log // inbound ERC20 or outbound ERC1155 share the same signature for our purposes if !d.incomingOnly { outbound, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{ FromBlock: from, @@ -431,17 +456,38 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro if err != nil { return nil, err } + inboundOrMixed, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{ + FromBlock: from, + ToBlock: to, + Topics: d.inboundERC20OutboundERC1155Topics(address), + }) + if err != nil { + return nil, err + } + } else { + inboundOrMixed, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{ + FromBlock: from, + ToBlock: to, + Topics: d.inboundTopics(address), + }) + if err != nil { + return nil, err + } } - inbound, err := d.client.FilterLogs(ctx, ethereum.FilterQuery{ + + inbound1155, err := d.client.FilterLogs(ctx, ethereum.FilterQuery{ FromBlock: from, ToBlock: to, - Topics: d.inboundTopics(address), + Topics: d.inboundTopicsERC1155Single(address), }) if err != nil { return nil, err } - logs := append(outbound, inbound...) + + logs := concatLogs(outbound, inboundOrMixed, inbound1155) + if len(logs) == 0 { + log.Debug("no logs found for account") continue } @@ -458,7 +504,22 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro "from", from, "to", to, "headers", len(headers)) } } + log.Debug("get erc20 transfers in range end", "chainID", d.client.NetworkID(), "from", from, "to", to, "headers", len(headers), "took", time.Since(start)) return headers, nil } + +func concatLogs(slices ...[]types.Log) []types.Log { + var totalLen int + for _, s := range slices { + totalLen += len(s) + } + tmp := make([]types.Log, totalLen) + var i int + for _, s := range slices { + i += copy(tmp[i:], s) + } + + return tmp +}