script_test.go 5.66 KB
Newer Older
1 2 3
package script

import (
4 5
	"bytes"
	"encoding/json"
6
	"fmt"
7
	"math/big"
8
	"strings"
9 10 11 12 13
	"testing"

	"github.com/holiman/uint256"
	"github.com/stretchr/testify/require"

14 15 16 17
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/crypto"
18 19 20 21 22 23 24 25 26 27 28 29 30
	"github.com/ethereum/go-ethereum/log"

	"github.com/ethereum-optimism/optimism/op-chain-ops/foundry"
	"github.com/ethereum-optimism/optimism/op-service/testlog"
)

//go:generate ./testdata/generate.sh

func TestScript(t *testing.T) {
	logger, captLog := testlog.CaptureLogger(t, log.LevelInfo)
	af := foundry.OpenArtifactsDir("./testdata/test-artifacts")

	scriptContext := DefaultContext
31
	h := NewHost(logger, af, nil, scriptContext)
32
	addr, err := h.LoadContract("ScriptExample.s.sol", "ScriptExample")
33 34 35 36
	require.NoError(t, err)

	require.NoError(t, h.EnableCheats())

37
	h.SetEnvVar("EXAMPLE_BOOL", "true")
38
	input := bytes4("run()")
39
	returnData, _, err := h.Call(scriptContext.Sender, addr, input[:], DefaultFoundryGasLimit, uint256.NewInt(0))
40
	require.NoError(t, err, "call failed: %x", string(returnData))
41
	require.NotNil(t, captLog.FindLog(testlog.NewMessageFilter("sender nonce 1")))
42 43 44 45

	require.NoError(t, h.cheatcodes.Precompile.DumpState("noop"))
	// and a second time, to see if we can revisit the host state.
	require.NoError(t, h.cheatcodes.Precompile.DumpState("noop"))
46
}
47 48 49 50 51 52 53 54 55 56 57 58 59 60

func TestScriptBroadcast(t *testing.T) {
	logger := testlog.Logger(t, log.LevelDebug)
	af := foundry.OpenArtifactsDir("./testdata/test-artifacts")

	mustEncodeCalldata := func(method, input string) []byte {
		packer, err := abi.JSON(strings.NewReader(fmt.Sprintf(`[{"type":"function","name":"%s","inputs":[{"type":"string","name":"input"}]}]`, method)))
		require.NoError(t, err)

		data, err := packer.Pack(method, input)
		require.NoError(t, err)
		return data
	}

61 62 63 64 65 66 67 68
	fooBar, err := af.ReadArtifact("ScriptExample.s.sol", "FooBar")
	require.NoError(t, err)

	expectedInitCode := bytes.Clone(fooBar.Bytecode.Object)
	// Add the contract init argument we use in the script
	expectedInitCode = append(expectedInitCode, leftPad32(big.NewInt(1234).Bytes())...)
	salt := uint256.NewInt(42).Bytes32()

69 70 71 72
	senderAddr := common.HexToAddress("0x0000000000000000000000000000000000Badc0d")
	scriptAddr := common.HexToAddress("0x5b73c5498c1e3b4dba84de0f1833c4a029d90519")
	coffeeAddr := common.HexToAddress("0x0000000000000000000000000000000000C0FFEE")
	cafeAddr := common.HexToAddress("0xcafe")
73 74
	expBroadcasts := []Broadcast{
		{
75 76 77 78 79 80 81
			From:    scriptAddr,
			To:      scriptAddr,
			Input:   mustEncodeCalldata("call1", "single_call1"),
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 23421,
			Type:    BroadcastCall,
			Nonce:   1, // first action by script (script already has a nonce of 1)
82 83
		},
		{
84 85 86 87 88 89 90
			From:    coffeeAddr,
			To:      scriptAddr,
			Input:   mustEncodeCalldata("call1", "startstop_call1"),
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 1521,
			Type:    BroadcastCall,
			Nonce:   0, // first action by 0xc0ffee
91 92
		},
		{
93 94 95 96 97 98 99
			From:    coffeeAddr,
			To:      scriptAddr,
			Input:   mustEncodeCalldata("call2", "startstop_call2"),
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 1565,
			Type:    BroadcastCall,
			Nonce:   1, // second action of 0xc0ffee
100 101
		},
		{
102 103 104 105 106 107 108
			From:    common.HexToAddress("0x1234"),
			To:      scriptAddr,
			Input:   mustEncodeCalldata("nested1", "nested"),
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 2763,
			Type:    BroadcastCall,
			Nonce:   0, // first action of 0x1234
109 110
		},
		{
111 112 113 114 115 116 117
			From:    common.HexToAddress("0x123456"),
			To:      crypto.CreateAddress(common.HexToAddress("0x123456"), 0),
			Input:   expectedInitCode,
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 39112,
			Type:    BroadcastCreate,
			Nonce:   0, // first action of 0x123456
118 119
		},
		{
120 121
			From:    DeterministicDeployerAddress,
			To:      crypto.CreateAddress2(DeterministicDeployerAddress, salt, crypto.Keccak256(expectedInitCode)),
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
			Input:   expectedInitCode,
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			Type:    BroadcastCreate2,
			GasUsed: 39112,
			Salt:    salt,
			Nonce:   0, // first action of 0xcafe
		},
		{
			From:    scriptAddr,
			To:      crypto.CreateAddress(scriptAddr, 2),
			Input:   expectedInitCode,
			Value:   (*hexutil.U256)(uint256.NewInt(0)),
			GasUsed: 39112,
			Type:    BroadcastCreate,
			Nonce:   2, // second action, on top of starting at 1.
137 138 139 140 141 142 143
		},
	}

	var broadcasts []Broadcast
	hook := func(broadcast Broadcast) {
		broadcasts = append(broadcasts, broadcast)
	}
144
	h := NewHost(logger, af, nil, DefaultContext, WithBroadcastHook(hook), WithCreate2Deployer())
145 146 147 148 149 150
	addr, err := h.LoadContract("ScriptExample.s.sol", "ScriptExample")
	require.NoError(t, err)

	require.NoError(t, h.EnableCheats())

	input := bytes4("runBroadcast()")
151
	returnData, _, err := h.Call(senderAddr, addr, input[:], DefaultFoundryGasLimit, uint256.NewInt(0))
152
	require.NoError(t, err, "call failed: %x", string(returnData))
153 154 155 156 157 158

	expected, err := json.MarshalIndent(expBroadcasts, "  ", "  ")
	require.NoError(t, err)
	got, err := json.MarshalIndent(broadcasts, "  ", "  ")
	require.NoError(t, err)
	require.Equal(t, string(expected), string(got))
159 160 161 162 163 164 165 166

	// Assert that the nonces for accounts participating in the
	// broadcast increase. The scriptAddr check is set to 3 to
	// account for the initial deployment of the contract and
	// two additional calls.
	require.EqualValues(t, 0, h.GetNonce(senderAddr))
	require.EqualValues(t, 3, h.GetNonce(scriptAddr))
	require.EqualValues(t, 2, h.GetNonce(coffeeAddr))
167 168 169
	// This is one because we still need to bump the nonce of the
	// address that will perform the send to the Create2Deployer.
	require.EqualValues(t, 1, h.GetNonce(cafeAddr))
170
}