Skip to content

Commit

Permalink
Add delayed abi primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
Agusx1211 committed Oct 28, 2023
1 parent 61f6a4d commit 8c74e7a
Show file tree
Hide file tree
Showing 2 changed files with 351 additions and 0 deletions.
198 changes: 198 additions & 0 deletions intents/packets/delayed_abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package packets

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

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

type delayedEncodeType struct {
Abi string `json:"abi"`
Func string `json:"func"`
Args json.RawMessage `json:"args"`
}

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

// Try decode args as array
var args1 []interface{}
err = json.Unmarshal(data.Args, &args1)
if err == nil {
enc := make([]string, len(args1))
// String args can be used right away, but any nested
// `delayedEncodeType` must be handled recursively
for i, arg := range args1 {
switch arg.(type) {
case string:
enc[i] = arg.(string)

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

rjsn, err := json.Marshal(nst["args"])
if err != nil {
return "", err
}

enc[i], err = EncodeDelayedABI(&delayedEncodeType{
Abi: nst["abi"].(string),
Func: nst["func"].(string),
Args: json.RawMessage(rjsn),
})
if err != nil {
return "", err
}

default:
spew.Dump(arg)
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
}

// Try decode args as object
var args2 map[string]interface{}
err = json.Unmarshal(data.Args, &args2)
if err == nil {
// Convert args to array using the order from the abi
// call this method again, for simplicity
args := make([]interface{}, len(order))
for i, argName := range order {
// If argName is not found, then fail
if _, ok := args2[argName]; !ok {
return "", fmt.Errorf("arg '%s' not found", argName)
}

args[i] = args2[argName]
}

jsn, err := json.Marshal(args)
if err != nil {
return "", err
}

return EncodeDelayedABI(&delayedEncodeType{
Abi: data.Abi,
Func: data.Func,
Args: jsn,
})
}

return "", err
}

// 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) {
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 the case for already encoded method abi
if strings.Contains(abi, "(") && strings.Contains(abi, ")") && strings.HasPrefix(abi, method) {
// We may or may not have name information
// transferFrom(address,address,uint256)
// vs
// transferFrom(address from,address to,uint256 val)

// Start by obtaning only the args
args := strings.Split(abi, "(")[1]
args = strings.Split(args, ")")[0]

// Split the args by comma, to get the individual types
argTypes := strings.Split(args, ",")

order := make([]string, len(argTypes))
types := make([]string, len(argTypes))

incompleteNaming := false

for i, arg := range argTypes {
// If starts with space, trim it
arg = strings.TrimLeft(arg, " ")

if strings.Contains(arg, " ") {
// We have name information, so we need to extract it
spl := strings.Split(arg, " ")

order[i] = spl[1]
types[i] = spl[0]

} else {
// We don't have name information, so we must
// mark this case as incomplete
incompleteNaming = true

// Assume that arg is the type
types[i] = arg
}
}

if incompleteNaming {
order = nil
}

// Re encode abi, now without name information
fnc := method + "(" + strings.Join(types, ",") + ")"
return fnc, order, nil
}

// 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")
}
153 changes: 153 additions & 0 deletions intents/packets/delayed_abi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package packets

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestGetMethodFromABI(t *testing.T) {
// From ABI, alone, in array
res, order, err := getMethodFromAbi(`[{"name":"transfer","type":"function","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`, "transfer")
assert.Nil(t, err)

assert.Equal(t, "transfer(address,uint256)", res)
assert.Equal(t, []string{"_to", "_value"}, order)

// From ABI, alone, as object
res, order, err = getMethodFromAbi(`{"name":"transfer","type":"function","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}`, "transfer")
assert.Nil(t, err)

assert.Equal(t, "transfer(address,uint256)", res)
assert.Equal(t, []string{"_to", "_value"}, order)

// From ABI, with many args
res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "fillOrKillOrder")
assert.Nil(t, err)

assert.Equal(t, "fillOrKillOrder(bytes32,uint256,address[],bytes)", res)
assert.Equal(t, []string{"_orderId", "_maxCost", "_fees", "_data"}, order)

res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "notExpired")
assert.Nil(t, err)

assert.Equal(t, "notExpired(uint256,string)", res)
assert.Equal(t, []string{"_val", "_data"}, order)

res, order, err = getMethodFromAbi(`[{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"otherMethods","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "otherMethods")
assert.Nil(t, err)

assert.Equal(t, "otherMethods()", res)
assert.Equal(t, []string{}, order)

// From plain method, without named args
res, order, err = getMethodFromAbi(`transfer(address,uint256)`, "transfer")
assert.Nil(t, err)

assert.Equal(t, "transfer(address,uint256)", res)
assert.Nil(t, order)

// From plain method, with named args
res, order, err = getMethodFromAbi(`transfer(address _to,uint256 _value, bytes _mas)`, "transfer")
assert.Nil(t, err)

assert.Equal(t, "transfer(address,uint256,bytes)", res)
assert.Equal(t, []string{"_to", "_value", "_mas"}, order)

// Mixed plain method should return nil order
res, order, err = getMethodFromAbi(`transfer(address _to,uint256, bytes _mas)`, "transfer")

assert.Nil(t, err)
assert.Equal(t, "transfer(address,uint256,bytes)", res)
assert.Nil(t, order)
}

func TestEncodeDelayedABI(t *testing.T) {
// Encode simple transferFrom, not named
res, err := EncodeDelayedABI(&delayedEncodeType{
Abi: `[{"name":"transferFrom","type":"function","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`,
Func: "transferFrom",
Args: json.RawMessage(`["0x0dc9603d4da53841C1C83f3B550C6143e60e0425","0x0dc9603d4da53841C1C83f3B550C6143e60e0425","100"]`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x23b872dd0000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000000000000000000000000000000000000000064")

// Encode simple transferFrom, named
res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `[{"name":"transferFrom","type":"function","inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}]`,
Func: "transferFrom",
Args: json.RawMessage(`{"_from": "0x0dc9603d4da53841C1C83f3B550C6143e60e0425", "_value": "100", "_to": "0x0dc9603d4da53841C1C83f3B550C6143e60e0425"}`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x23b872dd0000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000dc9603d4da53841c1c83f3b550c6143e60e04250000000000000000000000000000000000000000000000000000000000000064")

// Encode simple transferFrom, not named, passed as function
res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `transferFrom(address,address,uint256)`,
Func: "transferFrom",
Args: json.RawMessage(`["0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25","0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462","5192381927398174182391237"]`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d6462000000000000000000000000000000000000000000044b87969b06250e50bdc5")

// Encode simple transferFrom, named, passed as function
res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `transferFrom(address _from,address _to,uint256 _value)`,
Func: "transferFrom",
Args: json.RawMessage(`{"_from": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_value": "5192381927398174182391237", "_to": "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462"}`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d6462000000000000000000000000000000000000000000044b87969b06250e50bdc5")

// Encode nested bytes, passed as function
nestedEncodeType1 := &delayedEncodeType{
Abi: `transferFrom(uint256)`,
Func: "transferFrom",
Args: json.RawMessage(`["481923749816926378123"]`),
}

nestedEncodeType2 := &delayedEncodeType{
Abi: `hola(string)`,
Func: "hola",
Args: json.RawMessage(`["mundo"]`),
}

net1jsn, err := json.Marshal(nestedEncodeType1)
assert.Nil(t, err)

net2jsn, err := json.Marshal(nestedEncodeType2)
assert.Nil(t, err)

res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `[{"inputs":[{"internalType":"address","name":"addr","type":"address"},{"internalType":"bytes","name":"_arg1","type":"bytes"},{"internalType":"bytes","name":"_arg2","type":"bytes"}],"name":"caller","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_orderId","type":"bytes32"},{"internalType":"uint256","name":"_maxCost","type":"uint256"},{"internalType":"address[]","name":"_fees","type":"address[]"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"fillOrKillOrder","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_val","type":"uint256"},{"internalType":"string","name":"_data","type":"string"}],"name":"notExpired","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"s","outputs":[],"stateMutability":"nonpayable","type":"function"}]`,
Func: "caller",
Args: json.RawMessage(`{"addr": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_arg1": ` + string(net1jsn) + `, "_arg2": ` + string(net2jsn) + `}`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x8b6701df00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf25000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000002477a11f7e00000000000000000000000000000000000000000000001a2009191df61e988b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000646ce8ea55000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000056d756e646f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")

// Fail passing named args to non-named abi
res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `transferFrom(address,uint256)`,
Func: "transferFrom",
Args: json.RawMessage(`{"_from": "0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "_value": "5192381927398174182391237", "_to": "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462"}`),
})

assert.NotNil(t, err)

// Accept passing ordened args to named abi
res, err = EncodeDelayedABI(&delayedEncodeType{
Abi: `transferFrom(address _from,address _to,uint256 _value)`,
Func: "transferFrom",
Args: json.RawMessage(`["0x13915b1ea28Fd2E8197c88ff9D2422182E83bf25", "0x4Ad47F1611c78C824Ff3892c4aE1CC04637D6462", "9"]`),
})

assert.Nil(t, err)
assert.Equal(t, res, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d64620000000000000000000000000000000000000000000000000000000000000009")
}

0 comments on commit 8c74e7a

Please sign in to comment.