feat(wallet): Added detection of ERC1155 SingleTransfer events
This commit is contained in:
parent
a584ab086a
commit
57e370e7b9
|
@ -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
|
||||
|
|
|
@ -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...)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue