Last active
September 18, 2021 10:46
-
-
Save krebernisak/682ea5431dba61e672fbfca91d28bf9e to your computer and use it in GitHub Desktop.
Proposal for L2 xDomain Forwarder (with and escape hatch for upgrades)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "../ConfirmedOwner.v2.sol"; | |
import "./interfaces/ForwarderInterface.sol"; | |
import "./vendor/openzeppelin-solidity/v4.3.1/contracts/utils/Address.sol"; | |
/** | |
* @title CrossDomainForwarder - L1 xDomain account representation | |
* @notice L2 Contract which receives messages from a specific L1 address and transparently forwards them to the destination. | |
* @dev Any other L2 contract which uses this contract's address as a privileged position, | |
* can be considered to be owned by the L1 xDomain account | |
*/ | |
abstract contract CrossDomainForwarderV2 is ForwarderInterface, ConfirmedOwnerV2 { | |
bool private s_escapeHatchOpen = false; | |
bool private s_escapeHatchUpdatePending = false; | |
/** | |
* @notice creates a new xDomain Forwarder contract | |
* @param xDomainOwner the L1 owner address with access | |
*/ | |
constructor( | |
address xDomainOwner | |
) | |
ConfirmedOwnerV2(xDomainOwner) | |
{} | |
/** | |
* @notice receives messages from and forwards (call) them to the destination. | |
* @inheritdoc ForwarderInterface | |
*/ | |
function forward( | |
address target, | |
bytes memory data | |
) | |
override | |
external | |
{ | |
// 1. The call MUST come from an owner (xDomain call or local in emergencies) | |
_validateOwner(); | |
// 2. Make the external call | |
Address.functionCall(target, data, "Forwarder: call reverted"); | |
} | |
/// @notice receives messages from and forwards (delegatecall) them to the destination. | |
function forwardDelegateCall( | |
address target, | |
bytes memory data | |
) | |
external | |
{ | |
// 1. The call MUST come from an valid owner (xDomain call or local in emergencies) | |
_validateOwner(); | |
// 2. Before delegate hook to check state | |
bytes32 snapshot = _preDelegateCheck(); | |
// 3. Make the external delegatecall | |
Address.functionDelegateCall(target, data, "Forwarder: delegatecall reverted"); | |
// 4. Post delegate hook to check state | |
_postDelegateCheck(snapshot); | |
} | |
/// @notice opens the hatch and transfers ownership to local guardian acc. | |
function openHatch( | |
address guardian | |
) | |
external | |
{ | |
require(!s_escapeHatchOpen, "Forwarder: hatch already opened"); | |
// 1. The call MUST come from an valid owner (xDomain call - emergency started) | |
_validateOwner(); | |
// 2. Ownership transfer to local guardian acc | |
transferOwnership(guardian); | |
// 3. Schedule escape hatch state update on ownership transfer accepted | |
s_escapeHatchUpdatePending = true; | |
} | |
/// @notice closes the hatch and transfers ownership to xDomain owner acc | |
function closeHatch( | |
address xDomainOwner | |
) | |
external | |
{ | |
require(s_escapeHatchOpen, "Forwarder: hatch already closed"); | |
// 1. The call MUST come from an valid owner (local call - emergency ended) | |
_validateOwner(); | |
// 2. Ownership transfer to xDomain owner acc | |
transferOwnership(xDomainOwner); | |
// 3. Schedule escape hatch state update on ownership transfer accepted | |
s_escapeHatchUpdatePending = true; | |
} | |
/// @return true is the escape hatch is open | |
function escapeHatchOpen() | |
external | |
view | |
virtual | |
returns (bool) | |
{ | |
return s_escapeHatchOpen; | |
} | |
/// @return true if the escape hatch state update is pending | |
function escapeHatchUpdatePending() | |
external | |
view | |
virtual | |
returns (bool) | |
{ | |
return s_escapeHatchUpdatePending; | |
} | |
/// @notice execute the escape hatch state pending update | |
function _acceptOwnership() | |
internal | |
override | |
{ | |
super._acceptOwnership(); | |
// Flip the switch if pending | |
if (s_escapeHatchUpdatePending) { | |
s_escapeHatchOpen = !s_escapeHatchOpen; | |
s_escapeHatchUpdatePending = false; | |
} | |
} | |
/// @notice validate owner access | |
function _validateOwner() | |
internal | |
override | |
view | |
{ | |
// Currently either L1 xDomain or L2 local owner | |
// TODO: potentially allow access by both L1 owner and L2 owner (same time) so we are able to avoid Seq censorship options (long-term) | |
if (s_escapeHatchOpen) { | |
super._validateOwner(); | |
} else { | |
_validateXDomainSender(owner()); | |
} | |
} | |
/// @notice validate pending owner access | |
function _validatePendingOwner() | |
internal | |
override | |
view | |
{ | |
if (s_escapeHatchOpen && !s_escapeHatchUpdatePending) { | |
super._validatePendingOwner(); | |
} else { | |
_validateXDomainSender(pendingOwner()); | |
} | |
} | |
/// @notice validate xDomain sender (abstract) | |
function _validateXDomainSender( | |
address requiredSender | |
) | |
internal | |
virtual | |
view; | |
/// @notice pre delegate hook to prepare for incoming delegatecall validation | |
function _preDelegateCheck() | |
internal | |
virtual | |
view | |
returns (bytes32) | |
{ | |
return _takeSnapshot(); | |
} | |
/// @notice post delegate hook to check if delegate call was legal | |
function _postDelegateCheck( | |
bytes32 snapshot | |
) | |
internal | |
virtual | |
view | |
returns (bytes32) | |
{ | |
require(snapshot == _takeSnapshot(), "Forwarder: illegal delegatecall"); | |
} | |
/// @notice takes state snapshot so changes could be verified | |
function _takeSnapshot() | |
internal | |
virtual | |
view | |
returns (bytes32) | |
{ | |
return keccak256( | |
abi.encodePacked( | |
owner(), | |
pendingOwner(), | |
s_escapeHatchOpen, | |
s_escapeHatchUpdatePending | |
) | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment