Skip to content

Commit

Permalink
Merge pull request #38 from samuraisam/sam/polymorphic-method-args
Browse files Browse the repository at this point in the history
Fully support arbitrary smart contract calls
  • Loading branch information
Roberto Bayardo authored Dec 7, 2022
2 parents 12ef483 + 4c61c1f commit 4a6f21c
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 66 deletions.
81 changes: 57 additions & 24 deletions services/construction/preprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ package construction

import (
"context"
"encoding/json"
"encoding/hex"
"errors"
"fmt"
"log"
Expand Down Expand Up @@ -178,22 +178,15 @@ func (a *APIService) ConstructionPreprocess(
fmt.Errorf("%s is not a valid signature string", v),
)
}
var methodArgs []string
if v, ok := request.Metadata["method_args"]; ok {
methodArgsBytes, _ := json.Marshal(v)
err := json.Unmarshal(methodArgsBytes, &methodArgs)
if err != nil {
fmt.Println("Error in unmarshal")
}
}
data, err := constructContractCallData(methodSigStringObj, methodArgs)

data, err := constructContractCallData(methodSigStringObj, request.Metadata["method_args"])
if err != nil {
return nil, svcErrors.WrapErr(svcErrors.ErrFetchFunctionSignatureMethodID, err)
}
preprocessOutputOptions.ContractAddress = checkTo
preprocessOutputOptions.Data = data
preprocessOutputOptions.MethodSignature = methodSigStringObj
preprocessOutputOptions.MethodArgs = methodArgs
preprocessOutputOptions.MethodArgs = request.Metadata["method_args"]

}

Expand Down Expand Up @@ -372,26 +365,66 @@ func constructERC20TransferData(to string, value *big.Int) ([]byte, error) {

// constructContractCallData constructs the data field of a Polygon
// transaction
func constructContractCallData(methodSig string, methodArgs []string) ([]byte, error) {
func constructContractCallData(methodSig string, methodArgsGeneric interface{}) ([]byte, error) {
data, sigErr := contractCallMethodID(methodSig)
if sigErr != nil {
return nil, sigErr
}

arguments := abi.Arguments{}
argumentsData := []interface{}{}
// 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 := methodArgsGeneric.(type) {

methodID, err := contractCallMethodID(methodSig)
if err != nil {
return nil, err
// 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:
methodArgs = strings.TrimPrefix(methodArgs, "0x")
b, decErr := hex.DecodeString(methodArgs)
if decErr != nil {
return nil, fmt.Errorf("error decoding method args hex data: %w", decErr)
}
return append(data, b...), nil

// case 2: method args are a list of interface{} which will be converted to string before encoding
case []interface{}:
var strList []string
for i, genericVal := range methodArgs {
strVal, isStrVal := genericVal.(string)
if !isStrVal {
return nil, fmt.Errorf("invalid method_args type at index %d: %T (must be a string)",
i, genericVal,
)
}
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:
return encodeMethodArgsStrings(data, methodSig, methodArgs)

// case 4: there is no known way to decode the method args
default:
return nil, fmt.Errorf(
"invalid method_args type, accepted values are []string and hex-encoded string."+
" type received=%T value=%#v", methodArgsGeneric, methodArgsGeneric,
)
}
}

var data []byte
data = append(data, methodID...)
func encodeMethodArgsStrings(sigData []byte, methodSig string, methodArgs []string) ([]byte, error) {
var arguments abi.Arguments
var argumentsData []interface{}

splitSigByLeadingParenthesis := strings.Split(methodSig, "(")
if len(splitSigByLeadingParenthesis) < 2 {
return data, nil
return nil, nil
}
splitSigByTrailingParenthesis := strings.Split(splitSigByLeadingParenthesis[1], ")")
if len(splitSigByTrailingParenthesis) < 1 {
return data, nil
return nil, nil
}
splitSigByComma := strings.Split(splitSigByTrailingParenthesis[0], ",")

Expand All @@ -409,6 +442,7 @@ func constructContractCallData(methodSig string, methodArgs []string) ([]byte, e

arguments = append(arguments, argument...)
var argData interface{}

switch {
case v == "address":
{
Expand Down Expand Up @@ -442,7 +476,6 @@ func constructContractCallData(methodSig string, methodArgs []string) ([]byte, e
}
argumentsData = append(argumentsData, argData)
}
abiEncodeData, _ := arguments.PackValues(argumentsData)
data = append(data, abiEncodeData...)
return data, nil
encData, _ := arguments.PackValues(argumentsData)
return append(sigData, encData...), nil
}
23 changes: 23 additions & 0 deletions services/construction/preprocess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ var (
methodSignature = "approve(address,uint256)"
methodArgs = []string{"0xD10a72Cf054650931365Cc44D912a4FD75257058", "1000"}
expectedMethodArgs = []interface{}{"0xD10a72Cf054650931365Cc44D912a4FD75257058", "1000"}
complexMethodSignature = "mintItemBatch(address[],string)"
complexMethodArgs = "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000b406c0106ba32281ddfa75626479304feb70d0580000000000000000000000003cdc2ce790d740fd8b8e99baf738497c5e2de62000000000000000000000000006da92f4f1815e83cf5a020f952f0e3275a5b156000000000000000000000000f344767634735d588357ed5828488094bef02efe000000000000000000000000000000000000000000000000000000000000002e516d614b57483933397346454464576333347252395453433868647758624357574575454a6b6476714e334a7573000000000000000000000000000000000000"
complexMethodData = "0x079c66c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000b406c0106ba32281ddfa75626479304feb70d0580000000000000000000000003cdc2ce790d740fd8b8e99baf738497c5e2de62000000000000000000000000006da92f4f1815e83cf5a020f952f0e3275a5b156000000000000000000000000f344767634735d588357ed5828488094bef02efe000000000000000000000000000000000000000000000000000000000000002e516d614b57483933397346454464576333347252395453433868647758624357574575454a6b6476714e334a7573000000000000000000000000000000000000"
)

func TestPreprocess(t *testing.T) {
Expand Down Expand Up @@ -209,6 +212,26 @@ func TestPreprocess(t *testing.T) {
},
},
},
"happy path: generic contract call with pre-encoded arguments": {
operations: templateOperations(preprocessTransferValue, polygon.Currency),
metadata: map[string]interface{}{
"nonce": "34",
"method_signature": complexMethodSignature,
"method_args": complexMethodArgs,
},
expectedResponse: &types.ConstructionPreprocessResponse{
Options: map[string]interface{}{
"from": preprocessFromAddress,
"to": preprocessToAddress,
"value": "0x1",
"contract_address": preprocessToAddress,
"data": complexMethodData,
"nonce": "0x22",
"method_signature": complexMethodSignature,
"method_args": complexMethodArgs,
},
},
},
"happy path: native currency with gas limit": {
operations: templateOperations(preprocessTransferValue, polygon.Currency),
metadata: map[string]interface{}{
Expand Down
84 changes: 42 additions & 42 deletions services/construction/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,33 +73,33 @@ type Client interface {
//
// Value here is MATIC. It will always be 0 for ERC20 tokens
type options struct {
From string `json:"from"`
Nonce *big.Int `json:"nonce,omitempty"`
Data []byte `json:"data,omitempty"`
To string `json:"to"`
TokenAddress string `json:"token_address,omitempty"`
ContractAddress string `json:"contract_address,omitempty"`
Value *big.Int `json:"value,omitempty"`
GasLimit *big.Int `json:"gas_limit,omitempty"`
GasCap *big.Int `json:"gas_cap,omitempty"`
GasTip *big.Int `json:"gas_tip,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs []string `json:"method_args,omitempty"`
From string `json:"from"`
Nonce *big.Int `json:"nonce,omitempty"`
Data []byte `json:"data,omitempty"`
To string `json:"to"`
TokenAddress string `json:"token_address,omitempty"`
ContractAddress string `json:"contract_address,omitempty"`
Value *big.Int `json:"value,omitempty"`
GasLimit *big.Int `json:"gas_limit,omitempty"`
GasCap *big.Int `json:"gas_cap,omitempty"`
GasTip *big.Int `json:"gas_tip,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs interface{} `json:"method_args,omitempty"`
}

type optionsWire struct {
From string `json:"from"`
Nonce string `json:"nonce,omitempty"`
Data string `json:"data,omitempty"`
To string `json:"to"`
TokenAddress string `json:"token_address,omitempty"`
ContractAddress string `json:"contract_address,omitempty"`
Value string `json:"value,omitempty"`
GasLimit string `json:"gas_limit,omitempty"`
GasCap string `json:"gas_cap,omitempty"`
GasTip string `json:"gas_tip,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs []string `json:"method_args,omitempty"`
From string `json:"from"`
Nonce string `json:"nonce,omitempty"`
Data string `json:"data,omitempty"`
To string `json:"to"`
TokenAddress string `json:"token_address,omitempty"`
ContractAddress string `json:"contract_address,omitempty"`
Value string `json:"value,omitempty"`
GasLimit string `json:"gas_limit,omitempty"`
GasCap string `json:"gas_cap,omitempty"`
GasTip string `json:"gas_tip,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs interface{} `json:"method_args,omitempty"`
}

func (o *options) MarshalJSON() ([]byte, error) {
Expand Down Expand Up @@ -203,27 +203,27 @@ func (o *options) UnmarshalJSON(data []byte) error {
}

type metadata struct {
Nonce uint64 `json:"nonce"`
GasCap *big.Int `json:"gas_cap"`
GasTip *big.Int `json:"gas_tip"`
GasLimit uint64 `json:"gas_limit,omitempty"`
Data []byte `json:"data,omitempty"`
To string `json:"to,omitempty"`
Value *big.Int `json:"value,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs []string `json:"method_args,omitempty"`
Nonce uint64 `json:"nonce"`
GasCap *big.Int `json:"gas_cap"`
GasTip *big.Int `json:"gas_tip"`
GasLimit uint64 `json:"gas_limit,omitempty"`
Data []byte `json:"data,omitempty"`
To string `json:"to,omitempty"`
Value *big.Int `json:"value,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs interface{} `json:"method_args,omitempty"`
}

type metadataWire struct {
Nonce string `json:"nonce"`
GasCap string `json:"gas_cap"`
GasTip string `json:"gas_tip"`
GasLimit string `json:"gas_limit,omitempty"`
Data string `json:"data,omitempty"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs []string `json:"method_args,omitempty"`
Nonce string `json:"nonce"`
GasCap string `json:"gas_cap"`
GasTip string `json:"gas_tip"`
GasLimit string `json:"gas_limit,omitempty"`
Data string `json:"data,omitempty"`
To string `json:"to,omitempty"`
Value string `json:"value,omitempty"`
MethodSignature string `json:"method_signature,omitempty"`
MethodArgs interface{} `json:"method_args,omitempty"`
}

func (m *metadata) MarshalJSON() ([]byte, error) {
Expand Down

0 comments on commit 4a6f21c

Please sign in to comment.