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 }