Skip to content

Commit

Permalink
Preprocess args
Browse files Browse the repository at this point in the history
  • Loading branch information
songge-cb committed Jul 15, 2024
1 parent 48a8d94 commit 9a72373
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 23 deletions.
64 changes: 41 additions & 23 deletions services/construction/contract_call_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,20 @@ func ConstructContractCallDataGeneric(methodSig string, methodArgs interface{})
return nil, err
}

// preprocess method args
args, err := preprocessArgs(methodSig, methodArgs)
if err != nil {
return nil, err
}

// switch on the type of the method args. method args can come in from json as either a string or list of strings
switch methodArgs := methodArgs.(type) {
switch methodArgs := args.(type) {
// case 0: no method arguments, return the selector
case nil:
return data, nil

// case 1: method args are pre-compiled ABI data. decode the hex and create the call data directly
case string:
if strings.HasPrefix(methodSig, "0x") {
log.Printf("methodArgs is a string; %s", methodArgs)
}
methodArgs = strings.TrimPrefix(methodArgs, "0x")
b, decErr := hex.DecodeString(methodArgs)
if decErr != nil {
Expand All @@ -58,16 +61,6 @@ func ConstructContractCallDataGeneric(methodSig string, methodArgs interface{})

// case 2: method args are a list of interface{} which will be converted to string before encoding
case []interface{}:
if strings.HasPrefix(methodSig, "0x") && len(methodArgs) == 1 {
methodArgStr, _ := methodArgs[0].(string)
log.Printf("methodArgs is an interface array with length 1: %s", methodArgStr)
txData := strings.TrimPrefix(methodArgStr, "0x")
b, decErr := hex.DecodeString(txData)
if decErr != nil {
return nil, fmt.Errorf("error decoding method args hex data: %w", decErr)
}
return append(data, b...), nil
}
var strList []string
for i, genericVal := range methodArgs {
strVal, isStrVal := genericVal.(string)
Expand All @@ -78,19 +71,11 @@ func ConstructContractCallDataGeneric(methodSig string, methodArgs interface{})
}
strList = append(strList, strVal)
}

return encodeMethodArgsStrings(data, methodSig, strList)

// case 3: method args are encoded as a list of strings, which will be decoded
case []string:
if strings.HasPrefix(methodSig, "0x") && len(methodArgs) == 1 {
log.Printf("methodArgs is a string array with length 1: %s", methodArgs[0])
txData := strings.TrimPrefix(methodArgs[0], "0x")
b, decErr := hex.DecodeString(txData)
if decErr != nil {
return nil, fmt.Errorf("error decoding method args hex data: %w", decErr)
}
return append(data, b...), nil
}
return encodeMethodArgsStrings(data, methodSig, methodArgs)

// case 4: there is no known way to decode the method args
Expand All @@ -102,6 +87,33 @@ 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.
func preprocessArgs(methodSig string, methodArgs interface{}) (interface{}, error) {
if !strings.HasPrefix(methodSig, "0x") || len(methodSig) != 10 {
return methodArgs, nil
}

switch args := methodArgs.(type) {
case []interface{}:
if len(args) > 0 {
argStr, isStrVal := args[0].(string)
if !isStrVal {
return nil, fmt.Errorf("failed to convert method arg \"%T\" to string", args[0])
}
return argStr, nil
}
return methodArgs, nil
case []string:
if len(args) > 0 {
return args[0], nil
}
return methodArgs, nil
default:
return methodArgs, nil
}
}

// encodeMethodArgsStrings constructs the data field of a transaction for a list of string args.
// It attempts to first convert the string arg to it's corresponding type in the method signature,
// and then performs abi encoding to the converted args list and construct the data.
Expand Down Expand Up @@ -224,3 +236,9 @@ func contractCallMethodID(methodSig string) ([]byte, error) {

return hash.Sum(nil)[:4], nil
}

// decodeForABIData decodes the hex string as precompiled ABI data to bytes
func decodeForABIData(hexData string) ([]byte, error) {

Check failure on line 241 in services/construction/contract_call_data.go

View workflow job for this annotation

GitHub Actions / Lint

func `decodeForABIData` is unused (unused)
hexData = strings.TrimPrefix(hexData, "0x")
return hex.DecodeString(hexData)
}
65 changes: 65 additions & 0 deletions services/construction/contract_call_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ 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",
},
"error: case string: invalid method args hex data": {
methodSig: "attest((bytes32,(address,uint64,bool,bytes32,bytes,uint256)))",
methodArgs: "!!!",
Expand All @@ -75,3 +80,63 @@ func TestConstruction_ContractCallData(t *testing.T) {
})
}
}

func TestConstruction_preprocessArgs(t *testing.T) {
tests := map[string]struct {
methodSig string
methodArgs interface{}

expectedResponse interface{}
expectedError error
}{
"happy path: method sig is function name": {
methodSig: "withdraw(address,uint256,uint32,bytes)",
methodArgs: []interface{}{
"0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
"32941055343948244352",
"0",
"0x"},
expectedResponse: []interface{}{
"0x2Ae3F1Ec7F1F5012CFEab0185bfc7aa3cf0DEc22",
"32941055343948244352",
"0",
"0x"},
},
"happy path: method sig is hex string and args is nil": {
methodSig: "0xabcdef12",
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 hex string and args is a list of interface": {
methodSig: "0xabcdef12",
methodArgs: []interface{}{"34567890"},
expectedResponse: "34567890",
},
"happy path: method sig is hex string and args is a list of strings": {
methodSig: "0xabcdef12",
methodArgs: []string{"34567890"},
expectedResponse: "34567890",
},
"unhappy path: args is a list of interface and cannot be converted to strings": {
methodSig: "0xabcdef12",
methodArgs: []interface{}{34567890},
expectedError: errors.New("failed to convert method arg \"int\" to string"),
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
argsReturned, err := preprocessArgs(test.methodSig, test.methodArgs)
if err != nil {
assert.EqualError(t, err, test.expectedError.Error())
} else {
assert.Equal(t, test.expectedResponse, argsReturned)
}
})
}
}

0 comments on commit 9a72373

Please sign in to comment.