diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go index 003ee4e2..3786d3a6 100644 --- a/bchain/coins/eth/erc20.go +++ b/bchain/coins/eth/erc20.go @@ -30,7 +30,8 @@ var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":" {"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` // doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event -const erc20EventTransferSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +const erc20TransferMethodSignature = "0xa9059cbb" +const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" const erc20NameSignature = "0x06fdde03" const erc20SymbolSignature = "0x95d89b41" const erc20DecimalsSignature = "0x313ce567" @@ -49,7 +50,12 @@ var cachedContractsMux sync.Mutex func addressFromPaddedHex(s string) (string, error) { var t big.Int - _, ok := t.SetString(s, 0) + var ok bool + if has0xPrefix(s) { + _, ok = t.SetString(s[2:], 16) + } else { + _, ok = t.SetString(s, 16) + } if !ok { return "", errors.New("Data is not a number") } @@ -60,7 +66,7 @@ func addressFromPaddedHex(s string) (string, error) { func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) { var r []Erc20Transfer for _, l := range logs { - if len(l.Topics) == 3 && l.Topics[0] == erc20EventTransferSignature { + if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature { var t big.Int _, ok := t.SetString(l.Data, 0) if !ok { @@ -85,6 +91,28 @@ func erc20GetTransfersFromLog(logs []*rpcLog) ([]Erc20Transfer, error) { return r, nil } +func erc20GetTransfersFromTx(tx *rpcTransaction) ([]Erc20Transfer, error) { + var r []Erc20Transfer + if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) { + to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)]) + if err != nil { + return nil, err + } + var t big.Int + _, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16) + if !ok { + return nil, errors.New("Data is not a number") + } + r = append(r, Erc20Transfer{ + Contract: strings.ToLower(tx.To), + From: strings.ToLower(tx.From), + To: strings.ToLower(to), + Tokens: t, + }) + } + return r, nil +} + func (b *EthereumRPC) ethCall(data, to string) (string, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() diff --git a/bchain/coins/eth/erc20_test.go b/bchain/coins/eth/erc20_test.go index 2b0717fe..215f9a5f 100644 --- a/bchain/coins/eth/erc20_test.go +++ b/bchain/coins/eth/erc20_test.go @@ -3,6 +3,7 @@ package eth import ( + "blockbook/tests/dbtestdata" fmt "fmt" "math/big" "strings" @@ -137,3 +138,45 @@ func TestErc20_parseErc20StringProperty(t *testing.T) { }) } } + +func TestErc20_erc20GetTransfersFromTx(t *testing.T) { + p := NewEthereumParser(1) + b := dbtestdata.GetTestEthereumTypeBlock1(p) + bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16) + tests := []struct { + name string + args *rpcTransaction + want []Erc20Transfer + }{ + { + name: "0", + args: (b.Txs[0].CoinSpecificData.(completeTransaction)).Tx, + want: []Erc20Transfer{}, + }, + { + name: "1", + args: (b.Txs[1].CoinSpecificData.(completeTransaction)).Tx, + want: []Erc20Transfer{ + { + Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", + From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", + To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", + Tokens: *bn, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := erc20GetTransfersFromTx(tt.args) + if err != nil { + t.Errorf("erc20GetTransfersFromTx error = %v", err) + return + } + // the addresses could have different case + if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) { + t.Errorf("erc20GetTransfersFromTx = %+v, want %+v", got, tt.want) + } + }) + } +} diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 82a87604..6ed8f3e9 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -403,8 +403,12 @@ func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) { var r []Erc20Transfer var err error csd, ok := tx.CoinSpecificData.(completeTransaction) - if ok && csd.Receipt != nil { - r, err = erc20GetTransfersFromLog(csd.Receipt.Logs) + if ok { + if csd.Receipt != nil { + r, err = erc20GetTransfersFromLog(csd.Receipt.Logs) + } else { + r, err = erc20GetTransfersFromTx(csd.Tx) + } if err != nil { return nil, err } diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 347edd4e..fae51546 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -430,7 +430,7 @@ func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]* err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": blockNumber, "toBlock": blockNumber, - "topics": []string{erc20EventTransferSignature}, + "topics": []string{erc20TransferEventSignature}, }) if err != nil { return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)