Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
043a081
feat: integrate SysFeature for Interop
stevennevins Mar 31, 2026
cf9b369
feat: add interop migration functions and eth lockbox to portal2
stevennevins Mar 31, 2026
176d624
chore: rename function to migrateToSharedDisputeGame
stevennevins Apr 1, 2026
ab13d5a
chore: bump snapshots
stevennevins Apr 1, 2026
a9f3041
fix: use Portal2 in OPCM migrator
stevennevins Apr 1, 2026
397413f
fix: semver bumps from interface change
stevennevins Apr 1, 2026
c4346c8
fix: only overwrite the lock box when passing non-zero value
stevennevins Apr 1, 2026
273b28c
chore: update comments to refer to OptimismPortal2 instead of Optimis…
stevennevins Apr 1, 2026
086170c
fix: mismatched names
digorithm Apr 1, 2026
816b8d3
fix: enable INTEROP feature flag in v1 OPCM deploy path
digorithm Apr 1, 2026
5ce845f
fix: remove casting to OptimismPortalInterop
stevennevins Apr 1, 2026
e4b6d26
chore: remove OptimismPortalInterop dead code
stevennevins Apr 1, 2026
9638b9d
chore: update comment to remove OptimismPortalInterop
stevennevins Apr 1, 2026
1911c8a
chore: remove OptimismPortalInterop specific tests since we now test …
stevennevins Apr 1, 2026
d2240e9
fix: test name camelCase
stevennevins Apr 1, 2026
e511f27
chore: remove OptimismPortalInterop from Initializable tests and remo…
stevennevins Apr 1, 2026
22c57f5
fix: restore superRootsActive flag in super-roots withdrawal proof tests
digorithm Apr 1, 2026
8294268
Merge branch 'feat/interop-shared-dispute-game' of https://github.com…
digorithm Apr 1, 2026
4fbe810
fix: normalize INTEROP feature in SystemConfig fuzz test
digorithm Apr 1, 2026
fc31aee
chore: add references for OptimismPortalInterop removal issue
stevennevins Apr 2, 2026
6d72d2d
chore: remove stale comment about superRootsActive
stevennevins Apr 2, 2026
d0499e7
fix(portal): bump OptimismPortal2 version and fix OPCM_V2 interop upg…
stevennevins Apr 2, 2026
81ea957
chore: update checks for superRootActie to be for the INTEROP feature…
stevennevins Apr 2, 2026
b6e6cb4
fix: standard validator assertions to be for Portal2 instead of Porta…
stevennevins Apr 2, 2026
44e4c48
fix: change event params back to non-indexed versions
stevennevins Apr 2, 2026
b6886db
chore: clean up branching logic for portal/opcm feature flags
stevennevins Apr 2, 2026
f0c6490
chore: refactor the checks in assertValidPortal
stevennevins Apr 2, 2026
e1bd3ef
chore: organize functions by visibility
stevennevins Apr 2, 2026
d0f0baa
fix: implement the _assertValidInteropState helper and add to initalize
stevennevins Apr 2, 2026
ac4eda6
fix: update assert for INTEROP flag instead of superroots active
stevennevins Apr 2, 2026
8097395
chore: add clarification to the comment about the prerequisites to th…
stevennevins Apr 2, 2026
3e417af
chore: add TODO issue tracking for follow up removal
stevennevins Apr 2, 2026
7ca8a76
fix: add missing OptimismPortal_InvalidInteropState error to IOptimis…
digorithm Apr 2, 2026
6f4c1bd
fix: assertions for the forked upgrade scenario
stevennevins Apr 3, 2026
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
14 changes: 13 additions & 1 deletion packages/contracts-bedrock/interfaces/L1/IOptimismPortal2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,25 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase {
error OptimismPortal_InvalidOutputRootProof();
error OptimismPortal_InvalidProofTimestamp();
error OptimismPortal_InvalidRootClaim();
error OptimismPortal_MigratingToSameRegistry();
error OptimismPortal_NoReentrancy();
error OptimismPortal_NotUsingInterop();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom error OptimismPortal_NotUsingInterop violates the Optimism Solidity Style Guide naming convention for errors. Custom errors should take the format ContractName_ErrorDescription. Since this is in the IOptimismPortal2 interface, it should be named OptimismPortal2_NotUsingInterop to match the contract name.

Suggested change
error OptimismPortal_NotUsingInterop();
error OptimismPortal2_NotUsingInterop();

Spotted by Graphite (based on custom rule: Solidity Style Guide)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

error OptimismPortal_ProofNotOldEnough();
error OptimismPortal_Unproven();
error OptimismPortal_InvalidInteropState();
error OptimismPortal_InvalidLockboxState();
error OutOfGas();
error UnexpectedList();
error UnexpectedString();

event Initialized(uint8 version);
event ETHMigrated(address indexed lockbox, uint256 balance);
event PortalMigrated(
IETHLockbox oldLockbox,
IETHLockbox newLockbox,
IAnchorStateRegistry oldAnchorStateRegistry,
IAnchorStateRegistry newAnchorStateRegistry
);
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
Expand Down Expand Up @@ -71,9 +81,11 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase {
external;
function finalizedWithdrawals(bytes32) external view returns (bool);
function guardian() external view returns (address);
function initialize(ISystemConfig _systemConfig, IAnchorStateRegistry _anchorStateRegistry) external;
function initialize(ISystemConfig _systemConfig, IAnchorStateRegistry _anchorStateRegistry, IETHLockbox _ethLockbox) external;
function initVersion() external view returns (uint8);
function l2Sender() external view returns (address);
function migrateLiquidity() external;
function migrateToSharedDisputeGame(IETHLockbox _newLockbox, IAnchorStateRegistry _newAnchorStateRegistry) external;
function minimumGasLimit(uint64 _byteCount) external pure returns (uint64);
function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256);
function params() external view returns (uint128 prevBaseFee, uint64 prevBoughtGas, uint64 prevBlockNum); // nosemgrep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.so
import { IProxyAdminOwnedBase } from "interfaces/universal/IProxyAdminOwnedBase.sol";
import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol";

/// TODO(#19709) remove this file and migrate fully to the OptimismPortal2
interface IOptimismPortalInterop is IProxyAdminOwnedBase {
error ContentLengthMismatch();
error EmptyItem();
Expand Down Expand Up @@ -64,7 +65,7 @@ interface IOptimismPortalInterop is IProxyAdminOwnedBase {
function disputeGameFinalityDelaySeconds() external view returns (uint256);
function donateETH() external payable;
function superchainConfig() external view returns (ISuperchainConfig);
function migrateToSuperRoots(IETHLockbox _newLockbox, IAnchorStateRegistry _newAnchorStateRegistry) external;
function migrateToSharedDisputeGame(IETHLockbox _newLockbox, IAnchorStateRegistry _newAnchorStateRegistry) external;
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external;
function finalizeWithdrawalTransactionExternalProof(
Types.WithdrawalTransaction memory _tx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IOPContractsManagerContainer {
address resolvedDelegateProxy;
}

// TODO(#19709): Remove the reference to optimismPortalInteropImpl when we remove OptimismPortalInterop from src
struct Implementations {
address superchainConfigImpl;
address protocolVersionsImpl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ contract DeployImplementations is Script {
_output.optimismPortalImpl = impl;
}


/// TODO(#19709) remove this file and migrate fully to the OptimismPortal2
function deployOptimismPortalInteropImpl(Input memory _input, Output memory _output) private {
uint256 proofMaturityDelaySeconds = _input.proofMaturityDelaySeconds;
IOptimismPortalInterop impl = IOptimismPortalInterop(
Expand Down
90 changes: 90 additions & 0 deletions packages/contracts-bedrock/snapshots/abi/OptimismPortal2.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@
"internalType": "contract IAnchorStateRegistry",
"name": "_anchorStateRegistry",
"type": "address"
},
{
"internalType": "contract IETHLockbox",
"name": "_ethLockbox",
"type": "address"
}
],
"name": "initialize",
Expand All @@ -314,6 +319,31 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "migrateLiquidity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IETHLockbox",
"name": "_newLockbox",
"type": "address"
},
{
"internalType": "contract IAnchorStateRegistry",
"name": "_newAnchorStateRegistry",
"type": "address"
}
],
"name": "migrateToSharedDisputeGame",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -627,6 +657,25 @@
"stateMutability": "pure",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "lockbox",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "balance",
"type": "uint256"
}
],
"name": "ETHMigrated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand All @@ -640,6 +689,37 @@
"name": "Initialized",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "contract IETHLockbox",
"name": "oldLockbox",
"type": "address"
},
{
"indexed": false,
"internalType": "contract IETHLockbox",
"name": "newLockbox",
"type": "address"
},
{
"indexed": false,
"internalType": "contract IAnchorStateRegistry",
"name": "oldAnchorStateRegistry",
"type": "address"
},
{
"indexed": false,
"internalType": "contract IAnchorStateRegistry",
"name": "newAnchorStateRegistry",
"type": "address"
}
],
"name": "PortalMigrated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -819,6 +899,11 @@
"name": "OptimismPortal_InvalidRootClaim",
"type": "error"
},
{
"inputs": [],
"name": "OptimismPortal_MigratingToSameRegistry",
"type": "error"
},
{
"inputs": [],
"name": "OptimismPortal_NoReentrancy",
Expand All @@ -829,6 +914,11 @@
"name": "OptimismPortal_NotAllowedOnCGTMode",
"type": "error"
},
{
"inputs": [],
"name": "OptimismPortal_NotUsingInterop",
"type": "error"
},
{
"inputs": [],
"name": "OptimismPortal_ProofNotOldEnough",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@
"type": "address"
}
],
"name": "migrateToSuperRoots",
"name": "migrateToSharedDisputeGame",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
Expand Down
24 changes: 12 additions & 12 deletions packages/contracts-bedrock/snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"sourceCodeHash": "0xe772f7db8033e4a738850cb28ac4849d3a454c93732135a8a10d4f7cb498088e"
},
"src/L1/ETHLockbox.sol:ETHLockbox": {
"initCodeHash": "0x781079a80d379658eb4553622a9da86f7532ffa424f1e8957a82680ee9435f66",
"sourceCodeHash": "0xf4d9f6adc3d99d65b70df3255976980d36d37f8a4514ecc24d786dd03efdb7be"
"initCodeHash": "0xb2ff8426ab2eb36352f790748963c8e1f7a91caf16bee6a035c2c41bac532836",
"sourceCodeHash": "0x870004d5acc24a704277680f09ccca51a020a512e6c6d48cca583a23a0de43a2"
},
"src/L1/FeesDepositor.sol:FeesDepositor": {
"initCodeHash": "0xe2ca240d728f711df438b7aeb3589c95ad11a97d742539a692ddafaf1365eb54",
Expand All @@ -24,20 +24,20 @@
"sourceCodeHash": "0x9d16e900a764cd7f19db3656cf7a9e555b23b9c7e018641ed21566657847a314"
},
"src/L1/OPContractsManager.sol:OPContractsManager": {
"initCodeHash": "0xd59648acb50002957cfd952a0cc14a4d6c2a2f6cd3a0f7d485e61f633d4873f3",
"sourceCodeHash": "0x6d4ffaaa57441dec3c3ee8fd9ac59f95229defec405347af853e265bfd749235"
"initCodeHash": "0x01d04967c6597f401825dbb2f3e851e35593b12e89d31690d6a1c8874f1a7828",
"sourceCodeHash": "0x48c7027ea48fbd3edc91b55f6884d2ca12ec3f15600bdcb46308e70996f08ad2"
},
"src/L1/OPContractsManagerStandardValidator.sol:OPContractsManagerStandardValidator": {
"initCodeHash": "0x233f5f4b424bc2aabe170cf758c9ff80841ceab4c78e37edfe3a7bc660c5577d",
"sourceCodeHash": "0x7c0cb663f82b07da8dec8a7497cf2fa56a335fb5bdc57b612c86462f8527d4d5"
"initCodeHash": "0x0f526653885529a95b251f0854fe36ebcfebe54e05c264275fd4488538222d6f",
"sourceCodeHash": "0x351071d455ff5af83bc0edb52fd6a24a5e6917191e5443eb331fbf8dea21efb6"
},
"src/L1/OptimismPortal2.sol:OptimismPortal2": {
"initCodeHash": "0x8c296124bc1b1468cf301a434eebf3f0d9a194cde06876b993a8672577f08187",
"sourceCodeHash": "0xb14d8bceab135616e55fd560a077a4cc66fc3b535f09931d3b9167ee940fa62f"
"initCodeHash": "0x25efe4afd106a1e994f8ef92e7fda72a80d71edeb347d488f1cb0be59c760835",
"sourceCodeHash": "0xb14ab78410b24f848e2c56f13b76cc34df64b5c9fd845a6a64a1aea9dc652886"
},
"src/L1/OptimismPortalInterop.sol:OptimismPortalInterop": {
"initCodeHash": "0xbafd0b80deb0a834335052e32a4199a96121148d9bda05acb62535ac18bd9909",
"sourceCodeHash": "0x24373f3fd28c5c6ae93cc32e2a213bb47458bc0f36e81b2a7b20a7b6b0a97119"
"initCodeHash": "0xa7262e160851c0de73b96943a28141ea23d3337494f1c507ecfc66d8a8d83024",
"sourceCodeHash": "0x941a3ba130742d8671c27f230635614589e851ab685cd7094e2c4487c5e05585"
},
"src/L1/ProtocolVersions.sol:ProtocolVersions": {
"initCodeHash": "0xcb59ad9a5ec2a0831b7f4daa74bdacba82ffa03035dafb499a732c641e017f4e",
Expand All @@ -52,8 +52,8 @@
"sourceCodeHash": "0xb09cb2f7cbde8585fad5c5beb6811fa9044b156b4203da8005d3f6a7a68c30b2"
},
"src/L1/opcm/OPContractsManagerV2.sol:OPContractsManagerV2": {
"initCodeHash": "0x6c8af9dac0ff4dc0c783fcf8af06bde4d444ebab065c907785a24fd4f65f2414",
"sourceCodeHash": "0x937e16a99db4a376c8855b3df8eb529d19614c0fa3d5d7dbe334006bad1452a3"
"initCodeHash": "0x177c9727d6a6a450e279becd2a14103401ab3b177d820cc2af887aa3fe01c1f6",
"sourceCodeHash": "0x263e1d09fe1c1d7d4ed1f557866441bb256c82983815cb63c29f4d6a7ea73e88"
},
"src/L2/BaseFeeVault.sol:BaseFeeVault": {
"initCodeHash": "0xf1fb169c6dd4eceb5cec6ed6dfa3affc45970e5a01e00827d06af1f9e8df026d",
Expand Down
6 changes: 3 additions & 3 deletions packages/contracts-bedrock/src/L1/ETHLockbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ contract ETHLockbox is ProxyAdminOwnedBase, Initializable, ReinitializableBase,
mapping(IETHLockbox => bool) public authorizedLockboxes;

/// @notice Semantic version.
/// @custom:semver 1.2.1
/// @custom:semver 1.3.1
function version() public view virtual returns (string memory) {
return "1.2.1";
return "1.3.1";
}

/// @notice Constructs the ETHLockbox contract.
Expand Down Expand Up @@ -195,7 +195,7 @@ contract ETHLockbox is ProxyAdminOwnedBase, Initializable, ReinitializableBase,
}

/// @notice Migrates liquidity from the current ETH lockbox to another.
/// @dev Must be called atomically with `OptimismPortal.migrateToSuperRoots()` in the same
/// @dev Must be called atomically with `OptimismPortal.migrateToSharedDisputeGame()` in the same
/// transaction batch, or otherwise the OptimismPortal may not be able to unlock ETH
/// from the ETHLockbox on finalized withdrawals.
/// @param _lockbox The address of the ETH lockbox to migrate liquidity to.
Expand Down
12 changes: 8 additions & 4 deletions packages/contracts-bedrock/src/L1/OPContractsManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,7 @@ contract OPContractsManagerDeployer is OPContractsManagerBase {
// the ETHLockbox in U16 and then upgrade to U16a.
if (isDevFeatureEnabled(DevFeatures.OPTIMISM_PORTAL_INTEROP)) {
output.systemConfigProxy.setFeature(Features.ETH_LOCKBOX, true);
output.systemConfigProxy.setFeature(Features.INTEROP, true);
}

// Initialize the OptimismPortal.
Expand Down Expand Up @@ -1335,7 +1336,10 @@ contract OPContractsManagerDeployer is OPContractsManagerBase {
virtual
returns (bytes memory)
{
return abi.encodeCall(IOptimismPortal.initialize, (_output.systemConfigProxy, _output.anchorStateRegistryProxy));
return abi.encodeCall(
IOptimismPortal.initialize,
(_output.systemConfigProxy, _output.anchorStateRegistryProxy, IETHLockbox(address(0)))
);
}

/// @notice Helper method for encoding the OptimismPortalInterop initializer data.
Expand Down Expand Up @@ -1672,7 +1676,7 @@ contract OPContractsManagerInteropMigrator is OPContractsManagerBase {
clearGameImplementation(oldDisputeGameFactory, GameTypes.SUPER_CANNON_KONA);

// Migrate the portal to the new ETHLockbox and AnchorStateRegistry.
portals[i].migrateToSuperRoots(newEthLockbox, newAnchorStateRegistry);
portals[i].migrateToSharedDisputeGame(newEthLockbox, newAnchorStateRegistry);
}

// Separate context to avoid stack too deep.
Expand Down Expand Up @@ -1916,9 +1920,9 @@ contract OPContractsManager is ISemver {
/// @dev This needs to stay at 6.x.x because the next release will ship OPCMv2. Since we are
/// not actually planning to release a 7.x.x of OPCMv1, it needs to stay at 6.x.x to avoid
/// errors in the versioning rules of OPCMv2.
/// @custom:semver 6.0.5
/// @custom:semver 6.1.5
function version() public pure virtual returns (string memory) {
return "6.0.5";
return "6.1.5";
}

OPContractsManagerGameTypeAdder public immutable opcmGameTypeAdder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ import { IBigStepper } from "interfaces/dispute/IBigStepper.sol";
/// before and after an upgrade.
contract OPContractsManagerStandardValidator is ISemver {
/// @notice The semantic version of the OPContractsManagerStandardValidator contract.
/// @custom:semver 2.5.0
string public constant version = "2.5.0";
/// @custom:semver 2.6.0
string public constant version = "2.6.0";

/// @notice The SuperchainConfig contract.
ISuperchainConfig public superchainConfig;
Expand Down Expand Up @@ -437,24 +437,22 @@ contract OPContractsManagerStandardValidator is ISemver {
{
IOptimismPortal2 _portal = IOptimismPortal2(payable(_sysCfg.optimismPortal()));

if (DevFeatures.isDevFeatureEnabled(devFeatureBitmap, DevFeatures.OPTIMISM_PORTAL_INTEROP)) {
_errors = internalRequire(
LibString.eq(getVersion(address(_portal)), string.concat(getVersion(optimismPortalInteropImpl))),
"PORTAL-10",
_errors
);
_errors = internalRequire(
getProxyImplementation(_admin, address(_portal)) == optimismPortalInteropImpl, "PORTAL-20", _errors
);
// Use the OptimismPortalInteropImpl only when OPTIMISM_PORTAL_INTEROP is enabled without OPCM_V2 (legacy path).
// With OPCM_V2, the portal is always optimismPortalImpl regardless of INTEROP.
address expectedPortalImpl;
if (isOPCMV1AndInterop(devFeatureBitmap)) {
expectedPortalImpl = optimismPortalInteropImpl;
} else {
_errors = internalRequire(
LibString.eq(getVersion(address(_portal)), getVersion(optimismPortalImpl)), "PORTAL-10", _errors
);
_errors = internalRequire(
getProxyImplementation(_admin, address(_portal)) == optimismPortalImpl, "PORTAL-20", _errors
);
expectedPortalImpl = optimismPortalImpl;
}

_errors = internalRequire(
LibString.eq(getVersion(address(_portal)), getVersion(expectedPortalImpl)), "PORTAL-10", _errors
);
_errors = internalRequire(
getProxyImplementation(_admin, address(_portal)) == expectedPortalImpl, "PORTAL-20", _errors
);

IDisputeGameFactory _dgf = IDisputeGameFactory(_sysCfg.disputeGameFactory());
_errors = internalRequire(address(_portal.disputeGameFactory()) == address(_dgf), "PORTAL-30", _errors);
_errors = internalRequire(address(_portal.systemConfig()) == address(_sysCfg), "PORTAL-40", _errors);
Expand Down Expand Up @@ -767,6 +765,13 @@ contract OPContractsManagerStandardValidator is ISemver {
return errors_;
}

/// @notice Helper function to clean up branching logic in the assertValidOptimismPortal block where
/// The PortalImplementation address is conditional based on the feature flags currently set
function isOPCMV1AndInterop(bytes32 _devFeatureBitmap) internal pure returns (bool) {
return DevFeatures.isDevFeatureEnabled(_devFeatureBitmap, DevFeatures.OPTIMISM_PORTAL_INTEROP)
&& !DevFeatures.isDevFeatureEnabled(_devFeatureBitmap, DevFeatures.OPCM_V2);
}

/// @notice Returns true if the game type is a super game type.
function isSuperGame(GameType _gameType) internal pure returns (bool) {
return GameTypes.isSuperGame(_gameType);
Expand Down
Loading
Loading