diff --git a/.gas-snapshot b/.gas-snapshot index a6f2bd62..9bc7736a 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,4 +1,8 @@ NitroContracts2Point1Point2UpgradeActionTest:testShouldRevertOnEth() (gas: 61684) NitroContracts2Point1Point2UpgradeActionTest:testShouldRevertOnV2() (gas: 65683) -NitroContracts2Point1Point2UpgradeActionTest:testShouldUpgradeAndSetDecimals() (gas: 91928) +NitroContracts2Point1Point2UpgradeActionTest:testShouldUpgradeAndSetDecimals() (gas: 92108) +NitroContracts2Point1Point3UpgradeActionTest:testERC20() (gas: 13917471) +NitroContracts2Point1Point3UpgradeActionTest:testERC20BelowV2() (gas: 15598010) +NitroContracts2Point1Point3UpgradeActionTest:testEth() (gas: 13913461) +NitroContracts2Point1Point3UpgradeActionTest:testNotInbox() (gas: 13853316) UpgradeArbOSVersionAtTimestampActionTest:test_1() (gas: 165) \ No newline at end of file diff --git a/contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol b/contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol new file mode 100644 index 00000000..bfbb9d39 --- /dev/null +++ b/contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +interface IInbox { + function bridge() external view returns (address); + function sequencerInbox() external view returns (address); + function allowListEnabled() external view returns (bool); +} + +interface IERC20Bridge { + function nativeToken() external view returns (address); +} + +interface IERC20Bridge_v2 { + function nativeTokenDecimals() external view returns (uint8); +} + +/** + * @title NitroContracts2Point1Point3UpgradeAction + * @notice Upgrade the bridge to Inbox and SequencerInbox to v2.1.3 + * Will revert if the bridge is an ERC20Bridge below v2.x.x + */ +contract NitroContracts2Point1Point3UpgradeAction { + address public immutable newEthInboxImpl; + address public immutable newERC20InboxImpl; + address public immutable newEthSequencerInboxImpl; + address public immutable newERC20SequencerInboxImpl; + + constructor( + address _newEthInboxImpl, + address _newERC20InboxImpl, + address _newEthSequencerInboxImpl, + address _newERC20SequencerInboxImpl + ) { + require( + Address.isContract(_newEthInboxImpl), + "NitroContracts2Point1Point3UpgradeAction: _newEthInboxImpl is not a contract" + ); + require( + Address.isContract(_newERC20InboxImpl), + "NitroContracts2Point1Point3UpgradeAction: _newERC20InboxImpl is not a contract" + ); + require( + Address.isContract(_newEthSequencerInboxImpl), + "NitroContracts2Point1Point3UpgradeAction: _newEthSequencerInboxImpl is not a contract" + ); + require( + Address.isContract(_newERC20SequencerInboxImpl), + "NitroContracts2Point1Point3UpgradeAction: _newERC20SequencerInboxImpl is not a contract" + ); + + newEthInboxImpl = _newEthInboxImpl; + newERC20InboxImpl = _newERC20InboxImpl; + newEthSequencerInboxImpl = _newEthSequencerInboxImpl; + newERC20SequencerInboxImpl = _newERC20SequencerInboxImpl; + } + + function perform(address inbox, ProxyAdmin proxyAdmin) external { + // older upgrade action used to use the "rollup" address as the argument + // for 2.1.3 we cannot discover the inbox from the rollup, so instead we have to supply the inbox address + // this can be dangerous because both `bridge()` and `sequencerInbox()` also exists in the rollup contract + // the following check makes sure inbox is inbox as `allowListEnabled()` only exists in the inbox contract + try IInbox(inbox).allowListEnabled() returns (bool) {} + catch { + revert("NitroContracts2Point1Point3UpgradeAction: inbox is not an inbox"); + } + + address bridge = IInbox(inbox).bridge(); + address sequencerInbox = IInbox(inbox).sequencerInbox(); + + bool isERC20 = false; + + // if the bridge is an ERC20Bridge below v2.x.x, revert + try IERC20Bridge(bridge).nativeToken() returns (address) { + isERC20 = true; + // it is an ERC20Bridge, check if it is on v2.x.x + try IERC20Bridge_v2(address(bridge)).nativeTokenDecimals() returns (uint8) {} + catch { + // it is not on v2.x.x, revert + revert("NitroContracts2Point1Point3UpgradeAction: bridge is an ERC20Bridge below v2.x.x"); + } + } catch {} + + // upgrade the sequencer inbox + proxyAdmin.upgrade({ + proxy: TransparentUpgradeableProxy(payable((sequencerInbox))), + implementation: isERC20 ? newERC20SequencerInboxImpl : newEthSequencerInboxImpl + }); + + // upgrade the inbox + proxyAdmin.upgrade({ + proxy: TransparentUpgradeableProxy(payable((inbox))), + implementation: isERC20 ? newERC20InboxImpl : newEthInboxImpl + }); + } +} diff --git a/foundry.toml b/foundry.toml index 3117eb62..8b1ceb64 100644 --- a/foundry.toml +++ b/foundry.toml @@ -8,3 +8,4 @@ solc_version = '0.8.16' optimizer_runs = 2000 fs_permissions = [{ access = "read", path = "node_modules/@offchainlabs/"},{ access = "read", path = "node_modules/@arbitrum/"}, { access = "read", path = "node_modules/@openzeppelin/"},{ access = "read-write", path = "./scripts/foundry"}] script = 'scripts' +evm_version = 'cancun' diff --git a/package.json b/package.json index 51b2ef62..74e3f3ba 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@arbitrum/nitro-contracts-1.3.0": "npm:@arbitrum/nitro-contracts@1.3.0", "@arbitrum/nitro-contracts-2.1.0": "npm:@arbitrum/nitro-contracts@2.1.0", "@arbitrum/nitro-contracts-2.1.2": "npm:@arbitrum/nitro-contracts@2.1.2", + "@arbitrum/nitro-contracts-2.1.3": "npm:@arbitrum/nitro-contracts@2.1.3", "@arbitrum/token-bridge-1.2.2": "npm:@arbitrum/token-bridge-contracts@1.2.2", "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", "@nomicfoundation/hardhat-ethers": "^3.0.0", diff --git a/scripts/foundry/contract-upgrades/2.1.3/.env.sample b/scripts/foundry/contract-upgrades/2.1.3/.env.sample new file mode 100644 index 00000000..c189ee2f --- /dev/null +++ b/scripts/foundry/contract-upgrades/2.1.3/.env.sample @@ -0,0 +1,7 @@ +## These env vars are used for ExecuteNitroContracts2Point1Point2UpgradeScript +PARENT_CHAIN_IS_ARBITRUM=true +MAX_DATA_SIZE=104857 +UPGRADE_ACTION_ADDRESS= +INBOX_ADDRESS= +PROXY_ADMIN_ADDRESS= +PARENT_UPGRADE_EXECUTOR_ADDRESS= \ No newline at end of file diff --git a/scripts/foundry/contract-upgrades/2.1.3/DeployNitroContracts2Point1Point3UpgradeAction.s.sol b/scripts/foundry/contract-upgrades/2.1.3/DeployNitroContracts2Point1Point3UpgradeAction.s.sol new file mode 100644 index 00000000..8af199fc --- /dev/null +++ b/scripts/foundry/contract-upgrades/2.1.3/DeployNitroContracts2Point1Point3UpgradeAction.s.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import {DeploymentHelpersScript} from "../../helper/DeploymentHelpers.s.sol"; +import {NitroContracts2Point1Point3UpgradeAction} from + "../../../../contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol"; +import {MockArbSys} from "../../helper/MockArbSys.sol"; + +/** + * @title DeployNitroContracts2Point1Point3UpgradeActionScript + * @notice This script deploys the ERC20Bridge contract and NitroContracts2Point1Point3UpgradeAction contract. + */ +contract DeployNitroContracts2Point1Point3UpgradeActionScript is DeploymentHelpersScript { + function run() public { + bool isArbitrum = vm.envBool("PARENT_CHAIN_IS_ARBITRUM"); + if (isArbitrum) { + // etch a mock ArbSys contract so that foundry simulate it nicely + bytes memory mockArbSysCode = address(new MockArbSys()).code; + vm.etch(address(100), mockArbSysCode); + } + + vm.startBroadcast(); + + address reader4844Address; + if (!isArbitrum) { + // deploy blob reader + reader4844Address = deployBytecodeFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/out/yul/Reader4844.yul/Reader4844.json" + ); + } + + // deploy new ETHInbox contract from v2.1.3 + address newEthInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/Inbox.sol/Inbox.json", + abi.encode(vm.envUint("MAX_DATA_SIZE")) + ); + // deploy new ERC20Inbox contract from v2.1.3 + address newERC20InboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/ERC20Inbox.sol/ERC20Inbox.json", + abi.encode(vm.envUint("MAX_DATA_SIZE")) + ); + + // deploy new EthSequencerInbox contract from v2.1.3 + address newEthSeqInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/SequencerInbox.sol/SequencerInbox.json", + abi.encode(vm.envUint("MAX_DATA_SIZE"), reader4844Address, false) + ); + + // deploy new Erc20SequencerInbox contract from v2.1.3 + address newErc20SeqInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/SequencerInbox.sol/SequencerInbox.json", + abi.encode(vm.envUint("MAX_DATA_SIZE"), reader4844Address, true) + ); + + // deploy upgrade action + new NitroContracts2Point1Point3UpgradeAction( + newEthInboxImpl, newERC20InboxImpl, newEthSeqInboxImpl, newErc20SeqInboxImpl + ); + + vm.stopBroadcast(); + } +} diff --git a/scripts/foundry/contract-upgrades/2.1.3/ExecuteNitroContracts2Point1Point3Upgrade.s.sol b/scripts/foundry/contract-upgrades/2.1.3/ExecuteNitroContracts2Point1Point3Upgrade.s.sol new file mode 100644 index 00000000..89154dee --- /dev/null +++ b/scripts/foundry/contract-upgrades/2.1.3/ExecuteNitroContracts2Point1Point3Upgrade.s.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Script.sol"; +import { + NitroContracts2Point1Point3UpgradeAction, + ProxyAdmin +} from "../../../../contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol"; +import {IInboxBase} from "@arbitrum/nitro-contracts-1.2.1/src/bridge/IInboxBase.sol"; +import {ISequencerInbox} from "@arbitrum/nitro-contracts-2.1.2/src/bridge/ISequencerInbox.sol"; +import {IERC20Bridge} from "@arbitrum/nitro-contracts-2.1.2/src/bridge/IERC20Bridge.sol"; +import {IUpgradeExecutor} from "@offchainlabs/upgrade-executor/src/IUpgradeExecutor.sol"; + +/** + * @title ExecuteNitroContracts1Point2Point3UpgradeScript + * @notice This script executes nitro contracts 2.1.3 upgrade through UpgradeExecutor + */ +contract ExecuteNitroContracts2Point1Point3UpgradeScript is Script { + function run() public { + NitroContracts2Point1Point3UpgradeAction upgradeAction = + NitroContracts2Point1Point3UpgradeAction(vm.envAddress("UPGRADE_ACTION_ADDRESS")); + + address inbox = (vm.envAddress("INBOX_ADDRESS")); + + // validate MAX_DATA_SIZE + uint256 maxDataSize = vm.envUint("MAX_DATA_SIZE"); + require( + ISequencerInbox(upgradeAction.newEthInboxImpl()).maxDataSize() == maxDataSize + || ISequencerInbox(upgradeAction.newERC20InboxImpl()).maxDataSize() == maxDataSize + || ISequencerInbox(upgradeAction.newEthSequencerInboxImpl()).maxDataSize() == maxDataSize + || ISequencerInbox(upgradeAction.newERC20SequencerInboxImpl()).maxDataSize() == maxDataSize, + "MAX_DATA_SIZE mismatch with action" + ); + require(IInboxBase(inbox).maxDataSize() == maxDataSize, "MAX_DATA_SIZE mismatch with current deployment"); + + // prepare upgrade calldata + ProxyAdmin proxyAdmin = ProxyAdmin(vm.envAddress("PROXY_ADMIN_ADDRESS")); + bytes memory upgradeCalldata = + abi.encodeCall(NitroContracts2Point1Point3UpgradeAction.perform, (inbox, proxyAdmin)); + + // execute the upgrade + // action checks prerequisites, and script will fail if the action reverts + IUpgradeExecutor executor = IUpgradeExecutor(vm.envAddress("PARENT_UPGRADE_EXECUTOR_ADDRESS")); + vm.startBroadcast(); + executor.execute(address(upgradeAction), upgradeCalldata); + vm.stopBroadcast(); + } +} diff --git a/scripts/foundry/contract-upgrades/2.1.3/README.md b/scripts/foundry/contract-upgrades/2.1.3/README.md new file mode 100644 index 00000000..ec9454cc --- /dev/null +++ b/scripts/foundry/contract-upgrades/2.1.3/README.md @@ -0,0 +1,84 @@ +# Nitro contracts 2.1.3 upgrade + +> [!CAUTION] +> This is a patch version and is only necessary for chains that aren't ready for v3.0.0 whose parent chains are upgrading to include EIP7702. +> +> v3.0.0 is also compatible with an EIP7702 enabled parent chain. + +These scripts deploy and execute the `NitroContracts2Point1Point3UpgradeAction` contract which allows Orbit chains to upgrade to [2.1.3 release](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v2.1.3). Predeployed instances of the upgrade action exist on the chains listed in the following section. + +Upgrading to `v2.1.3` not required nor recommended if the chain aims to upgrade to v3.0.0 before the parent chain gets EIP7702. + +`NitroContracts2Point1Point3UpgradeAction` will perform the following action: + +1. Upgrade the `Inbox` or `ERC20Inbox` contract to `v2.1.3` +1. Upgrade the `SequencerInbox` contract to `v2.1.3` + +## Requirements + +This upgrade only support upgrading from the following [nitro-contract release](https://github.com/OffchainLabs/nitro-contracts/releases): + +- Inbox: v1.1.0 - v2.1.0 inclusive +- Outbox: v1.1.0 - v2.1.0 inclusive +- SequencerInbox: v1.2.1 - v2.1.0 inclusive +- Bridge: v1.1.0 - v2.1.0 inclusive (note this is not ERC20Bridge) +- ERC20Bridge: v2.0.0 - v2.1.2 inclusive +- RollupProxy: v1.1.0 - v2.1.0 inclusive +- RollupAdminLogic: v2.0.0 - v2.1.0 inclusive +- RollupUserLogic: v2.0.0 - v2.1.0 inclusive +- ChallengeManager: v2.0.0 - v2.1.0 inclusive + +Please refer to the top [README](/README.md#check-version-and-upgrade-path) `Check Version and Upgrade Path` on how to determine your current nitro contracts version. + +## Deployed instances + +### Mainnets +- L1 Mainnet: 0x9128ef6A57B2653CF78e650Dd97d0931dCaf79A2 +- L2 Arb1: 0xA350fE71079Aa86d48a8f2fDc600bbc6fa9CFE70 +- L2 Nova: 0x89611a8feff5a6376ea41265a05243FFBA225a59 +- L2 Base: 0x934a1e5187A5011AcECBACACF7cf6B22abE599A5 + +### Testnets +- L1 Sepolia: 0x82129aB330619388f46d3Cad387aEecb3843A16f +- L1 Holesky: 0x29D1bA37B3A7CC49990e1F613fdF9B33f9Cb3cEE. +- L2 ArbSepolia: 0x0E0Ee28333798F9aF0E76653beabC72F7477C287. +- L2 BaseSepolia: 0x5F6e79237387b01208FDf3a93efd455a2CADBa32. + +## How to use it + +1. Setup .env according to the example files, make sure you have everything correctly defined. The .env file must be in project root for recent foundry versions. + +> [!CAUTION] +> The .env file must be in project root. + +2. (Skip this step if you can use the deployed instances of action contract) + `DeployNitroContracts2Point1Point3UpgradeActionScript.s.sol` script deploys templates, and upgrade action itself. It can be executed in this directory like this: + +```bash +forge script --sender $DEPLOYER --rpc-url $PARENT_CHAIN_RPC --broadcast --slow DeployNitroContracts2Point1Point3UpgradeActionScript -vvv --verify --skip-simulation +# use --account XXX / --private-key XXX / --interactive / --ledger to set the account to send the transaction from +``` + +As a result, all templates and upgrade action are deployed. Note the last deployed address - that's the upgrade action. + +3. `ExecuteNitroContracts2Point1Point3Upgrade.s.sol` script uses previously deployed upgrade action to execute the upgrade. It makes following assumptions - L1UpgradeExecutor is the rollup owner, and there is an EOA which has executor rights on the L1UpgradeExecutor. Proceed with upgrade using the owner account (the one with executor rights on L1UpgradeExecutor): + +```bash +forge script --sender $EXECUTOR --rpc-url $PARENT_CHAIN_RPC --broadcast ExecuteNitroContracts2Point1Point3UpgradeScript -vvv +# use --account XXX / --private-key XXX / --interactive / --ledger to set the account to send the transaction from +``` + +If you have a multisig as executor, you can still run the above command without broadcasting to get the payload for the multisig transaction. + +4. That's it, upgrade has been performed. You can make sure it has successfully executed by checking the native token decimals. + +```bash +# should return 18 +cast call --rpc-url $PARENT_CHAIN_RPC $BRIDGE "nativeTokenDecimals()(uint8)" +``` + +## FAQ + +### Q: intrinsic gas too low when running foundry script + +A: try to add -g 1000 to the command diff --git a/scripts/orbit-versioner/orbitVersioner.ts b/scripts/orbit-versioner/orbitVersioner.ts index 09b55282..fa7484ea 100644 --- a/scripts/orbit-versioner/orbitVersioner.ts +++ b/scripts/orbit-versioner/orbitVersioner.ts @@ -141,12 +141,17 @@ function _checkForPossibleUpgrades( }, isFeeTokenChain: boolean ) { + // version need to be in descending order const targetVersionsDescending = [ // DISABLING BOLD UPGRADE FOR NOW // { // version: 'v3.0.0', // actionName: 'BOLD UpgradeAction', // }, + { + version: 'v2.1.3', + actionName: 'NitroContracts2Point1Point3UpgradeAction', + }, { version: 'v2.1.2', actionName: 'NitroContracts2Point1Point2UpgradeAction', @@ -161,7 +166,9 @@ function _checkForPossibleUpgrades( }, ] - for (const target of targetVersionsDescending) { + let canUpgradeTo = '' + let canUpgradeToActionName = '' + for (const target of targetVersionsDescending.reverse()) { if ( _canBeUpgradedToTargetVersion( target.version, @@ -169,12 +176,20 @@ function _checkForPossibleUpgrades( isFeeTokenChain ) ) { - console.log( - `This deployment can be upgraded to ${target.version} using ${target.actionName}` - ) - return + if (canUpgradeTo === '') { + canUpgradeTo = target.version + canUpgradeToActionName = target.actionName + } else { + throw new Error('Multiple upgrade paths found') + } } } + if (canUpgradeTo !== '') { + console.log( + `This deployment can be upgraded to ${canUpgradeTo} using ${canUpgradeToActionName}` + ) + return + } console.log('No upgrade path found') } @@ -184,11 +199,14 @@ function _canBeUpgradedToTargetVersion( currentVersions: { [key: string]: string | null }, - isFeeTokenChain: boolean + isFeeTokenChain: boolean, + verbose: boolean = false ): boolean { - console.log('\nChecking if deployment can be upgraded to', targetVersion) + if (verbose) + console.log('\nChecking if deployment can be upgraded to', targetVersion) let supportedSourceVersionsPerContract: { [key: string]: string[] } = {} + // DISABLING BOLD UPGRADE FOR NOW // if (targetVersion === 'v3.0.0') { // // v3.0.0 will upgrade bridge, inbox, rollupEventInbox, outbox, sequencerInbox, rollup logics, challengeManager @@ -226,7 +244,57 @@ function _canBeUpgradedToTargetVersion( // supportedSourceVersionsPerContract.Bridge = [] // } // } else - if (targetVersion === 'v2.1.2') { + if (targetVersion === 'v2.1.3') { + // v2.1.3 will upgrade the SequencerInbox and Inbox contracts to prevent 7702 accounts from calling certain functions + // v2.1.3 or v3.0.0 must be performed before the parent chain upgrades with 7702 + // has the same prerequisites as v3.0.0 + supportedSourceVersionsPerContract = { + Inbox: [ + 'v1.1.0', + 'v1.1.1', + 'v1.2.0', + 'v1.2.1', + 'v1.3.0', + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + 'v2.1.2', + ], + Outbox: ['any'], + Bridge: [ + 'v1.1.0', + 'v1.1.1', + 'v1.2.0', + 'v1.2.1', + 'v1.3.0', + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + 'v2.1.2', + ], + RollupEventInbox: ['any'], + RollupProxy: ['any'], + RollupAdminLogic: ['v2.0.0', 'v2.1.0', 'v2.1.1', 'v2.1.2'], + RollupUserLogic: ['v2.0.0', 'v2.1.0', 'v2.1.1', 'v2.1.2'], + ChallengeManager: ['v2.0.0', 'v2.1.0', 'v2.1.1', 'v2.1.2'], + SequencerInbox: [ + 'v1.2.1', + 'v1.3.0', + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + 'v2.1.2', + ], + } + if (isFeeTokenChain) { + supportedSourceVersionsPerContract.Bridge = [ + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + 'v2.1.2', + ] + } + } else if (targetVersion === 'v2.1.2') { // v2.1.2 will upgrade the ERC20Bridge contract to set decimals in storage // v2.1.2 is only required for custom fee token chains // only necessary if ERC20Bridge is < v2.0.0 @@ -245,15 +313,33 @@ function _canBeUpgradedToTargetVersion( } } else { supportedSourceVersionsPerContract = { - Inbox: ['v1.1.0', 'v1.1.1', 'v1.2.0', 'v1.2.1', 'v1.3.0'], + Inbox: [ + 'v1.1.0', + 'v1.1.1', + 'v1.2.0', + 'v1.2.1', + 'v1.3.0', + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + ], Outbox: ['any'], - Bridge: ['v1.1.0', 'v1.1.1', 'v1.2.0', 'v1.2.1', 'v1.3.0'], + Bridge: [ + 'v1.1.0', + 'v1.1.1', + 'v1.2.0', + 'v1.2.1', + 'v1.3.0', + 'v2.0.0', + 'v2.1.0', + 'v2.1.1', + ], RollupEventInbox: ['any'], RollupProxy: ['any'], - RollupAdminLogic: ['v2.1.0'], - RollupUserLogic: ['v2.1.0'], - ChallengeManager: ['v2.1.0'], - SequencerInbox: ['v1.2.1', 'v1.3.0', 'v2.0.0', 'v2.1.0'], + RollupAdminLogic: ['v2.0.0', 'v2.1.0', 'v2.1.1'], + RollupUserLogic: ['v2.0.0', 'v2.1.0', 'v2.1.1'], + ChallengeManager: ['v2.0.0', 'v2.1.0', 'v2.1.1'], + SequencerInbox: ['v1.2.1', 'v1.3.0', 'v2.0.0', 'v2.1.0', 'v2.1.1'], } } } else if (targetVersion === 'v2.1.0') { @@ -283,7 +369,7 @@ function _canBeUpgradedToTargetVersion( SequencerInbox: ['v1.1.0', 'v1.1.1'], } } else { - console.log('Unsupported target version') + if (verbose) console.log('Unsupported target version') return false } @@ -296,7 +382,7 @@ function _canBeUpgradedToTargetVersion( } if (!supportedSourceVersions.includes(currentVersions[contract]!)) { // found contract that can't be upgraded to target version - console.log('Cannot upgrade', contract, 'to', targetVersion) + if (verbose) console.log('Cannot upgrade', contract, 'to', targetVersion) return false } } @@ -308,7 +394,9 @@ function _getVersionOfDeployedContract(metadataHash: string): { version: string | null isErc20: boolean } { - for (const [version] of Object.entries(referentMetadataHashes)) { + // referentMetadataHashes should be in descending order of version + // we want to return the lowest version that matches the hash + for (const [version] of Object.entries(referentMetadataHashes).reverse()) { // check if given hash matches any of the referent hashes for specific version const versionHashes = referentMetadataHashes[version] const allHashes = [ diff --git a/scripts/orbit-versioner/referentMetadataHashes.json b/scripts/orbit-versioner/referentMetadataHashes.json index cc9b1b9f..28db4095 100644 --- a/scripts/orbit-versioner/referentMetadataHashes.json +++ b/scripts/orbit-versioner/referentMetadataHashes.json @@ -1,4 +1,66 @@ { + "v2.1.3": { + "eth": { + "Inbox": [ + "12203db8a1df622cbfdd5c34cc73814922a6eb991390c44059cc64611a45d7c9", + "12205dbd3b8d396e0948fa2a600b608e91cd302b0a5183ffc616f0537c120dd6" + ], + "Outbox": [ + "1220624da9aa741818f470b4caf3f8e08df88623d1802f66a3fd323c420b37cc", + "12205dac104f2ad698f9c94426317dc7bff86b274b37ccc62ef98ba1eef5d350" + ], + "SequencerInbox": [ + "1220a4d69803e223bfd54d28a9acae6029833ef5a6e9b2d4857a445f439c8dfb", + "122021d62fe49ecaac235631fb1ee6ae4e727f787b75249a7ff26f79985652fa" + ], + "Bridge": [ + "12207906c40a3078fa66d1082076c2f064076f59ebbb5f852887676ed77eb626", + "12200f406ae35b9e61c8f22ad62debe4c79988400a22eb888639f5ed6401bb7a" + ], + "RollupEventInbox": [ + "1220a69f3d907eaf365629a1e074f0ccd21cceab9cccdf01da621cd923ee2637", + "1220754c2454edc4cd4e4595061c4851bce8f1a59229f4b978315dc1e22c46e5" + ] + }, + "erc20": { + "Inbox": [ + "1220b00cd537c63f5a079ff98bff0c5bc0f5e505b12cd816fc6bebfd55b375fd", + "12203a6c443bb4ca8d47884e6d9b883bde915ec83d2867073e35bffdffc26f19" + ], + "Outbox": [ + "1220a71941053ce9337d0546cdb740773fdd025c466e7f7a3849011147fa21a4", + "122015a1779bfca810b2cd7771c349b522204b327445a8c607ff6956df29d174" + ], + "SequencerInbox": [ + "1220a4d69803e223bfd54d28a9acae6029833ef5a6e9b2d4857a445f439c8dfb", + "122021d62fe49ecaac235631fb1ee6ae4e727f787b75249a7ff26f79985652fa" + ], + "Bridge": [ + "1220424bceeda990292690ac333ea05580602995e00759d6ffb0f3e7f1f9bc00", + "1220e3d34866aedb2cdc2de9a5d57b6acc8616edaea86787054978831e14e141" + ], + "RollupEventInbox": [ + "1220a69f3d907eaf365629a1e074f0ccd21cceab9cccdf01da621cd923ee2637", + "1220754c2454edc4cd4e4595061c4851bce8f1a59229f4b978315dc1e22c46e5" + ] + }, + "RollupProxy": [ + "1220bab353bebd2841fa52caf85916c15589ea2750f92da55e48857dc5190a73", + "1220039b2791e4013e004cd51e37e19e1137184e0a7f5dec2621e721140c54ba" + ], + "RollupAdminLogic": [ + "1220234108252d432aa9102cd74587054f02d4911560a43b4cada21493e410a8", + "1220839876227496412329143261f9aee68b0488c8443af858067961fa782617" + ], + "RollupUserLogic": [ + "1220e522aeb65ca50fd75e371d71357f5aaf5d6bfc0a68fe6939eab69d97030e", + "12209d2c30d4500f07983f4c3ae838cdc93cdf73f3daf90d4dbd3288f072b2d1" + ], + "ChallengeManager": [ + "1220eb8f14661a955d488296113b2812b49527fae08d28fe585f2540c6eed075", + "1220d1bac57c40e879ffe5492c445773e1293aadfc210845af4536fc292e062b" + ] + }, "v2.1.2": { "eth": { "Inbox": [ diff --git a/test/signatures/NitroContracts2Point1Point3UpgradeAction b/test/signatures/NitroContracts2Point1Point3UpgradeAction new file mode 100644 index 00000000..d40187db --- /dev/null +++ b/test/signatures/NitroContracts2Point1Point3UpgradeAction @@ -0,0 +1,15 @@ + +╭------------------------------+------------╮ +| Method | Identifier | ++===========================================+ +| newERC20InboxImpl() | 384f98e8 | +|------------------------------+------------| +| newERC20SequencerInboxImpl() | 9d0c00b2 | +|------------------------------+------------| +| newEthInboxImpl() | b04a3917 | +|------------------------------+------------| +| newEthSequencerInboxImpl() | ef4f0619 | +|------------------------------+------------| +| perform(address,address) | 857d1ab7 | +╰------------------------------+------------╯ + diff --git a/test/storage/NitroContracts2Point1Point3UpgradeAction b/test/storage/NitroContracts2Point1Point3UpgradeAction new file mode 100644 index 00000000..1ec5dc07 --- /dev/null +++ b/test/storage/NitroContracts2Point1Point3UpgradeAction @@ -0,0 +1,6 @@ + +╭------+------+------+--------+-------+----------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++================================================+ +╰------+------+------+--------+-------+----------╯ + diff --git a/test/unit/NitroContracts2Point1Point3UpgradeAction.t.sol b/test/unit/NitroContracts2Point1Point3UpgradeAction.t.sol new file mode 100644 index 00000000..2ccc9b50 --- /dev/null +++ b/test/unit/NitroContracts2Point1Point3UpgradeAction.t.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Test.sol"; + +import {Bridge, IOwnable} from "@arbitrum/nitro-contracts-2.1.2/src/bridge/Bridge.sol"; +import { + ERC20Bridge as ERC20Bridge_2_1_0, + IOwnable as IOwnable_2_1_0 +} from "@arbitrum/nitro-contracts-2.1.0/src/bridge/ERC20Bridge.sol"; +import {Bridge as Bridge_2_1_0} from "@arbitrum/nitro-contracts-2.1.0/src/bridge/Bridge.sol"; +import {ERC20Inbox as ERC20Inbox_2_1_0} from "@arbitrum/nitro-contracts-2.1.0/src/bridge/ERC20Inbox.sol"; +import { + Inbox as Inbox_2_1_0, IInboxBase as IInboxBase_2_1_0 +} from "@arbitrum/nitro-contracts-2.1.0/src/bridge/Inbox.sol"; +import { + SequencerInbox as SequencerInbox_2_1_0, + ISequencerInbox as ISequencerInbox_2_1_0 +} from "@arbitrum/nitro-contracts-2.1.0/src/bridge/SequencerInbox.sol"; +import {IReader4844 as IReader4844_2_1_0} from "@arbitrum/nitro-contracts-2.1.0/src/libraries/IReader4844.sol"; + +import {ERC20Bridge as ERC20Bridge_1_3_0} from "@arbitrum/nitro-contracts-1.3.0/src/bridge/ERC20Bridge.sol"; + +import {NitroContracts2Point1Point3UpgradeAction} from + "contracts/parent-chain/contract-upgrades/NitroContracts2Point1Point3UpgradeAction.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {IUpgradeExecutor} from "@offchainlabs/upgrade-executor/src/IUpgradeExecutor.sol"; +import {DeploymentHelpersScript} from "../../scripts/foundry/helper/DeploymentHelpers.s.sol"; + +interface IUpgradeExecutorExtended is IUpgradeExecutor { + function initialize(address admin, address[] memory executors) external; +} + +contract FakeToken { + uint256 public decimals = 18; + + function allowance(address, address) external pure returns (uint256) { + return 0; + } + + function approve(address, uint256) external pure returns (bool) { + return true; + } +} + +contract NitroContracts2Point1Point3UpgradeActionTest is Test, DeploymentHelpersScript { + uint256 maxDataSize = 100_000; // dummy value + + IUpgradeExecutorExtended upgradeExecutor; + + address fakeToken; + + ProxyAdmin proxyAdmin; + + address fakeRollup = address(0xFF00); + address fakeReader = address(0xFF01); + + address erc20Bridge_2_1_0; + address bridge_2_1_0; + address erc20SequencerInbox_2_1_0; + address sequencerInbox_2_1_0; + address erc20Inbox_2_1_0; + address inbox_2_1_0; + + address newEthInboxImpl; + address newERC20InboxImpl; + address newEthSeqInboxImpl; + address newErc20SeqInboxImpl; + + function setUp() public { + // deploy a proxy admin + proxyAdmin = new ProxyAdmin(); + + // deploy an upgrade executor + address[] memory execs = new address[](1); + execs[0] = address(this); + upgradeExecutor = IUpgradeExecutorExtended( + address( + new TransparentUpgradeableProxy( + deployBytecodeFromJSON( + "/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json" + ), + address(proxyAdmin), + "" + ) + ) + ); + upgradeExecutor.initialize(address(this), execs); + + proxyAdmin.transferOwnership(address(upgradeExecutor)); + + // deploy a fake token + fakeToken = address(new FakeToken()); + + // deploy an ERC20Bridge on v2.1.0 + erc20Bridge_2_1_0 = + address(new TransparentUpgradeableProxy(address(new ERC20Bridge_2_1_0()), address(proxyAdmin), "")); + + // deploy an ETH Bridge on v2.1.0 + bridge_2_1_0 = address(new TransparentUpgradeableProxy(address(new Bridge_2_1_0()), address(proxyAdmin), "")); + + // deploy an ERC20 SequencerInbox on v2.1.0 + erc20SequencerInbox_2_1_0 = address( + new TransparentUpgradeableProxy( + address(new SequencerInbox_2_1_0(maxDataSize, IReader4844_2_1_0(fakeReader), true)), + address(proxyAdmin), + "" + ) + ); + + // deploy an ETH SequencerInbox on v2.1.0 + sequencerInbox_2_1_0 = address( + new TransparentUpgradeableProxy( + address(new SequencerInbox_2_1_0(maxDataSize, IReader4844_2_1_0(fakeReader), false)), + address(proxyAdmin), + "" + ) + ); + + // deploy an ERC20Inbox on v2.1.0 + erc20Inbox_2_1_0 = address( + new TransparentUpgradeableProxy(address(new ERC20Inbox_2_1_0(maxDataSize)), address(proxyAdmin), "") + ); + + // deploy an ETH Inbox on v2.1.0 + inbox_2_1_0 = + address(new TransparentUpgradeableProxy(address(new Inbox_2_1_0(maxDataSize)), address(proxyAdmin), "")); + + // initialize everything + Bridge_2_1_0(bridge_2_1_0).initialize(IOwnable_2_1_0(fakeRollup)); + ERC20Bridge_2_1_0(erc20Bridge_2_1_0).initialize(IOwnable_2_1_0(fakeRollup), fakeToken); + SequencerInbox_2_1_0(sequencerInbox_2_1_0).initialize( + Bridge_2_1_0(bridge_2_1_0), ISequencerInbox_2_1_0.MaxTimeVariation(10, 10, 10, 10) + ); + SequencerInbox_2_1_0(erc20SequencerInbox_2_1_0).initialize( + Bridge_2_1_0(erc20Bridge_2_1_0), ISequencerInbox_2_1_0.MaxTimeVariation(10, 10, 10, 10) + ); + Inbox_2_1_0(inbox_2_1_0).initialize(Bridge_2_1_0(bridge_2_1_0), SequencerInbox_2_1_0(sequencerInbox_2_1_0)); + ERC20Inbox_2_1_0(erc20Inbox_2_1_0).initialize( + Bridge_2_1_0(erc20Bridge_2_1_0), SequencerInbox_2_1_0(erc20SequencerInbox_2_1_0) + ); + } + + // copied from deployment script + function _deployActionScript() internal returns (NitroContracts2Point1Point3UpgradeAction) { + bool isArbitrum = false; + address reader4844Address; + if (!isArbitrum) { + // deploy blob reader + reader4844Address = deployBytecodeFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/out/yul/Reader4844.yul/Reader4844.json" + ); + } + + // deploy new ETHInbox contract from v2.1.3 + newEthInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/Inbox.sol/Inbox.json", + abi.encode(maxDataSize) + ); + // deploy new ERC20Inbox contract from v2.1.3 + newERC20InboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/ERC20Inbox.sol/ERC20Inbox.json", + abi.encode(maxDataSize) + ); + + // deploy new EthSequencerInbox contract from v2.1.3 + newEthSeqInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/SequencerInbox.sol/SequencerInbox.json", + abi.encode(maxDataSize, reader4844Address, false) + ); + + // deploy new Erc20SequencerInbox contract from v2.1.3 + newErc20SeqInboxImpl = deployBytecodeWithConstructorFromJSON( + "/node_modules/@arbitrum/nitro-contracts-2.1.3/build/contracts/src/bridge/SequencerInbox.sol/SequencerInbox.json", + abi.encode(maxDataSize, reader4844Address, true) + ); + + // deploy upgrade action + return new NitroContracts2Point1Point3UpgradeAction( + newEthInboxImpl, newERC20InboxImpl, newEthSeqInboxImpl, newErc20SeqInboxImpl + ); + } + + function testEth() public { + NitroContracts2Point1Point3UpgradeAction action = _deployActionScript(); + upgradeExecutor.execute(address(action), abi.encodeCall(action.perform, (inbox_2_1_0, proxyAdmin))); + + // check correctly upgraded + assertEq(proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(inbox_2_1_0))), newEthInboxImpl); + assertEq( + proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(sequencerInbox_2_1_0))), + newEthSeqInboxImpl + ); + } + + function testERC20() public { + NitroContracts2Point1Point3UpgradeAction action = _deployActionScript(); + upgradeExecutor.execute(address(action), abi.encodeCall(action.perform, (erc20Inbox_2_1_0, proxyAdmin))); + + // check correctly upgraded + assertEq( + proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(erc20Inbox_2_1_0))), newERC20InboxImpl + ); + assertEq( + proxyAdmin.getProxyImplementation(TransparentUpgradeableProxy(payable(erc20SequencerInbox_2_1_0))), + newErc20SeqInboxImpl + ); + } + + function testERC20BelowV2() public { + // just downgrade the bridge to v1.3.0 to simulate a v1.3.0 chain + address newImpl = address(new ERC20Bridge_1_3_0()); + vm.prank(address(upgradeExecutor)); + proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(erc20Bridge_2_1_0)), newImpl); + + NitroContracts2Point1Point3UpgradeAction action = _deployActionScript(); + + vm.expectRevert("NitroContracts2Point1Point3UpgradeAction: bridge is an ERC20Bridge below v2.x.x"); + upgradeExecutor.execute(address(action), abi.encodeCall(action.perform, (erc20Inbox_2_1_0, proxyAdmin))); + } + + function testNotInbox() public { + NitroContracts2Point1Point3UpgradeAction action = _deployActionScript(); + vm.mockCallRevert(address(inbox_2_1_0), abi.encodeWithSelector(IInboxBase_2_1_0.allowListEnabled.selector), ""); + vm.expectRevert("NitroContracts2Point1Point3UpgradeAction: inbox is not an inbox"); + upgradeExecutor.execute(address(action), abi.encodeCall(action.perform, (inbox_2_1_0, proxyAdmin))); + } +} diff --git a/yarn.lock b/yarn.lock index 33b2e028..24b259e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,6 +54,17 @@ patch-package "^6.4.7" solady "0.0.182" +"@arbitrum/nitro-contracts-2.1.3@npm:@arbitrum/nitro-contracts@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@arbitrum/nitro-contracts/-/nitro-contracts-2.1.3.tgz#94e88495e2c5623cafa44ff234e435c382b1a6e6" + integrity sha512-tN224y23Iw47EsTYxjechaHeXN6NVLvTO0cEPNw5Hw9jzAjM3jhST2+6XldQtL+p+qvMoHbdX7pFxKKR5+Z+lg== + dependencies: + "@offchainlabs/upgrade-executor" "1.1.0-beta.0" + "@openzeppelin/contracts" "4.5.0" + "@openzeppelin/contracts-upgradeable" "4.5.2" + patch-package "^6.4.7" + solady "0.0.182" + "@arbitrum/nitro-contracts@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@arbitrum/nitro-contracts/-/nitro-contracts-1.1.1.tgz#2d8a2f9ab757bb7654562aebe435bff833c4b98d"