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