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  }