diff --git a/services/construction/contract_call_data.go b/services/construction/contract_call_data.go index 908a1a5..66ed75c 100644 --- a/services/construction/contract_call_data.go +++ b/services/construction/contract_call_data.go @@ -29,6 +29,8 @@ import ( "golang.org/x/crypto/sha3" ) +const NoMethodSig = "NO-METHOD-SIG" + // ConstructContractCallDataGeneric constructs the data field of a transaction. // The methodArgs can be already in ABI encoded format in case of a single string // It can also be passed in as a slice of args, which requires further encoding. @@ -38,7 +40,7 @@ func ConstructContractCallDataGeneric(methodSig string, methodArgs interface{}) return nil, err } - // preprocess method args + // preprocess method args for fallback pattern contract call args, err := preprocessArgs(methodSig, methodArgs) if err != nil { return nil, err @@ -87,16 +89,16 @@ func ConstructContractCallDataGeneric(methodSig string, methodArgs interface{}) } } -// preprocessArgs converts methodArgs to a string value if methodSig is a 4-byte hex string. -// In this case, we expect methodArgs containing the pre-compiled ABI data. +// preprocessArgs converts methodArgs to a string value if methodSig is an empty string. +// We are calling a contract written with fallback pattern, which has no method signature. func preprocessArgs(methodSig string, methodArgs interface{}) (interface{}, error) { - if !strings.HasPrefix(methodSig, "0x") || len(methodSig) != 10 { + if methodSig != "" && methodSig != NoMethodSig { return methodArgs, nil } switch args := methodArgs.(type) { case []interface{}: - if len(args) > 0 { + if len(args) == 1 { argStr, isStrVal := args[0].(string) if !isStrVal { return nil, fmt.Errorf("failed to convert method arg \"%T\" to string", args[0]) @@ -105,7 +107,7 @@ func preprocessArgs(methodSig string, methodArgs interface{}) (interface{}, erro } return methodArgs, nil case []string: - if len(args) > 0 { + if len(args) == 1 { return args[0], nil } return methodArgs, nil @@ -219,13 +221,9 @@ func encodeMethodArgsStrings(methodID []byte, methodSig string, methodArgs []str // contractCallMethodID calculates the first 4 bytes of the method // signature for function call on contract func contractCallMethodID(methodSig string) ([]byte, error) { - if strings.HasPrefix(methodSig, "0x") && len(methodSig) == 10 { - // method signature is already a 4 byte hex string - result, err := hex.DecodeString(methodSig[2:]) - if err != nil { - return nil, err - } - return result, nil + if methodSig == "" || methodSig == NoMethodSig { + // contract call without method signature (fallback pattern) + return []byte{}, nil } fnSignature := []byte(methodSig) diff --git a/services/construction/contract_call_data_test.go b/services/construction/contract_call_data_test.go index 0461225..e3bafde 100644 --- a/services/construction/contract_call_data_test.go +++ b/services/construction/contract_call_data_test.go @@ -51,10 +51,15 @@ func TestConstruction_ContractCallData(t *testing.T) { methodArgs: []interface{}{"bool abc", "0x0000000000000000000000000000000000000000", "true"}, expectedResponse: "0x60d7a2780000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000008626f6f6c20616263000000000000000000000000000000000000000000000000", }, - "happy path: method sig is hex string and args is a list of interface": { - methodSig: "0xabcdef12", - methodArgs: []interface{}{"34567890"}, - expectedResponse: "0xabcdef1234567890", + "happy path: method sig is an empty string and args is a list of interface": { + methodSig: "", + methodArgs: []interface{}{"0xabcde12345"}, + expectedResponse: "0xabcde12345", + }, + "happy path: method sig is NO-METHOD-SIG and args is a list of interface": { + methodSig: NoMethodSig, + methodArgs: []interface{}{"0xaabbcc112233"}, + expectedResponse: "0xaabbcc112233", }, "error: case string: invalid method args hex data": { methodSig: "attest((bytes32,(address,uint64,bool,bytes32,bytes,uint256)))", @@ -102,29 +107,29 @@ func TestConstruction_preprocessArgs(t *testing.T) { "0", "0x"}, }, - "happy path: method sig is hex string and args is nil": { - methodSig: "0xabcdef12", + "happy path: method sig is empty and args is nil": { + methodSig: "", methodArgs: nil, expectedResponse: nil, }, - "happy path: method sig is hex string and args is a single string": { - methodSig: "0xabcdef12", - methodArgs: "34567890", - expectedResponse: "34567890", + "happy path: method sig is NO-METHOD-SIG and args is a single string": { + methodSig: NoMethodSig, + methodArgs: "0x12345", + expectedResponse: "0x12345", }, - "happy path: method sig is hex string and args is a list of interface": { - methodSig: "0xabcdef12", - methodArgs: []interface{}{"34567890"}, - expectedResponse: "34567890", + "happy path: method sig is empty and args is a list of interface": { + methodSig: "", + methodArgs: []interface{}{"0xabcde"}, + expectedResponse: "0xabcde", }, - "happy path: method sig is hex string and args is a list of strings": { - methodSig: "0xabcdef12", - methodArgs: []string{"34567890"}, - expectedResponse: "34567890", + "happy path: method sig is NO-METHOD-SIG and args is a list of strings": { + methodSig: NoMethodSig, + methodArgs: []string{"0x1a2b3c"}, + expectedResponse: "0x1a2b3c", }, "unhappy path: args is a list of interface and cannot be converted to strings": { - methodSig: "0xabcdef12", - methodArgs: []interface{}{34567890}, + methodSig: "", + methodArgs: []interface{}{34567}, expectedError: errors.New("failed to convert method arg \"int\" to string"), }, }