diff --git a/contracts/src/bridge/IL1Bridge.sol b/contracts/src/bridge/IL1Bridge.sol new file mode 100644 index 000000000..817208036 --- /dev/null +++ b/contracts/src/bridge/IL1Bridge.sol @@ -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; +} diff --git a/contracts/src/bridge/IL2Bridge.sol b/contracts/src/bridge/IL2Bridge.sol new file mode 100644 index 000000000..7eab0cf79 --- /dev/null +++ b/contracts/src/bridge/IL2Bridge.sol @@ -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; +} diff --git a/contracts/src/bridge/arbitrum/AddressAliasHelper.sol b/contracts/src/bridge/arbitrum/AddressAliasHelper.sol new file mode 100644 index 000000000..5f79c1042 --- /dev/null +++ b/contracts/src/bridge/arbitrum/AddressAliasHelper.sol @@ -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); + } +} diff --git a/contracts/src/bridge/arbitrum/L1Bridge.sol b/contracts/src/bridge/arbitrum/ArbL1Bridge.sol similarity index 72% rename from contracts/src/bridge/arbitrum/L1Bridge.sol rename to contracts/src/bridge/arbitrum/ArbL1Bridge.sol index 7108a2bb8..7dd394bf9 100644 --- a/contracts/src/bridge/arbitrum/L1Bridge.sol +++ b/contracts/src/bridge/arbitrum/ArbL1Bridge.sol @@ -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)); @@ -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, @@ -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"); + } } diff --git a/contracts/src/bridge/arbitrum/L2Bridge.sol b/contracts/src/bridge/arbitrum/ArbL2Bridge.sol similarity index 73% rename from contracts/src/bridge/arbitrum/L2Bridge.sol rename to contracts/src/bridge/arbitrum/ArbL2Bridge.sol index d4938e932..a0a216447 100644 --- a/contracts/src/bridge/arbitrum/L2Bridge.sol +++ b/contracts/src/bridge/arbitrum/ArbL2Bridge.sol @@ -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)); @@ -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"); + } } diff --git a/contracts/src/bridge/xdai/interfaces/IAMB.sol b/contracts/src/bridge/xdai/interfaces/IAMB.sol new file mode 100644 index 000000000..3f5ca3d5e --- /dev/null +++ b/contracts/src/bridge/xdai/interfaces/IAMB.sol @@ -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); +} diff --git a/contracts/src/bridge/xdai/xDaiL1Bridge.sol b/contracts/src/bridge/xdai/xDaiL1Bridge.sol new file mode 100644 index 000000000..99ac7f941 --- /dev/null +++ b/contracts/src/bridge/xdai/xDaiL1Bridge.sol @@ -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"); + } +} diff --git a/contracts/src/bridge/xdai/xDaiL2Bridge.sol b/contracts/src/bridge/xdai/xDaiL2Bridge.sol new file mode 100644 index 000000000..ce30834c4 --- /dev/null +++ b/contracts/src/bridge/xdai/xDaiL2Bridge.sol @@ -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"); + } +} diff --git a/contracts/src/gateway/BaseForeignGateway.sol b/contracts/src/gateway/BaseForeignGateway.sol new file mode 100644 index 000000000..802e5d907 --- /dev/null +++ b/contracts/src/gateway/BaseForeignGateway.sol @@ -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); + } +} diff --git a/contracts/src/gateway/BaseHomeGateway.sol b/contracts/src/gateway/BaseHomeGateway.sol new file mode 100644 index 000000000..e9f9edd42 --- /dev/null +++ b/contracts/src/gateway/BaseHomeGateway.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../arbitration/IArbitrator.sol"; +import "../bridge/IL2Bridge.sol"; + +import "./interfaces/IHomeGateway.sol"; +import "./interfaces/IForeignGateway.sol"; + +abstract contract BaseHomeGateway is IL2Bridge, IHomeGateway { + mapping(uint256 => bytes32) public disputeIDtoHash; + mapping(bytes32 => uint256) public disputeHashtoID; + + IForeignGateway public foreignGateway; + IArbitrator public arbitrator; + uint256 public chainID; + + modifier onlyFromL1() { + this.onlyAuthorized(); + _; + } + + constructor(IArbitrator _arbitrator, IForeignGateway _foreignGateway) { + arbitrator = _arbitrator; + foreignGateway = _foreignGateway; + + uint256 id; + assembly { + id := chainid() + } + chainID = id; + + emit MetaEvidence(0, "BRIDGE"); + } + + function rule(uint256 _disputeID, uint256 _ruling) external { + require(msg.sender == address(arbitrator), "Only Arbitrator"); + + bytes4 methodSelector = IForeignGateway.relayRule.selector; + bytes memory data = abi.encodeWithSelector(methodSelector, disputeIDtoHash[_disputeID], _ruling); + + this.sendCrossDomainMessage(data); + } + + /** + * Relay the createDispute call from the foreign gateway to the arbitrator. + */ + function relayCreateDispute( + bytes32 _disputeHash, + uint256 _choices, + bytes calldata _extraData + ) external onlyFromL1 { + uint256 disputeID = arbitrator.createDispute(_choices, _extraData); + disputeIDtoHash[disputeID] = _disputeHash; + disputeHashtoID[_disputeHash] = disputeID; + + emit Dispute(arbitrator, disputeID, 0, 0); + } + + function homeDisputeHashToID(bytes32 _disputeHash) external view returns (uint256) { + return disputeHashtoID[_disputeHash]; + } + + function homeToForeignDisputeID(uint256 _homeDisputeID) external view returns (uint256) { + bytes32 disputeHash = disputeIDtoHash[_homeDisputeID]; + require(disputeHash != 0, "Dispute does not exist"); + + return foreignGateway.foreignDisputeHashToID(disputeHash); + } + + function foreignChainID(uint256 _homeDisputeID) external view returns (uint256) { + return foreignGateway.chainID(); + } + + function foreignBridge(uint256 _homeDisputeID) external view returns (address) { + return address(foreignGateway); + } +} diff --git a/contracts/src/gateway/arbitrum/ArbitrumGateway.sol b/contracts/src/gateway/arbitrum/ArbitrumGateway.sol new file mode 100644 index 000000000..c30bee17f --- /dev/null +++ b/contracts/src/gateway/arbitrum/ArbitrumGateway.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../arbitration/IArbitrator.sol"; +import "../../bridge/arbitrum/ArbL2Bridge.sol"; + +import "../interfaces/IHomeGateway.sol"; +import "../BaseHomeGateway.sol"; + +contract ArbitrumGateway is BaseHomeGateway, ArbL2Bridge { + constructor( + IArbitrator _arbitrator, + IForeignGateway _foreignGateway, + address _l1Target + ) BaseHomeGateway(_arbitrator, _foreignGateway) ArbL2Bridge(_l1Target) {} +} diff --git a/contracts/src/gateway/arbitrum/EthereumGateway.sol b/contracts/src/gateway/arbitrum/EthereumGateway.sol new file mode 100644 index 000000000..e90bde916 --- /dev/null +++ b/contracts/src/gateway/arbitrum/EthereumGateway.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/arbitrum/ArbL1Bridge.sol"; + +import "../interfaces/IForeignGateway.sol"; +import "../BaseForeignGateway.sol"; + +contract EthereumGateway is BaseForeignGateway, ArbL1Bridge { + constructor( + uint256 _arbitrationCost, + IHomeGateway _homeGateway, + address _l2Target, + address _inbox + ) BaseForeignGateway(_arbitrationCost, _homeGateway) ArbL1Bridge(_l2Target, _inbox) {} +} diff --git a/contracts/src/gateway/interfaces/IForeignEvidence.sol b/contracts/src/gateway/interfaces/IForeignEvidence.sol new file mode 100644 index 000000000..88b4d0538 --- /dev/null +++ b/contracts/src/gateway/interfaces/IForeignEvidence.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IForeignEvidence { + function disputeID(uint256 _foreignDisputeID) external view returns (uint256); + + function homeChainID(uint256 _disputeID) external view returns (uint256); + + function homeBridge(uint256 _disputeID) external view returns (address); +} diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol new file mode 100644 index 000000000..5732f12a9 --- /dev/null +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../arbitration/IArbitrator.sol"; +import "./IForeignEvidence.sol"; + +interface IForeignGateway is IArbitrator, IForeignEvidence { + function chainID() external view returns (uint256); + + function foreignDisputeHashToID(bytes32 _disputeHash) external view returns (uint256); + + /** + * Relay the rule call from the home gateway to the arbitrable. + */ + function relayRule(bytes32 _disputeHash, uint256 _ruling) external; +} diff --git a/contracts/src/gateway/interfaces/IHomeEvidence.sol b/contracts/src/gateway/interfaces/IHomeEvidence.sol new file mode 100644 index 000000000..bda0011d4 --- /dev/null +++ b/contracts/src/gateway/interfaces/IHomeEvidence.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../evidence/IEvidence.sol"; + +interface IHomeEvidence is IEvidence { + function homeToForeignDisputeID(uint256 _homeDisputeID) external view returns (uint256); + + function foreignChainID(uint256 _homeDisputeID) external view returns (uint256); + + function foreignBridge(uint256 _homeDisputeID) external view returns (address); +} diff --git a/contracts/src/gateway/interfaces/IHomeGateway.sol b/contracts/src/gateway/interfaces/IHomeGateway.sol new file mode 100644 index 000000000..4f018d9c1 --- /dev/null +++ b/contracts/src/gateway/interfaces/IHomeGateway.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../arbitration/IArbitrable.sol"; +import "./IHomeEvidence.sol"; + +interface IHomeGateway is IArbitrable, IHomeEvidence { + function chainID() external view returns (uint256); + + function homeDisputeHashToID(bytes32 _disputeHash) external view returns (uint256); + + /** + * Relay the createDispute call from the foreign gateway to the arbitrator. + */ + function relayCreateDispute( + bytes32 _disputeHash, + uint256 _choices, + bytes calldata _extraData + ) external; +} diff --git a/contracts/src/gateway/xdai/EthereumGateway.sol b/contracts/src/gateway/xdai/EthereumGateway.sol new file mode 100644 index 000000000..44759d8bd --- /dev/null +++ b/contracts/src/gateway/xdai/EthereumGateway.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/xdai/xDaiL1Bridge.sol"; +import "../../bridge/xdai/interfaces/IAMB.sol"; + +import "../interfaces/IForeignGateway.sol"; +import "../BaseForeignGateway.sol"; + +contract EthereumGateway is BaseForeignGateway, xDaiL1Bridge { + constructor( + uint256 _arbitrationCost, + IHomeGateway _homeGateway, + address _l2Target, + IAMB _amb + ) BaseForeignGateway(_arbitrationCost, _homeGateway) xDaiL1Bridge(_l2Target, _amb) {} +} diff --git a/contracts/src/gateway/xdai/xDaiGateway.sol b/contracts/src/gateway/xdai/xDaiGateway.sol new file mode 100644 index 000000000..6effc4b92 --- /dev/null +++ b/contracts/src/gateway/xdai/xDaiGateway.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../arbitration/IArbitrator.sol"; +import "../../bridge/xdai/xDaiL2Bridge.sol"; +import "../../bridge/xdai/interfaces/IAMB.sol"; + +import "../interfaces/IHomeGateway.sol"; +import "../BaseHomeGateway.sol"; + +contract xDaiGateway is BaseHomeGateway, xDaiL2Bridge { + constructor( + IArbitrator _arbitrator, + IForeignGateway _foreignGateway, + address _l1Target, + IAMB _amb + ) BaseHomeGateway(_arbitrator, _foreignGateway) xDaiL2Bridge(_l1Target, _amb) {} +}