-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |