diff --git a/contracts/parent-chain/contract-upgrades/NitroContracts3Point2Point0UpgradeAction.sol b/contracts/parent-chain/contract-upgrades/NitroContracts3Point2Point0UpgradeAction.sol new file mode 100644 index 00000000..dde6cbcb --- /dev/null +++ b/contracts/parent-chain/contract-upgrades/NitroContracts3Point2Point0UpgradeAction.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.16; + +interface IRollupAdminLogic { + function upgradeSecondaryTo(address newImplementation) external; + function upgradeTo(address newImplementation) external; +} + +/** + * @title NitroContracts3Point2Point0UpgradeAction + * @notice Upgrade action for nitro contracts v3.2.0 + * @dev Only upgrades Rollup contract, other diff between v3.2.0 and v3.1.0 are irrelevant. + */ +contract NitroContracts3Point2Point0UpgradeAction { + address public immutable newRollupAdminLogicImpl; + address public immutable newRollupUserLogicImpl; + + constructor(address _newRollupAdminLogicImpl, address _newRollupUserLogicImpl) { + require(_newRollupAdminLogicImpl.code.length > 0, "invalid rollup admin logic impl"); + require(_newRollupUserLogicImpl.code.length > 0, "invalid rollup user logic impl"); + newRollupAdminLogicImpl = _newRollupAdminLogicImpl; + newRollupUserLogicImpl = _newRollupUserLogicImpl; + } + + function perform(address rollup) external { + // skip sequencer inbox upgrade since it only adds custom DA header support + // skip OSP upgrade since it only adds custom DA validation support + + // RollupAdminLogic is the primary implementation + // RollupUserLogic is the secondary implementation + // see: https://github.com/OffchainLabs/nitro-contracts/blob/d9a2f3353ff13c706b7807b097e7ba591d970d85/src/libraries/AdminFallbackProxy.sol#L141-L143 + + IRollupAdminLogic(rollup).upgradeTo(newRollupAdminLogicImpl); + IRollupAdminLogic(rollup).upgradeSecondaryTo(newRollupUserLogicImpl); + } +} diff --git a/scripts/chain-versioner/chainVersioner.ts b/scripts/chain-versioner/chainVersioner.ts index 3029bf4d..f7d95165 100644 --- a/scripts/chain-versioner/chainVersioner.ts +++ b/scripts/chain-versioner/chainVersioner.ts @@ -1,4 +1,5 @@ import metadataHashes from './referentMetadataHashes.json' +import contractAddresses from './referentContractAddresses.json' import { IBridge__factory, IInbox__factory, @@ -74,6 +75,7 @@ export type ChainVersionerReport = OrbitVersionerReport * Load the referent metadata hashes */ const referentMetadataHashes: MetadataHashesByVersion = metadataHashes +const referentContractAddresses: MetadataHashesByVersion = contractAddresses /** * Script will @@ -104,64 +106,56 @@ export async function runChainVersioner( const challengeManagerAddress = await rollup.challengeManager() const rollupEventInboxAddress = await rollup.rollupEventInbox() - // get metadata hashes - const metadataHashes: { [key: string]: string } = { - Inbox: await _getMetadataHash( - await _getLogicAddress(inboxAddress, provider), - provider - ), - Outbox: await _getMetadataHash( - await _getLogicAddress(outboxAddress, provider), - provider - ), - SequencerInbox: await _getMetadataHash( - await _getLogicAddress(seqInboxAddress, provider), - provider - ), - Bridge: await _getMetadataHash( - await _getLogicAddress(bridgeAddress, provider), - provider - ), - RollupEventInbox: await _getMetadataHash( - await _getLogicAddress(rollupEventInboxAddress, provider), - provider - ), - RollupProxy: await _getMetadataHash(rollupAddress, provider), - RollupAdminLogic: await _getMetadataHash( - await _getLogicAddress(rollupAddress, provider), - provider - ), - RollupUserLogic: await _getMetadataHash( - await _getAddressAtStorageSlot( - rollupAddress, - provider, - '0x2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d' - ), - provider - ), - ChallengeManager: await _getMetadataHash( - await _getLogicAddress(challengeManagerAddress, provider), - provider + // get logic addresses for each contract + const logicAddresses: { [key: string]: string } = { + Inbox: await _getLogicAddress(inboxAddress, provider), + Outbox: await _getLogicAddress(outboxAddress, provider), + SequencerInbox: await _getLogicAddress(seqInboxAddress, provider), + Bridge: await _getLogicAddress(bridgeAddress, provider), + RollupEventInbox: await _getLogicAddress(rollupEventInboxAddress, provider), + RollupProxy: rollupAddress, + RollupAdminLogic: await _getLogicAddress(rollupAddress, provider), + RollupUserLogic: await _getAddressAtStorageSlot( + rollupAddress, + provider, + '0x2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d' ), + ChallengeManager: await _getLogicAddress(challengeManagerAddress, provider), } if (process.env.DEV === 'true') { - log('\nMetadataHashes of deployed contracts:', metadataHashes, '\n') + log('\nLogic addresses of deployed contracts:', logicAddresses, '\n') } let isFeeTokenChain = false const versions: { [key: string]: string | null } = {} - // get and print version per bridge contract - Object.keys(metadataHashes).forEach(key => { - const { version, isErc20 } = _getVersionOfDeployedContract( - metadataHashes[key] - ) - versions[key] = version - if (key === 'Bridge' && isErc20) isFeeTokenChain = true + + for (const key of Object.keys(logicAddresses)) { + // try address-based lookup first (for create2 deployments like v3.2.0+) + let result = _getVersionOfDeployedContractByAddress(logicAddresses[key]) + + if (!result.version) { + // fall back to metadata hash lookup + try { + const metadataHash = await _getMetadataHash( + logicAddresses[key], + provider + ) + if (process.env.DEV === 'true') { + log(`MetadataHash of deployed ${key}:`, metadataHash) + } + result = _getVersionOfDeployedContract(metadataHash) + } catch { + // metadata hash extraction can fail for contracts without standard metadata + } + } + + versions[key] = result.version + if (key === 'Bridge' && result.isErc20) isFeeTokenChain = true log( `Version of deployed ${key}: ${versions[key] ? versions[key] : 'unknown'}` ) - }) + } // TODO: make this more generic to support other other upgrade paths in the future // TODO: also check osp @@ -188,6 +182,10 @@ function _checkForPossibleUpgrades( ): UpgradeRecommendation { // version need to be in descending order const targetVersionsDescending = [ + { + version: 'v3.2.0', + actionName: 'NitroContracts3Point2Point0UpgradeAction', + }, { version: 'v3.1.0', actionName: 'BOLD UpgradeAction', @@ -294,7 +292,19 @@ function _canBeUpgradedToTargetVersion( let supportedSourceVersionsPerContract: { [key: string]: string[] } = {} - if (targetVersion === 'v3.1.0') { + if (targetVersion === 'v3.2.0') { + supportedSourceVersionsPerContract = { + Inbox: ['v3.1.0'], + Outbox: ['v3.1.0'], + Bridge: ['v3.1.0'], + RollupEventInbox: ['any'], + RollupProxy: ['any'], + RollupAdminLogic: ['v3.1.0'], + RollupUserLogic: ['v3.1.0'], + ChallengeManager: ['v3.1.0'], + SequencerInbox: ['v3.1.0'], + } + } else if (targetVersion === 'v3.1.0') { // todo: remove once nitro supports bold for L3's if (parentChainId !== 1n && parentChainId !== 11155111n) { supportedSourceVersionsPerContract = { @@ -498,6 +508,35 @@ function _canBeUpgradedToTargetVersion( return true } +function _getVersionOfDeployedContractByAddress(logicAddress: string): { + version: string | null + isErc20: boolean +} { + const normalized = logicAddress.toLowerCase() + for (const [version] of Object.entries(referentContractAddresses).reverse()) { + const versionAddresses = referentContractAddresses[version] + const allAddresses = [ + ...Object.values(versionAddresses.eth).flat(), + ...Object.values(versionAddresses.erc20).flat(), + ...versionAddresses.RollupProxy, + ...versionAddresses.RollupAdminLogic, + ...versionAddresses.RollupUserLogic, + ...versionAddresses.ChallengeManager, + ].map(a => a.toLowerCase()) + + if (allAddresses.includes(normalized)) { + const erc20Addresses = [ + ...Object.values(versionAddresses.erc20).flat(), + ].map(a => a.toLowerCase()) + if (erc20Addresses.includes(normalized)) { + return { version, isErc20: true } + } + return { version, isErc20: false } + } + } + return { version: null, isErc20: false } +} + function _getVersionOfDeployedContract(metadataHash: string): { version: string | null isErc20: boolean diff --git a/scripts/chain-versioner/referentContractAddresses.json b/scripts/chain-versioner/referentContractAddresses.json new file mode 100644 index 00000000..add9f485 --- /dev/null +++ b/scripts/chain-versioner/referentContractAddresses.json @@ -0,0 +1,42 @@ +{ + "v3.2.0": { + "eth": { + "Inbox": [ + "0x2c358843740dB38a40f96FD627D4DA1dd859E554", + "0x8D3b93dfFFf4842E7B61FB553b383db2C6BC91c6" + ], + "Outbox": ["0x396765AEbE540575ef927F769b0d7b89594f931c"], + "SequencerInbox": [ + "0xDA2e941cc4C7D8166Ec207CB7f7B09bA4f94406c", + "0x3636c0bB4Cfd072d06cbf59754A2830D05fD3419", + "0x17d9a7f836cB3A97aCcf96E59f56504457044945", + "0xF6995BDC439B27deEA5d5d08e95E92451a7d36F5", + "0x41e2B560F0B41FBc4A063Eac5298da4C246BEA1B", + "0xb015D78fb9B890e96FD3E23819b2C8D9fffA3cC5" + ], + "Bridge": ["0xC678f7B95A7D1F77c6024c0086301D21402854b1"], + "RollupEventInbox": ["0x796FeE4adceD1cb47a3e3d1B6925472F8fC8f1f9"] + }, + "erc20": { + "Inbox": [ + "0x02E5bFcF09Cc317EDDCaca72a1b1265A57197Db5", + "0xBfd8916b9DCB60B3b437D2B3a6FF56F78DcD9Ff2" + ], + "Outbox": ["0x5128805C5331A3D445B72545d2461B2C3B05218c"], + "SequencerInbox": [ + "0xB8594C833f519126B614979867216EB4BAE82B6d", + "0x5Bf5D77bed887dC32beE7852920A6C93A3D950f1", + "0xD5396bCeE766352B4f87E75708E35E4844883Fb5", + "0xC7FeB25D44D87F753C4ecCb974426Aea8c817f72", + "0xE3E3A0EeFe4f3388eaf4AAA9A77Df0D0d489b776", + "0x2D721E1704c48c9F16353a3b346Aa1EFEc9aA86c" + ], + "Bridge": ["0x0124687D1F2869b0C2335B98ddc7FCf59DA2CEa1"], + "RollupEventInbox": ["0x2706682dD3bD709b055E0266D98BA380FE22B807"] + }, + "RollupProxy": [], + "RollupAdminLogic": ["0xAb7A44CE7e66963d2116dCe74AB63eeF88266C82"], + "RollupUserLogic": ["0xedC23dFC7D1e57EC07eA5ff7419634DbAe08Ed2C"], + "ChallengeManager": ["0xCAaa9332F940362aEAAADD1B0A59c229C4fD8f79"] + } +} diff --git a/scripts/foundry/contract-upgrades/3.2.0/.env.sample b/scripts/foundry/contract-upgrades/3.2.0/.env.sample new file mode 100644 index 00000000..a94cbe15 --- /dev/null +++ b/scripts/foundry/contract-upgrades/3.2.0/.env.sample @@ -0,0 +1,10 @@ +## Forge configuration (uncomment/adjust as needed) +## All FOUNDRY_* env vars are supported: https://book.getfoundry.sh/reference/config/ +# FOUNDRY_BROADCAST=true +# ETH_PRIVATE_KEY=0x... + +## Chain and contract addresses +PARENT_CHAIN_RPC= +ROLLUP_ADDRESS= +UPGRADE_ACTION_ADDRESS=0x352b46fc500757e07a7e6063870c3a2b2c72da90 +PARENT_UPGRADE_EXECUTOR_ADDRESS= diff --git a/scripts/foundry/contract-upgrades/3.2.0/DeployNitroContracts3Point2Point0UpgradeAction.s.sol b/scripts/foundry/contract-upgrades/3.2.0/DeployNitroContracts3Point2Point0UpgradeAction.s.sol new file mode 100644 index 00000000..6370bf12 --- /dev/null +++ b/scripts/foundry/contract-upgrades/3.2.0/DeployNitroContracts3Point2Point0UpgradeAction.s.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import {DeploymentHelpersScript} from "../../helper/DeploymentHelpers.s.sol"; +import { + NitroContracts3Point2Point0UpgradeAction +} from "../../../../contracts/parent-chain/contract-upgrades/NitroContracts3Point2Point0UpgradeAction.sol"; +import {MockArbSys} from "../../helper/MockArbSys.sol"; + +/** + * @title DeployNitroContracts3Point2Point0UpgradeActionScript + * @notice Deploys implementation contracts and the NitroContracts3Point2Point0UpgradeAction contract. + */ +contract DeployNitroContracts3Point2Point0UpgradeActionScript is DeploymentHelpersScript { + function run() public { + vm.startBroadcast(); + + // see scripts/orbit-versioner/referentContractAddresses.json + // there is only one address shared across all chains + address newAdminLogic = 0xAb7A44CE7e66963d2116dCe74AB63eeF88266C82; + address newUserLogic = 0xedC23dFC7D1e57EC07eA5ff7419634DbAe08Ed2C; + + // Deploy the action contract last. The CLI identifies the deployed action + // by taking the last CREATE from the broadcast file. + new NitroContracts3Point2Point0UpgradeAction{salt: 0}(newAdminLogic, newUserLogic); + + vm.stopBroadcast(); + } +} diff --git a/scripts/foundry/contract-upgrades/3.2.0/ExecuteNitroContracts3Point2Point0Upgrade.s.sol b/scripts/foundry/contract-upgrades/3.2.0/ExecuteNitroContracts3Point2Point0Upgrade.s.sol new file mode 100644 index 00000000..bcfb1122 --- /dev/null +++ b/scripts/foundry/contract-upgrades/3.2.0/ExecuteNitroContracts3Point2Point0Upgrade.s.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Script.sol"; +import { + NitroContracts3Point2Point0UpgradeAction +} from "../../../../contracts/parent-chain/contract-upgrades/NitroContracts3Point2Point0UpgradeAction.sol"; +import {IUpgradeExecutor} from "@offchainlabs/upgrade-executor/src/IUpgradeExecutor.sol"; + +/** + * @title ExecuteNitroContracts3Point2Point0UpgradeScript + * @notice Executes nitro contracts 3.2.0 upgrade through UpgradeExecutor + */ +contract ExecuteNitroContracts3Point2Point0UpgradeScript is Script { + function run() public { + NitroContracts3Point2Point0UpgradeAction upgradeAction = + NitroContracts3Point2Point0UpgradeAction(vm.envAddress("UPGRADE_ACTION_ADDRESS")); + + require( + address(upgradeAction).code.length > 0, + "Upgrade action contract not found at provided address, run deployment script first" + ); + + // prepare upgrade calldata + bytes memory upgradeCalldata = + abi.encodeCall(NitroContracts3Point2Point0UpgradeAction.perform, (vm.envAddress("ROLLUP_ADDRESS"))); + + // execute the upgrade + 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/3.2.0/README.md b/scripts/foundry/contract-upgrades/3.2.0/README.md new file mode 100644 index 00000000..9a2878cc --- /dev/null +++ b/scripts/foundry/contract-upgrades/3.2.0/README.md @@ -0,0 +1,76 @@ +# Nitro contracts 3.2.0 upgrade + +These scripts deploy and execute the `NitroContracts3Point2Point0UpgradeAction` contract which allows Orbit chains to upgrade to [3.2.0 release](https://github.com/OffchainLabs/nitro-contracts/releases/tag/v3.2.0). Predeployed instances of the upgrade action exist on the chains listed in the following section. + +`NitroContracts3Point2Point0UpgradeAction` will perform the following action: + +1. Upgrade `RollupAdminLogic` (primary proxy implementation) to v3.2.0 +2. Upgrade `RollupUserLogic` (secondary proxy implementation) to v3.2.0 + +Note that only the rollup logic contracts are upgraded. Other contracts (bridge, inbox, sequencer inbox, outbox, challenge manager) are unchanged as the diff between v3.2.0 and v3.1.0 for those contracts is not relevant for existing chains. There is no associated ArbOS upgrade for this version. + +## Requirements + +This upgrade only supports upgrading from the following [nitro-contract release](https://github.com/OffchainLabs/nitro-contracts/releases): + +- Inbox: v3.1.0 +- Outbox: v3.1.0 +- SequencerInbox: v3.1.0 +- Bridge: v3.1.0 +- RollupEventInbox: any +- RollupProxy: any +- RollupAdminLogic: v3.1.0 +- RollupUserLogic: v3.1.0 +- ChallengeManager: v3.1.0 + +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: TODO +- L2 Arb1: TODO +- L2 Base: TODO + +### Testnets +- TODO + +## 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) + `DeployNitroContracts3Point2Point0UpgradeActionScript.s.sol` script deploys the upgrade action. It can be executed in this directory like this: + +```bash +forge script --sender $DEPLOYER --rpc-url $PARENT_CHAIN_RPC --broadcast --slow DeployNitroContracts3Point2Point0UpgradeActionScript -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. `ExecuteNitroContracts3Point2Point0Upgrade.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 ExecuteNitroContracts3Point2Point0UpgradeScript -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. Verify the upgrade was successful by running the verify script against the rollup: + +```bash +forge script --rpc-url $PARENT_CHAIN_RPC VerifyNitroContracts3Point2Point0Upgrade -vvv +``` + +This pranks the rollup owner and calls `increaseBaseStake` (new in v3.2.0). If the call succeeds, the upgrade was applied correctly. If it reverts, the rollup is still on the old implementation. + +## FAQ + +### Q: intrinsic gas too low when running foundry script + +A: try to add -g 1000 to the command diff --git a/scripts/foundry/contract-upgrades/3.2.0/VerifyNitroContracts3Point2Point0Upgrade.s.sol b/scripts/foundry/contract-upgrades/3.2.0/VerifyNitroContracts3Point2Point0Upgrade.s.sol new file mode 100644 index 00000000..1e1cae76 --- /dev/null +++ b/scripts/foundry/contract-upgrades/3.2.0/VerifyNitroContracts3Point2Point0Upgrade.s.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.16; + +import "forge-std/Script.sol"; + +interface IRollupAdmin { + function owner() external view returns (address); + function increaseBaseStake(uint256 newBaseStake) external; +} + +/** + * @title VerifyNitroContracts3Point2Point0Upgrade + * @notice Verifies the upgrade to Nitro Contracts 3.2.0 by checking that + * increaseBaseStake (new in v3.2.0) is callable on the rollup. + */ +contract VerifyNitroContracts3Point2Point0Upgrade is Script { + function run() public { + address rollup = vm.envAddress("ROLLUP_ADDRESS"); + address owner = IRollupAdmin(rollup).owner(); + + vm.prank(owner); + IRollupAdmin(rollup).increaseBaseStake(type(uint256).max); + + console.log("Verification passed: increaseBaseStake is available (v3.2.0)"); + } +} diff --git a/test/signatures/NitroContracts3Point2Point0UpgradeAction b/test/signatures/NitroContracts3Point2Point0UpgradeAction new file mode 100644 index 00000000..6ac756ef --- /dev/null +++ b/test/signatures/NitroContracts3Point2Point0UpgradeAction @@ -0,0 +1,11 @@ + +╭---------------------------+------------╮ +| Method | Identifier | ++========================================+ +| newRollupAdminLogicImpl() | a4d733b9 | +|---------------------------+------------| +| newRollupUserLogicImpl() | b4b20033 | +|---------------------------+------------| +| perform(address) | b38ed43b | +╰---------------------------+------------╯ + diff --git a/test/storage/NitroContracts3Point2Point0UpgradeAction b/test/storage/NitroContracts3Point2Point0UpgradeAction new file mode 100644 index 00000000..1ec5dc07 --- /dev/null +++ b/test/storage/NitroContracts3Point2Point0UpgradeAction @@ -0,0 +1,6 @@ + +╭------+------+------+--------+-------+----------╮ +| Name | Type | Slot | Offset | Bytes | Contract | ++================================================+ +╰------+------+------+--------+-------+----------╯ +