From 7500b712ffff9e17ee62de9aa82ee4c9b7bfb3bb Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Fri, 24 Jun 2022 17:11:19 -0400 Subject: [PATCH 1/2] Add function for decoding event ABI data Signed-off-by: Peter Broadhurst --- .vscode/settings.json | 1 + internal/signermsgs/en_error_messges.go | 88 +++++------ pkg/abi/abi.go | 125 +++++++++++++--- pkg/abi/abi_test.go | 185 +++++++++++++++++++++++- pkg/abi/abidecode.go | 2 +- pkg/abi/typecomponents.go | 10 ++ 6 files changed, 345 insertions(+), 66 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b52bc454..b4e38eac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "Keccak", "keypair", "keystorev", + "pluggable", "resty", "rpcbackendmocks", "secp", diff --git a/internal/signermsgs/en_error_messges.go b/internal/signermsgs/en_error_messges.go index 5c0ff549..a8db59e9 100644 --- a/internal/signermsgs/en_error_messges.go +++ b/internal/signermsgs/en_error_messges.go @@ -27,47 +27,49 @@ var ffe = func(key, translation string, statusHint ...int) i18n.ErrorMessageKey //revive:disable var ( - MsgInvalidOutputType = ffe("FF22010", "Invalid output type: %s") - MsgInvalidParam = ffe("FF22011", "Invalid parameter at position %d for method %s: %s") - MsgRPCRequestFailed = ffe("FF22012", "Backend RPC request failed") - MsgReadDirFile = ffe("FF22013", "Directory listing failed") - MsgWalletNotAvailable = ffe("FF22014", "Wallet for address '%s' not available") - MsgWalletFailed = ffe("FF22015", "Wallet for address '%s' could not be initialized") - MsgBadGoTemplate = ffe("FF22016", "Bad go template for '%s' - try something like '{{ index .signing \"key-file\" }}' syntax") - MsgNoWalletEnabled = ffe("FF22017", "No wallets enabled in configuration") - MsgInvalidRequest = ffe("FF22018", "Invalid request data") - MsgInvalidParamCount = ffe("FF22019", "Invalid number of parameters: expected=%d received=%d") - MsgMissingFrom = ffe("FF22020", "Missing 'from' address") - MsgQueryChainID = ffe("FF22021", "Failed to query Chain ID") - MsgSigningFailed = ffe("FF22022", "Signing failed: %s") - MsgInvalidTransaction = ffe("FF22023", "Invalid eth_sendTransaction input") - MsgMissingRequestID = ffe("FF22024", "Invalid JSON/RPC request. Must set request ID") - MsgUnsupportedABIType = ffe("FF22025", "Unsupported elementary type '%s' in ABI type '%s'") - MsgUnsupportedABISuffix = ffe("FF22026", "Unsupported type suffix '%s' in ABI type '%s' - expected %s") - MsgMissingABISuffix = ffe("FF22027", "Missing type suffix in ABI type '%s' - expected %s") - MsgInvalidABISuffix = ffe("FF22028", "Invalid suffix in ABI type '%s' - expected %s") - MsgInvalidABIArraySpec = ffe("FF22029", "Invalid array suffix in ABI type '%s'") - MsgInvalidIntegerABIInput = ffe("FF22030", "Unable to parse '%v' of type %T as integer for component %s") - MsgInvalidFloatABIInput = ffe("FF22031", "Unable to parse '%v' of type %T as floating point number for component %s") - MsgInvalidStringABIInput = ffe("FF22032", "Unable to parse '%v' of type %T as string for component %s") - MsgInvalidBoolABIInput = ffe("FF22033", "Unable to parse '%v' of type %T as boolean for component %s") - MsgInvalidHexABIInput = ffe("FF22034", "Unable to parse input of type %T as hex for component %s") - MsgMustBeSliceABIInput = ffe("FF22035", "Unable to parse input of type %T for component %s - must be an array") - MsgFixedLengthABIArrayMismatch = ffe("FF22036", "Input array is length %d, and required fixed array length is %d for component %s") - MsgTupleABIArrayMismatch = ffe("FF22037", "Input array is length %d, and required tuple component count is %d for component %s") - 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") - 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") - MsgNotEnoughtBytesABIArrayCount = ffe("FF22045", "Insufficient bytes to read array index for component %s") - MsgABIArrayCountTooLarge = ffe("FF22046", "Array index %s too large for component %s") - MsgNotEnoughBytesABIValue = ffe("FF22047", "Insufficient bytes to read %s value %s") - MsgNotEnoughtBytesABISignature = ffe("FF22048", "Insufficient bytes to read signature") - MsgIncorrectABISignatureID = ffe("FF22049", "Incorrect ID for signature %s expected=%s found=%s") - MsgUnknownABIElementaryType = ffe("FF22050", "Unknown elementary type %s for component %s") - MsgUnknownTupleSerializer = ffe("FF22051", "Unknown tuple serialization option %d") - MsgInvalidFFIDetailsSchema = ffe("FF22052", "Invalid FFI details schema for '%s'") + MsgInvalidOutputType = ffe("FF22010", "Invalid output type: %s") + MsgInvalidParam = ffe("FF22011", "Invalid parameter at position %d for method %s: %s") + MsgRPCRequestFailed = ffe("FF22012", "Backend RPC request failed") + MsgReadDirFile = ffe("FF22013", "Directory listing failed") + MsgWalletNotAvailable = ffe("FF22014", "Wallet for address '%s' not available") + MsgWalletFailed = ffe("FF22015", "Wallet for address '%s' could not be initialized") + MsgBadGoTemplate = ffe("FF22016", "Bad go template for '%s' - try something like '{{ index .signing \"key-file\" }}' syntax") + MsgNoWalletEnabled = ffe("FF22017", "No wallets enabled in configuration") + MsgInvalidRequest = ffe("FF22018", "Invalid request data") + MsgInvalidParamCount = ffe("FF22019", "Invalid number of parameters: expected=%d received=%d") + MsgMissingFrom = ffe("FF22020", "Missing 'from' address") + MsgQueryChainID = ffe("FF22021", "Failed to query Chain ID") + MsgSigningFailed = ffe("FF22022", "Signing failed: %s") + MsgInvalidTransaction = ffe("FF22023", "Invalid eth_sendTransaction input") + MsgMissingRequestID = ffe("FF22024", "Invalid JSON/RPC request. Must set request ID") + MsgUnsupportedABIType = ffe("FF22025", "Unsupported elementary type '%s' in ABI type '%s'") + MsgUnsupportedABISuffix = ffe("FF22026", "Unsupported type suffix '%s' in ABI type '%s' - expected %s") + MsgMissingABISuffix = ffe("FF22027", "Missing type suffix in ABI type '%s' - expected %s") + MsgInvalidABISuffix = ffe("FF22028", "Invalid suffix in ABI type '%s' - expected %s") + MsgInvalidABIArraySpec = ffe("FF22029", "Invalid array suffix in ABI type '%s'") + MsgInvalidIntegerABIInput = ffe("FF22030", "Unable to parse '%v' of type %T as integer for component %s") + MsgInvalidFloatABIInput = ffe("FF22031", "Unable to parse '%v' of type %T as floating point number for component %s") + MsgInvalidStringABIInput = ffe("FF22032", "Unable to parse '%v' of type %T as string for component %s") + MsgInvalidBoolABIInput = ffe("FF22033", "Unable to parse '%v' of type %T as boolean for component %s") + MsgInvalidHexABIInput = ffe("FF22034", "Unable to parse input of type %T as hex for component %s") + MsgMustBeSliceABIInput = ffe("FF22035", "Unable to parse input of type %T for component %s - must be an array") + MsgFixedLengthABIArrayMismatch = ffe("FF22036", "Input array is length %d, and required fixed array length is %d for component %s") + MsgTupleABIArrayMismatch = ffe("FF22037", "Input array is length %d, and required tuple component count is %d for component %s") + 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") + 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") + MsgNotEnoughBytesABIArrayCount = ffe("FF22045", "Insufficient bytes to read array index for component %s") + MsgABIArrayCountTooLarge = ffe("FF22046", "Array index %s too large for component %s") + MsgNotEnoughBytesABIValue = ffe("FF22047", "Insufficient bytes to read %s value %s") + MsgNotEnoughBytesABISignature = ffe("FF22048", "Insufficient bytes to read signature") + MsgIncorrectABISignatureID = ffe("FF22049", "Incorrect ID for signature %s expected=%s found=%s") + MsgUnknownABIElementaryType = ffe("FF22050", "Unknown elementary type %s for component %s") + MsgUnknownTupleSerializer = ffe("FF22051", "Unknown tuple serialization option %d") + MsgInvalidFFIDetailsSchema = ffe("FF22052", "Invalid FFI details schema for '%s'") + MsgEventsInsufficientTopics = ffe("FF22053", "Ran out of topics for indexed fields at field %d of %s") + MsgEventSignatureMismatch = ffe("FF22054", "Event signature mismatch for '%s': expected='%s' found='%s'") ) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index c2a387af..840f3121 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -139,7 +139,7 @@ with a number of built-in options as follows: - Number serialization can be: - Base 10 formatted string - Hex with "0x" prefix - - Numeric up to the maximum safe Javscript values, then automatically switching to string + - Numeric up to the maximum safe Javascript values, then automatically switching to string - Byte serialization can be: - Hex with "0x" prefix - Hex without any prefix @@ -151,6 +151,7 @@ import ( "context" "encoding/hex" "encoding/json" + "fmt" "strings" "github.com/hyperledger/firefly-common/pkg/i18n" @@ -174,7 +175,7 @@ type EntryType string const ( Function EntryType = "function" // A function/method of the smart contract Constructor EntryType = "constructor" // The constructor - Receive EntryType = "receive" // The "receive Ethere" function + Receive EntryType = "receive" // The "receive ether" function Fallback EntryType = "fallback" // The default function to invoke Event EntryType = "event" // An event the smart contract can emit Error EntryType = "error" // An error definition @@ -279,7 +280,7 @@ func (e *Entry) ValidateCtx(ctx context.Context) (err error) { return nil } -// ParseJSON takes external JSON data, and parses againt the ABI to generate +// ParseJSON takes external JSON data, and parses against the ABI to generate // a component value tree. // // The component value tree can then be serialized to binary ABI data. @@ -375,9 +376,9 @@ func (e *Entry) GenerateFunctionSelectorCtx(ctx context.Context) ([]byte, error) return k[0:4], nil } -// IDBytes is a convenience function to get the ID as bytes. +// FunctionSelectorBytes is a convenience function to get the ID as bytes. // Will return a nil 4 bytes on error -func (e *Entry) IDBytes() []byte { +func (e *Entry) FunctionSelectorBytes() ethtypes.HexBytes0xPrefix { id, err := e.GenerateFunctionSelector() if err != nil { log.L(context.Background()).Warnf("ABI parsing failed: %s", err) @@ -386,17 +387,6 @@ func (e *Entry) IDBytes() []byte { return id } -// ID is a convenience function to get the ID as a hex string (no 0x prefix), which will -// return the empty string on failure -func (e *Entry) ID() string { - id, err := e.GenerateFunctionSelector() - if err != nil { - log.L(context.Background()).Warnf("ABI parsing failed: %s", err) - return "" - } - return hex.EncodeToString(id) -} - // 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) @@ -432,7 +422,7 @@ func (e *Entry) DecodeCallDataCtx(ctx context.Context, b []byte) (*ComponentValu return nil, err } if len(b) < 4 { - return nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughtBytesABISignature) + return nil, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABISignature) } if !bytes.Equal(id, b[0:4]) { return nil, i18n.NewError(ctx, signermsgs.MsgIncorrectABISignatureID, e.String(), hex.EncodeToString(id), hex.EncodeToString(b[0:4])) @@ -442,17 +432,116 @@ func (e *Entry) DecodeCallDataCtx(ctx context.Context, b []byte) (*ComponentValu } +// SignatureHash returns the keccak hash of the signature as bytes func (e *Entry) SignatureHash() (ethtypes.HexBytes0xPrefix, error) { + return e.SignatureHashCtx(context.Background()) +} + +func (e *Entry) SignatureHashCtx(context.Context) (ethtypes.HexBytes0xPrefix, error) { hash := sha3.NewLegacyKeccak256() sig, err := e.SignatureCtx(context.Background()) if err != nil { return nil, err } hash.Write([]byte(sig)) - return hash.Sum(nil), nil } +func (e *Entry) SignatureHashBytes() ethtypes.HexBytes0xPrefix { + sh, err := e.SignatureHashCtx(context.Background()) + if err != nil { + log.L(context.Background()).Errorf("Failed to generate signature: %s", err) + sh = make(ethtypes.HexBytes0xPrefix, 32) + } + return sh +} + +func (e *Entry) topicToValue(ctx context.Context, topicIdx int, topic ethtypes.HexBytes0xPrefix, input *typeComponent) (*ComponentValue, error) { + et := input.ElementaryType().(*elementaryTypeInfo) + if et != nil && et.fixed32 { + // Directly encoded into topic + return et.decodeABIData(ctx, fmt.Sprintf("topic[%d]", topicIdx), topic, 0, 0, input) + } + // For all other types it is just a hash of the output for indexing, so we can only + // logically return it as a hex string. The Solidity developer has to include + // the same data a second type non-indexed to get the real value. + return &ComponentValue{ + Component: &typeComponent{ + cType: ElementaryComponent, + elementaryType: (ElementaryTypeBytes).(*elementaryTypeInfo), + keyName: input.keyName, + parameter: input.parameter, // the original parameter will obviously have a different type to Bytes + }, + Value: []byte(topic), + }, nil +} + +// DecodeEventData takes the array of topics, and the event data, and builds a component value tree that parses +// the values against the ABI definition in the entry. Values are extracted from either the topic or data per +// the rules defined here: https://docs.soliditylang.org/en/v0.8.15/abi-spec.html +// +// If the event is non-anonymous, the signature hash of the event must match the first topic (or an error is thrown). +func (e *Entry) DecodeEventData(topics []ethtypes.HexBytes0xPrefix, data ethtypes.HexBytes0xPrefix) (*ComponentValue, error) { + return e.DecodeEventDataCtx(context.Background(), topics, data) +} + +func (e *Entry) DecodeEventDataCtx(ctx context.Context, topics []ethtypes.HexBytes0xPrefix, data ethtypes.HexBytes0xPrefix) (*ComponentValue, error) { + typeTree, err := e.Inputs.TypeComponentTree() + if err != nil { + return nil, err + } + inputTypes := typeTree.TupleChildren() + topicIdx := 0 + if !e.Anonymous && len(topics) >= 1 { + sigHashBytes := e.SignatureHashBytes() + if !bytes.Equal(topics[0], sigHashBytes) { + return nil, i18n.NewError(ctx, signermsgs.MsgEventSignatureMismatch, e, topics[0], sigHashBytes) + } + topicIdx++ + } + dataArgs := &typeComponent{ + cType: TupleComponent, + tupleChildren: make([]*typeComponent, 0, len(inputTypes)), + } + dataArgIndexMap := make(map[int]int) + valueTree := &ComponentValue{ + Component: typeTree, + Children: make([]*ComponentValue, len(inputTypes)), + } + for idx, input := range inputTypes { + if input.Parameter().Indexed { + // Extract the value (or value hash) from the topic + if topicIdx >= len(topics) { + return nil, i18n.NewError(ctx, signermsgs.MsgEventsInsufficientTopics, idx, e) + } + topic := topics[topicIdx] + topicIdx++ + valueTree.Children[idx], err = e.topicToValue(ctx, topicIdx, topic, input.(*typeComponent)) + if err != nil { + return nil, err + } + } else { + // Add this parameter to the list we expect to be encoded in the data, with a map + // back to where we store the output in the tree. + dataArgIndexMap[len(dataArgs.tupleChildren)] = idx + dataArgs.tupleChildren = append(dataArgs.tupleChildren, input.(*typeComponent)) + } + } + // If we have data args, decode them + if len(dataArgs.tupleChildren) > 0 { + dataValueTree, err := dataArgs.DecodeABIDataCtx(ctx, data, 0) + if err != nil { + return nil, err + } + // Map back to their original positions + for i, v := range dataValueTree.Children { + targetIdx := dataArgIndexMap[i] + valueTree.Children[targetIdx] = v + } + } + return valueTree, nil +} + func (e *Entry) SignatureCtx(ctx context.Context) (string, error) { buff := new(strings.Builder) buff.WriteString(e.Name) diff --git a/pkg/abi/abi_test.go b/pkg/abi/abi_test.go index 95a52625..7865d0cd 100644 --- a/pkg/abi/abi_test.go +++ b/pkg/abi/abi_test.go @@ -238,8 +238,8 @@ func TestABIModifyReParse(t *testing.T) { // Re-parse sorts it abi.Validate() assert.Equal(t, "foo(uint128)", abi[0].String()) - assert.Equal(t, "c56cb6b0", abi[0].ID()) - assert.Equal(t, []byte{0xc5, 0x6c, 0xb6, 0xb0}, abi[0].IDBytes()) + assert.Equal(t, "0xc56cb6b0", abi[0].FunctionSelectorBytes().String()) + assert.Equal(t, ethtypes.HexBytes0xPrefix{0xc5, 0x6c, 0xb6, 0xb0}, abi[0].FunctionSelectorBytes()) } @@ -274,8 +274,7 @@ func TestABIModifyBadInputs(t *testing.T) { assert.Regexp(t, "FF22028", err) assert.Empty(t, abi[0].Inputs[0].String()) - assert.Empty(t, abi[0].ID()) - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00}, abi[0].IDBytes()) + assert.Equal(t, ethtypes.HexBytes0xPrefix{0x00, 0x00, 0x00, 0x00}, abi[0].FunctionSelectorBytes()) } @@ -504,4 +503,182 @@ func TestSignatureHashInvalid(t *testing.T) { } _, err := e.SignatureHash() assert.Regexp(t, "FF22025", err) + + assert.Equal(t, make(ethtypes.HexBytes0xPrefix, 32), e.SignatureHashBytes()) +} + +func TestDecodeEventIndexedOnly(t *testing.T) { + e := &Entry{ + Anonymous: true, + Type: Event, + Inputs: ParameterArray{ + { + Name: "from", + Type: "address", + Indexed: true, + }, + { + Name: "to", + Type: "address", + Indexed: true, + }, + { + Name: "tokenId", + Type: "uint256", + Indexed: true, + }, + }, + } + v, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{ + ethtypes.MustNewHexBytes0xPrefix("0x0000000000000000000000000000000000000000000000000000000000000000"), + ethtypes.MustNewHexBytes0xPrefix("0x000000000000000000000000fb075bb99f2aa4c49955bf703509a227d7a12248"), + ethtypes.MustNewHexBytes0xPrefix("0x000000000000000000000000000000000000000000000000000000000000091d"), + }, ethtypes.HexBytes0xPrefix{}) + assert.NoError(t, err) + + j, err := v.JSON() + assert.NoError(t, err) + + assert.JSONEq(t, `{ + "from": "0000000000000000000000000000000000000000", + "to": "fb075bb99f2aa4c49955bf703509a227d7a12248", + "tokenId": "2333" + }`, string(j)) +} + +func TestDecodeEventMixed(t *testing.T) { + e := &Entry{ + Type: Event, + Name: "MyEvent", + Inputs: ParameterArray{ + { + Name: "indexed1", + Type: "uint256", + Indexed: true, + }, + { + Name: "indexed2", + Type: "address", + Indexed: true, + }, + { + Name: "unindexed1", + Type: "uint256", + }, + { + Name: "unindexed2", + Type: "bool", + }, + { + Name: "indexed3", + Type: "string", + Indexed: true, + }, + { + Name: "unindexed3", + Type: "string", + }, + }, + } + v, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{ + ethtypes.MustNewHexBytes0xPrefix("0x27f22555fe6499d07163873ce3237d90091053cdeb2280c652466f4e1be378e5"), + ethtypes.MustNewHexBytes0xPrefix("0x0000000000000000000000000000000000000000000000000000000000002b67"), + ethtypes.MustNewHexBytes0xPrefix("0x0000000000000000000000003968ef051b422d3d1cdc182a88bba8dd922e6fa4"), + ethtypes.MustNewHexBytes0xPrefix("0x592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba"), + }, ethtypes.MustNewHexBytes0xPrefix("0x00000000000000000000000000000000000000000000000000000000000056ce00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000")) + assert.NoError(t, err) + + j, err := v.JSON() + assert.NoError(t, err) + + assert.JSONEq(t, `{ + "indexed1": "11111", + "indexed2": "3968ef051b422d3d1cdc182a88bba8dd922e6fa4", + "unindexed1": "22222", + "unindexed2": true, + "indexed3": "592fa743889fc7f92ac2a37bb1f5ba1daf2a5c84741ca0e0061d243a2e6707ba", + "unindexed3": "Hello World" + }`, string(j)) +} + +func TestDecodeEventBadABI(t *testing.T) { + e := &Entry{ + Inputs: ParameterArray{ + { + Type: "wrong", + }, + }, + } + _, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{}, ethtypes.HexBytes0xPrefix{}) + assert.Regexp(t, "FF22025", err) +} + +func TestDecodeEventBadSignature(t *testing.T) { + e := &Entry{ + Name: "MyEvent", + Type: Event, + Inputs: ParameterArray{ + { + Name: "addr1", + Type: "address", + Indexed: true, + }, + }, + } + _, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{ + ethtypes.MustNewHexBytes0xPrefix("0x0000000000000000000000000000000000000000000000000000000000000000"), + }, ethtypes.HexBytes0xPrefix{}) + assert.Regexp(t, "FF22054", err) +} + +func TestDecodeEventInsufficientTopics(t *testing.T) { + e := &Entry{ + Name: "MyEvent", + Type: Event, + Anonymous: true, + Inputs: ParameterArray{ + { + Name: "addr1", + Type: "address", + Indexed: true, + }, + }, + } + _, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{}, ethtypes.HexBytes0xPrefix{}) + assert.Regexp(t, "FF22053", err) +} + +func TestDecodeEventBadValue(t *testing.T) { + e := &Entry{ + Name: "MyEvent", + Type: Event, + Anonymous: true, + Inputs: ParameterArray{ + { + Name: "addr1", + Type: "address", + Indexed: true, + }, + }, + } + _, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{ + ethtypes.MustNewHexBytes0xPrefix("0x"), + }, ethtypes.HexBytes0xPrefix{}) + assert.Regexp(t, "FF22047", err) +} + +func TestDecodeEventBadData(t *testing.T) { + e := &Entry{ + Name: "MyEvent", + Type: Event, + Anonymous: true, + Inputs: ParameterArray{ + { + Name: "addr1", + Type: "address", + }, + }, + } + _, err := e.DecodeEventData([]ethtypes.HexBytes0xPrefix{}, ethtypes.MustNewHexBytes0xPrefix("0x")) + assert.Regexp(t, "FF22047", err) } diff --git a/pkg/abi/abidecode.go b/pkg/abi/abidecode.go index 4685d081..a8ad94f8 100644 --- a/pkg/abi/abidecode.go +++ b/pkg/abi/abidecode.go @@ -111,7 +111,7 @@ func decodeABIUnsignedFloat(ctx context.Context, desc string, block []byte, head func decodeABILength(ctx context.Context, desc string, block []byte, offset int) (count int, err error) { if offset+32 > len(block) { - return -1, i18n.NewError(ctx, signermsgs.MsgNotEnoughtBytesABIArrayCount, desc) + return -1, i18n.NewError(ctx, signermsgs.MsgNotEnoughBytesABIArrayCount, desc) } i := new(big.Int).SetBytes(block[offset : offset+32]) if i.BitLen() > 32 { diff --git a/pkg/abi/typecomponents.go b/pkg/abi/typecomponents.go index 09a10254..d439e2a8 100644 --- a/pkg/abi/typecomponents.go +++ b/pkg/abi/typecomponents.go @@ -81,6 +81,7 @@ type elementaryTypeInfo struct { 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 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) @@ -156,6 +157,7 @@ var ( mMin: 8, mMax: 256, mMod: 8, + fixed32: true, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -170,6 +172,7 @@ var ( mMin: 8, mMax: 256, mMod: 8, + fixed32: true, jsonEncodingType: JSONEncodingTypeInteger, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getIntegerFromInterface(ctx, desc, input) @@ -181,6 +184,7 @@ var ( name: "address", suffixType: suffixTypeNone, defaultM: 160, // encoded as "uint160" + fixed32: true, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getUintBytesFromInterface(ctx, desc, input) }, @@ -192,6 +196,7 @@ var ( name: "bool", suffixType: suffixTypeNone, defaultM: 8, // encoded as "uint8" + fixed32: true, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBoolAsUnsignedIntegerFromInterface(ctx, desc, input) }, @@ -208,6 +213,7 @@ var ( mMod: 8, nMin: 1, nMax: 80, + fixed32: true, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -224,6 +230,7 @@ var ( mMod: 8, nMin: 1, nMax: 80, + fixed32: true, jsonEncodingType: JSONEncodingTypeFloat, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getFloatFromInterface(ctx, desc, input) @@ -236,6 +243,7 @@ var ( suffixType: suffixTypeMOptional, // note that "bytes" without a suffix is a special dynamic sized byte sequence mMin: 1, mMax: 32, + fixed32: false, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBytesFromInterface(ctx, desc, input) }, @@ -247,6 +255,7 @@ var ( name: "function", suffixType: suffixTypeNone, defaultM: 24, // encoded as "bytes24" + fixed32: true, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getBytesFromInterface(ctx, desc, input) }, @@ -257,6 +266,7 @@ var ( ElementaryTypeString = registerElementaryType(elementaryTypeInfo{ name: "string", suffixType: suffixTypeNone, + fixed32: false, readExternalData: func(ctx context.Context, desc string, input interface{}) (interface{}, error) { return getStringFromInterface(ctx, desc, input) }, From 86d9540318ef4986500053a29884522ca258c333 Mon Sep 17 00:00:00 2001 From: Peter Broadhurst Date: Mon, 27 Jun 2022 12:32:47 -0400 Subject: [PATCH 2/2] Improve function docs Signed-off-by: Peter Broadhurst --- pkg/abi/abi.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/abi/abi.go b/pkg/abi/abi.go index 840f3121..a3fde27a 100644 --- a/pkg/abi/abi.go +++ b/pkg/abi/abi.go @@ -377,7 +377,7 @@ func (e *Entry) GenerateFunctionSelectorCtx(ctx context.Context) ([]byte, error) } // FunctionSelectorBytes is a convenience function to get the ID as bytes. -// Will return a nil 4 bytes on error +// Will return all zeros on error (ensures non-nil) func (e *Entry) FunctionSelectorBytes() ethtypes.HexBytes0xPrefix { id, err := e.GenerateFunctionSelector() if err != nil { @@ -447,6 +447,8 @@ func (e *Entry) SignatureHashCtx(context.Context) (ethtypes.HexBytes0xPrefix, er return hash.Sum(nil), nil } +// SignatureHashBytes is a convenience function to get the signature hash as bytes. +// Will return all zeros on error (ensures non-nil) func (e *Entry) SignatureHashBytes() ethtypes.HexBytes0xPrefix { sh, err := e.SignatureHashCtx(context.Background()) if err != nil {