From db91824dc36d0af1517c8553294cf33423d5b483 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Sun, 1 May 2022 02:41:56 +0200 Subject: [PATCH] Store contract info in DB --- api/types.go | 98 ++++------ api/worker.go | 131 +++++++------ api/xpub.go | 2 +- bchain/basechain.go | 4 +- bchain/coins/blockchain.go | 8 +- bchain/coins/eth/contract.go | 133 ++++++------- bchain/coins/eth/contract_test.go | 20 +- bchain/coins/eth/dataparser.go | 4 +- bchain/coins/eth/dataparser_test.go | 5 + bchain/coins/eth/ethrpc.go | 46 +++-- bchain/types.go | 23 ++- bchain/types_ethereum_type.go | 53 ++--- db/rocksdb_ethereumtype.go | 187 ++++++++++++++---- db/rocksdb_ethereumtype_test.go | 204 ++++++++++++++------ server/public.go | 4 +- server/public_ethereumtype_test.go | 12 +- server/websocket.go | 2 +- static/templates/address.html | 4 +- static/templates/txdetail_ethereumtype.html | 2 +- tests/dbtestdata/dbtestdata_ethereumtype.go | 20 ++ tests/dbtestdata/fakechain_ethereumtype.go | 14 +- 21 files changed, 616 insertions(+), 360 deletions(-) diff --git a/api/types.go b/api/types.go index f801d9f4..c8241e36 100644 --- a/api/types.go +++ b/api/types.go @@ -135,58 +135,40 @@ type Vout struct { Type string `json:"type,omitempty"` } -// TokenType specifies type of token -type TokenType string - -// 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 { +// MultiTokenValue contains values for contract with id and value (like ERC1155) +type MultiTokenValue struct { Id *Amount `json:"id,omitempty"` Value *Amount `json:"value,omitempty"` } // Token contains info about tokens held by an address type Token struct { - Type TokenType `json:"type"` - Name string `json:"name"` - Path string `json:"path,omitempty"` - Contract string `json:"contract,omitempty"` - Transfers int `json:"transfers"` - Symbol string `json:"symbol,omitempty"` - Decimals int `json:"decimals,omitempty"` - BalanceSat *Amount `json:"balance,omitempty"` - Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens - IdValues []TokenTransferValues `json:"idValues,omitempty"` // multiple ERC1155 tokens - TotalReceivedSat *Amount `json:"totalReceived,omitempty"` - TotalSentSat *Amount `json:"totalSent,omitempty"` - ContractIndex string `json:"-"` + Type bchain.TokenTypeName `json:"type"` + Name string `json:"name"` + Path string `json:"path,omitempty"` + Contract string `json:"contract,omitempty"` + Transfers int `json:"transfers"` + Symbol string `json:"symbol,omitempty"` + Decimals int `json:"decimals,omitempty"` + BalanceSat *Amount `json:"balance,omitempty"` + Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens + MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"` // multiple ERC1155 tokens + TotalReceivedSat *Amount `json:"totalReceived,omitempty"` + TotalSentSat *Amount `json:"totalSent,omitempty"` + ContractIndex string `json:"-"` } // TokenTransfer contains info about a token transfer done in a transaction type TokenTransfer struct { - Type TokenType `json:"type"` - From string `json:"from"` - To string `json:"to"` - Token string `json:"token"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals int `json:"decimals"` - Value *Amount `json:"value,omitempty"` - Values []TokenTransferValues `json:"values,omitempty"` + Type bchain.TokenTypeName `json:"type"` + From string `json:"from"` + To string `json:"to"` + Token string `json:"token"` + Name string `json:"name"` + Symbol string `json:"symbol"` + Decimals int `json:"decimals"` + Value *Amount `json:"value,omitempty"` + MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"` } type EthereumInternalTransfer struct { @@ -290,22 +272,22 @@ type AddressFilter struct { // Address holds information about address and its transactions type Address struct { Paging - AddrStr string `json:"address"` - BalanceSat *Amount `json:"balance"` - TotalReceivedSat *Amount `json:"totalReceived,omitempty"` - TotalSentSat *Amount `json:"totalSent,omitempty"` - UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"` - UnconfirmedTxs int `json:"unconfirmedTxs"` - Txs int `json:"txs"` - NonTokenTxs int `json:"nonTokenTxs,omitempty"` - InternalTxs int `json:"internalTxs,omitempty"` - Transactions []*Tx `json:"transactions,omitempty"` - Txids []string `json:"txids,omitempty"` - Nonce string `json:"nonce,omitempty"` - UsedTokens int `json:"usedTokens,omitempty"` - Tokens []Token `json:"tokens,omitempty"` - Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"` - AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"` + AddrStr string `json:"address"` + BalanceSat *Amount `json:"balance"` + TotalReceivedSat *Amount `json:"totalReceived,omitempty"` + TotalSentSat *Amount `json:"totalSent,omitempty"` + UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"` + UnconfirmedTxs int `json:"unconfirmedTxs"` + Txs int `json:"txs"` + NonTokenTxs int `json:"nonTokenTxs,omitempty"` + InternalTxs int `json:"internalTxs,omitempty"` + Transactions []*Tx `json:"transactions,omitempty"` + Txids []string `json:"txids,omitempty"` + Nonce string `json:"nonce,omitempty"` + UsedTokens int `json:"usedTokens,omitempty"` + Tokens []Token `json:"tokens,omitempty"` + ContractInfo *bchain.ContractInfo `json:"contractInfo,omitempty"` + AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"` // helpers for explorer Filter string `json:"-"` XPubAddresses map[string]struct{} `json:"-"` diff --git a/api/worker.go b/api/worker.go index 4445d12c..0fad5f2b 100644 --- a/api/worker.go +++ b/api/worker.go @@ -151,7 +151,10 @@ func (w *Worker) getAddressAliases(addresses map[string]struct{}) AddressAliases } for a := range addresses { 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) 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) continue } - erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd) + typeName := bchain.EthereumTokenTypeMap[t.Type] + contractInfo, err := w.db.GetContractInfo(cd, typeName) 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 { - erc20c = &bchain.Erc20Contract{Name: t.Contract} + if contractInfo == nil { + 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 values []TokenTransferValues - if t.Type == bchain.ERC1155 { - values = make([]TokenTransferValues, len(t.IdValues)) + var values []MultiTokenValue + if t.Type == bchain.MultiToken { + values = make([]MultiTokenValue, len(t.MultiTokenValues)) for j := range values { - values[j].Id = (*Amount)(&t.IdValues[j].Id) - values[j].Value = (*Amount)(&t.IdValues[j].Value) + values[j].Id = (*Amount)(&t.MultiTokenValues[j].Id) + values[j].Value = (*Amount)(&t.MultiTokenValues[j].Value) } } else { value = (*Amount)(&t.Value) @@ -556,15 +567,15 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, add aggregateAddress(addresses, t.From) aggregateAddress(addresses, t.To) tokens[i] = TokenTransfer{ - Type: TokenTypeMap[t.Type], - Token: t.Contract, - From: t.From, - To: t.To, - Value: value, - Values: values, - Decimals: erc20c.Decimals, - Name: erc20c.Name, - Symbol: erc20c.Symbol, + Type: typeName, + Token: t.Contract, + From: t.From, + To: t.To, + Value: value, + MultiTokenValues: values, + Decimals: contractInfo.Decimals, + Name: contractInfo.Name, + Symbol: contractInfo.Symbol, } } 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) { - // TODO use db.contracts validContract := true - - ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract) + typeName := bchain.EthereumTokenTypeMap[c.Type] + ci, err := w.db.GetContractInfo(c.Contract, typeName) if err != nil { - return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract) + return nil, errors.Annotatef(err, "GetContractInfo %v", c.Contract) } if ci == nil { - ci = &bchain.Erc20Contract{} - addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract) - if len(addresses) > 0 { - ci.Contract = addresses[0] - ci.Name = addresses[0] + glog.Warningf("Contract %v %v not found in DB", c.Contract, typeName) + ci, err = w.chain.GetContractInfo(c.Contract) + if err != nil { + glog.Errorf("GetContractInfo from chain error %v, contract %v", err, c.Contract) + } + 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{ Contract: ci.Contract, Name: ci.Name, Symbol: ci.Symbol, + Type: typeName, Transfers: int(c.Txs), Decimals: ci.Decimals, ContractIndex: strconv.Itoa(index), } // return contract balances/values only at or above AccountDetailsTokenBalances if details >= AccountDetailsTokenBalances && validContract { - if c.Type == bchain.ERC20 { - t.Type = ERC20TokenType + if c.Type == bchain.FungibleToken { // get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract) if err != nil { @@ -789,11 +806,6 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i t.BalanceSat = (*Amount)(b) } } else { - if c.Type == bchain.ERC721 { - t.Type = ERC771TokenType - } else { - t.Type = ERC1155TokenType - } if len(c.Ids) > 0 { ids := make([]Amount, len(c.Ids)) for j := range ids { @@ -801,13 +813,13 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i } t.Ids = ids } - if len(c.IdValues) > 0 { - idValues := make([]TokenTransferValues, len(c.IdValues)) + if len(c.MultiTokenValues) > 0 { + idValues := make([]MultiTokenValue, len(c.MultiTokenValues)) for j := range idValues { - idValues[j].Id = (*Amount)(&c.IdValues[j].Id) - idValues[j].Value = (*Amount)(&c.IdValues[j].Value) + idValues[j].Id = (*Amount)(&c.MultiTokenValues[j].Id) + 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) { var b *big.Int validContract := true - ci, err := w.chain.EthereumTypeGetErc20ContractInfo(contract) + ci, err := w.db.GetContractInfo(contract, "") if err != nil { - return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", contract) + return nil, errors.Annotatef(err, "GetContractInfo %v", contract) } if ci == nil { - ci = &bchain.Erc20Contract{} - addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract) - if len(addresses) > 0 { - ci.Contract = addresses[0] - ci.Name = addresses[0] + glog.Warningf("Contract %v not found in DB", contract) + ci, err = w.chain.GetContractInfo(contract) + if err != nil { + glog.Errorf("GetContractInfo from chain error %v, contract %v", err, contract) + } + 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 if details >= AccountDetailsTokenBalances && validContract { @@ -843,7 +862,7 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch b = nil } return &Token{ - Type: ERC20TokenType, + Type: ci.Type, BalanceSat: (*Amount)(b), Contract: ci.Contract, Name: ci.Name, @@ -854,11 +873,11 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch }, 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 ( ba *db.AddrBalance tokens []Token - ci *bchain.Erc20Contract + ci *bchain.ContractInfo n uint64 nonContractTxs int internalTxs int @@ -912,7 +931,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto } tokens = tokens[:j] } - ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc) + ci, err = w.db.GetContractInfo(addrDesc, "") if err != nil { 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 ( ba *db.AddrBalance tokens []Token - erc20c *bchain.Erc20Contract + contractInfo *bchain.ContractInfo txm []string txs []*Tx txids []string @@ -1062,7 +1081,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco } if w.chainType == bchain.ChainEthereumType { 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 { return nil, err } @@ -1173,7 +1192,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco Transactions: txs, Txids: txids, Tokens: tokens, - Erc20Contract: erc20c, + ContractInfo: contractInfo, Nonce: nonce, AddressAliases: w.getAddressAliases(addresses), } diff --git a/api/xpub.go b/api/xpub.go index 8737373b..b555e379 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -266,7 +266,7 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd } } return Token{ - Type: XPUBAddressTokenType, + Type: bchain.XPUBAddressTokenType, Name: address, Decimals: w.chainParser.AmountDecimals(), BalanceSat: (*Amount)(balance), diff --git a/bchain/basechain.go b/bchain/basechain.go index 26ea6a5e..ee5d30e0 100644 --- a/bchain/basechain.go +++ b/bchain/basechain.go @@ -54,8 +54,8 @@ func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint return 0, errors.New("Not supported") } -// EthereumTypeGetErc20ContractInfo is not supported -func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) { +// GetContractInfo is not supported +func (b *BaseChain) GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) { return nil, errors.New("Not supported") } diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index c4008e8f..33b996e9 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -321,13 +321,13 @@ func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interf return c.b.EthereumTypeEstimateGas(params) } -func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) { - defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now()) - return c.b.EthereumTypeGetErc20ContractInfo(contractDesc) +func (c *blockChainWithMetrics) GetContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.ContractInfo, err error) { + defer func(s time.Time) { c.observeRPCLatency("GetContractInfo", s, err) }(time.Now()) + return c.b.GetContractInfo(contractDesc) } 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) } diff --git a/bchain/coins/eth/contract.go b/bchain/coins/eth/contract.go index ddc99294..76a84fb1 100644 --- a/bchain/coins/eth/contract.go +++ b/bchain/coins/eth/contract.go @@ -4,10 +4,8 @@ import ( "context" "math/big" "strings" - "sync" ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/golang/glog" "github.com/juju/errors" "github.com/trezor/blockbook/bchain" ) @@ -28,9 +26,6 @@ const contractSymbolSignature = "0x95d89b41" const contractDecimalsSignature = "0x313ce567" const contractBalanceOf = "0x70a08231" -var cachedContracts = make(map[string]*bchain.Erc20Contract) -var cachedContractsMux sync.Mutex - func addressFromPaddedHex(s string) (string, error) { var t big.Int var ok bool @@ -48,16 +43,16 @@ func addressFromPaddedHex(s string) (string, error) { func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) { tl := len(l.Topics) - var ttt bchain.TokenTransferType + var ttt bchain.TokenType var value big.Int if tl == 3 { - ttt = bchain.ERC20 + ttt = bchain.FungibleToken _, ok := value.SetString(l.Data, 0) if !ok { return nil, errors.New("ERC20 log Data is not a number") } } else if tl == 4 { - ttt = bchain.ERC721 + ttt = bchain.NonFungibleToken _, ok := value.SetString(l.Topics[3], 0) if !ok { 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 &bchain.TokenTransfer{ - Type: bchain.ERC1155, - Contract: EIP55AddressFromAddress(l.Address), - From: EIP55AddressFromAddress(from), - To: EIP55AddressFromAddress(to), - IdValues: []bchain.TokenTransferIdValue{{Id: id, Value: value}}, + Type: bchain.MultiToken, + Contract: EIP55AddressFromAddress(l.Address), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + MultiTokenValues: []bchain.MultiTokenValue{{Id: id, Value: value}}, }, nil } @@ -150,7 +145,7 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, if countIds != countValues { 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++ { var id, value big.Int o := offsetIds + 64 + 64*i @@ -163,14 +158,14 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, if !ok { 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{ - Type: bchain.ERC1155, - Contract: EIP55AddressFromAddress(l.Address), - From: EIP55AddressFromAddress(from), - To: EIP55AddressFromAddress(to), - IdValues: idValues, + Type: bchain.MultiToken, + Contract: EIP55AddressFromAddress(l.Address), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + MultiTokenValues: idValues, }, nil } 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") } r = append(r, &bchain.TokenTransfer{ - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: EIP55AddressFromAddress(tx.To), From: EIP55AddressFromAddress(tx.From), To: EIP55AddressFromAddress(to), @@ -238,7 +233,7 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer return nil, errors.New("Data is not a number") } r = append(r, &bchain.TokenTransfer{ - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: EIP55AddressFromAddress(tx.To), From: EIP55AddressFromAddress(from), To: EIP55AddressFromAddress(to), @@ -262,56 +257,52 @@ func (b *EthereumRPC) ethCall(data, to string) (string, error) { return r, nil } -// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract -func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) { - cds := string(contractDesc) - cachedContractsMux.Lock() - contract, found := cachedContracts[cds] - cachedContractsMux.Unlock() - if !found { - address := EIP55Address(contractDesc) - data, err := b.ethCall(contractNameSignature, address) - if err != nil { - // ignore the error from the eth_call - since geth v1.9.15 they changed the behavior - // and returning error "execution reverted" for some non contract addresses - // https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672 - glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address)) - return nil, nil - // return nil, errors.Annotatef(err, "erc20NameSignature %v", address) - } - name := parseSimpleStringProperty(data) - if name != "" { - data, err = b.ethCall(contractSymbolSignature, address) - if err != nil { - glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address)) - return nil, nil - // return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address) - } - symbol := parseSimpleStringProperty(data) - data, err = b.ethCall(contractDecimalsSignature, address) - if err != nil { - glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address)) - // return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address) - } - contract = &bchain.Erc20Contract{ - Contract: address, - Name: name, - Symbol: symbol, - } - d := parseSimpleNumericProperty(data) - if d != nil { - contract.Decimals = int(uint8(d.Uint64())) - } else { - contract.Decimals = EtherAmountDecimalPoint - } - } else { - contract = nil - } - cachedContractsMux.Lock() - cachedContracts[cds] = contract - cachedContractsMux.Unlock() +func (b *EthereumRPC) fetchContractInfo(address string) (*bchain.ContractInfo, error) { + var contract bchain.ContractInfo + data, err := b.ethCall(contractNameSignature, address) + if err != nil { + // ignore the error from the eth_call - since geth v1.9.15 they changed the behavior + // and returning error "execution reverted" for some non contract addresses + // https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672 + // glog.Warning(errors.Annotatef(err, "Contract NameSignature %v", address)) + return nil, nil + // return nil, errors.Annotatef(err, "erc20NameSignature %v", address) } - 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 diff --git a/bchain/coins/eth/contract_test.go b/bchain/coins/eth/contract_test.go index 7d609a0a..587d98a7 100644 --- a/bchain/coins/eth/contract_test.go +++ b/bchain/coins/eth/contract_test.go @@ -133,7 +133,7 @@ func Test_contractGetTransfersFromLog(t *testing.T) { }, want: bchain.TokenTransfers{ { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB", From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391", To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f", @@ -171,11 +171,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) { }, want: bchain.TokenTransfers{ { - Type: bchain.ERC1155, - Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c", - From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53", - To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec", - IdValues: []bchain.TokenTransferIdValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}}, + Type: bchain.MultiToken, + Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c", + From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53", + To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec", + MultiTokenValues: []bchain.MultiTokenValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}}, }, }, }, @@ -195,11 +195,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) { }, want: bchain.TokenTransfers{ { - Type: bchain.ERC1155, + Type: bchain.MultiToken, Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7", From: "0x0000000000000000000000000000000000000000", To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e", - IdValues: []bchain.TokenTransferIdValue{ + MultiTokenValues: []bchain.MultiTokenValue{ {Id: *big.NewInt(1776), Value: *big.NewInt(1)}, {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, want: bchain.TokenTransfers{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", @@ -260,7 +260,7 @@ func Test_contractGetTransfersFromTx(t *testing.T) { args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, want: bchain.TokenTransfers{ { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9", From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081", To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b", diff --git a/bchain/coins/eth/dataparser.go b/bchain/coins/eth/dataparser.go index a67c947e..060fcfca 100644 --- a/bchain/coins/eth/dataparser.go +++ b/bchain/coins/eth/dataparser.go @@ -39,8 +39,8 @@ func parseSimpleStringProperty(data string) string { if len(data) > 128 { n := parseSimpleNumericProperty(data[64:128]) if n != nil { - l := n.Uint64() - if l > 0 && 2*int(l) <= len(data)-128 { + l := n.Int64() + if l > 0 && int(l) <= ((len(data)-128)>>1) { b, err := hex.DecodeString(data[128 : 128+2*l]) if err == nil { return string(b) diff --git a/bchain/coins/eth/dataparser_test.go b/bchain/coins/eth/dataparser_test.go index 5aca77ec..745e6b62 100644 --- a/bchain/coins/eth/dataparser_test.go +++ b/bchain/coins/eth/dataparser_test.go @@ -45,6 +45,11 @@ func Test_parseSimpleStringProperty(t *testing.T) { args: "0x2234880850896048596206002535425366538144616734015984380565810000", want: "", }, + { + name: "garbage", + args: "6080604052600436106100225760003560e01c80630cbcae701461003957610031565b366100315761002f610077565b005b61002f610077565b34801561004557600080fd5b5061004e61014e565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b7f000000000000000000000000000000000000000000000000000000000000000061011c565b60043560601b60601c6bca11c0de15dead10cced00006000195460a01c036100e9577f696d706c6f63000000000000000000000000000000000000000000000000000060005260206000fd5b8060001955005b60405136810160405236600082376000803683600019545af43d6000833e80610117573d82fd5b503d81f35b80330361014357602436036101435763ca11c0de60003560e01c036101435761014361009d565b61014b6100f0565b50565b600073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff541660005260206000f3fea2646970667358221220f27ad3f3b75609baa5d26d65ec1001c4a59f38e89088d6b47517c1cd1faf22ab64736f6c634300080d0033", + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 092c835b..1e48bc54 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -537,23 +537,37 @@ type rpcTraceResult struct { 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) if call.Type == "CREATE" { d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ Type: bchain.CREATE, Value: *value, 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" { d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ Type: bchain.SELFDESTRUCT, Value: *value, - From: call.From, + From: call.From, // destroyed contract address To: call.To, }) + contracts = append(contracts, bchain.ContractInfo{Contract: call.From, DestructedInBlock: blockHeight}) } else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) { d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{ Value: *value, @@ -565,13 +579,15 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt d.Error = call.Error } 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 -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)) + contracts := make([]bchain.ContractInfo, 0) if ProcessInternalTransactions { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) 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"}) if err != nil { glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err) - return data, err + return data, contracts, err } if 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 { r := &result.Result @@ -591,11 +607,12 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b if r.Type == "CREATE" { d.Type = bchain.CREATE d.Contract = r.To + contracts = append(contracts, *b.getCreationContractInfo(d.Contract, blockHeight)) } else if r.Type == "SELFDESTRUCT" { d.Type = bchain.SELFDESTRUCT } for j := range r.Calls { - b.processCallTrace(&r.Calls[j], d) + contracts = b.processCallTrace(&r.Calls[j], d, contracts, blockHeight) } if 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 @@ -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) } // get block events + // TODO - could be possibly done in parallel to getInternalDataForBlock logs, ens, err := b.processEventsForBlock(head.Number) if err != nil { return nil, err } // error fetching internal data does not stop the block processing 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 - if err != nil || len(ens) > 0 { + if err != nil || len(ens) > 0 || len(contracts) > 0 { blockSpecificData = &bchain.EthereumBlockSpecificData{} if err != nil { blockSpecificData.InternalDataError = err.Error() @@ -658,7 +676,11 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error } if len(ens) > 0 { blockSpecificData.AddressAliasRecords = ens - glog.Info("ENS", ens) + // glog.Info("ENS", ens) + } + if len(contracts) > 0 { + blockSpecificData.Contracts = contracts + // glog.Info("Contracts", contracts) } } diff --git a/bchain/types.go b/bchain/types.go index 2f40dfe4..4d9af1f9 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -113,6 +113,27 @@ type MempoolTx struct { 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 type TokenTransfers []*TokenTransfer @@ -286,13 +307,13 @@ type BlockChain interface { EstimateFee(blocks int) (big.Int, error) SendRawTransaction(tx string) (string, error) GetMempoolEntry(txid string) (*MempoolEntry, error) + GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) // parser GetChainParser() BlockChainParser // EthereumType specific EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) - EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) } diff --git a/bchain/types_ethereum_type.go b/bchain/types_ethereum_type.go index 357f1667..a04a0810 100644 --- a/bchain/types_ethereum_type.go +++ b/bchain/types_ethereum_type.go @@ -47,16 +47,6 @@ const ( SELFDESTRUCT ) -// TokenTransferType - type of token transfer -type TokenTransferType int - -// TokenTransferType enumeration -const ( - ERC20 = TokenTransferType(iota) - ERC721 - ERC1155 -) - // EthereumInternalTransaction contains internal transfers type EthereumInternalData struct { Type EthereumInternalTransactionType `json:"type"` @@ -65,27 +55,41 @@ type EthereumInternalData struct { Error string } -// Erc20Contract contains info about ERC20 contract -type Erc20Contract struct { - Contract string `json:"contract"` - Name string `json:"name"` - Symbol string `json:"symbol"` - Decimals int `json:"decimals"` +// ContractInfo contains info about ERC20 contract +type ContractInfo struct { + Type TokenTypeName `json:"type"` + Contract string `json:"contract"` + Name string `json:"name"` + 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 Value big.Int } -// TokenTransfer contains a single ERC20/ERC721/ERC1155 token transfer +// TokenTransfer contains a single token transfer type TokenTransfer struct { - Type TokenTransferType - Contract string - From string - To string - Value big.Int - IdValues []TokenTransferIdValue + Type TokenType + Contract string + From string + To string + Value big.Int + MultiTokenValues []MultiTokenValue } // RpcTransaction is returned by eth_getTransactionByHash @@ -138,4 +142,5 @@ type AddressAliasRecord struct { type EthereumBlockSpecificData struct { InternalDataError string AddressAliasRecords []AddressAliasRecord + Contracts []ContractInfo } diff --git a/db/rocksdb_ethereumtype.go b/db/rocksdb_ethereumtype.go index 13457c7c..a75b5284 100644 --- a/db/rocksdb_ethereumtype.go +++ b/db/rocksdb_ethereumtype.go @@ -6,6 +6,7 @@ import ( "math/big" "sync" + vlq "github.com/bsm/go-vlq" "github.com/flier/gorocksdb" "github.com/golang/glog" "github.com/juju/errors" @@ -18,12 +19,12 @@ const ContractIndexOffset = 2 // AddrContract is Contract address with number of transactions done by given address type AddrContract struct { - Type bchain.TokenTransferType - Contract bchain.AddressDescriptor - Txs uint - Value big.Int // single value of ERC20 - Ids []big.Int // multiple ERC721 tokens - IdValues []bchain.TokenTransferIdValue // multiple ERC1155 tokens + Type bchain.TokenType + Contract bchain.AddressDescriptor + Txs uint + Value big.Int // single value of ERC20 + Ids []big.Int // multiple ERC721 tokens + MultiTokenValues []bchain.MultiTokenValue // multiple ERC1155 tokens } // AddrContracts contains number of transactions and contracts for an address @@ -48,10 +49,10 @@ func packAddrContracts(acs *AddrContracts) []byte { buf = append(buf, ac.Contract...) l = packVaruint(uint(ac.Type)+ac.Txs<<2, varBuf) buf = append(buf, varBuf[:l]...) - if ac.Type == bchain.ERC20 { + if ac.Type == bchain.FungibleToken { l = packBigint(&ac.Value, varBuf) buf = append(buf, varBuf[:l]...) - } else if ac.Type == bchain.ERC721 { + } else if ac.Type == bchain.NonFungibleToken { l = packVaruint(uint(len(ac.Ids)), varBuf) buf = append(buf, varBuf[:l]...) for i := range ac.Ids { @@ -59,12 +60,12 @@ func packAddrContracts(acs *AddrContracts) []byte { buf = append(buf, varBuf[:l]...) } } else { // bchain.ERC1155 - l = packVaruint(uint(len(ac.IdValues)), varBuf) + l = packVaruint(uint(len(ac.MultiTokenValues)), varBuf) buf = append(buf, varBuf[:l]...) - for i := range ac.IdValues { - l = packBigint(&ac.IdValues[i].Id, varBuf) + for i := range ac.MultiTokenValues { + l = packBigint(&ac.MultiTokenValues[i].Id, varBuf) buf = append(buf, varBuf[:l]...) - l = packBigint(&ac.IdValues[i].Value, varBuf) + l = packBigint(&ac.MultiTokenValues[i].Value, varBuf) 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]...) txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:]) buf = buf[eth.EthereumTypeAddressDescriptorLen+l:] - ttt := bchain.TokenTransferType(txs & 3) + ttt := bchain.TokenType(txs & 3) txs >>= 2 ac := AddrContract{ Type: ttt, Contract: contract, Txs: txs, } - if ttt == bchain.ERC20 { + if ttt == bchain.FungibleToken { b, ll := unpackBigint(buf) buf = buf[ll:] ac.Value = b } else { len, ll := unpackVaruint(buf) buf = buf[ll:] - if ttt == bchain.ERC721 { + if ttt == bchain.NonFungibleToken { ac.Ids = make([]big.Int, len) for i := uint(0); i < len; i++ { b, ll := unpackBigint(buf) @@ -109,14 +110,14 @@ func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrCo ac.Ids[i] = b } } else { - ac.IdValues = make([]bchain.TokenTransferIdValue, len) + ac.MultiTokenValues = make([]bchain.MultiTokenValue, len) for i := uint(0); i < len; i++ { b, ll := unpackBigint(buf) buf = buf[ll:] - ac.IdValues[i].Id = b + ac.MultiTokenValues[i].Id = b b, ll = unpackBigint(buf) 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) } } - if transfer.Type == bchain.ERC20 { + if transfer.Type == bchain.FungibleToken { aggregate(&c.Value, &transfer.Value) - } else if transfer.Type == bchain.ERC721 { + } else if transfer.Type == bchain.NonFungibleToken { if index < 0 { // remove token from the list 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) } } else { // bchain.ERC1155 - for _, t := range transfer.IdValues { - for i := range c.IdValues { + for _, t := range transfer.MultiTokenValues { + for i := range c.MultiTokenValues { // find the token in the list - if c.IdValues[i].Id.Cmp(&t.Id) == 0 { - aggregate(&c.IdValues[i].Value, &t.Value) + if c.MultiTokenValues[i].Id.Cmp(&t.Id) == 0 { + aggregate(&c.MultiTokenValues[i].Value, &t.Value) // if transfer from, remove if the value is zero - if index < 0 && len(c.IdValues[i].Value.Bits()) == 0 { - c.IdValues = append(c.IdValues[:i], c.IdValues[i+1:]...) + if index < 0 && len(c.MultiTokenValues[i].Value.Bits()) == 0 { + c.MultiTokenValues = append(c.MultiTokenValues[:i], c.MultiTokenValues[i+1:]...) } 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 // 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 { - c.IdValues = append(c.IdValues, bchain.TokenTransferIdValue{ + c.MultiTokenValues = append(c.MultiTokenValues, bchain.MultiTokenValue{ Id: t.Id, Value: *new(big.Int).Set(&t.Value), }) @@ -327,9 +328,9 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address type ethBlockTxContract struct { from, to, contract bchain.AddressDescriptor - transferType bchain.TokenTransferType + transferType bchain.TokenType value big.Int - idValues []bchain.TokenTransferIdValue + idValues []bchain.MultiTokenValue } type ethInternalTransfer struct { @@ -476,7 +477,7 @@ func (d *RocksDB) processContractTransfers(blockTx *ethBlockTx, tx *bchain.Tx, a bc.to = to bc.contract = contract bc.value = t.Value - bc.idValues = t.IdValues + bc.idValues = t.MultiTokenValues } return nil } @@ -699,6 +700,113 @@ func (d *RocksDB) storeInternalDataEthereumType(wb *gorocksdb.WriteBatch, blockT 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 { varBuf := make([]byte, maxPackedBigintBytes) buf = append(buf, blockTx.btxID...) @@ -715,7 +823,7 @@ func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte { buf = appendAddress(buf, c.contract) l = packVaruint(uint(c.transferType), varBuf) buf = append(buf, varBuf[:l]...) - if c.transferType == bchain.ERC1155 { + if c.transferType == bchain.MultiToken { l = packVaruint(uint(len(c.idValues)), varBuf) buf = append(buf, varBuf[:l]...) for i := range c.idValues { @@ -773,6 +881,11 @@ func (d *RocksDB) storeBlockSpecificDataEthereumType(wb *gorocksdb.WriteBatch, b return err } } + for i := range blockSpecificData.Contracts { + if err := d.StoreContractInfo(wb, &blockSpecificData.Contracts[i]); err != nil { + return err + } + } } return nil } @@ -823,12 +936,12 @@ func unpackBlockTx(buf []byte, pos int) (*ethBlockTx, int, error) { return nil, 0, err } cc, l = unpackVaruint(buf[pos:]) - c.transferType = bchain.TokenTransferType(cc) + c.transferType = bchain.TokenType(cc) pos += l - if c.transferType == bchain.ERC1155 { + if c.transferType == bchain.MultiToken { cc, l = unpackVaruint(buf[pos:]) pos += l - c.idValues = make([]bchain.TokenTransferIdValue, cc) + c.idValues = make([]bchain.MultiTokenValue, cc) for i := range c.idValues { c.idValues[i].Id, l = unpackBigint(buf[pos:]) pos += l @@ -938,9 +1051,9 @@ func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain index = transferTo } addToContract(addrContract, contractIndex, index, btxContract.contract, &bchain.TokenTransfer{ - Type: btxContract.transferType, - Value: btxContract.value, - IdValues: btxContract.idValues, + Type: btxContract.transferType, + Value: btxContract.value, + MultiTokenValues: btxContract.idValues, }, false) } } else { diff --git a/db/rocksdb_ethereumtype_test.go b/db/rocksdb_ethereumtype_test.go index 6631df9f..bf651335 100644 --- a/db/rocksdb_ethereumtype_test.go +++ b/db/rocksdb_ethereumtype_test.go @@ -58,11 +58,11 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil}, { 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), - "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.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{ { 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.EthTxidB1T2 + 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, }, } @@ -158,43 +177,43 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa if err := checkColumn(d, cfAddressContracts, []keyPair{ { 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), - "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), "010101" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("8086") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184"), nil, + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("8086") + + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("871180000950184"), nil, }, { dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "050300" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000854307892726464") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0"), nil, + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000854307892726464") + + 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.FungibleToken)) + bigintFromStringToHex("0"), nil, }, { dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser), - "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(2) + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10"), nil, + "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), "020000" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(1) + bigintFromStringToHex("1"), nil, + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0") + + 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.NonFungibleToken)) + varuintToHex(1) + bigintFromStringToHex("1"), nil, }, { dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser), - "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(0), nil, + "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.NonFungibleToken)) + varuintToHex(0), nil, }, { dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser), - "010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(0), nil, + "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.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{ { 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.EthTxidB2T2 + 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") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("854307892726464") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184") + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") + + "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.FungibleToken)) + bigintFromStringToHex("854307892726464") + + 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.FungibleToken)) + bigintFromStringToHex("7674999999999991915") + dbtestdata.EthTxidB2T3 + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + - "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(uint(bchain.ERC721)) + bigintFromStringToHex("1") + + "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.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.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.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, }, }); err != nil { @@ -722,13 +756,13 @@ func Test_packUnpackAddrContracts(t *testing.T) { InternalTxs: 8873, Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser), Txs: 8, Value: *big.NewInt(793201132), }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Txs: 41235, Ids: []big.Int{ @@ -740,10 +774,10 @@ func Test_packUnpackAddrContracts(t *testing.T) { }, }, { - Type: bchain.ERC1155, + Type: bchain.MultiToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), Txs: 64, - IdValues: []bchain.TokenTransferIdValue{ + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(1), Value: *big.NewInt(1412341234), @@ -796,7 +830,7 @@ func Test_addToContracts(t *testing.T) { index: 1, contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC20, + Type: bchain.FungibleToken, Value: *big.NewInt(123456), }, addTxCount: true, @@ -805,7 +839,7 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Txs: 1, Value: *big.NewInt(123456), @@ -819,7 +853,7 @@ func Test_addToContracts(t *testing.T) { index: ^1, contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC20, + Type: bchain.FungibleToken, Value: *big.NewInt(23456), }, addTxCount: true, @@ -828,7 +862,7 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, @@ -842,7 +876,7 @@ func Test_addToContracts(t *testing.T) { index: 1, contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Value: *big.NewInt(1), }, addTxCount: true, @@ -851,13 +885,13 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 1, Ids: []big.Int{*big.NewInt(1)}, @@ -871,7 +905,7 @@ func Test_addToContracts(t *testing.T) { index: 1, contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Value: *big.NewInt(2), }, addTxCount: true, @@ -880,13 +914,13 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 2, Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)}, @@ -900,7 +934,7 @@ func Test_addToContracts(t *testing.T) { index: ^1, contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Value: *big.NewInt(1), }, addTxCount: false, @@ -909,13 +943,13 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 2, Ids: []big.Int{*big.NewInt(2)}, @@ -929,8 +963,8 @@ func Test_addToContracts(t *testing.T) { index: 1, contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC1155, - IdValues: []bchain.TokenTransferIdValue{ + Type: bchain.MultiToken, + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(56789), @@ -943,22 +977,22 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 2, Ids: []big.Int{*big.NewInt(2)}, }, { - Type: bchain.ERC1155, + Type: bchain.MultiToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Txs: 1, - IdValues: []bchain.TokenTransferIdValue{ + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(56789), @@ -974,8 +1008,8 @@ func Test_addToContracts(t *testing.T) { index: 1, contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC1155, - IdValues: []bchain.TokenTransferIdValue{ + Type: bchain.MultiToken, + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(111), @@ -992,22 +1026,22 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 2, Ids: []big.Int{*big.NewInt(2)}, }, { - Type: bchain.ERC1155, + Type: bchain.MultiToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Txs: 2, - IdValues: []bchain.TokenTransferIdValue{ + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(56900), @@ -1027,8 +1061,8 @@ func Test_addToContracts(t *testing.T) { index: ^1, contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), transfer: &bchain.TokenTransfer{ - Type: bchain.ERC1155, - IdValues: []bchain.TokenTransferIdValue{ + Type: bchain.MultiToken, + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(112), @@ -1045,22 +1079,22 @@ func Test_addToContracts(t *testing.T) { wantAddrContracts: &AddrContracts{ Contracts: []AddrContract{ { - Type: bchain.ERC20, + Type: bchain.FungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), Value: *big.NewInt(100000), Txs: 2, }, { - Type: bchain.ERC721, + Type: bchain.NonFungibleToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), Txs: 2, Ids: []big.Int{*big.NewInt(2)}, }, { - Type: bchain.ERC1155, + Type: bchain.MultiToken, Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), Txs: 3, - IdValues: []bchain.TokenTransferIdValue{ + MultiTokenValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(11), Value: *big.NewInt(56788), @@ -1119,7 +1153,7 @@ func Test_packUnpackBlockTx(t *testing.T) { from: addressToAddrDesc(dbtestdata.EthAddr20, parser), to: addressToAddrDesc(dbtestdata.EthAddr5d, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), - transferType: bchain.ERC20, + transferType: bchain.FungibleToken, value: *big.NewInt(10000), }, }, @@ -1137,22 +1171,22 @@ func Test_packUnpackBlockTx(t *testing.T) { from: addressToAddrDesc(dbtestdata.EthAddr20, parser), to: addressToAddrDesc(dbtestdata.EthAddr3e, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), - transferType: bchain.ERC20, + transferType: bchain.FungibleToken, value: *big.NewInt(987654321), }, { from: addressToAddrDesc(dbtestdata.EthAddr4b, parser), to: addressToAddrDesc(dbtestdata.EthAddr55, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), - transferType: bchain.ERC721, + transferType: bchain.NonFungibleToken, value: *big.NewInt(13), }, { from: addressToAddrDesc(dbtestdata.EthAddr5d, parser), to: addressToAddrDesc(dbtestdata.EthAddr7b, parser), contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), - transferType: bchain.ERC1155, - idValues: []bchain.TokenTransferIdValue{ + transferType: bchain.MultiToken, + idValues: []bchain.MultiTokenValue{ { Id: *big.NewInt(1234), 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) + } + }) + } +} diff --git a/server/public.go b/server/public.go index be0dc402..963056dd 100644 --- a/server/public.go +++ b/server/public.go @@ -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 -func tokenTransfersCount(tx *api.Tx, t api.TokenType) int { +func tokenTransfersCount(tx *api.Tx, t bchain.TokenTypeName) int { count := 0 for i := range tx.TokenTransfers { 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 -func tokenCount(tokens []api.Token, t api.TokenType) int { +func tokenCount(tokens []api.Token, t bchain.TokenTypeName) int { count := 0 for i := range tokens { if tokens[i].Type == t { diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go index bd4461c6..93367959 100644 --- a/server/public_ethereumtype_test.go +++ b/server/public_ethereumtype_test.go @@ -24,7 +24,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Contract Contract 123 (S123) 0.000000000123450123 FAKE

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

Confirmed

Balance0.000000000123450123 FAKE
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ERC20 Tokens
ContractTokensTransfers
Contract 740.000000001000123074 S741
Contract 130.000000001000123013 S131
ERC721 Tokens
ContractTokensTransfers
Contract 20511

Transactions

ERC721 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
ID 1 S205
Fee: 0.00008794500041041 FAKE
Unconfirmed Transaction!0 FAKE
ERC20 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
0.000871180000950184 S74
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
7.674999999999991915 S13
Fee: 0.000216368 FAKE
Unconfirmed Transaction!0 FAKE
`, + `Trezor Fake Coin Explorer

Address 0.000000000123450123 FAKE

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

Confirmed

Balance0.000000000123450123 FAKE
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ERC20 Tokens
ContractTokensTransfers
Contract 740.001000123074 S741
Contract 130.000000001000123013 S131
ERC721 Tokens
ContractTokensTransfers
Contract 20511

Transactions

ERC721 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
ID 1 S205
Fee: 0.00008794500041041 FAKE
Unconfirmed Transaction!0 FAKE
ERC20 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
871.180000950184 S74
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
7.674999999999991915 S13
Fee: 0.000216368 FAKE
Unconfirmed Transaction!0 FAKE
`, }, }, { @@ -33,7 +33,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Contract Contract 93 (S93) 0.000000000123450093 FAKE

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

Confirmed

Balance0.000000000123450093 FAKE
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ERC1155 Tokens
ContractTokensTransfers
Contract 1111776:1 S111, 1898:10 S1111

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
0 FAKE
ERC1155 Token Transfers
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1776:1 S1111898:10 S111
Fee: 0.000081891755740665 FAKE
Unconfirmed Transaction!0 FAKE
`, + `Trezor Fake Coin Explorer

Address 0.000000000123450093 FAKE

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

Confirmed

Balance0.000000000123450093 FAKE
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ERC1155 Tokens
ContractTokensTransfers
Contract 1111776:1 S111, 1898:10 S1111

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
0 FAKE
ERC1155 Token Transfers
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1776:1 S1111898:10 S111
Fee: 0.000081891755740665 FAKE
Unconfirmed Transaction!0 FAKE
`, }, }, { @@ -42,7 +42,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101

Summary

In BlockUnconfirmed
StatusSuccess
Value0 FAKE
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE
Fees0.002081 FAKE
RBFON

Details

Input Data
Transfer
Method ID: 0xa9059cbb
Function: transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, + `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101

Summary

In BlockUnconfirmed
StatusSuccess
Value0 FAKE
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE
Fees0.002081 FAKE
RBFON

Details

Input Data
Transfer
Method ID: 0xa9059cbb
Function: transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, }, }, { @@ -64,7 +64,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","balance":"123450075","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":1,"nonTokenTxs":1,"internalTxs":1,"txids":["0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2"],"nonce":"75","tokens":[{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":2,"symbol":"S13","decimals":18,"balance":"1000075013"},{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":2,"symbol":"S74","decimals":18,"balance":"1000075074"}],"erc20Contract":{"contract":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","name":"Contract 75","symbol":"S75","decimals":18}}`, + `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","balance":"123450075","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":1,"nonTokenTxs":1,"internalTxs":1,"txids":["0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2"],"nonce":"75","tokens":[{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":2,"symbol":"S13","decimals":18,"balance":"1000075013"},{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":2,"symbol":"S74","decimals":12,"balance":"1000075074"}]}`, }, }, { @@ -73,7 +73,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","balance":"123450123","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":2,"transactions":[{"txid":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","vin":[{"n":0,"addresses":["0x837E3f699d85a4b0B99894567e9233dFB1DcB081"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"87945000410410","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x2","gasPrice":"0x59682f07","gas":"0x173a9","to":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","value":"0x0","input":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","hash":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","blockNumber":"0xb33b9f","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","transactionIndex":"0x1"},"receipt":{"gasUsed":"0xe506","status":"0x1","logs":[{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"},{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"}]}},"tokenTransfers":[{"type":"ERC721","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","name":"Contract 205","symbol":"S205","decimals":18,"value":"1"}],"ethereumSpecific":{"status":1,"nonce":2,"gasLimit":95145,"gasUsed":58630,"gasPrice":"1500000007","data":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","parsedData":{"methodId":"0x23b872dd","name":""}}},{"txid":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","vin":[{"n":0,"addresses":["0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x479CC461fEcd078F766eCc58533D6F69580CF3AC"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"216368000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x1df76","gasPrice":"0x3b9aca00","gas":"0x3d090","to":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","value":"0x0","input":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","hash":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","blockNumber":"0x41eee9","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","transactionIndex":"0x24"},"internalData":{"type":1,"contract":"0d0f936ee4c93e25944694d6c121de94d9760f11","transfers":[{"type":0,"from":"4bda106325c335df99eab7fe363cac8a0ba2a24d","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000010},{"type":2,"from":"4af4114f73d1c1c903ac9e0361b379d1291808a2","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000011}],"Error":""},"receipt":{"gasUsed":"0x34d30","status":"0x1","logs":[{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f8001"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x000000000000000000000000000000000000000000000000000308fd0e798ac0"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000000000000000000000000000000000000000000000","0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f8001000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x00000000000000000000000000000000000000000000000000031855667df7a8"},{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f606b"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000000","0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f606b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f48"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7675000000000000001"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"854307892726464"},{"type":"ERC20","from":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"871180000950184"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7674999999999991915"}],"ethereumSpecific":{"status":1,"nonce":122742,"gasLimit":250000,"gasUsed":216368,"gasPrice":"1000000000","data":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","parsedData":{"methodId":"0x4f150787","name":""}}}],"nonce":"123","tokens":[{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":1,"symbol":"S74","decimals":18,"balance":"1000123074"},{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":1,"symbol":"S13","decimals":18,"balance":"1000123013"},{"type":"ERC721","name":"Contract 205","contract":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","transfers":1,"symbol":"S205","decimals":18,"ids":["1"]}],"erc20Contract":{"contract":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","name":"Contract 123","symbol":"S123","decimals":18},"addressAliases":{"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b":{"Type":"ENS","Alias":"address7b.eth"}}}`, + `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","balance":"123450123","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":2,"transactions":[{"txid":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","vin":[{"n":0,"addresses":["0x837E3f699d85a4b0B99894567e9233dFB1DcB081"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"87945000410410","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x2","gasPrice":"0x59682f07","gas":"0x173a9","to":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","value":"0x0","input":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","hash":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","blockNumber":"0xb33b9f","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","transactionIndex":"0x1"},"receipt":{"gasUsed":"0xe506","status":"0x1","logs":[{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"},{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"}]}},"tokenTransfers":[{"type":"ERC721","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","name":"Contract 205","symbol":"S205","decimals":18,"value":"1"}],"ethereumSpecific":{"status":1,"nonce":2,"gasLimit":95145,"gasUsed":58630,"gasPrice":"1500000007","data":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","parsedData":{"methodId":"0x23b872dd","name":""}}},{"txid":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","vin":[{"n":0,"addresses":["0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x479CC461fEcd078F766eCc58533D6F69580CF3AC"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"216368000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x1df76","gasPrice":"0x3b9aca00","gas":"0x3d090","to":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","value":"0x0","input":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","hash":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","blockNumber":"0x41eee9","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","transactionIndex":"0x24"},"internalData":{"type":1,"contract":"0d0f936ee4c93e25944694d6c121de94d9760f11","transfers":[{"type":0,"from":"4bda106325c335df99eab7fe363cac8a0ba2a24d","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000010},{"type":2,"from":"4af4114f73d1c1c903ac9e0361b379d1291808a2","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000011}],"Error":""},"receipt":{"gasUsed":"0x34d30","status":"0x1","logs":[{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f8001"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x000000000000000000000000000000000000000000000000000308fd0e798ac0"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000000000000000000000000000000000000000000000","0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f8001000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x00000000000000000000000000000000000000000000000000031855667df7a8"},{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f606b"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000000","0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f606b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f48"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7675000000000000001"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":12,"value":"854307892726464"},{"type":"ERC20","from":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":12,"value":"871180000950184"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7674999999999991915"}],"ethereumSpecific":{"status":1,"nonce":122742,"gasLimit":250000,"gasUsed":216368,"gasPrice":"1000000000","data":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","parsedData":{"methodId":"0x4f150787","name":""}}}],"nonce":"123","tokens":[{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":1,"symbol":"S74","decimals":12,"balance":"1000123074"},{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":1,"symbol":"S13","decimals":18,"balance":"1000123013"},{"type":"ERC721","name":"Contract 205","contract":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","transfers":1,"symbol":"S205","decimals":18,"ids":["1"]}],"addressAliases":{"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b":{"Type":"ENS","Alias":"address7b.eth"}}}`, }, }, { @@ -82,7 +82,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}},"addressAliases":{"0x20cD153de35D469BA46127A0C8F18626b59a256A":{"Type":"ENS","Alias":"address20.eth"}}}`, + `{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":12,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}},"addressAliases":{"0x20cD153de35D469BA46127A0C8F18626b59a256A":{"Type":"ENS","Alias":"address20.eth"},"0x4af4114F73d1c1C903aC9E0361b379D1291808A2":{"Type":"Contract","Alias":"Contract 74"}}}`, }, }, } diff --git a/server/websocket.go b/server/websocket.go index 27a73827..ab47f565 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -896,7 +896,7 @@ func (s *WebsocketServer) sendOnNewTxAddr(stringAddressDescriptor string, tx *ap } 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() defer s.addressSubscriptionsLock.Unlock() subscribed := make(map[string]struct{}) diff --git a/static/templates/address.html b/static/templates/address.html index c662ff12..987ea89e 100644 --- a/static/templates/address.html +++ b/static/templates/address.html @@ -1,5 +1,5 @@ {{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}} -

{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}} {{formatAmount $addr.BalanceSat}} {{$cs}} +

{{if $addr.ContractInfo}}Contract {{$addr.ContractInfo.Name}} ({{$addr.ContractInfo.Symbol}}){{else}}Address{{end}} {{formatAmount $addr.BalanceSat}} {{$cs}}

{{$addr.AddrStr}} @@ -98,7 +98,7 @@ {{if $t.Contract}}{{$t.Name}}{{else}}{{$t.Name}}{{end}} - {{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}} {{$t.Transfers}} diff --git a/static/templates/txdetail_ethereumtype.html b/static/templates/txdetail_ethereumtype.html index d47d7f0c..28bcfe78 100644 --- a/static/templates/txdetail_ethereumtype.html +++ b/static/templates/txdetail_ethereumtype.html @@ -267,7 +267,7 @@
- {{- range $iv := $tt.Values -}} + {{- range $iv := $tt.MultiTokenValues -}} {{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}} {{- end -}}
diff --git a/tests/dbtestdata/dbtestdata_ethereumtype.go b/tests/dbtestdata/dbtestdata_ethereumtype.go index e34012bd..41cfac57 100644 --- a/tests/dbtestdata/dbtestdata_ethereumtype.go +++ b/tests/dbtestdata/dbtestdata_ethereumtype.go @@ -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{ InternalDataError: "test error", AddressAliasRecords: []bchain.AddressAliasRecord{ @@ -140,6 +153,12 @@ var Block2SpecificData = &bchain.EthereumBlockSpecificData{ Name: "address20", }, }, + Contracts: []bchain.ContractInfo{ + { + Contract: EthAddrContract4a, + DestructedInBlock: 44445, + }, + }, } type packedAndInternal struct { @@ -182,6 +201,7 @@ func GetTestEthereumTypeBlock1(parser bchain.BlockChainParser) *bchain.Block { packed: EthTx2Packed, internal: EthTx2InternalData, }}, parser), + CoinSpecificData: Block1SpecificData, } } diff --git a/tests/dbtestdata/fakechain_ethereumtype.go b/tests/dbtestdata/fakechain_ethereumtype.go index b1927616..4ee50472 100644 --- a/tests/dbtestdata/fakechain_ethereumtype.go +++ b/tests/dbtestdata/fakechain_ethereumtype.go @@ -117,13 +117,15 @@ func (c *fakeBlockChainEthereumType) EthereumTypeGetNonce(addrDesc bchain.Addres 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) - return &bchain.Erc20Contract{ - Contract: addresses[0], - Name: "Contract " + strconv.Itoa(int(contractDesc[0])), - Symbol: "S" + strconv.Itoa(int(contractDesc[0])), - Decimals: 18, + return &bchain.ContractInfo{ + Type: bchain.ERC20TokenType, + Contract: addresses[0], + Name: "Contract " + strconv.Itoa(int(contractDesc[0])), + Symbol: "S" + strconv.Itoa(int(contractDesc[0])), + Decimals: 18, + CreatedInBlock: 12345, }, nil }