Commit 0e1b8498 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6784 from ethereum-optimism/chain-ops/safe-impl

op-chain-ops: tx-builder safe types
parents c7215d44 4dc5695b
// Package safe contains types for working with Safe smart contract wallets. These are used to
// build batch transactions for the tx-builder app. The types are based on
// https://github.com/safe-global/safe-react-apps/blob/development/apps/tx-builder/src/typings/models.ts.
package safe
import (
"encoding/json"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
// BatchFile represents a Safe tx-builder transaction.
type BatchFile struct {
Version string `json:"version"`
ChainID *big.Int `json:"chainId"`
CreatedAt uint64 `json:"createdAt"`
Meta BatchFileMeta `json:"meta"`
Transactions []BatchTransaction `json:"transactions"`
}
// bathcFileMarshaling is a helper type used for JSON marshaling.
type batchFileMarshaling struct {
Version string `json:"version"`
ChainID string `json:"chainId"`
CreatedAt uint64 `json:"createdAt"`
Meta BatchFileMeta `json:"meta"`
Transactions []BatchTransaction `json:"transactions"`
}
// MarshalJSON will marshal a BatchFile to JSON.
func (b *BatchFile) MarshalJSON() ([]byte, error) {
return json.Marshal(batchFileMarshaling{
Version: b.Version,
ChainID: b.ChainID.String(),
CreatedAt: b.CreatedAt,
Meta: b.Meta,
Transactions: b.Transactions,
})
}
// UnmarshalJSON will unmarshal a BatchFile from JSON.
func (b *BatchFile) UnmarshalJSON(data []byte) error {
var bf batchFileMarshaling
if err := json.Unmarshal(data, &bf); err != nil {
return err
}
b.Version = bf.Version
chainId, ok := new(big.Int).SetString(bf.ChainID, 10)
if !ok {
return fmt.Errorf("cannot set chainId to %s", bf.ChainID)
}
b.ChainID = chainId
b.CreatedAt = bf.CreatedAt
b.Meta = bf.Meta
b.Transactions = bf.Transactions
return nil
}
// BatchFileMeta contains metadata about a BatchFile. Not all
// of the fields are required.
type BatchFileMeta struct {
TxBuilderVersion string `json:"txBuilderVersion,omitempty"`
Checksum string `json:"checksum,omitempty"`
CreatedFromSafeAddress string `json:"createdFromSafeAddress"`
CreatedFromOwnerAddress string `json:"createdFromOwnerAddress"`
Name string `json:"name"`
Description string `json:"description"`
}
// BatchTransaction represents a single call in a tx-builder transaction.
type BatchTransaction struct {
To common.Address `json:"to"`
Value *big.Int `json:"value"`
Data []byte `json:"data"`
Method ContractMethod `json:"contractMethod"`
InputValues map[string]string `json:"contractInputsValues"`
}
// UnmarshalJSON will unmarshal a BatchTransaction from JSON.
func (b *BatchTransaction) UnmarshalJSON(data []byte) error {
var bt batchTransactionMarshaling
if err := json.Unmarshal(data, &bt); err != nil {
return err
}
b.To = common.HexToAddress(bt.To)
b.Value = new(big.Int).SetUint64(bt.Value)
b.Method = bt.Method
b.InputValues = bt.InputValues
return nil
}
// MarshalJSON will marshal a BatchTransaction to JSON.
func (b *BatchTransaction) MarshalJSON() ([]byte, error) {
batch := batchTransactionMarshaling{
To: b.To.Hex(),
Value: b.Value.Uint64(),
Method: b.Method,
InputValues: b.InputValues,
}
if len(b.Data) != 0 {
hex := hexutil.Encode(b.Data)
batch.Data = &hex
}
return json.Marshal(batch)
}
// batchTransactionMarshaling is a helper type used for JSON marshaling.
type batchTransactionMarshaling struct {
To string `json:"to"`
Value uint64 `json:"value,string"`
Data *string `json:"data"`
Method ContractMethod `json:"contractMethod"`
InputValues map[string]string `json:"contractInputsValues"`
}
// ContractMethod represents a method call in a tx-builder transaction.
type ContractMethod struct {
Inputs []ContractInput `json:"inputs"`
Name string `json:"name"`
Payable bool `json:"payable"`
}
// ContractInput represents an input to a contract method.
type ContractInput struct {
InternalType string `json:"internalType"`
Name string `json:"name"`
Type string `json:"type"`
Components []ContractInput `json:"components,omitempty"`
}
package safe
import (
"bytes"
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/require"
)
func TestBatchFileJSONPrepareBedrock(t *testing.T) {
testBatchFileJSON(t, "testdata/batch-prepare-bedrock.json")
}
func TestBatchFileJSONL2OO(t *testing.T) {
testBatchFileJSON(t, "testdata/l2-output-oracle.json")
}
func testBatchFileJSON(t *testing.T, path string) {
b, err := os.ReadFile(path)
require.NoError(t, err)
dec := json.NewDecoder(bytes.NewReader(b))
decoded := new(BatchFile)
require.NoError(t, dec.Decode(decoded))
data, err := json.Marshal(decoded)
require.NoError(t, err)
require.JSONEq(t, string(b), string(data))
}
{"version":"1.0","chainId":"1","createdAt":1683299982633,"meta":{"name":"Transactions Batch","description":"","txBuilderVersion":"1.13.3","createdFromSafeAddress":"0xAB23dE0DbE0aedF356af3F815c8B47D88575D82d","createdFromOwnerAddress":"","checksum":"0x3be12fb2a12e07f516c3895194f8418c6640f27fb3324e4dc06dce337b7ae7c3"},"transactions":[{"to":"0x09AA72510eE2e1c705Dc4e2114b025a12E116bb8","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","payable":false},"contractInputsValues":{"newOwner":"0x3C0dD22068a69433938097ad335Cb44a9DBf5c1A"}},{"to":"0x20835fbB5Dcb9B9c3074C0780bB07790a7525f41","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","payable":false},"contractInputsValues":{"newOwner":"0x3C0dD22068a69433938097ad335Cb44a9DBf5c1A"}},{"to":"0xcAC4CDD0C2D87e65710C87dE3955974d6a0b6940","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwner","payable":false},"contractInputsValues":{"_owner":"0x3C0dD22068a69433938097ad335Cb44a9DBf5c1A"}},{"to":"0xE1229AbA7DC7e74C9995254bbaa40bedDB0B8B4d","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"changeAdmin","payable":false},"contractInputsValues":{"_admin":"0x3C0dD22068a69433938097ad335Cb44a9DBf5c1A"}}]}
\ No newline at end of file
{"version":"1.0","chainId":"1","createdAt":1691808995527,"meta":{"name":"Transactions Batch","description":"","txBuilderVersion":"1.16.1","createdFromSafeAddress":"0xc9D26D376dD75573E0C3247C141881F053d27Ae8","createdFromOwnerAddress":"","checksum":"0x2a88db9ce20d2eb5a80910842e9e94d5870497af45986a6c1c7e2c91d15e34f0"},"transactions":[{"to":"0xE5FF3b57695079f808a24256734483CD3889fA9E","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"bytes32","name":"_outputRoot","type":"bytes32"},{"internalType":"uint256","name":"_l2BlockNumber","type":"uint256"},{"internalType":"bytes32","name":"_l1BlockHash","type":"bytes32"},{"internalType":"uint256","name":"_l1BlockNumber","type":"uint256"}],"name":"proposeL2Output","payable":true},"contractInputsValues":{"_outputRoot":"0x5398552529cbd710f485e297bcf15233b8475bdad43280c99334f65a1d4278ff","_l2BlockNumber":"0","_l1BlockHash":"0x01f814a4547c01c18c0eb8b96cff19bc5dc83b1d2d8a8bbb03206587f594c80a","_l1BlockNumber":"1"}},{"to":"0xE5FF3b57695079f808a24256734483CD3889fA9E","value":"0","data":null,"contractMethod":{"inputs":[{"internalType":"uint256","name":"_l2OutputIndex","type":"uint256"}],"name":"deleteL2Outputs","payable":false},"contractInputsValues":{"_l2OutputIndex":"2"}}]}
\ 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