diff --git a/.vscode/settings.json b/.vscode/settings.json index d8a05abe18af6..0231a7e39d7bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editorconfig.generateAuto": false, - "files.trimTrailingWhitespace": true + "files.trimTrailingWhitespace": true, + "openInGitHub.defaultBranch": "develop", + "openInGitHub.alwaysUseDefaultBranch": false, + "openInGitHub.excludeCurrentRevision": true } diff --git a/op-chain-ops/interopgen/recipe.go b/op-chain-ops/interopgen/recipe.go index 15124248da500..948007d296be8 100644 --- a/op-chain-ops/interopgen/recipe.go +++ b/op-chain-ops/interopgen/recipe.go @@ -123,8 +123,9 @@ func (r *InteropDevRecipe) hydrated() InteropDevRecipe { const defaultBlockTime = 2 type InteropDevL2Recipe struct { - ChainID uint64 - BlockTime uint64 + ChainID uint64 + BlockTime uint64 + InteropOffset uint64 } func prefundL2Accounts(l1Cfg *L1Config, l2Cfg *L2Config, addrs devkeys.Addresses) error { @@ -257,8 +258,8 @@ func (r *InteropDevL2Recipe) build(l1ChainID uint64, addrs devkeys.Addresses) (* L2GenesisGraniteTimeOffset: new(hexutil.Uint64), L2GenesisHoloceneTimeOffset: new(hexutil.Uint64), L2GenesisIsthmusTimeOffset: new(hexutil.Uint64), + L2GenesisInteropTimeOffset: (*hexutil.Uint64)(&r.InteropOffset), L2GenesisJovianTimeOffset: nil, - L2GenesisInteropTimeOffset: new(hexutil.Uint64), L1CancunTimeOffset: new(hexutil.Uint64), L1PragueTimeOffset: new(hexutil.Uint64), UseInterop: true, diff --git a/op-e2e/actions/helpers/l2_sequencer.go b/op-e2e/actions/helpers/l2_sequencer.go index 7934057c22818..399b0804a3849 100644 --- a/op-e2e/actions/helpers/l2_sequencer.go +++ b/op-e2e/actions/helpers/l2_sequencer.go @@ -249,3 +249,10 @@ func (s *L2Sequencer) ActBuildL2ToIsthmus(t Testing) { s.ActL2EmptyBlock(t) } } + +func (s *L2Sequencer) ActBuildL2ToInterop(t Testing) { + require.NotNil(t, s.RollupCfg.InteropTime, "cannot activate InteropTime when it is not scheduled") + for s.L2Unsafe().Time < *s.RollupCfg.InteropTime { + s.ActL2EmptyBlock(t) + } +} diff --git a/op-e2e/actions/interop/dsl/assertions.go b/op-e2e/actions/interop/dsl/assertions.go new file mode 100644 index 0000000000000..3742e31f8e4e4 --- /dev/null +++ b/op-e2e/actions/interop/dsl/assertions.go @@ -0,0 +1,13 @@ +package dsl + +import ( + "github.com/stretchr/testify/require" +) + +func RequireL2UnsafeNumberEquals(t require.TestingT, c *Chain, unsafeNumber uint64) { + require.Equal(t, unsafeNumber, c.Sequencer.L2Unsafe().Number) +} + +func RequireL2UnsafeHashNotZero(t require.TestingT, c *Chain) { + require.NotZero(t, c.Sequencer.L2Unsafe().Hash) +} diff --git a/op-e2e/actions/interop/dsl/dsl.go b/op-e2e/actions/interop/dsl/dsl.go index d5f2a2f4b9eae..bf76ca18fcc73 100644 --- a/op-e2e/actions/interop/dsl/dsl.go +++ b/op-e2e/actions/interop/dsl/dsl.go @@ -65,8 +65,7 @@ type InteropDSL struct { func NewInteropDSL(t helpers.Testing, opts ...setupOption) *InteropDSL { setup := SetupInterop(t, opts...) actors := setup.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) t.Logf("ChainA: %v, ChainB: %v", actors.ChainA.ChainID, actors.ChainB.ChainID) allChains := []*Chain{actors.ChainA, actors.ChainB} diff --git a/op-e2e/actions/interop/dsl/interop.go b/op-e2e/actions/interop/dsl/interop.go index 85181d6010d85..b2b755bf07aac 100644 --- a/op-e2e/actions/interop/dsl/interop.go +++ b/op-e2e/actions/interop/dsl/interop.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum/go-ethereum/common" + gethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" ) @@ -91,7 +92,9 @@ func (actors *InteropActors) PrepareChainState(t helpers.Testing) { actors.ChainA.Sequencer.ActL2PipelineFull(t) actors.ChainB.Sequencer.ActL2PipelineFull(t) t.Log("Processed!") +} +func (actors *InteropActors) VerifyInitialState(t helpers.Testing) { // Verify initial state statusA := actors.ChainA.Sequencer.SyncStatus() statusB := actors.ChainB.Sequencer.SyncStatus() @@ -99,6 +102,11 @@ func (actors *InteropActors) PrepareChainState(t helpers.Testing) { require.Equal(t, uint64(0), statusB.UnsafeL2.Number) } +func (actors *InteropActors) PrepareAndVerifyInitialState(t helpers.Testing) { + actors.PrepareChainState(t) + actors.VerifyInitialState(t) +} + // messageExpiryTime is the time in seconds that a message will be valid for on the L2 chain. // At a 2 second block time, this should be small enough to cover all events buffered in the supervisor event queue. const messageExpiryTime = 120 // 2 minutes @@ -123,6 +131,15 @@ func SetMessageExpiryTime(expiryTime uint64) setupOption { } } +func SetInteropOffsetForAllL2s(offset uint64) setupOption { + return func(recipe *interopgen.InteropDevRecipe) { + for i, l2 := range recipe.L2s { + l2.InteropOffset = offset + recipe.L2s[i] = l2 + } + } +} + // SetupInterop creates an InteropSetup to instantiate actors on, with 2 L2 chains. func SetupInterop(t helpers.Testing, opts ...setupOption) *InteropSetup { recipe := interopgen.InteropDevRecipe{ @@ -307,3 +324,17 @@ func createL2Services( Batcher: batcher, } } + +// Creates a new L2 block, submits it to L1, and mines the L1 block. +func (actors *InteropActors) ActBatchAndMine(t helpers.Testing, chains ...*Chain) { + var batches []*gethTypes.Transaction + for _, c := range chains { + c.Batcher.ActSubmitAll(t) + batches = append(batches, c.Batcher.LastSubmitted) + } + actors.L1Miner.ActL1StartBlock(12)(t) + for _, b := range batches { + actors.L1Miner.ActL1IncludeTxByHash(b.Hash())(t) + } + actors.L1Miner.ActL1EndBlock(t) +} diff --git a/op-e2e/actions/interop/emitter_contract_test.go b/op-e2e/actions/interop/emitter_contract_test.go index cf2d742cf6a1b..0bd681092a6bd 100644 --- a/op-e2e/actions/interop/emitter_contract_test.go +++ b/op-e2e/actions/interop/emitter_contract_test.go @@ -45,7 +45,7 @@ func TestEmitterContract(gt *testing.T) { actors = is.CreateActors() aliceA = setupUser(t, is, actors.ChainA, 0) aliceB = setupUser(t, is, actors.ChainB, 0) - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) emitTx = initializeEmitterContractTest(t, aliceA, actors) } diff --git a/op-e2e/actions/interop/interop_test.go b/op-e2e/actions/interop/interop_test.go index c1f917f6f79a9..302367ab9a13d 100644 --- a/op-e2e/actions/interop/interop_test.go +++ b/op-e2e/actions/interop/interop_test.go @@ -23,7 +23,7 @@ func TestFullInterop(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) // sync the supervisor, handle initial events emitted by the nodes actors.ChainA.Sequencer.SyncSupervisor(t) @@ -163,8 +163,7 @@ func TestFinality(gt *testing.T) { testFinality := func(t helpers.StatefulTesting, extraBlocks int) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) actors.Supervisor.ProcessFull(t) // Build L2 block on chain A @@ -242,8 +241,7 @@ func TestInteropLocalSafeInvalidation(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) genesisB := actors.ChainB.Sequencer.SyncStatus() // build L2 block on chain B with invalid executing message pointing to A. @@ -357,8 +355,7 @@ func TestInteropCrossSafeDependencyDelay(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) // We create a batch with some empty blocks before and after the cross-chain message, // so multiple L2 blocks are all derived from the same L1 block. actors.ChainA.Sequencer.ActL2EmptyBlock(t) @@ -439,7 +436,7 @@ func TestInteropExecutingMessageOutOfRangeLogIndex(gt *testing.T) { t := helpers.NewDefaultTesting(gt) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) aliceA := setupUser(t, is, actors.ChainA, 0) // Execute a fake log on chain A diff --git a/op-e2e/actions/interop/interop_txplan_test.go b/op-e2e/actions/interop/interop_txplan_test.go index e4f09f56d6469..6135a2f907897 100644 --- a/op-e2e/actions/interop/interop_txplan_test.go +++ b/op-e2e/actions/interop/interop_txplan_test.go @@ -73,7 +73,7 @@ func TestTxPlanDeployEventLogger(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) aliceA := setupUser(t, is, actors.ChainA, 0) @@ -401,7 +401,8 @@ func TestInitAndExecMsgSameTimestamp(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) + alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -476,8 +477,7 @@ func TestBreakTimestampInvariant(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -575,8 +575,7 @@ func TestExecMsgDifferTxIndex(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) // only unsafe head of each chain progresses in this code block var targetNum uint64 { @@ -669,8 +668,7 @@ func TestExpiredMessage(gt *testing.T) { expiryTime := uint64(6) is := dsl.SetupInterop(t, dsl.SetMessageExpiryTime(expiryTime)) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -743,7 +741,7 @@ func TestCrossPatternSameTimestamp(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -873,7 +871,7 @@ func TestCrossPatternSameTx(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -964,7 +962,7 @@ func TestCycleInTx(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) actors.ChainA.Sequencer.ActL2StartBlock(t) @@ -1047,7 +1045,7 @@ func TestCycleInBlock(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) actors.ChainA.Sequencer.ActL2StartBlock(t) @@ -1130,7 +1128,7 @@ func TestCycleAcrossChainsSameTimestamp(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) @@ -1225,7 +1223,7 @@ func TestCycleAcrossChainsSameTx(gt *testing.T) { rng := rand.New(rand.NewSource(1234)) is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) + actors.PrepareAndVerifyInitialState(t) alice := setupUser(t, is, actors.ChainA, 0) bob := setupUser(t, is, actors.ChainB, 0) diff --git a/op-e2e/actions/interop/reset_test.go b/op-e2e/actions/interop/reset_test.go index b7585bb5430a2..c377d5621f90c 100644 --- a/op-e2e/actions/interop/reset_test.go +++ b/op-e2e/actions/interop/reset_test.go @@ -16,8 +16,7 @@ func TestReset(gt *testing.T) { is := dsl.SetupInterop(t) actors := is.CreateActors() - actors.PrepareChainState(t) - + actors.PrepareAndVerifyInitialState(t) // No blocks yet status := actors.ChainA.Sequencer.SyncStatus() require.Equal(t, uint64(0), status.UnsafeL2.Number) diff --git a/op-e2e/actions/upgrades/interop_fork_test.go b/op-e2e/actions/upgrades/interop_fork_test.go new file mode 100644 index 0000000000000..5fc715df51341 --- /dev/null +++ b/op-e2e/actions/upgrades/interop_fork_test.go @@ -0,0 +1,146 @@ +package upgrades + +import ( + "testing" + + "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/interop/dsl" + "github.com/ethereum-optimism/optimism/op-e2e/actions/upgrades/utils" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-node/rollup/event" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" +) + +func TestInteropUpgrade(gt *testing.T) { + t := helpers.NewDefaultTesting(gt) + + // Run Isthmus until the interop upgrade + is := dsl.SetupInterop(t, dsl.SetInteropOffsetForAllL2s(uint64(15))) + actors := is.CreateActors() + actors.PrepareAndVerifyInitialState(t) + + //////////////////////////// + // Pre-upgrade Block Production + //////////////////////////// + + // Start op-nodes + actors.ChainA.Sequencer.ActL2PipelineFull(t) + actors.ChainB.Sequencer.ActL2PipelineFull(t) + + rollupConfigA := is.Out.L2s[actors.ChainA.ChainID.String()].RollupCfg + rollupConfigB := is.Out.L2s[actors.ChainB.ChainID.String()].RollupCfg + + // Verify Interop is not active at genesis yet + l2Head := actors.ChainA.Sequencer.L2Unsafe() + require.NotZero(t, l2Head.Hash) + require.True(t, rollupConfigA.IsIsthmus(l2Head.Time), "Isthmus should be active at genesis in chain A") + require.False(t, rollupConfigA.IsInterop(l2Head.Time), "Interop should not be active at genesis in chain A") + + l2Head = actors.ChainB.Sequencer.L2Unsafe() + require.NotZero(t, l2Head.Hash) + require.True(t, rollupConfigB.IsIsthmus(l2Head.Time), "Isthmus should be active at genesis in chain B") + require.False(t, rollupConfigB.IsInterop(l2Head.Time), "Interop should not be active at genesis in chain B") + + // Submit all and mine for both chains + actors.ChainA.Sequencer.ActL2EmptyBlock(t) + actors.ChainB.Sequencer.ActL2EmptyBlock(t) + + currentBlockHeaderA := actors.ChainA.SequencerEngine.L2Chain().CurrentBlock() + currentBlockHeaderB := actors.ChainB.SequencerEngine.L2Chain().CurrentBlock() + require.False(t, rollupConfigA.IsInterop(currentBlockHeaderA.Time), + "Interop should not be active at the first L1 inclusion block in chain A") + require.False(t, rollupConfigB.IsInterop(currentBlockHeaderB.Time), + "Interop should not be active at the first L1 inclusion block in chain B") + + // Build a few L2 blocks. We only need the L1 inclusion to advance past Interop and Interop + // shouldn't activate with L2 time. + actors.ChainA.Sequencer.ActBuildL2ToInterop(t) + actors.ChainB.Sequencer.ActBuildL2ToInterop(t) + + //////////////////////////// + // Post-upgrade Block Production + //////////////////////////// + + activationBlockHeaderA := actors.ChainA.SequencerEngine.L2Chain().CurrentBlock() + activationBlockHeaderB := actors.ChainB.SequencerEngine.L2Chain().CurrentBlock() + activationBlockIDA := eth.HeaderBlockID(activationBlockHeaderA) + activationBlockIDB := eth.HeaderBlockID(activationBlockHeaderB) + require.Equal(t, activationBlockHeaderA.Number.Uint64(), activationBlockHeaderB.Number.Uint64()) + + require.True(t, rollupConfigA.IsInteropActivationBlock(activationBlockHeaderA.Time), + "Interop should be active at the first L1 inclusion block in chain A") + require.True(t, rollupConfigB.IsInteropActivationBlock(activationBlockHeaderB.Time), + "Interop should be active at the first L1 inclusion block in chain B") + + activationBlock := actors.ChainA.SequencerEngine.L2Chain().GetBlockByHash(activationBlockHeaderA.Hash()) + activationBlockTxs := activationBlock.Transactions() + VerifyInteropContractsDeployedCorrectly(t, actors.ChainA, activationBlockTxs, activationBlockIDA) + VerifyInteropContractsDeployedCorrectly(t, actors.ChainB, activationBlockTxs, activationBlockIDB) + + actors.ActBatchAndMine(t, actors.ChainA, actors.ChainB) + + actors.ChainA.Sequencer.ActL2PipelineFull(t) + actors.ChainB.Sequencer.ActL2PipelineFull(t) + + // The node will exhaust L1 data, + // it needs the supervisor to see the L1 block first, + // and provide it to the node. + for i := 0; i < 2; i++ { + actors.ChainA.Sequencer.ActL2EventsUntil(t, event.Is[derive.ExhaustedL1Event], 100, false) + actors.ChainB.Sequencer.ActL2EventsUntil(t, event.Is[derive.ExhaustedL1Event], 100, false) + actors.Supervisor.SignalLatestL1(t) // supervisor will be aware of latest L1 + actors.ChainA.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1 + actors.ChainB.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1 + actors.ChainA.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head. + actors.ChainB.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head. + } + + actors.ChainA.Sequencer.ActL1HeadSignal(t) + actors.ChainB.Sequencer.ActL1HeadSignal(t) + + actors.ChainA.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1 + actors.ChainB.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1 + + actors.Supervisor.ProcessFull(t) + + actors.ChainA.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head. + actors.ChainB.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head. + + // Verify the sync status is correct + statusA := actors.ChainA.Sequencer.SyncStatus() + require.Equal(t, activationBlockIDA, statusA.UnsafeL2.ID()) + require.Equal(t, activationBlockIDA, statusA.CrossUnsafeL2.ID()) + require.Equal(t, activationBlockIDA, statusA.LocalSafeL2.ID()) + require.Equal(t, activationBlockIDA, statusA.SafeL2.ID()) + require.Equal(t, uint64(0), statusA.FinalizedL2.Number) + + statusB := actors.ChainB.Sequencer.SyncStatus() + require.Equal(t, activationBlockIDB, statusB.UnsafeL2.ID()) + require.Equal(t, activationBlockIDB, statusB.CrossUnsafeL2.ID()) + require.Equal(t, activationBlockIDB, statusB.LocalSafeL2.ID()) + require.Equal(t, activationBlockIDB, statusB.SafeL2.ID()) + require.Equal(t, uint64(0), statusB.FinalizedL2.Number) +} + +func VerifyInteropContractsDeployedCorrectly(t helpers.Testing, chain *dsl.Chain, activationBlockTxs []*types.Transaction, activationBlockID eth.BlockID) { + require.Len(t, activationBlockTxs, 5) // 4 upgrade txs + 1 system deposit tx + upgradeTransactions := activationBlockTxs[1:] + upgradeTransactionBytes := make([]hexutil.Bytes, len(upgradeTransactions)) + for i, tx := range upgradeTransactions { + txBytes, err := tx.MarshalBinary() + require.NoError(t, err) + upgradeTransactionBytes[i] = txBytes + } + + expectedUpgradeTransactions, err := derive.InteropNetworkUpgradeTransactions() + require.NoError(t, err) + + require.Equal(t, upgradeTransactionBytes, expectedUpgradeTransactions) + + utils.RequireContractDeployedAndProxyUpdated(t, chain, derive.CrossL2InboxAddress, predeploys.CrossL2InboxAddr, activationBlockID) + utils.RequireContractDeployedAndProxyUpdated(t, chain, derive.L2ToL2MessengerAddress, predeploys.L2toL2CrossDomainMessengerAddr, activationBlockID) +} diff --git a/op-e2e/actions/upgrades/utils/assertions.go b/op-e2e/actions/upgrades/utils/assertions.go new file mode 100644 index 0000000000000..6e59bca05ba34 --- /dev/null +++ b/op-e2e/actions/upgrades/utils/assertions.go @@ -0,0 +1,32 @@ +package utils + +import ( + "math/big" + + actionhelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" + "github.com/ethereum-optimism/optimism/op-e2e/actions/interop/dsl" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/lmittmann/w3" + "github.com/stretchr/testify/require" +) + +var ProxyImplGetterFunc = w3.MustNewFunc(`implementation()`, `address`) + +func RequireContractDeployedAndProxyUpdated(t actionhelpers.Testing, chain *dsl.Chain, implAddr common.Address, proxyAddress common.Address, activationBlockID eth.BlockID) { + code, err := chain.SequencerEngine.EthClient().CodeAt(t.Ctx(), implAddr, big.NewInt(int64(activationBlockID.Number))) + require.NoError(t, err) + require.NotEmpty(t, code, "contract should be deployed") + selector, err := ProxyImplGetterFunc.EncodeArgs() + require.NoError(t, err) + implAddrBytes, err := chain.SequencerEngine.EthClient().CallContract(t.Ctx(), ethereum.CallMsg{ + To: &proxyAddress, + Data: selector, + }, big.NewInt(int64(activationBlockID.Number))) + require.NoError(t, err) + var implAddrActual common.Address + err = ProxyImplGetterFunc.DecodeReturns(implAddrBytes, &implAddrActual) + require.NoError(t, err) + require.Equal(t, implAddr, implAddrActual) +} diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index 2260a2ebf4799..b90708d74fe72 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -132,6 +132,14 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex upgradeTxs = append(upgradeTxs, isthmus...) } + if ba.rollupCfg.IsInteropActivationBlock(nextL2Time) { + interop, err := InteropNetworkUpgradeTransactions() + if err != nil { + return nil, NewCriticalError(fmt.Errorf("failed to build interop network upgrade txs: %w", err)) + } + upgradeTxs = append(upgradeTxs, interop...) + } + l1InfoTx, err := L1InfoDepositBytes(ba.rollupCfg, sysConfig, seqNumber, l1Info, nextL2Time) if err != nil { return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err)) diff --git a/op-node/rollup/derive/batches.go b/op-node/rollup/derive/batches.go index 102145f97a4e2..861a8be953cf4 100644 --- a/op-node/rollup/derive/batches.go +++ b/op-node/rollup/derive/batches.go @@ -133,6 +133,12 @@ func checkSingularBatch(cfg *rollup.Config, log log.Logger, l1Blocks []eth.L1Blo return BatchDrop } + // Future forks that contain upgrade transactions must be added here. + if (cfg.IsInteropActivationBlock(batch.Timestamp)) && len(batch.Transactions) > 0 { + log.Warn("dropping batch with user transactions in fork activation block") + return BatchDrop + } + spec := rollup.NewChainSpec(cfg) // Check if we ran out of sequencer time drift if max := batchOrigin.Time + spec.MaxSequencerDrift(batchOrigin.Time); batch.Timestamp > max { diff --git a/op-node/rollup/derive/interop_upgrade_transactions.go b/op-node/rollup/derive/interop_upgrade_transactions.go new file mode 100644 index 0000000000000..cf79f7bc98616 --- /dev/null +++ b/op-node/rollup/derive/interop_upgrade_transactions.go @@ -0,0 +1,97 @@ +package derive + +import ( + "math/big" + + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" +) + +var ( + // CrossL2Inbox Parameters + deployCrossL2InboxSource = UpgradeDepositSource{Intent: "Interop: CrossL2Inbox Deployment"} + updateCrossL2InboxProxySource = UpgradeDepositSource{Intent: "Interop: CrossL2Inbox Proxy Update"} + crossL2InboxDeployerAddress = common.HexToAddress("0x4220000000000000000000000000000000000000") + CrossL2InboxAddress = crypto.CreateAddress(crossL2InboxDeployerAddress, 0) + crossL2InboxDeploymentBytecode = common.FromHex("0x6080604052348015600e575f80fd5b506106828061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c8063331b637f1461004357806354fd4d5014610069578063ab4d6f75146100b2575b5f80fd5b610056610051366004610512565b6100c7565b6040519081526020015b60405180910390f35b6100a56040518060400160405280600581526020017f312e302e3100000000000000000000000000000000000000000000000000000081525081565b604051610060919061053b565b6100c56100c036600461058e565b61039e565b005b5f67ffffffffffffffff801683602001511115610110576040517fd1f79e8200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b604083015163ffffffff1015610152576040517f94338eba00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b606083015167ffffffffffffffff1015610198576040517f596a19a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82516040515f916101dd91859060200160609290921b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000168252601482015260340190565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe00181528282528051602091820120878201516060890151898501515f9487018590527fffffffffffffffff00000000000000000000000000000000000000000000000060c084811b8216602c8a015283901b1660348801527fffffffff0000000000000000000000000000000000000000000000000000000060e082901b16603c88015292965090949093919291016040516020818303038152906040526102ac906105bc565b90505f85826040516020016102cb929190918252602082015260400190565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0818403018152828252805160209182012060808d01519184018190529183015291505f90606001604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe081840301815291905280516020909101207effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f0300000000000000000000000000000000000000000000000000000000000000179a9950505050505050505050565b5f6103b76103b136859003850185610601565b836100c7565b90505f6103c38261043b565b509050806103fd576040517fe3c0081600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b827f5c37832d2e8d10e346e55ad62071a6a2f9fa5130614ef2ec6617555c6f467ba78560405161042d9190610622565b60405180910390a250505050565b5f805a835491505a6103e891031115939092509050565b803573ffffffffffffffffffffffffffffffffffffffff81168114610475575f80fd5b919050565b5f60a0828403121561048a575f80fd5b60405160a0810181811067ffffffffffffffff821117156104d2577f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b6040529050806104e183610452565b8152602083013560208201526040830135604082015260608301356060820152608083013560808201525092915050565b5f8060c08385031215610523575f80fd5b61052d848461047a565b9460a0939093013593505050565b602081525f82518060208401528060208501604085015e5f6040828501015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505092915050565b5f8082840360c08112156105a0575f80fd5b60a08112156105ad575f80fd5b50919360a08501359350915050565b805160208083015191908110156105fb577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8160200360031b1b821691505b50919050565b5f60a08284031215610611575f80fd5b61061b838361047a565b9392505050565b60a0810173ffffffffffffffffffffffffffffffffffffffff61064484610452565b168252602083013560208301526040830135604083015260608301356060830152608083013560808301529291505056fea164736f6c6343000819000a") + + // L2ToL2CrossDomainMessenger Parameters + deployL2ToL2MessengerSource = UpgradeDepositSource{Intent: "Interop: L2ToL2CrossDomainMessenger Deployment"} + updateL2ToL2MessengerProxySource = UpgradeDepositSource{Intent: "Interop: L2ToL2CrossDomainMessenger Proxy Update"} + l2ToL2MessengerDeployerAddress = common.HexToAddress("0x4220000000000000000000000000000000000001") + L2ToL2MessengerAddress = crypto.CreateAddress(l2ToL2MessengerDeployerAddress, 0) + l2ToL2MessengerDeploymentBytecode = common.FromHex("0x6080604052348015600e575f80fd5b506111928061001c5f395ff3fe6080604052600436106100b8575f3560e01c80637056f41f116100715780638d1d298f1161004c5780638d1d298f14610253578063b1b1b20914610266578063ecc7042814610294575f80fd5b80637056f41f146101b65780637936cbee146101d557806382e3702d14610215575f80fd5b806352617f3c116100a157806352617f3c1461011c57806354fd4d50146101425780636b0c3c5e14610197575f80fd5b806324794462146100bc57806338ffde18146100e3575b5f80fd5b3480156100c7575f80fd5b506100d06102c8565b6040519081526020015b60405180910390f35b3480156100ee575f80fd5b506100f7610347565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100da565b348015610127575f80fd5b5061012f5f81565b60405161ffff90911681526020016100da565b34801561014d575f80fd5b5061018a6040518060400160405280600581526020017f312e322e3000000000000000000000000000000000000000000000000000000081525081565b6040516100da9190610ca9565b3480156101a2575f80fd5b506100d06101b1366004610d2b565b6103c6565b3480156101c1575f80fd5b506100d06101d0366004610da2565b6104b2565b3480156101e0575f80fd5b506101e96106e5565b6040805173ffffffffffffffffffffffffffffffffffffffff90931683526020830191909152016100da565b348015610220575f80fd5b5061024361022f366004610dfa565b60026020525f908152604090205460ff1681565b60405190151581526020016100da565b61018a610261366004610e11565b610789565b348015610271575f80fd5b50610243610280366004610dfa565b5f6020819052908152604090205460ff1681565b34801561029f575f80fd5b506001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff166100d0565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c610321576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b5f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c6103a0576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c90565b5f61040a874688888888888080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610b0c92505050565b5f8181526002602052604090205490915060ff16610454576040517f6eca2e4b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b858473ffffffffffffffffffffffffffffffffffffffff16887f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208887876040516104a093929190610e67565b60405180910390a49695505050505050565b5f4685036104ec576040517f8ed9a95d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffbdffffffffffffffffffffffffffffffffffffdd73ffffffffffffffffffffffffffffffffffffffff85160161055b576040517f4faa250900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6105856001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690565b90506105ca864683338989898080601f0160208091040260200160405190810160405280939291908181526020018383808284375f92019190915250610b0c92505050565b5f81815260026020526040812080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811790915580549294507dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909216919061063583610ed0565b91906101000a8154817dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff02191690837dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff16021790555050808573ffffffffffffffffffffffffffffffffffffffff16877f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3203388886040516106d493929190610e67565b60405180910390a450949350505050565b5f807ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c61073f576040517fbca35af600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50507fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35c907f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75c90565b60607ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5c156107e4576040517f37ed32e800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60017ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d73420000000000000000000000000000000000002361082a6020860186610f31565b73ffffffffffffffffffffffffffffffffffffffff1614610877576040517f7987c15700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73420000000000000000000000000000000000002273ffffffffffffffffffffffffffffffffffffffff1663ab4d6f758585856040516108b8929190610f4c565b6040519081900381207fffffffff0000000000000000000000000000000000000000000000000000000060e085901b1682526108f79291600401610f5b565b5f604051808303815f87803b15801561090e575f80fd5b505af1158015610920573d5f803e3d5ffd5b505050505f805f805f6109338888610b4a565b94509450945094509450468514610976576040517f31ac221100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60808901355f61098a878387878a88610b0c565b5f8181526020819052604090205490915060ff16156109d5576040517f9ca9480b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f81815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055610a158285610c13565b5f8673ffffffffffffffffffffffffffffffffffffffff163485604051610a3c9190610fb4565b5f6040518083038185875af1925050503d805f8114610a76576040519150601f19603f3d011682016040523d82523d5f602084013e610a7b565b606091505b509950905080610a8d57885189602001fd5b8186847fc270d73e26d2d39dee7ef92093555927e344e243415547ecc350b2b5385b68a28c80519060200120604051610ac891815260200190565b60405180910390a4610ada5f80610c13565b50505050505050505f7ff13569814868ede994184d5a425471fb19e869768a33421cb701a2ba3d420c0a5d9392505050565b5f868686868686604051602001610b2896959493929190610fca565b6040516020818303038152906040528051906020012090509695505050505050565b5f808080606081610b5e602082898b611020565b810190610b6b9190610dfa565b90507f382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f3208114610bc6576040517fdf1eb58600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bd460806020898b611020565b810190610be19190611047565b91975095509350610bf5876080818b611020565b810190610c0291906110a9565b969995985093965092949392505050565b817f711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee75d807fb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f35d5050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f610cbb6020830184610c5d565b9392505050565b73ffffffffffffffffffffffffffffffffffffffff81168114610ce3575f80fd5b50565b5f8083601f840112610cf6575f80fd5b50813567ffffffffffffffff811115610d0d575f80fd5b602083019150836020828501011115610d24575f80fd5b9250929050565b5f805f805f8060a08789031215610d40575f80fd5b86359550602087013594506040870135610d5981610cc2565b93506060870135610d6981610cc2565b9250608087013567ffffffffffffffff811115610d84575f80fd5b610d9089828a01610ce6565b979a9699509497509295939492505050565b5f805f8060608587031215610db5575f80fd5b843593506020850135610dc781610cc2565b9250604085013567ffffffffffffffff811115610de2575f80fd5b610dee87828801610ce6565b95989497509550505050565b5f60208284031215610e0a575f80fd5b5035919050565b5f805f83850360c0811215610e24575f80fd5b60a0811215610e31575f80fd5b5083925060a084013567ffffffffffffffff811115610e4e575f80fd5b610e5a86828701610ce6565b9497909650939450505050565b73ffffffffffffffffffffffffffffffffffffffff8416815260406020820152816040820152818360608301375f818301606090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016010192915050565b5f7dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808316818103610f27577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b6001019392505050565b5f60208284031215610f41575f80fd5b8135610cbb81610cc2565b818382375f9101908152919050565b60c081018335610f6a81610cc2565b73ffffffffffffffffffffffffffffffffffffffff1682526020848101359083015260408085013590830152606080850135908301526080938401359382019390935260a0015290565b5f82518060208501845e5f920191825250919050565b8681528560208201528460408201525f73ffffffffffffffffffffffffffffffffffffffff808616606084015280851660808401525060c060a083015261101460c0830184610c5d565b98975050505050505050565b5f808585111561102e575f80fd5b8386111561103a575f80fd5b5050820193919092039150565b5f805f60608486031215611059575f80fd5b83359250602084013561106b81610cc2565b929592945050506040919091013590565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f80604083850312156110ba575f80fd5b82356110c581610cc2565b9150602083013567ffffffffffffffff808211156110e1575f80fd5b818501915085601f8301126110f4575f80fd5b8135818111156111065761110661107c565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561114c5761114c61107c565b81604052828152886020848701011115611164575f80fd5b826020860160208301375f602084830101528095505050505050925092905056fea164736f6c6343000819000a") +) + +func InteropNetworkUpgradeTransactions() ([]hexutil.Bytes, error) { + upgradeTxns := make([]hexutil.Bytes, 0, 4) // 2 + 2 updates + + // 1. Deploy CrossL2Inbox + deployCrossL2InboxTx, err := types.NewTx(&types.DepositTx{ + SourceHash: deployCrossL2InboxSource.SourceHash(), + From: crossL2InboxDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 420_000, + IsSystemTransaction: false, + Data: crossL2InboxDeploymentBytecode, + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, deployCrossL2InboxTx) + + // 2. Update CrossL2Inbox Proxy + updateCrossL2InboxProxyTx, err := types.NewTx(&types.DepositTx{ + SourceHash: updateCrossL2InboxProxySource.SourceHash(), + From: common.Address{}, + To: &predeploys.CrossL2InboxAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(CrossL2InboxAddress), + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, updateCrossL2InboxProxyTx) + + // 3. Deploy L2ToL2CrossDomainMessenger + deployL2ToL2MessengerTx, err := types.NewTx(&types.DepositTx{ + SourceHash: deployL2ToL2MessengerSource.SourceHash(), + From: l2ToL2MessengerDeployerAddress, + To: nil, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 1_100_000, + IsSystemTransaction: false, + Data: l2ToL2MessengerDeploymentBytecode, + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, deployL2ToL2MessengerTx) + + // 4. Update L2ToL2CrossDomainMessenger Proxy + updateL2ToL2MessengerProxyTx, err := types.NewTx(&types.DepositTx{ + SourceHash: updateL2ToL2MessengerProxySource.SourceHash(), + From: common.Address{}, + To: &predeploys.L2toL2CrossDomainMessengerAddr, + Mint: big.NewInt(0), + Value: big.NewInt(0), + Gas: 50_000, + IsSystemTransaction: false, + Data: upgradeToCalldata(L2ToL2MessengerAddress), + }).MarshalBinary() + if err != nil { + return nil, err + } + upgradeTxns = append(upgradeTxns, updateL2ToL2MessengerProxyTx) + + return upgradeTxns, nil +} diff --git a/op-node/rollup/derive/interop_upgrade_transactions_test.go b/op-node/rollup/derive/interop_upgrade_transactions_test.go new file mode 100644 index 0000000000000..1ce9040221c48 --- /dev/null +++ b/op-node/rollup/derive/interop_upgrade_transactions_test.go @@ -0,0 +1,78 @@ +package derive + +import ( + "encoding/hex" + "testing" + + "github.com/ethereum-optimism/optimism/op-service/predeploys" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" +) + +func TestInteropSourcesMatchSpec(t *testing.T) { + for _, test := range []struct { + source UpgradeDepositSource + expectedHash string + }{ + { + source: deployCrossL2InboxSource, + expectedHash: "0x6e5e214f73143df8fe6f6054a3ed7eb472d373376458a9c8aecdf23475beb616", + }, + { + source: updateCrossL2InboxProxySource, + expectedHash: "0x88c6b48354c367125a59792a93a7b60ad7cd66e516157dbba16558c68a46d3cb", + }, + { + source: deployL2ToL2MessengerSource, + expectedHash: "0xf5484697c7a9a791db32a3bf0763bf2ba686c77ae7d4c0a5ee8c222a92a8dcc2", + }, + { + source: updateL2ToL2MessengerProxySource, + expectedHash: "0xe54b4d06bbcc857f41ae00e89d820339ac5ce0034aac722c817b2873e03a7e68", + }, + } { + require.Equal(t, common.HexToHash(test.expectedHash), test.source.SourceHash(), "Source hash mismatch for intent: %s", test.source.Intent) + } +} + +func TestInteropNetworkTransactions(t *testing.T) { + upgradeTxns, err := InteropNetworkUpgradeTransactions() + require.NoError(t, err) + require.Len(t, upgradeTxns, 4) + + // 1. Deploy CrossL2Inbox + sender1, tx1 := toDepositTxn(t, upgradeTxns[0]) + require.Equal(t, crossL2InboxDeployerAddress, sender1, "sender mismatch tx 1") + require.Equal(t, deployCrossL2InboxSource.SourceHash(), tx1.SourceHash(), "source hash mismatch tx 1") + require.Nil(t, tx1.To(), "to mismatch tx 1") + require.Equal(t, uint64(420000), tx1.Gas(), "gas mismatch tx 1") + require.Equal(t, crossL2InboxDeploymentBytecode, tx1.Data(), "data mismatch tx 1") + + // 2. Update CrossL2Inbox Proxy + sender2, tx2 := toDepositTxn(t, upgradeTxns[1]) + require.Equal(t, common.Address{}, sender2, "sender mismatch tx 2") + require.Equal(t, updateCrossL2InboxProxySource.SourceHash(), tx2.SourceHash(), "source hash mismatch tx 2") + require.NotNil(t, tx2.To(), "to mismatch tx 2") + require.Equal(t, predeploys.CrossL2InboxAddr, *tx2.To(), "to mismatch tx 2") + require.Equal(t, uint64(50_000), tx2.Gas(), "gas mismatch tx 2") + expectedData, _ := hex.DecodeString("3659cfe6000000000000000000000000691300f512e48b463c2617b34eef1a9f82ee7dbf") + require.Equal(t, expectedData, tx2.Data(), "data mismatch tx 2") + + // 3. Deploy L2ToL2CrossDomainMessenger + sender3, tx3 := toDepositTxn(t, upgradeTxns[2]) + require.Equal(t, l2ToL2MessengerDeployerAddress, sender3, "sender mismatch tx 3") + require.Equal(t, deployL2ToL2MessengerSource.SourceHash(), tx3.SourceHash(), "source hash mismatch tx 3") + require.Nil(t, tx3.To(), "to mismatch tx 3") + require.Equal(t, uint64(1100000), tx3.Gas(), "gas mismatch tx 3") + require.Equal(t, l2ToL2MessengerDeploymentBytecode, tx3.Data(), "data mismatch tx 3") + + // 4. Update L2ToL2CrossDomainMessenger Proxy + sender4, tx4 := toDepositTxn(t, upgradeTxns[3]) + require.Equal(t, common.Address{}, sender4, "sender mismatch tx 4") + require.Equal(t, updateL2ToL2MessengerProxySource.SourceHash(), tx4.SourceHash(), "source hash mismatch tx 4") + require.NotNil(t, tx4.To(), "to mismatch tx 4") + require.Equal(t, predeploys.L2toL2CrossDomainMessengerAddr, *tx4.To(), "to mismatch tx 4") + require.Equal(t, uint64(50_000), tx4.Gas(), "gas mismatch tx 4") + expectedData, _ = hex.DecodeString("3659cfe60000000000000000000000000d0edd0ebd0e94d218670a8de867eb5c4d37cadd") + require.Equal(t, expectedData, tx4.Data(), "data mismatch tx 4") +}