From 6fdf6e297c8a511cc65bec11aa834873e2fa8a20 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 14 Apr 2022 16:24:26 +0200 Subject: [PATCH] Parse ethereum input data --- api/types.go | 1 + api/worker.go | 47 ++- bchain/coins/eth/dataparser.go | 234 +++++++++++++- bchain/coins/eth/dataparser_test.go | 318 +++++++++++++++++++- bchain/types_ethereum_type.go | 27 +- db/rocksdb_ethereumtype.go | 62 +++- db/rocksdb_ethereumtype_test.go | 17 +- fourbyte/fourbyte.go | 5 +- fourbyte/fourbyte_test.go | 12 +- server/public.go | 15 +- server/public_ethereumtype_test.go | 61 ++++ server/public_test.go | 13 +- static/templates/address.html | 68 ++++- static/templates/tx.html | 40 +++ static/templates/txdetail_ethereumtype.html | 5 +- 15 files changed, 873 insertions(+), 52 deletions(-) diff --git a/api/types.go b/api/types.go index dc6644d0..545e58ca 100644 --- a/api/types.go +++ b/api/types.go @@ -207,6 +207,7 @@ type EthereumSpecific struct { GasUsed *big.Int `json:"gasUsed"` GasPrice *Amount `json:"gasPrice"` Data string `json:"data,omitempty"` + ParsedData *bchain.EthereumParsedInputData `json:"parsedData,omitempty"` InternalTransfers []EthereumInternalTransfer `json:"internalTransfers,omitempty"` } diff --git a/api/worker.go b/api/worker.go index c5f2e054..a2ec91ee 100644 --- a/api/worker.go +++ b/api/worker.go @@ -127,6 +127,23 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON) } +func (w *Worker) getParsedEthereumInputData(data string) *bchain.EthereumParsedInputData { + var err error + var signatures *[]bchain.FourByteSignature + fourBytes := eth.GetSignatureFromData(data) + if fourBytes != 0 { + signatures, err = w.db.GetFourByteSignatures(fourBytes) + if err != nil { + glog.Errorf("GetFourByteSignatures(%v) error %v", fourBytes, err) + return nil + } + if signatures == nil { + return nil + } + } + return eth.ParseInputData(signatures, data) +} + // GetTransactionFromBchainTx reads transaction data from txid func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool) (*Tx, error) { var err error @@ -270,6 +287,8 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe } } + parsedInputData := w.getParsedEthereumInputData(ethTxData.Data) + // mempool txs do not have fees yet if ethTxData.GasUsed != nil { feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed) @@ -278,12 +297,13 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe valOutSat = bchainTx.Vout[0].ValueSat } ethSpecific = &EthereumSpecific{ - GasLimit: ethTxData.GasLimit, - GasPrice: (*Amount)(ethTxData.GasPrice), - GasUsed: ethTxData.GasUsed, - Nonce: ethTxData.Nonce, - Status: ethTxData.Status, - Data: ethTxData.Data, + GasLimit: ethTxData.GasLimit, + GasPrice: (*Amount)(ethTxData.GasPrice), + GasUsed: ethTxData.GasUsed, + Nonce: ethTxData.Nonce, + Status: ethTxData.Status, + Data: ethTxData.Data, + ParsedData: parsedInputData, } if internalData != nil { ethSpecific.Type = internalData.Type @@ -674,7 +694,6 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i } t := Token{ - Type: ERC20TokenType, Contract: ci.Contract, Name: ci.Name, Symbol: ci.Symbol, @@ -685,6 +704,7 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i // return contract balances/values only at or above AccountDetailsTokenBalances if details >= AccountDetailsTokenBalances && validContract { if c.Type == bchain.ERC20 { + t.Type = ERC20TokenType // 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 { @@ -694,15 +714,20 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i t.BalanceSat = (*Amount)(b) } } else { - if len(t.Ids) > 0 { - ids := make([]Amount, len(t.Ids)) + 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 { ids[j] = (Amount)(c.Ids[j]) } t.Ids = ids } - if len(t.IdValues) > 0 { - idValues := make([]TokenTransferValues, len(t.IdValues)) + if len(c.IdValues) > 0 { + idValues := make([]TokenTransferValues, len(c.IdValues)) for j := range idValues { idValues[j].Id = (*Amount)(&c.IdValues[j].Id) idValues[j].Value = (*Amount)(&c.IdValues[j].Value) diff --git a/bchain/coins/eth/dataparser.go b/bchain/coins/eth/dataparser.go index 55e632c3..88043a96 100644 --- a/bchain/coins/eth/dataparser.go +++ b/bchain/coins/eth/dataparser.go @@ -4,8 +4,15 @@ import ( "bytes" "encoding/hex" "math/big" + "runtime/debug" + "strconv" + "strings" "unicode" "unicode/utf8" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/golang/glog" + "github.com/trezor/blockbook/bchain" ) func parseSimpleNumericProperty(data string) *big.Int { @@ -58,15 +65,230 @@ func parseSimpleStringProperty(data string) string { return "" } -func Decamel(s string) string { +func decamel(s string) string { var b bytes.Buffer splittable := false - for _, v := range s { - if splittable && unicode.IsUpper(v) { - b.WriteByte(' ') + for i, v := range s { + if i == 0 { + b.WriteRune(unicode.ToUpper(v)) + } else { + if splittable && unicode.IsUpper(v) { + b.WriteByte(' ') + } + b.WriteRune(v) + splittable = unicode.IsLower(v) || unicode.IsNumber(v) } - b.WriteRune(v) - splittable = unicode.IsLower(v) || unicode.IsNumber(v) } return b.String() } + +func GetSignatureFromData(data string) uint32 { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) < 8 { + return 0 + } + sig, err := strconv.ParseUint(data[:8], 16, 32) + if err != nil { + return 0 + } + return uint32(sig) +} + +const ErrorTy byte = 255 + +func processParam(data string, index int, t *abi.Type, processed []bool) ([]string, int, bool) { + var retval []string + d := index << 6 + if d+64 > len(data) { + return nil, 0, false + } + block := data[d : d+64] + switch t.T { + // static types + case abi.IntTy, abi.UintTy, abi.BoolTy: + var n big.Int + _, ok := n.SetString(block, 16) + if !ok { + return nil, 0, false + } + if t.T == abi.BoolTy { + if n.Int64() != 0 { + retval = []string{"true"} + } else { + retval = []string{"false"} + } + } else { + retval = []string{n.String()} + } + processed[index] = true + index++ + case abi.AddressTy: + b, err := hex.DecodeString(block[24:]) + if err != nil { + return nil, 0, false + } + retval = []string{EIP55Address(b)} + processed[index] = true + index++ + case abi.FixedBytesTy: + retval = []string{"0x" + block[:t.Size<<1]} + processed[index] = true + index++ + case abi.ArrayTy: + for i := 0; i < t.Size; i++ { + var r []string + var ok bool + r, index, ok = processParam(data, index, t.Elem, processed) + if !ok { + return nil, 0, false + } + retval = append(retval, r...) + } + // dynamic types + case abi.StringTy, abi.BytesTy, abi.SliceTy: + // get offset of dynamic type + offset, err := strconv.ParseInt(block, 16, 64) + if err != nil { + return nil, 0, false + } + processed[index] = true + index++ + offset <<= 1 + d = int(offset) + dynIndex := d >> 6 + if d+64 > len(data) || d < 0 { + return nil, 0, false + } + // get element count of dynamic type + c, err := strconv.ParseInt(data[d:d+64], 16, 64) + count := int(c) + if err != nil { + return nil, 0, false + } + processed[dynIndex] = true + dynIndex++ + if t.T == abi.StringTy || t.T == abi.BytesTy { + d += 64 + de := d + (count << 1) + if de > len(data) { + return nil, 0, false + } + if count == 0 { + retval = []string{""} + } else { + block = data[d:de] + if t.T == abi.StringTy { + b, err := hex.DecodeString(block) + if err != nil { + return nil, 0, false + } + retval = []string{string(b)} + } else { + retval = []string{"0x" + block} + } + count = ((count - 1) >> 5) + 1 + for i := 0; i < count; i++ { + processed[dynIndex] = true + dynIndex++ + } + } + } else { + for i := 0; i < count; i++ { + var r []string + var ok bool + r, dynIndex, ok = processParam(data, dynIndex, t.Elem, processed) + if !ok { + return nil, 0, false + } + retval = append(retval, r...) + } + } + // types not processed + case abi.HashTy, abi.FixedPointTy, abi.FunctionTy, abi.TupleTy: + fallthrough + default: + return nil, 0, false + } + return retval, index, true +} + +func tryParseParams(data string, params []string, parsedParams []abi.Type) []bchain.EthereumParsedInputParam { + processed := make([]bool, len(data)/64) + parsed := make([]bchain.EthereumParsedInputParam, len(params)) + index := 0 + var values []string + var ok bool + for i := range params { + t := &parsedParams[i] + values, index, ok = processParam(data, index, t, processed) + if !ok { + return nil + } + parsed[i] = bchain.EthereumParsedInputParam{Type: params[i], Values: values} + } + // all data must be processed, otherwise wrong signature + for _, p := range processed { + if !p { + return nil + } + } + return parsed +} + +// ParseInputData tries to parse transaction input data from known FourByteSignatures +// as there may be multiple signatures for the same four bytes, it tries to match the input to the known parameters +// it does not parse tuples for now +func ParseInputData(signatures *[]bchain.FourByteSignature, data string) *bchain.EthereumParsedInputData { + if len(data) <= 2 { // data is empty or 0x + return &bchain.EthereumParsedInputData{Name: "Transfer"} + } + if len(data) < 10 || (len(data)-10)%64 != 0 { + return nil + } + parsed := bchain.EthereumParsedInputData{ + MethodId: data[:10], + } + defer func() { + if r := recover(); r != nil { + glog.Error("ParseInputData recovered from panic: ", r, ", ", data, ",signatures ", signatures) + debug.PrintStack() + } + }() + if signatures != nil { + data = data[10:] + for i := range *signatures { + s := &(*signatures)[i] + // if not yet done, set DecamelName and Function and parse parameter types from string to abi.Type + // the signatures are stored in cache + if s.DecamelName == "" { + s.DecamelName = decamel(s.Name) + s.Function = s.Name + "(" + strings.Join(s.Parameters, ", ") + ")" + s.ParsedParameters = make([]abi.Type, len(s.Parameters)) + for j := range s.Parameters { + var t abi.Type + if len(s.Parameters[j]) > 0 && s.Parameters[j][0] == '(' { + // Tuple type is not supported for now + t = abi.Type{T: abi.TupleTy} + } else { + var err error + t, err = abi.NewType(s.Parameters[j], "", nil) + if err != nil { + t = abi.Type{T: ErrorTy} + } + } + s.ParsedParameters[j] = t + } + } + parsedParams := tryParseParams(data, s.Parameters, s.ParsedParameters) + if parsedParams != nil { + parsed.Name = s.DecamelName + parsed.Function = s.Function + parsed.Params = parsedParams + break + } + } + } + return &parsed +} diff --git a/bchain/coins/eth/dataparser_test.go b/bchain/coins/eth/dataparser_test.go index 9af84c1f..234dd8cd 100644 --- a/bchain/coins/eth/dataparser_test.go +++ b/bchain/coins/eth/dataparser_test.go @@ -2,7 +2,12 @@ package eth -import "testing" +import ( + "reflect" + "testing" + + "github.com/trezor/blockbook/bchain" +) func Test_parseSimpleStringProperty(t *testing.T) { tests := []struct { @@ -51,3 +56,314 @@ func Test_parseSimpleStringProperty(t *testing.T) { }) } } + +func TestGetSignatureFromData(t *testing.T) { + tests := []struct { + name string + data string + want uint32 + }{ + { + name: "0x9e53a69a", + data: "0x9e53a69a000000000000000000000000000000000000000000000", + want: 2656282266, + }, + { + name: "9e53a69b", + data: "9e53a69b000000000000000000000000000000000000000000000", + want: 2656282267, + }, + { + name: "0x9e53 short", + data: "0x9e53", + want: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetSignatureFromData(tt.data); got != tt.want { + t.Errorf("GetSignatureFromData() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestParseInputData(t *testing.T) { + signatures := []bchain.FourByteSignature{ + { + Name: "mintFighter", + Parameters: []string{}, + }, + { + Name: "cancelMultipleMakerOrders", + Parameters: []string{"uint256[]"}, + }, + { + Name: "mockRegisterFact", + Parameters: []string{"bytes32"}, + }, + { + Name: "vestingDeposits", + Parameters: []string{"address"}, + }, + { + Name: "addLiquidityETH", + Parameters: []string{"address", "uint256", "uint256", "uint256", "address", "uint256"}, + }, + { + Name: "spread", + Parameters: []string{"uint256", "address[]"}, + }, + { + Name: "registerWithConfig", + Parameters: []string{"string", "address", "uint256", "bytes32", "address", "address"}, + }, + { + Name: "atomicMatch_", + Parameters: []string{"address[14]", "uint256[18]", "uint8[8]", "bytes", "bytes", "bytes", "bytes", "bytes", "bytes", "uint8[2]", "bytes32[5]"}, + }, + { + Name: "transmitAndSellTokenForEth", + Parameters: []string{"address", "uint256", "uint256", "uint256", "address", "(uint8,bytes32,bytes32)", "bytes"}, + }, + } + tests := []struct { + name string + signatures *[]bchain.FourByteSignature + data string + want *bchain.EthereumParsedInputData + wantErr bool + }{ + { + name: "transfer", + signatures: &signatures, + data: "", + want: &bchain.EthereumParsedInputData{ + Name: "Transfer", + }, + }, + { + name: "mintFighter", + signatures: &signatures, + data: "0xa19b9082", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xa19b9082", + Name: "Mint Fighter", + Function: "mintFighter()", + Params: []bchain.EthereumParsedInputParam{}, + }, + }, + { + name: "mockRegisterFact", + signatures: &signatures, + data: "0xf69507abdc8fa8fe57a22de66a1d5898496c524068cb04c31f72497b3ac9f3b449e58725", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xf69507ab", + Name: "Mock Register Fact", + Function: "mockRegisterFact(bytes32)", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "bytes32", + Values: []string{"0xdc8fa8fe57a22de66a1d5898496c524068cb04c31f72497b3ac9f3b449e58725"}, + }, + }, + }, + }, + { + name: "cancelMultipleMakerOrders", + signatures: &signatures, + data: "0x9e53a69a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000017f62f8db30", + want: &bchain.EthereumParsedInputData{ + MethodId: "0x9e53a69a", + Name: "Cancel Multiple Maker Orders", + Function: "cancelMultipleMakerOrders(uint256[])", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "uint256[]", + Values: []string{"1646632950576"}, + }, + }, + }, + }, + { + name: "addLiquidityETH", + signatures: &signatures, + data: "0xf305d719000000000000000000000000b80e5aaa2131c07568128f68b8538ed3c8951234000000000000000000000000000000000000007e37be2022c0914b2680000000000000000000000000000000000000000000007e37be2022c0914b26800000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000009f64b014ca26f2def573246543dd1115b229e4f400000000000000000000000000000000000000000000000000000000623f56f8", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xf305d719", + Name: "Add Liquidity ETH", + Function: "addLiquidityETH(address, uint256, uint256, uint256, address, uint256)", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "address", + Values: []string{"0xB80e5AaA2131c07568128f68b8538eD3C8951234"}, + }, + { + Type: "uint256", + Values: []string{"10000000000000000000000000000000"}, + }, + { + Type: "uint256", + Values: []string{"10000000000000000000000000000000"}, + }, + { + Type: "uint256", + Values: []string{"1000000000000000000"}, + }, + { + Type: "address", + Values: []string{"0x9f64B014CA26F2DeF573246543DD1115b229e4F4"}, + }, + { + Type: "uint256", + Values: []string{"1648318200"}, + }, + }, + }, + }, + { + name: "addLiquidityETH data don't match - too long", + signatures: &signatures, + data: "0xf305d719000000000000000000000000b80e5aaa2131c07568128f68b8538ed3c8951234000000000000000000000000000000000000007e37be2022c0914b2680000000000000000000000000000000000000000000007e37be2022c0914b26800000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000009f64b014ca26f2def573246543dd1115b229e4f400000000000000000000000000000000000000000000000000000000623f56f800000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xf305d719", + }, + }, + { + name: "addLiquidityETH data don't match - too short", + signatures: &signatures, + data: "0xf305d719000000000000000000000000b80e5aaa2131c07568128f68b8538ed3c8951234000000000000000000000000000000000000007e37be2022c0914b2680000000000000000000000000000000000000000000007e37be2022c0914b26800000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000009f64b014ca26f2def573246543dd1115b229e4f4", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xf305d719", + }, + }, + { + name: "spread", + signatures: &signatures, + data: "0xcd51b093000000000000000000000000000000000000000000000000016345785d8a00000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000048c999d9206fcf2a0ecde10049de6dc2d1704bb2000000000000000000000000d2dae6b2309ada5d4c983b4c7d2c942452adc759", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xcd51b093", + Name: "Spread", + Function: "spread(uint256, address[])", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "uint256", + Values: []string{"100000000000000000"}, + }, + { + Type: "address[]", + Values: []string{"0x48c999d9206fcf2A0ecdE10049de6Dc2d1704Bb2", "0xD2DAE6B2309aDa5d4c983B4c7D2c942452aDC759"}, + }, + }, + }, + }, + { + name: "atomicMatch_", // mainnet tx 0x57aff22b0f812e05467fb73caec8ac0364a535382496e5f64eb9df9fb32bd85f + signatures: &signatures, + data: "0xab834bab0000000000000000000000007f268357a8c2552623316e2562d90e642bb538e50000000000000000000000001676b0ab0aeb83122c58abc3d6a50b6c4a9d376300000000000000000000000024c57fbb5c260edf158583818177cfd5c2dec4700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000baf2127b49fc93cbca6269fade0f7f31df4c88a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007f268357a8c2552623316e2562d90e642bb538e500000000000000000000000024c57fbb5c260edf158583818177cfd5c2dec47000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005b3256965e7c3cf26e11fcaf296dfc8807c01073000000000000000000000000baf2127b49fc93cbca6269fade0f7f31df4c88a70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000062531f6400000000000000000000000000000000000000000000000000000000000000000227db897c05fe6409bc72c6bee932b99a92ca45e155cf85e763424e7a3ee61500000000000000000000000000000000000000000000000000000000000002ee000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002386f26fc10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000625313f800000000000000000000000000000000000000000000000000000000627aa14b79166058af7dd96e2190730f926c56d6131af9d72b4dd2138b58c30e268c7f300000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000007c000000000000000000000000000000000000000000000000000000000000008e00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b200000000000000000000000000000000000000000000000000000000000000b20000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000001c77e6196859305642ea4751b9597a9507472acb04b9f1f4759aa0f27af41edd8960513f1649f58782cacce26b1341575b584594f940bba0614aff302d25b4b10477e6196859305642ea4751b9597a9507472acb04b9f1f4759aa0f27af41edd8960513f1649f58782cacce26b1341575b584594f940bba0614aff302d25b4b104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4fb16a59500000000000000000000000000000000000000000000000000000000000000000000000000000000000000001676b0ab0aeb83122c58abc3d6a50b6c4a9d3763000000000000000000000000f25f4f4f6517101dc947d1c0370571ebdd25f14a00000000000000000000000000000000000000000000000000000000000002c7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4fb16a59500000000000000000000000024c57fbb5c260edf158583818177cfd5c2dec4700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f25f4f4f6517101dc947d1c0370571ebdd25f14a00000000000000000000000000000000000000000000000000000000000002c7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e400000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e4000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xab834bab", + Name: "Atomic Match_", + Function: "atomicMatch_(address[14], uint256[18], uint8[8], bytes, bytes, bytes, bytes, bytes, bytes, uint8[2], bytes32[5])", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "address[14]", + Values: []string{ + "0x7f268357A8c2552623316e2562D90e642bB538E5", "0x1676b0AB0Aeb83122C58ABC3d6a50B6c4A9d3763", "0x24C57FBB5c260EDf158583818177Cfd5C2dec470", "0x0000000000000000000000000000000000000000", + "0xBAf2127B49fC93CbcA6269FAdE0F7F31dF4c88a7", "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000", "0x7f268357A8c2552623316e2562D90e642bB538E5", + "0x24C57FBB5c260EDf158583818177Cfd5C2dec470", "0x0000000000000000000000000000000000000000", "0x5b3256965e7C3cF26E11FCAf296DfC8807C01073", "0xBAf2127B49fC93CbcA6269FAdE0F7F31dF4c88a7", + "0x0000000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000"}, + }, + { + Type: "uint256[18]", + Values: []string{ + "750", "0", "0", "0", "10000000000000000", "0", "1649614692", "0", "975047921716720136517384107537725863826800092678142650456874303300963329557", + "750", "0", "0", "0", "10000000000000000", "0", "1649611768", "1652203851", "54769390272606378508076535204478407261307419838517394120712398796227861053232"}, + }, + { + Type: "uint8[8]", + Values: []string{"1", "0", "0", "1", "1", "1", "0", "1"}, + }, + { + Type: "bytes", + Values: []string{"0xfb16a59500000000000000000000000000000000000000000000000000000000000000000000000000000000000000001676b0ab0aeb83122c58abc3d6a50b6c4a9d3763000000000000000000000000f25f4f4f6517101dc947d1c0370571ebdd25f14a00000000000000000000000000000000000000000000000000000000000002c7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000"}, + }, + { + Type: "bytes", + Values: []string{"0xfb16a59500000000000000000000000024c57fbb5c260edf158583818177cfd5c2dec4700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f25f4f4f6517101dc947d1c0370571ebdd25f14a00000000000000000000000000000000000000000000000000000000000002c7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000"}, + }, + { + Type: "bytes", + Values: []string{"0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + }, + { + Type: "bytes", + Values: []string{"0x000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}, + }, + { + Type: "bytes", + Values: []string{""}, + }, + { + Type: "bytes", + Values: []string{""}, + }, + { + Type: "uint8[2]", + Values: []string{"28", "28"}, + }, + { + Type: "bytes32[5]", + Values: []string{"0x77e6196859305642ea4751b9597a9507472acb04b9f1f4759aa0f27af41edd89", "0x60513f1649f58782cacce26b1341575b584594f940bba0614aff302d25b4b104", + "0x77e6196859305642ea4751b9597a9507472acb04b9f1f4759aa0f27af41edd89", "0x60513f1649f58782cacce26b1341575b584594f940bba0614aff302d25b4b104", + "0x0000000000000000000000000000000000000000000000000000000000000000"}, + }, + }, + }, + }, + { + name: "registerWithConfig", + signatures: &signatures, + data: "0xf7a1696300000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000769cbf44073741ccb4c39c945402130b46fa8a70000000000000000000000000000000000000000000000000000000012cf35707a8c22626793047f41a428e815e2bb12ced6d5db4246a8b0bda488c541647bef0000000000000000000000004976fb03c32e5b8cfe2b6ccb31c09ba78ebaba410000000000000000000000000769cbf44073741ccb4c39c945402130b46fa8a700000000000000000000000000000000000000000000000000000000000000076d6f6e7369746100000000000000000000000000000000000000000000000000", + want: &bchain.EthereumParsedInputData{ + MethodId: "0xf7a16963", + Name: "Register With Config", + Function: "registerWithConfig(string, address, uint256, bytes32, address, address)", + Params: []bchain.EthereumParsedInputParam{ + { + Type: "string", + Values: []string{"monsita"}, + }, + { + Type: "address", + Values: []string{"0x0769cBf44073741cCb4C39c945402130B46fa8A7"}, + }, + { + Type: "uint256", + Values: []string{"315569520"}, + }, + { + Type: "bytes32", + Values: []string{"0x7a8c22626793047f41a428e815e2bb12ced6d5db4246a8b0bda488c541647bef"}, + }, + { + Type: "address", + Values: []string{"0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"}, + }, + { + Type: "address", + Values: []string{"0x0769cBf44073741cCb4C39c945402130B46fa8A7"}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ParseInputData(tt.signatures, tt.data) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseInputData() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/bchain/types_ethereum_type.go b/bchain/types_ethereum_type.go index 4061eea7..a6315324 100644 --- a/bchain/types_ethereum_type.go +++ b/bchain/types_ethereum_type.go @@ -1,6 +1,10 @@ package bchain -import "math/big" +import ( + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" +) // EthereumType specific @@ -12,6 +16,27 @@ type EthereumInternalTransfer struct { Value big.Int `json:"value"` } +type FourByteSignature struct { + // stored in DB + Name string + Parameters []string + // processed from DB data and stored only in cache + DecamelName string + Function string + ParsedParameters []abi.Type +} + +type EthereumParsedInputParam struct { + Type string `json:"type"` + Values []string `json:"values,omitempty"` +} +type EthereumParsedInputData struct { + MethodId string `json:"methodId"` + Name string `json:"name"` + Function string `json:"function,omitempty"` + Params []EthereumParsedInputParam `json:"params,omitempty"` +} + // EthereumInternalTransactionType - type of ethereum transaction from internal data type EthereumInternalTransactionType int diff --git a/db/rocksdb_ethereumtype.go b/db/rocksdb_ethereumtype.go index 5bbc1c10..3310afe3 100644 --- a/db/rocksdb_ethereumtype.go +++ b/db/rocksdb_ethereumtype.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/hex" "math/big" + "sync" "github.com/flier/gorocksdb" "github.com/golang/glog" @@ -578,11 +579,8 @@ func (d *RocksDB) unpackEthInternalData(buf []byte) (*bchain.EthereumInternalDat return &id, nil } -type FourByteSignature struct { - Name string - Parameters []string -} - +// FourByteSignature contains 4byte signature of transaction value with parameters +// and parsed parameters (that are not stored in DB) func packFourByteKey(fourBytes uint32, id uint32) []byte { key := make([]byte, 0, 8) key = append(key, packUint(fourBytes)...) @@ -590,7 +588,7 @@ func packFourByteKey(fourBytes uint32, id uint32) []byte { return key } -func packFourByteSignature(signature *FourByteSignature) []byte { +func packFourByteSignature(signature *bchain.FourByteSignature) []byte { buf := packString(signature.Name) for i := range signature.Parameters { buf = append(buf, packString(signature.Parameters[i])...) @@ -598,8 +596,8 @@ func packFourByteSignature(signature *FourByteSignature) []byte { return buf } -func unpackFourByteSignature(buf []byte) (*FourByteSignature, error) { - var signature FourByteSignature +func unpackFourByteSignature(buf []byte) (*bchain.FourByteSignature, error) { + var signature bchain.FourByteSignature var l int signature.Name, l = unpackString(buf) for l < len(buf) { @@ -610,7 +608,8 @@ func unpackFourByteSignature(buf []byte) (*FourByteSignature, error) { return &signature, nil } -func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*FourByteSignature, error) { +// GetFourByteSignature gets all 4byte signature of given fourBytes and id +func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*bchain.FourByteSignature, error) { key := packFourByteKey(fourBytes, id) val, err := d.db.GetCF(d.ro, d.cfh[cfFunctionSignatures], key) if err != nil { @@ -624,12 +623,51 @@ func (d *RocksDB) GetFourByteSignature(fourBytes uint32, id uint32) (*FourByteSi return unpackFourByteSignature(buf) } -func (d *RocksDB) StoreFourByteSignature(wb *gorocksdb.WriteBatch, fourBytes uint32, id uint32, signature *FourByteSignature) error { +var cachedByteSignatures = make(map[uint32]*[]bchain.FourByteSignature) +var cachedByteSignaturesMux sync.Mutex + +// GetFourByteSignatures gets all 4byte signatures of given fourBytes +// (there may be more than one signature starting with the same four bytes) +func (d *RocksDB) GetFourByteSignatures(fourBytes uint32) (*[]bchain.FourByteSignature, error) { + cachedByteSignaturesMux.Lock() + signatures, found := cachedByteSignatures[fourBytes] + cachedByteSignaturesMux.Unlock() + if !found { + retval := []bchain.FourByteSignature{} + key := packUint(fourBytes) + it := d.db.NewIteratorCF(d.ro, d.cfh[cfFunctionSignatures]) + defer it.Close() + for it.Seek(key); it.Valid(); it.Next() { + current := it.Key().Data() + if bytes.Compare(current[:4], key) > 0 { + break + } + val := it.Value().Data() + signature, err := unpackFourByteSignature(val) + if err != nil { + return nil, err + } + retval = append(retval, *signature) + } + cachedByteSignaturesMux.Lock() + cachedByteSignatures[fourBytes] = &retval + cachedByteSignaturesMux.Unlock() + return &retval, nil + } + return signatures, nil +} + +// StoreFourByteSignature stores 4byte signature in DB +func (d *RocksDB) StoreFourByteSignature(wb *gorocksdb.WriteBatch, fourBytes uint32, id uint32, signature *bchain.FourByteSignature) error { key := packFourByteKey(fourBytes, id) wb.PutCF(d.cfh[cfFunctionSignatures], key, packFourByteSignature(signature)) + cachedByteSignaturesMux.Lock() + delete(cachedByteSignatures, fourBytes) + cachedByteSignaturesMux.Unlock() return nil } +// GetEthereumInternalData gets transaction internal data from DB func (d *RocksDB) GetEthereumInternalData(txid string) (*bchain.EthereumInternalData, error) { btxID, err := d.chainParser.PackTxid(txid) if err != nil { @@ -902,7 +940,9 @@ func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain glog.Warning("AddressContracts ", addrDesc, ", contract ", contractIndex, " Txs would be negative, tx ", hex.EncodeToString(btxID)) } } else { - glog.Warning("AddressContracts ", addrDesc, ", contract ", btxContract.contract, " not found, tx ", hex.EncodeToString(btxID)) + if !isZeroAddress(addrDesc) { + glog.Warning("AddressContracts ", addrDesc, ", contract ", btxContract.contract, " not found, tx ", hex.EncodeToString(btxID)) + } } } } else { diff --git a/db/rocksdb_ethereumtype_test.go b/db/rocksdb_ethereumtype_test.go index 9d257c70..a61fc958 100644 --- a/db/rocksdb_ethereumtype_test.go +++ b/db/rocksdb_ethereumtype_test.go @@ -301,7 +301,7 @@ func formatInternalData(in *bchain.EthereumInternalData) *bchain.EthereumInterna func testFourByteSignature(t *testing.T, d *RocksDB) { fourBytes := uint32(1234123) id := uint32(42313) - signature := FourByteSignature{ + signature := bchain.FourByteSignature{ Name: "xyz", Parameters: []string{"address", "(bytes,uint256[],uint256)", "uint16"}, } @@ -320,6 +320,13 @@ func testFourByteSignature(t *testing.T, d *RocksDB) { if !reflect.DeepEqual(*got, signature) { t.Errorf("testFourByteSignature: got %+v, want %+v", got, signature) } + gotSlice, err := d.GetFourByteSignatures(fourBytes) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(*gotSlice, []bchain.FourByteSignature{signature}) { + t.Errorf("testFourByteSignature: got %+v, want %+v", *gotSlice, []bchain.FourByteSignature{signature}) + } } // TestRocksDB_Index_EthereumType is an integration test probing the whole indexing functionality for EthereumType chains @@ -1165,24 +1172,24 @@ func Test_packUnpackBlockTx(t *testing.T) { func Test_packUnpackFourByteSignature(t *testing.T) { tests := []struct { name string - signature FourByteSignature + signature bchain.FourByteSignature }{ { name: "no params", - signature: FourByteSignature{ + signature: bchain.FourByteSignature{ Name: "abcdef", }, }, { name: "one param", - signature: FourByteSignature{ + signature: bchain.FourByteSignature{ Name: "opqr", Parameters: []string{"uint16"}, }, }, { name: "multiple params", - signature: FourByteSignature{ + signature: bchain.FourByteSignature{ Name: "xyz", Parameters: []string{"address", "(bytes,uint256[],uint256)", "uint16"}, }, diff --git a/fourbyte/fourbyte.go b/fourbyte/fourbyte.go index 76b56b3c..21d6fe12 100644 --- a/fourbyte/fourbyte.go +++ b/fourbyte/fourbyte.go @@ -11,6 +11,7 @@ import ( "github.com/flier/gorocksdb" "github.com/golang/glog" + "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/db" ) @@ -98,13 +99,13 @@ func (fd *FourByteSignaturesDownloader) getPageWithRetry(url string) (*signature return nil, errors.New("Too many retries to 4byte signatures") } -func parseSignatureFromText(t string) *db.FourByteSignature { +func parseSignatureFromText(t string) *bchain.FourByteSignature { s := strings.Index(t, "(") e := strings.LastIndex(t, ")") if s < 0 || e < 0 { return nil } - var signature db.FourByteSignature + var signature bchain.FourByteSignature signature.Name = t[:s] params := t[s+1 : e] if len(params) > 0 { diff --git a/fourbyte/fourbyte_test.go b/fourbyte/fourbyte_test.go index 95c7b3af..c64ddad5 100644 --- a/fourbyte/fourbyte_test.go +++ b/fourbyte/fourbyte_test.go @@ -4,26 +4,26 @@ import ( "reflect" "testing" - "github.com/trezor/blockbook/db" + "github.com/trezor/blockbook/bchain" ) func Test_parseSignatureFromText(t *testing.T) { tests := []struct { name string signature string - want db.FourByteSignature + want bchain.FourByteSignature }{ { name: "_gonsPerFragment", signature: "_gonsPerFragment()", - want: db.FourByteSignature{ + want: bchain.FourByteSignature{ Name: "_gonsPerFragment", }, }, { name: "vestingDeposits", signature: "vestingDeposits(address)", - want: db.FourByteSignature{ + want: bchain.FourByteSignature{ Name: "vestingDeposits", Parameters: []string{"address"}, }, @@ -31,7 +31,7 @@ func Test_parseSignatureFromText(t *testing.T) { { name: "batchTransferTokenB", signature: "batchTransferTokenB(address[],uint256)", - want: db.FourByteSignature{ + want: bchain.FourByteSignature{ Name: "batchTransferTokenB", Parameters: []string{"address[]", "uint256"}, }, @@ -39,7 +39,7 @@ func Test_parseSignatureFromText(t *testing.T) { { name: "transmitAndSellTokenForEth", signature: "transmitAndSellTokenForEth(address,uint256,uint256,uint256,address,(uint8,bytes32,bytes32),bytes)", - want: db.FourByteSignature{ + want: bchain.FourByteSignature{ Name: "transmitAndSellTokenForEth", Parameters: []string{"address", "uint256", "uint256", "uint256", "address", "(uint8,bytes32,bytes32)", "bytes"}, }, diff --git a/server/public.go b/server/public.go index e778306b..be0dc402 100644 --- a/server/public.go +++ b/server/public.go @@ -457,6 +457,8 @@ func (s *PublicServer) parseTemplates() []*template.Template { "isOwnAddress": isOwnAddress, "toJSON": toJSON, "tokenTransfersCount": tokenTransfersCount, + "tokenCount": tokenCount, + "hasPrefix": strings.HasPrefix, } var createTemplate func(filenames ...string) *template.Template if s.debug { @@ -559,7 +561,7 @@ func isOwnAddress(td *TemplateData, a string) bool { return a == td.AddrStr } -// called from template, returns count of token transfers of given type +// called from template, returns count of token transfers of given type in a tx func tokenTransfersCount(tx *api.Tx, t api.TokenType) int { count := 0 for i := range tx.TokenTransfers { @@ -570,6 +572,17 @@ func tokenTransfersCount(tx *api.Tx, t api.TokenType) int { return count } +// called from template, returns count of tokens in array of given type +func tokenCount(tokens []api.Token, t api.TokenType) int { + count := 0 + for i := range tokens { + if tokens[i].Type == t { + count++ + } + } + return count +} + func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { var tx *api.Tx var err error diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go index c6a02055..27a6009d 100644 --- a/server/public_ethereumtype_test.go +++ b/server/public_ethereumtype_test.go @@ -8,13 +8,43 @@ import ( "net/http/httptest" "testing" + "github.com/flier/gorocksdb" "github.com/golang/glog" + "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/bchain/coins/eth" + "github.com/trezor/blockbook/db" "github.com/trezor/blockbook/tests/dbtestdata" ) func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { tests := []httpTests{ + { + name: "explorerAddress " + dbtestdata.EthAddr7b, + r: newGetRequest(ts.URL + "/address/" + dbtestdata.EthAddr7b), + 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
`, + }, + }, + { + name: "explorerAddress " + dbtestdata.EthAddr5d, + r: newGetRequest(ts.URL + "/address/" + dbtestdata.EthAddr5d), + 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
`, + }, + }, + { + name: "explorerTx " + dbtestdata.EthTxidB1T2, + r: newGetRequest(ts.URL + "/tx/0x" + dbtestdata.EthTxidB1T2), + 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
`, + }, + }, { name: "apiIndex", r: newGetRequest(ts.URL + "/api"), @@ -37,11 +67,42 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { `{"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}}`, }, }, + { + name: "apiAddress EthAddr7b details=txs", + r: newGetRequest(ts.URL + "/api/v2/address/" + dbtestdata.EthAddr7b + "?details=txs"), + 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}}`, + }, + }, + { + name: "apiTx EthTxidB1T2", + r: newGetRequest(ts.URL + "/api/v2/tx/0x" + dbtestdata.EthTxidB1T2), + 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"]}]}}}`, + }, + }, } performHttpTests(tests, t, ts) } +func initEthereumTypeDB(d *db.RocksDB) error { + // add 0xa9059cbb transfer(address,uint256) signature + wb := gorocksdb.NewWriteBatch() + defer wb.Destroy() + if err := d.StoreFourByteSignature(wb, 2835717307, 145, &bchain.FourByteSignature{ + Name: "transfer", + Parameters: []string{"address", "uint256"}, + }); err != nil { + return err + } + return d.WriteBatch(wb) +} + func Test_PublicServer_EthereumType(t *testing.T) { parser := eth.NewEthereumParser(1) chain, err := dbtestdata.NewFakeBlockChainEthereumType(parser) diff --git a/server/public_test.go b/server/public_test.go index f06d55d2..341a670d 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -74,10 +74,15 @@ func setupRocksDB(parser bchain.BlockChainParser, chain bchain.BlockChain, t *te if err := d.ConnectBlock(block2); err != nil { t.Fatal(err) } - if err := InitTestFiatRates(d); err != nil { + if err := initTestFiatRates(d); err != nil { t.Fatal(err) } is.FinishedSync(block2.Height) + if parser.GetChainType() == bchain.ChainEthereumType { + if err := initEthereumTypeDB(d); err != nil { + t.Fatal(err) + } + } return d, is, tmp } @@ -168,8 +173,8 @@ func insertFiatRate(date string, rates map[string]float64, d *db.RocksDB) error return d.FiatRatesStoreTicker(ticker) } -// InitTestFiatRates initializes test data for /api/v2/tickers endpoint -func InitTestFiatRates(d *db.RocksDB) error { +// initTestFiatRates initializes test data for /api/v2/tickers endpoint +func initTestFiatRates(d *db.RocksDB) error { if err := insertFiatRate("20180320020000", map[string]float64{ "usd": 2000.0, "eur": 1300.0, @@ -235,7 +240,7 @@ func performHttpTests(tests []httpTests, t *testing.T, ts *httptest.Server) { b := string(bb) for _, c := range tt.body { if !strings.Contains(b, c) { - t.Errorf("got %v, want to contain %v", b, c) + t.Errorf("got\n%v\nwant to contain %v", b, c) break } } diff --git a/static/templates/address.html b/static/templates/address.html index 7c661b78..730355ae 100644 --- a/static/templates/address.html +++ b/static/templates/address.html @@ -30,7 +30,7 @@ Nonce {{$addr.Nonce}} - {{- if $addr.Tokens -}} + {{if tokenCount $addr.Tokens "ERC20"}} ERC20 Tokens @@ -41,13 +41,75 @@ Tokens Transfers - {{- range $t := $addr.Tokens -}} + {{range $t := $addr.Tokens}} + {{if eq $t.Type "ERC20"}} {{if $t.Contract}}{{$t.Name}}{{else}}{{$t.Name}}{{end}} {{formatAmountWithDecimals $t.BalanceSat $t.Decimals}} {{$t.Symbol}} {{$t.Transfers}} - {{- end -}} + {{end}} + {{end}} + + + + + {{- end -}} + {{if tokenCount $addr.Tokens "ERC721"}} + + ERC721 Tokens + + + + + + + + + {{range $t := $addr.Tokens}} + {{if eq $t.Type "ERC721"}} + + + + + + {{end}} + {{end}} + +
ContractTokensTransfers
{{if $t.Contract}}{{$t.Name}}{{else}}{{$t.Name}}{{end}} + {{range $i, $iv := $t.Ids}} + {{if $i}}, {{end}} + {{formatAmountWithDecimals $iv 0}} + {{end}} + {{$t.Transfers}}
+ + + {{- end -}} + {{if tokenCount $addr.Tokens "ERC1155"}} + + ERC1155 Tokens + + + + + + + + + {{range $t := $addr.Tokens}} + {{if eq $t.Type "ERC1155"}} + + + + + + {{end}} + {{end}}
ContractTokensTransfers
{{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}} + {{$t.Transfers}}
diff --git a/static/templates/tx.html b/static/templates/tx.html index 3f3f21d1..6a260a79 100644 --- a/static/templates/tx.html +++ b/static/templates/tx.html @@ -84,6 +84,46 @@
{{template "txdetail" .}}
+{{if eq .ChainType 1}} +{{if $tx.EthereumSpecific.ParsedData}} +{{if $tx.EthereumSpecific.ParsedData.Function }} +
+
Input Data
+
+ {{if $tx.EthereumSpecific.ParsedData.Name}}
{{$tx.EthereumSpecific.ParsedData.Name}}
{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}
Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}
{{end}} + {{if $tx.EthereumSpecific.ParsedData.Function}}
Function: {{$tx.EthereumSpecific.ParsedData.Function}}
{{end}} + {{if $tx.EthereumSpecific.ParsedData.Params}} +
+ + + + + + + + + + {{range $i,$p := $tx.EthereumSpecific.ParsedData.Params}} + + + + + + {{end}} + +
#TypeData
{{$i}}{{$p.Type}} + {{range $j,$v := $p.Values}} + {{if $j}}
{{end}} + {{if hasPrefix $p.Type "address"}}{{$v}}{{else}}{{$v}}{{end}} + {{end}} +
+
+ {{end}} +
+
+{{end}} +{{end}} +{{end}}
Raw Transaction
diff --git a/static/templates/txdetail_ethereumtype.html b/static/templates/txdetail_ethereumtype.html index aadb773e..d47d7f0c 100644 --- a/static/templates/txdetail_ethereumtype.html +++ b/static/templates/txdetail_ethereumtype.html @@ -6,6 +6,9 @@ {{if eq $tx.EthereumSpecific.Status 1}}{{end}}{{if eq $tx.EthereumSpecific.Status 0}}{{end}}
{{- if $tx.Blocktime}}
{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}
{{end -}} + {{if $tx.EthereumSpecific.ParsedData}} + {{if $tx.EthereumSpecific.ParsedData.Name}}
{{$tx.EthereumSpecific.ParsedData.Name}}
{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}
Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}
{{end}} + {{end}} {{if $tx.EthereumSpecific.Error}}
Error: {{$tx.EthereumSpecific.Error}}
{{end}}
@@ -265,7 +268,7 @@
{{- range $iv := $tt.Values -}} - {{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value $tt.Decimals}} {{$tt.Symbol}} + {{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}} {{- end -}}