Commit 521fab3b authored by Blaine Malone's avatar Blaine Malone Committed by GitHub

feat: Change OPCM salt to include user supplied arg (#12152)

* feat: Change OPCM salt to include user supplied arg

* fix: removing temporary assumption.

* fix: bumped OPContractsManager version.

* fix: snapshots update.

* fix: bump OPContractsManager version.

* fix: snapshots update.

* fix: pre-pr run

* fix: wiring up Create2Salt as SaltMixer for DeployOPChainInput
parent 368c1332
......@@ -34,6 +34,7 @@ type DeployOPChainInput struct {
BlobBaseFeeScalar uint32
L2ChainId *big.Int
OpcmProxy common.Address
SaltMixer string
}
func (input *DeployOPChainInput) InputSet() bool {
......
......@@ -40,6 +40,7 @@ func DeployOPChain(ctx context.Context, env *Env, artifactsFS foundry.StatDirFs,
BlobBaseFeeScalar: 801949,
L2ChainId: chainID.Big(),
OpcmProxy: st.ImplementationsDeployment.OpcmProxyAddress,
SaltMixer: st.Create2Salt.String(), // passing through salt generated at state initialization
}
var dco opcm.DeployOPChainOutput
......
......@@ -75,7 +75,8 @@ type L2Config struct {
Challenger common.Address
SystemConfigOwner common.Address
genesis.L2InitializationConfig
Prefund map[common.Address]*big.Int
Prefund map[common.Address]*big.Int
SaltMixer string
}
func (c *L2Config) Check(log log.Logger) error {
......
......@@ -207,6 +207,7 @@ func DeployL2ToL1(l1Host *script.Host, superCfg *SuperchainConfig, superDeployme
BlobBaseFeeScalar: cfg.GasPriceOracleBlobBaseFeeScalar,
L2ChainId: new(big.Int).SetUint64(cfg.L2ChainID),
OpcmProxy: superDeployment.OpcmProxy,
SaltMixer: cfg.SaltMixer,
})
if err != nil {
return nil, fmt.Errorf("failed to deploy L2 OP chain: %w", err)
......
......@@ -47,6 +47,7 @@ contract DeployOPChainInput is BaseDeployIO {
uint32 internal _blobBaseFeeScalar;
uint256 internal _l2ChainId;
OPContractsManager internal _opcmProxy;
string internal _saltMixer;
function set(bytes4 _sel, address _addr) public {
require(_addr != address(0), "DeployOPChainInput: cannot set zero address");
......@@ -73,6 +74,12 @@ contract DeployOPChainInput is BaseDeployIO {
}
}
function set(bytes4 _sel, string memory _value) public {
require((bytes(_value).length != 0), "DeployImplementationsInput: cannot set empty string");
if (_sel == this.saltMixer.selector) _saltMixer = _value;
else revert("DeployOPChainInput: unknown selector");
}
function opChainProxyAdminOwner() public view returns (address) {
require(_opChainProxyAdminOwner != address(0), "DeployOPChainInput: not set");
return _opChainProxyAdminOwner;
......@@ -145,6 +152,10 @@ contract DeployOPChainInput is BaseDeployIO {
DeployUtils.assertImplementationSet(address(_opcmProxy));
return _opcmProxy;
}
function saltMixer() public view returns (string memory) {
return _saltMixer;
}
}
contract DeployOPChainOutput is BaseDeployIO {
......@@ -486,7 +497,8 @@ contract DeployOPChain is Script {
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots()
startingAnchorRoots: _doi.startingAnchorRoots(),
saltMixer: _doi.saltMixer()
});
vm.broadcast(msg.sender);
......
......@@ -32,8 +32,8 @@
"sourceCodeHash": "0xde4df0f9633dc0cdb1c9f634003ea5b0f7c5c1aebc407bc1b2f44c0ecf938649"
},
"src/L1/OPContractsManager.sol": {
"initCodeHash": "0x292d367322dc74744e8c98c463021e1abae77e57954eef8bac6e2081fcba5644",
"sourceCodeHash": "0xbfcc2032df842e50067d4b4a75ce66cc14cc34e67d35e37e2160215be57d8e2e"
"initCodeHash": "0x8f00d4415fe9bef59c1aec5b6729105c686e0238ce947432b2b5a035589cff19",
"sourceCodeHash": "0x4b1cb591b22821ae7246fe47260e1ece74f2cb0463fb949de66fe2b6a986a32c"
},
"src/L1/OptimismPortal.sol": {
"initCodeHash": "0xbe2c0c81b3459014f287d8c89cdc0d27dde5d1f44e5d024fa1e4773ddc47c190",
......
......@@ -162,6 +162,11 @@
"internalType": "bytes",
"name": "startingAnchorRoots",
"type": "bytes"
},
{
"internalType": "string",
"name": "saltMixer",
"type": "string"
}
],
"internalType": "struct OPContractsManager.DeployInput",
......@@ -565,6 +570,11 @@
"name": "InvalidRoleAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidStartingAnchorRoots",
"type": "error"
},
{
"inputs": [],
"name": "LatestReleaseNotSet",
......
......@@ -162,6 +162,11 @@
"internalType": "bytes",
"name": "startingAnchorRoots",
"type": "bytes"
},
{
"internalType": "string",
"name": "saltMixer",
"type": "string"
}
],
"internalType": "struct OPContractsManager.DeployInput",
......@@ -565,6 +570,11 @@
"name": "InvalidRoleAddress",
"type": "error"
},
{
"inputs": [],
"name": "InvalidStartingAnchorRoots",
"type": "error"
},
{
"inputs": [],
"name": "LatestReleaseNotSet",
......
......@@ -62,6 +62,8 @@ contract OPContractsManager is ISemver, Initializable {
// The correct type is AnchorStateRegistry.StartingAnchorRoot[] memory,
// but OP Deployer does not yet support structs.
bytes startingAnchorRoots;
// The salt mixer is used as part of making the resulting salt unique.
string saltMixer;
}
/// @notice The full set of outputs from deploying a new OP Stack chain.
......@@ -124,8 +126,8 @@ contract OPContractsManager is ISemver, Initializable {
// -------- Constants and Variables --------
/// @custom:semver 1.0.0-beta.9
string public constant version = "1.0.0-beta.9";
/// @custom:semver 1.0.0-beta.10
string public constant version = "1.0.0-beta.10";
/// @notice Represents the interface version so consumers know how to decode the DeployOutput struct
/// that's emitted in the `Deployed` event. Whenever that struct changes, a new version should be used.
......@@ -187,6 +189,9 @@ contract OPContractsManager is ISemver, Initializable {
/// @notice Thrown when the latest release is not set upon initialization.
error LatestReleaseNotSet();
/// @notice Thrown when the starting anchor roots are not provided.
error InvalidStartingAnchorRoots();
// -------- Methods --------
/// @notice OPCM is proxied. Therefore the `initialize` function replaces most constructor logic for this contract.
......@@ -218,10 +223,11 @@ contract OPContractsManager is ISemver, Initializable {
function deploy(DeployInput calldata _input) external returns (DeployOutput memory) {
assertValidInputs(_input);
// TODO Determine how we want to choose salt, e.g. are we concerned about chain ID squatting
// since this approach means a chain ID can only be used once.
uint256 l2ChainId = _input.l2ChainId;
bytes32 salt = bytes32(_input.l2ChainId);
// The salt for a non-proxy contract is a function of the chain ID and the salt mixer.
string memory saltMixer = _input.saltMixer;
bytes32 salt = keccak256(abi.encode(l2ChainId, saltMixer));
DeployOutput memory output;
// -------- Deploy Chain Singletons --------
......@@ -238,17 +244,19 @@ contract OPContractsManager is ISemver, Initializable {
// -------- Deploy Proxy Contracts --------
// Deploy ERC-1967 proxied contracts.
output.l1ERC721BridgeProxy = L1ERC721Bridge(deployProxy(l2ChainId, output.opChainProxyAdmin, "L1ERC721Bridge"));
output.l1ERC721BridgeProxy =
L1ERC721Bridge(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "L1ERC721Bridge"));
output.optimismPortalProxy =
OptimismPortal2(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "OptimismPortal")));
output.systemConfigProxy = SystemConfig(deployProxy(l2ChainId, output.opChainProxyAdmin, "SystemConfig"));
OptimismPortal2(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "OptimismPortal")));
output.systemConfigProxy =
SystemConfig(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "SystemConfig"));
output.optimismMintableERC20FactoryProxy = OptimismMintableERC20Factory(
deployProxy(l2ChainId, output.opChainProxyAdmin, "OptimismMintableERC20Factory")
deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "OptimismMintableERC20Factory")
);
output.disputeGameFactoryProxy =
DisputeGameFactory(deployProxy(l2ChainId, output.opChainProxyAdmin, "DisputeGameFactory"));
DisputeGameFactory(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "DisputeGameFactory"));
output.anchorStateRegistryProxy =
AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, "AnchorStateRegistry"));
AnchorStateRegistry(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "AnchorStateRegistry"));
// Deploy legacy proxied contracts.
output.l1StandardBridgeProxy = L1StandardBridge(
......@@ -275,10 +283,12 @@ contract OPContractsManager is ISemver, Initializable {
);
// We have two delayed WETH contracts per chain, one for each of the permissioned and permissionless games.
output.delayedWETHPermissionlessGameProxy =
DelayedWETH(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "DelayedWETHPermissionlessGame")));
output.delayedWETHPermissionedGameProxy =
DelayedWETH(payable(deployProxy(l2ChainId, output.opChainProxyAdmin, "DelayedWETHPermissionedGame")));
output.delayedWETHPermissionlessGameProxy = DelayedWETH(
payable(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "DelayedWETHPermissionlessGame"))
);
output.delayedWETHPermissionedGameProxy = DelayedWETH(
payable(deployProxy(l2ChainId, output.opChainProxyAdmin, saltMixer, "DelayedWETHPermissionedGame"))
);
// While not a proxy, we deploy the PermissionedDisputeGame here as well because it's bespoke per chain.
output.permissionedDisputeGame = PermissionedDisputeGame(
......@@ -358,6 +368,8 @@ contract OPContractsManager is ISemver, Initializable {
if (_input.roles.unsafeBlockSigner == address(0)) revert InvalidRoleAddress("unsafeBlockSigner");
if (_input.roles.proposer == address(0)) revert InvalidRoleAddress("proposer");
if (_input.roles.challenger == address(0)) revert InvalidRoleAddress("challenger");
if (_input.startingAnchorRoots.length == 0) revert InvalidStartingAnchorRoots();
}
/// @notice Maps an L2 chain ID to an L1 batch inbox address as defined by the standard
......@@ -372,17 +384,18 @@ contract OPContractsManager is ISemver, Initializable {
}
/// @notice Deterministically deploys a new proxy contract owned by the provided ProxyAdmin.
/// The salt is computed as a function of the L2 chain ID and the contract name. This is required
/// because we deploy many identical proxies, so they each require a unique salt for determinism.
/// The salt is computed as a function of the L2 chain ID, the salt mixer and the contract name.
/// This is required because we deploy many identical proxies, so they each require a unique salt for determinism.
function deployProxy(
uint256 _l2ChainId,
ProxyAdmin _proxyAdmin,
string memory _saltMixer,
string memory _contractName
)
internal
returns (address)
{
bytes32 salt = keccak256(abi.encode(_l2ChainId, _contractName));
bytes32 salt = keccak256(abi.encode(_l2ChainId, _saltMixer, _contractName));
return Blueprint.deployFrom(blueprint.proxy, salt, abi.encode(_proxyAdmin));
}
......
......@@ -68,7 +68,8 @@ contract OPContractsManager_Deploy_Test is DeployOPChain_TestBase {
basefeeScalar: _doi.basefeeScalar(),
blobBasefeeScalar: _doi.blobBaseFeeScalar(),
l2ChainId: _doi.l2ChainId(),
startingAnchorRoots: _doi.startingAnchorRoots()
startingAnchorRoots: _doi.startingAnchorRoots(),
saltMixer: _doi.saltMixer()
});
}
......
......@@ -48,6 +48,7 @@ contract DeployOPChainInput_Test is Test {
uint32 blobBaseFeeScalar = 200;
uint256 l2ChainId = 300;
OPContractsManager opcm = OPContractsManager(makeAddr("opcm"));
string saltMixer = "saltMixer";
function setUp() public {
doi = new DeployOPChainInput();
......@@ -353,6 +354,7 @@ contract DeployOPChain_TestBase is Test {
uint256 l2ChainId = 300;
AnchorStateRegistry.StartingAnchorRoot[] startingAnchorRoots;
OPContractsManager opcm = OPContractsManager(address(0));
string saltMixer = "defaultSaltMixer";
function setUp() public virtual {
// Set defaults for reference types
......@@ -470,6 +472,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
doi.set(doi.blobBaseFeeScalar.selector, blobBaseFeeScalar);
doi.set(doi.l2ChainId.selector, l2ChainId);
doi.set(doi.opcmProxy.selector, address(opcm)); // Not fuzzed since it must be an actual instance.
doi.set(doi.saltMixer.selector, saltMixer);
deployOPChain.run(doi, doo);
......@@ -485,6 +488,7 @@ contract DeployOPChain_Test is DeployOPChain_TestBase {
assertEq(basefeeScalar, doi.basefeeScalar(), "700");
assertEq(blobBaseFeeScalar, doi.blobBaseFeeScalar(), "800");
assertEq(l2ChainId, doi.l2ChainId(), "900");
assertEq(saltMixer, doi.saltMixer(), "1000");
// Assert inputs were properly passed through to the contract initializers.
assertEq(address(doo.opChainProxyAdmin().owner()), opChainProxyAdminOwner, "2100");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment