diff --git a/x/evm/types/params.go b/x/evm/types/params.go index d5940b8f5c..fd38060bf9 100644 --- a/x/evm/types/params.go +++ b/x/evm/types/params.go @@ -16,6 +16,7 @@ package types import ( + "bytes" "fmt" "math/big" @@ -203,6 +204,50 @@ func validateEnabledPrecompiles(enabledPrecompiles []string) error { } } + if err := checkIfSortedInBytesRepr(enabledPrecompiles); err != nil { + return fmt.Errorf("enabled precompiles are not sorted: %v", err) + } + + if err := checkIfUniqueInBytesRepr(enabledPrecompiles); err != nil { + return fmt.Errorf("enabled precompiles are not unique: %v", err) + } + + return nil +} + +func checkIfSortedInBytesRepr(addrs []string) error { + n := len(addrs) + addrsInBytes := make([]common.Address, n) + for i, addr := range addrs { + addrsInBytes[i] = common.HexToAddress(addr) + } + + for i := 0; i < n-1; i++ { + cmp := bytes.Compare(addrsInBytes[i].Bytes(), addrsInBytes[i+1].Bytes()) + if cmp == 1 { + return fmt.Errorf("addresses are not sorted, %v > %v", addrsInBytes[i], addrsInBytes[i+1]) + } + } + + return nil +} + +func checkIfUniqueInBytesRepr(hexAddrs []string) error { + n := len(hexAddrs) + addrs := make([]common.Address, n) + for i, hexAddr := range hexAddrs { + addrs[i] = common.HexToAddress(hexAddr) + } + + exists := make(map[common.Address]struct{}, n) + for _, addr := range addrs { + if _, ok := exists[addr]; ok { + return fmt.Errorf("addr %v not unique", addr) + } + + exists[addr] = struct{}{} + } + return nil } diff --git a/x/evm/types/params_test.go b/x/evm/types/params_test.go index f7fbdb16ce..892d298c58 100644 --- a/x/evm/types/params_test.go +++ b/x/evm/types/params_test.go @@ -112,6 +112,16 @@ func TestParamsValidatePriv(t *testing.T) { require.NoError(t, validateEnabledPrecompiles(nil)) require.NoError(t, validateEnabledPrecompiles([]string{})) require.NoError(t, validateEnabledPrecompiles([]string{validEthAddress})) + + // check if sorted + addr1 := "0x1000000000000000000000000000000000000000" + addr2 := "0x2000000000000000000000000000000000000000" + require.NoError(t, validateEnabledPrecompiles([]string{addr1, addr2})) + require.Error(t, validateEnabledPrecompiles([]string{addr2, addr1})) + + // check if unique + require.NoError(t, validateEnabledPrecompiles([]string{addr1, addr2})) + require.Error(t, validateEnabledPrecompiles([]string{addr1, addr1})) } func TestValidateChainConfig(t *testing.T) { @@ -229,3 +239,87 @@ func TestCheckIfEnabledPrecompilesAreRegistered(t *testing.T) { } } } + +func TestCheckIfSortedInBytesRepr(t *testing.T) { + addr1 := "0x1000000000000000000000000000000000000000" + addr2 := "0x2000000000000000000000000000000000000000" + + // NOTE: we sort in bytes representation, so proper order will be []string{mixedCaseAddr, upperCaseAddr}, + // and it differs from lexicographically sorted strings + upperCaseAddr := "0xAB00000000000000000000000000000000000000" + mixedCaseAddr := "0xaA00000000000000000000000000000000000000" + + testCases := []struct { + name string + addrs []string + sorted bool + }{ + { + name: "test-case #1", + addrs: []string{addr1, addr2}, + sorted: true, + }, + { + name: "test-case #2", + addrs: []string{addr2, addr1}, + sorted: false, + }, + { + name: "test-case #3", + addrs: []string{mixedCaseAddr, upperCaseAddr}, + sorted: true, + }, + } + + for _, tc := range testCases { + err := checkIfSortedInBytesRepr(tc.addrs) + + if tc.sorted { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +} + +func TestCheckIfUniqueInBytesRepr(t *testing.T) { + addr1 := "0x1000000000000000000000000000000000000000" + addr2 := "0x2000000000000000000000000000000000000000" + + // NOTE: we check uniqueness in bytes representation, so lowerCaseAddr and mixedCaseAddr are the same, + // despite it differs in string representation + lowerCaseAddr := "0xab00000000000000000000000000000000000000" + mixedCaseAddr := "0xAb00000000000000000000000000000000000000" + + testCases := []struct { + name string + addrs []string + unique bool + }{ + { + name: "test-case #1", + addrs: []string{addr1, addr2}, + unique: true, + }, + { + name: "test-case #2", + addrs: []string{addr1, addr1}, + unique: false, + }, + { + name: "test-case #3", + addrs: []string{lowerCaseAddr, mixedCaseAddr}, + unique: false, + }, + } + + for _, tc := range testCases { + err := checkIfUniqueInBytesRepr(tc.addrs) + + if tc.unique { + require.NoError(t, err, tc.name) + } else { + require.Error(t, err, tc.name) + } + } +}