github.com/ethereum/go-ethereum@v1.16.1/accounts/abi/topics_test.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package abi 18 19 import ( 20 "math" 21 "math/big" 22 "reflect" 23 "testing" 24 25 "github.com/ethereum/go-ethereum/common" 26 "github.com/ethereum/go-ethereum/crypto" 27 ) 28 29 func TestMakeTopics(t *testing.T) { 30 t.Parallel() 31 type args struct { 32 query [][]interface{} 33 } 34 tests := []struct { 35 name string 36 args args 37 want [][]common.Hash 38 wantErr bool 39 }{ 40 { 41 "support fixed byte types, right padded to 32 bytes", 42 args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}}, 43 [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, 44 false, 45 }, 46 { 47 "support common hash types in topics", 48 args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}}, 49 [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, 50 false, 51 }, 52 { 53 "support address types in topics", 54 args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}}, 55 [][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}}, 56 false, 57 }, 58 { 59 "support positive *big.Int types in topics", 60 args{[][]interface{}{ 61 {big.NewInt(1)}, 62 {big.NewInt(1).Lsh(big.NewInt(2), 254)}, 63 }}, 64 [][]common.Hash{ 65 {common.HexToHash("0000000000000000000000000000000000000000000000000000000000000001")}, 66 {common.Hash{128}}, 67 }, 68 false, 69 }, 70 { 71 "support negative *big.Int types in topics", 72 args{[][]interface{}{ 73 {big.NewInt(-1)}, 74 {big.NewInt(math.MinInt64)}, 75 }}, 76 [][]common.Hash{ 77 {common.MaxHash}, 78 {common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")}, 79 }, 80 false, 81 }, 82 { 83 "support boolean types in topics", 84 args{[][]interface{}{ 85 {true}, 86 {false}, 87 }}, 88 [][]common.Hash{ 89 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, 90 {common.Hash{0}}, 91 }, 92 false, 93 }, 94 { 95 "support int/uint(8/16/32/64) types in topics", 96 args{[][]interface{}{ 97 {int8(-2)}, 98 {int16(-3)}, 99 {int32(-4)}, 100 {int64(-5)}, 101 {int8(1)}, 102 {int16(256)}, 103 {int32(65536)}, 104 {int64(4294967296)}, 105 {uint8(1)}, 106 {uint16(256)}, 107 {uint32(65536)}, 108 {uint64(4294967296)}, 109 }}, 110 [][]common.Hash{ 111 {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}}, 112 {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}}, 113 {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}}, 114 {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}}, 115 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, 116 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}}, 117 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}}, 118 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, 119 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}, 120 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}}, 121 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}}, 122 {common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}}, 123 }, 124 false, 125 }, 126 { 127 "support string types in topics", 128 args{[][]interface{}{{"hello world"}}}, 129 [][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}}, 130 false, 131 }, 132 { 133 "support byte slice types in topics", 134 args{[][]interface{}{{[]byte{1, 2, 3}}}}, 135 [][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}}, 136 false, 137 }, 138 } 139 for _, tt := range tests { 140 t.Run(tt.name, func(t *testing.T) { 141 t.Parallel() 142 got, err := MakeTopics(tt.args.query...) 143 if (err != nil) != tt.wantErr { 144 t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr) 145 return 146 } 147 if !reflect.DeepEqual(got, tt.want) { 148 t.Errorf("makeTopics() = %v, want %v", got, tt.want) 149 } 150 }) 151 } 152 153 t.Run("does not mutate big.Int", func(t *testing.T) { 154 t.Parallel() 155 want := [][]common.Hash{{common.HexToHash("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")}} 156 157 in := big.NewInt(-1) 158 got, err := MakeTopics([]interface{}{in}) 159 if err != nil { 160 t.Fatalf("makeTopics() error = %v", err) 161 } 162 if !reflect.DeepEqual(got, want) { 163 t.Fatalf("makeTopics() = %v, want %v", got, want) 164 } 165 if orig := big.NewInt(-1); in.Cmp(orig) != 0 { 166 t.Fatalf("makeTopics() mutated an input parameter from %v to %v", orig, in) 167 } 168 }) 169 } 170 171 type args struct { 172 createObj func() interface{} 173 resultObj func() interface{} 174 resultMap func() map[string]interface{} 175 fields Arguments 176 topics []common.Hash 177 } 178 179 type bytesStruct struct { 180 StaticBytes [5]byte 181 } 182 type int8Struct struct { 183 Int8Value int8 184 } 185 type int256Struct struct { 186 Int256Value *big.Int 187 } 188 189 type hashStruct struct { 190 HashValue common.Hash 191 } 192 193 type funcStruct struct { 194 FuncValue [24]byte 195 } 196 197 type topicTest struct { 198 name string 199 args args 200 wantErr bool 201 } 202 203 func setupTopicsTests() []topicTest { 204 bytesType, _ := NewType("bytes5", "", nil) 205 int8Type, _ := NewType("int8", "", nil) 206 int256Type, _ := NewType("int256", "", nil) 207 tupleType, _ := NewType("tuple(int256,int8)", "", nil) 208 stringType, _ := NewType("string", "", nil) 209 funcType, _ := NewType("function", "", nil) 210 211 tests := []topicTest{ 212 { 213 name: "support fixed byte types, right padded to 32 bytes", 214 args: args{ 215 createObj: func() interface{} { return &bytesStruct{} }, 216 resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} }, 217 resultMap: func() map[string]interface{} { 218 return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}} 219 }, 220 fields: Arguments{Argument{ 221 Name: "staticBytes", 222 Type: bytesType, 223 Indexed: true, 224 }}, 225 topics: []common.Hash{ 226 {1, 2, 3, 4, 5}, 227 }, 228 }, 229 wantErr: false, 230 }, 231 { 232 name: "int8 with negative value", 233 args: args{ 234 createObj: func() interface{} { return &int8Struct{} }, 235 resultObj: func() interface{} { return &int8Struct{Int8Value: -1} }, 236 resultMap: func() map[string]interface{} { 237 return map[string]interface{}{"int8Value": int8(-1)} 238 }, 239 fields: Arguments{Argument{ 240 Name: "int8Value", 241 Type: int8Type, 242 Indexed: true, 243 }}, 244 topics: []common.Hash{ 245 {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 246 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 247 }, 248 }, 249 wantErr: false, 250 }, 251 { 252 name: "int256 with negative value", 253 args: args{ 254 createObj: func() interface{} { return &int256Struct{} }, 255 resultObj: func() interface{} { return &int256Struct{Int256Value: big.NewInt(-1)} }, 256 resultMap: func() map[string]interface{} { 257 return map[string]interface{}{"int256Value": big.NewInt(-1)} 258 }, 259 fields: Arguments{Argument{ 260 Name: "int256Value", 261 Type: int256Type, 262 Indexed: true, 263 }}, 264 topics: []common.Hash{ 265 {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 266 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 267 }, 268 }, 269 wantErr: false, 270 }, 271 { 272 name: "hash type", 273 args: args{ 274 createObj: func() interface{} { return &hashStruct{} }, 275 resultObj: func() interface{} { return &hashStruct{crypto.Keccak256Hash([]byte("stringtopic"))} }, 276 resultMap: func() map[string]interface{} { 277 return map[string]interface{}{"hashValue": crypto.Keccak256Hash([]byte("stringtopic"))} 278 }, 279 fields: Arguments{Argument{ 280 Name: "hashValue", 281 Type: stringType, 282 Indexed: true, 283 }}, 284 topics: []common.Hash{ 285 crypto.Keccak256Hash([]byte("stringtopic")), 286 }, 287 }, 288 wantErr: false, 289 }, 290 { 291 name: "function type", 292 args: args{ 293 createObj: func() interface{} { return &funcStruct{} }, 294 resultObj: func() interface{} { 295 return &funcStruct{[24]byte{255, 255, 255, 255, 255, 255, 255, 255, 296 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} 297 }, 298 resultMap: func() map[string]interface{} { 299 return map[string]interface{}{"funcValue": [24]byte{255, 255, 255, 255, 255, 255, 255, 255, 300 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}} 301 }, 302 fields: Arguments{Argument{ 303 Name: "funcValue", 304 Type: funcType, 305 Indexed: true, 306 }}, 307 topics: []common.Hash{ 308 {0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 309 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 310 }, 311 }, 312 wantErr: false, 313 }, 314 { 315 name: "error on topic/field count mismatch", 316 args: args{ 317 createObj: func() interface{} { return nil }, 318 resultObj: func() interface{} { return nil }, 319 resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, 320 fields: Arguments{Argument{ 321 Name: "tupletype", 322 Type: tupleType, 323 Indexed: true, 324 }}, 325 topics: []common.Hash{}, 326 }, 327 wantErr: true, 328 }, 329 { 330 name: "error on unindexed arguments", 331 args: args{ 332 createObj: func() interface{} { return &int256Struct{} }, 333 resultObj: func() interface{} { return &int256Struct{} }, 334 resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, 335 fields: Arguments{Argument{ 336 Name: "int256Value", 337 Type: int256Type, 338 Indexed: false, 339 }}, 340 topics: []common.Hash{ 341 {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 342 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 343 }, 344 }, 345 wantErr: true, 346 }, 347 { 348 name: "error on tuple in topic reconstruction", 349 args: args{ 350 createObj: func() interface{} { return &tupleType }, 351 resultObj: func() interface{} { return &tupleType }, 352 resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, 353 fields: Arguments{Argument{ 354 Name: "tupletype", 355 Type: tupleType, 356 Indexed: true, 357 }}, 358 topics: []common.Hash{{0}}, 359 }, 360 wantErr: true, 361 }, 362 { 363 name: "error on improper encoded function", 364 args: args{ 365 createObj: func() interface{} { return &funcStruct{} }, 366 resultObj: func() interface{} { return &funcStruct{} }, 367 resultMap: func() map[string]interface{} { 368 return make(map[string]interface{}) 369 }, 370 fields: Arguments{Argument{ 371 Name: "funcValue", 372 Type: funcType, 373 Indexed: true, 374 }}, 375 topics: []common.Hash{ 376 {0, 0, 0, 0, 0, 0, 0, 128, 255, 255, 255, 255, 255, 255, 255, 255, 377 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 378 }, 379 }, 380 wantErr: true, 381 }, 382 } 383 384 return tests 385 } 386 387 func TestParseTopics(t *testing.T) { 388 t.Parallel() 389 tests := setupTopicsTests() 390 391 for _, tt := range tests { 392 t.Run(tt.name, func(t *testing.T) { 393 t.Parallel() 394 createObj := tt.args.createObj() 395 if err := ParseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { 396 t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr) 397 } 398 resultObj := tt.args.resultObj() 399 if !reflect.DeepEqual(createObj, resultObj) { 400 t.Errorf("parseTopics() = %v, want %v", createObj, resultObj) 401 } 402 }) 403 } 404 } 405 406 func TestParseTopicsIntoMap(t *testing.T) { 407 t.Parallel() 408 tests := setupTopicsTests() 409 410 for _, tt := range tests { 411 t.Run(tt.name, func(t *testing.T) { 412 t.Parallel() 413 outMap := make(map[string]interface{}) 414 if err := ParseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr { 415 t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr) 416 } 417 resultMap := tt.args.resultMap() 418 if !reflect.DeepEqual(outMap, resultMap) { 419 t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap) 420 } 421 }) 422 } 423 }