Skip to content

Instantly share code, notes, and snippets.

@krebernisak
Last active September 18, 2021 10:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save krebernisak/682ea5431dba61e672fbfca91d28bf9e to your computer and use it in GitHub Desktop.
Save krebernisak/682ea5431dba61e672fbfca91d28bf9e to your computer and use it in GitHub Desktop.
Proposal for L2 xDomain Forwarder (with and escape hatch for upgrades)
// 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