Parse ERC20 transfer from tx payload data
parent
7edea80209
commit
2e9f87e39d
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue