Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

intents: add new contractCall transaction type #157

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions intents/intent.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion intents/intent.ridl
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,21 @@ struct IntentDataGetIdToken
struct TransactionRaw
- type: string
- to: string
- value: string
- value?: string
pkieltyka marked this conversation as resolved.
Show resolved Hide resolved
- data: string

struct AbiData
pkieltyka marked this conversation as resolved.
Show resolved Hide resolved
- abi: string
- func?: string
- args: []any

enum TransactionType: string
- transaction
- erc20send
- erc721send
- erc1155send
- delayedEncode
- contractCall

struct TransactionERC20
- type: string
Expand All @@ -180,13 +186,21 @@ struct TransactionERC1155Value
+ go.field.name = ID
- amount: string

# NOTE: TransactionDelayedEncode is deprecated, please use TransactionContractCall instead
pkieltyka marked this conversation as resolved.
Show resolved Hide resolved
struct TransactionDelayedEncode
- type: string
- to: string
- value: string
- data: any
+ go.field.type = json.RawMessage

struct TransactionContractCall
- type: string
- to: string
- value?: string
- data: AbiData
+ go.field.type = AbiData

struct TransactionERC1155
- type: string
- tokenAddress: string
Expand Down
142 changes: 142 additions & 0 deletions intents/intent_data_transaction_contract_abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package intents

import (
"encoding/json"
"errors"
"fmt"
"strings"

"github.com/0xsequence/ethkit/ethcoder"
"github.com/0xsequence/ethkit/go-ethereum/common"
)

type contractCallType struct {
Abi string `json:"abi"`
Func string `json:"func"`
Args []any `json:"args"`
}

func EncodeContractCall(data *contractCallType) (string, error) {
// Get the method from the abi
method, _, err := getMethodFromAbi(data.Abi, data.Func)
if err != nil {
return "", err
}

enc := make([]string, len(data.Args))

// String args can be used right away, but any nested
// `contractCallType` must be handled recursively
for i, arg := range data.Args {
switch arg := arg.(type) {
case string:
enc[i] = arg

case map[string]interface{}:
nst := arg

var funcName string
if v, ok := nst["func"].(string); ok {
funcName = v
}

args, ok := nst["args"].([]interface{})
if !ok {
return "", fmt.Errorf("nested args expected to be an array")
}

enc[i], err = EncodeContractCall(&contractCallType{
Abi: nst["abi"].(string), // TODO: should error check this..? or will it return "" ?
pkieltyka marked this conversation as resolved.
Show resolved Hide resolved
Func: funcName,
Args: args,
})
if err != nil {
return "", err
}

default:
return "", fmt.Errorf("invalid arg type")
}
}

// Encode the method call
res, err := ethcoder.AbiEncodeMethodCalldataFromStringValues(method, enc)
if err != nil {
return "", err
}

return "0x" + common.Bytes2Hex(res), nil
}

// The abi may be a:
// - already encoded method abi: transferFrom(address,address,uint256)
// - already encoded named method: transferFrom(address from,address to,uint256 val)
// - an array of function abis: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_maxCost\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_fees\",\"type\":\"address\"}],\"name\":\"fillOrKillOrder\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}]"
// - or a single function abi: "{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_maxCost\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_fees\",\"type\":\"address\"}],\"name\":\"fillOrKillOrder\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}"
// And it must always return it encoded, like this:
// - transferFrom(address,address,uint256)
// making sure that the method matches the returned one
func getMethodFromAbi(abi string, method string) (string, []string, error) {
//
// First attempt to parse `abi` string as a plain method abi
// ie. transferFrom(address,address,uint256)
//

// Handle the case for already encoded method abi.
// NOTE: we do not need the know the `method` argument here.
abi = strings.TrimSpace(abi)
if len(abi) > 0 && strings.Contains(abi, "(") && abi[len(abi)-1] == ')' {
// NOTE: even though the ethcoder function is `ParseEventDef`, designed for event type parsing
// the abi format for a single function structure is the same, so it works. Perhaps we will rename
// `ParseEventDef` in the future, or just add another method with a different name.
eventDef, err := ethcoder.ParseEventDef(abi)
if err != nil {
return "", nil, err
}
return eventDef.Sig, eventDef.ArgNames, nil
}

//
// If above didn't work, attempt to parse `abi` string as
// a JSON object of the full abi definition
//

type FunctionAbi struct {
Name string `json:"name"`
Type string `json:"type"`
Inputs []struct {
InternalType string `json:"internalType"`
Name string `json:"name"`
Type string `json:"type"`
} `json:"inputs"`
}

// Handle array of function abis and single function abi
var abis []FunctionAbi
if strings.HasPrefix(abi, "[") {
if err := json.Unmarshal([]byte(abi), &abis); err != nil {
return "", nil, err
}
} else {
var singleAbi FunctionAbi
if err := json.Unmarshal([]byte(abi), &singleAbi); err != nil {
return "", nil, err
}
abis = append(abis, singleAbi)
}

// Find the correct method and encode it
for _, fnAbi := range abis {
if fnAbi.Name == method {
var paramTypes []string
order := make([]string, len(fnAbi.Inputs))
for i, input := range fnAbi.Inputs {
paramTypes = append(paramTypes, input.Type)
order[i] = input.Name
}
return method + "(" + strings.Join(paramTypes, ",") + ")", order, nil
}
}

return "", nil, errors.New("Method not found in ABI")
}
Loading
Loading