@@ -70,6 +70,10 @@ contract Portal is Initializable, ResourceMetering, ISemver {
7070 /// It is not safe to trust `ERC20.balanceOf` as it may lie.
7171 uint256 internal _balance;
7272
73+ // Owner of the current chain
74+ address public chainOwner;
75+
76+
7377 /// @notice Emitted when a transaction is deposited from L1 to L2.
7478 /// The parameters of this event are read by the rollup node and used to derive deposit
7579 /// transactions on L2.
@@ -90,16 +94,42 @@ contract Portal is Initializable, ResourceMetering, ISemver {
9094 /// @param success Whether the withdrawal transaction was successful.
9195 event WithdrawalFinalized (bytes32 indexed withdrawalHash , bool success );
9296
97+ /// @notice Emitted when a chain owner is set
98+ /// @param chainOwner address of the chain owner
99+ event ChainOwnerSet (address chainOwner );
100+
101+ /// @notice Emitted when the chain owner executes a withdrawal.
102+ /// @param recipient The address that received the funds.
103+ /// @param token The token address (Constants.ETHER for native ETH).
104+ /// @param amount The amount withdrawn.
105+ event ChainOwnerExitWithdrawal (address indexed recipient , address indexed token , uint256 amount );
106+
93107 /// @notice Reverts when paused.
94108 modifier whenNotPaused () {
95109 if (paused ()) revert CallPaused ();
96110 _;
97111 }
98112
113+ /// @notice Reverts if caller is not the proxy admin.
114+ modifier onlyAdmin () {
115+ address admin;
116+ bytes32 adminSlot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 ;
117+ assembly {
118+ admin := sload (adminSlot)
119+ }
120+ require (msg .sender == admin, "Portal: caller is not admin " );
121+ _;
122+ }
123+
124+ modifier onlyChainOwner () {
125+ require (msg .sender == chainOwner, "Portal: caller is not chain owner " );
126+ _;
127+ }
128+
99129 /// @notice Semantic version.
100- /// @custom:semver 1.0 .0
130+ /// @custom:semver 1.1 .0
101131 function version () public pure virtual returns (string memory ) {
102- return "1.0 .0 " ;
132+ return "1.1 .0 " ;
103133 }
104134
105135 /// @notice Constructs the OptimismPortal contract.
@@ -383,7 +413,12 @@ contract Portal is Initializable, ResourceMetering, ISemver {
383413 }
384414
385415 _depositTransaction ({
386- _to: _to, _mint: _mint, _value: _value, _gasLimit: _gasLimit, _isCreation: _isCreation, _data: _data
416+ _to: _to,
417+ _mint: _mint,
418+ _value: _value,
419+ _gasLimit: _gasLimit,
420+ _isCreation: _isCreation,
421+ _data: _data
387422 });
388423 }
389424
@@ -405,7 +440,12 @@ contract Portal is Initializable, ResourceMetering, ISemver {
405440 if (token != Constants.ETHER && msg .value != 0 ) revert NoValue ();
406441
407442 _depositTransaction ({
408- _to: _to, _mint: msg .value , _value: _value, _gasLimit: _gasLimit, _isCreation: _isCreation, _data: _data
443+ _to: _to,
444+ _mint: msg .value ,
445+ _value: _value,
446+ _gasLimit: _gasLimit,
447+ _isCreation: _isCreation,
448+ _data: _data
409449 });
410450 }
411451
@@ -479,6 +519,53 @@ contract Portal is Initializable, ResourceMetering, ISemver {
479519 );
480520 }
481521
522+ function setChainOwner (address _chainOwner ) external onlyAdmin {
523+ chainOwner = _chainOwner;`
524+ emit ChainOwnerSet (chainOwner);
525+ }
526+
527+ /// @notice Allows owner to withdraw the gas paying token held by the portal.
528+ /// Can be called regardless of pause state.
529+ /// @param _recipient The address to receive the withdrawn funds.
530+ function chainOwnerExitPortalNetworkToken (address _recipient ) external onlyChainOwner {
531+ chainOwnerExitPortal (address (0 ), _recipient);
532+ }
533+
534+ /// @notice Allows owner to withdraw all tokens held by the portal.
535+ /// Can be called regardless of pause state.
536+ /// @param _asset The token address to withdraw, or address(0) for the gas paying token.
537+ /// @param _recipient The address to receive the withdrawn funds.
538+ function chainOwnerExitPortal (address _asset , address _recipient ) public onlyChainOwner {
539+ require (_recipient != address (0 ), "Portal: zero recipient " );
540+
541+ address token;
542+ if (_asset != address (0 )) {
543+ token = _asset;
544+ } else {
545+ (token,) = gasPayingToken ();
546+ }
547+
548+ uint256 amount;
549+ if (token == Constants.ETHER) {
550+ amount = address (this ).balance;
551+ if (amount > 0 ) {
552+ (bool success ,) = _recipient.call {value: amount}("" );
553+ require (success, "Portal: ETH transfer failed " );
554+ }
555+ } else {
556+ amount = IERC20 (token).balanceOf (address (this ));
557+ if (amount > 0 ) {
558+ (address gasToken ,) = gasPayingToken ();
559+ if (token == gasToken) {
560+ _balance = 0 ;
561+ }
562+ IERC20 (token).safeTransfer (_recipient, amount);
563+ }
564+ }
565+
566+ emit ChainOwnerExitWithdrawal (_recipient, token, amount);
567+ }
568+
482569 /// @notice Determine if a given output is finalized.
483570 /// Reverts if the call to l2Oracle.getL2Output reverts.
484571 /// Returns a boolean otherwise.
0 commit comments