Process ERC721 and ERC1155 tokens

This commit is contained in:
Martin Boehm
2022-01-18 22:19:49 +01:00
committed by Martin
parent 45a53e41a1
commit 9a0790a71d
20 changed files with 2232 additions and 950 deletions

View File

@@ -138,11 +138,20 @@ type Vout struct {
// TokenType specifies type of token
type TokenType string
// ERC20TokenType is Ethereum ERC20 token
const ERC20TokenType TokenType = "ERC20"
// Token types
const (
// Ethereum token types
ERC20TokenType TokenType = "ERC20"
ERC771TokenType TokenType = "ERC721"
ERC1155TokenType TokenType = "ERC1155"
// XPUBAddressTokenType is address derived from xpub
const XPUBAddressTokenType TokenType = "XPUBAddress"
// XPUBAddressTokenType is address derived from xpub
XPUBAddressTokenType TokenType = "XPUBAddress"
)
// TokenTypeMap maps bchain.TokenTransferType to TokenType
// the map must match all bchain.TokenTransferTypes to avoid index out of range panic
var TokenTypeMap []TokenType = []TokenType{ERC20TokenType, ERC771TokenType, ERC1155TokenType}
// Token contains info about tokens held by an address
type Token struct {
@@ -159,16 +168,23 @@ type Token struct {
ContractIndex string `json:"-"`
}
// TokenTransferValues contains values for ERC1155 contract
type TokenTransferValues struct {
Id *Amount `json:"id,omitempty"`
Value *Amount `json:"value,omitempty"`
}
// TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct {
Type TokenType `json:"type"`
From string `json:"from"`
To string `json:"to"`
Token string `json:"token"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Value *Amount `json:"value"`
Type TokenType `json:"type"`
From string `json:"from"`
To string `json:"to"`
Token string `json:"token"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Value *Amount `json:"value,omitempty"`
Values []TokenTransferValues `json:"values,omitempty"`
}
// EthereumSpecific contains ethereum specific transaction data

View File

@@ -255,11 +255,11 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
}
pValInSat = &valInSat
} else if w.chainType == bchain.ChainEthereumType {
ets, err := w.chainParser.EthereumTypeGetErc20FromTx(bchainTx)
tokenTransfers, err := w.chainParser.EthereumTypeGetTokenTransfersFromTx(bchainTx)
if err != nil {
glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx)
glog.Errorf("GetTokenTransfersFromTx error %v, %v", err, bchainTx)
}
tokens = w.getTokensFromErc20(ets)
tokens = w.getEthereumTokensTransfers(tokenTransfers)
ethTxData := eth.GetEthereumTxData(bchainTx)
// mempool txs do not have fees yet
if ethTxData.GasUsed != nil {
@@ -381,7 +381,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
if len(mempoolTx.Vout) > 0 {
valOutSat = mempoolTx.Vout[0].ValueSat
}
tokens = w.getTokensFromErc20(mempoolTx.Erc20)
tokens = w.getEthereumTokensTransfers(mempoolTx.TokenTransfers)
ethTxData := eth.GetEthereumTxDataFromSpecificData(mempoolTx.CoinSpecificData)
ethSpecific = &EthereumSpecific{
GasLimit: ethTxData.GasLimit,
@@ -410,29 +410,30 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
return r, nil
}
func (w *Worker) getTokensFromErc20(erc20 []bchain.Erc20Transfer) []TokenTransfer {
tokens := make([]TokenTransfer, len(erc20))
for i := range erc20 {
e := &erc20[i]
cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract)
func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []TokenTransfer {
sort.Sort(transfers)
tokens := make([]TokenTransfer, len(transfers))
for i := range transfers {
t := transfers[i]
cd, err := w.chainParser.GetAddrDescFromAddress(t.Contract)
if err != nil {
glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract)
glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, t.Contract)
continue
}
erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd)
if err != nil {
glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract)
glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, t.Contract)
}
if erc20c == nil {
erc20c = &bchain.Erc20Contract{Name: e.Contract}
erc20c = &bchain.Erc20Contract{Name: t.Contract}
}
tokens[i] = TokenTransfer{
Type: ERC20TokenType,
Token: e.Contract,
From: e.From,
To: e.To,
Type: TokenTypeMap[t.Type],
Token: t.Contract,
From: t.From,
To: t.To,
Decimals: erc20c.Decimals,
Value: (*Amount)(&e.Tokens),
Value: (*Amount)(&t.Value),
Name: erc20c.Name,
Symbol: erc20c.Symbol,
}

View File

@@ -300,7 +300,7 @@ func (p *BaseParser) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor,
return nil, errors.New("Not supported")
}
// EthereumTypeGetErc20FromTx is unsupported
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
// EthereumTypeGetTokenTransfersFromTx is unsupported
func (p *BaseParser) EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error) {
return nil, errors.New("Not supported")
}

View File

@@ -0,0 +1,329 @@
package eth
import (
"context"
"math/big"
"strings"
"sync"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
)
const erc20TransferMethodSignature = "0xa9059cbb" // transfer(address,uint256)
const erc721TransferFromMethodSignature = "0x23b872dd" // transferFrom(address,address,uint256)
const erc721SafeTransferFromMethodSignature = "0x42842e0e" // safeTransferFrom(address,address,uint256)
const erc721SafeTransferFromWithDataMethodSignature = "0xb88d4fde" // safeTransferFrom(address,address,uint256,bytes)
const tokenTransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
const tokenERC1155TransferSingleEventSignature = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"
const tokenERC1155TransferBatchEventSignature = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb"
const contractNameSignature = "0x06fdde03"
const contractSymbolSignature = "0x95d89b41"
const contractDecimalsSignature = "0x313ce567"
const contractBalanceOf = "0x70a08231"
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) {
var t big.Int
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")
}
a := ethcommon.BigToAddress(&t)
return a.String(), nil
}
func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) {
tl := len(l.Topics)
var ttt bchain.TokenTransferType
var value big.Int
if tl == 3 {
ttt = bchain.ERC20
_, ok := value.SetString(l.Data, 0)
if !ok {
return nil, errors.New("ERC20 log Data is not a number")
}
} else if tl == 4 {
ttt = bchain.ERC721
_, ok := value.SetString(l.Topics[3], 0)
if !ok {
return nil, errors.New("ERC721 log Topics[3] is not a number")
}
} else {
return nil, nil
}
from, err := addressFromPaddedHex(l.Topics[1])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(l.Topics[2])
if err != nil {
return nil, err
}
return &bchain.TokenTransfer{
Type: ttt,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
Value: value,
}, nil
}
func processERC1155TransferSingleEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) {
from, err := addressFromPaddedHex(l.Topics[2])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(l.Topics[3])
if err != nil {
return nil, err
}
var id, value big.Int
data := l.Data
if has0xPrefix(l.Data) {
data = data[2:]
}
_, ok := id.SetString(data[:64], 16)
if !ok {
return nil, errors.New("ERC1155 log Data id is not a number")
}
_, ok = value.SetString(data[64:128], 16)
if !ok {
return nil, errors.New("ERC1155 log Data value is not a number")
}
return &bchain.TokenTransfer{
Type: bchain.ERC1155,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
IdValues: []bchain.TokenTransferIdValue{{Id: id, Value: value}},
}, nil
}
func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) {
from, err := addressFromPaddedHex(l.Topics[2])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(l.Topics[3])
if err != nil {
return nil, err
}
data := l.Data
if has0xPrefix(l.Data) {
data = data[2:]
}
var b big.Int
_, ok := b.SetString(data[:64], 16)
if !ok || !b.IsInt64() {
return nil, errors.New("ERC1155 TransferBatch, not a number")
}
offsetIds := int(b.Int64()) * 2
_, ok = b.SetString(data[64:128], 16)
if !ok || !b.IsInt64() {
return nil, errors.New("ERC1155 TransferBatch, not a number")
}
offsetValues := int(b.Int64()) * 2
_, ok = b.SetString(data[offsetIds:offsetIds+64], 16)
if !ok || !b.IsInt64() {
return nil, errors.New("ERC1155 TransferBatch, not a number")
}
countIds := int(b.Int64())
_, ok = b.SetString(data[offsetValues:offsetValues+64], 16)
if !ok || !b.IsInt64() {
return nil, errors.New("ERC1155 TransferBatch, not a number")
}
countValues := int(b.Int64())
if countIds != countValues {
return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match")
}
idValues := make([]bchain.TokenTransferIdValue, countValues)
for i := 0; i < countValues; i++ {
var id, value big.Int
o := offsetIds + 64 + 64*i
_, ok := id.SetString(data[o:o+64], 16)
if !ok {
return nil, errors.New("ERC1155 log Data id is not a number")
}
o = offsetValues + 64 + 64*i
_, ok = value.SetString(data[o:o+64], 16)
if !ok {
return nil, errors.New("ERC1155 log Data value is not a number")
}
idValues[i] = bchain.TokenTransferIdValue{Id: id, Value: value}
}
return &bchain.TokenTransfer{
Type: bchain.ERC1155,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
IdValues: idValues,
}, nil
}
func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) {
var r bchain.TokenTransfers
var tt *bchain.TokenTransfer
var err error
for _, l := range logs {
tl := len(l.Topics)
if tl > 0 {
signature := l.Topics[0]
if signature == tokenTransferEventSignature {
tt, err = processTransferEvent(l)
} else if signature == tokenERC1155TransferSingleEventSignature && tl == 4 {
tt, err = processERC1155TransferSingleEvent(l)
} else if signature == tokenERC1155TransferBatchEventSignature {
tt, err = processERC1155TransferBatchEvent(l)
} else {
continue
}
if err != nil {
return nil, err
}
if tt != nil {
r = append(r, tt)
}
}
}
return r, nil
}
func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfers, error) {
var r bchain.TokenTransfers
if len(tx.Payload) == 10+128 && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) {
to, err := addressFromPaddedHex(tx.Payload[10 : 10+64])
if err != nil {
return nil, err
}
var t big.Int
_, ok := t.SetString(tx.Payload[10+64:], 16)
if !ok {
return nil, errors.New("Data is not a number")
}
r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC20,
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(to),
Value: t,
})
} else if len(tx.Payload) >= 10+192 &&
(strings.HasPrefix(tx.Payload, erc721TransferFromMethodSignature) ||
strings.HasPrefix(tx.Payload, erc721SafeTransferFromMethodSignature) ||
strings.HasPrefix(tx.Payload, erc721SafeTransferFromWithDataMethodSignature)) {
from, err := addressFromPaddedHex(tx.Payload[10 : 10+64])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(tx.Payload[10+64 : 10+128])
if err != nil {
return nil, err
}
var t big.Int
_, ok := t.SetString(tx.Payload[10+128:10+192], 16)
if !ok {
return nil, errors.New("Data is not a number")
}
r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC721,
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
Value: t,
})
}
return r, nil
}
func (b *EthereumRPC) ethCall(data, to string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var r string
err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{
"data": data,
"to": to,
}, "latest")
if err != nil {
return "", err
}
return r, nil
}
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
cds := string(contractDesc)
cachedContractsMux.Lock()
contract, found := cachedContracts[cds]
cachedContractsMux.Unlock()
if !found {
address := EIP55Address(contractDesc)
data, err := b.ethCall(contractNameSignature, address)
if err != nil {
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
name := parseSimpleStringProperty(data)
if name != "" {
data, err = b.ethCall(contractSymbolSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseSimpleStringProperty(data)
data, err = b.ethCall(contractDecimalsSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
}
contract = &bchain.Erc20Contract{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseSimpleNumericProperty(data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
contract = nil
}
cachedContractsMux.Lock()
cachedContracts[cds] = contract
cachedContractsMux.Unlock()
}
return contract, nil
}
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
addr := EIP55Address(addrDesc)
contract := EIP55Address(contractDesc)
req := contractBalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
data, err := b.ethCall(req, contract)
if err != nil {
return nil, err
}
r := parseSimpleNumericProperty(data)
if r == nil {
return nil, errors.New("Invalid balance")
}
return r, nil
}

View File

@@ -0,0 +1,291 @@
//go:build unittest
package eth
import (
"fmt"
"math/big"
"strings"
"testing"
"github.com/trezor/blockbook/bchain"
"github.com/trezor/blockbook/tests/dbtestdata"
)
func Test_contractGetTransfersFromLog(t *testing.T) {
tests := []struct {
name string
args []*bchain.RpcLog
want bchain.TokenTransfers
wantErr bool
}{
{
name: "ERC20 transfer 1",
args: []*bchain.RpcLog{
{
Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8",
"0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2",
},
Data: "0x0000000000000000000000000000000000000000000000000000000000000123",
},
},
want: bchain.TokenTransfers{
{
Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8",
To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2",
Value: *big.NewInt(0x123),
},
},
},
{
name: "ERC20 transfer 2",
args: []*bchain.RpcLog{
{ // Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
},
Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b",
},
{ // Transfer
Address: "0xc778417e063141139fce010982780140aa0cd5ab",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
},
Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0",
},
{ // not Transfer
Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000",
},
{ // not Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b",
"0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
},
want: bchain.TokenTransfers{
{
Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
Value: *big.NewInt(0x6a8313d60b1f606b),
},
{
Contract: "0xc778417e063141139fce010982780140aa0cd5ab",
From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
Value: *big.NewInt(0x308fd0e798ac0),
},
},
},
{
name: "ERC721 transfer 1",
args: []*bchain.RpcLog{
{ // Approval
Address: "0x5689b918D34C038901870105A6C7fc24744D31eB",
Topics: []string{
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000000000000000000000000000000000000000001396",
},
Data: "0x",
},
{ // Transfer
Address: "0x5689b918D34C038901870105A6C7fc24744D31eB",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391",
"0x0000000000000000000000006a016d7eec560549ffa0fbdb7f15c2b27302087f",
"0x0000000000000000000000000000000000000000000000000000000000001396",
},
Data: "0x",
},
{ // OrdersMatched
Address: "0x7Be8076f4EA4A4AD08075C2508e481d6C946D12b",
Topics: []string{
"0xc4109843e0b7d514e4c093114b863f8e7d8d9a458c372cd51bfe526b588006c9",
"0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391",
"0x0000000000000000000000006a016d7eec560549ffa0fbdb7f15c2b27302087f",
"0x0000000000000000000000000000000000000000000000000000000000000000",
},
Data: "0x000000000000000000000000000000000000000000000000000000000000000069d3f0cc25f121f2aa96215f51ec4b4f1966f2d2ffbd3d8d8a45ad27b1c90323000000000000000000000000000000000000000000000000008e1bc9bf040000",
},
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC721,
Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB",
From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391",
To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f",
Value: *big.NewInt(0x1396),
},
},
},
{
name: "ERC1155 TransferSingle",
args: []*bchain.RpcLog{
{ // Transfer
Address: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
Topics: []string{
"0x5f9832c7244497a64c11c4a4f7597934bdf02b0361c54ad8e90091c2ce1f9e3c",
},
Data: "0x000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
},
{ // TransferSingle
Address: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
Topics: []string{
"0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62",
"0x0000000000000000000000009248a6048a58db9f0212dc7cd85ee8741128be72",
"0x000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed53",
"0x0000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec",
},
Data: "0x00000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000011",
},
{ // unknown
Address: "0x9248A6048a58db9f0212dC7CD85eE8741128be72",
Topics: []string{
"0x0b7bef9468bee71526deef3cbbded0ec1a0aa3d5a3e81eaffb0e758552b33199",
},
Data: "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec0000000000000000000000000000000000000000000000000000000000000001",
},
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC1155,
Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53",
To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec",
IdValues: []bchain.TokenTransferIdValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}},
},
},
},
{
name: "ERC1155 TransferBatch",
args: []*bchain.RpcLog{
{ // TransferBatch
Address: "0x6c42C26a081c2F509F8bb68fb7Ac3062311cCfB7",
Topics: []string{
"0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb",
"0x0000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
},
Data: "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000006f0000000000000000000000000000000000000000000000000000000000000076a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a",
},
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC1155,
Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7",
From: "0x0000000000000000000000000000000000000000",
To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
IdValues: []bchain.TokenTransferIdValue{
{Id: *big.NewInt(1776), Value: *big.NewInt(1)},
{Id: *big.NewInt(1898), Value: *big.NewInt(10)},
},
},
},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := contractGetTransfersFromLog(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("contractGetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != len(tt.want) {
t.Errorf("contractGetTransfersFromLog len not same, %+v, want %+v", got, tt.want)
}
for i := range got {
// the addresses could have different case
if strings.ToLower(fmt.Sprint(got[i])) != strings.ToLower(fmt.Sprint(tt.want[i])) {
t.Errorf("contractGetTransfersFromLog %d = %+v, want %+v", i, got[i], tt.want[i])
}
}
})
}
}
func Test_contractGetTransfersFromTx(t *testing.T) {
p := NewEthereumParser(1)
b1 := dbtestdata.GetTestEthereumTypeBlock1(p)
b2 := dbtestdata.GetTestEthereumTypeBlock2(p)
bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16)
tests := []struct {
name string
args *bchain.RpcTransaction
want bchain.TokenTransfers
}{
{
name: "no contract transfer",
args: (b1.Txs[0].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{},
},
{
name: "ERC20 transfer",
args: (b1.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{
{
Type: bchain.ERC20,
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
Value: *bn,
},
},
},
{
name: "ERC721 transferFrom",
args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{
{
Type: bchain.ERC721,
Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9",
From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081",
To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b",
Value: *big.NewInt(1),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := contractGetTransfersFromTx(tt.args)
if err != nil {
t.Errorf("contractGetTransfersFromTx error = %v", err)
return
}
if len(got) != len(tt.want) {
t.Errorf("contractGetTransfersFromTx len not same, %+v, want %+v", got, tt.want)
}
for i := range got {
// the addresses could have different case
if strings.ToLower(fmt.Sprint(got[i])) != strings.ToLower(fmt.Sprint(tt.want[i])) {
t.Errorf("contractGetTransfersFromTx %d = %+v, want %+v", i, got[i], tt.want[i])
}
}
})
}
}

View File

@@ -0,0 +1,58 @@
package eth
import (
"bytes"
"encoding/hex"
"math/big"
"unicode/utf8"
)
func parseSimpleNumericProperty(data string) *big.Int {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 64 {
data = data[:64]
}
if len(data) == 64 {
var n big.Int
_, ok := n.SetString(data, 16)
if ok {
return &n
}
}
return nil
}
func parseSimpleStringProperty(data string) string {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 128 {
n := parseSimpleNumericProperty(data[64:128])
if n != nil {
l := n.Uint64()
if l > 0 && 2*int(l) <= len(data)-128 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)
}
}
}
}
// allow string properties as UTF-8 data
b, err := hex.DecodeString(data)
if err == nil {
i := bytes.Index(b, []byte{0})
if i > 32 {
i = 32
}
if i > 0 {
b = b[:i]
}
if utf8.Valid(b) {
return string(b)
}
}
return ""
}

View File

@@ -0,0 +1,53 @@
//go:build unittest
package eth
import "testing"
func Test_parseSimpleStringProperty(t *testing.T) {
tests := []struct {
name string
args string
want string
}{
{
name: "1",
args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000",
want: "XPLODDE",
},
{
name: "2",
args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000",
want: "BitClave - Consumer Activity Token",
},
{
name: "short",
args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000",
want: "Dai Stablecoin v1.0",
},
{
name: "short2",
args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444",
want: "Dai Stablecoin v1.0 DDDDDDDDDDDD",
},
{
name: "long",
args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: "Uniswap V1",
},
{
name: "garbage",
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseSimpleStringProperty(tt.args)
// the addresses could have different case
if got != tt.want {
t.Errorf("parseSimpleStringProperty = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,245 +0,0 @@
package eth
import (
"bytes"
"context"
"encoding/hex"
"math/big"
"strings"
"sync"
"unicode/utf8"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
)
var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"},
{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"},
{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"},
{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"},
{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"},
{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"},
{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"},
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"},
{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"},
{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"},
{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"},
{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"},
{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"},
{"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 erc20TransferMethodSignature = "0xa9059cbb"
const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
const erc20NameSignature = "0x06fdde03"
const erc20SymbolSignature = "0x95d89b41"
const erc20DecimalsSignature = "0x313ce567"
const erc20BalanceOf = "0x70a08231"
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) {
var t big.Int
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")
}
a := ethcommon.BigToAddress(&t)
return a.String(), nil
}
func erc20GetTransfersFromLog(logs []*bchain.RpcLog) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
for _, l := range logs {
if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature {
var t big.Int
_, ok := t.SetString(l.Data, 0)
if !ok {
return nil, errors.New("Data is not a number")
}
from, err := addressFromPaddedHex(l.Topics[1])
if err != nil {
return nil, err
}
to, err := addressFromPaddedHex(l.Topics[2])
if err != nil {
return nil, err
}
r = append(r, bchain.Erc20Transfer{
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
Tokens: t,
})
}
}
return r, nil
}
func erc20GetTransfersFromTx(tx *bchain.RpcTransaction) ([]bchain.Erc20Transfer, error) {
var r []bchain.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, bchain.Erc20Transfer{
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(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()
var r string
err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{
"data": data,
"to": to,
}, "latest")
if err != nil {
return "", err
}
return r, nil
}
func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 64 {
data = data[:64]
}
if len(data) == 64 {
var n big.Int
_, ok := n.SetString(data, 16)
if ok {
return &n
}
}
if glog.V(1) {
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
}
return nil
}
func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string {
if has0xPrefix(data) {
data = data[2:]
}
if len(data) > 128 {
n := parseErc20NumericProperty(contractDesc, data[64:128])
if n != nil {
l := n.Uint64()
if l > 0 && 2*int(l) <= len(data)-128 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)
}
}
}
}
// allow string properties as UTF-8 data
b, err := hex.DecodeString(data)
if err == nil {
i := bytes.Index(b, []byte{0})
if i > 32 {
i = 32
}
if i > 0 {
b = b[:i]
}
if utf8.Valid(b) {
return string(b)
}
}
if glog.V(1) {
glog.Warning("Cannot parse '", data, "' for contract ", contractDesc)
}
return ""
}
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
cds := string(contractDesc)
cachedContractsMux.Lock()
contract, found := cachedContracts[cds]
cachedContractsMux.Unlock()
if !found {
address := EIP55Address(contractDesc)
data, err := b.ethCall(erc20NameSignature, address)
if err != nil {
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
name := parseErc20StringProperty(contractDesc, data)
if name != "" {
data, err = b.ethCall(erc20SymbolSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseErc20StringProperty(contractDesc, data)
data, err = b.ethCall(erc20DecimalsSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
}
contract = &bchain.Erc20Contract{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseErc20NumericProperty(contractDesc, data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
contract = nil
}
cachedContractsMux.Lock()
cachedContracts[cds] = contract
cachedContractsMux.Unlock()
}
return contract, nil
}
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
addr := EIP55Address(addrDesc)
contract := EIP55Address(contractDesc)
req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:]
data, err := b.ethCall(req, contract)
if err != nil {
return nil, err
}
r := parseErc20NumericProperty(contractDesc, data)
if r == nil {
return nil, errors.New("Invalid balance")
}
return r, nil
}

View File

@@ -1,204 +0,0 @@
//go:build unittest
package eth
import (
"fmt"
"math/big"
"strings"
"testing"
"github.com/trezor/blockbook/bchain"
"github.com/trezor/blockbook/tests/dbtestdata"
)
func TestErc20_erc20GetTransfersFromLog(t *testing.T) {
tests := []struct {
name string
args []*bchain.RpcLog
want []bchain.Erc20Transfer
wantErr bool
}{
{
name: "1",
args: []*bchain.RpcLog{
{
Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8",
"0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2",
},
Data: "0x0000000000000000000000000000000000000000000000000000000000000123",
},
},
want: []bchain.Erc20Transfer{
{
Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96",
From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8",
To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2",
Tokens: *big.NewInt(0x123),
},
},
},
{
name: "2",
args: []*bchain.RpcLog{
{ // Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
},
Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b",
},
{ // Transfer
Address: "0xc778417e063141139fce010982780140aa0cd5ab",
Topics: []string{
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
},
Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0",
},
{ // not Transfer
Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000",
},
{ // not Transfer
Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
Topics: []string{
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3",
"0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b",
"0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa",
},
Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
},
},
want: []bchain.Erc20Transfer{
{
Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11",
From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
Tokens: *big.NewInt(0x6a8313d60b1f606b),
},
{
Contract: "0xc778417e063141139fce010982780140aa0cd5ab",
From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d",
To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed",
Tokens: *big.NewInt(0x308fd0e798ac0),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := erc20GetTransfersFromLog(tt.args)
if (err != nil) != tt.wantErr {
t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr)
return
}
// the addresses could have different case
if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) {
t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want)
}
})
}
}
func TestErc20_parseErc20StringProperty(t *testing.T) {
tests := []struct {
name string
args string
want string
}{
{
name: "1",
args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000",
want: "XPLODDE",
},
{
name: "2",
args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000",
want: "BitClave - Consumer Activity Token",
},
{
name: "short",
args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000",
want: "Dai Stablecoin v1.0",
},
{
name: "short2",
args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444",
want: "Dai Stablecoin v1.0 DDDDDDDDDDDD",
},
{
name: "long",
args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
want: "Uniswap V1",
},
{
name: "garbage",
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseErc20StringProperty(nil, tt.args)
// the addresses could have different case
if got != tt.want {
t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want)
}
})
}
}
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 *bchain.RpcTransaction
want []bchain.Erc20Transfer
}{
{
name: "0",
args: (b.Txs[0].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: []bchain.Erc20Transfer{},
},
{
name: "1",
args: (b.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: []bchain.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

@@ -13,9 +13,12 @@ import (
"golang.org/x/crypto/sha3"
)
// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length
// EthereumTypeAddressDescriptorLen - the AddressDescriptor of EthereumType has fixed length
const EthereumTypeAddressDescriptorLen = 20
// EthereumTypeTxidLen - the length of Txid
const EthereumTypeTxidLen = 32
// EtherAmountDecimalPoint defines number of decimal points in Ether amounts
const EtherAmountDecimalPoint = 18
@@ -388,7 +391,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
// PackedTxidLen returns length in bytes of packed txid
func (p *EthereumParser) PackedTxidLen() int {
return 32
return EthereumTypeTxidLen
}
// PackTxid packs txid to byte array
@@ -437,16 +440,16 @@ func GetHeightFromTx(tx *bchain.Tx) (uint32, error) {
return uint32(n), nil
}
// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx
func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) {
var r []bchain.Erc20Transfer
// EthereumTypeGetTokenTransfersFromTx returns contract transfers from bchain.Tx
func (p *EthereumParser) EthereumTypeGetTokenTransfersFromTx(tx *bchain.Tx) (bchain.TokenTransfers, error) {
var r bchain.TokenTransfers
var err error
csd, ok := tx.CoinSpecificData.(bchain.EthereumSpecificData)
if ok {
if csd.Receipt != nil {
r, err = erc20GetTransfersFromLog(csd.Receipt.Logs)
r, err = contractGetTransfersFromLog(csd.Receipt.Logs)
} else {
r, err = erc20GetTransfersFromTx(csd.Tx)
r, err = contractGetTransfersFromTx(csd.Tx)
}
if err != nil {
return nil, err
@@ -518,7 +521,7 @@ func ParseErrorFromOutput(output string) string {
if len(output) < 8+64+64+64 || output[:8] != errorOutputSignature {
return ""
}
return parseErc20StringProperty(nil, output[8:])
return parseSimpleStringProperty(output[8:])
}
// PackInternalTransactionError packs common error messages to single byte to save DB space

View File

@@ -491,14 +491,14 @@ func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (jso
return raw, nil
}
func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*bchain.RpcLog, error) {
func (b *EthereumRPC) getTokenTransferEventsForBlock(blockNumber string) (map[string][]*bchain.RpcLog, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
var logs []rpcLogWithTxHash
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
"fromBlock": blockNumber,
"toBlock": blockNumber,
"topics": []string{erc20TransferEventSignature},
"topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature},
})
if err != nil {
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
@@ -630,8 +630,8 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
if err != nil {
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
}
// get ERC20 events
logs, err := b.getERC20EventsForBlock(head.Number)
// get contract transfers events
logs, err := b.getTokenTransferEventsForBlock(head.Number)
if err != nil {
return nil, err
}

View File

@@ -74,11 +74,11 @@ func (m *MempoolEthereumType) createTxEntry(txid string, txTime uint32) (txEntry
addrIndexes, input.AddrDesc = appendAddress(addrIndexes, ^int32(i), a, parser)
}
}
t, err := parser.EthereumTypeGetErc20FromTx(tx)
t, err := parser.EthereumTypeGetTokenTransfersFromTx(tx)
if err != nil {
glog.Error("GetErc20FromTx for tx ", txid, ", ", err)
glog.Error("GetGetTokenTransfersFromTx for tx ", txid, ", ", err)
} else {
mtx.Erc20 = t
mtx.TokenTransfers = t
for i := range t {
addrIndexes, _ = appendAddress(addrIndexes, ^int32(i+1), t[i].From, parser)
addrIndexes, _ = appendAddress(addrIndexes, int32(i+1), t[i].To, parser)

View File

@@ -102,15 +102,24 @@ type MempoolVin struct {
// MempoolTx is blockchain transaction in mempool
// optimized for onNewTx notification
type MempoolTx struct {
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version"`
LockTime uint32 `json:"locktime"`
Vin []MempoolVin `json:"vin"`
Vout []Vout `json:"vout"`
Blocktime int64 `json:"blocktime,omitempty"`
Erc20 []Erc20Transfer `json:"-"`
CoinSpecificData interface{} `json:"-"`
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version"`
LockTime uint32 `json:"locktime"`
Vin []MempoolVin `json:"vin"`
Vout []Vout `json:"vout"`
Blocktime int64 `json:"blocktime,omitempty"`
TokenTransfers TokenTransfers `json:"-"`
CoinSpecificData interface{} `json:"-"`
}
// TokenTransfers is array of TokenTransfer
type TokenTransfers []*TokenTransfer
func (a TokenTransfers) Len() int { return len(a) }
func (a TokenTransfers) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a TokenTransfers) Less(i, j int) bool {
return a[i].Type < a[j].Type
}
// Block is block header and list of transactions
@@ -328,7 +337,7 @@ type BlockChainParser interface {
DeriveAddressDescriptors(descriptor *XpubDescriptor, change uint32, indexes []uint32) ([]AddressDescriptor, error)
DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
// EthereumType specific
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error)
}
// Mempool defines common interface to mempool

View File

@@ -22,6 +22,16 @@ const (
SELFDESTRUCT
)
// TokenTransferType - type of token transfer
type TokenTransferType int
// TokenTransferType enumeration
const (
ERC20 = TokenTransferType(iota)
ERC721
ERC1155
)
// EthereumInternalTransaction contains internal transfers
type EthereumInternalData struct {
Type EthereumInternalTransactionType `json:"type"`
@@ -38,12 +48,19 @@ type Erc20Contract struct {
Decimals int `json:"decimals"`
}
// Erc20Transfer contains a single ERC20 token transfer
type Erc20Transfer struct {
type TokenTransferIdValue struct {
Id big.Int
Value big.Int
}
// TokenTransfer contains a single ERC20/ERC721/ERC1155 token transfer
type TokenTransfer struct {
Type TokenTransferType
Contract string
From string
To string
Tokens big.Int
Value big.Int
IdValues []TokenTransferIdValue
}
// RpcTransaction is returned by eth_getTransactionByHash

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ package db
import (
"encoding/hex"
"math/big"
"reflect"
"testing"
@@ -22,6 +23,12 @@ func ethereumTestnetParser() *eth.EthereumParser {
return eth.NewEthereumParser(1)
}
func bigintFromStringToHex(s string) string {
var b big.Int
b.SetString(s, 0)
return bigintToHex(&b)
}
func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool) {
if err := checkColumn(d, cfHeight, []keyPair{
{
@@ -35,7 +42,7 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
}
}
if err := checkColumn(d, cfAddresses, []keyPair{
{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1, ^1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{2}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^2}), nil},
{addressKeyHex(dbtestdata.EthAddr9f, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}), nil},
@@ -48,8 +55,14 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
if err := checkColumn(d, cfAddressContracts, []keyPair{
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil,
},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil},
}); err != nil {
@@ -81,16 +94,10 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
{
"0041eee8",
dbtestdata.EthTxidB1T1 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" + "00" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" +
dbtestdata.EthTxidB1T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
"06" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) +
"02" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser),
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"),
nil,
},
}
@@ -111,7 +118,7 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
},
{
"0041eee9",
"2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" + uintToHex(1534859988) + varuintToHex(2) + varuintToHex(2345678),
"2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" + uintToHex(1534859988) + varuintToHex(6) + varuintToHex(2345678),
nil,
},
}); err != nil {
@@ -120,18 +127,27 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
}
}
if err := checkColumn(d, cfAddresses, []keyPair{
{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1, ^1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{2}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^2}), nil},
{addressKeyHex(dbtestdata.EthAddr9f, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}), nil},
{addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0, 1}), nil},
{addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^3, 2}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1, 1}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddrZero, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{transferFrom}), nil},
{addressKeyHex(dbtestdata.EthAddr3e, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{^0, 2}), nil},
{addressKeyHex(dbtestdata.EthAddr4b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^0, ^1, 2, ^3, 3, ^2}), nil},
{addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 3}), nil},
{addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T6, []int32{0, ^0, 4, ^4}) + txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^3, 2}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}), nil},
{addressKeyHex(dbtestdata.EthAddr5d, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{^0, 2}), nil},
{addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{4}) + txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 3}), nil},
{addressKeyHex(dbtestdata.EthAddr83, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{^0, ^2}), nil},
{addressKeyHex(dbtestdata.EthAddr92, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddrA3, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{^2}), nil},
{addressKeyHex(dbtestdata.EthAddrContract0d, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1}), nil},
{addressKeyHex(dbtestdata.EthAddrContract47, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddrContract4a, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^1}), nil},
{addressKeyHex(dbtestdata.EthAddrContract6f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{0}), nil},
{addressKeyHex(dbtestdata.EthAddrContractCd, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{0}), nil},
}); err != nil {
{
t.Fatal(err)
@@ -139,15 +155,53 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
}
if err := checkColumn(d, cfAddressContracts, []keyPair{
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "040200" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "020102", nil},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser),
"030202" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(1) + bigintFromStringToHex("150") + bigintFromStringToHex("1"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser),
"010101" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("8086") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"050300" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(2) + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser),
"020000" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(1) + bigintFromStringToHex("1"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(0), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser),
"010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(0), nil,
},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser), "010100", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "030104", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), "010101" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser), "010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser), "010001", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser), "010100", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "020102", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser), "010100", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser), "010100", nil},
}); err != nil {
{
t.Fatal(err)
@@ -185,22 +239,25 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
{
"0041eee9",
dbtestdata.EthTxidB2T1 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" + "00" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" +
dbtestdata.EthTxidB2T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser) +
"05" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) +
"08" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser),
"04" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("7675000000000000001") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.EthTxidB2T3 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(uint(bchain.ERC721)) + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T4 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.ERC1155)) + "01" + bigintFromStringToHex("150") + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T5 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrZero, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.ERC1155)) + "02" + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10") +
dbtestdata.EthTxidB2T6 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"),
nil,
},
}); err != nil {
@@ -285,6 +342,10 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
// get transactions for various addresses / low-high ranges
verifyGetTransactions(t, d, "0x"+dbtestdata.EthAddr55, 0, 10000000, []txidIndex{
{"0x" + dbtestdata.EthTxidB2T6, 0},
{"0x" + dbtestdata.EthTxidB2T6, ^0},
{"0x" + dbtestdata.EthTxidB2T6, 4},
{"0x" + dbtestdata.EthTxidB2T6, ^4},
{"0x" + dbtestdata.EthTxidB2T2, ^3},
{"0x" + dbtestdata.EthTxidB2T2, 2},
{"0x" + dbtestdata.EthTxidB2T1, ^0},
@@ -347,7 +408,7 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
}
iw := &BlockInfo{
Hash: "0x2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee",
Txs: 2,
Txs: 6,
Size: 2345678,
Time: 1534859988,
Height: 4321001,
@@ -468,3 +529,607 @@ func Test_BulkConnect_EthereumType(t *testing.T) {
t.Fatal("Expecting is.BlockTimes 4321002, got ", len(d.is.BlockTimes))
}
}
func Test_packUnpackEthInternalData(t *testing.T) {
parser := ethereumTestnetParser()
db := &RocksDB{chainParser: parser}
tests := []struct {
name string
data ethInternalData
want *bchain.EthereumInternalData
}{
{
name: "CALL 1",
data: ethInternalData{
internalType: bchain.CALL,
transfers: []ethInternalTransfer{
{
internalType: bchain.CALL,
from: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
to: addressToAddrDesc(dbtestdata.EthAddr20, parser),
value: *big.NewInt(412342134),
},
},
},
want: &bchain.EthereumInternalData{
Type: bchain.CALL,
Transfers: []bchain.EthereumInternalTransfer{
{
Type: bchain.CALL,
From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr3e),
To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr20),
Value: *big.NewInt(412342134),
},
},
},
},
{
name: "CALL 2",
data: ethInternalData{
internalType: bchain.CALL,
errorMsg: "error error error",
transfers: []ethInternalTransfer{
{
internalType: bchain.CALL,
from: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
to: addressToAddrDesc(dbtestdata.EthAddr20, parser),
value: *big.NewInt(4123421341),
},
{
internalType: bchain.CREATE,
from: addressToAddrDesc(dbtestdata.EthAddr4b, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
value: *big.NewInt(123),
},
{
internalType: bchain.SELFDESTRUCT,
from: addressToAddrDesc(dbtestdata.EthAddr7b, parser),
to: addressToAddrDesc(dbtestdata.EthAddr83, parser),
value: *big.NewInt(67890),
},
},
},
want: &bchain.EthereumInternalData{
Type: bchain.CALL,
Error: "error error error",
Transfers: []bchain.EthereumInternalTransfer{
{
Type: bchain.CALL,
From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr3e),
To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr20),
Value: *big.NewInt(4123421341),
},
{
Type: bchain.CREATE,
From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr4b),
To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr55),
Value: *big.NewInt(123),
},
{
Type: bchain.SELFDESTRUCT,
From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr7b),
To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr83),
Value: *big.NewInt(67890),
},
},
},
},
{
name: "CREATE",
data: ethInternalData{
internalType: bchain.CREATE,
contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser),
},
want: &bchain.EthereumInternalData{
Type: bchain.CREATE,
Contract: eth.EIP55AddressFromAddress(dbtestdata.EthAddrContract0d),
Transfers: []bchain.EthereumInternalTransfer{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
packed := packEthInternalData(&tt.data)
got, err := db.unpackEthInternalData(packed)
if err != nil {
t.Errorf("unpackEthInternalData() error = %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("packEthInternalData/unpackEthInternalData = %+v, want %+v", got, tt.want)
}
})
}
}
func Test_packUnpackAddrContracts(t *testing.T) {
parser := ethereumTestnetParser()
type args struct {
buf []byte
addrDesc bchain.AddressDescriptor
}
tests := []struct {
name string
data AddrContracts
}{
{
name: "1",
data: AddrContracts{
TotalTxs: 30,
NonContractTxs: 20,
InternalTxs: 10,
Contracts: []AddrContract{},
},
},
{
name: "2",
data: AddrContracts{
TotalTxs: 12345,
NonContractTxs: 444,
InternalTxs: 8873,
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser),
Txs: 8,
Value: *big.NewInt(793201132),
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 41235,
Ids: []big.Int{
*big.NewInt(1),
*big.NewInt(2),
*big.NewInt(3),
*big.NewInt(3144223412344123),
*big.NewInt(5),
},
},
{
Type: bchain.ERC1155,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
Txs: 64,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(1),
Value: *big.NewInt(1412341234),
},
{
Id: *big.NewInt(123412341234),
Value: *big.NewInt(3),
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
packed := packAddrContracts(&tt.data)
got, err := unpackAddrContracts(packed, nil)
if err != nil {
t.Errorf("unpackAddrContracts() error = %v", err)
return
}
if !reflect.DeepEqual(got, &tt.data) {
t.Errorf("unpackAddrContracts() = %v, want %v", got, tt.data)
}
})
}
}
func Test_addToContracts(t *testing.T) {
// the test builds addToContracts that keeps contracts of an address
// the test adds and removes values from addToContracts, therefore the order of tests is important
addrContracts := &AddrContracts{}
parser := ethereumTestnetParser()
type args struct {
index int32
contract bchain.AddressDescriptor
transfer *bchain.TokenTransfer
addTxCount bool
}
tests := []struct {
name string
args args
wantIndex int32
wantAddrContracts *AddrContracts
}{
{
name: "ERC20 to",
args: args{
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC20,
Value: *big.NewInt(123456),
},
addTxCount: true,
},
wantIndex: 0 + ContractIndexOffset, // the first contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 1,
Value: *big.NewInt(123456),
},
},
},
},
{
name: "ERC20 from",
args: args{
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC20,
Value: *big.NewInt(23456),
},
addTxCount: true,
},
wantIndex: ^(0 + ContractIndexOffset), // the first contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
},
},
},
{
name: "ERC721 to id 1",
args: args{
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Value: *big.NewInt(1),
},
addTxCount: true,
},
wantIndex: 1 + ContractIndexOffset, // the 2nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 1,
Ids: []big.Int{*big.NewInt(1)},
},
},
},
},
{
name: "ERC721 to id 2",
args: args{
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Value: *big.NewInt(2),
},
addTxCount: true,
},
wantIndex: 1 + ContractIndexOffset, // the 2nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)},
},
},
},
},
{
name: "ERC721 from id 1, addTxCount=false",
args: args{
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Value: *big.NewInt(1),
},
addTxCount: false,
},
wantIndex: ^(1 + ContractIndexOffset), // the 2nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
},
},
},
{
name: "ERC1155 to id 11, value 56789",
args: args{
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56789),
},
},
},
addTxCount: true,
},
wantIndex: 2 + ContractIndexOffset, // the 3nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 1,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56789),
},
},
},
},
},
},
{
name: "ERC1155 to id 11, value 111 and id 22, value 222",
args: args{
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(111),
},
{
Id: *big.NewInt(22),
Value: *big.NewInt(222),
},
},
},
addTxCount: true,
},
wantIndex: 2 + ContractIndexOffset, // the 3nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 2,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56900),
},
{
Id: *big.NewInt(22),
Value: *big.NewInt(222),
},
},
},
},
},
},
{
name: "ERC1155 from id 11, value 112 and id 22, value 222",
args: args{
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(112),
},
{
Id: *big.NewInt(22),
Value: *big.NewInt(222),
},
},
},
addTxCount: true,
},
wantIndex: ^(2 + ContractIndexOffset), // the 3nd contract of the address
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 3,
IdValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56788),
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
contractIndex, found := findContractInAddressContracts(tt.args.contract, addrContracts.Contracts)
if !found {
contractIndex = len(addrContracts.Contracts)
addrContracts.Contracts = append(addrContracts.Contracts, AddrContract{
Contract: tt.args.contract,
Type: tt.args.transfer.Type,
})
}
if got := addToContract(&addrContracts.Contracts[contractIndex], contractIndex, tt.args.index, tt.args.contract, tt.args.transfer, tt.args.addTxCount); got != tt.wantIndex {
t.Errorf("addToContracts() = %v, want %v", got, tt.wantIndex)
}
if !reflect.DeepEqual(addrContracts, tt.wantAddrContracts) {
t.Errorf("addToContracts() = %+v, want %+v", addrContracts, tt.wantAddrContracts)
}
})
}
}
func Test_packUnpackBlockTx(t *testing.T) {
parser := ethereumTestnetParser()
tests := []struct {
name string
blockTx ethBlockTx
pos int
}{
{
name: "no contract",
blockTx: ethBlockTx{
btxID: hexToBytes(dbtestdata.EthTxidB1T1),
from: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contracts: []ethBlockTxContract{},
},
pos: 73,
},
{
name: "ERC20",
blockTx: ethBlockTx{
btxID: hexToBytes(dbtestdata.EthTxidB1T1),
from: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contracts: []ethBlockTxContract{
{
from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20,
value: *big.NewInt(10000),
},
},
},
pos: 137,
},
{
name: "multiple contracts",
blockTx: ethBlockTx{
btxID: hexToBytes(dbtestdata.EthTxidB1T1),
from: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contracts: []ethBlockTxContract{
{
from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20,
value: *big.NewInt(987654321),
},
{
from: addressToAddrDesc(dbtestdata.EthAddr4b, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transferType: bchain.ERC721,
value: *big.NewInt(13),
},
{
from: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
to: addressToAddrDesc(dbtestdata.EthAddr7b, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transferType: bchain.ERC1155,
idValues: []bchain.TokenTransferIdValue{
{
Id: *big.NewInt(1234),
Value: *big.NewInt(98765),
},
{
Id: *big.NewInt(5566),
Value: *big.NewInt(12341234421),
},
},
},
},
},
pos: 280,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := make([]byte, 0)
packed := packBlockTx(buf, &tt.blockTx)
got, pos, err := unpackBlockTx(packed, 0)
if err != nil {
t.Errorf("unpackBlockTx() error = %v", err)
return
}
if !reflect.DeepEqual(*got, tt.blockTx) {
t.Errorf("unpackBlockTx() got = %v, want %v", *got, tt.blockTx)
}
if pos != tt.pos {
t.Errorf("unpackBlockTx() pos = %v, want %v", pos, tt.pos)
}
})
}
}

View File

@@ -152,10 +152,10 @@ func checkColumn(d *RocksDB, col int, kp []keyPair) error {
defer it.Close()
i := 0
for it.SeekToFirst(); it.Valid(); it.Next() {
if i >= len(kp) {
return errors.Errorf("Expected less rows in column %v", cfNames[col])
}
key := hex.EncodeToString(it.Key().Data())
if i >= len(kp) {
return errors.Errorf("Expected less rows in column %v, superfluous key %v", cfNames[col], key)
}
if key != kp[i].Key {
return errors.Errorf("Incorrect key %v found in column %v row %v, expecting %v", key, cfNames[col], i, kp[i].Key)
}

View File

@@ -84,9 +84,21 @@ Column families used only by **Ethereum type** coins:
- **addressContracts** (used only by Ethereum type coins)
Maps *addrDesc* to *total number of transactions*, *number of non contract transactions*, *number of internal transactions* and array of *contracts* with *number of transfers* of given address.
Maps *addrDesc* to *total number of transactions*, *number of non contract transactions*, *number of internal transactions*
and array of *contracts* with *number of transfers* of given address.
```
(addrDesc []byte) -> (total_txs vuint)+(non-contract_txs vuint)+(internal_txs vuint)+[]((contractAddrDesc []byte)+(nr_transfers vuint))
(addrDesc []byte) -> (total_txs vuint)+(non-contract_txs vuint)+(internal_txs vuint)+
[]((contractAddrDesc []byte)+(type+4*nr_transfers vuint))+
<(value bigInt) if ERC20> or <(nr_values vuint)+[](id bigInt) if ERC721> or <(nr_values vuint)+[]((id bigInt)+(value bigInt)) if ERC1155>
```
- **internalData** (used only by Ethereum type coins)
Maps *txid* to *type (CALL 0 | CREATE 1)*, *addrDesc of created contract for CREATE type*, array of *type (CALL 0 | CREATE 1 | SELFDESTRUCT 2)*, *from addrDesc*, *to addrDesc*, *value bigInt* and possible *error*.
```
(txid []byte) -> (type+2*nr_transfers vuint)+<(addrDesc []byte) if CREATE>+
[]((type byte)+(fromAddrDesc []byte)+(toAddrDesc []byte)+(value bigInt))+
(error []byte)
```
- **blockTxs**
@@ -104,9 +116,14 @@ Column families used only by **Ethereum type** coins:
- Ethereum type
The value is an array of transaction data. For each transaction is stored *txid*,
*from* and *to* address descriptors and array of *contract address descriptors* with *transfer address descriptors*.
*from* and *to* address descriptors and array of contract transfer infos consisting of
*from*, *to* and *contract* address descriptors, *type (ERC20 0 | ERC721 1 | ERC1155 2)* and value (or list of id+value for ERC1155)
```
(height uint32) -> []((txid [32]byte)+(from addrDesc)+(to addrDesc)+(nr_contracts vuint)+[]((contract addrDesc)+(addr addrDesc)))
(height uint32) -> [](
(txid [32]byte)+(from addrDesc)+(to addrDesc)+(nr_contracts vuint)+
[]((from addrDesc)+(to addrDesc)+(contract addrDesc)+(type byte)+
<(value bigInt) if ERC20 or ERC721> or <(nr_values vuint)+[]((id bigInt)+(value bigInt)) if ERC1155>)
)
```
- **transactions**

View File

@@ -919,8 +919,8 @@ func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string
}
}
}
for i := range tx.Erc20 {
addrDesc, err := s.chainParser.GetAddrDescFromAddress(tx.Erc20[i].From)
for i := range tx.TokenTransfers {
addrDesc, err := s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].From)
if err == nil && len(addrDesc) > 0 {
sad := string(addrDesc)
as, ok := s.addressSubscriptions[sad]
@@ -928,7 +928,7 @@ func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string
subscribed[sad] = struct{}{}
}
}
addrDesc, err = s.chainParser.GetAddrDescFromAddress(tx.Erc20[i].To)
addrDesc, err = s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].To)
if err == nil && len(addrDesc) > 0 {
sad := string(addrDesc)
as, ok := s.addressSubscriptions[sad]

File diff suppressed because one or more lines are too long