Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add arbitrum specific gateway contracts #7

Merged
merged 11 commits into from
Jan 20, 2022
32 changes: 32 additions & 0 deletions contracts/src/bridge/IL1Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IL1Bridge {
/**
* Sends an arbitrary message from one domain to another.
*
* @dev The caller needs to pay some ETH to cover the gas costs
* of the call on L2. Excess ETH that is not used by gas costs will
* be refunded to the `msg.sender` address on L2.
*
* @notice if a user does not desire immediate redemption, they should
* provide a DepositValue of at least CallValue + MaxSubmissionCost.
* If they do desire immediate execution, they should provide a DepositValue
* of at least CallValue + MaxSubmissionCost + (GasPrice x MaxGas).
*
* @param _calldata The L2 encoded message data.
* @param _maxGas Gas limit for immediate L2 execution attempt.
* @param _gasPriceBid L2 Gas price bid for immediate L2 execution attempt.
* @return Unique id to track the message request/transaction.
*/
function sendCrossDomainMessage(
bytes memory _calldata,
uint256 _maxGas,
uint256 _gasPriceBid
) external payable returns (uint256);

function getSubmissionPrice(uint256 _calldatasize) external view returns (uint256);

function onlyAuthorized() external;
}
15 changes: 15 additions & 0 deletions contracts/src/bridge/IL2Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IL2Bridge {
/**
* Sends an arbitrary message from one domain to another.
*
* @param _calldata The L1 encoded message data.
* @return Unique id to track the message request/transaction.
*/
function sendCrossDomainMessage(bytes memory _calldata) external returns (uint256);

function onlyAuthorized() external;
}
39 changes: 39 additions & 0 deletions contracts/src/bridge/arbitrum/AddressAliasHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: Apache-2.0

/*
* Copyright 2019-2021, Offchain Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

pragma solidity >=0.7.0;

library AddressAliasHelper {
uint160 constant offset = uint160(0x1111000000000000000000000000000000001111);

/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
l2Address = address(uint160(l1Address) + offset);
}

/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
l1Address = address(uint160(l2Address) - offset);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
pragma solidity ^0.8.0;

import "./interfaces/IInbox.sol";
import "./interfaces/IOutbox.sol";
import "./interfaces/IArbRetryableTx.sol";

contract L1Bridge {
import "../IL1Bridge.sol";

contract ArbL1Bridge is IL1Bridge {
address public l2Target;
IInbox public inbox;
IArbRetryableTx constant arbRetryableTx = IArbRetryableTx(address(110));
Expand Down Expand Up @@ -39,7 +42,8 @@ contract L1Bridge {
uint256 _maxGas,
uint256 _gasPriceBid
) external payable returns (uint256) {
(uint256 baseSubmissionCost, ) = arbRetryableTx.getSubmissionPrice(_calldata.length);
uint256 baseSubmissionCost = getSubmissionPrice(_calldata.length);
require(msg.value >= baseSubmissionCost + (_maxGas * _gasPriceBid));

uint256 ticketID = inbox.createRetryableTicket{value: msg.value}(
l2Target,
Expand All @@ -55,4 +59,15 @@ contract L1Bridge {
emit RetryableTicketCreated(ticketID);
return ticketID;
}

function getSubmissionPrice(uint256 _calldatasize) public view returns (uint256) {
(uint256 submissionCost, ) = arbRetryableTx.getSubmissionPrice(_calldatasize);
return submissionCost;
}

function onlyAuthorized() external {
IOutbox outbox = IOutbox(inbox.bridge().activeOutbox());
address l2Sender = outbox.l2ToL1Sender();
require(l2Sender == l2Target, "Only L2 target");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
pragma solidity ^0.8.0;

import "./interfaces/IArbSys.sol";
import "./AddressAliasHelper.sol";

contract L2Bridge {
import "../IL2Bridge.sol";

contract ArbL2Bridge is IL2Bridge {
address public l1Target;
IArbSys constant arbsys = IArbSys(address(100));

Expand All @@ -20,10 +23,14 @@ contract L2Bridge {
* @param _calldata The L1 encoded message data.
* @return Unique id to track the message request/transaction.
*/
function sendCrossDomainMessage(bytes memory _calldata) external payable returns (uint256) {
function sendCrossDomainMessage(bytes memory _calldata) external returns (uint256) {
uint256 withdrawalId = arbsys.sendTxToL1(l1Target, _calldata);

emit L2ToL1TxCreated(withdrawalId);
return withdrawalId;
}

function onlyAuthorized() external {
require(msg.sender == AddressAliasHelper.applyL1ToL2Alias(l1Target), "Only L1 target");
}
}
18 changes: 18 additions & 0 deletions contracts/src/bridge/xdai/interfaces/IAMB.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IAMB {
function requireToPassMessage(
address _contract,
bytes memory _data,
uint256 _gas
) external returns (bytes32);

function maxGasPerTx() external view returns (uint256);

function messageSender() external view returns (address);

function messageSourceChainId() external view returns (bytes32);

function messageId() external view returns (bytes32);
}
42 changes: 42 additions & 0 deletions contracts/src/bridge/xdai/xDaiL1Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./interfaces/IAMB.sol";

import "../IL1Bridge.sol";

contract xDaiL1Bridge is IL1Bridge {
address public l2Target;
IAMB amb;

constructor(address _l2Target, IAMB _amb) {
l2Target = _l2Target;
amb = _amb;
}

function sendCrossDomainMessage(
bytes memory _calldata,
uint256 _maxGas,
uint256 _gasPriceBid
) external payable returns (uint256) {
bytes32 id = amb.requireToPassMessage(l2Target, _calldata, amb.maxGasPerTx());
return uint256(id);
}

/**
* @dev The xDai bridge gas cost doesn't depend on the calldata size
*
*/
function getSubmissionPrice(
uint256 /* _calldatasize */
) public view returns (uint256) {
return 0;
}

function onlyAuthorized() external {
require(msg.sender == address(amb), "Only AMB allowed");
// require(amb.messageSourceChainId() == foreignChainId, "Only foreign chain allowed");
require(amb.messageSender() == l2Target, "Only foreign gateway allowed");
}
}
28 changes: 28 additions & 0 deletions contracts/src/bridge/xdai/xDaiL2Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./interfaces/IAMB.sol";

import "../IL2Bridge.sol";

contract xDaiL2Bridge is IL2Bridge {
address public l1Target;
IAMB amb;

constructor(address _l1Target, IAMB _amb) {
l1Target = _l1Target;
amb = _amb;
}

function sendCrossDomainMessage(bytes memory _calldata) external returns (uint256) {
bytes32 id = amb.requireToPassMessage(l1Target, _calldata, amb.maxGasPerTx());
return uint256(id);
}

function onlyAuthorized() external {
require(msg.sender == address(amb), "Only AMB allowed");
// require(amb.messageSourceChainId() == homeChainId, "Only home chain allowed");
require(amb.messageSender() == l1Target, "Only home gateway allowed");
}
}
127 changes: 127 additions & 0 deletions contracts/src/gateway/BaseForeignGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../arbitration/IArbitrable.sol";
import "../bridge/IL1Bridge.sol";

import "./interfaces/IHomeGateway.sol";
import "./interfaces/IForeignGateway.sol";

abstract contract BaseForeignGateway is IL1Bridge, IForeignGateway {
// @dev Note the disputeID needs to start from one as
// the KlerosV1 proxy governor depends on this implementation.
uint256 internal localDisputeID = 1;

// For now this is just a constant, but we'd probably need to
// implement the same arbitrationCost calculation code we'll have
// in the V2 court.
uint256 internal internalArbitrationCost;

struct DisputeData {
uint256 id;
address arbitrable;
}
mapping(uint256 => bytes32) public disputeIDtoHash;
mapping(bytes32 => DisputeData) public disputeHashtoDisputeData;

IHomeGateway public homeGateway;
uint256 public chainID;

modifier onlyFromL2() {
this.onlyAuthorized();
_;
}

constructor(uint256 _arbitrationCost, IHomeGateway _homeGateway) {
internalArbitrationCost = _arbitrationCost;
homeGateway = _homeGateway;

uint256 id;
assembly {
id := chainid()
}
chainID = id;
}

function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) {
require(msg.value >= arbitrationCost(_extraData), "Not paid enough for arbitration");

disputeID = localDisputeID++;
bytes32 disputeHash = keccak256(
abi.encodePacked(
chainID,
blockhash(block.number - 1),
"createDispute",
disputeID,
arbitrationCost(_extraData),
_extraData,
msg.sender
)
);
disputeIDtoHash[disputeID] = disputeHash;
disputeHashtoDisputeData[disputeHash] = DisputeData({id: disputeID, arbitrable: msg.sender});

bytes4 methodSelector = IHomeGateway.relayCreateDispute.selector;
bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _choices, _extraData);

uint256 bridgeCost = this.getSubmissionPrice(data.length);
// We only pay for the submissionPrice gas cost
// which is minimum gas cost required for submitting a
// arbitrum retryable ticket to the retry buffer for
// bridge to L2.
// For immediate inclusion a user/bot needs to pay (GasPrice x MaxGas)
// with the associated ticketId that is emitted by this function
// after the ticket is successfully submitted.
// For more details, see:
// https://developer.offchainlabs.com/docs/l1_l2_messages#retryable-tickets-contract-api
//
// We do NOT forward the arbitrationCost ETH funds to the HomeGateway yet,
// only the calldata.
this.sendCrossDomainMessage{value: bridgeCost}(data, 0, 0);

emit DisputeCreation(disputeID, IArbitrable(msg.sender));
}

function arbitrationCost(bytes calldata _extraData) public view returns (uint256 cost) {
// Calculate the size of calldata that will be passed to the L2 bridge
// as that is a factor for the bridging cost.
// Calldata size of relayCreateDispute:
// relayCreateDispute methodId +
// (createDispute methodId + bytes32 disputeHash + uint256 _choices + bytes _extraData)
// 4 + 4 + 32 + 32 + dynamic
uint256 calldatasize = 82 + _extraData.length;

uint256 bridgeCost = this.getSubmissionPrice(calldatasize);
return bridgeCost + internalArbitrationCost;
}

/**
* Relay the rule call from the home gateway to the arbitrable.
*/
function relayRule(bytes32 _disputeHash, uint256 _ruling) external onlyFromL2 {
DisputeData memory dispute = disputeHashtoDisputeData[_disputeHash];

IArbitrable arbitrable = IArbitrable(dispute.arbitrable);
arbitrable.rule(dispute.id, _ruling);
}

function foreignDisputeHashToID(bytes32 _disputeHash) external view returns (uint256) {
return disputeHashtoDisputeData[_disputeHash].id;
}

function disputeID(uint256 _foreignDisputeID) external view returns (uint256) {
bytes32 disputeHash = disputeIDtoHash[_foreignDisputeID];
require(disputeHash != 0, "Dispute does not exist");

return homeGateway.homeDisputeHashToID(disputeHash);
}

function homeChainID(uint256 _disputeID) external view returns (uint256) {
return homeGateway.chainID();
}

function homeBridge(uint256 _disputeID) external view returns (address) {
return address(homeGateway);
}
}
Loading