-
Notifications
You must be signed in to change notification settings - Fork 3.8k
feat: OPCM v2 #18079
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
feat: OPCM v2 #18079
Conversation
d515a99 to
0bdef54
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #18079 +/- ##
===========================================
- Coverage 76.58% 76.47% -0.12%
===========================================
Files 179 181 +2
Lines 10732 10935 +203
===========================================
+ Hits 8219 8362 +143
- Misses 2367 2429 +62
+ Partials 146 144 -2
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
6fb6a47 to
73eb1b6
Compare
| ISystemConfig.initialize, | ||
| ( | ||
| _cfg.systemConfigOwner, | ||
| _cfg.basefeeScalar, | ||
| _cfg.blobBasefeeScalar, | ||
| bytes32(uint256(uint160(_cfg.batcher))), | ||
| _cfg.gasLimit, | ||
| _cfg.unsafeBlockSigner, | ||
| _cfg.resourceConfig, | ||
| chainIdToBatchInboxAddress(_cfg.l2ChainId), | ||
| addrs, | ||
| _cfg.l2ChainId, | ||
| _cfg.superchainConfig | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, let's refactor this initializer to use some structs, then we won't get the stack too deep problem here and it will be easier to extend this in the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be a good future improvement, I didn't want to modify too much in the contracts for now
| // Upgrade to StorageSetter. | ||
| _proxyAdmin.upgrade(payable(_target), address(implementations().storageSetterImpl)); | ||
|
|
||
| // Reset the initialized slot by zeroing the single byte at `_offset` (from the right). | ||
| bytes32 current = StorageSetter(_target).getBytes32(_slot); | ||
| uint256 mask = ~(uint256(0xff) << (uint256(_offset) * 8)); | ||
| StorageSetter(_target).setBytes32(_slot, bytes32(uint256(current) & mask)); | ||
|
|
||
| // Upgrade to the implementation and call the initializer. | ||
| _proxyAdmin.upgradeAndCall(payable(address(_target)), _implementation, _data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish there were initializer getter methods that Initializable has so we can assert onchain that we properly reset the initializer and did not change anything else, but seems those don't exist
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if it's overkill to fork initializable and add these getters? Would be a consistent philosophy to #18079 (comment) around doing things out of an abundance of caution
83ace94 to
71c612a
Compare
5c89c68 to
049f5bb
Compare
4143f86 to
7492dac
Compare
maurelian
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first round of comments and questions
packages/contracts-bedrock/interfaces/L1/opcm/IOPContractsManagerContainer.sol
Show resolved
Hide resolved
| cfg.blobBasefeeScalar = _input.blobBasefeeScalar; | ||
| cfg.gasLimit = _input.gasLimit; | ||
| cfg.l2ChainId = _input.l2ChainId; | ||
| cfg.resourceConfig = Constants.DEFAULT_RESOURCE_CONFIG(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm, why is resourceConfig becoming a configurable input? Is that wanted or needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one is mostly just because it's hard to figure out how to pass this around if you don't have it as a field on FullConfig. It needs to make its way to the _execute function somehow. resourceConfig is already configurable by chain operators if they want it to be, easy enough to just set it to the recommended default value manually.
| ) | ||
| }); | ||
| cfg.disputeGameConfigs[1] = IOPContractsManagerV2.DisputeGameConfig({ | ||
| enabled: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only one enabled. Is there any benefit to including disable game types in the array or could we cut it down to a single game config?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes - including disabled game types in the array forces users to explicitly consider each game type. If you don't force the user to include an input for each game type then they can easily fail to provide an input and potentially misconfigure a game. Better to be very explicit.
| /// have any code in production environments but can be made to have code in tests. | ||
| /// @return True if the contract is running in a testing environment, false otherwise. | ||
| function _isTestingEnvironment() internal view returns (bool) { | ||
| return address(0xbeefcafe).code.length > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this should be in Constants.sol
packages/contracts-bedrock/snapshots/storageLayout/OPContractsManagerV2.json
Show resolved
Hide resolved
| address ret; | ||
| if (LibString.eq(_contractName, "L1StandardBridge")) { | ||
| // L1StandardBridge is a special case ChugSplashProxy (legacy). | ||
| ret = Blueprint.deployFrom( | ||
| blueprints().l1ChugSplashProxy, | ||
| _computeSalt(_args.l2ChainId, _args.saltMixer, "L1StandardBridge"), | ||
| abi.encode(_args.proxyAdmin) | ||
| ); | ||
|
|
||
| // ChugSplashProxy requires setting the proxy type on the ProxyAdmin. | ||
| _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.CHUGSPLASH); | ||
| } else if (LibString.eq(_contractName, "L1CrossDomainMessenger")) { | ||
| // L1CrossDomainMessenger is a special case ResolvedDelegateProxy (legacy). | ||
| string memory l1XdmName = "OVM_L1CrossDomainMessenger"; | ||
| ret = Blueprint.deployFrom( | ||
| blueprints().resolvedDelegateProxy, | ||
| _computeSalt(_args.l2ChainId, _args.saltMixer, "L1CrossDomainMessenger"), | ||
| abi.encode(_args.addressManager, l1XdmName) | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it better to have this function be really big and detect which contracts get the nonstandard proxies, or should we break them out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This felt marginally cleaner to me than having three different functions. Do you have a specific alternative in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about just using an early return pattern instead of if/else? I think that would improve readability somewhat.
I acknowledge this is a bikeshed, if you feel strongly I'm OK with it.
7c0f993 to
64f664f
Compare
| } | ||
|
|
||
| // All V1 upgrade tests can safely be skipped for V2. | ||
| skipIfDevFeatureEnabled(DevFeatures.OPCM_V2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this still make sense to use? Maybe it saves some test running time, but the OPCMv1 works the same either way now right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm purposefully making it so the OPCMv1 doesn't work either way by not labeling the OPCMv1 address when OPCMv2 is enabled. This breaks all the tests that rely on OPCMv1 which makes it a lot easier to figure out what coverage we need iteratively (can delete these skips, see what fails, those are the tests we need to copy over to V2). I'll remove all these skips once we're completely happy with the V2 coverage.
packages/contracts-bedrock/test/L1/OPContractsManagerStandardValidator.t.sol
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment only: fork tests and feature flags look to be quite a hassle in this file.
| if (isDevFeatureEnabled(DevFeatures.CANNON_KONA)) { | ||
| addGameType(GameTypes.CANNON_KONA, cannonKonaPrestate); | ||
| } | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block would have made more sense to me on first reading if the comment in FeatureFlags.sol was reflected here too:
OPCMv2 also automatically implies DEPLOY_V2_DISPUTE_GAMES and CANNON_KONA.
| _assertValidUpgradeInstructions(_inp.extraInstructions); | ||
|
|
||
| // Load the chain contracts. | ||
| ChainContracts memory cts = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: revert with error in case _inp.systemConfig is address(0).
| assertEq("PDDG-DWETH-20,PLDG-DWETH-20,CKDG-DWETH-20", _validate(true)); | ||
| } else { | ||
| assertEq("PLDG-DWETH-20", _validate(true)); | ||
| assertEq("PDDG-DWETH-20,PLDG-DWETH-20", _validate(true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OPCMv2 also automatically implies DEPLOY_V2_DISPUTE_GAMES and CANNON_KONA.
I think this block is unreachable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise for a bunch of other occurences in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point
| if (isForkTest()) { | ||
| assertEq("PDDG-DWETH-30", _validate(true)); | ||
| } else { | ||
| if (isDevFeatureEnabled(DevFeatures.OPCM_V2)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For most occurences in this file I don't see why we need to branch on this flag, as there are no changes to the StandardValidator. Both OPCMs should be able to get the system to the same valid state.
The only place that I think we really need isDevFeatureEnabled(DevFeatures.OPCM_V2) is in setUp() where you decide whether to add the enabled games using either opcm.addGameType() or opcmv2.upgrade().
--
OK, having written this, I realize that the difference is that V2 sets the same WETH for all games, whereas V1 keeps them separate. Would it be easier to just update V1 to act like V2 to save us from all this branching? I suppose that since it's already written we could maybe just live with it for now and look forward to deleting the flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not update V1 to act like V2. Updating V1 means either committing to immediately shipping this change in V1, or it means adding yet another feature flag in the V1 which we'd probably not actually use because the V1 would be replaced by the V2 by the time it's used.
| ) | ||
| ); | ||
| } else { | ||
| // Special case handling, don't bother with the standard flow. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this a "special case" isn't it just what happens whenever this is called by upgrade()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Special case" here means that we're not using the _loadOrDeployProxy pattern. I should clarify the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. The placement of the comment seems to suggest that it applies to the full block. I think it can be handled just by expanding the text a bit more.
| ExtraInstruction[] memory instructions = new ExtraInstruction[](1); | ||
| instructions[0] = ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that this has become slightly confusing now that fd308b4 has added an ExtraInstruction field to FullConfig, ie. there is :
_cfg.extraInstructionsprovided as an arg todeploy()which is passed into_execute(), andinstructionscreated here and passed into_loadChainContracts()
WDYT if _loadChainContracts() accepted a boolean instead, and then use that boolean to create the PermittedProxyDeployment: ALL instruction inline?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok will handle it this way. Separately, I think we probably need to add an _assertValidDeployInstructions function.
| return payable(abi.decode(result, (address))); | ||
| } else if (!loadCanFail) { | ||
| // Load not permitted to fail but did, revert. | ||
| revert OPContractsManagerV2_ProxyMustLoad(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: might be nicer for debugging:
| revert OPContractsManagerV2_ProxyMustLoad(); | |
| revert OPContractsManagerV2_ProxyMustLoad(_contractName); |
a8fe8d1 to
fd308b4
Compare
| _loadChainContracts(ISystemConfig(address(0)), _cfg.l2ChainId, _cfg.saltMixer, instructions); | ||
|
|
||
| // Execute the deployment. | ||
| return _execute(_cfg, cts, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename to apply
| // Dispute game configuration. | ||
| DisputeGameConfig[] disputeGameConfigs; | ||
| // Extra deployment instructions. | ||
| ExtraInstruction[] extraInstructions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's get rid of this
| IProxyAdmin proxyAdmin; | ||
| IAddressManager addressManager; | ||
| ISystemConfig systemConfig; | ||
| if (isInitialDeployment) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This config could have it's own separate function:
if(isInitialDeployment){
(proxyAdmin, addressManager, systemConfig, ethLockbox) = _setUpInitialDeployment(...);
} else {
(proxyAdmin, addressManager, systemConfig, ethLockbox) = _setUpUpgrade(...);
}|
|
||
| /// @notice Validates the deployment/upgrade config. | ||
| /// @param _cfg The full config. | ||
| function _assertValidFullConfig(FullConfig memory _cfg) internal pure { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of validating the input, we should consider updating _execute() to call standardValidator.validateWithOverrides() at the end. The overrides should be taken from the FullConfig.
We would need to add a new string expectedErrors arg, which will allow us to define which errors are allowed. We had discussed including a boolean like useStandardValidator, but that is not necessary, we should always call it and ensure that its response matches expectedErrors.
| { | ||
| bool loadCanFail = _hasInstruction( | ||
| _instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes(_contractName) }) | ||
| ) || _hasInstruction(_instructions, ExtraInstruction({ key: "PermittedProxyDeployment", data: bytes("ALL") })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be a good idea to remove this magic value ALL and use a constant instead, wdyt?
| // We've failed to load, but we allowed that failure. | ||
| // Deploy the right proxy depending on the contract name. | ||
| address ret; | ||
| if (LibString.eq(_contractName, "L1StandardBridge")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might prefer to use a constant instead of hardcoded contract names.
|
|
||
| // ChugSplashProxy requires setting the proxy type on the ProxyAdmin. | ||
| _args.proxyAdmin.setProxyType(ret, IProxyAdmin.ProxyType.CHUGSPLASH); | ||
| } else if (LibString.eq(_contractName, "L1CrossDomainMessenger")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
| _proxyAdmin.getProxyImplementation(payable(_target)) != address(0) | ||
| && SemverComp.gt(ISemver(_target).version(), ISemver(_implementation).version()) | ||
| ) { | ||
| revert OPContractsManagerV2_DowngradeNotAllowed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be useful to add the contract address that is violating the downgrade check to the error.
revert OPContractsManagerV2_DowngradeNotAllowed(_target);| /// have any code in production environments but can be made to have code in tests. | ||
| /// @return True if the contract is running in a testing environment, false otherwise. | ||
| function _isTestingEnvironment() internal view returns (bool) { | ||
| return address(0xbeefcafe).code.length > 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use Constants.TESTING_ENVIRONMENT_ADDRESS here.
| /// @notice Helper function to load data from a source contract as bytes. | ||
| /// @param _source The source contract to load the data from. | ||
| /// @param _selector The selector of the function to call on the source contract. | ||
| /// @param _instructions The extra upgrade instructions for the data load. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing _name param in natspec.
Description
This PR introduces OPCMv2. Refer to the design document and the product requirements document for more information.