Skip to content

Commit

Permalink
Merge pull request #12 from kaleido-io/decode-events
Browse files Browse the repository at this point in the history
Add function for decoding event ABI data
  • Loading branch information
peterbroadhurst authored Jun 27, 2022
2 parents db8cbc0 + 86d9540 commit c2155da
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 67 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"Keccak",
"keypair",
"keystorev",
"pluggable",
"resty",
"rpcbackendmocks",
"secp",
Expand Down
88 changes: 45 additions & 43 deletions internal/signermsgs/en_error_messges.go
Original file line number Diff line number Diff line change
Expand Up @@ -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'")
)
129 changes: 110 additions & 19 deletions pkg/abi/abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -151,6 +151,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"strings"

"github.com/hyperledger/firefly-common/pkg/i18n"
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
// Will return a nil 4 bytes on error
func (e *Entry) IDBytes() []byte {
// FunctionSelectorBytes is a convenience function to get the ID as bytes.
// Will return all zeros on error (ensures non-nil)
func (e *Entry) FunctionSelectorBytes() ethtypes.HexBytes0xPrefix {
id, err := e.GenerateFunctionSelector()
if err != nil {
log.L(context.Background()).Warnf("ABI parsing failed: %s", err)
Expand All @@ -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)
Expand Down Expand Up @@ -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]))
Expand All @@ -442,17 +432,118 @@ 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
}

// 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 {
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)
Expand Down
Loading

0 comments on commit c2155da

Please sign in to comment.