Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 38 additions & 16 deletions contracts/HubPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,10 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {
ancillaryData = AncillaryData.appendKeyValueAddress(ancillaryData, "proposer", rootBundleProposal.proposer);
}

// This function allows a caller to load the contract with raw ETH to perform L2 calls. This is needed for arbitrum
// calls, but may also be needed for others.
function loadEthForL2Calls() public payable {}
Copy link
Member

Choose a reason for hiding this comment

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

so ugly, but neccessary. Alternatively, why not make the functions that need to pay ETH for arbitrum messages payable? I'm guessing its only the functions that call adapter.relayMessage?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As discussed IRL, we can decide if we want to address this in a follow-up, but not a super high priority, as doing it this way may end up being more complicated.


/*************************************************
* INTERNAL FUNCTIONS *
*************************************************/
Expand Down Expand Up @@ -608,13 +612,18 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {
// If the net send amount for this token is positive then: 1) send tokens from L1->L2 to facilitate the L2
// relayer refund, 2) Update the liquidity trackers for the associated pooled tokens.
if (netSendAmounts[i] > 0) {
IERC20(l1Tokens[i]).safeTransfer(address(adapter), uint256(netSendAmounts[i]));
adapter.relayTokens(
l1Tokens[i], // l1Token.
whitelistedRoutes[l1Tokens[i]][chainId], // l2Token.
uint256(netSendAmounts[i]), // amount.
crossChainContracts[chainId].spokePool // to. This should be the spokePool.
// Perform delegatecall to use the adapter's code with this contract's context. We opt for delegatecall's
// complexity in exchange for lower gas costs.
(bool success, ) = address(adapter).delegatecall(
abi.encodeWithSignature(
"relayTokens(address,address,uint256,address)",
l1Tokens[i], // l1Token.
whitelistedRoutes[l1Tokens[i]][chainId], // l2Token.
uint256(netSendAmounts[i]), // amount.
crossChainContracts[chainId].spokePool // to. This should be the spokePool.
)
);
require(success, "delegatecall failed");

// Liquid reserves is decreased by the amount sent. utilizedReserves is increased by the amount sent.
pooledTokens[l1Tokens[i]].utilizedReserves += netSendAmounts[i];
Expand All @@ -628,14 +637,20 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {

function _relayRootBundleToSpokePool(uint256 chainId) internal {
AdapterInterface adapter = crossChainContracts[chainId].adapter;
adapter.relayMessage(
crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2.

// Perform delegatecall to use the adapter's code with this contract's context.
(bool success, ) = address(adapter).delegatecall(
abi.encodeWithSignature(
"relayRootBundle(bytes32,bytes32)",
rootBundleProposal.relayerRefundRoot,
rootBundleProposal.slowRelayRoot
) // message
"relayMessage(address,bytes)",
crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2.
abi.encodeWithSignature(
"relayRootBundle(bytes32,bytes32)",
rootBundleProposal.relayerRefundRoot,
rootBundleProposal.slowRelayRoot
) // message
)
);
require(success, "delegatecall failed");
}

function _exchangeRateCurrent(address l1Token) internal returns (uint256) {
Expand Down Expand Up @@ -731,10 +746,17 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {

function _relaySpokePoolAdminFunction(uint256 chainId, bytes memory functionData) internal {
AdapterInterface adapter = crossChainContracts[chainId].adapter;
adapter.relayMessage(
crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2.
functionData
require(address(adapter) != address(0), "Adapter not initialized");

// Perform delegatecall to use the adapter's code with this contract's context.
(bool success, ) = address(adapter).delegatecall(
abi.encodeWithSignature(
"relayMessage(address,bytes)",
crossChainContracts[chainId].spokePool, // target. This should be the spokePool on the L2.
functionData
)
);
require(success, "delegatecall failed");
emit SpokePoolAdminFunctionTriggered(chainId, functionData);
}

Expand All @@ -747,7 +769,7 @@ contract HubPool is HubPoolInterface, Testable, Lockable, MultiCaller, Ownable {
// over the optimism bridge, for example. If false then this was set as a result of unwinding LP tokens, with the
// intention of sending ETH to the LP. In this case, do nothing as we intend on sending the ETH to the LP.
function _depositEthToWeth() internal {
if (functionCallStackOriginatesFromOutsideThisContract()) weth.deposit{ value: address(this).balance }();
if (functionCallStackOriginatesFromOutsideThisContract()) weth.deposit{ value: msg.value }();
}

// Added to enable the HubPool to receive ETH. This will occur both when the HubPool unwraps WETH to send to LPs and
Expand Down
52 changes: 12 additions & 40 deletions contracts/chain-adapters/Arbitrum_Adapter.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import "./Base_Adapter.sol";
import "../interfaces/AdapterInterface.sol";
import "../interfaces/AdapterInterface.sol";
import "../interfaces/WETH9.sol";

import "@uma/core/contracts/common/implementation/Lockable.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface ArbitrumL1InboxLike {
Expand All @@ -33,27 +31,27 @@ interface ArbitrumL1ERC20GatewayLike {
) external payable returns (bytes memory);
}

contract Arbitrum_Adapter is Base_Adapter, Lockable {
contract Arbitrum_Adapter is AdapterInterface {
// Gas limit for immediate L2 execution attempt (can be estimated via NodeInterface.estimateRetryableTicket).
// NodeInterface precompile interface exists at L2 address 0x00000000000000000000000000000000000000C8
uint32 public l2GasLimit = 5_000_000;
uint32 public immutable l2GasLimit = 5_000_000;

// Amount of ETH allocated to pay for the base submission fee. The base submission fee is a parameter unique to
// retryable transactions; the user is charged the base submission fee to cover the storage costs of keeping their
// ticket’s calldata in the retry buffer. (current base submission fee is queryable via
// ArbRetryableTx.getSubmissionPrice). ArbRetryableTicket precompile interface exists at L2 address
// 0x000000000000000000000000000000000000006E.
uint256 public l2MaxSubmissionCost = 0.1e18;
uint256 public immutable l2MaxSubmissionCost = 0.1e18;

// L2 Gas price bid for immediate L2 execution attempt (queryable via standard eth*gasPrice RPC)
uint256 public l2GasPrice = 10e9; // 10 gWei
uint256 public immutable l2GasPrice = 10e9; // 10 gWei

// This address on L2 receives extra ETH that is left over after relaying a message via the inbox.
address public l2RefundL2Address;
address public immutable l2RefundL2Address;

ArbitrumL1InboxLike public l1Inbox;
ArbitrumL1InboxLike public immutable l1Inbox;

ArbitrumL1ERC20GatewayLike public l1ERC20Gateway;
ArbitrumL1ERC20GatewayLike public immutable l1ERC20Gateway;

event L2GasLimitSet(uint32 newL2GasLimit);

Expand All @@ -63,38 +61,14 @@ contract Arbitrum_Adapter is Base_Adapter, Lockable {

event L2RefundL2AddressSet(address newL2RefundL2Address);

constructor(
address _hubPool,
ArbitrumL1InboxLike _l1ArbitrumInbox,
ArbitrumL1ERC20GatewayLike _l1ERC20Gateway
) Base_Adapter(_hubPool) {
constructor(ArbitrumL1InboxLike _l1ArbitrumInbox, ArbitrumL1ERC20GatewayLike _l1ERC20Gateway) {
l1Inbox = _l1ArbitrumInbox;
l1ERC20Gateway = _l1ERC20Gateway;

l2RefundL2Address = owner();
}

function setL2GasLimit(uint32 _l2GasLimit) public onlyOwner {
l2GasLimit = _l2GasLimit;
emit L2GasLimitSet(l2GasLimit);
}

function setL2MaxSubmissionCost(uint256 _l2MaxSubmissionCost) public onlyOwner {
l2MaxSubmissionCost = _l2MaxSubmissionCost;
emit L2MaxSubmissionCostSet(l2MaxSubmissionCost);
l2RefundL2Address = msg.sender;
}

function setL2GasPrice(uint256 _l2GasPrice) public onlyOwner {
l2GasPrice = _l2GasPrice;
emit L2GasPriceSet(l2GasPrice);
}

function setL2RefundL2Address(address _l2RefundL2Address) public onlyOwner {
l2RefundL2Address = _l2RefundL2Address;
emit L2RefundL2AddressSet(l2RefundL2Address);
}

function relayMessage(address target, bytes memory message) external payable override nonReentrant onlyHubPool {
function relayMessage(address target, bytes memory message) external payable override {
uint256 requiredL1CallValue = getL1CallValue();
require(address(this).balance >= requiredL1CallValue, "Insufficient ETH balance");

Expand All @@ -117,14 +91,12 @@ contract Arbitrum_Adapter is Base_Adapter, Lockable {
address l2Token, // l2Token is unused for Arbitrum.
uint256 amount,
address to
) external payable override nonReentrant onlyHubPool {
) external payable override {
l1ERC20Gateway.outboundTransfer(l1Token, to, amount, l2GasLimit, l2GasPrice, "");
emit TokensRelayed(l1Token, l2Token, amount, to);
}

function getL1CallValue() public view returns (uint256) {
return l2MaxSubmissionCost + l2GasPrice * l2GasLimit;
}

receive() external payable {}
}
23 changes: 0 additions & 23 deletions contracts/chain-adapters/Base_Adapter.sol

This file was deleted.

80 changes: 80 additions & 0 deletions contracts/chain-adapters/CrossDomainEnabled.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.9.0;

/* Interface Imports */
import { ICrossDomainMessenger } from "@eth-optimism/contracts/libraries/bridge/ICrossDomainMessenger.sol";

/**
* @title CrossDomainEnabled
* @dev Helper contract for contracts performing cross-domain communications
*
* Compiler used: defined by inheriting contract
*/
contract CrossDomainEnabled {
Copy link
Member

Choose a reason for hiding this comment

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

/*************
* Variables *
*************/

// Messenger contract used to send and recieve messages from the other domain.
address public immutable messenger;

/***************
* Constructor *
***************/

/**
* @param _messenger Address of the CrossDomainMessenger on the current layer.
*/
constructor(address _messenger) {
messenger = _messenger;
}

/**********************
* Function Modifiers *
**********************/

/**
* Enforces that the modified function is only callable by a specific cross-domain account.
* @param _sourceDomainAccount The only account on the originating domain which is
* authenticated to call this function.
*/
modifier onlyFromCrossDomainAccount(address _sourceDomainAccount) {
require(msg.sender == address(getCrossDomainMessenger()), "OVM_XCHAIN: messenger contract unauthenticated");

require(
getCrossDomainMessenger().xDomainMessageSender() == _sourceDomainAccount,
"OVM_XCHAIN: wrong sender of cross-domain message"
);

_;
}

/**********************
* Internal Functions *
**********************/

/**
* Gets the messenger, usually from storage. This function is exposed in case a child contract
* needs to override.
* @return The address of the cross-domain messenger contract which should be used.
*/
function getCrossDomainMessenger() internal virtual returns (ICrossDomainMessenger) {
return ICrossDomainMessenger(messenger);
}

/**q
* Sends a message to an account on another domain
* @param _crossDomainTarget The intended recipient on the destination domain
* @param _message The data to send to the target (usually calldata to a function with
* `onlyFromCrossDomainAccount()`)
* @param _gasLimit The gasLimit for the receipt of the message on the target domain.
*/
function sendCrossDomainMessage(
address _crossDomainTarget,
uint32 _gasLimit,
bytes memory _message
) internal {
// slither-disable-next-line reentrancy-events, reentrancy-benign
getCrossDomainMessenger().sendMessage(_crossDomainTarget, _message, _gasLimit);
}
}
11 changes: 3 additions & 8 deletions contracts/chain-adapters/Ethereum_Adapter.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import "./Base_Adapter.sol";
import "../interfaces/AdapterInterface.sol";
import "../interfaces/WETH9.sol";

Expand All @@ -10,12 +9,10 @@ import "@uma/core/contracts/common/implementation/Lockable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract Ethereum_Adapter is Base_Adapter, Lockable {
contract Ethereum_Adapter is AdapterInterface {
using SafeERC20 for IERC20;

constructor(address _hubPool) Base_Adapter(_hubPool) {}

function relayMessage(address target, bytes memory message) external payable override nonReentrant onlyHubPool {
function relayMessage(address target, bytes memory message) external payable override {
_executeCall(target, message);
emit MessageRelayed(target, message);
}
Expand All @@ -26,7 +23,7 @@ contract Ethereum_Adapter is Base_Adapter, Lockable {
// on this network.
uint256 amount,
address to
) external payable override nonReentrant onlyHubPool {
) external payable override {
IERC20(l1Token).safeTransfer(to, amount);
emit TokensRelayed(l1Token, l2Token, amount, to);
}
Expand All @@ -46,6 +43,4 @@ contract Ethereum_Adapter is Base_Adapter, Lockable {
}
require(success, "execute call failed");
}

receive() external payable {}
}
8 changes: 4 additions & 4 deletions contracts/chain-adapters/Mock_Adapter.sol
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.0;

import "./Base_Adapter.sol";
import "../interfaces/AdapterInterface.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
* @notice Sends cross chain messages Optimism L2 network.
* @dev This contract's owner should be set to the BridgeAdmin deployed on the same L1 network so that only the
* BridgeAdmin can call cross-chain administrative functions on the L2 SpokePool via this messenger.
*/
contract Mock_Adapter is Base_Adapter {
contract Mock_Adapter is AdapterInterface {
event RelayMessageCalled(address target, bytes message, address caller);

event RelayTokensCalled(address l1Token, address l2Token, uint256 amount, address to, address caller);

Mock_Bridge public bridge;
Mock_Bridge public immutable bridge;

constructor(address _hubPool) Base_Adapter(_hubPool) {
constructor() {
bridge = new Mock_Bridge();
}

Expand Down
Loading