Skip to content

Commit

Permalink
use refactored ethkit/ethcoder abi methods
Browse files Browse the repository at this point in the history
  • Loading branch information
pkieltyka committed Oct 17, 2024
1 parent 1d7a58d commit 82a5295
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 84 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.0

toolchain go1.23.1

// replace github.com/0xsequence/ethkit => /Users/peter/Dev/0xsequence/ethkit
replace github.com/0xsequence/ethkit => /Users/peter/Dev/0xsequence/ethkit

require (
github.com/0xsequence/ethkit v1.27.7
Expand Down
187 changes: 160 additions & 27 deletions intents/intent_data_transaction_contract_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import (
"encoding/json"
"errors"
"fmt"
"reflect"
"strings"

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

type contractCallType struct {
Expand All @@ -17,20 +20,153 @@ type contractCallType struct {
}

func EncodeContractCall(data *contractCallType) (string, error) {
data.Abi = strings.TrimSpace(data.Abi)

// Get the method from the abi
method, _, err := getMethodFromAbi(data.Abi, data.Func)
method, _, argTypes, err := getMethodFromAbi(data.Abi, data.Func)
if err != nil {
return "", err
}

// Prepare the arguments, which may be nested
argStringValues, err := prepareContractCallArgs(data.Args)
if err != nil {
return "", err
}

enc := make([]string, len(data.Args))
spew.Dump("method", method)
spew.Dump("enc", argStringValues)
spew.Dump("argTypes", argTypes)

// Encode the method call
// TODO: pass argTypes so we dont have to decode again... we can just copy what we do inside of `ethcoder.AbiEncodeMethodCalldataFromStringValuesAny`
// res, err := ethcoder.AbiEncodeMethodCalldataFromStringValuesAny(method, enc)
// if err != nil {
// return "", err
// }

// TODO: we need to fix up and update ethcoder.AbiEncodeMethodCalldata to upgrade the method parsing functions... etc...

// TODO: we are calling ParseEventDef multiple times..

argValues, err := ethcoder.ABIUnmarshalStringValuesAny(argTypes, argStringValues)
if err != nil {
return "", err
}
// return AbiEncodeMethodCalldata(methodExpr, argValues)

var mabi abi.ABI
var methodName string

if len(data.Abi) > 0 && strings.Contains(data.Abi, "(") && data.Abi[len(data.Abi)-1] == ')' {
abiSig, err := ethcoder.ParseABISignature(data.Abi)
if err != nil {
return "", err
}
mabi, err = ethcoder.EventDefToABI(abiSig, true)
if err != nil {
return "", err
}
methodName = abiSig.Name
spew.Dump(mabi)
spew.Dump(argValues)
} else {
mabi, err = abi.JSON(strings.NewReader(data.Abi))
if err != nil {
return "", err
}
methodName = data.Func
}

args, err := packableArgValues(mabi, methodName, argValues)
if err != nil {
return "", err
}

packed, err := mabi.Pack(methodName, args...)
if err != nil {
return "", err
}

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

func packableArgValues(mabi abi.ABI, method string, argValues []any) ([]any, error) {
m, ok := mabi.Methods[method]
if !ok {
return nil, errors.New("method not found in abi")
}

if len(m.Inputs) != len(argValues) {
return nil, errors.New("method inputs length does not match arg values length")
}

fmt.Println("$$$$$$$$$$$$$$$$$$$")
spew.Dump(m.Inputs)

out := make([]any, len(argValues))

for i, input := range m.Inputs {
isTuple := false
typ := input.Type.String()
if len(typ) >= 2 && typ[0] == '(' && typ[len(typ)-1] == ')' {
isTuple = true
}

if !isTuple {
out[i] = argValues[i]
} else {
// build struct for the tuple, as that is what the geth abi encoder expects
// NOTE: in future we could fork or modify it if we want to avoid the need for this,
// as it means decoding tuples will be more intensive the necessary.

spew.Dump(input)

fields := []reflect.StructField{}

v, ok := argValues[i].([]any)
if !ok {
vv, ok := argValues[i].([]string)
if !ok {
return nil, errors.New("tuple arg values must be an array")
}
v = make([]any, len(vv))
for j, x := range vv {
v[j] = x
}
}

for j, vv := range v {
fields = append(fields, reflect.StructField{
Name: fmt.Sprintf("Name%d", j),
Type: reflect.TypeOf(vv),
})
}

structType := reflect.StructOf(fields)
instance := reflect.New(structType).Elem()

// String args can be used right away, but any nested
// `contractCallType` must be handled recursively
for i, arg := range data.Args {
for j, vv := range v {
instance.Field(j).Set(reflect.ValueOf(vv))
}

spew.Dump(instance.Interface())

out[i] = instance.Interface()
}
}

return out, nil
}

func prepareContractCallArgs(args []any) ([]any, error) {
var err error
out := make([]any, len(args))

for i, arg := range args {
switch arg := arg.(type) {
case string:
enc[i] = arg
case string, []string, []any:
out[i] = arg

case map[string]interface{}:
nst := arg
Expand All @@ -42,32 +178,29 @@ func EncodeContractCall(data *contractCallType) (string, error) {

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

abi, _ := nst["abi"].(string)
abi, ok := nst["abi"].(string)
if !ok {
return nil, fmt.Errorf("nested encode expects an 'abi' field")
}

enc[i], err = EncodeContractCall(&contractCallType{
out[i], err = EncodeContractCall(&contractCallType{
Abi: abi,
Func: funcName,
Args: args,
})
if err != nil {
return "", err
return nil, err
}

default:
return "", fmt.Errorf("invalid arg type")
return nil, fmt.Errorf("abi encoding fail due to invalid arg type, '%T'", arg)
}
}

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

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

// The abi may be a:
Expand All @@ -78,7 +211,7 @@ func EncodeContractCall(data *contractCallType) (string, error) {
// 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) {
func getMethodFromAbi(abi string, method string) (string, []string, []string, error) {
//
// First attempt to parse `abi` string as a plain method abi
// ie. transferFrom(address,address,uint256)
Expand All @@ -91,11 +224,11 @@ func getMethodFromAbi(abi string, method string) (string, []string, error) {
// 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)
abiSig, err := ethcoder.ParseABISignature(abi)
if err != nil {
return "", nil, err
return "", nil, nil, err
}
return eventDef.Sig, eventDef.ArgNames, nil
return abiSig.Signature, abiSig.ArgNames, abiSig.ArgTypes, nil
}

//
Expand All @@ -117,12 +250,12 @@ func getMethodFromAbi(abi string, method string) (string, []string, error) {
var abis []FunctionAbi
if strings.HasPrefix(abi, "[") {
if err := json.Unmarshal([]byte(abi), &abis); err != nil {
return "", nil, err
return "", nil, nil, err
}
} else {
var singleAbi FunctionAbi
if err := json.Unmarshal([]byte(abi), &singleAbi); err != nil {
return "", nil, err
return "", nil, nil, err
}
abis = append(abis, singleAbi)
}
Expand All @@ -136,9 +269,9 @@ func getMethodFromAbi(abi string, method string) (string, []string, error) {
paramTypes = append(paramTypes, input.Type)
order[i] = input.Name
}
return method + "(" + strings.Join(paramTypes, ",") + ")", order, nil
return method + "(" + strings.Join(paramTypes, ",") + ")", order, paramTypes, nil
}
}

return "", nil, errors.New("Method not found in ABI")
return "", nil, nil, fmt.Errorf("method not found in abi")
}
34 changes: 24 additions & 10 deletions intents/intent_data_transaction_contract_abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,54 +11,55 @@ import (

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")
res, argNames, argTypes, 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)
require.Equal(t, "transfer(address,uint256)", res)
require.Equal(t, []string{"_to", "_value"}, argNames)
require.Equal(t, []string{"address", "uint256"}, argTypes)

// From ABI, alone, as object
res, order, err = getMethodFromAbi(`{"name":"transfer","type":"function","inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}]}`, "transfer")
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")
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")
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")
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")
res, order, _, err = getMethodFromAbi(`transfer(address,uint256)`, "transfer")
assert.Nil(t, err)

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

// From plain method, with named args
res, order, err = getMethodFromAbi(`transfer(address _to,uint256 _value, bytes _mas)`, "transfer")
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")
res, order, _, err = getMethodFromAbi(`transfer(address _to,uint256, bytes _mas)`, "transfer")

assert.Nil(t, err)
assert.Equal(t, "transfer(address,uint256,bytes)", res)
Expand Down Expand Up @@ -159,4 +160,17 @@ func TestEncodeContractCall(t *testing.T) {
})
require.Nil(t, err)
require.Equal(t, "0x23b872dd00000000000000000000000013915b1ea28fd2e8197c88ff9d2422182e83bf250000000000000000000000004ad47f1611c78c824ff3892c4ae1cc04637d64620000000000000000000000000000000000000000000000000000000000000009", res)

// ...
res, err = EncodeContractCall(&contractCallType{
Abi: `fillOrder(uint256 orderId, uint256 maxCost, address[] fees, (uint256 a, address b) extra)`,
Args: []any{
"48774435471364917511246724398022004900255301025912680232738918790354204737320",
"1000000000000000000",
[]string{"0x8541D65829f98f7D71A4655cCD7B2bB8494673bF"},
[]string{"123456789", "0x1231f65f29f98e7D71A4655cCD7B2bc441211feb"},
},
})
require.Nil(t, err)
require.Equal(t, "0x326a62086bd55a2877890bd58871eefe886770a7734077a74981910a75d7b1f044b5bf280000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000075bcd150000000000000000000000001231f65f29f98e7d71a4655ccd7b2bc441211feb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000008541d65829f98f7d71a4655ccd7b2bb8494673bf", res)
}
2 changes: 1 addition & 1 deletion intents/intent_data_transaction_delayed_abi.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type delayedEncodeType struct {
// Deprecated: use EncodeContractCall instead
func EncodeDelayedABI(data *delayedEncodeType) (string, error) {
// Get the method from the abi
method, order, err := getMethodFromAbi(data.Abi, data.Func)
method, order, _, err := getMethodFromAbi(data.Abi, data.Func)
if err != nil {
return "", err
}
Expand Down
2 changes: 0 additions & 2 deletions intents/intent_data_transaction_delayed_abi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,12 @@ func TestEncodeDelayedABI(t *testing.T) {

// Encode nested bytes, passed as function

// TODO/NOTE: this makes no sense, we are making up an abi type of transferFrom just to encode a uint256
nestedEncodeType1 := &delayedEncodeType{
Abi: `transferFrom(uint256)`,
Func: "transferFrom",
Args: json.RawMessage(`["481923749816926378123"]`),
}

// TODO/NOTE: this makes no sense, we are making up a random abi just so we can encode a string..
nestedEncodeType2 := &delayedEncodeType{
Abi: `hola(string)`,
Func: "hola",
Expand Down
Loading

0 comments on commit 82a5295

Please sign in to comment.