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 (
|
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
|
||||||
|
|
|
@ -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...)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue