Commit 1714c430 authored by Mark Tyneway's avatar Mark Tyneway

builder: more working

parent e14f8f5b
package safe
import (
// "github.com/ethereum-optimism/optimism/op-bindings/bindings"
)
/**
batch := new(BatchFile)
to := common.Address{}
value := new(big.Int)
sig := "foo(uint256)"
args := []any{big.NewInt(0)}
batch.AddCall(to, value, sig, args)
calldata := []byte{}
batch.AddRawCall(to, value, calldata)
**/
......@@ -8,7 +8,6 @@ import (
"fmt"
"golang.org/x/exp/maps"
"math/big"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
......@@ -25,6 +24,8 @@ type Batch struct {
Transactions []BatchTransaction `json:"transactions"`
}
// AddCall will add a call to the batch. After a series of calls are
// added to the batch, it can be serialized to JSON.
func (b *Batch) AddCall(to common.Address, value *big.Int, sig string, args []any, iface abi.ABI) error {
// Attempt to pull out the signature from the top level methods.
// The abi package uses normalization that we do not want to be
......@@ -45,7 +46,14 @@ func (b *Batch) AddCall(to common.Address, value *big.Int, sig string, args []an
return fmt.Errorf("%s not found in abi, options are %s", sig, methods)
}
if len(args) != len(method.Inputs) {
size := 0
for _, input := range method.Inputs {
if err := countArgs(&size, input); err != nil {
return err
}
}
if len(args) != len(method.Inputs) && len(args) != size {
return fmt.Errorf("requires %d inputs but got %d for %s", len(method.Inputs), len(args), method.RawName)
}
......@@ -55,37 +63,14 @@ func (b *Batch) AddCall(to common.Address, value *big.Int, sig string, args []an
}
inputValues := make(map[string]string)
contractInputs := make([]ContractInput, 0)
// TODO: refactor this to be recursive
// it should return a ContractInput and take an Input in
for i, input := range method.Inputs {
inputType, err := stringifyType(input.Type)
contractInput, err := createContractInput(input, contractInputs)
if err != nil {
return err
}
internalType := input.Type.String()
if inputType == "tuple" {
internalType = input.Type.TupleRawName
}
components := make([]ContractInput, len(input.Type.TupleElems))
for j, elem := range input.Type.TupleElems {
str := input.Type.TupleRawNames[j]
components[j] = ContractInput{
InternalType: elem.String(),
Name: str,
Type: elem.String(),
}
}
contractInput := ContractInput{
InternalType: internalType,
Name: input.Name,
Type: inputType,
Components: components,
}
contractMethod.Inputs = append(contractMethod.Inputs, contractInput)
contractMethod.Inputs = append(contractMethod.Inputs, contractInput...)
str, err := stringifyArg(args[i])
if err != nil {
......@@ -94,9 +79,15 @@ func (b *Batch) AddCall(to common.Address, value *big.Int, sig string, args []an
inputValues[input.Name] = str
}
data, err := method.Inputs.PackValues(args)
if err != nil {
return err
}
batchTransaction := BatchTransaction{
To: to,
Value: value,
Data: data,
Method: contractMethod,
InputValues: inputValues,
}
......@@ -106,75 +97,6 @@ func (b *Batch) AddCall(to common.Address, value *big.Int, sig string, args []an
return nil
}
func stringifyType(t abi.Type) (string, error) {
switch t.T {
case abi.TupleTy:
return "tuple", nil
case abi.BoolTy:
return "bool", nil
case abi.AddressTy:
return "address", nil
case abi.UintTy:
return t.String(), nil
case abi.IntTy:
return t.String(), nil
default:
return "", fmt.Errorf("unknown type: %T", t.T)
}
}
func stringifyArg(argument any) (string, error) {
switch arg := argument.(type) {
case common.Address:
return arg.String(), nil
case *common.Address:
return arg.String(), nil
case *big.Int:
return arg.String(), nil
case big.Int:
return arg.String(), nil
case bool:
if arg {
return "true", nil
}
return "false", nil
case int64:
return strconv.FormatInt(arg, 10), nil
case int32:
return strconv.FormatInt(int64(arg), 10), nil
case int16:
return strconv.FormatInt(int64(arg), 10), nil
case int8:
return strconv.FormatInt(int64(arg), 10), nil
case int:
return strconv.FormatInt(int64(arg), 10), nil
case uint64:
return strconv.FormatUint(uint64(arg), 10), nil
case uint32:
return strconv.FormatUint(uint64(arg), 10), nil
case uint16:
return strconv.FormatUint(uint64(arg), 10), nil
case uint8:
return strconv.FormatUint(uint64(arg), 10), nil
case uint:
return strconv.FormatUint(uint64(arg), 10), nil
case []byte:
return hexutil.Encode(arg), nil
case []any:
ret := make([]string, len(arg))
for i, v := range arg {
str, err := stringifyArg(v)
if err != nil {
return "", err
}
ret[i] = str
}
return "[" + strings.Join(ret, ",") + "]", nil
default:
return "", fmt.Errorf("unknown type as argument: %T", arg)
}
}
// bathcFileMarshaling is a helper type used for JSON marshaling.
type batchMarshaling struct {
Version string `json:"version"`
......
......@@ -8,6 +8,8 @@ import (
"os"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
......@@ -33,40 +35,53 @@ func testBatchJSON(t *testing.T, path string) {
require.JSONEq(t, string(b), string(data))
}
func TestBatchAddCall(t *testing.T) {
func TestBatchAddCallFinalizeWithdrawalTransaction(t *testing.T) {
file, err := os.ReadFile("testdata/portal-abi.json")
require.NoError(t, err)
portalABI, err := abi.JSON(bytes.NewReader(file))
require.NoError(t, err)
sig := "finalizeWithdrawalTransaction"
argument := []any{
bindings.TypesWithdrawalTransaction{
Nonce: big.NewInt(0),
Sender: common.Address{19: 0x01},
Target: common.Address{19: 0x02},
Value: big.NewInt(1),
GasLimit: big.NewInt(2),
Data: []byte{},
},
}
batch := new(Batch)
to := common.Address{19: 0x01}
value := big.NewInt(222)
require.NoError(t, batch.AddCall(to, value, sig, argument, portalABI))
d, err := json.MarshalIndent(batch, " ", " ")
require.NoError(t, err)
fmt.Println(string(d))
}
func TestBatchAddCallDespostTransaction(t *testing.T) {
file, err := os.ReadFile("testdata/portal-abi.json")
require.NoError(t, err)
portalABI, err := abi.JSON(bytes.NewReader(file))
require.NoError(t, err)
batch := new(Batch)
to := common.Address{19: 0x01}
value := big.NewInt(222)
/*
sig := "depositTransaction"
args := []any{
common.Address{01},
big.NewInt(2),
uint64(100),
false,
[]byte{},
}
*/
sig := "finalizeWithdrawalTransaction"
args := []any{
[]any{
big.NewInt(0),
common.Address{19: 0x01},
common.Address{19: 0x02},
big.NewInt(1),
big.NewInt(2),
[]byte{},
},
sig := "depositTransaction"
argument := []any{
common.Address{01},
big.NewInt(2),
uint64(100),
false,
[]byte{},
}
require.NoError(t, batch.AddCall(to, value, sig, args, portalABI))
require.NoError(t, batch.AddCall(to, value, sig, argument, portalABI))
d, err := json.MarshalIndent(batch, " ", " ")
require.NoError(t, err)
fmt.Println(string(d))
......
package safe
import (
"fmt"
"math/big"
"reflect"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// stringifyArg converts a Go type to a string that is representable by ABI.
// To do so, this function must be recursive to handle nested tuples.
func stringifyArg(argument any) (string, error) {
switch arg := argument.(type) {
case common.Address:
return arg.String(), nil
case *common.Address:
return arg.String(), nil
case *big.Int:
return arg.String(), nil
case big.Int:
return arg.String(), nil
case bool:
if arg {
return "true", nil
}
return "false", nil
case int64:
return strconv.FormatInt(arg, 10), nil
case int32:
return strconv.FormatInt(int64(arg), 10), nil
case int16:
return strconv.FormatInt(int64(arg), 10), nil
case int8:
return strconv.FormatInt(int64(arg), 10), nil
case int:
return strconv.FormatInt(int64(arg), 10), nil
case uint64:
return strconv.FormatUint(uint64(arg), 10), nil
case uint32:
return strconv.FormatUint(uint64(arg), 10), nil
case uint16:
return strconv.FormatUint(uint64(arg), 10), nil
case uint8:
return strconv.FormatUint(uint64(arg), 10), nil
case uint:
return strconv.FormatUint(uint64(arg), 10), nil
case []byte:
return hexutil.Encode(arg), nil
case []any:
ret := make([]string, len(arg))
for i, v := range arg {
str, err := stringifyArg(v)
if err != nil {
return "", err
}
ret[i] = str
}
return "[" + strings.Join(ret, ",") + "]", nil
default:
typ := reflect.TypeOf(argument)
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
if typ.Kind() == reflect.Struct {
v := reflect.ValueOf(argument)
numField := v.NumField()
ret := make([]string, numField)
for i := 0; i < numField; i++ {
val := v.Field(i).Interface()
str, err := stringifyArg(val)
if err != nil {
return "", err
}
ret[i] = str
}
return "[" + strings.Join(ret, ",") + "]", nil
}
return "", fmt.Errorf("unknown type as argument: %T", arg)
}
}
// countArgs will recursively count the number of arguments in an abi.Argument.
func countArgs(total *int, input abi.Argument) error {
for i, elem := range input.Type.TupleElems {
e := *elem
*total++
arg := abi.Argument{
Name: input.Type.TupleRawNames[i],
Type: e,
}
return countArgs(total, arg)
}
return nil
}
// createContractInput converts an abi.Argument to one or more ContractInputs.
func createContractInput(input abi.Argument, inputs []ContractInput) ([]ContractInput, error) {
inputType, err := stringifyType(input.Type)
if err != nil {
return nil, err
}
// TODO: could probably do better than string comparison?
internalType := input.Type.String()
if inputType == "tuple" {
internalType = input.Type.TupleRawName
}
components := make([]ContractInput, 0)
for i, elem := range input.Type.TupleElems {
e := *elem
arg := abi.Argument{
Name: input.Type.TupleRawNames[i],
Type: e,
}
component, err := createContractInput(arg, inputs)
if err != nil {
return nil, err
}
components = append(components, component...)
}
contractInput := ContractInput{
InternalType: internalType,
Name: input.Name,
Type: inputType,
Components: components,
}
inputs = append(inputs, contractInput)
return inputs, nil
}
// stringifyType turns an abi.Type into a string
func stringifyType(t abi.Type) (string, error) {
switch t.T {
case abi.TupleTy:
return "tuple", nil
case abi.BoolTy:
return t.String(), nil
case abi.AddressTy:
return t.String(), nil
case abi.UintTy:
return t.String(), nil
case abi.IntTy:
return t.String(), nil
case abi.StringTy:
return t.String(), nil
case abi.BytesTy:
return t.String(), nil
default:
return "", fmt.Errorf("unknown type: %d", t.T)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment