Commit e14f8f5b authored by Mark Tyneway's avatar Mark Tyneway

op-chain-ops: wip

parent cfa5e24a
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)
**/
......@@ -6,44 +6,201 @@ package safe
import (
"encoding/json"
"fmt"
"golang.org/x/exp/maps"
"math/big"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// BatchFile represents a Safe tx-builder transaction.
type BatchFile struct {
// Batch represents a Safe tx-builder transaction.
type Batch struct {
Version string `json:"version"`
ChainID *big.Int `json:"chainId"`
CreatedAt uint64 `json:"createdAt"`
Meta BatchFileMeta `json:"meta"`
Meta BatchMeta `json:"meta"`
Transactions []BatchTransaction `json:"transactions"`
}
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
// coupled to, so attempt to search for the raw name if the top
// level name is not found to handle overloading more gracefully.
method, ok := iface.Methods[sig]
if !ok {
for _, m := range iface.Methods {
if m.RawName == sig || m.Sig == sig {
method = m
ok = true
}
}
}
if !ok {
keys := maps.Keys(iface.Methods)
methods := strings.Join(keys, ",")
return fmt.Errorf("%s not found in abi, options are %s", sig, methods)
}
if len(args) != len(method.Inputs) {
return fmt.Errorf("requires %d inputs but got %d for %s", len(method.Inputs), len(args), method.RawName)
}
contractMethod := ContractMethod{
Name: method.RawName,
Payable: method.Payable,
}
inputValues := make(map[string]string)
// 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)
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)
str, err := stringifyArg(args[i])
if err != nil {
return err
}
inputValues[input.Name] = str
}
batchTransaction := BatchTransaction{
To: to,
Value: value,
Method: contractMethod,
InputValues: inputValues,
}
b.Transactions = append(b.Transactions, batchTransaction)
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 batchFileMarshaling struct {
type batchMarshaling struct {
Version string `json:"version"`
ChainID string `json:"chainId"`
CreatedAt uint64 `json:"createdAt"`
Meta BatchFileMeta `json:"meta"`
Meta BatchMeta `json:"meta"`
Transactions []BatchTransaction `json:"transactions"`
}
// MarshalJSON will marshal a BatchFile to JSON.
func (b *BatchFile) MarshalJSON() ([]byte, error) {
return json.Marshal(batchFileMarshaling{
// MarshalJSON will marshal a Batch to JSON.
func (b *Batch) MarshalJSON() ([]byte, error) {
batch := batchMarshaling{
Version: b.Version,
ChainID: b.ChainID.String(),
CreatedAt: b.CreatedAt,
Meta: b.Meta,
Transactions: b.Transactions,
})
}
if b.ChainID != nil {
batch.ChainID = b.ChainID.String()
}
return json.Marshal(batch)
}
// UnmarshalJSON will unmarshal a BatchFile from JSON.
func (b *BatchFile) UnmarshalJSON(data []byte) error {
var bf batchFileMarshaling
// UnmarshalJSON will unmarshal a Batch from JSON.
func (b *Batch) UnmarshalJSON(data []byte) error {
var bf batchMarshaling
if err := json.Unmarshal(data, &bf); err != nil {
return err
}
......@@ -59,9 +216,9 @@ func (b *BatchFile) UnmarshalJSON(data []byte) error {
return nil
}
// BatchFileMeta contains metadata about a BatchFile. Not all
// BatchMeta contains metadata about a Batch. Not all
// of the fields are required.
type BatchFileMeta struct {
type BatchMeta struct {
TxBuilderVersion string `json:"txBuilderVersion,omitempty"`
Checksum string `json:"checksum,omitempty"`
CreatedFromSafeAddress string `json:"createdFromSafeAddress"`
......
......@@ -3,27 +3,71 @@ package safe
import (
"bytes"
"encoding/json"
"fmt"
"math/big"
"os"
"testing"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestBatchFileJSONPrepareBedrock(t *testing.T) {
testBatchFileJSON(t, "testdata/batch-prepare-bedrock.json")
func TestBatchJSONPrepareBedrock(t *testing.T) {
testBatchJSON(t, "testdata/batch-prepare-bedrock.json")
}
func TestBatchFileJSONL2OO(t *testing.T) {
testBatchFileJSON(t, "testdata/l2-output-oracle.json")
func TestBatchJSONL2OO(t *testing.T) {
testBatchJSON(t, "testdata/l2-output-oracle.json")
}
func testBatchFileJSON(t *testing.T, path string) {
func testBatchJSON(t *testing.T, path string) {
b, err := os.ReadFile(path)
require.NoError(t, err)
dec := json.NewDecoder(bytes.NewReader(b))
decoded := new(BatchFile)
decoded := new(Batch)
require.NoError(t, dec.Decode(decoded))
data, err := json.Marshal(decoded)
require.NoError(t, err)
require.JSONEq(t, string(b), string(data))
}
func TestBatchAddCall(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{},
},
}
require.NoError(t, batch.AddCall(to, value, sig, args, portalABI))
d, err := json.MarshalIndent(batch, " ", " ")
require.NoError(t, err)
fmt.Println(string(d))
}
This diff is collapsed.
{"version":"1.0","chainId":"1","createdAt":1692061927446,"meta":{"name":"Transactions Batch","description":"","txBuilderVersion":"1.16.1","createdFromSafeAddress":"0xc9D26D376dD75573E0C3247C141881F053d27Ae8","createdFromOwnerAddress":"","checksum":"0x9d01f75b6ab0cb240db3e3be44efdb96d64b2c88ef160bb13f46f279845c1a72"},"transactions":[{"to":"0x9e760aBd847E48A56b4a348Cba56Ae7267FeCE80","value":"0","data":null,"contractMethod":{"inputs":[{"components":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Types.WithdrawalTransaction","name":"_tx","type":"tuple"}],"name":"finalizeWithdrawalTransaction","payable":false},"contractInputsValues":{"_tx":"[0,\"0x0000000000000000000000000000000000000000\",\"0x0000000000000000000000000000000000000000\",0,0,\"0x\"]"}}]}
\ No newline at end of file
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