Skip to content

Commit

Permalink
Merge pull request #7 from kleros/feature/gateway
Browse files Browse the repository at this point in the history
Add arbitrum specific gateway contracts
  • Loading branch information
jaybuidl authored Jan 20, 2022
2 parents 0675548 + 16c9a02 commit f56aa9f
Show file tree
Hide file tree
Showing 18 changed files with 539 additions and 4 deletions.
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

0 comments on commit f56aa9f

Please sign in to comment.