• Mark Tyneway's avatar
    op-chain-ops: option for skipping calldata encoding · 2fe1deab
    Mark Tyneway authored
    Adds a field to the safe `Batch` struct so that it can
    optionally not include the calldata serialization in the
    JSON that it serializes to. This is because the Safe UI
    will not produce any human readable output when the JSON
    includes the calldata, it ignores the human readable ABI
    and arguments. In some cases, we may want to rely on the
    UI more so this gives the option to do so.
    2fe1deab
batch_test.go 3.94 KB
package safe

import (
	"bytes"
	"encoding/json"
	"errors"
	"math/big"
	"os"
	"testing"

	"github.com/ethereum-optimism/optimism/op-bindings/bindings"

	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"

	"github.com/stretchr/testify/require"
)

func TestBatchJSONPrepareBedrock(t *testing.T) {
	testBatchJSON(t, "testdata/batch-prepare-bedrock.json")
}

func TestBatchJSONL2OO(t *testing.T) {
	testBatchJSON(t, "testdata/l2-output-oracle.json")
}

func testBatchJSON(t *testing.T, path string) {
	b, err := os.ReadFile(path)
	require.NoError(t, err)
	dec := json.NewDecoder(bytes.NewReader(b))
	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))
}

// TestBatchAddCallFinalizeWithdrawalTransaction ensures that structs can be serialized correctly.
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))
	require.NoError(t, batch.Check())
	require.Equal(t, batch.Transactions[0].Signature(), "finalizeWithdrawalTransaction((uint256,address,address,uint256,uint256,bytes))")

	expected, err := os.ReadFile("testdata/finalize-withdrawal-tx.json")
	require.NoError(t, err)

	serialized, err := json.Marshal(batch)
	require.NoError(t, err)
	require.JSONEq(t, string(expected), string(serialized))
}

// TestBatchAddCallDespostTransaction ensures that simple calls can be serialized correctly.
func TestBatchAddCallDespositTransaction(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"
	argument := []any{
		common.Address{01},
		big.NewInt(2),
		uint64(100),
		false,
		[]byte{},
	}

	require.NoError(t, batch.AddCall(to, value, sig, argument, &portalABI))
	require.NoError(t, batch.Check())
	require.Equal(t, batch.Transactions[0].Signature(), "depositTransaction(address,uint256,uint64,bool,bytes)")

	expected, err := os.ReadFile("testdata/deposit-tx.json")
	require.NoError(t, err)

	serialized, err := json.Marshal(batch)
	require.NoError(t, err)
	require.JSONEq(t, string(expected), string(serialized))
}

// TestBatchCheck checks for the various failure cases of Batch.Check
// as well as a simple check for a valid batch.
func TestBatchCheck(t *testing.T) {
	cases := []struct {
		name string
		bt   BatchTransaction
		err  error
	}{
		{
			name: "bad-input-count",
			bt: BatchTransaction{
				Method: ContractMethod{},
				InputValues: map[string]string{
					"foo": "bar",
				},
			},
			err: errors.New("expected 0 inputs but got 1"),
		},
		{
			name: "bad-calldata-too-small",
			bt: BatchTransaction{
				Data: []byte{0x01},
			},
			err: errors.New("must have at least 4 bytes of calldata, got 1"),
		},
		{
			name: "bad-calldata-mismatch",
			bt: BatchTransaction{
				Data: []byte{0x01, 0x02, 0x03, 0x04},
				Method: ContractMethod{
					Name: "foo",
				},
			},
			err: errors.New("data does not match signature"),
		},
		{
			name: "good-calldata",
			bt: BatchTransaction{
				Data: []byte{0xc2, 0x98, 0x55, 0x78},
				Method: ContractMethod{
					Name: "foo",
				},
			},
			err: nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			require.Equal(t, tc.err, tc.bt.Check())
		})
	}
}