Skip to content

Commit

Permalink
feat: forwarders for AVM and OVM networks (#625)
Browse files Browse the repository at this point in the history
* feat: forwarders for AVM and OVM networks

Signed-off-by: bennett <bennett@umaproject.org>

---------

Signed-off-by: bennett <bennett@umaproject.org>
Co-authored-by: nicholaspai <9457025+nicholaspai@users.noreply.github.com>
Co-authored-by: Matt Rice <matthewcrice32@gmail.com>
  • Loading branch information
3 people authored Oct 3, 2024
1 parent abe2f7f commit 3d61c1b
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 22 deletions.
14 changes: 2 additions & 12 deletions contracts/Arbitrum_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.19;

import "./SpokePool.sol";
import "./libraries/CircleCCTPAdapter.sol";
import { CrossDomainAddressUtils } from "./libraries/CrossDomainAddressUtils.sol";
import { ArbitrumL2ERC20GatewayLike } from "./interfaces/ArbitrumBridge.sol";

/**
Expand Down Expand Up @@ -54,7 +55,7 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
}

modifier onlyFromCrossDomainAdmin() {
require(msg.sender == _applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
require(msg.sender == CrossDomainAddressUtils.applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
_;
}

Expand Down Expand Up @@ -111,17 +112,6 @@ contract Arbitrum_SpokePool is SpokePool, CircleCCTPAdapter {
emit WhitelistedTokens(_l2Token, _l1Token);
}

// L1 addresses are transformed during l1->l2 calls.
// See https://developer.offchainlabs.com/docs/l1_l2_messages#address-aliasing for more information.
// This cannot be pulled directly from Arbitrum contracts because their contracts are not 0.8.X compatible and
// this operation takes advantage of overflows, whose behavior changed in 0.8.0.
function _applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
// Allows overflows as explained above.
unchecked {
l2Address = address(uint160(l1Address) + uint160(0x1111000000000000000000000000000000001111));
}
}

// Apply AVM-specific transformation to cross domain admin address on L1.
function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}
}
12 changes: 2 additions & 10 deletions contracts/ZkSync_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import "./SpokePool.sol";
import { CrossDomainAddressUtils } from "./libraries/CrossDomainAddressUtils.sol";

// https://github.com/matter-labs/era-contracts/blob/6391c0d7bf6184d7f6718060e3991ba6f0efe4a7/zksync/contracts/bridge/L2ERC20Bridge.sol#L104
interface ZkBridgeLike {
Expand Down Expand Up @@ -62,7 +63,7 @@ contract ZkSync_SpokePool is SpokePool {
}

modifier onlyFromCrossDomainAdmin() {
require(msg.sender == _applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
require(msg.sender == CrossDomainAddressUtils.applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
_;
}

Expand Down Expand Up @@ -118,14 +119,5 @@ contract ZkSync_SpokePool is SpokePool {
emit SetZkBridge(address(_zkErc20Bridge), oldErc20Bridge);
}

// L1 addresses are transformed during l1->l2 calls.
// See https://github.com/matter-labs/era-contracts/blob/main/docs/Overview.md#mailboxfacet for more information.
// Another source: https://github.com/matter-labs/era-contracts/blob/41c25aa16d182f757c3fed1463c78a81896f65e6/ethereum/contracts/vendor/AddressAliasHelper.sol#L28
function _applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + uint160(0x1111000000000000000000000000000000001111));
}
}

function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}
}
28 changes: 28 additions & 0 deletions contracts/chain-adapters/Arbitrum_Forwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { ForwarderBase } from "./ForwarderBase.sol";
import { CrossDomainAddressUtils } from "../libraries/CrossDomainAddressUtils.sol";

/**
* @title Arbitrum_Forwarder
* @notice This contract expects to receive messages and tokens from the hub pool on L1 and forwards messages to a spoke pool on L3.
* It rejects messages which do not originate from a cross domain admin, which is set as the hub pool.
* @custom:security-contact bugs@across.to
*/
contract Arbitrum_Forwarder is ForwarderBase {
// On Arbitrum, L1 msg.senders are derived by aliasing a L1 address.
modifier onlyFromCrossDomainAdmin() {
require(msg.sender == CrossDomainAddressUtils.applyL1ToL2Alias(crossDomainAdmin), "ONLY_COUNTERPART_GATEWAY");
_;
}

/**
* @notice Constructs an Arbitrum-specific forwarder contract.
* @dev Since this is a proxy contract, we only set immutable variables in the constructor, and leave everything else to be initialized.
* This includes variables like the cross domain admin.
*/
constructor() ForwarderBase() {}

function _requireAdminSender() internal override onlyFromCrossDomainAdmin {}
}
31 changes: 31 additions & 0 deletions contracts/chain-adapters/Ovm_Forwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { ForwarderBase } from "./ForwarderBase.sol";
import { LibOptimismUpgradeable } from "@openzeppelin/contracts-upgradeable/crosschain/optimism/LibOptimismUpgradeable.sol";
import { Lib_PredeployAddresses } from "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol";

/**
* @title Ovm_Forwarder
* @notice This contract expects to receive messages and tokens from the hub pool on L1 and forwards messages to a spoke pool on L3.
* It rejects messages which do not originate from a cross domain admin, which is set as the hub pool.
* @dev This forwarder assumes that the cross domain messenger predeploy contract is set to the same contract as the standard OpStack predeploy.
* (0x4200000000000000000000000000000000000007). This is because in order to determine the L1 msg.sender of a cross-chain message, we must
* query information from that contract.
* @custom:security-contact bugs@across.to
*/
contract Ovm_Forwarder is ForwarderBase {
// Address of the cross domain messenger contract.
address public constant MESSENGER = Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER;

error NotCrossDomainAdmin();

/**
@notice Constructs an Ovm specific forwarder contract.
*/
constructor() ForwarderBase() {}

function _requireAdminSender() internal view override {
if (LibOptimismUpgradeable.crossChainSender(MESSENGER) != crossDomainAdmin) revert NotCrossDomainAdmin();
}
}
17 changes: 17 additions & 0 deletions contracts/libraries/CrossDomainAddressUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

/**
* @title AddressUtils
* @notice This library contains internal functions for manipulating addresses.
*/
library CrossDomainAddressUtils {
// L1 addresses are transformed during l1->l2 calls.
// This cannot be pulled directly from Arbitrum contracts because their contracts are not 0.8.X compatible and
// this operation takes advantage of overflows, whose behavior changed in 0.8.0.
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + uint160(0x1111000000000000000000000000000000001111));
}
}
}

0 comments on commit 3d61c1b

Please sign in to comment.