Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

interface ICreate2Deployer {
/**
* @dev Deploys a contract using `CREATE2`. The address where the
* contract will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `value`.
* - if `value` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 value, bytes32 salt, bytes memory code) external;
/**
* @dev Deployment of the {ERC1820Implementer}.
* Further information: https://eips.ethereum.org/EIPS/eip-1820
*/
function deployERC1820Implementer(uint256 value, bytes32 salt) external;
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}.
* Any change in the `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address);
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a
* contract located at `deployer`. If `deployer` is this contract's address, returns the
* same value as {computeAddress}.
*/
function computeAddressWithDeployer(
bytes32 salt,
bytes32 codeHash,
address deployer
)
external
pure
returns (address);

receive() external payable;
}
151 changes: 151 additions & 0 deletions packages/contracts-bedrock/scripts/deploy/PredeployHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import { Script } from "forge-std/Script.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { console } from "forge-std/console.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { ICreate2Deployer } from "interfaces/preinstalls/ICreate2Deployer.sol";
import { TransactionGeneration } from "scripts/deploy/TransactionGeneration.s.sol";

/// @title PredeployHelper
/// @notice Helper script for managing predeploy configurations during network upgrades.
/// This contract collects all predeploys that need to be deployed, computes their
/// CREATE2 addresses, and handles special cases requiring constructor arguments.
contract PredeployHelper is Script {
/// @notice Address of the Create2Deployer predeploy.
address payable immutable CREATE2_DEPLOYER = payable(Preinstalls.Create2Deployer);

/// @notice Represents a predeploy contract to be deployed during a network upgrade.
/// @param proxy The address of the proxy contract that will be upgraded.
/// @param name The name of the predeploy contract.
/// @param initCode The initialization code (bytecode + constructor args) for deployment.
/// @param implementation The computed CREATE2 address where the implementation will be deployed.
struct Predeploy {
address proxy;
string name;
bytes initCode;
address implementation;
}

/// @notice Array storing all predeploys to be deployed.
Predeploy[] private predeploys;

/// @notice The fork version being deployed.
uint256 private fork;

/// @notice Whether the CrossL2Inbox predeploy should be enabled.
bool private enableCrossL2Inbox;

/// @notice Constructs a new PredeployHelper with the specified fork configuration.
/// @param _fork The fork version to deploy predeploys for.
/// @param _enableCrossL2Inbox Whether to enable the CrossL2Inbox predeploy.
constructor(uint256 _fork, bool _enableCrossL2Inbox) {
fork = _fork;
enableCrossL2Inbox = _enableCrossL2Inbox;
}

/// @notice Collects all predeploys that need to be deployed for the configured fork.
/// @param _input The input struct containing chain configuration and deployment parameters.
/// @return Array of Predeploy structs containing deployment information for each predeploy.
function getPredeploys(TransactionGeneration.Input memory _input) external returns (Predeploy[] memory) {
uint160 prefix = uint160(0x420) << 148;

for (uint256 i = 0; i < Predeploys.PREDEPLOY_COUNT; i++) {
address addr = address(prefix | uint160(i));
// Skip if not supported or not proxied or needs constructor args
if (_needsConstructorArgs(addr)) {
continue;
}
_addPredeploy(addr, bytes(""));
}

// Add predeploys with constructor arguments
_addPredeploysWithArgs(_input);

// Copy storage array to memory for return
Predeploy[] memory result = new Predeploy[](predeploys.length);
for (uint256 i = 0; i < predeploys.length; i++) {
result[i] = predeploys[i];
}
return result;
}

/// @notice Adds a predeploy to the deployment list with optional constructor arguments.
/// @param _addr The proxy address of the predeploy contract.
/// @param _args ABI-encoded constructor arguments (empty bytes for no-arg constructors).
function _addPredeploy(address _addr, bytes memory _args) internal {
if (!Predeploys.isSupportedPredeploy(_addr, fork, enableCrossL2Inbox) || Predeploys.notProxied(_addr)) {
return;
}
string memory _name = Predeploys.getName(_addr);
bytes memory initCode = abi.encodePacked(vm.getCode(_name), _args);
bytes32 salt = keccak256(abi.encode(_name));
address implementation = ICreate2Deployer(CREATE2_DEPLOYER).computeAddress(salt, keccak256(initCode));

predeploys.push(Predeploy({ proxy: _addr, name: _name, initCode: initCode, implementation: implementation }));
}

/// @notice Checks if a predeploy requires constructor arguments or special handling.
/// @param _proxy The address of the proxy contract to check.
/// @return True if the predeploy requires constructor arguments, false otherwise.
function _needsConstructorArgs(address _proxy) private pure returns (bool) {
return _proxy == Predeploys.SEQUENCER_FEE_WALLET || _proxy == Predeploys.BASE_FEE_VAULT
|| _proxy == Predeploys.L1_FEE_VAULT || _proxy == Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY
|| _proxy == Predeploys.PROXY_ADMIN;
}

/// @notice Adds predeploys with constructor arguments to the deployment list.
/// @param _input The input struct containing configuration parameters.
function _addPredeploysWithArgs(TransactionGeneration.Input memory _input) internal {
// Add SequencerFeeVault
_addFeeVault(
Predeploys.SEQUENCER_FEE_WALLET,
_input.sequencerFeeVaultRecipient,
_input.sequencerFeeVaultMinimumWithdrawalAmount,
_input.sequencerFeeVaultWithdrawalNetwork
);
// Add BaseFeeVault
_addFeeVault(
Predeploys.BASE_FEE_VAULT,
_input.baseFeeVaultRecipient,
_input.baseFeeVaultMinimumWithdrawalAmount,
_input.baseFeeVaultWithdrawalNetwork
);
// Add L1FeeVault
_addFeeVault(
Predeploys.L1_FEE_VAULT,
_input.l1FeeVaultRecipient,
_input.l1FeeVaultMinimumWithdrawalAmount,
_input.l1FeeVaultWithdrawalNetwork
);
// Add OptimismMintableERC721Factory
_addOptimismMintableERC721Factory(_input);
}
Comment thread
0xiamflux marked this conversation as resolved.

/// @notice Adds a fee vault to the deployment list with its constructor arguments.
/// @param _feeVault The address of the fee vault contract to add.
/// @param _feeVaultRecipient The recipient of the fee vault.
/// @param _feeVaultMinimumWithdrawalAmount The minimum withdrawal amount for the fee vault.
/// @param _feeVaultWithdrawalNetwork The withdrawal network for the fee vault.
function _addFeeVault(
address _feeVault,
address _feeVaultRecipient,
uint256 _feeVaultMinimumWithdrawalAmount,
uint256 _feeVaultWithdrawalNetwork
)
internal
{
_addPredeploy(
_feeVault, abi.encode(_feeVaultRecipient, _feeVaultMinimumWithdrawalAmount, _feeVaultWithdrawalNetwork)
);
}

/// @notice Adds the OptimismMintableERC721Factory predeploy with its constructor arguments
/// @param _input The input struct containing configuration parameters
function _addOptimismMintableERC721Factory(TransactionGeneration.Input memory _input) internal {
_addPredeploy(
Predeploys.OPTIMISM_MINTABLE_ERC721_FACTORY, abi.encode(_input.l1ERC721BridgeProxy, _input.l2ChainID)
);
}
}
196 changes: 196 additions & 0 deletions packages/contracts-bedrock/scripts/deploy/TransactionGeneration.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import { Script } from "forge-std/Script.sol";
import { NetworkUpgradeTxns } from "src/libraries/NetworkUpgradeTxns.sol";
import { L2ContractsManager } from "src/L2/L2ContractsManager.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { ProxyAdmin } from "src/universal/ProxyAdmin.sol";
import { Config, Fork } from "scripts/libraries/Config.sol";
import { console2 as console } from "forge-std/console2.sol";
import { PredeployHelper } from "scripts/deploy/PredeployHelper.sol";
import { Preinstalls } from "src/libraries/Preinstalls.sol";
import { ICreate2Deployer } from "interfaces/preinstalls/ICreate2Deployer.sol";
import { L2ImplementationsDeployer } from "src/L2/L2ImplementationsDeployer.sol";

/// @title TransactionGenerationScript
/// @notice Script that generates Network Upgrade Transactions (NUTs) for deploying L2 contracts during a hard fork.
/// This script creates a sequence of transactions that deploy Predeploy contracts using CREATE2 and execute
/// and the L2ContractsManager. The last transaction is the execution of the L2ContractsManager.
contract TransactionGeneration is Script {
/// @notice Address of the Create2Deployer predeploy.
address payable immutable CREATE2_DEPLOYER = payable(Preinstalls.Create2Deployer);

/// @notice Array of Network Upgrade Transactions.
NetworkUpgradeTxns.NetworkUpgradeTxn[] private txns;

/// @notice Helper for managing predeploy configurations.
PredeployHelper internal helper;

/// @notice Address of the L2ImplementationsDeployer contract.
address private l2ImplDeployerAddress;

/// @notice Input struct for the script.
/// @param l2ChainID The ID of the L2 chain.
/// @param l1ChainID The ID of the L1 chain.
/// @param l1CrossDomainMessengerProxy The address of the L1 Cross Domain Messenger proxy.
/// @param l1StandardBridgeProxy The address of the L1 Standard Bridge proxy.
/// @param l1ERC721BridgeProxy The address of the L1 ERC721 Bridge proxy.
/// @param opChainProxyAdminOwner The address of the OP Chain Proxy Admin owner.
/// @param sequencerFeeVaultRecipient The address of the Sequencer Fee Vault recipient.
/// @param sequencerFeeVaultMinimumWithdrawalAmount The minimum withdrawal amount for the Sequencer Fee Vault.
/// @param sequencerFeeVaultWithdrawalNetwork The withdrawal network for the Sequencer Fee Vault.
/// @param baseFeeVaultRecipient The address of the Base Fee Vault recipient.
/// @param baseFeeVaultMinimumWithdrawalAmount The minimum withdrawal amount for the Base Fee Vault.
/// @param baseFeeVaultWithdrawalNetwork The withdrawal network for the Base Fee Vault.
/// @param l1FeeVaultRecipient The address of the L1 Fee Vault recipient.
/// @param l1FeeVaultMinimumWithdrawalAmount The minimum withdrawal amount for the L1 Fee Vault.
/// @param l1FeeVaultWithdrawalNetwork The withdrawal network for the L1 Fee Vault.
/// @param l2ImplDeployerAddress The address of the already-deployed L2ImplementationsDeployer.
/// @param l2cmName The name of the L2 Contracts Manager.
struct Input {
uint256 l2ChainID;
uint256 l1ChainID;
address payable l1CrossDomainMessengerProxy;
address payable l1StandardBridgeProxy;
address payable l1ERC721BridgeProxy;
address opChainProxyAdminOwner;
address sequencerFeeVaultRecipient;
uint256 sequencerFeeVaultMinimumWithdrawalAmount;
uint256 sequencerFeeVaultWithdrawalNetwork;
address baseFeeVaultRecipient;
uint256 baseFeeVaultMinimumWithdrawalAmount;
uint256 baseFeeVaultWithdrawalNetwork;
address l1FeeVaultRecipient;
uint256 l1FeeVaultMinimumWithdrawalAmount;
uint256 l1FeeVaultWithdrawalNetwork;
address l2ImplDeployerAddress;
string l2cmName;
}

/// @notice Output struct for the script
/// @param txns Array of Network Upgrade Transactions generated
/// @param l2cmAddress Address where the L2ContractsManager is deployed
/// @param predeploys Array of predeploys that were changed
struct Output {
NetworkUpgradeTxns.NetworkUpgradeTxn[] txns;
address l2cmAddress;
PredeployHelper.Predeploy[] predeploys;
}

/// @notice Generates Network Upgrade Transactions for deploying L2 contracts during a hard fork
/// @dev Creates a sequence of transactions that:
/// 1. Deploy new predeploy implementations via L2ImplementationsDeployer
/// 2. Deploy the L2ContractsManager via CREATE2
/// 3. Execute the L2ContractsManager to upgrade all predeploy proxies
/// The final artifact is written to deployments/nut-xfork-upgrade-transactions.json
/// @param _input The input struct containing chain configuration and deployment parameters
/// @return Output struct containing the generated transactions, L2CM address, and changed predeploys
function run(Input memory _input) external returns (Output memory) {
helper = new PredeployHelper(uint256(Config.fork()), Config.fork() >= Fork.INTEROP);

// Set the L2ImplementationsDeployer address from input
l2ImplDeployerAddress = _input.l2ImplDeployerAddress;

// Get all changed predeploy implementations
PredeployHelper.Predeploy[] memory predeploys = helper.getPredeploys(_input);

// Generate deployment transactions for each changed predeploy implementations
generateDeploymentTransactions(predeploys);

// Generate the L2ContractsManager deployment transaction
generateL2ContractsManagerDeploymentTransaction(_input.l2cmName);

// Generate L2ContractsManager execute transaction
address l2cmAddress = ICreate2Deployer(CREATE2_DEPLOYER).computeAddress(
keccak256(abi.encode(_input.l2cmName)), keccak256(vm.getCode(_input.l2cmName))
);
generateL2ContractsManagerExecuteTransaction(_input.l2cmName, l2cmAddress, predeploys);

// Write all transactions to JSON artifact file
NetworkUpgradeTxns.writeArtifact(txns, "deployments/nut-xfork-upgrade-transactions.json");

return Output({ txns: txns, l2cmAddress: l2cmAddress, predeploys: predeploys });
}

/// @notice Generates deployment transactions for all changed predeploys using L2ImplementationsDeployer
/// @dev Each predeploy is deployed via the L2ImplementationsDeployer with a salt derived from its name
/// @param predeploys Array of predeploys that need to be deployed
function generateDeploymentTransactions(PredeployHelper.Predeploy[] memory predeploys) internal {
for (uint256 i = 0; i < predeploys.length; i++) {
txns.push(
NetworkUpgradeTxns.newTx({
intent: string.concat("XFork: ", predeploys[i].name, " Deployment"),
from: address(0),
to: l2ImplDeployerAddress,
mint: 0,
value: 0,
gas: 1_000_000_000,
isSystemTransaction: false,
data: abi.encodeCall(
L2ImplementationsDeployer.deploy,
(0, keccak256(abi.encode(predeploys[i].name)), predeploys[i].initCode)
)
})
);
}
}

/// @notice Generates a deployment transaction for the L2ContractsManager using CREATE2
/// @dev The L2ContractsManager is deployed via the Create2Deployer preinstall with a salt derived from its name
/// @param _l2cmName The name of the L2ContractsManager contract to deploy
function generateL2ContractsManagerDeploymentTransaction(string memory _l2cmName) internal {
// Generate the L2ContractsManager deployment transaction
txns.push(
NetworkUpgradeTxns.newTx({
intent: string.concat("XFork: ", _l2cmName, " Deployment"),
from: address(0),
to: CREATE2_DEPLOYER,
mint: 0,
value: 0,
gas: 1_000_000,
isSystemTransaction: false,
data: abi.encodeCall(ICreate2Deployer.deploy, (0, keccak256(abi.encode(_l2cmName)), vm.getCode(_l2cmName)))
})
);
}

/// @notice Generates a transaction that executes the L2ContractsManager via ProxyAdmin to upgrade predeploys
/// @dev The transaction calls ProxyAdmin.performDelegateCall to delegatecall into the L2ContractsManager,
/// which upgrades all predeploy proxies to their new implementations
/// @param _l2cmName The name of the L2ContractsManager contract
/// @param _l2cmAddress The address where the L2ContractsManager is deployed
/// @param predeploys Array of predeploys that were deployed and need to be upgraded
function generateL2ContractsManagerExecuteTransaction(
string memory _l2cmName,
address _l2cmAddress,
PredeployHelper.Predeploy[] memory predeploys
)
internal
{
// Build the ProxyUpgrade array for the L2ContractsManager
L2ContractsManager.ProxyUpgrade[] memory proxyUpgrades =
new L2ContractsManager.ProxyUpgrade[](predeploys.length);
for (uint256 i = 0; i < predeploys.length; i++) {
proxyUpgrades[i] = L2ContractsManager.ProxyUpgrade({
proxy: predeploys[i].proxy,
implementation: predeploys[i].implementation
});
}

// Create transaction that calls execute() on the deployed L2ContractsManager
txns.push(
NetworkUpgradeTxns.newTx({
intent: string.concat("XFork: ", _l2cmName, " Execute"),
from: Constants.DEPOSITOR_ACCOUNT,
to: Predeploys.PROXY_ADMIN,
mint: 0,
value: 0,
gas: type(uint64).max,
isSystemTransaction: false,
data: abi.encodeCall(ProxyAdmin.performDelegateCall, (_l2cmAddress, proxyUpgrades))
})
);
}
}
Loading