Store contract info in DB

This commit is contained in:
Martin Boehm 2022-05-01 02:41:56 +02:00 committed by Martin
parent 2507e12057
commit db91824dc3
21 changed files with 616 additions and 360 deletions

View File

@ -135,58 +135,40 @@ type Vout struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
} }
// TokenType specifies type of token // MultiTokenValue contains values for contract with id and value (like ERC1155)
type TokenType string type MultiTokenValue struct {
// Token types
const (
// Ethereum token types
ERC20TokenType TokenType = "ERC20"
ERC771TokenType TokenType = "ERC721"
ERC1155TokenType TokenType = "ERC1155"
// 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}
// TokenTransferValues contains values for ERC1155 contract
type TokenTransferValues struct {
Id *Amount `json:"id,omitempty"` Id *Amount `json:"id,omitempty"`
Value *Amount `json:"value,omitempty"` Value *Amount `json:"value,omitempty"`
} }
// Token contains info about tokens held by an address // Token contains info about tokens held by an address
type Token struct { type Token struct {
Type TokenType `json:"type"` Type bchain.TokenTypeName `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
Contract string `json:"contract,omitempty"` Contract string `json:"contract,omitempty"`
Transfers int `json:"transfers"` Transfers int `json:"transfers"`
Symbol string `json:"symbol,omitempty"` Symbol string `json:"symbol,omitempty"`
Decimals int `json:"decimals,omitempty"` Decimals int `json:"decimals,omitempty"`
BalanceSat *Amount `json:"balance,omitempty"` BalanceSat *Amount `json:"balance,omitempty"`
Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens
IdValues []TokenTransferValues `json:"idValues,omitempty"` // multiple ERC1155 tokens MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"` // multiple ERC1155 tokens
TotalReceivedSat *Amount `json:"totalReceived,omitempty"` TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"` TotalSentSat *Amount `json:"totalSent,omitempty"`
ContractIndex string `json:"-"` ContractIndex string `json:"-"`
} }
// TokenTransfer contains info about a token transfer done in a transaction // TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct { type TokenTransfer struct {
Type TokenType `json:"type"` Type bchain.TokenTypeName `json:"type"`
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
Token string `json:"token"` Token string `json:"token"`
Name string `json:"name"` Name string `json:"name"`
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Decimals int `json:"decimals"` Decimals int `json:"decimals"`
Value *Amount `json:"value,omitempty"` Value *Amount `json:"value,omitempty"`
Values []TokenTransferValues `json:"values,omitempty"` MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"`
} }
type EthereumInternalTransfer struct { type EthereumInternalTransfer struct {
@ -290,22 +272,22 @@ type AddressFilter struct {
// Address holds information about address and its transactions // Address holds information about address and its transactions
type Address struct { type Address struct {
Paging Paging
AddrStr string `json:"address"` AddrStr string `json:"address"`
BalanceSat *Amount `json:"balance"` BalanceSat *Amount `json:"balance"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"` TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"` TotalSentSat *Amount `json:"totalSent,omitempty"`
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"` UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedTxs int `json:"unconfirmedTxs"` UnconfirmedTxs int `json:"unconfirmedTxs"`
Txs int `json:"txs"` Txs int `json:"txs"`
NonTokenTxs int `json:"nonTokenTxs,omitempty"` NonTokenTxs int `json:"nonTokenTxs,omitempty"`
InternalTxs int `json:"internalTxs,omitempty"` InternalTxs int `json:"internalTxs,omitempty"`
Transactions []*Tx `json:"transactions,omitempty"` Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"txids,omitempty"` Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"` Nonce string `json:"nonce,omitempty"`
UsedTokens int `json:"usedTokens,omitempty"` UsedTokens int `json:"usedTokens,omitempty"`
Tokens []Token `json:"tokens,omitempty"` Tokens []Token `json:"tokens,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"` ContractInfo *bchain.ContractInfo `json:"contractInfo,omitempty"`
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"` AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
// helpers for explorer // helpers for explorer
Filter string `json:"-"` Filter string `json:"-"`
XPubAddresses map[string]struct{} `json:"-"` XPubAddresses map[string]struct{} `json:"-"`

View File

@ -151,7 +151,10 @@ func (w *Worker) getAddressAliases(addresses map[string]struct{}) AddressAliases
} }
for a := range addresses { for a := range addresses {
if w.chainType == bchain.ChainEthereumType { if w.chainType == bchain.ChainEthereumType {
// TODO get contract name ci, err := w.db.GetContractInfoForAddress(a)
if err == nil && ci != nil && ci.Name != "" {
aliases[a] = AddressAlias{Type: "Contract", Alias: ci.Name}
}
} }
n := w.db.GetAddressAlias(a) n := w.db.GetAddressAlias(a)
if len(n) > 0 { if len(n) > 0 {
@ -535,20 +538,28 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, add
glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, t.Contract) glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, t.Contract)
continue continue
} }
erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd) typeName := bchain.EthereumTokenTypeMap[t.Type]
contractInfo, err := w.db.GetContractInfo(cd, typeName)
if err != nil { if err != nil {
glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, t.Contract) glog.Errorf("GetContractInfo error %v, contract %v", err, t.Contract)
} }
if erc20c == nil { if contractInfo == nil {
erc20c = &bchain.Erc20Contract{Name: t.Contract} glog.Warningf("Contract %v %v not found in DB", t.Contract, typeName)
contractInfo, err = w.chain.GetContractInfo(cd)
if err != nil {
glog.Errorf("GetContractInfo from chain error %v, contract %v", err, t.Contract)
}
if contractInfo == nil {
contractInfo = &bchain.ContractInfo{Name: t.Contract, Type: bchain.UnknownTokenType}
}
} }
var value *Amount var value *Amount
var values []TokenTransferValues var values []MultiTokenValue
if t.Type == bchain.ERC1155 { if t.Type == bchain.MultiToken {
values = make([]TokenTransferValues, len(t.IdValues)) values = make([]MultiTokenValue, len(t.MultiTokenValues))
for j := range values { for j := range values {
values[j].Id = (*Amount)(&t.IdValues[j].Id) values[j].Id = (*Amount)(&t.MultiTokenValues[j].Id)
values[j].Value = (*Amount)(&t.IdValues[j].Value) values[j].Value = (*Amount)(&t.MultiTokenValues[j].Value)
} }
} else { } else {
value = (*Amount)(&t.Value) value = (*Amount)(&t.Value)
@ -556,15 +567,15 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, add
aggregateAddress(addresses, t.From) aggregateAddress(addresses, t.From)
aggregateAddress(addresses, t.To) aggregateAddress(addresses, t.To)
tokens[i] = TokenTransfer{ tokens[i] = TokenTransfer{
Type: TokenTypeMap[t.Type], Type: typeName,
Token: t.Contract, Token: t.Contract,
From: t.From, From: t.From,
To: t.To, To: t.To,
Value: value, Value: value,
Values: values, MultiTokenValues: values,
Decimals: erc20c.Decimals, Decimals: contractInfo.Decimals,
Name: erc20c.Name, Name: contractInfo.Name,
Symbol: erc20c.Symbol, Symbol: contractInfo.Symbol,
} }
} }
return tokens return tokens
@ -751,35 +762,41 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
} }
func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails) (*Token, error) { func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails) (*Token, error) {
// TODO use db.contracts
validContract := true validContract := true
typeName := bchain.EthereumTokenTypeMap[c.Type]
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract) ci, err := w.db.GetContractInfo(c.Contract, typeName)
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract) return nil, errors.Annotatef(err, "GetContractInfo %v", c.Contract)
} }
if ci == nil { if ci == nil {
ci = &bchain.Erc20Contract{} glog.Warningf("Contract %v %v not found in DB", c.Contract, typeName)
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract) ci, err = w.chain.GetContractInfo(c.Contract)
if len(addresses) > 0 { if err != nil {
ci.Contract = addresses[0] glog.Errorf("GetContractInfo from chain error %v, contract %v", err, c.Contract)
ci.Name = addresses[0] }
if ci == nil {
ci = &bchain.ContractInfo{Type: bchain.UnknownTokenType}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
} }
validContract = false
} }
t := Token{ t := Token{
Contract: ci.Contract, Contract: ci.Contract,
Name: ci.Name, Name: ci.Name,
Symbol: ci.Symbol, Symbol: ci.Symbol,
Type: typeName,
Transfers: int(c.Txs), Transfers: int(c.Txs),
Decimals: ci.Decimals, Decimals: ci.Decimals,
ContractIndex: strconv.Itoa(index), ContractIndex: strconv.Itoa(index),
} }
// return contract balances/values only at or above AccountDetailsTokenBalances // return contract balances/values only at or above AccountDetailsTokenBalances
if details >= AccountDetailsTokenBalances && validContract { if details >= AccountDetailsTokenBalances && validContract {
if c.Type == bchain.ERC20 { if c.Type == bchain.FungibleToken {
t.Type = ERC20TokenType
// get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct // get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct
b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract) b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
if err != nil { if err != nil {
@ -789,11 +806,6 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
t.BalanceSat = (*Amount)(b) t.BalanceSat = (*Amount)(b)
} }
} else { } else {
if c.Type == bchain.ERC721 {
t.Type = ERC771TokenType
} else {
t.Type = ERC1155TokenType
}
if len(c.Ids) > 0 { if len(c.Ids) > 0 {
ids := make([]Amount, len(c.Ids)) ids := make([]Amount, len(c.Ids))
for j := range ids { for j := range ids {
@ -801,13 +813,13 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
} }
t.Ids = ids t.Ids = ids
} }
if len(c.IdValues) > 0 { if len(c.MultiTokenValues) > 0 {
idValues := make([]TokenTransferValues, len(c.IdValues)) idValues := make([]MultiTokenValue, len(c.MultiTokenValues))
for j := range idValues { for j := range idValues {
idValues[j].Id = (*Amount)(&c.IdValues[j].Id) idValues[j].Id = (*Amount)(&c.MultiTokenValues[j].Id)
idValues[j].Value = (*Amount)(&c.IdValues[j].Value) idValues[j].Value = (*Amount)(&c.MultiTokenValues[j].Value)
} }
t.IdValues = idValues t.MultiTokenValues = idValues
} }
} }
} }
@ -819,18 +831,25 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bchain.AddressDescriptor, details AccountDetails) (*Token, error) { func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bchain.AddressDescriptor, details AccountDetails) (*Token, error) {
var b *big.Int var b *big.Int
validContract := true validContract := true
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(contract) ci, err := w.db.GetContractInfo(contract, "")
if err != nil { if err != nil {
return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", contract) return nil, errors.Annotatef(err, "GetContractInfo %v", contract)
} }
if ci == nil { if ci == nil {
ci = &bchain.Erc20Contract{} glog.Warningf("Contract %v not found in DB", contract)
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract) ci, err = w.chain.GetContractInfo(contract)
if len(addresses) > 0 { if err != nil {
ci.Contract = addresses[0] glog.Errorf("GetContractInfo from chain error %v, contract %v", err, contract)
ci.Name = addresses[0] }
if ci == nil {
ci = &bchain.ContractInfo{Type: bchain.UnknownTokenType}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
} }
validContract = false
} }
// do not read contract balances etc in case of Basic option // do not read contract balances etc in case of Basic option
if details >= AccountDetailsTokenBalances && validContract { if details >= AccountDetailsTokenBalances && validContract {
@ -843,7 +862,7 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch
b = nil b = nil
} }
return &Token{ return &Token{
Type: ERC20TokenType, Type: ci.Type,
BalanceSat: (*Amount)(b), BalanceSat: (*Amount)(b),
Contract: ci.Contract, Contract: ci.Contract,
Name: ci.Name, Name: ci.Name,
@ -854,11 +873,11 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch
}, nil }, nil
} }
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, int, error) { func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.ContractInfo, uint64, int, int, int, error) {
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
tokens []Token tokens []Token
ci *bchain.Erc20Contract ci *bchain.ContractInfo
n uint64 n uint64
nonContractTxs int nonContractTxs int
internalTxs int internalTxs int
@ -912,7 +931,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
} }
tokens = tokens[:j] tokens = tokens[:j]
} }
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc) ci, err = w.db.GetContractInfo(addrDesc, "")
if err != nil { if err != nil {
return nil, nil, nil, 0, 0, 0, 0, err return nil, nil, nil, 0, 0, 0, 0, err
} }
@ -1043,7 +1062,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
tokens []Token tokens []Token
erc20c *bchain.Erc20Contract contractInfo *bchain.ContractInfo
txm []string txm []string
txs []*Tx txs []*Tx
txids []string txids []string
@ -1062,7 +1081,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
} }
if w.chainType == bchain.ChainEthereumType { if w.chainType == bchain.ChainEthereumType {
var n uint64 var n uint64
ba, tokens, erc20c, n, nonTokenTxs, internalTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter) ba, tokens, contractInfo, n, nonTokenTxs, internalTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1173,7 +1192,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
Transactions: txs, Transactions: txs,
Txids: txids, Txids: txids,
Tokens: tokens, Tokens: tokens,
Erc20Contract: erc20c, ContractInfo: contractInfo,
Nonce: nonce, Nonce: nonce,
AddressAliases: w.getAddressAliases(addresses), AddressAliases: w.getAddressAliases(addresses),
} }

View File

@ -266,7 +266,7 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
} }
} }
return Token{ return Token{
Type: XPUBAddressTokenType, Type: bchain.XPUBAddressTokenType,
Name: address, Name: address,
Decimals: w.chainParser.AmountDecimals(), Decimals: w.chainParser.AmountDecimals(),
BalanceSat: (*Amount)(balance), BalanceSat: (*Amount)(balance),

View File

@ -54,8 +54,8 @@ func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint
return 0, errors.New("Not supported") return 0, errors.New("Not supported")
} }
// EthereumTypeGetErc20ContractInfo is not supported // GetContractInfo is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) { func (b *BaseChain) GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) {
return nil, errors.New("Not supported") return nil, errors.New("Not supported")
} }

View File

@ -321,13 +321,13 @@ func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interf
return c.b.EthereumTypeEstimateGas(params) return c.b.EthereumTypeEstimateGas(params)
} }
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) { func (c *blockChainWithMetrics) GetContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.ContractInfo, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now()) defer func(s time.Time) { c.observeRPCLatency("GetContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc) return c.b.GetContractInfo(contractDesc)
} }
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) { func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now()) defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractBalance", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc) return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
} }

View File

@ -4,10 +4,8 @@ import (
"context" "context"
"math/big" "math/big"
"strings" "strings"
"sync"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/golang/glog"
"github.com/juju/errors" "github.com/juju/errors"
"github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/bchain"
) )
@ -28,9 +26,6 @@ const contractSymbolSignature = "0x95d89b41"
const contractDecimalsSignature = "0x313ce567" const contractDecimalsSignature = "0x313ce567"
const contractBalanceOf = "0x70a08231" const contractBalanceOf = "0x70a08231"
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) { func addressFromPaddedHex(s string) (string, error) {
var t big.Int var t big.Int
var ok bool var ok bool
@ -48,16 +43,16 @@ func addressFromPaddedHex(s string) (string, error) {
func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) { func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) {
tl := len(l.Topics) tl := len(l.Topics)
var ttt bchain.TokenTransferType var ttt bchain.TokenType
var value big.Int var value big.Int
if tl == 3 { if tl == 3 {
ttt = bchain.ERC20 ttt = bchain.FungibleToken
_, ok := value.SetString(l.Data, 0) _, ok := value.SetString(l.Data, 0)
if !ok { if !ok {
return nil, errors.New("ERC20 log Data is not a number") return nil, errors.New("ERC20 log Data is not a number")
} }
} else if tl == 4 { } else if tl == 4 {
ttt = bchain.ERC721 ttt = bchain.NonFungibleToken
_, ok := value.SetString(l.Topics[3], 0) _, ok := value.SetString(l.Topics[3], 0)
if !ok { if !ok {
return nil, errors.New("ERC721 log Topics[3] is not a number") return nil, errors.New("ERC721 log Topics[3] is not a number")
@ -105,11 +100,11 @@ func processERC1155TransferSingleEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
return nil, errors.New("ERC1155 log Data value is not a number") return nil, errors.New("ERC1155 log Data value is not a number")
} }
return &bchain.TokenTransfer{ return &bchain.TokenTransfer{
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: EIP55AddressFromAddress(l.Address), Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from), From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to), To: EIP55AddressFromAddress(to),
IdValues: []bchain.TokenTransferIdValue{{Id: id, Value: value}}, MultiTokenValues: []bchain.MultiTokenValue{{Id: id, Value: value}},
}, nil }, nil
} }
@ -150,7 +145,7 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
if countIds != countValues { if countIds != countValues {
return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match") return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match")
} }
idValues := make([]bchain.TokenTransferIdValue, countValues) idValues := make([]bchain.MultiTokenValue, countValues)
for i := 0; i < countValues; i++ { for i := 0; i < countValues; i++ {
var id, value big.Int var id, value big.Int
o := offsetIds + 64 + 64*i o := offsetIds + 64 + 64*i
@ -163,14 +158,14 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
if !ok { if !ok {
return nil, errors.New("ERC1155 log Data value is not a number") return nil, errors.New("ERC1155 log Data value is not a number")
} }
idValues[i] = bchain.TokenTransferIdValue{Id: id, Value: value} idValues[i] = bchain.MultiTokenValue{Id: id, Value: value}
} }
return &bchain.TokenTransfer{ return &bchain.TokenTransfer{
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: EIP55AddressFromAddress(l.Address), Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from), From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to), To: EIP55AddressFromAddress(to),
IdValues: idValues, MultiTokenValues: idValues,
}, nil }, nil
} }
func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) { func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) {
@ -214,7 +209,7 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer
return nil, errors.New("Data is not a number") return nil, errors.New("Data is not a number")
} }
r = append(r, &bchain.TokenTransfer{ r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: EIP55AddressFromAddress(tx.To), Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From), From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(to), To: EIP55AddressFromAddress(to),
@ -238,7 +233,7 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer
return nil, errors.New("Data is not a number") return nil, errors.New("Data is not a number")
} }
r = append(r, &bchain.TokenTransfer{ r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: EIP55AddressFromAddress(tx.To), Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(from), From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to), To: EIP55AddressFromAddress(to),
@ -262,56 +257,52 @@ func (b *EthereumRPC) ethCall(data, to string) (string, error) {
return r, nil return r, nil
} }
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract func (b *EthereumRPC) fetchContractInfo(address string) (*bchain.ContractInfo, error) {
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { var contract bchain.ContractInfo
cds := string(contractDesc) data, err := b.ethCall(contractNameSignature, address)
cachedContractsMux.Lock() if err != nil {
contract, found := cachedContracts[cds] // ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
cachedContractsMux.Unlock() // and returning error "execution reverted" for some non contract addresses
if !found { // https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
address := EIP55Address(contractDesc) // glog.Warning(errors.Annotatef(err, "Contract NameSignature %v", address))
data, err := b.ethCall(contractNameSignature, address) return nil, nil
if err != nil { // return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
// 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 name := parseSimpleStringProperty(data)
if name != "" {
data, err = b.ethCall(contractSymbolSignature, address)
if err != nil {
// glog.Warning(errors.Annotatef(err, "Contract SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseSimpleStringProperty(data)
data, _ = b.ethCall(contractDecimalsSignature, address)
// if err != nil {
// glog.Warning(errors.Annotatef(err, "Contract DecimalsSignature %v", address))
// // return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
// }
contract = bchain.ContractInfo{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseSimpleNumericProperty(data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
return nil, nil
}
return &contract, nil
}
// GetContractInfo returns information about a contract
func (b *EthereumRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) {
address := EIP55Address(contractDesc)
return b.fetchContractInfo(address)
} }
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address

View File

@ -133,7 +133,7 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
}, },
want: bchain.TokenTransfers{ want: bchain.TokenTransfers{
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB", Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB",
From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391", From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391",
To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f", To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f",
@ -171,11 +171,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
}, },
want: bchain.TokenTransfers{ want: bchain.TokenTransfers{
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c", Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53", From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53",
To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec", To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec",
IdValues: []bchain.TokenTransferIdValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}}, MultiTokenValues: []bchain.MultiTokenValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}},
}, },
}, },
}, },
@ -195,11 +195,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
}, },
want: bchain.TokenTransfers{ want: bchain.TokenTransfers{
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7", Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7",
From: "0x0000000000000000000000000000000000000000", From: "0x0000000000000000000000000000000000000000",
To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e", To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{Id: *big.NewInt(1776), Value: *big.NewInt(1)}, {Id: *big.NewInt(1776), Value: *big.NewInt(1)},
{Id: *big.NewInt(1898), Value: *big.NewInt(10)}, {Id: *big.NewInt(1898), Value: *big.NewInt(10)},
}, },
@ -247,7 +247,7 @@ func Test_contractGetTransfersFromTx(t *testing.T) {
args: (b1.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, args: (b1.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{ want: bchain.TokenTransfers{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
@ -260,7 +260,7 @@ func Test_contractGetTransfersFromTx(t *testing.T) {
args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{ want: bchain.TokenTransfers{
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9", Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9",
From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081", From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081",
To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b", To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b",

View File

@ -39,8 +39,8 @@ func parseSimpleStringProperty(data string) string {
if len(data) > 128 { if len(data) > 128 {
n := parseSimpleNumericProperty(data[64:128]) n := parseSimpleNumericProperty(data[64:128])
if n != nil { if n != nil {
l := n.Uint64() l := n.Int64()
if l > 0 && 2*int(l) <= len(data)-128 { if l > 0 && int(l) <= ((len(data)-128)>>1) {
b, err := hex.DecodeString(data[128 : 128+2*l]) b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil { if err == nil {
return string(b) return string(b)

View File

@ -45,6 +45,11 @@ func Test_parseSimpleStringProperty(t *testing.T) {
args: "0x2234880850896048596206002535425366538144616734015984380565810000", args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "", want: "",
}, },
{
name: "garbage",
args: "6080604052600436106100225760003560e01c80630cbcae701461003957610031565b366100315761002f610077565b005b61002f610077565b34801561004557600080fd5b5061004e61014e565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b7f000000000000000000000000000000000000000000000000000000000000000061011c565b60043560601b60601c6bca11c0de15dead10cced00006000195460a01c036100e9577f696d706c6f63000000000000000000000000000000000000000000000000000060005260206000fd5b8060001955005b60405136810160405236600082376000803683600019545af43d6000833e80610117573d82fd5b503d81f35b80330361014357602436036101435763ca11c0de60003560e01c036101435761014361009d565b61014b6100f0565b50565b600073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff541660005260206000f3fea2646970667358221220f27ad3f3b75609baa5d26d65ec1001c4a59f38e89088d6b47517c1cd1faf22ab64736f6c634300080d0033",
want: "",
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -537,23 +537,37 @@ type rpcTraceResult struct {
Result rpcCallTrace `json:"result"` Result rpcCallTrace `json:"result"`
} }
func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInternalData) { func (b *EthereumRPC) getCreationContractInfo(contract string, height uint32) *bchain.ContractInfo {
ci, err := b.fetchContractInfo(contract)
if ci == nil || err != nil {
ci = &bchain.ContractInfo{
Contract: contract,
}
}
ci.Type = bchain.UnknownTokenType
ci.CreatedInBlock = height
return ci
}
func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInternalData, contracts []bchain.ContractInfo, blockHeight uint32) []bchain.ContractInfo {
value, err := hexutil.DecodeBig(call.Value) value, err := hexutil.DecodeBig(call.Value)
if call.Type == "CREATE" { if call.Type == "CREATE" {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.CREATE, Type: bchain.CREATE,
Value: *value, Value: *value,
From: call.From, From: call.From,
To: call.To, To: call.To, // new contract address
}) })
contracts = append(contracts, *b.getCreationContractInfo(call.To, blockHeight))
} else if call.Type == "SELFDESTRUCT" { } else if call.Type == "SELFDESTRUCT" {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.SELFDESTRUCT, Type: bchain.SELFDESTRUCT,
Value: *value, Value: *value,
From: call.From, From: call.From, // destroyed contract address
To: call.To, To: call.To,
}) })
contracts = append(contracts, bchain.ContractInfo{Contract: call.From, DestructedInBlock: blockHeight})
} else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) { } else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Value: *value, Value: *value,
@ -565,13 +579,15 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
d.Error = call.Error d.Error = call.Error
} }
for i := range call.Calls { for i := range call.Calls {
b.processCallTrace(&call.Calls[i], d) contracts = b.processCallTrace(&call.Calls[i], d, contracts, blockHeight)
} }
return contracts
} }
// getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts // getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, error) { func (b *EthereumRPC) getInternalDataForBlock(blockHash string, blockHeight uint32, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, []bchain.ContractInfo, error) {
data := make([]bchain.EthereumInternalData, len(transactions)) data := make([]bchain.EthereumInternalData, len(transactions))
contracts := make([]bchain.ContractInfo, 0)
if ProcessInternalTransactions { if ProcessInternalTransactions {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout) ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel() defer cancel()
@ -579,11 +595,11 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
err := b.rpc.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"}) err := b.rpc.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"})
if err != nil { if err != nil {
glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err) glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err)
return data, err return data, contracts, err
} }
if len(trace) != len(data) { if len(trace) != len(data) {
glog.Error("debug_traceBlockByHash block ", blockHash, ", error: trace length does not match block length ", len(trace), "!=", len(data)) glog.Error("debug_traceBlockByHash block ", blockHash, ", error: trace length does not match block length ", len(trace), "!=", len(data))
return data, err return data, contracts, err
} }
for i, result := range trace { for i, result := range trace {
r := &result.Result r := &result.Result
@ -591,11 +607,12 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
if r.Type == "CREATE" { if r.Type == "CREATE" {
d.Type = bchain.CREATE d.Type = bchain.CREATE
d.Contract = r.To d.Contract = r.To
contracts = append(contracts, *b.getCreationContractInfo(d.Contract, blockHeight))
} else if r.Type == "SELFDESTRUCT" { } else if r.Type == "SELFDESTRUCT" {
d.Type = bchain.SELFDESTRUCT d.Type = bchain.SELFDESTRUCT
} }
for j := range r.Calls { for j := range r.Calls {
b.processCallTrace(&r.Calls[j], d) contracts = b.processCallTrace(&r.Calls[j], d, contracts, blockHeight)
} }
if r.Error != "" { if r.Error != "" {
baseError := PackInternalTransactionError(r.Error) baseError := PackInternalTransactionError(r.Error)
@ -620,7 +637,7 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
} }
} }
} }
return data, nil return data, contracts, nil
} }
// GetBlock returns block with given hash or height, hash has precedence if both passed // GetBlock returns block with given hash or height, hash has precedence if both passed
@ -642,15 +659,16 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
} }
// get block events // get block events
// TODO - could be possibly done in parallel to getInternalDataForBlock
logs, ens, err := b.processEventsForBlock(head.Number) logs, ens, err := b.processEventsForBlock(head.Number)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// error fetching internal data does not stop the block processing // error fetching internal data does not stop the block processing
var blockSpecificData *bchain.EthereumBlockSpecificData var blockSpecificData *bchain.EthereumBlockSpecificData
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions) internalData, contracts, err := b.getInternalDataForBlock(head.Hash, bbh.Height, body.Transactions)
// pass internalData error and ENS records in blockSpecificData to be stored // pass internalData error and ENS records in blockSpecificData to be stored
if err != nil || len(ens) > 0 { if err != nil || len(ens) > 0 || len(contracts) > 0 {
blockSpecificData = &bchain.EthereumBlockSpecificData{} blockSpecificData = &bchain.EthereumBlockSpecificData{}
if err != nil { if err != nil {
blockSpecificData.InternalDataError = err.Error() blockSpecificData.InternalDataError = err.Error()
@ -658,7 +676,11 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
} }
if len(ens) > 0 { if len(ens) > 0 {
blockSpecificData.AddressAliasRecords = ens blockSpecificData.AddressAliasRecords = ens
glog.Info("ENS", ens) // glog.Info("ENS", ens)
}
if len(contracts) > 0 {
blockSpecificData.Contracts = contracts
// glog.Info("Contracts", contracts)
} }
} }

View File

@ -113,6 +113,27 @@ type MempoolTx struct {
CoinSpecificData interface{} `json:"-"` CoinSpecificData interface{} `json:"-"`
} }
// TokenType - type of token
type TokenType int
// TokenType enumeration
const (
FungibleToken = TokenType(iota) // ERC20
NonFungibleToken // ERC721
MultiToken // ERC1155
)
// TokenTypeName specifies type of token
type TokenTypeName string
// Token types
const (
UnknownTokenType TokenTypeName = ""
// XPUBAddressTokenType is address derived from xpub
XPUBAddressTokenType TokenTypeName = "XPUBAddress"
)
// TokenTransfers is array of TokenTransfer // TokenTransfers is array of TokenTransfer
type TokenTransfers []*TokenTransfer type TokenTransfers []*TokenTransfer
@ -286,13 +307,13 @@ type BlockChain interface {
EstimateFee(blocks int) (big.Int, error) EstimateFee(blocks int) (big.Int, error)
SendRawTransaction(tx string) (string, error) SendRawTransaction(tx string) (string, error)
GetMempoolEntry(txid string) (*MempoolEntry, error) GetMempoolEntry(txid string) (*MempoolEntry, error)
GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error)
// parser // parser
GetChainParser() BlockChainParser GetChainParser() BlockChainParser
// EthereumType specific // EthereumType specific
EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error)
EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error)
EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error)
EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error)
EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error)
} }

View File

@ -47,16 +47,6 @@ const (
SELFDESTRUCT SELFDESTRUCT
) )
// TokenTransferType - type of token transfer
type TokenTransferType int
// TokenTransferType enumeration
const (
ERC20 = TokenTransferType(iota)
ERC721
ERC1155
)
// EthereumInternalTransaction contains internal transfers // EthereumInternalTransaction contains internal transfers
type EthereumInternalData struct { type EthereumInternalData struct {
Type EthereumInternalTransactionType `json:"type"` Type EthereumInternalTransactionType `json:"type"`
@ -65,27 +55,41 @@ type EthereumInternalData struct {
Error string Error string
} }
// Erc20Contract contains info about ERC20 contract // ContractInfo contains info about ERC20 contract
type Erc20Contract struct { type ContractInfo struct {
Contract string `json:"contract"` Type TokenTypeName `json:"type"`
Name string `json:"name"` Contract string `json:"contract"`
Symbol string `json:"symbol"` Name string `json:"name"`
Decimals int `json:"decimals"` Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
CreatedInBlock uint32 `json:"createdInBlock,omitempty"`
DestructedInBlock uint32 `json:"destructedInBlock,omitempty"`
} }
type TokenTransferIdValue struct { // Ethereum token type names
const (
ERC20TokenType TokenTypeName = "ERC20"
ERC771TokenType TokenTypeName = "ERC721"
ERC1155TokenType TokenTypeName = "ERC1155"
)
// EthereumTokenTypeMap maps bchain.TokenType to TokenTypeName
// the map must match all bchain.TokenType to avoid index out of range panic
var EthereumTokenTypeMap []TokenTypeName = []TokenTypeName{ERC20TokenType, ERC771TokenType, ERC1155TokenType}
type MultiTokenValue struct {
Id big.Int Id big.Int
Value big.Int Value big.Int
} }
// TokenTransfer contains a single ERC20/ERC721/ERC1155 token transfer // TokenTransfer contains a single token transfer
type TokenTransfer struct { type TokenTransfer struct {
Type TokenTransferType Type TokenType
Contract string Contract string
From string From string
To string To string
Value big.Int Value big.Int
IdValues []TokenTransferIdValue MultiTokenValues []MultiTokenValue
} }
// RpcTransaction is returned by eth_getTransactionByHash // RpcTransaction is returned by eth_getTransactionByHash
@ -138,4 +142,5 @@ type AddressAliasRecord struct {
type EthereumBlockSpecificData struct { type EthereumBlockSpecificData struct {
InternalDataError string InternalDataError string
AddressAliasRecords []AddressAliasRecord AddressAliasRecords []AddressAliasRecord
Contracts []ContractInfo
} }

View File

@ -6,6 +6,7 @@ import (
"math/big" "math/big"
"sync" "sync"
vlq "github.com/bsm/go-vlq"
"github.com/flier/gorocksdb" "github.com/flier/gorocksdb"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/juju/errors" "github.com/juju/errors"
@ -18,12 +19,12 @@ const ContractIndexOffset = 2
// AddrContract is Contract address with number of transactions done by given address // AddrContract is Contract address with number of transactions done by given address
type AddrContract struct { type AddrContract struct {
Type bchain.TokenTransferType Type bchain.TokenType
Contract bchain.AddressDescriptor Contract bchain.AddressDescriptor
Txs uint Txs uint
Value big.Int // single value of ERC20 Value big.Int // single value of ERC20
Ids []big.Int // multiple ERC721 tokens Ids []big.Int // multiple ERC721 tokens
IdValues []bchain.TokenTransferIdValue // multiple ERC1155 tokens MultiTokenValues []bchain.MultiTokenValue // multiple ERC1155 tokens
} }
// AddrContracts contains number of transactions and contracts for an address // AddrContracts contains number of transactions and contracts for an address
@ -48,10 +49,10 @@ func packAddrContracts(acs *AddrContracts) []byte {
buf = append(buf, ac.Contract...) buf = append(buf, ac.Contract...)
l = packVaruint(uint(ac.Type)+ac.Txs<<2, varBuf) l = packVaruint(uint(ac.Type)+ac.Txs<<2, varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
if ac.Type == bchain.ERC20 { if ac.Type == bchain.FungibleToken {
l = packBigint(&ac.Value, varBuf) l = packBigint(&ac.Value, varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
} else if ac.Type == bchain.ERC721 { } else if ac.Type == bchain.NonFungibleToken {
l = packVaruint(uint(len(ac.Ids)), varBuf) l = packVaruint(uint(len(ac.Ids)), varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
for i := range ac.Ids { for i := range ac.Ids {
@ -59,12 +60,12 @@ func packAddrContracts(acs *AddrContracts) []byte {
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
} }
} else { // bchain.ERC1155 } else { // bchain.ERC1155
l = packVaruint(uint(len(ac.IdValues)), varBuf) l = packVaruint(uint(len(ac.MultiTokenValues)), varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
for i := range ac.IdValues { for i := range ac.MultiTokenValues {
l = packBigint(&ac.IdValues[i].Id, varBuf) l = packBigint(&ac.MultiTokenValues[i].Id, varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
l = packBigint(&ac.IdValues[i].Value, varBuf) l = packBigint(&ac.MultiTokenValues[i].Value, varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
} }
} }
@ -87,21 +88,21 @@ func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrCo
contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...) contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...)
txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:]) txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:])
buf = buf[eth.EthereumTypeAddressDescriptorLen+l:] buf = buf[eth.EthereumTypeAddressDescriptorLen+l:]
ttt := bchain.TokenTransferType(txs & 3) ttt := bchain.TokenType(txs & 3)
txs >>= 2 txs >>= 2
ac := AddrContract{ ac := AddrContract{
Type: ttt, Type: ttt,
Contract: contract, Contract: contract,
Txs: txs, Txs: txs,
} }
if ttt == bchain.ERC20 { if ttt == bchain.FungibleToken {
b, ll := unpackBigint(buf) b, ll := unpackBigint(buf)
buf = buf[ll:] buf = buf[ll:]
ac.Value = b ac.Value = b
} else { } else {
len, ll := unpackVaruint(buf) len, ll := unpackVaruint(buf)
buf = buf[ll:] buf = buf[ll:]
if ttt == bchain.ERC721 { if ttt == bchain.NonFungibleToken {
ac.Ids = make([]big.Int, len) ac.Ids = make([]big.Int, len)
for i := uint(0); i < len; i++ { for i := uint(0); i < len; i++ {
b, ll := unpackBigint(buf) b, ll := unpackBigint(buf)
@ -109,14 +110,14 @@ func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrCo
ac.Ids[i] = b ac.Ids[i] = b
} }
} else { } else {
ac.IdValues = make([]bchain.TokenTransferIdValue, len) ac.MultiTokenValues = make([]bchain.MultiTokenValue, len)
for i := uint(0); i < len; i++ { for i := uint(0); i < len; i++ {
b, ll := unpackBigint(buf) b, ll := unpackBigint(buf)
buf = buf[ll:] buf = buf[ll:]
ac.IdValues[i].Id = b ac.MultiTokenValues[i].Id = b
b, ll = unpackBigint(buf) b, ll = unpackBigint(buf)
buf = buf[ll:] buf = buf[ll:]
ac.IdValues[i].Value = b ac.MultiTokenValues[i].Value = b
} }
} }
} }
@ -226,9 +227,9 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
s.Add(s, v) s.Add(s, v)
} }
} }
if transfer.Type == bchain.ERC20 { if transfer.Type == bchain.FungibleToken {
aggregate(&c.Value, &transfer.Value) aggregate(&c.Value, &transfer.Value)
} else if transfer.Type == bchain.ERC721 { } else if transfer.Type == bchain.NonFungibleToken {
if index < 0 { if index < 0 {
// remove token from the list // remove token from the list
for i := range c.Ids { for i := range c.Ids {
@ -242,14 +243,14 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
c.Ids = append(c.Ids, transfer.Value) c.Ids = append(c.Ids, transfer.Value)
} }
} else { // bchain.ERC1155 } else { // bchain.ERC1155
for _, t := range transfer.IdValues { for _, t := range transfer.MultiTokenValues {
for i := range c.IdValues { for i := range c.MultiTokenValues {
// find the token in the list // find the token in the list
if c.IdValues[i].Id.Cmp(&t.Id) == 0 { if c.MultiTokenValues[i].Id.Cmp(&t.Id) == 0 {
aggregate(&c.IdValues[i].Value, &t.Value) aggregate(&c.MultiTokenValues[i].Value, &t.Value)
// if transfer from, remove if the value is zero // if transfer from, remove if the value is zero
if index < 0 && len(c.IdValues[i].Value.Bits()) == 0 { if index < 0 && len(c.MultiTokenValues[i].Value.Bits()) == 0 {
c.IdValues = append(c.IdValues[:i], c.IdValues[i+1:]...) c.MultiTokenValues = append(c.MultiTokenValues[:i], c.MultiTokenValues[i+1:]...)
} }
goto nextTransfer goto nextTransfer
} }
@ -257,7 +258,7 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
// if not found and transfer to, add to the list // if not found and transfer to, add to the list
// it is necessary to add a copy of the value so that subsequent calls to addToContract do not change the transfer value // it is necessary to add a copy of the value so that subsequent calls to addToContract do not change the transfer value
if index >= 0 { if index >= 0 {
c.IdValues = append(c.IdValues, bchain.TokenTransferIdValue{ c.MultiTokenValues = append(c.MultiTokenValues, bchain.MultiTokenValue{
Id: t.Id, Id: t.Id,
Value: *new(big.Int).Set(&t.Value), Value: *new(big.Int).Set(&t.Value),
}) })
@ -327,9 +328,9 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address
type ethBlockTxContract struct { type ethBlockTxContract struct {
from, to, contract bchain.AddressDescriptor from, to, contract bchain.AddressDescriptor
transferType bchain.TokenTransferType transferType bchain.TokenType
value big.Int value big.Int
idValues []bchain.TokenTransferIdValue idValues []bchain.MultiTokenValue
} }
type ethInternalTransfer struct { type ethInternalTransfer struct {
@ -476,7 +477,7 @@ func (d *RocksDB) processContractTransfers(blockTx *ethBlockTx, tx *bchain.Tx, a
bc.to = to bc.to = to
bc.contract = contract bc.contract = contract
bc.value = t.Value bc.value = t.Value
bc.idValues = t.IdValues bc.idValues = t.MultiTokenValues
} }
return nil return nil
} }
@ -699,6 +700,113 @@ func (d *RocksDB) storeInternalDataEthereumType(wb *gorocksdb.WriteBatch, blockT
return nil return nil
} }
var cachedContracts = make(map[string]*bchain.ContractInfo)
var cachedContractsMux sync.Mutex
func packContractInfo(contractInfo *bchain.ContractInfo) []byte {
buf := packString(contractInfo.Name)
buf = append(buf, packString(contractInfo.Symbol)...)
buf = append(buf, packString(string(contractInfo.Type))...)
varBuf := make([]byte, vlq.MaxLen64)
l := packVaruint(uint(contractInfo.Decimals), varBuf)
buf = append(buf, varBuf[:l]...)
l = packVaruint(uint(contractInfo.CreatedInBlock), varBuf)
buf = append(buf, varBuf[:l]...)
l = packVaruint(uint(contractInfo.DestructedInBlock), varBuf)
buf = append(buf, varBuf[:l]...)
return buf
}
func unpackContractInfo(buf []byte) (*bchain.ContractInfo, error) {
var contractInfo bchain.ContractInfo
var s string
var l int
var ui uint
contractInfo.Name, l = unpackString(buf)
buf = buf[l:]
contractInfo.Symbol, l = unpackString(buf)
buf = buf[l:]
s, l = unpackString(buf)
contractInfo.Type = bchain.TokenTypeName(s)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.Decimals = int(ui)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.CreatedInBlock = uint32(ui)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.DestructedInBlock = uint32(ui)
return &contractInfo, nil
}
func (d *RocksDB) GetContractInfoForAddress(address string) (*bchain.ContractInfo, error) {
contract, err := d.chainParser.GetAddrDescFromAddress(address)
if err != nil || contract == nil {
return nil, err
}
return d.GetContractInfo(contract, "")
}
// GetContractInfo gets contract from cache or DB and possibly updates the type from typeFromContext
// this is because it is hard to guess the type of the contract using API, it is easier to set it the first time its usage is detected in tx
func (d *RocksDB) GetContractInfo(contract bchain.AddressDescriptor, typeFromContext bchain.TokenTypeName) (*bchain.ContractInfo, error) {
cacheKey := string(contract)
cachedContractsMux.Lock()
contractInfo, found := cachedContracts[cacheKey]
cachedContractsMux.Unlock()
if !found {
val, err := d.db.GetCF(d.ro, d.cfh[cfContracts], contract)
if err != nil {
return nil, err
}
defer val.Free()
buf := val.Data()
if len(buf) == 0 {
return nil, nil
}
contractInfo, err = unpackContractInfo(buf)
addresses, _, _ := d.chainParser.GetAddressesFromAddrDesc(contract)
if len(addresses) > 0 {
contractInfo.Contract = addresses[0]
}
// if the type is specified and stored contractInfo has unknown type, set and store it
if typeFromContext != bchain.UnknownTokenType && contractInfo.Type == bchain.UnknownTokenType {
contractInfo.Type = typeFromContext
err = d.db.PutCF(d.wo, d.cfh[cfContracts], contract, packContractInfo(contractInfo))
}
cachedContractsMux.Lock()
cachedContracts[cacheKey] = contractInfo
cachedContractsMux.Unlock()
}
return contractInfo, nil
}
// StoreContractInfo stores contractInfo in DB
// if CreatedInBlock==0 and DestructedInBlock!=0, it is evaluated as a desctruction of a contract, the contract info is updated
// in all other cases the contractInfo overwrites previously stored data in DB (however it should not really happen as contract is created only once)
func (d *RocksDB) StoreContractInfo(wb *gorocksdb.WriteBatch, contractInfo *bchain.ContractInfo) error {
if contractInfo.Contract != "" {
key, err := d.chainParser.GetAddrDescFromAddress(contractInfo.Contract)
if err != nil {
return err
}
if contractInfo.CreatedInBlock == 0 && contractInfo.DestructedInBlock != 0 {
storedCI, err := d.GetContractInfo(key, "")
if err != nil {
return err
}
if storedCI == nil {
return nil
}
storedCI.DestructedInBlock = contractInfo.DestructedInBlock
contractInfo = storedCI
}
wb.PutCF(d.cfh[cfContracts], key, packContractInfo(contractInfo))
}
return nil
}
func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte { func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte {
varBuf := make([]byte, maxPackedBigintBytes) varBuf := make([]byte, maxPackedBigintBytes)
buf = append(buf, blockTx.btxID...) buf = append(buf, blockTx.btxID...)
@ -715,7 +823,7 @@ func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte {
buf = appendAddress(buf, c.contract) buf = appendAddress(buf, c.contract)
l = packVaruint(uint(c.transferType), varBuf) l = packVaruint(uint(c.transferType), varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
if c.transferType == bchain.ERC1155 { if c.transferType == bchain.MultiToken {
l = packVaruint(uint(len(c.idValues)), varBuf) l = packVaruint(uint(len(c.idValues)), varBuf)
buf = append(buf, varBuf[:l]...) buf = append(buf, varBuf[:l]...)
for i := range c.idValues { for i := range c.idValues {
@ -773,6 +881,11 @@ func (d *RocksDB) storeBlockSpecificDataEthereumType(wb *gorocksdb.WriteBatch, b
return err return err
} }
} }
for i := range blockSpecificData.Contracts {
if err := d.StoreContractInfo(wb, &blockSpecificData.Contracts[i]); err != nil {
return err
}
}
} }
return nil return nil
} }
@ -823,12 +936,12 @@ func unpackBlockTx(buf []byte, pos int) (*ethBlockTx, int, error) {
return nil, 0, err return nil, 0, err
} }
cc, l = unpackVaruint(buf[pos:]) cc, l = unpackVaruint(buf[pos:])
c.transferType = bchain.TokenTransferType(cc) c.transferType = bchain.TokenType(cc)
pos += l pos += l
if c.transferType == bchain.ERC1155 { if c.transferType == bchain.MultiToken {
cc, l = unpackVaruint(buf[pos:]) cc, l = unpackVaruint(buf[pos:])
pos += l pos += l
c.idValues = make([]bchain.TokenTransferIdValue, cc) c.idValues = make([]bchain.MultiTokenValue, cc)
for i := range c.idValues { for i := range c.idValues {
c.idValues[i].Id, l = unpackBigint(buf[pos:]) c.idValues[i].Id, l = unpackBigint(buf[pos:])
pos += l pos += l
@ -938,9 +1051,9 @@ func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain
index = transferTo index = transferTo
} }
addToContract(addrContract, contractIndex, index, btxContract.contract, &bchain.TokenTransfer{ addToContract(addrContract, contractIndex, index, btxContract.contract, &bchain.TokenTransfer{
Type: btxContract.transferType, Type: btxContract.transferType,
Value: btxContract.value, Value: btxContract.value,
IdValues: btxContract.idValues, MultiTokenValues: btxContract.idValues,
}, false) }, false)
} }
} else { } else {

View File

@ -58,11 +58,11 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil},
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"), nil, "020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil, "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintToHex(big.NewInt(0)), nil,
}, },
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil},
@ -72,6 +72,25 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
} }
} }
var destructedInBlock uint
if afterDisconnect {
destructedInBlock = 44445
}
if err := checkColumn(d, cfContracts, []keyPair{
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser),
"0b436f6e7472616374203734" + // Contract 74
"03533734" + // S74
"054552433230" + // ERC20
varuintToHex(12) + varuintToHex(44444) + varuintToHex(destructedInBlock),
nil,
},
}); err != nil {
{
t.Fatal(err)
}
}
if err := checkColumn(d, cfInternalData, []keyPair{ if err := checkColumn(d, cfInternalData, []keyPair{
{ {
dbtestdata.EthTxidB1T2, dbtestdata.EthTxidB1T2,
@ -98,7 +117,7 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" +
dbtestdata.EthTxidB1T2 + dbtestdata.EthTxidB1T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, 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"), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"),
nil, nil,
}, },
} }
@ -158,43 +177,43 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
if err := checkColumn(d, cfAddressContracts, []keyPair{ if err := checkColumn(d, cfAddressContracts, []keyPair{
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil, "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintToHex(big.NewInt(0)), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), 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, "030202" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(1) + bigintFromStringToHex("150") + bigintFromStringToHex("1"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser),
"010101" + "010101" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("8086") + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("8086") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184"), nil, dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("871180000950184"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"050300" + "050300" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000854307892726464") + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0"), nil, dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser), 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, "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(2) + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser),
"020000" + "020000" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(1) + bigintFromStringToHex("1"), nil, dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.NonFungibleToken)) + varuintToHex(1) + bigintFromStringToHex("1"), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(0), nil, "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.NonFungibleToken)) + varuintToHex(0), nil,
}, },
{ {
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser), dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser),
"010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(0), nil, "010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(0), nil,
}, },
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser), "010100", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser), "010100", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "030104", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "030104", nil},
@ -209,6 +228,21 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
} }
} }
if err := checkColumn(d, cfContracts, []keyPair{
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser),
"0b436f6e7472616374203734" + // Contract 74
"03533734" + // S74
"054552433230" + // ERC20
varuintToHex(12) + varuintToHex(44444) + varuintToHex(44445),
nil,
},
}); err != nil {
{
t.Fatal(err)
}
}
if err := checkColumn(d, cfInternalData, []keyPair{ if err := checkColumn(d, cfInternalData, []keyPair{
{ {
dbtestdata.EthTxidB1T2, dbtestdata.EthTxidB1T2,
@ -243,22 +277,22 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" +
dbtestdata.EthTxidB2T2 + dbtestdata.EthTxidB2T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, 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") + "04" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + 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.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + 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.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + 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.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.EthTxidB2T3 + dbtestdata.EthTxidB2T3 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + 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") + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(uint(bchain.NonFungibleToken)) + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T4 + dbtestdata.EthTxidB2T4 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser) + 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") + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.MultiToken)) + "01" + bigintFromStringToHex("150") + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T5 + dbtestdata.EthTxidB2T5 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + 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") + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrZero, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.MultiToken)) + "02" + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10") +
dbtestdata.EthTxidB2T6 + dbtestdata.EthTxidB2T6 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + 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"), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"),
nil, nil,
}, },
}); err != nil { }); err != nil {
@ -722,13 +756,13 @@ func Test_packUnpackAddrContracts(t *testing.T) {
InternalTxs: 8873, InternalTxs: 8873,
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser),
Txs: 8, Txs: 8,
Value: *big.NewInt(793201132), Value: *big.NewInt(793201132),
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 41235, Txs: 41235,
Ids: []big.Int{ Ids: []big.Int{
@ -740,10 +774,10 @@ func Test_packUnpackAddrContracts(t *testing.T) {
}, },
}, },
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
Txs: 64, Txs: 64,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(1), Id: *big.NewInt(1),
Value: *big.NewInt(1412341234), Value: *big.NewInt(1412341234),
@ -796,7 +830,7 @@ func Test_addToContracts(t *testing.T) {
index: 1, index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC20, Type: bchain.FungibleToken,
Value: *big.NewInt(123456), Value: *big.NewInt(123456),
}, },
addTxCount: true, addTxCount: true,
@ -805,7 +839,7 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 1, Txs: 1,
Value: *big.NewInt(123456), Value: *big.NewInt(123456),
@ -819,7 +853,7 @@ func Test_addToContracts(t *testing.T) {
index: ^1, index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC20, Type: bchain.FungibleToken,
Value: *big.NewInt(23456), Value: *big.NewInt(23456),
}, },
addTxCount: true, addTxCount: true,
@ -828,7 +862,7 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
@ -842,7 +876,7 @@ func Test_addToContracts(t *testing.T) {
index: 1, index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Value: *big.NewInt(1), Value: *big.NewInt(1),
}, },
addTxCount: true, addTxCount: true,
@ -851,13 +885,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 1, Txs: 1,
Ids: []big.Int{*big.NewInt(1)}, Ids: []big.Int{*big.NewInt(1)},
@ -871,7 +905,7 @@ func Test_addToContracts(t *testing.T) {
index: 1, index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Value: *big.NewInt(2), Value: *big.NewInt(2),
}, },
addTxCount: true, addTxCount: true,
@ -880,13 +914,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2, Txs: 2,
Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)}, Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)},
@ -900,7 +934,7 @@ func Test_addToContracts(t *testing.T) {
index: ^1, index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Value: *big.NewInt(1), Value: *big.NewInt(1),
}, },
addTxCount: false, addTxCount: false,
@ -909,13 +943,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2, Txs: 2,
Ids: []big.Int{*big.NewInt(2)}, Ids: []big.Int{*big.NewInt(2)},
@ -929,8 +963,8 @@ func Test_addToContracts(t *testing.T) {
index: 1, index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155, Type: bchain.MultiToken,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(56789), Value: *big.NewInt(56789),
@ -943,22 +977,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2, Txs: 2,
Ids: []big.Int{*big.NewInt(2)}, Ids: []big.Int{*big.NewInt(2)},
}, },
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 1, Txs: 1,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(56789), Value: *big.NewInt(56789),
@ -974,8 +1008,8 @@ func Test_addToContracts(t *testing.T) {
index: 1, index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155, Type: bchain.MultiToken,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(111), Value: *big.NewInt(111),
@ -992,22 +1026,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2, Txs: 2,
Ids: []big.Int{*big.NewInt(2)}, Ids: []big.Int{*big.NewInt(2)},
}, },
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 2, Txs: 2,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(56900), Value: *big.NewInt(56900),
@ -1027,8 +1061,8 @@ func Test_addToContracts(t *testing.T) {
index: ^1, index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{ transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155, Type: bchain.MultiToken,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(112), Value: *big.NewInt(112),
@ -1045,22 +1079,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{ wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{ Contracts: []AddrContract{
{ {
Type: bchain.ERC20, Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000), Value: *big.NewInt(100000),
Txs: 2, Txs: 2,
}, },
{ {
Type: bchain.ERC721, Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2, Txs: 2,
Ids: []big.Int{*big.NewInt(2)}, Ids: []big.Int{*big.NewInt(2)},
}, },
{ {
Type: bchain.ERC1155, Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 3, Txs: 3,
IdValues: []bchain.TokenTransferIdValue{ MultiTokenValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(11), Id: *big.NewInt(11),
Value: *big.NewInt(56788), Value: *big.NewInt(56788),
@ -1119,7 +1153,7 @@ func Test_packUnpackBlockTx(t *testing.T) {
from: addressToAddrDesc(dbtestdata.EthAddr20, parser), from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr5d, parser), to: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20, transferType: bchain.FungibleToken,
value: *big.NewInt(10000), value: *big.NewInt(10000),
}, },
}, },
@ -1137,22 +1171,22 @@ func Test_packUnpackBlockTx(t *testing.T) {
from: addressToAddrDesc(dbtestdata.EthAddr20, parser), from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr3e, parser), to: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20, transferType: bchain.FungibleToken,
value: *big.NewInt(987654321), value: *big.NewInt(987654321),
}, },
{ {
from: addressToAddrDesc(dbtestdata.EthAddr4b, parser), from: addressToAddrDesc(dbtestdata.EthAddr4b, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser), to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transferType: bchain.ERC721, transferType: bchain.NonFungibleToken,
value: *big.NewInt(13), value: *big.NewInt(13),
}, },
{ {
from: addressToAddrDesc(dbtestdata.EthAddr5d, parser), from: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
to: addressToAddrDesc(dbtestdata.EthAddr7b, parser), to: addressToAddrDesc(dbtestdata.EthAddr7b, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transferType: bchain.ERC1155, transferType: bchain.MultiToken,
idValues: []bchain.TokenTransferIdValue{ idValues: []bchain.MultiTokenValue{
{ {
Id: *big.NewInt(1234), Id: *big.NewInt(1234),
Value: *big.NewInt(98765), Value: *big.NewInt(98765),
@ -1222,3 +1256,45 @@ func Test_packUnpackFourByteSignature(t *testing.T) {
}) })
} }
} }
func Test_packUnpackContractInfo(t *testing.T) {
tests := []struct {
name string
contractInfo bchain.ContractInfo
}{
{
name: "empty",
contractInfo: bchain.ContractInfo{},
},
{
name: "unknown",
contractInfo: bchain.ContractInfo{
Type: bchain.UnknownTokenType,
Name: "Test contract",
Symbol: "TCT",
Decimals: 18,
CreatedInBlock: 1234567,
DestructedInBlock: 234567890,
},
},
{
name: "ERC20",
contractInfo: bchain.ContractInfo{
Type: bchain.ERC20TokenType,
Name: "GreenContract🟢",
Symbol: "🟢",
Decimals: 0,
CreatedInBlock: 1,
DestructedInBlock: 2,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := packContractInfo(&tt.contractInfo)
if got, err := unpackContractInfo(buf); !reflect.DeepEqual(*got, tt.contractInfo) || err != nil {
t.Errorf("packUnpackContractInfo() = %v, want %v, error %v", *got, tt.contractInfo, err)
}
})
}
}

View File

@ -562,7 +562,7 @@ func isOwnAddress(td *TemplateData, a string) bool {
} }
// called from template, returns count of token transfers of given type in a tx // called from template, returns count of token transfers of given type in a tx
func tokenTransfersCount(tx *api.Tx, t api.TokenType) int { func tokenTransfersCount(tx *api.Tx, t bchain.TokenTypeName) int {
count := 0 count := 0
for i := range tx.TokenTransfers { for i := range tx.TokenTransfers {
if tx.TokenTransfers[i].Type == t { if tx.TokenTransfers[i].Type == t {
@ -573,7 +573,7 @@ func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
} }
// called from template, returns count of tokens in array of given type // called from template, returns count of tokens in array of given type
func tokenCount(tokens []api.Token, t api.TokenType) int { func tokenCount(tokens []api.Token, t bchain.TokenTypeName) int {
count := 0 count := 0
for i := range tokens { for i := range tokens {
if tokens[i].Type == t { if tokens[i].Type == t {

File diff suppressed because one or more lines are too long

View File

@ -896,7 +896,7 @@ func (s *WebsocketServer) sendOnNewTxAddr(stringAddressDescriptor string, tx *ap
} }
func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string]struct{} { func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string]struct{} {
// check if there is any subscription in inputs, outputs and erc20 // check if there is any subscription in inputs, outputs and token transfers
s.addressSubscriptionsLock.Lock() s.addressSubscriptionsLock.Lock()
defer s.addressSubscriptionsLock.Unlock() defer s.addressSubscriptionsLock.Unlock()
subscribed := make(map[string]struct{}) subscribed := make(map[string]struct{})

View File

@ -1,5 +1,5 @@
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}} {{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
<h1>{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}} <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small> <h1>{{if $addr.ContractInfo}}Contract {{$addr.ContractInfo.Name}} ({{$addr.ContractInfo.Symbol}}){{else}}Address{{end}} <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small>
</h1> </h1>
<div class="alert alert-data ellipsis"> <div class="alert alert-data ellipsis">
<span class="data">{{$addr.AddrStr}}</span> <span class="data">{{$addr.AddrStr}}</span>
@ -98,7 +98,7 @@
<tr> <tr>
<td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td> <td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
<td class="data"> <td class="data">
{{range $i, $iv := $t.IdValues}}{{if $i}}, {{end}}{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$t.Symbol}}{{end}} {{range $i, $iv := $t.MultiTokenValues}}{{if $i}}, {{end}}{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$t.Symbol}}{{end}}
</td> </td>
<td class="data">{{$t.Transfers}}</td> <td class="data">{{$t.Transfers}}</td>
</tr> </tr>

View File

@ -267,7 +267,7 @@
</div> </div>
</div> </div>
<div class="col-md-3 text-right" style="padding: .4rem 0;"> <div class="col-md-3 text-right" style="padding: .4rem 0;">
{{- range $iv := $tt.Values -}} {{- range $iv := $tt.MultiTokenValues -}}
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}} {{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}}
{{- end -}} {{- end -}}
</div> </div>

View File

@ -128,6 +128,19 @@ var EthTx4InternalData = &bchain.EthereumInternalData{
}, },
} }
var Block1SpecificData = &bchain.EthereumBlockSpecificData{
Contracts: []bchain.ContractInfo{
{
Contract: EthAddrContract4a,
Type: bchain.ERC20TokenType,
Name: "Contract 74",
Symbol: "S74",
Decimals: 12,
CreatedInBlock: 44444,
},
},
}
var Block2SpecificData = &bchain.EthereumBlockSpecificData{ var Block2SpecificData = &bchain.EthereumBlockSpecificData{
InternalDataError: "test error", InternalDataError: "test error",
AddressAliasRecords: []bchain.AddressAliasRecord{ AddressAliasRecords: []bchain.AddressAliasRecord{
@ -140,6 +153,12 @@ var Block2SpecificData = &bchain.EthereumBlockSpecificData{
Name: "address20", Name: "address20",
}, },
}, },
Contracts: []bchain.ContractInfo{
{
Contract: EthAddrContract4a,
DestructedInBlock: 44445,
},
},
} }
type packedAndInternal struct { type packedAndInternal struct {
@ -182,6 +201,7 @@ func GetTestEthereumTypeBlock1(parser bchain.BlockChainParser) *bchain.Block {
packed: EthTx2Packed, packed: EthTx2Packed,
internal: EthTx2InternalData, internal: EthTx2InternalData,
}}, parser), }}, parser),
CoinSpecificData: Block1SpecificData,
} }
} }

View File

@ -117,13 +117,15 @@ func (c *fakeBlockChainEthereumType) EthereumTypeGetNonce(addrDesc bchain.Addres
return uint64(addrDesc[0]), nil return uint64(addrDesc[0]), nil
} }
func (c *fakeBlockChainEthereumType) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { func (c *fakeBlockChainEthereumType) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) {
addresses, _, _ := c.Parser.GetAddressesFromAddrDesc(contractDesc) addresses, _, _ := c.Parser.GetAddressesFromAddrDesc(contractDesc)
return &bchain.Erc20Contract{ return &bchain.ContractInfo{
Contract: addresses[0], Type: bchain.ERC20TokenType,
Name: "Contract " + strconv.Itoa(int(contractDesc[0])), Contract: addresses[0],
Symbol: "S" + strconv.Itoa(int(contractDesc[0])), Name: "Contract " + strconv.Itoa(int(contractDesc[0])),
Decimals: 18, Symbol: "S" + strconv.Itoa(int(contractDesc[0])),
Decimals: 18,
CreatedInBlock: 12345,
}, nil }, nil
} }