feat(wallet): Added detection of ERC1155 SingleTransfer events

This commit is contained in:
Ivan Belyakov 2023-09-22 18:09:14 +02:00 committed by IvanBelyakoff
parent a584ab086a
commit 57e370e7b9
5 changed files with 342 additions and 71 deletions

View File

@ -20,20 +20,24 @@ type EventType string
const ( const (
// Transaction types // Transaction types
EthTransfer Type = "eth" EthTransfer Type = "eth"
Erc20Transfer Type = "erc20" Erc20Transfer Type = "erc20"
Erc721Transfer Type = "erc721" Erc721Transfer Type = "erc721"
UniswapV2Swap Type = "uniswapV2Swap" Erc1155SingleTransfer Type = "erc1155"
UniswapV3Swap Type = "uniswapV3Swap" Erc1155BatchTransfer Type = "erc1155"
HopBridgeFrom Type = "HopBridgeFrom" UniswapV2Swap Type = "uniswapV2Swap"
HopBridgeTo Type = "HopBridgeTo" UniswapV3Swap Type = "uniswapV3Swap"
unknownTransaction Type = "unknown" HopBridgeFrom Type = "HopBridgeFrom"
HopBridgeTo Type = "HopBridgeTo"
unknownTransaction Type = "unknown"
// Event types // Event types
WETHDepositEventType EventType = "wethDepositEvent" WETHDepositEventType EventType = "wethDepositEvent"
WETHWithdrawalEventType EventType = "wethWithdrawalEvent" WETHWithdrawalEventType EventType = "wethWithdrawalEvent"
Erc20TransferEventType EventType = "erc20Event" Erc20TransferEventType EventType = "erc20Event"
Erc721TransferEventType EventType = "erc721Event" Erc721TransferEventType EventType = "erc721Event"
Erc1155TransferSingleEventType EventType = "erc1155SingleEvent"
Erc1155TransferBatchEventType EventType = "erc1155BatchEvent"
UniswapV2SwapEventType EventType = "uniswapV2SwapEvent" UniswapV2SwapEventType EventType = "uniswapV2SwapEvent"
UniswapV3SwapEventType EventType = "uniswapV3SwapEvent" UniswapV3SwapEventType EventType = "uniswapV3SwapEvent"
HopBridgeTransferSentToL2EventType EventType = "hopBridgeTransferSentToL2Event" 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, uint256 value)
// Transfer (index_topic_1 address from, index_topic_2 address to, index_topic_3 uint256 tokenId) // 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 erc20TransferEventIndexedParameters = 3 // signature, from, to
erc721TransferEventIndexedParameters = 4 // signature, from, to, tokenId 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) // 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 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) wethDepositEventSignatureHash := GetEventSignatureHash(wethDepositEventSignature)
wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature) wethWithdrawalEventSignatureHash := GetEventSignatureHash(wethWithdrawalEventSignature)
erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature) erc20_721TransferEventSignatureHash := GetEventSignatureHash(Erc20_721TransferEventSignature)
erc1155TransferSingleEventSignatureHash := GetEventSignatureHash(Erc1155TransferSingleEventSignature)
erc1155TransferBatchEventSignatureHash := GetEventSignatureHash(Erc1155TransferBatchEventSignature)
uniswapV2SwapEventSignatureHash := GetEventSignatureHash(uniswapV2SwapEventSignature) uniswapV2SwapEventSignatureHash := GetEventSignatureHash(uniswapV2SwapEventSignature)
uniswapV3SwapEventSignatureHash := GetEventSignatureHash(uniswapV3SwapEventSignature) uniswapV3SwapEventSignatureHash := GetEventSignatureHash(uniswapV3SwapEventSignature)
hopBridgeTransferSentToL2EventSignatureHash := GetEventSignatureHash(hopBridgeTransferSentToL2EventSignature) hopBridgeTransferSentToL2EventSignatureHash := GetEventSignatureHash(hopBridgeTransferSentToL2EventSignature)
@ -99,6 +108,10 @@ func GetEventType(log *types.Log) EventType {
case erc721TransferEventIndexedParameters: case erc721TransferEventIndexedParameters:
return Erc721TransferEventType return Erc721TransferEventType
} }
case erc1155TransferSingleEventSignatureHash:
return Erc1155TransferSingleEventType
case erc1155TransferBatchEventSignatureHash:
return Erc1155TransferBatchEventType
case uniswapV2SwapEventSignatureHash: case uniswapV2SwapEventSignatureHash:
return UniswapV2SwapEventType return UniswapV2SwapEventType
case uniswapV3SwapEventSignatureHash: case uniswapV3SwapEventSignatureHash:
@ -123,6 +136,10 @@ func EventTypeToSubtransactionType(eventType EventType) Type {
return Erc20Transfer return Erc20Transfer
case Erc721TransferEventType: case Erc721TransferEventType:
return Erc721Transfer return Erc721Transfer
case Erc1155TransferSingleEventType:
return Erc1155SingleTransfer
case Erc1155TransferBatchEventType:
return Erc1155BatchTransfer
case UniswapV2SwapEventType: case UniswapV2SwapEventType:
return UniswapV2Swap return UniswapV2Swap
case UniswapV3SwapEventType: case UniswapV3SwapEventType:
@ -149,7 +166,12 @@ func GetFirstEvent(logs []*types.Log) (EventType, *types.Log) {
func IsTokenTransfer(logs []*types.Log) bool { func IsTokenTransfer(logs []*types.Log) bool {
eventType, _ := GetFirstEvent(logs) 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) { 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) { func ParseErc20TransferLog(ethlog *types.Log) (from, to common.Address, amount *big.Int) {
amount = new(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) log.Warn("not enough topics for erc20 transfer", "topics", ethlog.Topics)
return 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) { func ParseErc721TransferLog(ethlog *types.Log) (from, to common.Address, tokenID *big.Int) {
tokenID = new(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) log.Warn("not enough topics for erc721 transfer", "topics", ethlog.Topics)
return return
} }
@ -248,6 +270,37 @@ func ParseErc721TransferLog(ethlog *types.Log) (from, to common.Address, tokenID
return 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) { 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) amount0In = new(big.Int)
amount1In = new(big.Int) amount1In = new(big.Int)
@ -502,6 +555,14 @@ func ExtractTokenIdentity(dbEntryType Type, log *types.Log, tx *types.Transactio
txTokenID = tokenID txTokenID = tokenID
txFrom = &from txFrom = &from
txTo = &to 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 return

View File

@ -116,9 +116,10 @@ type erc20HistoricalCommand struct {
chainClient chain.ClientInterface chainClient chain.ClientInterface
feed *event.Feed feed *event.Feed
iterator *IterativeDownloader iterator *IterativeDownloader
to *big.Int to *big.Int
from *big.Int from *big.Int
foundHeaders []*DBHeader foundHeaders []*DBHeader
} }
@ -166,7 +167,7 @@ func (c *erc20HistoricalCommand) Run(ctx context.Context) (err error) {
for !c.iterator.Finished() { for !c.iterator.Finished() {
headers, _, _, err := c.iterator.Next(ctx) headers, _, _, err := c.iterator.Next(ctx)
if err != nil { 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 return err
} }
c.foundHeaders = append(c.foundHeaders, headers...) c.foundHeaders = append(c.foundHeaders, headers...)

View File

@ -281,6 +281,7 @@ func (c *findBlocksCommand) Run(parent context.Context) (err error) {
break 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 { 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) log.Debug("ERC20 tail should be checked", "initial from", c.fromBlockNumber, "actual from", from, "first ETH block", c.startBlockNumber)
c.reachedETHHistoryStart = true c.reachedETHHistoryStart = true

View File

@ -11,6 +11,8 @@ import (
"time" "time"
"github.com/stretchr/testify/mock" "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/stretchr/testify/require"
"github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum"
@ -25,6 +27,7 @@ import (
"github.com/status-im/status-go/rpc/chain" "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/async"
"github.com/status-im/status-go/services/wallet/balance" "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/t/helpers"
"github.com/status-im/status-go/params" "github.com/status-im/status-go/params"
@ -39,17 +42,19 @@ import (
type TestClient struct { type TestClient struct {
t *testing.T t *testing.T
// [][block, newBalance, nonceDiff] // [][block, newBalance, nonceDiff]
balances [][]int balances [][]int
outgoingERC20Transfers []testERC20Transfer outgoingERC20Transfers []testERC20Transfer
incomingERC20Transfers []testERC20Transfer incomingERC20Transfers []testERC20Transfer
balanceHistory map[uint64]*big.Int outgoingERC1155SingleTransfers []testERC20Transfer
tokenBalanceHistory map[common.Address]map[uint64]*big.Int incomingERC1155SingleTransfers []testERC20Transfer
nonceHistory map[uint64]uint64 balanceHistory map[uint64]*big.Int
traceAPICalls bool tokenBalanceHistory map[common.Address]map[uint64]*big.Int
printPreparedData bool nonceHistory map[uint64]uint64
rw sync.RWMutex traceAPICalls bool
callsCounter map[string]int printPreparedData bool
currentBlock uint64 rw sync.RWMutex
callsCounter map[string]int
currentBlock uint64
} }
func (tc *TestClient) incCounter(method string) { func (tc *TestClient) incCounter(method string) {
@ -126,11 +131,36 @@ func (tc *TestClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([
if tc.traceAPICalls { if tc.traceAPICalls {
tc.t.Log("FilterLogs") tc.t.Log("FilterLogs")
} }
//checking only ERC20 for now
incomingAddress := q.Topics[len(q.Topics)-1] // We do not verify addresses for now
allTransfers := tc.incomingERC20Transfers allTransfers := []testERC20Transfer{}
if len(incomingAddress) == 0 { signatures := q.Topics[0]
allTransfers = tc.outgoingERC20Transfers 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{} logs := []types.Log{}
@ -341,6 +371,15 @@ func (tc *TestClient) prepareTokenBalanceHistory(toBlock int) {
transfersPerToken[transfer.address] = append(transfersPerToken[transfer.address], transfer) 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{} tc.tokenBalanceHistory = map[common.Address]map[uint64]*big.Int{}
for token, transfers := range transfersPerToken { for token, transfers := range transfersPerToken {
@ -539,16 +578,18 @@ type testERC20Transfer struct {
} }
type findBlockCase struct { type findBlockCase struct {
balanceChanges [][]int balanceChanges [][]int
ERC20BalanceChanges [][]int ERC20BalanceChanges [][]int
fromBlock int64 fromBlock int64
toBlock int64 toBlock int64
rangeSize int rangeSize int
expectedBlocksFound int expectedBlocksFound int
outgoingERC20Transfers []testERC20Transfer outgoingERC20Transfers []testERC20Transfer
incomingERC20Transfers []testERC20Transfer incomingERC20Transfers []testERC20Transfer
label string outgoingERC1155SingleTransfers []testERC20Transfer
expectedCalls map[string]int incomingERC1155SingleTransfers []testERC20Transfer
label string
expectedCalls map[string]int
} }
func transferInEachBlock() [][]int { func transferInEachBlock() [][]int {
@ -577,11 +618,7 @@ func getCases() []findBlockCase {
toBlock: 100, toBlock: 100,
expectedBlocksFound: 6, expectedBlocksFound: 6,
expectedCalls: map[string]int{ expectedCalls: map[string]int{
"BalanceAt": 27, "FilterLogs": 15,
//TODO(rasom) NonceAt is flaky, sometimes it's called 18 times, sometimes 17
//to be investigated
//"NonceAt": 18,
"FilterLogs": 10,
"HeaderByNumber": 5, "HeaderByNumber": 5,
}, },
} }
@ -593,7 +630,7 @@ func getCases() []findBlockCase {
expectedCalls: map[string]int{ expectedCalls: map[string]int{
"BalanceAt": 101, "BalanceAt": 101,
"NonceAt": 0, "NonceAt": 0,
"FilterLogs": 10, "FilterLogs": 15,
"HeaderByNumber": 100, "HeaderByNumber": 100,
}, },
} }
@ -660,7 +697,7 @@ func getCases() []findBlockCase {
{big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, {big.NewInt(6), tokenTXXAddress, big.NewInt(1)},
}, },
expectedCalls: map[string]int{ expectedCalls: map[string]int{
"FilterLogs": 3, "FilterLogs": 5,
"CallContract": 3, "CallContract": 3,
}, },
} }
@ -677,8 +714,7 @@ func getCases() []findBlockCase {
{big.NewInt(6), tokenTXXAddress, big.NewInt(1)}, {big.NewInt(6), tokenTXXAddress, big.NewInt(1)},
}, },
expectedCalls: map[string]int{ expectedCalls: map[string]int{
"FilterLogs": 3, "FilterLogs": 5,
"CallContract": 5,
}, },
} }
@ -690,12 +726,115 @@ func getCases() []findBlockCase {
label: "single block range, no transactions", label: "single block range, no transactions",
expectedCalls: map[string]int{ expectedCalls: map[string]int{
// only two requests to check the range for incoming ERC20 // only two requests to check the range for incoming ERC20
"FilterLogs": 2, "FilterLogs": 3,
// no contract calls as ERC20 is not checked // no contract calls as ERC20 is not checked
"CallContract": 0, "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, case1)
cases = append(cases, case100transfers) cases = append(cases, case100transfers)
cases = append(cases, case3) cases = append(cases, case3)
@ -707,6 +846,12 @@ func getCases() []findBlockCase {
cases = append(cases, case8emptyHistoryWithERC20Transfers) cases = append(cases, case8emptyHistoryWithERC20Transfers)
cases = append(cases, case9emptyHistoryWithERC20Transfers) cases = append(cases, case9emptyHistoryWithERC20Transfers)
cases = append(cases, case10) 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) //cases = append([]findBlockCase{}, case10)
@ -718,7 +863,7 @@ var tokenTXYAddress = common.HexToAddress("0x73211")
func TestFindBlocksCommand(t *testing.T) { func TestFindBlocksCommand(t *testing.T) {
for idx, testCase := range getCases() { for idx, testCase := range getCases() {
t.Log("case #", idx) t.Log("case #", idx+1)
ctx := context.Background() ctx := context.Background()
group := async.NewGroup(ctx) group := async.NewGroup(ctx)
@ -728,11 +873,13 @@ func TestFindBlocksCommand(t *testing.T) {
wdb := NewDB(db) wdb := NewDB(db)
tc := &TestClient{ tc := &TestClient{
t: t, t: t,
balances: testCase.balanceChanges, balances: testCase.balanceChanges,
outgoingERC20Transfers: testCase.outgoingERC20Transfers, outgoingERC20Transfers: testCase.outgoingERC20Transfers,
incomingERC20Transfers: testCase.incomingERC20Transfers, incomingERC20Transfers: testCase.incomingERC20Transfers,
callsCounter: map[string]int{}, outgoingERC1155SingleTransfers: testCase.outgoingERC1155SingleTransfers,
incomingERC1155SingleTransfers: testCase.incomingERC1155SingleTransfers,
callsCounter: map[string]int{},
} }
//tc.traceAPICalls = true //tc.traceAPICalls = true
//tc.printPreparedData = true //tc.printPreparedData = true

View File

@ -240,11 +240,13 @@ func NewERC20TransfersDownloader(client chain.ClientInterface, accounts []common
signature := w_common.GetEventSignatureHash(w_common.Erc20_721TransferEventSignature) signature := w_common.GetEventSignatureHash(w_common.Erc20_721TransferEventSignature)
return &ERC20TransfersDownloader{ return &ERC20TransfersDownloader{
client: client, client: client,
accounts: accounts, accounts: accounts,
incomingOnly: incomingOnly, signature: signature,
signature: signature, incomingOnly: incomingOnly,
signer: signer, 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 incomingOnly bool
// hash of the Transfer event signature // 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 is used to derive tx sender from tx signature
signer types.Signer signer types.Signer
@ -279,6 +283,14 @@ func (d *ERC20TransfersDownloader) outboundTopics(address common.Address) [][]co
return [][]common.Hash{{d.signature}, {d.paddedAddress(address)}, {}} 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) { func (d *ETHDownloader) subTransactionsFromTransactionHash(parent context.Context, txHash common.Hash, address common.Address) ([]Transfer, error) {
ctx, cancel := context.WithTimeout(parent, 3*time.Second) ctx, cancel := context.WithTimeout(parent, 3*time.Second)
tx, _, err := d.chainClient.TransactionByHash(ctx, txHash) 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)) rst := make([]Transfer, 0, len(receipt.Logs))
for _, log := range receipt.Logs { for _, log := range receipt.Logs {
eventType := w_common.GetEventType(log) 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 // Other types of events get always added
mustAppend := false mustAppend := false
switch eventType { switch eventType {
@ -335,6 +347,11 @@ func (d *ETHDownloader) subTransactionsFromTransactionData(tx *types.Transaction
if trFrom == address || trTo == address { if trFrom == address || trTo == address {
mustAppend = true 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: case w_common.UniswapV2SwapEventType, w_common.UniswapV3SwapEventType:
mustAppend = true mustAppend = true
case w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType: case w_common.HopBridgeTransferSentToL2EventType, w_common.HopBridgeTransferFromL1CompletedEventType:
@ -382,6 +399,13 @@ func (d *ERC20TransfersDownloader) blocksFromLogs(parent context.Context, logs [
return nil, err 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{ header := &DBHeader{
Number: big.NewInt(int64(l.BlockNumber)), Number: big.NewInt(int64(l.BlockNumber)),
Hash: l.BlockHash, Hash: l.BlockHash,
@ -392,7 +416,7 @@ func (d *ERC20TransfersDownloader) blocksFromLogs(parent context.Context, logs [
ID: id, ID: id,
From: address, From: address,
Loaded: false, Loaded: false,
Type: w_common.Erc20Transfer, Type: logType,
Log: &l, Log: &l,
BaseGasFees: baseGasFee, BaseGasFees: baseGasFee,
}}, }},
@ -422,6 +446,7 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro
var err error var err error
for _, address := range d.accounts { for _, address := range d.accounts {
outbound := []types.Log{} outbound := []types.Log{}
var inboundOrMixed []types.Log // inbound ERC20 or outbound ERC1155 share the same signature for our purposes
if !d.incomingOnly { if !d.incomingOnly {
outbound, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{ outbound, err = d.client.FilterLogs(ctx, ethereum.FilterQuery{
FromBlock: from, FromBlock: from,
@ -431,17 +456,38 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro
if err != nil { if err != nil {
return nil, err 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, FromBlock: from,
ToBlock: to, ToBlock: to,
Topics: d.inboundTopics(address), Topics: d.inboundTopicsERC1155Single(address),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
logs := append(outbound, inbound...)
logs := concatLogs(outbound, inboundOrMixed, inbound1155)
if len(logs) == 0 { if len(logs) == 0 {
log.Debug("no logs found for account")
continue continue
} }
@ -458,7 +504,22 @@ func (d *ERC20TransfersDownloader) GetHeadersInRange(parent context.Context, fro
"from", from, "to", to, "headers", len(headers)) "from", from, "to", to, "headers", len(headers))
} }
} }
log.Debug("get erc20 transfers in range end", "chainID", d.client.NetworkID(), log.Debug("get erc20 transfers in range end", "chainID", d.client.NetworkID(),
"from", from, "to", to, "headers", len(headers), "took", time.Since(start)) "from", from, "to", to, "headers", len(headers), "took", time.Since(start))
return headers, nil 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
}