Parse ERC20 transfer from tx payload data

ethereum
Martin Boehm 2018-12-18 13:14:07 +01:00
parent 7edea80209
commit 2e9f87e39d
4 changed files with 81 additions and 6 deletions

View File

@ -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()

View File

@ -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)
}
})
}
}

View File

@ -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
}

View File

@ -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)