-
Notifications
You must be signed in to change notification settings - Fork 4k
feat(op-acceptance-tests): l2cm upgrade accaptance tests and fork tests on karst betanet #20668
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
0179e8a
9782ee9
a118a9c
329e54d
0a8fe52
1971c09
a12f6d0
e2eb850
81bcb09
da9943e
331ce17
96f15bc
7cd0a82
71506e6
fff70c2
d292e28
e05e520
5185926
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package karst | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/base/withdrawal" | ||
| gameTypes "github.com/ethereum-optimism/optimism/op-challenger/game/types" | ||
| "github.com/ethereum-optimism/optimism/op-devstack/presets" | ||
| "github.com/ethereum-optimism/optimism/op-devstack/sysgo" | ||
| ) | ||
|
|
||
| // TestWithdrawal_Karst creates a withdrawal from the L2StandardBridge and | ||
| // observes the full withdrawal flow, including finalization on L1. | ||
| func TestWithdrawal_Karst(gt *testing.T) { | ||
| withdrawal.TestWithdrawal(gt, gameTypes.CannonGameType, | ||
| presets.WithDeployerOptions(sysgo.WithKarstAtGenesis), | ||
| ) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,15 +10,18 @@ import { console } from "forge-std/console.sol"; | |
| // Scripts | ||
| import { ExecuteNUTBundle } from "scripts/upgrade/ExecuteNUTBundle.s.sol"; | ||
| import { GenerateNUTBundle } from "scripts/upgrade/GenerateNUTBundle.s.sol"; | ||
| import { Config } from "scripts/libraries/Config.sol"; | ||
| import { UpgradeUtils } from "scripts/libraries/UpgradeUtils.sol"; | ||
|
|
||
| // Libraries | ||
| import { LibString } from "@solady/utils/LibString.sol"; | ||
| import { Predeploys } from "src/libraries/Predeploys.sol"; | ||
| import { Preinstalls } from "src/libraries/Preinstalls.sol"; | ||
| import { DevFeatures } from "src/libraries/DevFeatures.sol"; | ||
| import { SemverComp } from "src/libraries/SemverComp.sol"; | ||
| import { Types } from "src/libraries/Types.sol"; | ||
| import { NetworkUpgradeTxns } from "src/libraries/NetworkUpgradeTxns.sol"; | ||
| import { Constants } from "src/libraries/Constants.sol"; | ||
|
|
||
| // Interfaces | ||
| import { ICrossDomainMessenger } from "interfaces/universal/ICrossDomainMessenger.sol"; | ||
|
|
@@ -90,11 +93,38 @@ contract L2ForkUpgrade_TestInit is CommonTest { | |
| } | ||
|
|
||
| /// @notice Executes the current generated NUT bundle with any fork-specific wrappers. | ||
| /// No-op when the bundle has already been applied to the forked chain. | ||
| function _executeCurrentBundle() internal virtual { | ||
| if (_isCurrentBundleAlreadyApplied()) return; | ||
| PastNUTBundles.ForkWrappers memory w = PastNUTBundles.wrappersForFork(currentFork); | ||
| PastNUTBundles.executeWithWrappers(executeScript, w.pre, _currentBundleTxns(), w.post); | ||
| } | ||
|
|
||
| /// @notice Executes the current generated NUT bundle with any fork-specific wrappers, or | ||
| /// switches to the fork after the fork if L2CM activation test is enabled. | ||
| function _executeCurrentBundleOrSwitchFork() internal { | ||
| if (isL2CMActivationTest()) { | ||
| uint256 l2BlockAfterFork = Config.l2BlockAfterFork(); | ||
| if (l2BlockAfterFork == 0) { | ||
| vm.createSelectFork(Config.l2ForkRpcUrl()); | ||
| } else { | ||
| vm.createSelectFork(Config.l2ForkRpcUrl(), l2BlockAfterFork); | ||
| } | ||
| console.log("Setup: L2 fork switched to after the fork!"); | ||
| return; | ||
| } | ||
| _executeCurrentBundle(); | ||
| } | ||
|
|
||
| /// @notice Returns true when the current bundle has already been applied to the forked chain. | ||
| /// Uses two checks: ConditionalDeployer exists (Karst ran) and this bundle's | ||
| /// L2ContractsManager was deployed at its expected address. | ||
| function _isCurrentBundleAlreadyApplied() internal view returns (bool) { | ||
| if (Predeploys.CONDITIONAL_DEPLOYER.code.length == 0) return false; | ||
| address l2cm = PastNUTBundles.extractL2CM(_currentBundleTxns(), Constants.CURRENT_BUNDLE_PATH); | ||
| return l2cm.code.length > 0; | ||
| } | ||
|
|
||
| /// @notice Copies the cached current bundle transactions from storage to memory. | ||
| function _currentBundleTxns() internal view returns (NetworkUpgradeTxns.NetworkUpgradeTxn[] memory txns_) { | ||
| uint256 len = currentBundleTxns.length; | ||
|
|
@@ -177,7 +207,7 @@ contract L2ForkUpgrade_Versions_Test is L2ForkUpgrade_TestInit { | |
| PreUpgradeVersionState memory preState = _capturePreUpgradeVersionState(); | ||
|
|
||
| // Execute bundle on forked L2 | ||
| _executeCurrentBundle(); | ||
| _executeCurrentBundleOrSwitchFork(); | ||
|
|
||
| // Verify all versions were updated | ||
| _verifyAllVersionsUpdated(preState); | ||
|
|
@@ -282,7 +312,7 @@ contract L2ForkUpgrade_Initialization_Test is L2ForkUpgrade_TestInit { | |
| PreUpgradeInitializationState memory preState = _capturePreUpgradeInitializationState(); | ||
|
|
||
| // Execute bundle on forked L2 | ||
| _executeCurrentBundle(); | ||
| _executeCurrentBundleOrSwitchFork(); | ||
|
|
||
| // Verify initialization state was preserved | ||
| _verifyInitializationState(preState); | ||
|
|
@@ -636,8 +666,8 @@ contract L2ForkUpgrade_Implementations_Test is L2ForkUpgrade_TestInit { | |
| // Skip if running with an unoptimized Foundry profile | ||
| skipIfUnoptimized(); | ||
|
|
||
| // Execute upgrade | ||
| _executeCurrentBundle(); | ||
| // Execute bundle on forked L2 | ||
| _executeCurrentBundleOrSwitchFork(); | ||
|
|
||
| // Get all upgradeable predeploys | ||
| address[] memory predeploys = Predeploys.getUpgradeablePredeploys(); | ||
|
|
@@ -688,17 +718,39 @@ contract L2ForkUpgrade_Events_Test is L2ForkUpgrade_TestInit { | |
| // Skip if running with an unoptimized Foundry profile | ||
| skipIfUnoptimized(); | ||
|
|
||
| // Skip when the bundle is already applied: Upgraded events are historical and cannot be | ||
| // replayed via vm.recordLogs() | ||
| if (_isCurrentBundleAlreadyApplied()) { | ||
| vm.skip(true); | ||
| return; | ||
| } | ||
|
|
||
| // Get StorageSetter implementation to filter out intermediate upgrade events | ||
| (address storageSetterImpl,,,) = generateScript.implementationConfigs("StorageSetter"); | ||
|
|
||
| // Start recording logs | ||
| vm.recordLogs(); | ||
|
|
||
| Vm.Log[] memory logs; | ||
| if (!isL2CMActivationTest()) { | ||
| // Start recording logs | ||
| vm.recordLogs(); | ||
| } | ||
| // Execute upgrade bundle | ||
| _executeCurrentBundle(); | ||
| _executeCurrentBundleOrSwitchFork(); | ||
|
|
||
| // Get all recorded logs | ||
| Vm.Log[] memory logs = vm.getRecordedLogs(); | ||
| if (!isL2CMActivationTest()) { | ||
| logs = vm.getRecordedLogs(); | ||
| } else { | ||
| bytes32[] memory topics = new bytes32[](1); | ||
| uint256 activationBlockNumber = Config.l2ForkBlockNumber() + 1; | ||
| topics[0] = UPGRADED_EVENT_TOPIC; | ||
| Vm.EthGetLogs[] memory ethLogs = vm.eth_getLogs( | ||
| activationBlockNumber, | ||
| activationBlockNumber, | ||
| address(0), | ||
| topics | ||
| ); | ||
| logs = _ethGetLogsToLogs(ethLogs); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently the L2CM activation path only checks the historical Can we also assert that the activation block contains the expected activation bundle txns? Otherwise missing, reordered, or corrupted upgrade txns could still be missed if the expected upgrade logs are present. |
||
| } | ||
|
|
||
| // Get all upgradeable predeploys | ||
| address[] memory predeploys = Predeploys.getUpgradeablePredeploys(); | ||
|
|
@@ -750,6 +802,97 @@ contract L2ForkUpgrade_Events_Test is L2ForkUpgrade_TestInit { | |
| assertTrue(foundEvent, string.concat("Upgraded event not found for ", name, ": ", vm.toString(predeploy))); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Converts RPC logs from `vm.eth_getLogs` into the shape returned by `vm.getRecordedLogs`. | ||
| /// @param _ethLogs The RPC logs from `vm.eth_getLogs`. | ||
| /// @return logs_ The logs in the shape returned by `vm.getRecordedLogs`. | ||
| function _ethGetLogsToLogs(Vm.EthGetLogs[] memory _ethLogs) internal pure returns (Vm.Log[] memory logs_) { | ||
| logs_ = new Vm.Log[](_ethLogs.length); | ||
| for (uint256 i = 0; i < _ethLogs.length; i++) { | ||
| logs_[i].topics = _ethLogs[i].topics; | ||
| logs_[i].data = _ethLogs[i].data; | ||
| logs_[i].emitter = _ethLogs[i].emitter; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// @title L2ForkUpgrade_ActivationBlockTxns_Test | ||
| /// @notice Verifies the activation block contains the expected NUT bundle transactions. | ||
| contract L2ForkUpgrade_ActivationBlockTxns_Test is L2ForkUpgrade_TestInit { | ||
| /// @notice Fetches the activation block via RPC and asserts that the NUT bundle transactions | ||
| /// are present in the correct order with the correct from, to, and calldata. | ||
| function test_l2ForkUpgrade_activationBlockContainsNUTBundle_succeeds() public { | ||
| if (!isL2CMActivationTest()) { | ||
| vm.skip(true); | ||
| return; | ||
| } | ||
| skipIfUnoptimized(); | ||
|
|
||
| // activationBlock is the first block after the pre-fork snapshot where the NUT bundle runs. | ||
| uint256 activationBlock = Config.l2ForkBlockNumber() + 1; | ||
| NetworkUpgradeTxns.NetworkUpgradeTxn[] memory bundleTxns = _currentBundleTxns(); | ||
|
|
||
| // setUp already selected the L2 fork, so vm.rpc uses L2_FORK_RPC_URL. | ||
| string memory blockJson = string( | ||
| vm.rpc( | ||
| "eth_getBlockByNumber", | ||
| string.concat('["0x', LibString.toHexStringNoPrefix(activationBlock), '", true]') | ||
| ) | ||
| ); | ||
|
|
||
| address[] memory froms = vm.parseJsonAddressArray(blockJson, ".transactions[*].from"); | ||
| address[] memory tos = vm.parseJsonAddressArray(blockJson, ".transactions[*].to"); | ||
| bytes[] memory inputs = vm.parseJsonBytesArray(blockJson, ".transactions[*].input"); | ||
|
|
||
| // The activation block also contains the L1 attributes deposit (and potentially other | ||
| // system transactions) before the NUT bundle. Find the bundle start by matching the | ||
| // first bundle transaction's from+to. | ||
| uint256 bundleStart = _findBundleStart(froms, tos, bundleTxns[0]); | ||
|
|
||
| assertGe( | ||
| froms.length - bundleStart, | ||
| bundleTxns.length, | ||
| "Activation block does not contain enough transactions for the full NUT bundle" | ||
| ); | ||
|
|
||
| for (uint256 i = 0; i < bundleTxns.length; i++) { | ||
| uint256 blockIdx = bundleStart + i; | ||
| assertEq( | ||
| froms[blockIdx], | ||
| bundleTxns[i].from, | ||
| string.concat("from mismatch at bundle index ", vm.toString(i), ": ", bundleTxns[i].intent) | ||
| ); | ||
| assertEq( | ||
| tos[blockIdx], | ||
| bundleTxns[i].to, | ||
| string.concat("to mismatch at bundle index ", vm.toString(i), ": ", bundleTxns[i].intent) | ||
| ); | ||
| assertEq( | ||
| keccak256(inputs[blockIdx]), | ||
| keccak256(bundleTxns[i].data), | ||
| string.concat("data mismatch at bundle index ", vm.toString(i), ": ", bundleTxns[i].intent) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Returns the block transaction index where the NUT bundle starts, identified by | ||
| /// matching the first bundle transaction's from+to pair. | ||
| function _findBundleStart( | ||
| address[] memory _froms, | ||
| address[] memory _tos, | ||
| NetworkUpgradeTxns.NetworkUpgradeTxn memory _firstBundleTxn | ||
| ) | ||
| internal | ||
| pure | ||
| returns (uint256) | ||
| { | ||
| for (uint256 i = 0; i < _froms.length; i++) { | ||
| if (_froms[i] == _firstBundleTxn.from && _tos[i] == _firstBundleTxn.to) { | ||
| return i; | ||
| } | ||
| } | ||
| revert("L2ForkUpgrade_ActivationBlockTxns_Test: NUT bundle start not found in activation block"); | ||
| } | ||
| } | ||
|
|
||
| /// @title L2ForkUpgrade_GasProfile_Test | ||
|
|
@@ -982,3 +1125,21 @@ contract L2ForkUpgrade_GasProfile_Test is L2ForkUpgrade_TestInit { | |
| _logAdjustments(measurements); | ||
| } | ||
| } | ||
|
|
||
| /// @title L2ForkUpgrade_DeterministicDeploymentProxy_Test | ||
| /// @notice Sanity check that the forked L2 has the deterministic deployment proxy preinstall. | ||
| contract L2ForkUpgrade_DeterministicDeploymentProxy_Test is CommonTest { | ||
| function setUp() public virtual override { | ||
| super.setUp(); | ||
| skipIfNotL2ForkTest("L2ForkUpgrade: deterministic deployer test requires L2 fork"); | ||
| } | ||
|
|
||
| /// @notice Arachnid's proxy must be deployed at the canonical address on forked L2 state. | ||
| function test_l2ForkUpgrade_deterministicDeploymentProxyExistence_succeeds() external view { | ||
| address proxy = Preinstalls.DeterministicDeploymentProxy; | ||
| assertNotEq(proxy.code.length, 0, "DeterministicDeploymentProxy must have code"); | ||
| assertEq( | ||
| proxy.code, Preinstalls.DeterministicDeploymentProxyCode, "unexpected DeterministicDeploymentProxy bytecode" | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs to be fixed before merge.
The justfile documents
L2_BLOCK_AFTER_FORK, but this readsL2_FORK_BLOCK_NUMBERand defaults to 0. I assume 0 is meant to preserve the existing "use latest" behaviour from the normal L2 fork setup. But activation mode passes this value directly tocreateSelectFork(url, block), so following the documented command makes the post upgrade fork select explicit block 0, not latest or the intended after fork block.Could we do one of the following:
L2_BLOCK_AFTER_FORK_executeCurrentBundleOrSwitchFork()callcreateSelectFork(url)overload when this returns 0?The pre/post activation flow can become:
L2_BLOCK_BEFORE_FORKThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in d292e28 and e05e520