From 9cea8aa4d5655daa43618944dbc1d77415f3867b Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 1 May 2023 13:44:42 +0200 Subject: [PATCH 1/2] Support bytes20 and bytes32 in Ethereum contract bindings There are quite a few functions in our Solidity contracts that accept `bytes20` or `bytes32` as a parameter. One of many examples are `requestHeartbeat(bytes20 walletPubKeyHash, bytes calldata message)` from the `WalletCoordinator` contract and `getWallet(bytes32 walletID)` from the `WalletRegistry` contract. Given there was no parsing function defined in our Ethereum contract parsing logic, all functions accepting `bytes32` and `bytes20` were skipped during the code generation: ``` WARNING: Unsupported param type for method getWallet: ABI Type: bytes32 Go Type: [32]byte the method won't be callable with 'ethereum' command ``` This changeset adds two parsing functions: one for `bytes20` and another for `bytes32` Solidity type. In theory, we could have other variations of `bytesN` but this is not the case for our codebase, and defining a generic function is more complicated given we do not deal with slices but fixed-size arrays. Even though the proposed solution does not work for all `bytesN` types, it is enough for now. --- pkg/utils/decode/bytes.go | 35 +++++ pkg/utils/decode/bytes_test.go | 126 ++++++++++++++++++ tools/generators/ethereum/contract_parsing.go | 4 + 3 files changed, 165 insertions(+) create mode 100644 pkg/utils/decode/bytes.go create mode 100644 pkg/utils/decode/bytes_test.go diff --git a/pkg/utils/decode/bytes.go b/pkg/utils/decode/bytes.go new file mode 100644 index 0000000..3e667a6 --- /dev/null +++ b/pkg/utils/decode/bytes.go @@ -0,0 +1,35 @@ +package decode + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +func ParseBytes20(str string) ([20]byte, error) { + bytesArray := [20]byte{} + slice, err := hexutil.Decode(str) + if err != nil { + return bytesArray, err + } + if len(slice) != 20 { + return bytesArray, fmt.Errorf("expected 20 bytes array; has: [%v]", len(slice)) + } + + copy(bytesArray[:], slice) + return bytesArray, nil +} + +func ParseBytes32(str string) ([32]byte, error) { + bytesArray := [32]byte{} + slice, err := hexutil.Decode(str) + if err != nil { + return bytesArray, err + } + if len(slice) != 32 { + return bytesArray, fmt.Errorf("expected 32 bytes array; has: [%v]", len(slice)) + } + + copy(bytesArray[:], slice) + return bytesArray, nil +} diff --git a/pkg/utils/decode/bytes_test.go b/pkg/utils/decode/bytes_test.go new file mode 100644 index 0000000..96b774b --- /dev/null +++ b/pkg/utils/decode/bytes_test.go @@ -0,0 +1,126 @@ +package decode + +import ( + "fmt" + "testing" +) + +func TestParseBytes20(t *testing.T) { + hexEncoded := "0x3805eed0bb0792eff8815addedb36add2c7257e5" + bytes, err := ParseBytes20(hexEncoded) + if err != nil { + t.Fatal(err) + } + + roundtrip := fmt.Sprintf("0x%x", bytes) + + if roundtrip != hexEncoded { + t.Errorf( + "unexpected parsed bytes\nexpected: %v\nactual: %v\n", + hexEncoded, + roundtrip, + ) + } +} + +func TestParseBytes20_Fail(t *testing.T) { + + var tests = map[string]struct { + input string + expectedError string + }{ + "too short": { + input: "0xFFFF", + expectedError: "expected 20 bytes array; has: [2]", + }, + "empty": { + input: "", + expectedError: "empty hex string", + }, + "just 0x prefix": { + input: "0x", + expectedError: "expected 20 bytes array; has: [0]", + }, + "no prefix": { + input: "FF", + expectedError: "hex string without 0x prefix", + }, + "invalid hex": { + input: "0xLMA0", + expectedError: "invalid hex string", + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + _, actualError := ParseBytes20(test.input) + if actualError.Error() != test.expectedError { + t.Errorf( + "unexpected error\nexpected: %v\nactual: %v\n", + test.expectedError, + actualError, + ) + } + }) + } +} + +func TestParseBytes32(t *testing.T) { + hexEncoded := "0xad63a8286ea7fa22d75e167216171417a96c4753946fd45e3a8dff4e4f29a830" + bytes, err := ParseBytes32(hexEncoded) + if err != nil { + t.Fatal(err) + } + + roundtrip := fmt.Sprintf("0x%x", bytes) + + if roundtrip != hexEncoded { + t.Errorf( + "unexpected parsed bytes\nexpected: %v\nactual: %v\n", + hexEncoded, + roundtrip, + ) + } +} + +func TestParseBytes32_Fail(t *testing.T) { + + var tests = map[string]struct { + input string + expectedError string + }{ + "too short": { + input: "0xFFFF", + expectedError: "expected 32 bytes array; has: [2]", + }, + "empty": { + input: "", + expectedError: "empty hex string", + }, + "just 0x prefix": { + input: "0x", + expectedError: "expected 32 bytes array; has: [0]", + }, + "no prefix": { + input: "FF", + expectedError: "hex string without 0x prefix", + }, + "invalid hex": { + input: "0xLMA0", + expectedError: "invalid hex string", + }, + } + + for testName, test := range tests { + t.Run(testName, func(t *testing.T) { + _, actualError := ParseBytes32(test.input) + if actualError.Error() != test.expectedError { + t.Errorf( + "unexpected error\nexpected: %v\nactual: %v\n", + test.expectedError, + actualError, + ) + } + }) + } +} diff --git a/tools/generators/ethereum/contract_parsing.go b/tools/generators/ethereum/contract_parsing.go index 0a3cbfb..9f1b47e 100644 --- a/tools/generators/ethereum/contract_parsing.go +++ b/tools/generators/ethereum/contract_parsing.go @@ -231,6 +231,10 @@ func buildMethodInfo( switch goType { case "[]byte": cmdParsingFn = "hexutil.Decode(%s)" + case "[20]byte": + cmdParsingFn = "decode.ParseBytes20(%s)" + case "[32]byte": + cmdParsingFn = "decode.ParseBytes32(%s)" case "common.Address": cmdParsingFn = "chainutil.AddressFromHex(%s)" case "*big.Int": From b412b7ab27edc79eca0cc3cd30bd9c2c27d2462c Mon Sep 17 00:00:00 2001 From: Piotr Dyraga Date: Mon, 1 May 2023 13:54:25 +0200 Subject: [PATCH 2/2] Added documentation to ParseBytes20 and ParseBytes32 --- pkg/utils/decode/bytes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/utils/decode/bytes.go b/pkg/utils/decode/bytes.go index 3e667a6..4f92530 100644 --- a/pkg/utils/decode/bytes.go +++ b/pkg/utils/decode/bytes.go @@ -6,6 +6,8 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +// ParseBytes20 parses `string` into `[20]byte` type. The input string must have +// 20 bytes hex-encoded. func ParseBytes20(str string) ([20]byte, error) { bytesArray := [20]byte{} slice, err := hexutil.Decode(str) @@ -20,6 +22,8 @@ func ParseBytes20(str string) ([20]byte, error) { return bytesArray, nil } +// ParseBytes32 parses `string` into `[32]byte` type. The input string must have +// 32 bytes hex-encoded. func ParseBytes32(str string) ([32]byte, error) { bytesArray := [32]byte{} slice, err := hexutil.Decode(str)