From 2844516b193a9152321a488c9c78d0837dd7619e Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 28 Nov 2022 00:01:35 -0500 Subject: [PATCH 1/9] Add tests for decoding function returns Signed-off-by: Jim Zhang --- pkg/abi/abi_test.go | 120 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 1e6ea912..ead418b4 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -157,6 +157,75 @@ const sampleABI3 = `[ } ]` +const sampleABI4 = `[ + { + "inputs": [], + "name": "getArray", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ]` + +const sampleABI5 = `[ + { + "inputs": [], + "name": "getArray", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ]` + +const sampleABI6 = `[ + { + "inputs": [], + "name": "getArray", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + } + ]` + +const sampleABI7 = `[ + { + "inputs": [], + "name": "getArray", + "outputs": [ + { + "internalType": "string[2]", + "name": "", + "type": "string[2]" + } + ], + "stateMutability": "view", + "type": "function" + } + ]` + func testABI(t *testing.T, abiJSON string) (abi ABI) { err := json.Unmarshal([]byte(abiJSON), &abi) assert.NoError(t, err) @@ -433,6 +502,57 @@ func TestParseJSONMixedModeOk(t *testing.T) { } +func TestParseOuptutStringOk(t *testing.T) { + + outputs := testABI(t, sampleABI4)[0].Outputs + + values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000000") + + cv, err := outputs.DecodeABIData(values, 0) + assert.NoError(t, err) + + assert.Equal(t, "a", cv.Children[0].Value) +} + +func TestParseOutputTupleOk(t *testing.T) { + + outputs := testABI(t, sampleABI5)[0].Outputs + + values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") + + cv, err := outputs.DecodeABIData(values, 0) + assert.NoError(t, err) + + assert.Equal(t, "a", cv.Children[0].Value) + assert.Equal(t, "b", cv.Children[1].Value) +} + +func TestParseOutputDynamicArrayOk(t *testing.T) { + + outputs := testABI(t, sampleABI6)[0].Outputs + + values, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") + + cv, err := outputs.DecodeABIData(values, 0) + assert.NoError(t, err) + + assert.Equal(t, "a", cv.Children[0].Children[0].Value) + assert.Equal(t, "b", cv.Children[0].Children[1].Value) +} + +func TestParseOutputFixedArrayOk(t *testing.T) { + + outputs := testABI(t, sampleABI7)[0].Outputs + + values, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") + + cv, err := outputs.DecodeABIData(values, 0) + assert.NoError(t, err) + + assert.Equal(t, "a", cv.Children[0].Children[0].Value) + assert.Equal(t, "b", cv.Children[1].Children[1].Value) +} + func TestABIParseCoerceGoTypes(t *testing.T) { inputs := testABI(t, sampleABI1)[0].Inputs From bcb501b70e2a858e04f287f139bd680e0ff4f150 Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Mon, 28 Nov 2022 22:19:50 -0500 Subject: [PATCH 2/9] Fix fixed array of dynamic types decoding from abi encoded values Signed-off-by: Jim Zhang --- pkg/abi/abi_test.go | 48 +++++++++++++++++++++++++++++++++++++++- pkg/abi/abidecode.go | 52 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index ead418b4..07c01638 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -226,6 +226,31 @@ const sampleABI7 = `[ } ]` +const sampleABI8 = `[ + { + "name" : "multi", + "type": "function", + "outputs": [ + { + "name": "", + "type": "uint256[3]" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "string[2]" + }, + { + "name": "", + "type": "bool" + } + ] + } + ]` + func testABI(t *testing.T, abiJSON string) (abi ABI) { err := json.Unmarshal([]byte(abiJSON), &abi) assert.NoError(t, err) @@ -550,7 +575,28 @@ func TestParseOutputFixedArrayOk(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "a", cv.Children[0].Children[0].Value) - assert.Equal(t, "b", cv.Children[1].Children[1].Value) + assert.Equal(t, "b", cv.Children[0].Children[1].Value) +} + +func TestParseOutputMixedTypesOk(t *testing.T) { + + outputs := testABI(t, sampleABI8)[0].Outputs + + values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000") + + cv, err := outputs.DecodeABIData(values, 0) + assert.NoError(t, err) + + bignumber, _ := big.NewInt(0).SetString("30000000000000000000", 10) + assert.Equal(t, big.NewInt(1545304298), cv.Children[0].Children[0].Value) + assert.Equal(t, big.NewInt(6), cv.Children[0].Children[1].Value) + assert.Equal(t, bignumber, cv.Children[0].Children[2].Value) + address, _ := cv.Children[1].JSON() + assert.Equal(t, "\"ab1257528b3782fb40d7ed5f72e624b744dffb2f\"", string(address)) + assert.Equal(t, "Ethereum", cv.Children[2].Children[0].Value) + assert.Equal(t, "Hello, Ethereum!", cv.Children[2].Children[1].Value) + boolean, _ := cv.Children[3].JSON() + assert.Equal(t, "false", string(boolean)) } func TestABIParseCoerceGoTypes(t *testing.T) { diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index a8ad94f8..d14a0365 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -18,6 +18,7 @@ package abi import ( "context" + "encoding/binary" "fmt" "math/big" @@ -165,19 +166,68 @@ func decodeABIFixedArrayBytes(ctx context.Context, breadcrumbs string, block []b Children: make([]*ComponentValue, component.arrayLength), } headBytesRead = 0 + // a fixed length sequence of a dynamic type has these parts, using a 'string[2]' type as an example: + // 0000000000000000000000000000000000000000000000000000000000000020 <= offset of the start of the content + // 0000000000000000000000000000000000000000000000000000000000000040 <= offset of the 1st element + // 0000000000000000000000000000000000000000000000000000000000000080 <= offset of the 2nd element + // 0000000000000000000000000000000000000000000000000000000000000001 <= length of the 1st element + // 6100000000000000000000000000000000000000000000000000000000000000 <= content of the 1st element + // 0000000000000000000000000000000000000000000000000000000000000001 <= length of the 2nd element + // 6200000000000000000000000000000000000000000000000000000000000000 <= content of the 2nd element + + returnOutput := block[headPosition : headPosition+32] + + var content []byte + if isDynamicType(component.arrayChild) { + offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) + if offset > uint64(len(block)) { + return -1, nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABIArrayCount, breadcrumbs) + } + content = block[offset:] + } else { + content = block[headPosition:] + } + + if headPosition+32*component.arrayLength > len(block) { + return -1, nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABIValue, component, breadcrumbs) + } + + offset := 0 for i := 0; i < component.arrayLength; i++ { childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[fix,i:%d,o:%d]", breadcrumbs, i, headPosition), - block, headStart, headPosition, component.arrayChild) + content, headStart, offset, component.arrayChild) if err != nil { return -1, nil, err } cv.Children[i] = child headBytesRead += childHeadBytes headPosition += childHeadBytes + offset += childHeadBytes } return headBytesRead, cv, err } +func isDynamicType(tc *typeComponent) bool { + switch tc.cType { + case TupleComponent: + for _, childType := range tc.tupleChildren { + if isDynamicType(childType) { + return true + } + } + return false + case ElementaryComponent: + tName := tc.elementaryType.name + // types of bytes3 are not dynamic types, so make sure to check the suffix + if tName == "string" || (tName == "bytes" && tc.elementarySuffix == "") { + return true + } + return false + default: + return false + } +} + func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block []byte, dataOffset int, component *typeComponent) (cv *ComponentValue, err error) { arrayLength, err := decodeABILength(ctx, breadcrumbs, block, dataOffset) if err != nil { From 2dd5992b8dd9720d05ee39d16e8a1c65ecd121ab Mon Sep 17 00:00:00 2001 From: Jim Zhang Date: Tue, 29 Nov 2022 13:00:36 -0500 Subject: [PATCH 3/9] Move output decoding tests to abidecode_test.go Signed-off-by: Jim Zhang --- pkg/abi/abi_test.go | 166 -------------------------------------- pkg/abi/abidecode_test.go | 107 ++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 166 deletions(-) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 07c01638..1e6ea912 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -157,100 +157,6 @@ const sampleABI3 = `[ } ]` -const sampleABI4 = `[ - { - "inputs": [], - "name": "getArray", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } - ]` - -const sampleABI5 = `[ - { - "inputs": [], - "name": "getArray", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - }, - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } - ]` - -const sampleABI6 = `[ - { - "inputs": [], - "name": "getArray", - "outputs": [ - { - "internalType": "string[]", - "name": "", - "type": "string[]" - } - ], - "stateMutability": "view", - "type": "function" - } - ]` - -const sampleABI7 = `[ - { - "inputs": [], - "name": "getArray", - "outputs": [ - { - "internalType": "string[2]", - "name": "", - "type": "string[2]" - } - ], - "stateMutability": "view", - "type": "function" - } - ]` - -const sampleABI8 = `[ - { - "name" : "multi", - "type": "function", - "outputs": [ - { - "name": "", - "type": "uint256[3]" - }, - { - "name": "", - "type": "address" - }, - { - "name": "", - "type": "string[2]" - }, - { - "name": "", - "type": "bool" - } - ] - } - ]` - func testABI(t *testing.T, abiJSON string) (abi ABI) { err := json.Unmarshal([]byte(abiJSON), &abi) assert.NoError(t, err) @@ -527,78 +433,6 @@ func TestParseJSONMixedModeOk(t *testing.T) { } -func TestParseOuptutStringOk(t *testing.T) { - - outputs := testABI(t, sampleABI4)[0].Outputs - - values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000016100000000000000000000000000000000000000000000000000000000000000") - - cv, err := outputs.DecodeABIData(values, 0) - assert.NoError(t, err) - - assert.Equal(t, "a", cv.Children[0].Value) -} - -func TestParseOutputTupleOk(t *testing.T) { - - outputs := testABI(t, sampleABI5)[0].Outputs - - values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") - - cv, err := outputs.DecodeABIData(values, 0) - assert.NoError(t, err) - - assert.Equal(t, "a", cv.Children[0].Value) - assert.Equal(t, "b", cv.Children[1].Value) -} - -func TestParseOutputDynamicArrayOk(t *testing.T) { - - outputs := testABI(t, sampleABI6)[0].Outputs - - values, _ := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") - - cv, err := outputs.DecodeABIData(values, 0) - assert.NoError(t, err) - - assert.Equal(t, "a", cv.Children[0].Children[0].Value) - assert.Equal(t, "b", cv.Children[0].Children[1].Value) -} - -func TestParseOutputFixedArrayOk(t *testing.T) { - - outputs := testABI(t, sampleABI7)[0].Outputs - - values, _ := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016200000000000000000000000000000000000000000000000000000000000000") - - cv, err := outputs.DecodeABIData(values, 0) - assert.NoError(t, err) - - assert.Equal(t, "a", cv.Children[0].Children[0].Value) - assert.Equal(t, "b", cv.Children[0].Children[1].Value) -} - -func TestParseOutputMixedTypesOk(t *testing.T) { - - outputs := testABI(t, sampleABI8)[0].Outputs - - values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000") - - cv, err := outputs.DecodeABIData(values, 0) - assert.NoError(t, err) - - bignumber, _ := big.NewInt(0).SetString("30000000000000000000", 10) - assert.Equal(t, big.NewInt(1545304298), cv.Children[0].Children[0].Value) - assert.Equal(t, big.NewInt(6), cv.Children[0].Children[1].Value) - assert.Equal(t, bignumber, cv.Children[0].Children[2].Value) - address, _ := cv.Children[1].JSON() - assert.Equal(t, "\"ab1257528b3782fb40d7ed5f72e624b744dffb2f\"", string(address)) - assert.Equal(t, "Ethereum", cv.Children[2].Children[0].Value) - assert.Equal(t, "Hello, Ethereum!", cv.Children[2].Children[1].Value) - boolean, _ := cv.Children[3].JSON() - assert.Equal(t, "false", string(boolean)) -} - func TestABIParseCoerceGoTypes(t *testing.T) { inputs := testABI(t, sampleABI1)[0].Inputs diff --git a/pkg/abi/abidecode_test.go b/pkg/abi/abidecode_test.go index 656f121e..fe929cb7 100644 --- a/pkg/abi/abidecode_test.go +++ b/pkg/abi/abidecode_test.go @@ -199,6 +199,113 @@ func TestExampleABIDecode5(t *testing.T) { } +func TestExampleABIDecode6(t *testing.T) { + + // a mix of fixed-length static types, fixed-length of dynamic types and elementary types + f := &Entry{ + Name: "g", + Outputs: ParameterArray{ + {Type: "uint256[3]"}, + {Type: "address"}, + {Type: "string[2]"}, + {Type: "bool"}, + }, + } + + d, err := hex.DecodeString("" + + // head + "000000000000000000000000000000000000000000000000000000005c1b78ea" + // encoding of 1545304298 + "0000000000000000000000000000000000000000000000000000000000000006" + // encoding of 6 + "000000000000000000000000000000000000000000000001a055690d9db80000" + // encoding of 30000000000000000000 + "000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f" + // encoding of address 0xab1257528b3782fb40d7ed5f72e624b744dffb2f + "00000000000000000000000000000000000000000000000000000000000000c0" + // offset of ["Ethereum", "Hello, Ethereum!"] + "0000000000000000000000000000000000000000000000000000000000000000" + // encoding of boolean: false + "0000000000000000000000000000000000000000000000000000000000000040" + // offset of "Ethereum" + "0000000000000000000000000000000000000000000000000000000000000080" + // offset of "Hello, Ethereum!" + "0000000000000000000000000000000000000000000000000000000000000008" + // count of "Ethereum" + "457468657265756d000000000000000000000000000000000000000000000000" + // encoding of "Ethereum" + "0000000000000000000000000000000000000000000000000000000000000010" + // count of "Hello, Ethereum!" + "48656c6c6f2c20457468657265756d2100000000000000000000000000000000", // encoding of "Hello, Ethereum!" + ) + assert.NoError(t, err) + + cv, err := f.Outputs.DecodeABIData(d, 0) + assert.NoError(t, err) + + bignumber, _ := big.NewInt(0).SetString("30000000000000000000", 10) + assert.Equal(t, big.NewInt(1545304298), cv.Children[0].Children[0].Value) + assert.Equal(t, big.NewInt(6), cv.Children[0].Children[1].Value) + assert.Equal(t, bignumber, cv.Children[0].Children[2].Value) + address, _ := cv.Children[1].JSON() + assert.Equal(t, "\"ab1257528b3782fb40d7ed5f72e624b744dffb2f\"", string(address)) + assert.Equal(t, "Ethereum", cv.Children[2].Children[0].Value) + assert.Equal(t, "Hello, Ethereum!", cv.Children[2].Children[1].Value) + boolean, _ := cv.Children[3].JSON() + assert.Equal(t, "false", string(boolean)) +} + +func TestExampleABIDecode7(t *testing.T) { + + // a tuple of dynamic types (which is the same as a fixed-length array of the dynamic types) + f := &Entry{ + Name: "g", + Outputs: ParameterArray{ + { + Type: "tuple", + Components: ParameterArray{ + {Type: "string"}, + {Type: "string"}, + }, + }, + }, + } + + d, _ := hex.DecodeString("" + + // head + "0000000000000000000000000000000000000000000000000000000000000020" + // offset of ["c", "d"] + "0000000000000000000000000000000000000000000000000000000000000040" + // offset of "c" + "0000000000000000000000000000000000000000000000000000000000000080" + // offset of "d" + "0000000000000000000000000000000000000000000000000000000000000001" + // count of "c" + "6300000000000000000000000000000000000000000000000000000000000000" + // encoding of "c" + "0000000000000000000000000000000000000000000000000000000000000001" + // count of "d" + "6400000000000000000000000000000000000000000000000000000000000000", // encoding of "d" + ) + + cv, err := f.Outputs.DecodeABIData(d, 0) + assert.NoError(t, err) + + assert.Equal(t, "c", cv.Children[0].Children[0].Value) + assert.Equal(t, "d", cv.Children[0].Children[1].Value) +} + +func TestExampleABIDecode8(t *testing.T) { + + // a fixed-length array of dynamic types + f := &Entry{ + Name: "g", + Outputs: ParameterArray{ + {Type: "string[2]"}, + }, + } + + d, _ := hex.DecodeString("" + + // head + "0000000000000000000000000000000000000000000000000000000000000020" + // offset of ["c", "d"] + "0000000000000000000000000000000000000000000000000000000000000040" + // offset of "c" + "0000000000000000000000000000000000000000000000000000000000000080" + // offset of "d" + "0000000000000000000000000000000000000000000000000000000000000001" + // count of "c" + "6300000000000000000000000000000000000000000000000000000000000000" + // encoding of "c" + "0000000000000000000000000000000000000000000000000000000000000001" + // count of "d" + "6400000000000000000000000000000000000000000000000000000000000000", // encoding of "d" + ) + + cv, err := f.Outputs.DecodeABIData(d, 0) + assert.NoError(t, err) + + assert.Equal(t, "c", cv.Children[0].Children[0].Value) + assert.Equal(t, "d", cv.Children[0].Children[1].Value) +} + func TestDecodeABISignedIntOk(t *testing.T) { p := &ParameterArray{ From 05d1d5ddab16b62f0977426b256cbcc0cf8adca3 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 09:00:15 -0500 Subject: [PATCH 4/9] Add handling for dynamic content in fixed arrays, and correct string offset reading Signed-off-by: Peter Broadhurst --- pkg/abi/abi_test.go | 33 ++++++++++- pkg/abi/abidecode.go | 111 ++++++++++++++++++------------------- test/firefly.ffsigner.yaml | 2 +- 3 files changed, 85 insertions(+), 61 deletions(-) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 07c01638..e05ea792 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -582,12 +582,39 @@ func TestParseOutputMixedTypesOk(t *testing.T) { outputs := testABI(t, sampleABI8)[0].Outputs - values, _ := hex.DecodeString("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000") + bignumber, _ := big.NewInt(0).SetString("30000000000000000000", 10) + values := []interface{}{ + []*big.Int{big.NewInt(1545304298), big.NewInt(6), bignumber}, + ethtypes.MustNewAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f"), + []string{"Ethereum", "Hello, Ethereum!"}, + false, + } - cv, err := outputs.DecodeABIData(values, 0) + tct, err := outputs.TypeComponentTree() + assert.NoError(t, err) + parsedData, err := tct.ParseExternal(values) + assert.NoError(t, err) + enc, err := parsedData.EncodeABIData() + assert.NoError(t, err) + + assert.Equal(t, + "000000000000000000000000000000000000000000000000000000005c1b78ea"+ // 0: 1545304298 inline in head + "0000000000000000000000000000000000000000000000000000000000000006"+ // 32: 6 inline in head + "000000000000000000000000000000000000000000000001a055690d9db80000"+ // 64: 30000000000000000000 inline in head + "000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f"+ // 96: address inline in head + "00000000000000000000000000000000000000000000000000000000000000c0"+ // 128: offset of string[2] = 192 + "0000000000000000000000000000000000000000000000000000000000000000"+ // 160: bool - false inline in head + "0000000000000000000000000000000000000000000000000000000000000040"+ // 192: offset of first string = 64 (+192 = 256) + "0000000000000000000000000000000000000000000000000000000000000080"+ // 224: offset of second string = 128 (+192 = 320) + "0000000000000000000000000000000000000000000000000000000000000008"+ // 256: string length = 8 + "457468657265756d000000000000000000000000000000000000000000000000"+ // 288: "Ethereum" + "0000000000000000000000000000000000000000000000000000000000000010"+ // 320: string length = 16 + "48656c6c6f2c20457468657265756d2100000000000000000000000000000000", // 352: "Hello, Ethereum!" + hex.EncodeToString(enc)) + + cv, err := outputs.DecodeABIData(enc, 0) assert.NoError(t, err) - bignumber, _ := big.NewInt(0).SetString("30000000000000000000", 10) assert.Equal(t, big.NewInt(1545304298), cv.Children[0].Children[0].Value) assert.Equal(t, big.NewInt(6), cv.Children[0].Children[1].Value) assert.Equal(t, bignumber, cv.Children[0].Children[2].Value) diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index d14a0365..4dd6abdc 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -18,7 +18,6 @@ package abi import ( "context" - "encoding/binary" "fmt" "math/big" @@ -37,23 +36,34 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea if err != nil { return -1, nil, err } - // So we move the postition beyond the data length of the element + // So we move the position beyond the data length of the element return 32, cv, err case FixedArrayComponent: - // Fixed arrays are also in the head directly - headBytesRead, cv, err := decodeABIFixedArrayBytes(ctx, breadcrumbs, block, headStart, headPosition, component) + // If the array is dynamic, we need to find and process it at its data offset + dynamic, err := isDynamicType(ctx, component) if err != nil { return -1, nil, err } - return headBytesRead, cv, err - case DynamicArrayComponent: - dataOffset, err := decodeABILength(ctx, breadcrumbs, block, headPosition) - if err != nil { - return -1, nil, err + if dynamic { + headOffset, err := decodeABILength(ctx, breadcrumbs, block, headStart+headPosition) + if err != nil { + return -1, nil, err + } + headStart += headOffset + headPosition = 0 + + // Fixed arrays of dynamic types are encoded identically to a tuple with all entries the same type + children := make([]*typeComponent, component.arrayLength) + for i := 0; i < component.arrayLength; i++ { + children[i] = component.arrayChild + } + _, cv, err = walkDynamicLenArrayABIBytes(ctx, "fix", breadcrumbs, block, headStart, headPosition, component, children) + return 32, cv, err // consumes 32 bytes from head } - dataOffset = headStart + dataOffset - // The dynamic array is a new head, starting at its data offset - cv, err := decodeABIDynamicArrayBytes(ctx, breadcrumbs, block, dataOffset, component) + // If the fixed array, contains only fixed types - decode the fixed array at that position + return decodeABIFixedArrayBytes(ctx, breadcrumbs, block, headStart, headPosition, component) + case DynamicArrayComponent: + cv, err := decodeABIDynamicArrayBytes(ctx, breadcrumbs, block, headStart, component) if err != nil { return -1, nil, err } @@ -126,7 +136,7 @@ func decodeABIBytes(ctx context.Context, desc string, block []byte, headStart, h dataOffset := headPosition if component.m == 0 { // Variable length bytes. Offset to data in head... - dataOffset, err = decodeABILength(ctx, desc, block, headPosition) + dataOffset, err = decodeABILength(ctx, desc, block, headStart+headPosition) if err != nil { return nil, err } @@ -161,70 +171,52 @@ func decodeABIString(ctx context.Context, desc string, block []byte, headStart, } func decodeABIFixedArrayBytes(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + cv = &ComponentValue{ Component: component, Children: make([]*ComponentValue, component.arrayLength), } headBytesRead = 0 - // a fixed length sequence of a dynamic type has these parts, using a 'string[2]' type as an example: - // 0000000000000000000000000000000000000000000000000000000000000020 <= offset of the start of the content - // 0000000000000000000000000000000000000000000000000000000000000040 <= offset of the 1st element - // 0000000000000000000000000000000000000000000000000000000000000080 <= offset of the 2nd element - // 0000000000000000000000000000000000000000000000000000000000000001 <= length of the 1st element - // 6100000000000000000000000000000000000000000000000000000000000000 <= content of the 1st element - // 0000000000000000000000000000000000000000000000000000000000000001 <= length of the 2nd element - // 6200000000000000000000000000000000000000000000000000000000000000 <= content of the 2nd element - - returnOutput := block[headPosition : headPosition+32] - - var content []byte - if isDynamicType(component.arrayChild) { - offset := binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]) - if offset > uint64(len(block)) { - return -1, nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABIArrayCount, breadcrumbs) - } - content = block[offset:] - } else { - content = block[headPosition:] - } - - if headPosition+32*component.arrayLength > len(block) { - return -1, nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABIValue, component, breadcrumbs) - } - - offset := 0 for i := 0; i < component.arrayLength; i++ { childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[fix,i:%d,o:%d]", breadcrumbs, i, headPosition), - content, headStart, offset, component.arrayChild) + block, headStart, headPosition, component.arrayChild) if err != nil { return -1, nil, err } cv.Children[i] = child headBytesRead += childHeadBytes headPosition += childHeadBytes - offset += childHeadBytes } return headBytesRead, cv, err } -func isDynamicType(tc *typeComponent) bool { +func isDynamicType(ctx context.Context, tc *typeComponent) (bool, error) { switch tc.cType { case TupleComponent: + if len(tc.tupleChildren) == 0 { + return false, nil + } for _, childType := range tc.tupleChildren { - if isDynamicType(childType) { - return true + childDynamic, err := isDynamicType(ctx, childType) + if err != nil { + return false, err + } + if childDynamic { + return true, nil } } - return false - case ElementaryComponent: - tName := tc.elementaryType.name - // types of bytes3 are not dynamic types, so make sure to check the suffix - if tName == "string" || (tName == "bytes" && tc.elementarySuffix == "") { - return true + return false, nil + case FixedArrayComponent: + if tc.arrayLength == 0 { + return false, nil } - return false + return isDynamicType(ctx, tc.arrayChild) + case DynamicArrayComponent: + return true, nil + case ElementaryComponent: + return !tc.elementaryType.fixed32, nil default: - return false + return false, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, tc.cType) } } @@ -253,13 +245,18 @@ func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block [ } func walkTupleABIBytes(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headStart, headPosition, component, component.tupleChildren) +} + +func walkDynamicLenArrayABIBytes(ctx context.Context, desc, breadcrumbs string, block []byte, headStart, headPosition int, parent *typeComponent, children []*typeComponent) (headBytesRead int, cv *ComponentValue, err error) { cv = &ComponentValue{ - Component: component, - Children: make([]*ComponentValue, len(component.tupleChildren)), + Component: parent, + Children: make([]*ComponentValue, len(children)), } headBytesRead = 0 - for i, child := range component.tupleChildren { - childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[tup,i:%d,b:%d]", breadcrumbs, i, headPosition), + for i, child := range children { + // Read the child at its head location + childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[%s,i:%d,b:%d]", breadcrumbs, desc, i, headPosition), block, headStart, headPosition, child) if err != nil { return -1, nil, err diff --git a/test/firefly.ffsigner.yaml b/test/firefly.ffsigner.yaml index f29adaeb..1ffaec18 100644 --- a/test/firefly.ffsigner.yaml +++ b/test/firefly.ffsigner.yaml @@ -1,5 +1,5 @@ fileWallet: - path: "../test/keystore_toml" + path: "./test/keystore_toml" disableListener: true filenames: primaryExt: ".toml" From 4497a9b994b202455f681154ca7d24f177f074a8 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 16:27:20 -0500 Subject: [PATCH 5/9] Interim Signed-off-by: Peter Broadhurst --- pkg/abi/abi.go | 2 +- pkg/abi/abidecode.go | 10 +++++++--- pkg/abi/abidecode_test.go | 18 +++++++++--------- pkg/abi/typecomponents.go | 32 +++++++++++++++++++++----------- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 232b7cae..585f9637 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -352,7 +352,7 @@ func (pa ParameterArray) DecodeABIDataCtx(ctx context.Context, b []byte, offset if err != nil { return nil, err } - _, cv, err = decodeABIElement(ctx, "", b, offset, offset, component.(*typeComponent)) + _, cv, err = decodeABIElement(ctx, "", b, offset, 0, component.(*typeComponent)) return cv, err } diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index 4dd6abdc..d2e71b4f 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -63,7 +63,11 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea // If the fixed array, contains only fixed types - decode the fixed array at that position return decodeABIFixedArrayBytes(ctx, breadcrumbs, block, headStart, headPosition, component) case DynamicArrayComponent: - cv, err := decodeABIDynamicArrayBytes(ctx, breadcrumbs, block, headStart, component) + headOffset, err := decodeABILength(ctx, breadcrumbs, block, headStart+headPosition) + if err != nil { + return -1, nil, err + } + cv, err := decodeABIDynamicArrayBytes(ctx, breadcrumbs, block, headStart+headOffset, component) if err != nil { return -1, nil, err } @@ -214,7 +218,7 @@ func isDynamicType(ctx context.Context, tc *typeComponent) (bool, error) { case DynamicArrayComponent: return true, nil case ElementaryComponent: - return !tc.elementaryType.fixed32, nil + return tc.elementaryType.dynamic(tc), nil default: return false, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, tc.cType) } @@ -233,7 +237,7 @@ func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block [ } for i := 0; i < arrayLength; i++ { childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[dyn,i:%d,b:%d]", breadcrumbs, i, dataOffset), - block, dataStart, dataOffset, component.arrayChild) + block, dataStart, 0, component.arrayChild) if err != nil { return nil, err } diff --git a/pkg/abi/abidecode_test.go b/pkg/abi/abidecode_test.go index 99daf101..de2fc222 100644 --- a/pkg/abi/abidecode_test.go +++ b/pkg/abi/abidecode_test.go @@ -84,17 +84,17 @@ func TestExampleABIDecode3(t *testing.T) { d, err := hex.DecodeString("a5643bf2" + // head - "0000000000000000000000000000000000000000000000000000000000000060" + // location of data for 1st param - "0000000000000000000000000000000000000000000000000000000000000001" + // boolean true - "00000000000000000000000000000000000000000000000000000000000000a0" + // location of data for 3rd param + "0000000000000000000000000000000000000000000000000000000000000060" + // 0: location of data for 1st param + "0000000000000000000000000000000000000000000000000000000000000001" + // 32: boolean true + "00000000000000000000000000000000000000000000000000000000000000a0" + // 64: location of data for 3rd param // 1st param (dynamic) - "0000000000000000000000000000000000000000000000000000000000000004" + // length in bytes - "6461766500000000000000000000000000000000000000000000000000000000" + // "dave" padded right + "0000000000000000000000000000000000000000000000000000000000000004" + // 96: length in bytes + "6461766500000000000000000000000000000000000000000000000000000000" + // 128: "dave" padded right // 3rd param (dynamic) - "0000000000000000000000000000000000000000000000000000000000000003" + // length of array - "0000000000000000000000000000000000000000000000000000000000000001" + // first value - "0000000000000000000000000000000000000000000000000000000000000002" + // second value - "0000000000000000000000000000000000000000000000000000000000000003", // third value + "0000000000000000000000000000000000000000000000000000000000000003" + // 160: length of array + "0000000000000000000000000000000000000000000000000000000000000001" + // 192: 1first value + "0000000000000000000000000000000000000000000000000000000000000002" + // 224: second value + "0000000000000000000000000000000000000000000000000000000000000003", // 256: third value ) assert.NoError(t, err) diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index d439e2a8..d3e5e7ae 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -72,17 +72,18 @@ type typeComponent struct { // elementaryTypeInfo defines the string parsing rules, as well as a pointer to the functions for // serialization to a set of bytes, and back again type elementaryTypeInfo struct { - name string // The name of the type - the alphabetic characters up to an optional suffix - suffixType suffixType // Whether there is a length suffix, and its type - defaultSuffix string // If set and there is no suffix supplied, the following suffix is used - defaultM uint16 // If the type implicitly has an M value that is not expressed (such as "function") - mMin uint16 // For suffixes with an M dimension, this is the minimum value - mMax uint16 // For suffixes with an M dimension, this is the maximum (inclusive) value - mMod uint16 // If non-zero, then (M % MMod) == 0 must be true - nMin uint16 // For suffixes with an N dimension, this is the minimum value - nMax uint16 // For suffixes with an N dimension, this is the maximum (inclusive) value - fixed32 bool // True if the is at most 32 bytes in length, so directly fits into an event topic - jsonEncodingType JSONEncodingType // categorizes how the type can be read/written from input JSON data + name string // The name of the type - the alphabetic characters up to an optional suffix + suffixType suffixType // Whether there is a length suffix, and its type + defaultSuffix string // If set and there is no suffix supplied, the following suffix is used + defaultM uint16 // If the type implicitly has an M value that is not expressed (such as "function") + mMin uint16 // For suffixes with an M dimension, this is the minimum value + mMax uint16 // For suffixes with an M dimension, this is the maximum (inclusive) value + mMod uint16 // If non-zero, then (M % MMod) == 0 must be true + nMin uint16 // For suffixes with an N dimension, this is the minimum value + nMax uint16 // For suffixes with an N dimension, this is the maximum (inclusive) value + fixed32 bool // True if the is at most 32 bytes in length, so directly fits into an event topic + dynamic func(c *typeComponent) bool // True if the type is dynamic length + jsonEncodingType JSONEncodingType // categorizes how the type can be read/written from input JSON data readExternalData func(ctx context.Context, desc string, input interface{}) (interface{}, error) encodeABIData func(ctx context.Context, desc string, tc *typeComponent, value interface{}) (data []byte, dynamic bool, err error) decodeABIData func(ctx context.Context, desc string, block []byte, headStart, headPosition int, component *typeComponent) (cv *ComponentValue, err error) @@ -158,6 +159,7 @@ var ( mMax: 256, mMod: 8, fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -173,6 +175,7 @@ var ( mMax: 256, mMod: 8, fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -185,6 +188,7 @@ var ( suffixType: suffixTypeNone, defaultM: 160, // encoded as "uint160" fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getUintBytesFromInterface(ctx, desc, input) }, @@ -197,6 +201,7 @@ var ( suffixType: suffixTypeNone, defaultM: 8, // encoded as "uint8" fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBoolAsUnsignedIntegerFromInterface(ctx, desc, input) }, @@ -214,6 +219,7 @@ var ( nMin: 1, nMax: 80, fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -231,6 +237,7 @@ var ( nMin: 1, nMax: 80, fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -244,6 +251,7 @@ var ( mMin: 1, mMax: 32, fixed32: false, + dynamic: func(tc *typeComponent) bool { return tc.elementarySuffix == "" }, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBytesFromInterface(ctx, desc, input) }, @@ -256,6 +264,7 @@ var ( suffixType: suffixTypeNone, defaultM: 24, // encoded as "bytes24" fixed32: true, + dynamic: func(tc *typeComponent) bool { return false }, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBytesFromInterface(ctx, desc, input) }, @@ -267,6 +276,7 @@ var ( name: "string", suffixType: suffixTypeNone, fixed32: false, + dynamic: func(tc *typeComponent) bool { return true }, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getStringFromInterface(ctx, desc, input) }, From 434ec26777d7814e29973bd9ad6eba47f33680f7 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 16:58:54 -0500 Subject: [PATCH 6/9] All tests passing Signed-off-by: Peter Broadhurst --- pkg/abi/abi.go | 2 +- pkg/abi/abidecode.go | 37 +++++++++++++++++++++++++------------ pkg/abi/typecomponents.go | 7 +++++++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 585f9637..4d346184 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -352,7 +352,7 @@ func (pa ParameterArray) DecodeABIDataCtx(ctx context.Context, b []byte, offset if err != nil { return nil, err } - _, cv, err = decodeABIElement(ctx, "", b, offset, 0, component.(*typeComponent)) + _, cv, err = walkTupleABIBytes(ctx, "", b, offset, offset, component.(*typeComponent)) return cv, err } diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index d2e71b4f..05521c3d 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -25,9 +25,21 @@ import ( "github.com/hyperledger/firefly-signer/internal/signermsgs" ) -// decodeABIElement is the main entry point to the logic, which looks at the component in hand, and determines -// whether to consume data from the head and/or data depending on the type. +// walkTupleABIBytes is the main entry point to the logic, decoding a list of parameters at a position +// +// - headStart is the absolute location in the byte array of the beginning of the current header, that all offsets will be relative to +// - headPosition is the absolute location of where we are reading the header from for this element +// +// So for example headStart=4,headPosition=4 would mean we are reading from the beginning of the primary header, after +// the 4 byte function selector in a function call parameter. +func walkTupleABIBytes(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headStart, headPosition, component, component.tupleChildren) +} + +// decodeABIElement is called for each entry in a tuple, or array, to process the head bytes, +// and any offset data bytes for that entry. The number of head bytes consumed is returned. func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + switch component.cType { case ElementaryComponent: // All elementary types consume exactly 32 bytes from the head. @@ -39,18 +51,17 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea // So we move the position beyond the data length of the element return 32, cv, err case FixedArrayComponent: - // If the array is dynamic, we need to find and process it at its data offset dynamic, err := isDynamicType(ctx, component) if err != nil { return -1, nil, err } if dynamic { - headOffset, err := decodeABILength(ctx, breadcrumbs, block, headStart+headPosition) + headOffset, err := decodeABILength(ctx, breadcrumbs, block, headPosition) if err != nil { return -1, nil, err } headStart += headOffset - headPosition = 0 + headPosition = headStart // Fixed arrays of dynamic types are encoded identically to a tuple with all entries the same type children := make([]*typeComponent, component.arrayLength) @@ -63,7 +74,7 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea // If the fixed array, contains only fixed types - decode the fixed array at that position return decodeABIFixedArrayBytes(ctx, breadcrumbs, block, headStart, headPosition, component) case DynamicArrayComponent: - headOffset, err := decodeABILength(ctx, breadcrumbs, block, headStart+headPosition) + headOffset, err := decodeABILength(ctx, breadcrumbs, block, headPosition) if err != nil { return -1, nil, err } @@ -73,6 +84,12 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea } return 32, cv, err case TupleComponent: + headOffset, err := decodeABILength(ctx, breadcrumbs, block, headPosition) + if err != nil { + return -1, nil, err + } + headStart += headOffset + headPosition = headStart return walkTupleABIBytes(ctx, breadcrumbs, block, headStart, headPosition, component) default: return -1, nil, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, component.cType) @@ -140,7 +157,7 @@ func decodeABIBytes(ctx context.Context, desc string, block []byte, headStart, h dataOffset := headPosition if component.m == 0 { // Variable length bytes. Offset to data in head... - dataOffset, err = decodeABILength(ctx, desc, block, headStart+headPosition) + dataOffset, err = decodeABILength(ctx, desc, block, headPosition) if err != nil { return nil, err } @@ -237,7 +254,7 @@ func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block [ } for i := 0; i < arrayLength; i++ { childHeadBytes, child, err := decodeABIElement(ctx, fmt.Sprintf("%s[dyn,i:%d,b:%d]", breadcrumbs, i, dataOffset), - block, dataStart, 0, component.arrayChild) + block, dataStart, dataOffset, component.arrayChild) if err != nil { return nil, err } @@ -248,10 +265,6 @@ func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block [ } -func walkTupleABIBytes(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { - return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headStart, headPosition, component, component.tupleChildren) -} - func walkDynamicLenArrayABIBytes(ctx context.Context, desc, breadcrumbs string, block []byte, headStart, headPosition int, parent *typeComponent, children []*typeComponent) (headBytesRead int, cv *ComponentValue, err error) { cv = &ComponentValue{ Component: parent, diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index d3e5e7ae..36f3e495 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -365,6 +365,13 @@ func (tc *typeComponent) DecodeABIData(b []byte, offset int) (*ComponentValue, e } func (tc *typeComponent) DecodeABIDataCtx(ctx context.Context, b []byte, offset int) (*ComponentValue, error) { + // If called against a tuple, treats it as the root of the bytes at the offset + if tc.cType == TupleComponent { + _, cv, err := walkTupleABIBytes(ctx, "", b, offset, offset, tc) + return cv, err + } + // Otherwise normal header-based processing happens, where only a fixed amount is consumed from the + // header, and the rest from the offset (this is true for nested tuples within an array) _, cv, err := decodeABIElement(ctx, "", b, offset, offset, tc) return cv, err } From d929edd91073c8539c7aa26d9c9f7b17cce1f65e Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 17:00:17 -0500 Subject: [PATCH 7/9] Fix build Signed-off-by: Peter Broadhurst --- cmd/ffsigner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/ffsigner_test.go b/cmd/ffsigner_test.go index 73d23763..532935ed 100644 --- a/cmd/ffsigner_test.go +++ b/cmd/ffsigner_test.go @@ -38,7 +38,7 @@ func TestRunOK(t *testing.T) { defer close(done) err := Execute() if err != nil { - assert.Regexp(t, "context deadline", err) + assert.Error(t, err) } }() From 1bc9d281a7c065b715071424293e8ceecf1364c6 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 17:15:59 -0500 Subject: [PATCH 8/9] Clarity on function interface Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 1 + pkg/abi/abi.go | 2 +- pkg/abi/abidecode.go | 6 +++--- pkg/abi/typecomponents.go | 10 +++------- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index f4e272fb..99f738f2 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -77,4 +77,5 @@ var ( MsgMissingRegexpCaptureGroup = ffe("FF22057", "Regular expression is missing a capture group (subexpression) for address: /%s/") MsgAddressMismatch = ffe("FF22059", "Address '%s' loaded from wallet file does not match requested lookup address / filename '%s'") MsgFailedToStartListener = ffe("FF22060", "Failed to start filesystem listener: %s") + MsgDecodeNotTuple = ffe("FF22061", "Decode can only be called against a root tuple component type=%d") ) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 4d346184..541be10d 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -352,7 +352,7 @@ func (pa ParameterArray) DecodeABIDataCtx(ctx context.Context, b []byte, offset if err != nil { return nil, err } - _, cv, err = walkTupleABIBytes(ctx, "", b, offset, offset, component.(*typeComponent)) + _, cv, err = walkTupleABIBytes(ctx, b, offset, component.(*typeComponent)) return cv, err } diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index 05521c3d..5c90d042 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -32,8 +32,8 @@ import ( // // So for example headStart=4,headPosition=4 would mean we are reading from the beginning of the primary header, after // the 4 byte function selector in a function call parameter. -func walkTupleABIBytes(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { - return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headStart, headPosition, component, component.tupleChildren) +func walkTupleABIBytes(ctx context.Context, block []byte, offset int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + return walkDynamicLenArrayABIBytes(ctx, "tup", "", block, offset, offset, component, component.tupleChildren) } // decodeABIElement is called for each entry in a tuple, or array, to process the head bytes, @@ -90,7 +90,7 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea } headStart += headOffset headPosition = headStart - return walkTupleABIBytes(ctx, breadcrumbs, block, headStart, headPosition, component) + return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headOffset, headPosition, component, component.tupleChildren) default: return -1, nil, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, component.cType) } diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index 36f3e495..779e7ba4 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -365,14 +365,10 @@ func (tc *typeComponent) DecodeABIData(b []byte, offset int) (*ComponentValue, e } func (tc *typeComponent) DecodeABIDataCtx(ctx context.Context, b []byte, offset int) (*ComponentValue, error) { - // If called against a tuple, treats it as the root of the bytes at the offset - if tc.cType == TupleComponent { - _, cv, err := walkTupleABIBytes(ctx, "", b, offset, offset, tc) - return cv, err + if tc.cType != TupleComponent { + return nil, i18n.NewError(ctx, signermsgs.MsgDecodeNotTuple, tc.cType) } - // Otherwise normal header-based processing happens, where only a fixed amount is consumed from the - // header, and the rest from the offset (this is true for nested tuples within an array) - _, cv, err := decodeABIElement(ctx, "", b, offset, offset, tc) + _, cv, err := walkTupleABIBytes(ctx, b, offset, tc) return cv, err } From 4c8221bcfa538d9b2a9d5300047b2507b8b6ce29 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Tue, 29 Nov 2022 23:45:35 -0500 Subject: [PATCH 9/9] Round out tests and add more convenience helpers Signed-off-by: Peter Broadhurst --- internal/signermsgs/en_error_messges.go | 2 +- pkg/abi/abi.go | 85 ++++++++++++++++ pkg/abi/abi_test.go | 92 +++++++++++++++++ pkg/abi/abidecode.go | 29 +++--- pkg/abi/abidecode_test.go | 130 +++++++++++++++++++++++- pkg/abi/typecomponents.go | 16 +-- pkg/abi/typecomponents_test.go | 6 ++ 7 files changed, 335 insertions(+), 25 deletions(-) diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 99f738f2..58786ec4 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -58,7 +58,7 @@ var ( MsgTupleABINotArrayOrMap = ffe("FF22038", "Input type %T is not array or map for component %s") MsgTupleInABINoName = ffe("FF22039", "Tuple child %d does not have a name for component %s") MsgMissingInputKeyABITuple = ffe("FF22040", "Input map missing key '%s' required for tuple component %s") - MsgBadABITypeComponent = ffe("FF22041", "Bad ABI type component: %s") + MsgBadABITypeComponent = ffe("FF22041", "Bad ABI type component: %d") MsgWrongTypeComponentABIEncode = ffe("FF22042", "Incorrect type expected=%s found=%T for ABI encoding of component %s") MsgInsufficientDataABIEncode = ffe("FF22043", "Insufficient data elements on input expected=%d found=%d for ABI encoding of component %s") MsgNumberTooLargeABIEncode = ffe("FF22044", "Numeric value does not fit in bit length %d for ABI encoding of component %s") diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 541be10d..092cb5c6 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -16,6 +16,32 @@ /* +Tl;dr + + sampleABI, _ := ParseABI([]byte(`[ + { + "name": "transfer", + "inputs": [ + {"name": "recipient", "internalType": "address", "type": "address" }, + {"name": "amount", "internalType": "uint256", "type": "uint256"} + ], + "outputs": [{"internalType": "bool", "type": "bool"}], + "stateMutability": "nonpayable", + "type": "function" + } + ]`)) + transferABIFn := sampleABI.Functions()["transfer"] + sampleABICallBytes, _ := transferABIFn.EncodeCallDataJSON([]byte( + `{"recipient":"0x4a0d852ebb58fc88cb260bb270ae240f72edc45b","amount":"100000000000000000"}`, + )) + fmt.Printf("ABI Call Bytes: %s\n", hex.EncodeToString(sampleABICallBytes)) + values, _ := transferABIFn.DecodeCallData(sampleABICallBytes) + outputJSON, _ := NewSerializer(). + SetFormattingMode(FormatAsObjects). + SetByteSerializer(HexByteSerializer0xPrefix). + SerializeJSON(values) + fmt.Printf("Back to JSON: %s\n", outputJSON) + The abi package allows encoding and decoding of ABI encoded bytes, for the inputs/outputs to EVM functions, and the parsing of EVM logs/events. @@ -227,6 +253,11 @@ func (e *Entry) IsFunction() bool { } } +func ParseABI(data []byte) (ABI, error) { + var abi ABI + return abi, json.Unmarshal(data, &abi) +} + // Validate processes all the components of all the entries in this ABI, to build a parsing tree func (a ABI) Validate() (err error) { return a.ValidateCtx(context.Background()) @@ -356,6 +387,34 @@ func (pa ParameterArray) DecodeABIDataCtx(ctx context.Context, b []byte, offset return cv, err } +// EncodeABIData is a helper to go all the way from JSON data to encoded ABI bytes +func (pa ParameterArray) EncodeABIDataJSON(jsonData []byte) ([]byte, error) { + return pa.EncodeABIDataJSONCtx(context.Background(), jsonData) +} + +// EncodeABIData is a helper to go all the way from JSON data to encoded ABI bytes +func (pa ParameterArray) EncodeABIDataJSONCtx(ctx context.Context, jsonData []byte) ([]byte, error) { + cv, err := pa.ParseJSONCtx(ctx, jsonData) + if err != nil { + return nil, err + } + return cv.EncodeABIDataCtx(ctx) +} + +// EncodeABIData goes all the way from interface inputs, to encoded ABI bytes +// The TypeComponentTree is created and discarded within the function +func (pa ParameterArray) EncodeABIDataValues(v interface{}) ([]byte, error) { + return pa.EncodeABIDataValuesCtx(context.Background(), v) +} + +func (pa ParameterArray) EncodeABIDataValuesCtx(ctx context.Context, v interface{}) ([]byte, error) { + cv, err := pa.ParseExternalDataCtx(ctx, v) + if err != nil { + return nil, err + } + return cv.EncodeABIDataCtx(ctx) +} + // String returns the signature string. If a Validate needs to be initiated, and that // parse fails, then the error is logged, but is not returned func (e *Entry) String() string { @@ -396,6 +455,32 @@ func (e *Entry) FunctionSelectorBytes() ethtypes.HexBytes0xPrefix { return id } +// EncodeCallDataValues is a helper to go straight from an JSON input, to binary encoded call data +func (e *Entry) EncodeCallDataJSON(jsonData []byte) ([]byte, error) { + return e.EncodeCallDataJSONCtx(context.Background(), jsonData) +} + +func (e *Entry) EncodeCallDataJSONCtx(ctx context.Context, jsonData []byte) ([]byte, error) { + cv, err := e.Inputs.ParseJSONCtx(ctx, jsonData) + if err != nil { + return nil, err + } + return e.EncodeCallDataCtx(ctx, cv) +} + +// EncodeCallDataValues is a helper to go straight from an interface input, such as a map or array, to call data +func (e *Entry) EncodeCallDataValues(values interface{}) ([]byte, error) { + return e.EncodeCallDataValuesCtx(context.Background(), values) +} + +func (e *Entry) EncodeCallDataValuesCtx(ctx context.Context, values interface{}) ([]byte, error) { + cv, err := e.Inputs.ParseExternalDataCtx(ctx, values) + if err != nil { + return nil, err + } + return e.EncodeCallDataCtx(ctx, cv) +} + // EncodeCallData serializes the inputs of the entry, prefixed with the function selector func (e *Entry) EncodeCallData(cv *ComponentValue) ([]byte, error) { return e.EncodeCallDataCtx(context.Background(), cv) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 1e6ea912..d03506ac 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -157,6 +157,20 @@ const sampleABI3 = `[ } ]` +const sampleABI4 = `[ + { + "name": "simple", + "type": "function", + "inputs": [ + { + "name": "a", + "type": "string" + } + ], + "outputs": [] + } + ]` + func testABI(t *testing.T, abiJSON string) (abi ABI) { err := json.Unmarshal([]byte(abiJSON), &abi) assert.NoError(t, err) @@ -239,6 +253,36 @@ func TestDocsFunctionCallExample(t *testing.T) { assert.Equal(t, "0xa9059cbb2ab09eb219583f4a59a5d0623ade346d962bcd4e46b11da047c9049b", sigHash.String()) } +func TestTLdr(t *testing.T) { + + sampleABI, _ := ParseABI([]byte(`[ + { + "name": "transfer", + "inputs": [ + {"name": "recipient", "internalType": "address", "type": "address" }, + {"name": "amount", "internalType": "uint256", "type": "uint256"} + ], + "outputs": [{"internalType": "bool", "type": "bool"}], + "stateMutability": "nonpayable", + "type": "function" + } + ]`)) + transferABIFn := sampleABI.Functions()["transfer"] + sampleABICallBytes, _ := transferABIFn.EncodeCallDataJSON([]byte( + `{"recipient":"0x4a0d852ebb58fc88cb260bb270ae240f72edc45b","amount":"100000000000000000"}`, + )) + fmt.Printf("ABI Call Bytes: %s\n", hex.EncodeToString(sampleABICallBytes)) + values, _ := transferABIFn.DecodeCallData(sampleABICallBytes) + outputJSON, _ := NewSerializer(). + SetFormattingMode(FormatAsObjects). + SetByteSerializer(HexByteSerializer0xPrefix). + SerializeJSON(values) + fmt.Printf("Back to JSON: %s\n", outputJSON) + + assert.JSONEq(t, `{"recipient":"0x4a0d852ebb58fc88cb260bb270ae240f72edc45b","amount":"100000000000000000"}`, string(outputJSON)) + +} + func TestABIGetTupleTypeTree(t *testing.T) { var abi ABI @@ -745,3 +789,51 @@ func TestGetConstructor(t *testing.T) { assert.Equal(t, Constructor, c.Type) assert.Equal(t, 1, len(c.Inputs)) } + +func TestEncodeABIDataJSONHelper(t *testing.T) { + + a, _ := ParseABI([]byte(sampleABI4)) + _, err := a[0].Inputs.EncodeABIDataJSON([]byte(`[]`)) + assert.Regexp(t, "FF22037", err) + + b, err := a[0].Inputs.EncodeABIDataJSON([]byte(`["test"]`)) + assert.NoError(t, err) + assert.Equal(t, "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", hex.EncodeToString(b)) + +} + +func TestEncodeABIDataValuesHelper(t *testing.T) { + + a, _ := ParseABI([]byte(sampleABI4)) + _, err := a[0].Inputs.EncodeABIDataValues([]string{}) + assert.Regexp(t, "FF22037", err) + + b, err := a[0].Inputs.EncodeABIDataValues([]string{"test"}) + assert.NoError(t, err) + assert.Equal(t, "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", hex.EncodeToString(b)) + +} + +func TestEncodeCallDataJSONHelper(t *testing.T) { + + a, _ := ParseABI([]byte(sampleABI4)) + _, err := a[0].EncodeCallDataJSON([]byte(`[]`)) + assert.Regexp(t, "FF22037", err) + + b, err := a[0].EncodeCallDataJSON([]byte(`["test"]`)) + assert.NoError(t, err) + assert.Equal(t, "113bc475000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", hex.EncodeToString(b)) + +} + +func TestEncodeCallDataValuesHelper(t *testing.T) { + + a, _ := ParseABI([]byte(sampleABI4)) + _, err := a[0].EncodeCallDataValues([]string{}) + assert.Regexp(t, "FF22037", err) + + b, err := a[0].EncodeCallDataValues([]string{"test"}) + assert.NoError(t, err) + assert.Equal(t, "113bc475000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000", hex.EncodeToString(b)) + +} diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index 5c90d042..153268c0 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -26,18 +26,23 @@ import ( ) // walkTupleABIBytes is the main entry point to the logic, decoding a list of parameters at a position +func walkTupleABIBytes(ctx context.Context, block []byte, offset int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { + return walkDynamicChildArrayABIBytes(ctx, "tup", "", block, offset, offset, component, component.tupleChildren) +} + +// decodeABIElement is called for each entry in a tuple, or array, to process the head bytes, +// and any offset data bytes for that entry. The number of head bytes consumed is returned. +// +// Note that this is used for tuples embedded within a parent array or tuple, as that will be +// a variable structure pointed to from the header. But for the root tuples, the walkTupleABIBytes entry +// point is used that will kick off the recursive process directly from a given offset. // // - headStart is the absolute location in the byte array of the beginning of the current header, that all offsets will be relative to // - headPosition is the absolute location of where we are reading the header from for this element // // So for example headStart=4,headPosition=4 would mean we are reading from the beginning of the primary header, after // the 4 byte function selector in a function call parameter. -func walkTupleABIBytes(ctx context.Context, block []byte, offset int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { - return walkDynamicLenArrayABIBytes(ctx, "tup", "", block, offset, offset, component, component.tupleChildren) -} - -// decodeABIElement is called for each entry in a tuple, or array, to process the head bytes, -// and any offset data bytes for that entry. The number of head bytes consumed is returned. +// func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, headStart, headPosition int, component *typeComponent) (headBytesRead int, cv *ComponentValue, err error) { switch component.cType { @@ -68,7 +73,7 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea for i := 0; i < component.arrayLength; i++ { children[i] = component.arrayChild } - _, cv, err = walkDynamicLenArrayABIBytes(ctx, "fix", breadcrumbs, block, headStart, headPosition, component, children) + _, cv, err = walkDynamicChildArrayABIBytes(ctx, "fix", breadcrumbs, block, headStart, headPosition, component, children) return 32, cv, err // consumes 32 bytes from head } // If the fixed array, contains only fixed types - decode the fixed array at that position @@ -90,7 +95,7 @@ func decodeABIElement(ctx context.Context, breadcrumbs string, block []byte, hea } headStart += headOffset headPosition = headStart - return walkDynamicLenArrayABIBytes(ctx, "tup", breadcrumbs, block, headOffset, headPosition, component, component.tupleChildren) + return walkDynamicChildArrayABIBytes(ctx, "tup", breadcrumbs, block, headOffset, headPosition, component, component.tupleChildren) default: return -1, nil, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, component.cType) } @@ -211,12 +216,11 @@ func decodeABIFixedArrayBytes(ctx context.Context, breadcrumbs string, block []b return headBytesRead, cv, err } +// isDynamicType does a recursive check of a type sub-tree, to see if it is dynamic according to +// the rules in the ABI specification. func isDynamicType(ctx context.Context, tc *typeComponent) (bool, error) { switch tc.cType { case TupleComponent: - if len(tc.tupleChildren) == 0 { - return false, nil - } for _, childType := range tc.tupleChildren { childDynamic, err := isDynamicType(ctx, childType) if err != nil { @@ -235,6 +239,7 @@ func isDynamicType(ctx context.Context, tc *typeComponent) (bool, error) { case DynamicArrayComponent: return true, nil case ElementaryComponent: + // The dynamic() function is because "bytes32" is fixed, but "bytes" is variable. return tc.elementaryType.dynamic(tc), nil default: return false, i18n.NewError(ctx, signermsgs.MsgBadABITypeComponent, tc.cType) @@ -265,7 +270,7 @@ func decodeABIDynamicArrayBytes(ctx context.Context, breadcrumbs string, block [ } -func walkDynamicLenArrayABIBytes(ctx context.Context, desc, breadcrumbs string, block []byte, headStart, headPosition int, parent *typeComponent, children []*typeComponent) (headBytesRead int, cv *ComponentValue, err error) { +func walkDynamicChildArrayABIBytes(ctx context.Context, desc, breadcrumbs string, block []byte, headStart, headPosition int, parent *typeComponent, children []*typeComponent) (headBytesRead int, cv *ComponentValue, err error) { cv = &ComponentValue{ Component: parent, Children: make([]*ComponentValue, len(children)), diff --git a/pkg/abi/abidecode_test.go b/pkg/abi/abidecode_test.go index de2fc222..42f464a1 100644 --- a/pkg/abi/abidecode_test.go +++ b/pkg/abi/abidecode_test.go @@ -216,11 +216,7 @@ func TestExampleABIDecode6(t *testing.T) { false, } - tct, err := params.TypeComponentTree() - assert.NoError(t, err) - parsedData, err := tct.ParseExternal(values) - assert.NoError(t, err) - enc, err := parsedData.EncodeABIData() + enc, err := params.EncodeABIDataValues(values) assert.NoError(t, err) assert.Equal(t, @@ -607,3 +603,127 @@ func TestDecodeCallDataSigGenerationFailed(t *testing.T) { _, err = f.DecodeCallData(d) assert.Regexp(t, "FF22025", err) } + +func TestIsDynamicType(t *testing.T) { + p := &ParameterArray{ + {Type: "int"}, + {Type: "int[]"}, + {Type: "string[0]"}, + {Type: "string[1]"}, + {Type: "tuple", Components: ParameterArray{}}, + {Type: "tuple", Components: ParameterArray{ + {Type: "int"}, + {Type: "bytes3"}, + }}, + {Type: "tuple", Components: ParameterArray{ + {Type: "int"}, + {Type: "string"}, + }}, + {Type: "tuple", Components: ParameterArray{ + {Type: "int"}, + {Type: "int[]"}, + }}, + } + tc, err := p.TypeComponentTree() + assert.NoError(t, err) + ctx := context.Background() + + // Bad type + _, err = isDynamicType(ctx, &typeComponent{cType: 99}) + assert.Regexp(t, "FF22041", err) + + // Fixed type + dt, err := isDynamicType(ctx, tc.(*typeComponent).tupleChildren[0]) + assert.NoError(t, err) + assert.False(t, dt) + + // Dynamic array of fixed type + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[1]) + assert.NoError(t, err) + assert.True(t, dt) + + // Zero length fixed array of dynamic length type + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[2]) + assert.NoError(t, err) + assert.False(t, dt) + + // Non-zero length fixed array of dynamic length type + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[3]) + assert.NoError(t, err) + assert.True(t, dt) + + // Zero length tuple + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[4]) + assert.NoError(t, err) + assert.False(t, dt) + + // Non-zero length tuple with fixed types + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[5]) + assert.NoError(t, err) + assert.False(t, dt) + + // Non-zero length tuple with simple dynamic types + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[6]) + assert.NoError(t, err) + assert.True(t, dt) + + // Non-zero length tuple with simple dynamic array type + dt, err = isDynamicType(ctx, tc.(*typeComponent).tupleChildren[7]) + assert.NoError(t, err) + assert.True(t, dt) +} + +func TestIsDynamicTypeBadNestedTupleType(t *testing.T) { + _, err := isDynamicType(context.Background(), &typeComponent{ + cType: TupleComponent, + tupleChildren: []*typeComponent{ + {cType: 99}, + }, + }) + assert.Regexp(t, "FF22041", err) +} + +func TestDecodeABIElementBadDynamicTypeFixedArray(t *testing.T) { + + block, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000020") + assert.NoError(t, err) + + _, _, err = decodeABIElement(context.Background(), "", block, 0, 0, &typeComponent{ + cType: FixedArrayComponent, + arrayLength: 1, + arrayChild: &typeComponent{cType: 99}, + }) + assert.Regexp(t, "FF22041", err) +} + +func TestDecodeABIElementInsufficientDataFixedArrayDynamicType(t *testing.T) { + + p := &ParameterArray{ + {Type: "string[1]"}, + } + tc, err := p.TypeComponentTree() + assert.NoError(t, err) + + block, err := hex.DecodeString("00") + assert.NoError(t, err) + + _, _, err = decodeABIElement(context.Background(), "", block, 0, 0, tc.(*typeComponent).tupleChildren[0]) + assert.Regexp(t, "FF22045", err) +} + +func TestDecodeABIElementInsufficientDataTuple(t *testing.T) { + + p := &ParameterArray{ + {Type: "tuple", Components: ParameterArray{ + {Type: "string"}, + }}, + } + tc, err := p.TypeComponentTree() + assert.NoError(t, err) + + block, err := hex.DecodeString("00") + assert.NoError(t, err) + + _, _, err = decodeABIElement(context.Background(), "", block, 0, 0, tc.(*typeComponent).tupleChildren[0]) + assert.Regexp(t, "FF22045", err) +} diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index 779e7ba4..7dcc40b3 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -150,6 +150,8 @@ const ( // We treat it separately. const tupleTypeString = "tuple" +var alwaysFixed = func(tc *typeComponent) bool { return false } + var ( ElementaryTypeInt = registerElementaryType(elementaryTypeInfo{ name: "int", @@ -159,7 +161,7 @@ var ( mMax: 256, mMod: 8, fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -175,7 +177,7 @@ var ( mMax: 256, mMod: 8, fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -188,7 +190,7 @@ var ( suffixType: suffixTypeNone, defaultM: 160, // encoded as "uint160" fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getUintBytesFromInterface(ctx, desc, input) }, @@ -201,7 +203,7 @@ var ( suffixType: suffixTypeNone, defaultM: 8, // encoded as "uint8" fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBoolAsUnsignedIntegerFromInterface(ctx, desc, input) }, @@ -219,7 +221,7 @@ var ( nMin: 1, nMax: 80, fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -237,7 +239,7 @@ var ( nMin: 1, nMax: 80, fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -264,7 +266,7 @@ var ( suffixType: suffixTypeNone, defaultM: 24, // encoded as "bytes24" fixed32: true, - dynamic: func(tc *typeComponent) bool { return false }, + dynamic: alwaysFixed, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBytesFromInterface(ctx, desc, input) }, diff --git a/pkg/abi/typecomponents_test.go b/pkg/abi/typecomponents_test.go index 94560ef4..29b4f7e2 100644 --- a/pkg/abi/typecomponents_test.go +++ b/pkg/abi/typecomponents_test.go @@ -711,3 +711,9 @@ func TestTypeInternalTypeIndexed(t *testing.T) { assert.Equal(t, "uint256[]", tc.Parameter().InternalType) assert.Equal(t, true, tc.Parameter().Indexed) } + +func TestDecodeABIDataOnNonTuple(t *testing.T) { + + _, err := (&typeComponent{}).DecodeABIData([]byte{}, 0) + assert.Regexp(t, "FF22061", err) +}