mirror of
https://github.com/trezor/blockbook.git
synced 2026-02-20 00:51:39 +01:00
Process ERC721 and ERC1155 tokens
This commit is contained in:
40
api/types.go
40
api/types.go
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
329
bchain/coins/eth/contract.go
Normal file
329
bchain/coins/eth/contract.go
Normal 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
|
||||
}
|
||||
291
bchain/coins/eth/contract_test.go
Normal file
291
bchain/coins/eth/contract_test.go
Normal 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])
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
58
bchain/coins/eth/dataparser.go
Normal file
58
bchain/coins/eth/dataparser.go
Normal 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 ""
|
||||
}
|
||||
53
bchain/coins/eth/dataparser_test.go
Normal file
53
bchain/coins/eth/dataparser_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user