github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/articulate/function_test.go (about)

     1  package articulate
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math/big"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base"
    11  	"github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/types"
    12  	"github.com/ethereum/go-ethereum/accounts/abi"
    13  )
    14  
    15  var ensRegistrar = `[{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"releaseDeed","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"getAllowedTime","outputs":[{"name":"timestamp","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"unhashedName","type":"string"}],"name":"invalidateName","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"hash","type":"bytes32"},{"name":"owner","type":"address"},{"name":"value","type":"uint256"},{"name":"salt","type":"bytes32"}],"name":"shaBid","outputs":[{"name":"sealedBid","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"bidder","type":"address"},{"name":"seal","type":"bytes32"}],"name":"cancelBid","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"entries","outputs":[{"name":"","type":"uint8"},{"name":"","type":"address"},{"name":"","type":"uint256"},{"name":"","type":"uint256"},{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"ens","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_value","type":"uint256"},{"name":"_salt","type":"bytes32"}],"name":"unsealBid","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"transferRegistrars","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"bytes32"}],"name":"sealedBids","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"state","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"newOwner","type":"address"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_hash","type":"bytes32"},{"name":"_timestamp","type":"uint256"}],"name":"isAllowed","outputs":[{"name":"allowed","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"finalizeAuction","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"registryStarted","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"launchLength","outputs":[{"name":"","type":"uint32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"sealedBid","type":"bytes32"}],"name":"newBid","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"labels","type":"bytes32[]"}],"name":"eraseNode","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hashes","type":"bytes32[]"}],"name":"startAuctions","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"hash","type":"bytes32"},{"name":"deed","type":"address"},{"name":"registrationDate","type":"uint256"}],"name":"acceptRegistrarTransfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_hash","type":"bytes32"}],"name":"startAuction","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"rootNode","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"hashes","type":"bytes32[]"},{"name":"sealedBid","type":"bytes32"}],"name":"startAuctionsAndBid","outputs":[],"payable":true,"type":"function"},{"inputs":[{"name":"_ens","type":"address"},{"name":"_rootNode","type":"bytes32"},{"name":"_startDate","type":"uint256"}],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"AuctionStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"bidder","type":"address"},{"indexed":false,"name":"deposit","type":"uint256"}],"name":"NewBid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"status","type":"uint8"}],"name":"BidRevealed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"HashRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"}],"name":"HashReleased","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"hash","type":"bytes32"},{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"registrationDate","type":"uint256"}],"name":"HashInvalidated","type":"event"}]`
    16  
    17  func TestArticulateFunction(t *testing.T) {
    18  	const abiJson = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]`
    19  	abi, err := abi.JSON(strings.NewReader(abiJson))
    20  	if err != nil {
    21  		t.Fatal(err)
    22  	}
    23  	const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158`
    24  
    25  	am := abi.Methods["get"]
    26  	f := types.FunctionFromAbiMethod(&am)
    27  	if err = ArticulateFunction(f, hexdata, ""); err != nil {
    28  		t.Fatal(err)
    29  	}
    30  
    31  	if ilen := len(f.Inputs); ilen > 1 {
    32  		t.Fatal("wrong input count", ilen)
    33  	}
    34  	if paramType := f.Inputs[0].ParameterType; paramType != "address" {
    35  		t.Fatal("wrong parameter type", paramType)
    36  	}
    37  	if value := f.Inputs[0].Value; value != "0x0000000000000000000000000000000000000001" {
    38  		t.Fatal("wrong value", value)
    39  	}
    40  }
    41  
    42  func TestArticulateArguments(t *testing.T) {
    43  	abiJson := `[{"inputs":[{"internalType":"uint256","name":"chainId_","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"guy","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":true,"inputs":[{"indexed":true,"internalType":"bytes4","name":"sig","type":"bytes4"},{"indexed":true,"internalType":"address","name":"usr","type":"address"},{"indexed":true,"internalType":"bytes32","name":"arg1","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"arg2","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"LogNote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"src","type":"address"},{"indexed":true,"internalType":"address","name":"dst","type":"address"},{"indexed":false,"internalType":"uint256","name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"burn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"deny","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"move","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"holder","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"bool","name":"allowed","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"pull","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"usr","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"push","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"guy","type":"address"}],"name":"rely","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"src","type":"address"},{"internalType":"address","name":"dst","type":"address"},{"internalType":"uint256","name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"wards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"}]`
    44  	abi, err := abi.JSON(strings.NewReader(abiJson))
    45  	if err != nil {
    46  		t.Fatal(err)
    47  	}
    48  	input := `0xa9059cbb000000000000000000000000f5aab2d0b50cb3bf2b6a5a9ed18580fd736668be000000000000000000000000000000000000000000000199d413696741200000`
    49  
    50  	abiMethod := abi.Methods["transfer"]
    51  	f := types.FunctionFromAbiMethod(&abiMethod)
    52  	if err = articulateArguments(abiMethod.Inputs, input[10:], nil, f.Inputs); err != nil {
    53  		t.Fatal(err)
    54  	}
    55  
    56  	if paramType := f.Inputs[0].ParameterType; paramType != "address" {
    57  		t.Fatal("wrong type of the first parameter", paramType)
    58  	}
    59  	if value := f.Inputs[0].Value; value != "0xf5aab2d0b50cb3bf2b6a5a9ed18580fd736668be" {
    60  		t.Fatal("wrong value of the first parameter", value)
    61  	}
    62  
    63  	if paramType := f.Inputs[1].ParameterType; paramType != "uint256" {
    64  		t.Fatal("wrong type of the second parameter", paramType)
    65  	}
    66  	if value := f.Inputs[1].Value; value != "7560000000000000000000" {
    67  		t.Fatal("wrong value of the second parameter", value)
    68  	}
    69  }
    70  
    71  func TestArticulateArgumentsMixedIndexed(t *testing.T) {
    72  	abi, err := abi.JSON(strings.NewReader(ensRegistrar))
    73  	if err != nil {
    74  		t.Fatal(err)
    75  	}
    76  	txData := `0x000000000000000000000000000000000000000000000000000000005911a79a`
    77  	txTopics := []base.Hash{
    78  		base.HexToHash("0x87e97e825a1d1fa0c54e1d36c7506c1dea8b1efd451fe68b000cf96f7cf40003"),
    79  		base.HexToHash("0x000bf9f2adc93a1da7b9e61f44ee6504f99c467a2812b354d70a07f0b3cdc58c"),
    80  	}
    81  	abiEvent := abi.Events["AuctionStarted"]
    82  	result := types.FunctionFromAbiEvent(&abiEvent)
    83  
    84  	if err = articulateArguments(abiEvent.Inputs, txData[2:], txTopics, result.Inputs); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	argNameToResultParam := make(map[string]types.Parameter)
    89  	for _, param := range result.Inputs {
    90  		argNameToResultParam[param.Name] = param
    91  	}
    92  
    93  	// Indexed
    94  	if value := argNameToResultParam["hash"].Value; value != "0x000bf9f2adc93a1da7b9e61f44ee6504f99c467a2812b354d70a07f0b3cdc58c" {
    95  		t.Fatal("wrong hash value:", value)
    96  	}
    97  
    98  	// Nonindexed
    99  	if value := argNameToResultParam["registrationDate"].Value; value != "1494329242" {
   100  		t.Fatal("wrong registrationDate value:", value)
   101  	}
   102  }
   103  
   104  // We use this type alias and the following function only to encapsulate the big .Int
   105  // type. Note that unlike elsewhere, we need to use the big .Int as an alias because
   106  // the Go Ethereum code expects big.Ints for int256 types.
   107  type myBig = big.Int
   108  
   109  func newMyBig(v int64) *myBig { return big.NewInt(v) }
   110  
   111  func TestArticulateArgumentsData(t *testing.T) {
   112  	parsedAbi, err := abi.JSON(strings.NewReader(ensRegistrar))
   113  	if err != nil {
   114  		t.Fatal(err)
   115  	}
   116  
   117  	var abiMethod abi.Method
   118  	var result *types.Function
   119  	var expected any
   120  	var packed []byte
   121  
   122  	// type: hash
   123  	abiMethod = parsedAbi.Methods["releaseDeed"]
   124  	result = types.FunctionFromAbiMethod(&abiMethod)
   125  	expected = "0x00120aa407bdbff1d93ea98dafc5f1da56b589b427167ec414bccbe0cfdfd573"
   126  	packed, err = abiMethod.Inputs.Pack(expected)
   127  	if err != nil {
   128  		return
   129  	}
   130  
   131  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	if value := result.Inputs[0].Value; value != "0x00120aa407bdbff1d93ea98dafc5f1da56b589b427167ec414bccbe0cfdfd573" {
   135  		t.Fatal("wrong result value:", value)
   136  	}
   137  
   138  	// type: uint256
   139  	abiMethod = parsedAbi.Methods["getAllowedTime"]
   140  	result = types.FunctionFromAbiMethod(&abiMethod)
   141  	expected = newMyBig(123).String()
   142  	packed, err = abiMethod.Inputs.Pack(newMyBig(123))
   143  	if err != nil {
   144  		return
   145  	}
   146  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   147  		t.Fatal(err)
   148  	}
   149  	if value := result.Inputs[0].Value; value != expected {
   150  		t.Fatal("wrong result value:", value)
   151  	}
   152  
   153  	// type: string
   154  	abiMethod = parsedAbi.Methods["invalidateName"]
   155  	result = types.FunctionFromAbiMethod(&abiMethod)
   156  	expected = "some test string"
   157  	packed, err = abiMethod.Inputs.Pack(expected)
   158  	if err != nil {
   159  		return
   160  	}
   161  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	if value := result.Inputs[0].Value; value != expected {
   165  		t.Fatal("wrong result value:", value)
   166  	}
   167  
   168  	// type: address
   169  	abiMethod = parsedAbi.Methods["cancelBid"]
   170  	result = types.FunctionFromAbiMethod(&abiMethod)
   171  	expected = "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5"
   172  	packed, err = abiMethod.Inputs.Pack(expected)
   173  	if err != nil {
   174  		return
   175  	}
   176  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	if value := result.Inputs[0].Value; value != expected {
   180  		t.Fatal("wrong result value:", value)
   181  	}
   182  
   183  	// type: uint8
   184  	abiMethod = parsedAbi.Methods["state"]
   185  	result = types.FunctionFromAbiMethod(&abiMethod)
   186  	expected = 2
   187  	packed, err = abiMethod.Inputs.Pack(expected)
   188  	if err != nil {
   189  		return
   190  	}
   191  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  	if value := result.Inputs[0].Value; value != expected {
   195  		t.Fatal("wrong result value:", value)
   196  	}
   197  
   198  	// type: bool
   199  	abiMethod = parsedAbi.Methods["isAllowed"]
   200  	result = types.FunctionFromAbiMethod(&abiMethod)
   201  	expected = true
   202  	packed, err = abiMethod.Inputs.Pack(expected)
   203  	if err != nil {
   204  		return
   205  	}
   206  	if err = articulateArguments(abiMethod.Inputs, string(packed), nil, result.Inputs); err != nil {
   207  		t.Fatal(err)
   208  	}
   209  	if value := result.Inputs[0].Value; value != expected {
   210  		t.Fatal("wrong result value:", value)
   211  	}
   212  }
   213  
   214  func TestArticulateArgumentsSlice(t *testing.T) {
   215  	abi, err := abi.JSON(strings.NewReader(ensRegistrar))
   216  	if err != nil {
   217  		t.Fatal(err)
   218  	}
   219  	txData := `0x0000000000000000000000000000000000000000000000000000000000000040cb93e7ddea88eb37f5419784b399cf13f7df44079d05905006044dd14bb898110000000000000000000000000000000000000000000000000000000000000003000bf9f2adc93a1da7b9e61f44ee6504f99c467a2812b354d70a07f0b3cdc58c0007cc5734453f8d7bbacd4b3a8e753250dc4a432aaa5be5b048c59e0b5ac5fc00120aa407bdbff1d93ea98dafc5f1da56b589b427167ec414bccbe0cfdfd573`
   220  	expectedBytes, _ := json.Marshal(&[]string{
   221  		"0x000bf9f2adc93a1da7b9e61f44ee6504f99c467a2812b354d70a07f0b3cdc58c",
   222  		"0x0007cc5734453f8d7bbacd4b3a8e753250dc4a432aaa5be5b048c59e0b5ac5fc",
   223  		"0x00120aa407bdbff1d93ea98dafc5f1da56b589b427167ec414bccbe0cfdfd573",
   224  	})
   225  	expected := string(expectedBytes)
   226  	abiMethod := abi.Methods["startAuctionsAndBid"]
   227  	result := types.FunctionFromAbiMethod(&abiMethod)
   228  
   229  	if err = articulateArguments(abiMethod.Inputs, txData[2:], nil, result.Inputs); err != nil {
   230  		t.Fatal(err)
   231  	}
   232  
   233  	if outputLen := len(result.Inputs); outputLen != 2 {
   234  		t.Fatal("wrong output length:", outputLen)
   235  	}
   236  
   237  	got := result.Inputs[0]
   238  
   239  	if name := got.Name; name != "hashes" {
   240  		t.Fatal("wrong name", name)
   241  	}
   242  	if inputType := got.ParameterType; inputType != "bytes32[]" {
   243  		t.Fatal("wrong type", inputType)
   244  	}
   245  	value, err := json.Marshal(&got.Value)
   246  	if err != nil {
   247  		t.Fatal("error while marshalling JSON:", err)
   248  	}
   249  	if string(value) != expected {
   250  		t.Fatal("wrong value", string(value))
   251  	}
   252  }
   253  
   254  func TestArticulateArgumentsComplex(t *testing.T) {
   255  	// https://etherscan.io/tx/0xfa62ab036f36b4755bfa0d6e3e641d08daa7e41e95b0c1f2246485702319efb8
   256  	// operate(tuple[] accounts,tuple[] actions)
   257  	abiJson := `[{"constant":false,"inputs":[{"components":[{"name":"owner","type":"address"},{"name":"number","type":"uint256"}],"name":"accounts","type":"tuple[]"},{"components":[{"name":"actionType","type":"uint8"},{"name":"accountId","type":"uint256"},{"components":[{"name":"sign","type":"bool"},{"name":"denomination","type":"uint8"},{"name":"ref","type":"uint8"},{"name":"value","type":"uint256"}],"name":"amount","type":"tuple"},{"name":"primaryMarketId","type":"uint256"},{"name":"secondaryMarketId","type":"uint256"},{"name":"otherAddress","type":"address"},{"name":"otherAccountId","type":"uint256"},{"name":"data","type":"bytes"}],"name":"actions","type":"tuple[]"}],"name":"operate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
   258  	txData := `0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000066215d23b8a247c80c2d1b7bef4befc2ab384bce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010f0cf064dd59200000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000020000000000000000000000007a603dc3eb0f4e3883929ee15a3c86d2ac45f4450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045f783cce6b7ff23b2ab2d70e416cdb7d6055f510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000`
   259  	abi, err := abi.JSON(strings.NewReader(abiJson))
   260  	if err != nil {
   261  		t.Fatal(err)
   262  	}
   263  	abiMethod := abi.Methods["operate"]
   264  	result := types.FunctionFromAbiMethod(&abiMethod)
   265  
   266  	if err = articulateArguments(abiMethod.Inputs, txData[2:], nil, result.Inputs); err != nil {
   267  		t.Fatal(err)
   268  	}
   269  
   270  	if inputLength := len(result.Inputs); inputLength != 2 {
   271  		t.Fatal("wrong input length:", inputLength)
   272  	}
   273  
   274  	first := result.Inputs[0]
   275  	expectedMap := []map[string]any{{
   276  		"number": "0",
   277  		"owner":  "0x66215d23b8a247c80c2d1b7bef4befc2ab384bce",
   278  	}}
   279  	expected, _ := json.Marshal(&expectedMap)
   280  	value, err := json.Marshal(&first.Value)
   281  	if err != nil {
   282  		t.Fatal("error while marshalling JSON:", err)
   283  	}
   284  	if string(value) != string(expected) {
   285  		t.Fatal("wrong input #1 value:", string(value))
   286  	}
   287  }
   288  
   289  func TestArticulateArgumentsTupleWrongType(t *testing.T) {
   290  	// The below ABI is just like:
   291  	// https://etherscan.io/tx/0xfa62ab036f36b4755bfa0d6e3e641d08daa7e41e95b0c1f2246485702319efb8
   292  	// operate(tuple[] accounts,tuple[] actions)
   293  	// but it uses bytes32 type for `owner` instead of address type. We should still articulate this as a hash
   294  	abiJson := `[{"constant":false,"inputs":[{"components":[{"name":"owner","type":"bytes32"},{"name":"number","type":"uint256"}],"name":"accounts","type":"tuple[]"},{"components":[{"name":"actionType","type":"uint8"},{"name":"accountId","type":"uint256"},{"components":[{"name":"sign","type":"bool"},{"name":"denomination","type":"uint8"},{"name":"ref","type":"uint8"},{"name":"value","type":"uint256"}],"name":"amount","type":"tuple"},{"name":"primaryMarketId","type":"uint256"},{"name":"secondaryMarketId","type":"uint256"},{"name":"otherAddress","type":"address"},{"name":"otherAccountId","type":"uint256"},{"name":"data","type":"bytes"}],"name":"actions","type":"tuple[]"}],"name":"operate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
   295  	txData := `0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000066215d23b8a247c80c2d1b7bef4befc2ab384bce0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010f0cf064dd59200000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000020000000000000000000000007a603dc3eb0f4e3883929ee15a3c86d2ac45f4450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045f783cce6b7ff23b2ab2d70e416cdb7d6055f510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000`
   296  	abi, err := abi.JSON(strings.NewReader(abiJson))
   297  	if err != nil {
   298  		t.Fatal(err)
   299  	}
   300  	abiMethod := abi.Methods["operate"]
   301  	result := types.FunctionFromAbiMethod(&abiMethod)
   302  
   303  	if err = articulateArguments(abiMethod.Inputs, txData[2:], nil, result.Inputs); err != nil {
   304  		t.Fatal(err)
   305  	}
   306  
   307  	if inputLength := len(result.Inputs); inputLength != 2 {
   308  		t.Fatal("wrong input length:", inputLength)
   309  	}
   310  
   311  	first := result.Inputs[0]
   312  	expectedMap := []map[string]any{{
   313  		"number": "0",
   314  		"owner":  "0x00000000000000000000000066215d23b8a247c80c2d1b7bef4befc2ab384bce",
   315  	}}
   316  	expected, _ := json.Marshal(&expectedMap)
   317  	value, err := json.Marshal(&first.Value)
   318  	if err != nil {
   319  		t.Fatal("error while marshalling JSON:", err)
   320  	}
   321  	if string(value) != string(expected) {
   322  		t.Fatal("wrong input #1 value:", string(value))
   323  	}
   324  }
   325  
   326  func TestArticulateArgumentsTupleTuple(t *testing.T) {
   327  	// This ABI comes from https://docs.soliditylang.org/en/latest/abi-spec.html#handling-tuple-types
   328  	abiJson := `[
   329    {
   330      "name": "f",
   331      "type": "function",
   332      "inputs": [
   333        {
   334          "name": "s",
   335          "type": "tuple",
   336          "components": [
   337            {
   338              "name": "a",
   339              "type": "uint256"
   340            },
   341            {
   342              "name": "b",
   343              "type": "uint256[]"
   344            },
   345            {
   346              "name": "c",
   347              "type": "tuple[]",
   348              "components": [
   349                {
   350                  "name": "x",
   351                  "type": "uint256"
   352                },
   353                {
   354                  "name": "y",
   355                  "type": "uint256"
   356                }
   357              ]
   358            }
   359          ]
   360        },
   361        {
   362          "name": "t",
   363          "type": "tuple",
   364          "components": [
   365            {
   366              "name": "x",
   367              "type": "uint256"
   368            },
   369            {
   370              "name": "y",
   371              "type": "uint256"
   372            }
   373          ]
   374        },
   375        {
   376          "name": "a",
   377          "type": "uint256"
   378        }
   379      ],
   380      "outputs": []
   381    }
   382  ]`
   383  
   384  	parsedAbi, err := abi.JSON(strings.NewReader(abiJson))
   385  	if err != nil {
   386  		t.Fatal(err)
   387  	}
   388  	abiMethod := parsedAbi.Methods["f"]
   389  	result := types.FunctionFromAbiMethod(&abiMethod)
   390  
   391  	first := struct {
   392  		A *myBig   `json:"a"`
   393  		B []*myBig `json:"b"`
   394  		C []struct {
   395  			X *myBig `json:"x"`
   396  			Y *myBig `json:"y"`
   397  		} `json:"c"`
   398  	}{
   399  		A: newMyBig(1),
   400  		B: []*myBig{},
   401  		C: []struct {
   402  			X *myBig `json:"x"`
   403  			Y *myBig `json:"y"`
   404  		}{{
   405  			X: newMyBig(1),
   406  			Y: newMyBig(2),
   407  		}},
   408  	}
   409  	second := struct {
   410  		X *myBig `json:"x"`
   411  		Y *myBig `json:"y"`
   412  	}{
   413  		X: newMyBig(1),
   414  		Y: newMyBig(2),
   415  	}
   416  	third := newMyBig(3)
   417  
   418  	payload, err := abiMethod.Inputs.Pack(first, second, third)
   419  	if err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	txData := base.Bytes2Hex(payload)
   423  
   424  	if err = articulateArguments(abiMethod.Inputs, txData, nil, result.Inputs); err != nil {
   425  		t.Fatal(err)
   426  	}
   427  
   428  	if inputLength := len(result.Inputs); inputLength != 3 {
   429  		t.Fatal("wrong input length:", inputLength)
   430  	}
   431  
   432  	expected, _ := json.Marshal(map[string]any{
   433  		"a": "1",
   434  		"b": []string{},
   435  		"c": []map[string]string{
   436  			{
   437  				"x": "1",
   438  				"y": "2",
   439  			},
   440  		},
   441  	})
   442  	value, err := json.Marshal(&result.Inputs[0].Value)
   443  	if err != nil {
   444  		t.Fatal("error while marshalling JSON:", err)
   445  	}
   446  	if string(value) != string(expected) {
   447  		fmt.Println(string(expected))
   448  		t.Fatal("wrong value of the first input:", string(value))
   449  	}
   450  
   451  	expected, _ = json.Marshal(map[string]string{
   452  		"x": "1",
   453  		"y": "2",
   454  	})
   455  	value, err = json.Marshal(&result.Inputs[1].Value)
   456  	if err != nil {
   457  		t.Fatal("error while marshalling JSON:", err)
   458  	}
   459  	if string(value) != string(expected) {
   460  		t.Fatal("wrong value of the second input:", value)
   461  	}
   462  
   463  	expected, _ = json.Marshal(third)
   464  	if value := result.Inputs[2].Value; value != string(expected) {
   465  		t.Fatal("wrong value of the third input:", value)
   466  	}
   467  }
   468  
   469  func TestArticulateAnonymousArguments(t *testing.T) {
   470  	// peek() from 0x729d19f657bd0614b4985cf1d82531c67569197b
   471  	abiJson := `[{"constant":true,"inputs":[],"name":"peek","outputs":[{"name":"","type":"bytes32"},{"name":"","type":"bool"}],"payable":false,"type":"function"}]`
   472  	abi, err := abi.JSON(strings.NewReader(abiJson))
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  	output := `0x00000000000000000000000000000000000000000000002993a384ff8db780000000000000000000000000000000000000000000000000000000000000000001`
   477  
   478  	abiMethod := abi.Methods["peek"]
   479  	f := types.FunctionFromAbiMethod(&abiMethod)
   480  
   481  	if err = articulateArguments(abiMethod.Outputs, output[2:], nil, f.Outputs); err != nil {
   482  		t.Fatal(err)
   483  	}
   484  
   485  	if paramType := f.Outputs[0].ParameterType; paramType != "bytes32" {
   486  		t.Fatal("wrong type of the first parameter", paramType)
   487  	}
   488  	if value := f.Outputs[0].Value; value != "0x00000000000000000000000000000000000000000000002993a384ff8db78000" {
   489  		t.Fatal("wrong value of the first parameter", value)
   490  	}
   491  
   492  	if paramType := f.Outputs[1].ParameterType; paramType != "bool" {
   493  		t.Fatal("wrong type of the second parameter", paramType)
   494  	}
   495  	if value := f.Outputs[1].Value; value != true {
   496  		t.Fatal("wrong value of the second parameter", value)
   497  	}
   498  }