Skip to content
Merged
113 changes: 113 additions & 0 deletions packages/contracts-bedrock/test/L1/opcm/OPContractsManagerV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VmSafe } from "forge-std/Vm.sol";
import { CommonTest } from "test/setup/CommonTest.sol";
import { DisputeGames } from "test/setup/DisputeGames.sol";
import { PastUpgrades } from "test/setup/PastUpgrades.sol";
import { BatchUpgrader } from "test/L1/opcm/helpers/BatchUpgrader.sol";

// Libraries
import { Config } from "scripts/libraries/Config.sol";
Expand Down Expand Up @@ -728,6 +729,118 @@ contract OPContractsManagerV2_Upgrade_Test is OPContractsManagerV2_Upgrade_TestI
"permissioned prestate not updated"
);
}

/// @notice Tests that multiple upgrade operations (15 chains) can be executed within a single transaction.
/// This enforces the OPCMV2 invariant that approximately 15 upgrade operations should be
/// executable in one transaction.
Comment thread
maurelian marked this conversation as resolved.
Outdated
function test_batchUpgrade_multipleChains_succeeds() public {
uint256 numberOfChains = 15;

// 1. Deploy BatchUpgrader helper contract.
BatchUpgrader batchUpgrader = new BatchUpgrader(opcmV2);

// 2. Set up base configuration for deploying chains.
IOPContractsManagerV2.FullConfig memory baseConfig;
baseConfig.superchainConfig = superchainConfig;
baseConfig.proxyAdminOwner = address(batchUpgrader);
baseConfig.systemConfigOwner = makeAddr("systemConfigOwner");
baseConfig.batcher = makeAddr("batcher");
baseConfig.unsafeBlockSigner = makeAddr("unsafeBlockSigner");
baseConfig.startingAnchorRoot = Proposal({ root: Hash.wrap(bytes32(hex"1234")), l2SequenceNumber: 123 });
baseConfig.startingRespectedGameType = GameTypes.PERMISSIONED_CANNON;
baseConfig.basefeeScalar = 1368;
baseConfig.blobBasefeeScalar = 801949;
baseConfig.gasLimit = 60_000_000;
baseConfig.resourceConfig = IResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});

// Set up dispute game configs.
address initialChallenger = DisputeGames.permissionedGameChallenger(disputeGameFactory);
address initialProposer = DisputeGames.permissionedGameProposer(disputeGameFactory);
baseConfig.disputeGameConfigs = new IOPContractsManagerUtils.DisputeGameConfig[](3);
baseConfig.disputeGameConfigs[0] = IOPContractsManagerUtils.DisputeGameConfig({
enabled: true,
initBond: DEFAULT_DISPUTE_GAME_INIT_BOND,
gameType: GameTypes.CANNON,
gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonPrestate }))
});
baseConfig.disputeGameConfigs[1] = IOPContractsManagerUtils.DisputeGameConfig({
enabled: true,
initBond: DEFAULT_DISPUTE_GAME_INIT_BOND,
gameType: GameTypes.PERMISSIONED_CANNON,
gameArgs: abi.encode(
IOPContractsManagerUtils.PermissionedDisputeGameConfig({
absolutePrestate: cannonPrestate,
proposer: initialProposer,
challenger: initialChallenger
})
)
});
baseConfig.disputeGameConfigs[2] = IOPContractsManagerUtils.DisputeGameConfig({
enabled: true,
initBond: DEFAULT_DISPUTE_GAME_INIT_BOND,
gameType: GameTypes.CANNON_KONA,
gameArgs: abi.encode(IOPContractsManagerUtils.FaultDisputeGameConfig({ absolutePrestate: cannonKonaPrestate }))
});

// 3. Deploy 5 separate chains using opcmV2.deploy().
IOPContractsManagerV2.ChainContracts[] memory chains =
new IOPContractsManagerV2.ChainContracts[](numberOfChains);
for (uint256 i = 0; i < numberOfChains; i++) {
IOPContractsManagerV2.FullConfig memory config = baseConfig;
config.saltMixer = string.concat("chain-", vm.toString(i));
config.l2ChainId = 1000 + i;
chains[i] = opcmV2.deploy(config);
}

// 4. Prepare upgrade inputs for each chain.
IOPContractsManagerV2.UpgradeInput[] memory upgradeInputs =
new IOPContractsManagerV2.UpgradeInput[](numberOfChains);
for (uint256 i = 0; i < numberOfChains; i++) {
upgradeInputs[i] = IOPContractsManagerV2.UpgradeInput({
systemConfig: chains[i].systemConfig,
disputeGameConfigs: baseConfig.disputeGameConfigs,
extraInstructions: new IOPContractsManagerUtils.ExtraInstruction[](0)
});
}

// 5. Execute batch upgrade - all 5 upgrades in a single transaction.
batchUpgrader.batchUpgrade(upgradeInputs);
VmSafe.Gas memory gas = vm.lastCallGas();

// 6. Verify that the upgrade gas usage is less than the EIP-7825 gas limit.
// See https://eip.tools/eip/eip-7825.md for more details.
// The upgradeGasBuffer amount below is an approximation of the overhead that is required
// to execute a call to Safe.executeTransaction() prior to the call to IOPContractsManagerV2.upgrade().
// The approximate value of 65,000 gas, was taken from a previous upgrade transaction on OP Mainnet:
// https://dashboard.tenderly.co/oplabs/op-mainnet/tx/0x9b9aa2d8e857e1a28e55b124e931eac706b3ae04c1b33ba949f0366359860993/gas-usage?trace=0.1.7
uint256 fusakaLimit = 2 ** 24;
uint256 upgradeGasBuffer = 65_000;
assertLt(gas.gasTotalUsed, fusakaLimit - upgradeGasBuffer, "Upgrade exceeds gas target");

// 7. Verify all chains upgraded successfully.
for (uint256 i = 0; i < numberOfChains; i++) {
ISystemConfig systemConfig = chains[i].systemConfig;

// Verify OPCM release version was updated.
string memory version = systemConfig.lastUsedOPCMVersion();
assertEq(version, opcmV2.version(), string.concat("Chain ", vm.toString(i), " version mismatch"));

// Verify SystemConfig implementation was upgraded.
address impl = EIP1967Helper.getImplementation(address(systemConfig));
assertEq(
impl,
opcmV2.implementations().systemConfigImpl,
string.concat("Chain ", vm.toString(i), " SystemConfig impl not upgraded")
);
}
}
}

/// @title OPContractsManagerV2_IsPermittedUpgradeSequence_Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import { IOPContractsManagerV2 } from "interfaces/L1/opcm/IOPContractsManagerV2.sol";

/// @title BatchUpgrader
/// @notice Helper contract for testing that multiple upgrade operations can be executed
/// within a single transaction. Used to enforce the OPCMV2 invariant that
/// approximately 5 upgrade operations should be executable in one transaction.
contract BatchUpgrader {
/// @notice The OPContractsManagerV2 instance to use for upgrades.
IOPContractsManagerV2 public immutable opcm;

/// @param _opcm The OPContractsManagerV2 contract address.
constructor(IOPContractsManagerV2 _opcm) {
opcm = _opcm;
}

/// @notice Executes 5 upgrade operations sequentially in a single transaction.
/// @param _inputs Array of exactly 5 upgrade inputs, one per chain to upgrade.
Comment thread
maurelian marked this conversation as resolved.
Outdated
function batchUpgrade(IOPContractsManagerV2.UpgradeInput[] memory _inputs) external {
for (uint256 i = 0; i < _inputs.length; i++) {
(bool success, bytes memory returnData) =
address(opcm).delegatecall(abi.encodeCall(IOPContractsManagerV2.upgrade, (_inputs[i])));
Comment thread
maurelian marked this conversation as resolved.
require(success, string(returnData));
}
}
}