From 737c5fe24cc679805f21d5eafefac41654896a12 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Tue, 12 Apr 2022 21:21:23 +0100 Subject: [PATCH 01/23] governance minimization and bug fixes --- .../bridge/FastBridgeReceiverOnEthereum.sol | 50 +++----------- .../src/bridge/FastBridgeSenderToEthereum.sol | 68 ++++++++----------- .../bridge/SafeBridgeReceiverOnEthereum.sol | 31 ++------- .../bridge/interfaces/IFastBridgeReceiver.sol | 5 -- .../bridge/interfaces/IFastBridgeSender.sol | 6 ++ .../src/gateway/ForeignGatewayOnEthereum.sol | 21 +++++- .../src/gateway/HomeGatewayToEthereum.sol | 11 +++ .../gateway/interfaces/IForeignGateway.sol | 1 + 8 files changed, 78 insertions(+), 115 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index e70cd9f07..9021e6216 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -46,36 +46,26 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid // * Storage * // // ************************************* // - uint256 public constant ONE_BASIS_POINT = 1e4; // One basis point, for scaling. uint256 public override claimDeposit; // The deposit required to submit a claim. uint256 public override challengeDeposit; // The deposit required to submit a challenge. uint256 public override challengeDuration; // The duration of the period allowing to challenge a claim. - uint256 public override alpha; // Basis point of claim or challenge deposit that are lost when dishonest. mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. - /** * @dev Constructor. - * @param _governor The governor's address. - * @param _safeBridgeSender The address of the Safe Bridge sender on Arbitrum. * @param _inbox The address of the Arbitrum Inbox contract. * @param _claimDeposit The deposit amount to submit a claim in wei. * @param _challengeDeposit The deposit amount to submit a challenge in wei. * @param _challengeDuration The duration of the period allowing to challenge a claim. - * @param _alpha Basis point of claim or challenge deposit that are lost when dishonest. */ constructor( - address _governor, - address _safeBridgeSender, address _inbox, uint256 _claimDeposit, uint256 _challengeDeposit, - uint256 _challengeDuration, - uint256 _alpha - ) SafeBridgeReceiverOnEthereum(_governor, _safeBridgeSender, _inbox) { + uint256 _challengeDuration + ) SafeBridgeReceiverOnEthereum(_inbox) { claimDeposit = _claimDeposit; challengeDeposit = _challengeDeposit; challengeDuration = _challengeDuration; - alpha = _alpha; } // ************************************* // @@ -127,18 +117,18 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid /** * @dev Relay the message for this `ticketID` if the challenge period has passed and the claim is unchallenged. The hash computed over `messageData` and the other parameters must match the hash provided by the claim. * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blockNumber The block number on the cross-domain chain when the message with this ticketID has been sent. + * @param _blocknumber The block number on the cross-domain chain when the message with this ticketID has been sent. * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. */ function verifyAndRelay( uint256 _ticketID, - uint256 _blockNumber, + uint256 _blocknumber, bytes calldata _messageData ) external override { Ticket storage ticket = tickets[_ticketID]; require(ticket.claim.bridger != address(0), "Claim does not exist"); require( - ticket.claim.messageHash == keccak256(abi.encode(_ticketID, _blockNumber, _messageData)), + ticket.claim.messageHash == keccak256(abi.encode(_ticketID, _blocknumber, _messageData)), "Invalid hash" ); require(ticket.claim.claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); @@ -154,12 +144,12 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid * Note: Access restricted to the Safe Bridge. * @dev Relay the message for this `ticketID` as provided by the Safe Bridge. Resolve a challenged claim for this `ticketID` if any. * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blockNumber The block number on the cross-domain chain when the message with this ticketID has been sent. + * @param _blocknumber The block number on the cross-domain chain when the message with this ticketID has been sent. * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. */ function verifyAndRelaySafe( uint256 _ticketID, - uint256 _blockNumber, + uint256 _blocknumber, bytes calldata _messageData ) external override { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); @@ -168,7 +158,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid require(ticket.relayed == false, "Message already relayed"); // Claim assessment if any - bytes32 messageHash = keccak256(abi.encode(_ticketID, _blockNumber, _messageData)); + bytes32 messageHash = keccak256(abi.encode(_ticketID, _blocknumber, _messageData)); if (ticket.claim.bridger != address(0) && ticket.claim.messageHash == messageHash) { ticket.claim.verified = true; } @@ -187,7 +177,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid require(ticket.claim.bridger != address(0), "Claim does not exist"); require(ticket.claim.verified == true, "Claim not verified: deposit forfeited"); - uint256 amount = ticket.claim.claimDeposit + (ticket.challenge.challengeDeposit * alpha) / ONE_BASIS_POINT; + uint256 amount = ticket.claim.claimDeposit + ticket.challenge.challengeDeposit / 2; ticket.claim.claimDeposit = 0; ticket.challenge.challengeDeposit = 0; payable(ticket.claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. @@ -204,7 +194,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid require(ticket.challenge.challenger != address(0), "Challenge does not exist"); require(ticket.claim.verified == false, "Claim verified: deposit forfeited"); - uint256 amount = ticket.challenge.challengeDeposit + (ticket.claim.claimDeposit * alpha) / ONE_BASIS_POINT; + uint256 amount = ticket.challenge.challengeDeposit + ticket.claim.claimDeposit / 2; ticket.claim.claimDeposit = 0; ticket.challenge.challengeDeposit = 0; payable(ticket.challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. @@ -229,26 +219,6 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid return (start, end); } - // ************************ // - // * Governance * // - // ************************ // - - function changeClaimDeposit(uint256 _claimDeposit) external onlyByGovernor { - claimDeposit = _claimDeposit; - } - - function changeChallengeDeposit(uint256 _challengeDeposit) external onlyByGovernor { - challengeDeposit = _challengeDeposit; - } - - function changeChallengePeriodDuration(uint256 _challengeDuration) external onlyByGovernor { - challengeDuration = _challengeDuration; - } - - function changeAlpha(uint256 _alpha) external onlyByGovernor { - alpha = _alpha; - } - // ************************ // // * Internal * // // ************************ // diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index 20cee36c7..c120e177c 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -25,7 +25,7 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe struct Ticket { bytes32 messageHash; - uint256 blockNumber; + uint256 blocknumber; bool sentSafe; } @@ -33,35 +33,18 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe // * Storage * // // ************************************* // - address public governor; // The governor of the contract. IFastBridgeReceiver public fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. - address public fastBridgeSender; // The address of the Fast Bridge sender on Arbitrum, generally the Home Gateway. uint256 public currentTicketID = 1; // Zero means not set, start at 1. mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. - // ************************************* // - // * Function Modifiers * // - // ************************************* // - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - /** * @dev Constructor. - * @param _governor The governor's address. * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. - * @param _fastBridgeSender The address of the Fast Bridge sender on Arbitrum, generally the Home Gateway. */ constructor( - address _governor, - IFastBridgeReceiver _fastBridgeReceiver, - address _fastBridgeSender + IFastBridgeReceiver _fastBridgeReceiver ) SafeBridgeSenderToEthereum() { - governor = _governor; fastBridgeReceiver = _fastBridgeReceiver; - fastBridgeSender = _fastBridgeSender; } // ************************************* // @@ -69,40 +52,48 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe // ************************************* // /** - * Note: Access restricted to the `fastSender`, generally the Gateway. * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. * @param _receiver The address of the contract on Ethereum which receives the calldata. * @param _calldata The receiving domain encoded message data. * @return ticketID The identifier to provide to sendSafeFallback(). */ function sendFast(address _receiver, bytes memory _calldata) external override returns (uint256 ticketID) { - require(msg.sender == fastBridgeSender, "Access not allowed: Fast Sender only."); - + require(_calldata.length >= 4, "Malformed calldata: lacks function selector."); ticketID = currentTicketID++; - (bytes32 messageHash, bytes memory messageData) = _encode(ticketID, _receiver, _calldata); - emit OutgoingMessage(ticketID, block.number, _receiver, messageHash, messageData); + (bytes32 messageHash, ) = _encode( + ticketID, + block.number, + msg.sender, + _receiver, + _calldata); + emit OutgoingMessage(ticketID, block.number, msg.sender, _receiver, messageHash, _calldata); - tickets[ticketID] = Ticket({messageHash: messageHash, blockNumber: block.number, sentSafe: false}); + tickets[ticketID] = Ticket({messageHash: messageHash, blocknumber: block.number, sentSafe: false}); } + /** * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. * @param _ticketID The ticketID as returned by `sendFast()`. + * @param _fastMsgSender The msg.sender which called sendFast() to register this ticketID. * @param _receiver The address of the contract on Ethereum which receives the calldata. * @param _calldata The receiving domain encoded message data. */ function sendSafeFallback( uint256 _ticketID, + address _fastBridgeReceiver, + address _fastMsgSender, address _receiver, bytes memory _calldata ) external payable override { // TODO: check if keeping _calldata in storage in sendFast() is cheaper than passing it again as a parameter here + // However calldata gas cost-benefit analysis will change with EIP-4488. Ticket storage ticket = tickets[_ticketID]; require(ticket.messageHash != 0, "Ticket does not exist."); require(ticket.sentSafe == false, "Ticket already sent safely."); - (bytes32 messageHash, bytes memory messageData) = _encode(_ticketID, _receiver, _calldata); + (bytes32 messageHash, bytes memory messageData) = _encode(_ticketID, ticket.blocknumber, _fastMsgSender, _receiver, _calldata); require(ticket.messageHash == messageHash, "Invalid message for ticketID."); // Safe Bridge message envelope @@ -110,20 +101,12 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe bytes memory safeMessageData = abi.encodeWithSelector( methodSelector, _ticketID, - ticket.blockNumber, + ticket.blocknumber, messageData ); - + ticket.sentSafe = true; // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafe(address(fastBridgeReceiver), safeMessageData); - } - - // ************************ // - // * Governance * // - // ************************ // - - function changeFastSender(address _fastBridgeSender) external onlyByGovernor { - fastBridgeSender = _fastBridgeSender; + _sendSafe(address(_fastBridgeReceiver), safeMessageData); } // ************************ // @@ -132,13 +115,16 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe function _encode( uint256 _ticketID, + uint256 _blocknumber, + address _msgSender, address _receiver, bytes memory _calldata - ) internal view returns (bytes32 messageHash, bytes memory messageData) { - // Encode the receiver address with the function signature + arguments i.e calldata - messageData = abi.encode(_receiver, _calldata); + ) internal pure returns (bytes32 messageHash, bytes memory messageData) { + // Encode the receiver address with the function signature + _msgSender as the first argument, then the rest of the args + (bytes4 functionSelector, bytes memory _args) = abi.decode(_calldata, (bytes4, bytes)); + messageData = abi.encode(_receiver, abi.encodeWithSelector(functionSelector, _msgSender, _args)); // Compute the hash over the message header (ticketID, blockNumber) and body (data). - messageHash = keccak256(abi.encode(_ticketID, block.number, messageData)); + messageHash = keccak256(abi.encode(_ticketID, _blocknumber, messageData)); } } diff --git a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol index 07a43f833..3241ece5d 100644 --- a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol @@ -23,35 +23,24 @@ contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { // * Storage * // // ************************************* // - address public governor; // The governor of the contract. address public safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. IInbox public inbox; // The address of the Arbitrum Inbox contract. - // ************************************* // - // * Function Modifiers * // - // ************************************* // - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - /** * @dev Constructor. - * @param _governor The governor's address. * @param _safeBridgeSender The address of the Safe Bridge sender on Arbitrum. * @param _inbox The address of the Arbitrum Inbox contract. */ constructor( - address _governor, - address _safeBridgeSender, address _inbox ) { - governor = _governor; inbox = IInbox(_inbox); - safeBridgeSender = _safeBridgeSender; } + function setSafeBridgeSender(address _safeBridgeSender) external { + if (safeBridgeSender == address(0) ) + safeBridgeSender = _safeBridgeSender; + } // ************************************* // // * Views * // // ************************************* // @@ -60,16 +49,4 @@ contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); return outbox.l2ToL1Sender() == safeBridgeSender; } - - // ************************ // - // * Governance * // - // ************************ // - - function setSafeBridgeSender(address _safeBridgeSender) external onlyByGovernor { - safeBridgeSender = _safeBridgeSender; - } - - function setInbox(address _inbox) external onlyByGovernor { - inbox = IInbox(_inbox); - } } diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 0b508f8d1..871cf094d 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -109,9 +109,4 @@ interface IFastBridgeReceiver { * @return amount The duration of the period allowing to challenge a claim. */ function challengeDuration() external view returns (uint256 amount); - - /** - * @return amount Basis point of claim or challenge deposit that are lost when dishonest. - */ - function alpha() external view returns (uint256 amount); } diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index c5ed12ab8..080704923 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -11,6 +11,7 @@ interface IFastBridgeSender { * @dev The Fast Bridge participants need to watch for these events and relay the messageHash on the FastBridgeReceiverOnEthereum. * @param ticketID The ticket identifier referring to a message going through the bridge. * @param blockNumber The block number when the message with this ticketID has been created. + * @param messageSender The address of the cross-domain receiver of the message, generally the Foreign Gateway. * @param target The address of the cross-domain receiver of the message, generally the Foreign Gateway. * @param messageHash The hash uniquely identifying this message. * @param message The message data. @@ -18,6 +19,7 @@ interface IFastBridgeSender { event OutgoingMessage( uint256 indexed ticketID, uint256 blockNumber, + address messageSender, address target, bytes32 indexed messageHash, bytes message @@ -39,11 +41,15 @@ interface IFastBridgeSender { /** * @dev Sends an arbitrary message across domain using the Safe Bridge, which relies on the chain's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. * @param _ticketID The ticketID as returned by `sendFast()`. + * @param _fastBridgeReceiver The address of the fast bridge receiver deployment. + * @param _fastMsgSender The msg.sender which called sendFast() * @param _receiver The cross-domain contract address which receives the calldata. * @param _calldata The receiving domain encoded message data. */ function sendSafeFallback( uint256 _ticketID, + address _fastBridgeReceiver, + address _fastMsgSender, address _receiver, bytes memory _calldata ) external payable; diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGatewayOnEthereum.sol index c0614bc72..c7cb74a0d 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/ForeignGatewayOnEthereum.sol @@ -45,6 +45,8 @@ contract ForeignGatewayOnEthereum is IForeignGateway { address public governor; IFastBridgeReceiver public fastbridge; + IFastBridgeReceiver public depreciatedFastbridge; + uint256 fastbridgeExpiration; address public homeGateway; event OutgoingDispute( @@ -57,7 +59,10 @@ contract ForeignGatewayOnEthereum is IForeignGateway { ); modifier onlyFromFastBridge() { - require(address(fastbridge) == msg.sender, "Access not allowed: Fast Bridge only."); + require( + address(fastbridge) == msg.sender || + ( (block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender) + , "Access not allowed: Fast Bridge only."); _; } @@ -84,6 +89,16 @@ contract ForeignGatewayOnEthereum is IForeignGateway { } } + /** @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastbridge The address of the new fastBridge. + */ + function changeFastbridge(IFastBridgeReceiver _fastbridge) external onlyByGovernor { + // grace period to relay remaining messages in the relay / bridging process + fastbridgeExpiration = block.timestamp + _fastbridge.challengeDuration() + 1209600; // 2 weeks + depreciatedFastbridge = fastbridge; + fastbridge = _fastbridge; + } + /** @dev Changes the `feeForJuror` property value of a specified subcourt. * @param _subcourtID The ID of the subcourt. * @param _feeForJuror The new value for the `feeForJuror` property value. @@ -130,10 +145,12 @@ contract ForeignGatewayOnEthereum is IForeignGateway { * Relay the rule call from the home gateway to the arbitrable. */ function relayRule( + address _messageOrigin, bytes32 _disputeHash, uint256 _ruling, address _relayer - ) external onlyFromFastBridge { + ) external onlyFromFastBridge(){ + require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGatewayToEthereum.sol index 6ce6cff60..d22124e26 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/HomeGatewayToEthereum.sol @@ -24,6 +24,7 @@ contract HomeGatewayToEthereum is IHomeGateway { mapping(uint256 => bytes32) public disputeIDtoHash; mapping(bytes32 => uint256) public disputeHashtoID; + address public governor; IArbitrator public arbitrator; IFastBridgeSender public fastbridge; address public foreignGateway; @@ -37,11 +38,13 @@ contract HomeGatewayToEthereum is IHomeGateway { mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; constructor( + address _governor, IArbitrator _arbitrator, IFastBridgeSender _fastbridge, address _foreignGateway, uint256 _foreignChainID ) { + governor = _governor; arbitrator = _arbitrator; fastbridge = _fastbridge; foreignGateway = _foreignGateway; @@ -113,6 +116,14 @@ contract HomeGatewayToEthereum is IHomeGateway { fastbridge.sendFast(foreignGateway, data); } + /** @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastbridge The address of the new fastBridge. + */ + function changeFastbridge(IFastBridgeSender _fastbridge) external { + require(governor == msg.sender, "Access not allowed: Governor only."); + fastbridge = _fastbridge; + } + function disputeHashToHomeID(bytes32 _disputeHash) external view returns (uint256) { return disputeHashtoID[_disputeHash]; } diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol index a507b0ed3..1277dd435 100644 --- a/contracts/src/gateway/interfaces/IForeignGateway.sol +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -11,6 +11,7 @@ interface IForeignGateway is IArbitrator { * Relay the rule call from the home gateway to the arbitrable. */ function relayRule( + address _messageOrigin, bytes32 _disputeHash, uint256 _ruling, address _forwarder From cdf3a8387f7cf0a7616b0718d1fa143281e8ef27 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 12:25:54 +0100 Subject: [PATCH 02/23] feat: unhappy path with merkle tree --- .../bridge/FastBridgeReceiverOnEthereum.sol | 331 +++++++++++------- .../src/bridge/FastBridgeSenderToEthereum.sol | 215 ++++++++---- .../bridge/SafeBridgeReceiverOnEthereum.sol | 12 +- .../bridge/interfaces/IFastBridgeReceiver.sol | 84 ++--- .../bridge/interfaces/IFastBridgeSender.sol | 36 +- .../src/gateway/HomeGatewayToEthereum.sol | 12 +- 6 files changed, 400 insertions(+), 290 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 9021e6216..1be384dbe 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -23,49 +23,43 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid // ************************************* // struct Claim { - bytes32 messageHash; + bytes32 batchMerkleRoot; address bridger; - uint256 claimedAt; - uint256 claimDeposit; - bool verified; + bool honest; } struct Challenge { address challenger; - uint256 challengedAt; - uint256 challengeDeposit; - } - - struct Ticket { - Claim claim; - Challenge challenge; - bool relayed; + bool honest; } // ************************************* // // * Storage * // // ************************************* // - uint256 public override claimDeposit; // The deposit required to submit a claim. - uint256 public override challengeDeposit; // The deposit required to submit a challenge. - uint256 public override challengeDuration; // The duration of the period allowing to challenge a claim. - mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable genesis; // Marks the beginning of the first epoch. + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + + mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) + mapping(uint256 => Claim) public claims; // epoch => claim + mapping(uint256 => Challenge) public challenges; // epoch => challenge + mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + /** * @dev Constructor. * @param _inbox The address of the Arbitrum Inbox contract. - * @param _claimDeposit The deposit amount to submit a claim in wei. - * @param _challengeDeposit The deposit amount to submit a challenge in wei. - * @param _challengeDuration The duration of the period allowing to challenge a claim. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of the period allowing to challenge a claim. */ constructor( address _inbox, - uint256 _claimDeposit, - uint256 _challengeDeposit, - uint256 _challengeDuration + uint256 _deposit, + uint256 _epochPeriod ) SafeBridgeReceiverOnEthereum(_inbox) { - claimDeposit = _claimDeposit; - challengeDeposit = _challengeDeposit; - challengeDuration = _challengeDuration; + deposit = _deposit; + epochPeriod = _epochPeriod; + genesis = block.timestamp; } // ************************************* // @@ -73,131 +67,150 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid // ************************************* // /** - * @dev Submit a claim about the `messageHash` for a particular Fast Bridge `ticketID` and submit a deposit. The `messageHash` should match the one on the sending side otherwise the sender will lose his deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _messageHash The hash claimed for the ticket. + * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. */ - function claim(uint256 _ticketID, bytes32 _messageHash) external payable override { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.claim.bridger == address(0), "Claim already made"); - require(ticket.relayed == false, "Claim already relayed"); // already relayed via verifyAndRelaySafe() without claim. - require(msg.value >= claimDeposit, "Not enough claim deposit"); - - ticket.claim = Claim({ - messageHash: _messageHash, - bridger: msg.sender, - claimedAt: block.timestamp, - claimDeposit: msg.value, - verified: false - }); - - emit ClaimReceived(_ticketID, _messageHash, block.timestamp); + //0x11afd631e338f093f1b49382d72a0ae9e4688ef0c78b11141a4145e045b7daa4 + function claim(bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + uint256 epochClaim = epochCount - 1; // Can only claim last completed epoch. + uint256 claimDeadline = genesis + epochCount * epochPeriod + epochPeriod / 2; + + require(block.timestamp < claimDeadline, "Claim period expired."); + require(claims[epochClaim].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[epochClaim] = Claim({batchMerkleRoot: _batchMerkleRoot, bridger: msg.sender, honest: false}); + + emit ClaimReceived(epochClaim, _batchMerkleRoot); } /** - * @dev Submit a challenge for a particular Fast Bridge `ticketID` and submit a deposit. The `messageHash` in the claim already made for this `ticketID` should be different from the one on the sending side, otherwise the sender will lose his deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. */ - function challenge(uint256 _ticketID) external payable override { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.claim.bridger != address(0), "Claim does not exist"); - require(block.timestamp - ticket.claim.claimedAt < challengeDuration, "Challenge period over"); - require(ticket.challenge.challenger == address(0), "Claim already challenged"); - require(msg.value >= challengeDeposit, "Not enough challenge deposit"); - - ticket.challenge = Challenge({ - challenger: msg.sender, - challengedAt: block.timestamp, - challengeDeposit: msg.value - }); - - emit ClaimChallenged(_ticketID, block.timestamp); + function challenge() external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + require(claims[epochChallenge].bridger != address(0), "No claim to challenge."); + + challenges[epochChallenge] = Challenge({challenger: msg.sender, honest: false}); + + emit ClaimChallenged(epochChallenge); } /** - * @dev Relay the message for this `ticketID` if the challenge period has passed and the claim is unchallenged. The hash computed over `messageData` and the other parameters must match the hash provided by the claim. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blocknumber The block number on the cross-domain chain when the message with this ticketID has been sent. - * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. */ - function verifyAndRelay( - uint256 _ticketID, - uint256 _blocknumber, - bytes calldata _messageData - ) external override { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.claim.bridger != address(0), "Claim does not exist"); - require( - ticket.claim.messageHash == keccak256(abi.encode(_ticketID, _blocknumber, _messageData)), - "Invalid hash" - ); - require(ticket.claim.claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); - require(ticket.challenge.challenger == address(0), "Claim is challenged"); - require(ticket.relayed == false, "Message already relayed"); - - ticket.claim.verified = true; - ticket.relayed = true; - require(_relay(_messageData), "Failed to call contract"); // Checks-Effects-Interaction + function verify(uint256 _epoch) public { + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + require(_epoch + 1 < epochCount, "Challenge period for epoch has not elapsed."); + require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + + if (challenges[_epoch].challenger == address(0)) { + // optimistic happy path + claim.honest = true; + fastInbox[_epoch] = claim.batchMerkleRoot; + } } /** * Note: Access restricted to the Safe Bridge. - * @dev Relay the message for this `ticketID` as provided by the Safe Bridge. Resolve a challenged claim for this `ticketID` if any. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blocknumber The block number on the cross-domain chain when the message with this ticketID has been sent. - * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param _epoch The epoch to verify. + * @param _batchMerkleRoot The true batch merkle root for the epoch. */ - function verifyAndRelaySafe( - uint256 _ticketID, - uint256 _blocknumber, - bytes calldata _messageData - ) external override { + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - Ticket storage ticket = tickets[_ticketID]; - require(ticket.relayed == false, "Message already relayed"); + fastInbox[_epoch] = _batchMerkleRoot; - // Claim assessment if any - bytes32 messageHash = keccak256(abi.encode(_ticketID, _blocknumber, _messageData)); - if (ticket.claim.bridger != address(0) && ticket.claim.messageHash == messageHash) { - ticket.claim.verified = true; + Claim storage claim = claims[_epoch]; + Challenge storage challenge = challenges[_epoch]; + + if (_batchMerkleRoot == bytes32(0)) { + // in case of bridge sender lack of liveliness during epoch + // and dishonest claim on bridge receiver for the same epoch. + challenge.honest = true; + } else if (_batchMerkleRoot == claim.batchMerkleRoot) { + claim.honest = true; + } else { + challenge.honest = true; } + } + + bytes32 lastReplay; + + /** + * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. + * @param _epoch The epoch in which the message was batched by the bridge. + * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. + * @param _message The data on the cross-domain chain for the message. + * @param _nonce The nonce (index in the merkle tree) to avoid replay. + */ + function verifyProofAndRelayMessage( + uint256 _epoch, + bytes32[] calldata _proof, + bytes calldata _message, + uint256 _nonce + ) external override { + bytes32 batchMerkleRoot = 0x9ca2d28b3bdce533cdff8ca9baeba8665df6e1096ecfa1850402b5e880ddf239; //fastInbox[_epoch]; + require(batchMerkleRoot != bytes32(0), "Invalid epoch."); + + uint256 index = _nonce / 256; + uint256 offset = _nonce - index * 256; + + bytes32 replay = relayed[_epoch][index]; + lastReplay = replay; + require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); + relayed[_epoch][index] = replay | bytes32(1 << offset); - ticket.relayed = true; - require(_relay(_messageData), "Failed to call contract"); // Checks-Effects-Interaction + // Claim assessment if any + bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); + + require(_validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction } /** - * @dev Sends the deposit back to the Bridger if his claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. + * @param _epoch The epoch associated with the claim deposit to withraw. */ - function withdrawClaimDeposit(uint256 _ticketID) external override { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.relayed == true, "Message not relayed yet"); - require(ticket.claim.bridger != address(0), "Claim does not exist"); - require(ticket.claim.verified == true, "Claim not verified: deposit forfeited"); - - uint256 amount = ticket.claim.claimDeposit + ticket.challenge.challengeDeposit / 2; - ticket.claim.claimDeposit = 0; - ticket.challenge.challengeDeposit = 0; - payable(ticket.claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + function withdrawClaimDeposit(uint256 _epoch) external override { + Claim memory claim = claims[_epoch]; + Challenge memory challenge = challenges[_epoch]; + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + + uint256 amount = deposit; + if (challenge.challenger != address(0)) amount = (deposit * 3) / 2; // half burnt + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction } /** - * @dev Sends the deposit back to the Challenger if his challenge is successful. Includes a portion of the Bridger's deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @dev Sends the deposit back to the Challenger if their challenge is successful. Includes a portion of the Bridger's deposit. + * @param _epoch The epoch associated with the challenge deposit to withraw. */ - function withdrawChallengeDeposit(uint256 _ticketID) external override { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.relayed == true, "Message not relayed"); - require(ticket.challenge.challenger != address(0), "Challenge does not exist"); - require(ticket.claim.verified == false, "Claim verified: deposit forfeited"); - - uint256 amount = ticket.challenge.challengeDeposit + ticket.claim.claimDeposit / 2; - ticket.claim.claimDeposit = 0; - ticket.challenge.challengeDeposit = 0; - payable(ticket.challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + function withdrawChallengeDeposit(uint256 _epoch) external override { + Challenge memory challenge = challenges[_epoch]; + require(challenge.challenger != address(0), "Claim does not exist"); + require(challenge.honest == true, "Claim not verified: deposit forfeited"); + + uint256 amount = (deposit * 3) / 2; + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction } @@ -206,26 +219,80 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid // ************************************* // /** - * @dev Returns the `start` and `end` time of challenge period for this `ticketID`. - * @return start The start time of the challenge period. - * @return end The end time of the challenge period. + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. */ - function challengePeriod(uint256 _ticketID) external view override returns (uint256 start, uint256 end) { - Ticket storage ticket = tickets[_ticketID]; - require(ticket.claim.bridger != address(0), "Claim does not exist"); - - start = ticket.claim.claimedAt; - end = start + challengeDuration; + function challengePeriod() external view override returns (uint256 start, uint256 end) { + // start begins latest after the claim deadline expiry + // however can begin as soon as a claim is made + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + start = genesis + epochChallenge * epochPeriod + epochPeriod / 2; + end = start + epochPeriod / 2; return (start, end); } + /** + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function epochCount() external view returns (uint256 _epochCount) { + _epochCount = (block.timestamp - genesis) / epochPeriod; + } + // ************************ // // * Internal * // // ************************ // - function _relay(bytes calldata _messageData) internal returns (bool success) { + function _relay(bytes calldata _messageData) public returns (bool success) { // Decode the receiver address from the data encoded by the IFastBridgeSender (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); (success, ) = address(receiver).call(data); } + + // ******************************* // + // * Merkle Proof * // + // ******************************* // + + /** @dev Validates membership of leaf in merkle tree with merkle proof. + * @param proof The merkle proof. + * @param leaf The leaf to validate membership in merkle tree. + * @param merkleRoot The root of the merkle tree. + */ + function _validateProof( + bytes32[] memory proof, + bytes32 leaf, + bytes32 merkleRoot + ) internal pure returns (bool) { + return (merkleRoot == _calculateRoot(proof, leaf)); + } + + /** @dev Calculates merkle root from proof. + * @param proof The merkle proof. + * @param leaf The leaf to validate membership in merkle tree.. + */ + function _calculateRoot(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + uint256 proofLength = proof.length; + require(proofLength <= 32, "Invalid Proof"); + bytes32 h = leaf; + for (uint256 i = 0; i < proofLength; i++) { + bytes32 proofElement = proof[i]; + // effecient hash + if (proofElement > h) + assembly { + mstore(0x00, h) + mstore(0x20, proofElement) + h := keccak256(0x00, 0x40) + } + else + assembly { + mstore(0x00, proofElement) + mstore(0x20, h) + h := keccak256(0x00, 0x40) + } + } + return h; + } } diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index c120e177c..f8ad56b38 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -20,31 +20,41 @@ import "./interfaces/IFastBridgeReceiver.sol"; */ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender { // ************************************* // - // * Enums / Structs * // + // * Storage * // // ************************************* // - struct Ticket { - bytes32 messageHash; - uint256 blocknumber; - bool sentSafe; - } + IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. + + uint256 public immutable genesis; // Marks the beginning of the first epoch. + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + + bytes32[64] public batch; // merkle tree, supports 2^64 outbound messages + uint256 public batchSize; // merkle tree leaf count + mapping(uint256 => bytes32) public fastOutbox; // epoch => merkle root of batched messages // ************************************* // - // * Storage * // + // * Events * // // ************************************* // - IFastBridgeReceiver public fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. - uint256 public currentTicketID = 1; // Zero means not set, start at 1. - mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiverOnEthereum. + */ + event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); /** * @dev Constructor. * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _genesis The immutable genesis state variable from the FastBridgeSeneder. */ constructor( - IFastBridgeReceiver _fastBridgeReceiver + IFastBridgeReceiver _fastBridgeReceiver, + uint256 _epochPeriod, + uint256 _genesis ) SafeBridgeSenderToEthereum() { fastBridgeReceiver = _fastBridgeReceiver; + genesis = _genesis; + epochPeriod = _epochPeriod; } // ************************************* // @@ -55,76 +65,153 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. * @param _receiver The address of the contract on Ethereum which receives the calldata. * @param _calldata The receiving domain encoded message data. - * @return ticketID The identifier to provide to sendSafeFallback(). */ - function sendFast(address _receiver, bytes memory _calldata) external override returns (uint256 ticketID) { - require(_calldata.length >= 4, "Malformed calldata: lacks function selector."); - ticketID = currentTicketID++; - - (bytes32 messageHash, ) = _encode( - ticketID, - block.number, - msg.sender, - _receiver, - _calldata); - emit OutgoingMessage(ticketID, block.number, msg.sender, _receiver, messageHash, _calldata); - - tickets[ticketID] = Ticket({messageHash: messageHash, blocknumber: block.number, sentSafe: false}); + //0xa60a4db5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 + //0x5B38Da6a701c568545dCfcB03FcB875f56beddC4a60a4db55B38Da6a701c568545dCfcB03FcB875f56beddC4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 + function sendFast(address _receiver, bytes memory _calldata) external override { + bytes32 messageHash = _encode(_receiver, _calldata); + insertInBatch(messageHash); + emit MessageReceived(_receiver, _calldata, batchSize); } + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendBatch() public { + uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; + require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); + require(batchSize > 0, "No messages to send."); + + bytes32 batchMerkleRoot = getBatchMerkleRoot(); + + // set merkle root in outbox and reset merkle tree + fastOutbox[epochFinalized] = batchMerkleRoot; + batchSize = 0; + + emit SendBatch(epochFinalized, batchMerkleRoot); + } /** * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _ticketID The ticketID as returned by `sendFast()`. - * @param _fastMsgSender The msg.sender which called sendFast() to register this ticketID. - * @param _receiver The address of the contract on Ethereum which receives the calldata. - * @param _calldata The receiving domain encoded message data. + * @param _epoch The blocknumber of the batch */ - function sendSafeFallback( - uint256 _ticketID, - address _fastBridgeReceiver, - address _fastMsgSender, - address _receiver, - bytes memory _calldata - ) external payable override { - // TODO: check if keeping _calldata in storage in sendFast() is cheaper than passing it again as a parameter here - // However calldata gas cost-benefit analysis will change with EIP-4488. - Ticket storage ticket = tickets[_ticketID]; - require(ticket.messageHash != 0, "Ticket does not exist."); - require(ticket.sentSafe == false, "Ticket already sent safely."); - - (bytes32 messageHash, bytes memory messageData) = _encode(_ticketID, ticket.blocknumber, _fastMsgSender, _receiver, _calldata); - require(ticket.messageHash == messageHash, "Invalid message for ticketID."); + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector( - methodSelector, - _ticketID, - ticket.blocknumber, - messageData - ); - ticket.sentSafe = true; + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafe(address(_fastBridgeReceiver), safeMessageData); + _sendSafe(address(fastBridgeReceiver), safeMessageData); } // ************************ // // * Internal * // // ************************ // - function _encode( - uint256 _ticketID, - uint256 _blocknumber, - address _msgSender, - address _receiver, - bytes memory _calldata - ) internal pure returns (bytes32 messageHash, bytes memory messageData) { + function _encode(address _receiver, bytes memory _calldata) internal view returns (bytes32 messageHash) { // Encode the receiver address with the function signature + _msgSender as the first argument, then the rest of the args - (bytes4 functionSelector, bytes memory _args) = abi.decode(_calldata, (bytes4, bytes)); - messageData = abi.encode(_receiver, abi.encodeWithSelector(functionSelector, _msgSender, _args)); + bytes4 functionSelector; + bytes memory _args; + assembly { + functionSelector := mload(add(_calldata, 32)) + mstore(add(_calldata, 4), mload(_calldata)) + _args := add(_calldata, 4) + } + bytes memory messageData = abi.encodePacked( + _receiver, + abi.encodeWithSelector(functionSelector, msg.sender, _args) + ); + + // Compute the hash over the message header (current batchSize acts as a nonce) and body (data). + messageHash = sha256(abi.encodePacked(messageData, batchSize)); + } + + // ************************************ // + // * MerkleTree * // + // ************************************ // + + // ************************************* // + // * State Modifiers * // + // ************************************* // - // Compute the hash over the message header (ticketID, blockNumber) and body (data). - messageHash = keccak256(abi.encode(_ticketID, _blocknumber, messageData)); + /** @dev Append _messageHash leaf into merkle tree. + * `O(log(n))` where + * `n` is the number of leaves (batchSize). + * Note: Although each insertion is O(log(n)), + * Complexity of n insertions is O(n). + * @param _messageHash The leaf to insert in the merkle tree. + */ + function insertInBatch(bytes32 _messageHash) internal { + uint256 size = batchSize + 1; + batchSize = size; + uint256 hashBitField = (size ^ (size - 1)) & size; + uint256 height; + while ((hashBitField & 1) == 0) { + bytes32 node = batch[height]; + if (node > _messageHash) + assembly { + // effecient hash + mstore(0x00, _messageHash) + mstore(0x20, node) + _messageHash := keccak256(0x00, 0x40) + } + else + assembly { + // effecient hash + mstore(0x00, node) + mstore(0x20, _messageHash) + _messageHash := keccak256(0x00, 0x40) + } + hashBitField /= 2; + height = height + 1; + } + batch[height] = _messageHash; + } + + /** @dev Gets the history of merkle roots in the outbox + * @param _epoch requested epoch outbox history. + */ + function getBatchMerkleRootHistory(uint256 _epoch) public view returns (bytes32) { + return fastOutbox[_epoch]; + } + + /** @dev Gets the merkle root for the current epoch batch + * `O(log(n))` where + * `n` is the current number of leaves (batchSize) + */ + function getBatchMerkleRoot() public view returns (bytes32) { + bytes32 node; + uint256 size = batchSize; + uint256 height = 0; + bool isFirstHash = true; + while (size > 0) { + if ((size & 1) == 1) { + // avoid redundant calculation + if (isFirstHash) { + node = batch[height]; + isFirstHash = false; + } else { + bytes32 hash = batch[height]; + // effecient hash + if (hash > node) + assembly { + mstore(0x00, node) + mstore(0x20, hash) + node := keccak256(0x00, 0x40) + } + else + assembly { + mstore(0x00, hash) + mstore(0x20, node) + node := keccak256(0x00, 0x40) + } + } + } + size /= 2; + height++; + } + return node; } } diff --git a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol index 3241ece5d..b5c6be5e0 100644 --- a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol @@ -23,24 +23,22 @@ contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { // * Storage * // // ************************************* // + // will be set as immutable in production deployment for gas optimization address public safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. - IInbox public inbox; // The address of the Arbitrum Inbox contract. + IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. /** * @dev Constructor. - * @param _safeBridgeSender The address of the Safe Bridge sender on Arbitrum. * @param _inbox The address of the Arbitrum Inbox contract. */ - constructor( - address _inbox - ) { + constructor(address _inbox) { inbox = IInbox(_inbox); } function setSafeBridgeSender(address _safeBridgeSender) external { - if (safeBridgeSender == address(0) ) - safeBridgeSender = _safeBridgeSender; + if (safeBridgeSender == address(0)) safeBridgeSender = _safeBridgeSender; } + // ************************************* // // * Views * // // ************************************* // diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 871cf094d..a0ed0abbb 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -17,96 +17,74 @@ interface IFastBridgeReceiver { /** * @dev The Fast Bridge participants watch for these events to decide if a challenge should be submitted. - * @param ticketID The ticket identifier referring to a message going through the bridge. - * @param messageHash The claimed hash corresponding to this `ticketID`. It should match the hash from the sending side otherwise it will be challenged. - * @param claimedAt The timestamp of the claim creation. + * @param _epoch The epoch in the the claim was made. + * @param _batchMerkleRoot The timestamp of the claim creation. */ - event ClaimReceived(uint256 indexed ticketID, bytes32 indexed messageHash, uint256 claimedAt); + event ClaimReceived(uint256 _epoch, bytes32 indexed _batchMerkleRoot); /** * @dev The Fast Bridge participants watch for these events to call `sendSafeFallback()` on the sending side. - * @param ticketID The ticket identifier referring to a message going through the bridge. - * @param challengedAt The timestamp of the challenge creation. + * @param _epoch The epoch associated with the challenged claim. */ - event ClaimChallenged(uint256 indexed ticketID, uint256 challengedAt); + event ClaimChallenged(uint256 _epoch); // ************************************* // // * Function Modifiers * // // ************************************* // /** - * @dev Submit a claim about the `messageHash` for a particular Fast Bridge `ticketID` and submit a deposit. The `messageHash` should match the one on the sending side otherwise the sender will lose his deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _messageHash The hash claimed for the ticket. + * @dev Submit a claim about the `_batchMerkleRoot` for the latests completed Fast bridge epoch and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. + * @param _batchMerkleRoot The hash claimed for the ticket. */ - function claim(uint256 _ticketID, bytes32 _messageHash) external payable; + function claim(bytes32 _batchMerkleRoot) external payable; /** - * @dev Submit a challenge for a particular Fast Bridge `ticketID` and submit a deposit. The `messageHash` in the claim already made for this `ticketID` should be different from the one on the sending side, otherwise the sender will lose his deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. */ - function challenge(uint256 _ticketID) external payable; + function challenge() external payable; /** - * @dev Relay the message for this `ticketID` if the challenge period has passed and the claim is unchallenged. The hash computed over `messageData` and the other parameters must match the hash provided by the claim. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blockNumber The block number on the cross-domain chain when the message with this ticketID has been sent. - * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. + * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. + * @param _epoch The epoch in which the message was batched by the bridge. + * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. + * @param _message The data on the cross-domain chain for the message. + * @param _nonce The nonce (index in the merkle tree) to avoid replay. */ - function verifyAndRelay( - uint256 _ticketID, - uint256 _blockNumber, - bytes calldata _messageData + function verifyProofAndRelayMessage( + uint256 _epoch, + bytes32[] calldata _proof, + bytes calldata _message, + uint256 _nonce ) external; /** * Note: Access restricted to the Safe Bridge. - * @dev Relay the message for this `ticketID` as provided by the Safe Bridge. Resolve a challenged claim for this `ticketID` if any. - * @param _ticketID The ticket identifier referring to a message going through the bridge. - * @param _blockNumber The block number on the cross-domain chain when the message with this ticketID has been sent. - * @param _messageData The data on the cross-domain chain for the message sent with this ticketID. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param _epoch The epoch associated with the _batchmerkleRoot. + * @param _batchMerkleRoot The true batch merkle root for the epoch sent by the safe bridge. */ - function verifyAndRelaySafe( - uint256 _ticketID, - uint256 _blockNumber, - bytes calldata _messageData - ) external; + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external; /** - * @dev Sends the deposit back to the Bridger if his claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. + * @param _epoch The epoch associated with the claim deposit to withraw. */ - function withdrawClaimDeposit(uint256 _ticketID) external; + function withdrawClaimDeposit(uint256 _epoch) external; /** * @dev Sends the deposit back to the Challenger if his challenge is successful. Includes a portion of the Bridger's deposit. - * @param _ticketID The ticket identifier referring to a message going through the bridge. + * @param _epoch The epoch associated with the challenge deposit to withraw. */ - function withdrawChallengeDeposit(uint256 _ticketID) external; + function withdrawChallengeDeposit(uint256 _epoch) external; // ************************************* // // * Public Views * // // ************************************* // /** - * @dev Returns the `start` and `end` time of challenge period for this `ticketID`. + * @dev Returns the `start` and `end` time of challenge period for this `epoch`. * @return start The start time of the challenge period. * @return end The end time of the challenge period. */ - function challengePeriod(uint256 _ticketID) external view returns (uint256 start, uint256 end); - - /** - * @return amount The deposit required to submit a claim. - */ - function claimDeposit() external view returns (uint256 amount); - - /** - * @return amount The deposit required to submit a challenge. - */ - function challengeDeposit() external view returns (uint256 amount); - - /** - * @return amount The duration of the period allowing to challenge a claim. - */ - function challengeDuration() external view returns (uint256 amount); + function challengePeriod() external view returns (uint256 start, uint256 end); } diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index 080704923..b9ab5655e 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -9,48 +9,28 @@ interface IFastBridgeSender { /** * @dev The Fast Bridge participants need to watch for these events and relay the messageHash on the FastBridgeReceiverOnEthereum. - * @param ticketID The ticket identifier referring to a message going through the bridge. - * @param blockNumber The block number when the message with this ticketID has been created. - * @param messageSender The address of the cross-domain receiver of the message, generally the Foreign Gateway. * @param target The address of the cross-domain receiver of the message, generally the Foreign Gateway. - * @param messageHash The hash uniquely identifying this message. * @param message The message data. + * @param nonce The message nonce. */ - event OutgoingMessage( - uint256 indexed ticketID, - uint256 blockNumber, - address messageSender, - address target, - bytes32 indexed messageHash, - bytes message - ); + event MessageReceived(address target, bytes message, uint256 nonce); // ************************************* // // * Function Modifiers * // // ************************************* // /** - * Note: Access must be restricted to the sending application. + * Note: Access must be restricted by the receiving contract. + * Message is sent with the message sender address as the first argument. * @dev Sends an arbitrary message across domain using the Fast Bridge. * @param _receiver The cross-domain contract address which receives the calldata. * @param _calldata The receiving domain encoded message data. - * @return ticketID The identifier to provide to sendSafeFallback(). */ - function sendFast(address _receiver, bytes memory _calldata) external returns (uint256 ticketID); + function sendFast(address _receiver, bytes memory _calldata) external; /** - * @dev Sends an arbitrary message across domain using the Safe Bridge, which relies on the chain's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _ticketID The ticketID as returned by `sendFast()`. - * @param _fastBridgeReceiver The address of the fast bridge receiver deployment. - * @param _fastMsgSender The msg.sender which called sendFast() - * @param _receiver The cross-domain contract address which receives the calldata. - * @param _calldata The receiving domain encoded message data. + * @dev Sends a markle root representing an arbitrary batch of messages across domain using the Safe Bridge, which relies on the chain's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch block number of batch */ - function sendSafeFallback( - uint256 _ticketID, - address _fastBridgeReceiver, - address _fastMsgSender, - address _receiver, - bytes memory _calldata - ) external payable; + function sendSafeFallback(uint256 _epoch) external payable; } diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGatewayToEthereum.sol index d22124e26..a00f0f767 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/HomeGatewayToEthereum.sol @@ -27,9 +27,9 @@ contract HomeGatewayToEthereum is IHomeGateway { address public governor; IArbitrator public arbitrator; IFastBridgeSender public fastbridge; - address public foreignGateway; - uint256 public chainID; - uint256 public foreignChainID; + address public override foreignGateway; + uint256 public override chainID; + uint256 public override foreignChainID; struct RelayedData { uint256 arbitrationCost; @@ -77,7 +77,7 @@ contract HomeGatewayToEthereum is IHomeGateway { uint256 _choices, bytes calldata _extraData, address _arbitrable - ) external payable { + ) external payable override { bytes32 disputeHash = keccak256( abi.encodePacked( _originalChainID, @@ -104,7 +104,7 @@ contract HomeGatewayToEthereum is IHomeGateway { emit Dispute(arbitrator, disputeID, 0, 0); } - function rule(uint256 _disputeID, uint256 _ruling) external { + function rule(uint256 _disputeID, uint256 _ruling) external override { require(msg.sender == address(arbitrator), "Only Arbitrator"); bytes32 disputeHash = disputeIDtoHash[_disputeID]; @@ -124,7 +124,7 @@ contract HomeGatewayToEthereum is IHomeGateway { fastbridge = _fastbridge; } - function disputeHashToHomeID(bytes32 _disputeHash) external view returns (uint256) { + function disputeHashToHomeID(bytes32 _disputeHash) external view override returns (uint256) { return disputeHashtoID[_disputeHash]; } } From 7430258c7ef82f9c91cf818c6b148757146c68f2 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:00:56 +0100 Subject: [PATCH 03/23] feat: immutable chainIDs increase gas efficiency --- .../bridge/interfaces/IFastBridgeReceiver.sol | 5 ++++ .../src/gateway/ForeignGatewayOnEthereum.sol | 28 +++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index a0ed0abbb..fc5bec75b 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -87,4 +87,9 @@ interface IFastBridgeReceiver { * @return end The end time of the challenge period. */ function challengePeriod() external view returns (uint256 start, uint256 end); + + /** + * @dev Returns the epoch period. + */ + function epochPeriod() external view returns (uint256 epoch); } diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGatewayOnEthereum.sol index c7cb74a0d..a5cb5ca83 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/ForeignGatewayOnEthereum.sol @@ -31,8 +31,8 @@ contract ForeignGatewayOnEthereum is IForeignGateway { // feeForJuror by subcourtID uint256[] internal feeForJuror; - uint256 public chainID; - uint256 public homeChainID; + uint256 public immutable chainID; + uint256 public immutable homeChainID; struct DisputeData { uint248 id; @@ -61,8 +61,9 @@ contract ForeignGatewayOnEthereum is IForeignGateway { modifier onlyFromFastBridge() { require( address(fastbridge) == msg.sender || - ( (block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender) - , "Access not allowed: Fast Bridge only."); + ((block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender), + "Access not allowed: Fast Bridge only." + ); _; } @@ -76,17 +77,15 @@ contract ForeignGatewayOnEthereum is IForeignGateway { IFastBridgeReceiver _fastbridge, uint256[] memory _feeForJuror, address _homeGateway, - uint256 _homeChainID + uint256 _homeChainID, + uint256 _chainID ) { governor = _governor; fastbridge = _fastbridge; feeForJuror = _feeForJuror; homeGateway = _homeGateway; homeChainID = _homeChainID; - - assembly { - sstore(chainID.slot, chainid()) - } + chainID = _chainID; } /** @dev Changes the fastBridge, useful to increase the claim deposit. @@ -94,7 +93,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { */ function changeFastbridge(IFastBridgeReceiver _fastbridge) external onlyByGovernor { // grace period to relay remaining messages in the relay / bridging process - fastbridgeExpiration = block.timestamp + _fastbridge.challengeDuration() + 1209600; // 2 weeks + fastbridgeExpiration = block.timestamp + _fastbridge.epochPeriod() + 1209600; // 2 weeks depreciatedFastbridge = fastbridge; fastbridge = _fastbridge; } @@ -107,6 +106,13 @@ contract ForeignGatewayOnEthereum is IForeignGateway { feeForJuror[_subcourtID] = _feeForJuror; } + /** @dev Creates the `feeForJuror` property value for a new subcourt. + * @param _feeForJuror The new value for the `feeForJuror` property value. + */ + function createSubcourtJurorFee(uint256 _feeForJuror) external onlyByGovernor { + feeForJuror.push(_feeForJuror); + } + function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) { require(msg.value >= arbitrationCost(_extraData), "Not paid enough for arbitration"); @@ -149,7 +155,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { bytes32 _disputeHash, uint256 _ruling, address _relayer - ) external onlyFromFastBridge(){ + ) external onlyFromFastBridge { require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; From dabbc533f9825977ebc922543fc46f13a4c374ce Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:01:16 +0100 Subject: [PATCH 04/23] feat: updated tests --- contracts/deploy/01-foreign-chain.ts | 25 +++++++++++-------------- contracts/deploy/02-home-chain.ts | 17 +++++++++++------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index 370b851ac..f0523c2ff 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -12,18 +12,18 @@ enum ForeignChains { } const paramsByChainId = { 1: { - claimDeposit: parseEther("0.1"), - challengeDuration: 86400, // 1 day - homeChainId: 42161, + deposit: parseEther("0.1"), + epochPeriod: 86400, // 1 day + homeChainId: 42161, // arbitrum }, 4: { - claimDeposit: parseEther("0.1"), - challengeDuration: 120, // 2 min + deposit: parseEther("0.1"), + epochPeriod: 120, // 2 min homeChainId: 421611, }, 31337: { - claimDeposit: parseEther("0.1"), - challengeDuration: 120, // 2 min + deposit: parseEther("0.1"), + epochPeriod: 120, // 2 min homeChainId: 31337, }, }; @@ -56,10 +56,10 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme nonce = await homeChainProvider.getTransactionCount(deployer); nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } - const { claimDeposit, challengeDuration, homeChainId } = paramsByChainId[chainId]; - const challengeDeposit = claimDeposit; + const { deposit, challengeDuration, homeChainId } = paramsByChainId[chainId]; const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); + const chainIdAsBytes32 = hexZeroPad("0x"+chainId.toString(16), 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); @@ -67,13 +67,9 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { from: deployer, args: [ - deployer, - ethers.constants.AddressZero, // should be safeBridgeSender ethers.constants.AddressZero, // should be Arbitrum Inbox - claimDeposit, - challengeDeposit, + deposit, challengeDuration, - bridgeAlpha, ], log: true, }); @@ -86,6 +82,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme [ethers.BigNumber.from(10).pow(17)], homeGatewayAddress, homeChainIdAsBytes32, + chainIdAsBytes32 ], log: true, }); diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 87cef5270..68086fba3 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -34,15 +34,20 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const foreignChainId = chainId === 31337 ? 31337 : Number(await hre.companionNetworks.foreign.getChainId()); const homeGateway = await deploy("HomeGatewayToEthereum", { from: deployer, - args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + args: [ + deployer, + klerosCore.address, + fastBridgeSender.address, + foreignGateway.address, + foreignChainId], log: true, }); // nonce+1 - const fastSender = await hre.ethers - .getContractAt("FastBridgeSenderToEthereum", fastBridgeSender.address) - .then((contract) => contract.fastBridgeSender()); - if (fastSender === ethers.constants.AddressZero) { - await execute("FastBridgeSenderToEthereum", { from: deployer, log: true }, "changeFastSender", homeGateway.address); + const safeBridgeSender = await hre.ethers + .getContractAt("FastBridgeReceiverOnEthereum", fastBridgeReceiver.address) + .then((contract) => contract.safeBridgeSender()); + if (safeBridgeSender === ethers.constants.AddressZero) { + await execute("FastBridgeReceiverOnEthereum", { from: deployer, log: true }, "setSafeBridgeSender", fastBridgeSender.address); } }; From 6b545f114fa4f9761993bd26988e878c7c64ee32 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:14:14 +0100 Subject: [PATCH 05/23] chore: test typos --- contracts/deploy/01-foreign-chain.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index f0523c2ff..ee5e83c27 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -56,7 +56,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme nonce = await homeChainProvider.getTransactionCount(deployer); nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } - const { deposit, challengeDuration, homeChainId } = paramsByChainId[chainId]; + const { deposit, epochPeriod, homeChainId } = paramsByChainId[chainId]; const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); const chainIdAsBytes32 = hexZeroPad("0x"+chainId.toString(16), 32); @@ -69,7 +69,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme args: [ ethers.constants.AddressZero, // should be Arbitrum Inbox deposit, - challengeDuration, + epochPeriod, ], log: true, }); From 160827da61d174fccb7aea0b96685cb3bb76bf96 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:21:42 +0100 Subject: [PATCH 06/23] chore: whitespace formatting --- contracts/deploy/01-foreign-chain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index ee5e83c27..e352087fa 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -59,7 +59,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const { deposit, epochPeriod, homeChainId } = paramsByChainId[chainId]; const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); - const chainIdAsBytes32 = hexZeroPad("0x"+chainId.toString(16), 32); + const chainIdAsBytes32 = hexZeroPad("0x" + chainId.toString(16), 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); From 3dbec7470aebaf93aa1c2969885222b8cb490259 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:24:40 +0100 Subject: [PATCH 07/23] chore: remove comment --- contracts/src/bridge/FastBridgeReceiverOnEthereum.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 1be384dbe..348ba8fd3 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -70,7 +70,6 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. */ - //0x11afd631e338f093f1b49382d72a0ae9e4688ef0c78b11141a4145e045b7daa4 function claim(bytes32 _batchMerkleRoot) external payable override { require(msg.value >= deposit, "Insufficient claim deposit."); require(_batchMerkleRoot != bytes32(0), "Invalid claim."); From 71161747e63234a75e742e2656ace8836da75e27 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:26:16 +0100 Subject: [PATCH 08/23] chore: remove testing value --- contracts/src/bridge/FastBridgeReceiverOnEthereum.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 348ba8fd3..91f5a0a74 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -159,7 +159,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid bytes calldata _message, uint256 _nonce ) external override { - bytes32 batchMerkleRoot = 0x9ca2d28b3bdce533cdff8ca9baeba8665df6e1096ecfa1850402b5e880ddf239; //fastInbox[_epoch]; + bytes32 batchMerkleRoot = fastInbox[_epoch]; require(batchMerkleRoot != bytes32(0), "Invalid epoch."); uint256 index = _nonce / 256; From 927f896a27447471aa7a04fc5aae924363b0cf60 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Sun, 15 May 2022 18:34:54 +0100 Subject: [PATCH 09/23] style: remove testing comments --- contracts/src/bridge/FastBridgeSenderToEthereum.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index f8ad56b38..43581320d 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -66,8 +66,6 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe * @param _receiver The address of the contract on Ethereum which receives the calldata. * @param _calldata The receiving domain encoded message data. */ - //0xa60a4db5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 - //0x5B38Da6a701c568545dCfcB03FcB875f56beddC4a60a4db55B38Da6a701c568545dCfcB03FcB875f56beddC4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000 function sendFast(address _receiver, bytes memory _calldata) external override { bytes32 messageHash = _encode(_receiver, _calldata); insertInBatch(messageHash); From f8c1a4fa0aa503fceabeea49a2006ed4cc5700e1 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 16 May 2022 16:51:14 +0100 Subject: [PATCH 10/23] test: adapt deployment scripts --- contracts/deploy/01-foreign-chain.ts | 19 +++++++++++++++---- contracts/deploy/02-home-chain.ts | 8 ++++++-- .../bridge/FastBridgeReceiverOnEthereum.sol | 8 +++++--- .../src/bridge/FastBridgeSenderToEthereum.sol | 2 +- .../bridge/SafeBridgeReceiverOnEthereum.sol | 9 +++------ .../src/gateway/ForeignGatewayOnEthereum.sol | 9 +++++---- .../src/gateway/HomeGatewayToEthereum.sol | 14 ++++++-------- 7 files changed, 41 insertions(+), 28 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index e352087fa..fae4e9f6b 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -2,7 +2,7 @@ import { parseEther } from "ethers/lib/utils"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; - +import { ethers } from "hardhat"; import getContractAddress from "../deploy-helpers/getContractAddress"; enum ForeignChains { @@ -15,16 +15,22 @@ const paramsByChainId = { deposit: parseEther("0.1"), epochPeriod: 86400, // 1 day homeChainId: 42161, // arbitrum + arbInbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", // https://developer.offchainlabs.com/docs/useful_addresses + genesis: 1652709415 // sample genesis time }, 4: { deposit: parseEther("0.1"), epochPeriod: 120, // 2 min - homeChainId: 421611, + homeChainId: 421611, // arbitrum testnet + arbInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", //https://developer.offchainlabs.com/docs/useful_addresses + genesis: 1652709415 // sample genesis time }, 31337: { deposit: parseEther("0.1"), epochPeriod: 120, // 2 min homeChainId: 31337, + arbInbox: ethers.constants.AddressZero, + genesis: 1652709415 // sample genesis time }, }; @@ -56,20 +62,25 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme nonce = await homeChainProvider.getTransactionCount(deployer); nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } - const { deposit, epochPeriod, homeChainId } = paramsByChainId[chainId]; + const { deposit, epochPeriod, homeChainId, arbInbox, genesis } = paramsByChainId[chainId]; const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); const chainIdAsBytes32 = hexZeroPad("0x" + chainId.toString(16), 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); + nonce -= 1; + const fastBridgeSenderAddress = getContractAddress(deployer, nonce); + console.log("calculated future fastBridgeSender address for nonce %d: %s", nonce, fastBridgeSenderAddress); const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { from: deployer, args: [ - ethers.constants.AddressZero, // should be Arbitrum Inbox + arbInbox, // should be Arbitrum Inbox deposit, epochPeriod, + fastBridgeSenderAddress, + genesis // sample genesis time ], log: true, }); diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 68086fba3..2595c74d8 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -20,9 +20,10 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) chainId === 31337 ? await deployments.get("FastBridgeReceiverOnEthereum") : await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); + //const genesisSynchronization = fastBridgeReceiver. const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { from: deployer, - args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero], + args: [fastBridgeReceiver.address, 120, 1652709415], log: true, }); // nonce+0 @@ -39,7 +40,9 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) klerosCore.address, fastBridgeSender.address, foreignGateway.address, - foreignChainId], + foreignChainId, + chainId + ], log: true, }); // nonce+1 @@ -49,6 +52,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) if (safeBridgeSender === ethers.constants.AddressZero) { await execute("FastBridgeReceiverOnEthereum", { from: deployer, log: true }, "setSafeBridgeSender", fastBridgeSender.address); } + }; deployHomeGateway.tags = ["HomeChain", "HomeGateway"]; diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 91f5a0a74..a76f552e0 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -55,11 +55,13 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid constructor( address _inbox, uint256 _deposit, - uint256 _epochPeriod - ) SafeBridgeReceiverOnEthereum(_inbox) { + uint256 _epochPeriod, + address _safeBridgeSender, + uint256 _genesis + ) SafeBridgeReceiverOnEthereum(_inbox, _safeBridgeSender) { deposit = _deposit; epochPeriod = _epochPeriod; - genesis = block.timestamp; + genesis = _genesis; } // ************************************* // diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index 43581320d..f478fd5e8 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -53,8 +53,8 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe uint256 _genesis ) SafeBridgeSenderToEthereum() { fastBridgeReceiver = _fastBridgeReceiver; - genesis = _genesis; epochPeriod = _epochPeriod; + genesis = _genesis; } // ************************************* // diff --git a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol index b5c6be5e0..86f6cabb7 100644 --- a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol @@ -24,19 +24,16 @@ contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { // ************************************* // // will be set as immutable in production deployment for gas optimization - address public safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. + address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. /** * @dev Constructor. * @param _inbox The address of the Arbitrum Inbox contract. */ - constructor(address _inbox) { + constructor(address _inbox, address _safeBridgeSender) { inbox = IInbox(_inbox); - } - - function setSafeBridgeSender(address _safeBridgeSender) external { - if (safeBridgeSender == address(0)) safeBridgeSender = _safeBridgeSender; + safeBridgeSender = _safeBridgeSender; } // ************************************* // diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGatewayOnEthereum.sol index a5cb5ca83..7e1e1dd62 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/ForeignGatewayOnEthereum.sol @@ -46,8 +46,8 @@ contract ForeignGatewayOnEthereum is IForeignGateway { address public governor; IFastBridgeReceiver public fastbridge; IFastBridgeReceiver public depreciatedFastbridge; - uint256 fastbridgeExpiration; - address public homeGateway; + uint256 public fastbridgeExpiration; + address public immutable homeGateway; event OutgoingDispute( bytes32 disputeHash, @@ -90,10 +90,11 @@ contract ForeignGatewayOnEthereum is IForeignGateway { /** @dev Changes the fastBridge, useful to increase the claim deposit. * @param _fastbridge The address of the new fastBridge. + * @param _gracePeriod The duration to accept messages from the deprecated bridge (if at all). */ - function changeFastbridge(IFastBridgeReceiver _fastbridge) external onlyByGovernor { + function changeFastbridge(IFastBridgeReceiver _fastbridge, uint256 _gracePeriod) external onlyByGovernor { // grace period to relay remaining messages in the relay / bridging process - fastbridgeExpiration = block.timestamp + _fastbridge.epochPeriod() + 1209600; // 2 weeks + fastbridgeExpiration = block.timestamp + _fastbridge.epochPeriod() + _gracePeriod; // 2 weeks depreciatedFastbridge = fastbridge; fastbridge = _fastbridge; } diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGatewayToEthereum.sol index a00f0f767..eac9020f8 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/HomeGatewayToEthereum.sol @@ -25,11 +25,11 @@ contract HomeGatewayToEthereum is IHomeGateway { mapping(bytes32 => uint256) public disputeHashtoID; address public governor; - IArbitrator public arbitrator; + IArbitrator public immutable arbitrator; IFastBridgeSender public fastbridge; address public override foreignGateway; - uint256 public override chainID; - uint256 public override foreignChainID; + uint256 public immutable override chainID; + uint256 public immutable override foreignChainID; struct RelayedData { uint256 arbitrationCost; @@ -42,17 +42,15 @@ contract HomeGatewayToEthereum is IHomeGateway { IArbitrator _arbitrator, IFastBridgeSender _fastbridge, address _foreignGateway, - uint256 _foreignChainID + uint256 _foreignChainID, + uint256 _chainID ) { governor = _governor; arbitrator = _arbitrator; fastbridge = _fastbridge; foreignGateway = _foreignGateway; foreignChainID = _foreignChainID; - - assembly { - sstore(chainID.slot, chainid()) - } + chainID = _chainID; emit MetaEvidence(0, "BRIDGE"); } From d622a5528c83de869b9f364394ffbc8b5662c67b Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 18 May 2022 09:46:33 +0100 Subject: [PATCH 11/23] test: deploy central arbitrator for adhoc tests --- contracts/deploy/00-home-chain-arbitration.ts | 6 ++++ contracts/deploy/01-foreign-chain.ts | 30 ++++++++++++++++--- contracts/deploy/02-home-chain.ts | 24 +++++++++++++-- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/contracts/deploy/00-home-chain-arbitration.ts b/contracts/deploy/00-home-chain-arbitration.ts index bcb27b7be..be3d8d51a 100644 --- a/contracts/deploy/00-home-chain-arbitration.ts +++ b/contracts/deploy/00-home-chain-arbitration.ts @@ -50,6 +50,12 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment) ).address ); } + const centralizedArbitrator = await deploy("CentralizedArbitrator", { + from: deployer, + args: [0, 0, 0], + log: true, + }); + const pnk = pnkByChain.get(Number(await getChainId())) ?? AddressZero; const minStake = BigNumber.from(10).pow(20).mul(2); const alpha = 10000; diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index fae4e9f6b..e5b0ba14a 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -63,14 +63,14 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } const { deposit, epochPeriod, homeChainId, arbInbox, genesis } = paramsByChainId[chainId]; - const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); const chainIdAsBytes32 = hexZeroPad("0x" + chainId.toString(16), 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - nonce -= 1; - const fastBridgeSenderAddress = getContractAddress(deployer, nonce); + const homeGatewayCentralizedArbitratorAddress = getContractAddress(deployer, nonce+1); + console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); + const fastBridgeSenderAddress = getContractAddress(deployer, nonce-1); console.log("calculated future fastBridgeSender address for nonce %d: %s", nonce, fastBridgeSenderAddress); const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { @@ -80,13 +80,14 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme deposit, epochPeriod, fastBridgeSenderAddress, - genesis // sample genesis time + genesis ], log: true, }); const foreignGateway = await deploy("ForeignGatewayOnEthereum", { from: deployer, + contract: "ForeignGatewayOnEthereum", args: [ deployer, fastBridgeReceiver.address, @@ -98,6 +99,20 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme log: true, }); + const foreignGatewayCentralizedArbitrator = await deploy("ForeignGatewayOnEthereumCentralizedArbitrator", { + from: deployer, + contract: "ForeignGatewayOnEthereum", + args: [ + deployer, + fastBridgeReceiver.address, + [ethers.BigNumber.from(10).pow(17)], + homeGatewayCentralizedArbitratorAddress, + homeChainIdAsBytes32, + chainIdAsBytes32 + ], + log: true, + }); + const metaEvidenceUri = "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; const arbitrable = await deploy("ArbitrableExample", { @@ -105,6 +120,13 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme args: [foreignGateway.address, metaEvidenceUri], log: true, }); + + const arbitrableCentralizedArbitrator = await deploy("ArbitrableExampleCentralizedArbitrator", { + from: deployer, + contract: "ArbitrableExample", + args: [foreignGateway.address, metaEvidenceUri], + log: true, + }); }; deployForeignGateway.tags = ["ForeignChain", "ForeignGateway"]; diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 2595c74d8..fefd6bef7 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -20,14 +20,18 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) chainId === 31337 ? await deployments.get("FastBridgeReceiverOnEthereum") : await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); - //const genesisSynchronization = fastBridgeReceiver. + + const genesisSynchronization = 1652709415; // sample genesis time + const epochPeriod = 120; + const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { from: deployer, - args: [fastBridgeReceiver.address, 120, 1652709415], + args: [fastBridgeReceiver.address, epochPeriod, genesisSynchronization], log: true, }); // nonce+0 const klerosCore = await deployments.get("KlerosCore"); + const centralizedArbitrator = await deployments.get("CentralizedArbitrator"); const foreignGateway = chainId === 31337 ? await deployments.get("ForeignGatewayOnEthereum") @@ -35,6 +39,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const foreignChainId = chainId === 31337 ? 31337 : Number(await hre.companionNetworks.foreign.getChainId()); const homeGateway = await deploy("HomeGatewayToEthereum", { from: deployer, + contract: "HomeGatewayToEthereum", args: [ deployer, klerosCore.address, @@ -46,6 +51,21 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) log: true, }); // nonce+1 + const homeGatewayCentralizedArbitrator = await deploy("HomeGatewayToEthereumCentralizedArbitrator", { + from: deployer, + contract: "HomeGatewayToEthereum", + args: [ + deployer, + centralizedArbitrator.address, + fastBridgeSender.address, + foreignGateway.address, + foreignChainId, + chainId + ], + log: true, + }); // nonce+1 + + // comment out and call manually if gas calculation errors const safeBridgeSender = await hre.ethers .getContractAt("FastBridgeReceiverOnEthereum", fastBridgeReceiver.address) .then((contract) => contract.safeBridgeSender()); From 77ce88ecb251817b8172bf7376e99e68752eb1c0 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 18 May 2022 11:18:23 +0100 Subject: [PATCH 12/23] style: lint fix --- contracts/deploy/01-foreign-chain.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index e5b0ba14a..d1e854446 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -22,7 +22,7 @@ const paramsByChainId = { deposit: parseEther("0.1"), epochPeriod: 120, // 2 min homeChainId: 421611, // arbitrum testnet - arbInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", //https://developer.offchainlabs.com/docs/useful_addresses + arbInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", // https://developer.offchainlabs.com/docs/useful_addresses genesis: 1652709415 // sample genesis time }, 31337: { @@ -35,7 +35,7 @@ const paramsByChainId = { }; const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { ethers, deployments, getNamedAccounts, getChainId, config } = hre; + const { deployments, getNamedAccounts, getChainId, config } = hre; const { deploy } = deployments; const { providers } = ethers; const { hexZeroPad } = hre.ethers.utils; @@ -68,9 +68,9 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - const homeGatewayCentralizedArbitratorAddress = getContractAddress(deployer, nonce+1); + const homeGatewayCentralizedArbitratorAddress = getContractAddress(deployer, nonce + 1); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - const fastBridgeSenderAddress = getContractAddress(deployer, nonce-1); + const fastBridgeSenderAddress = getContractAddress(deployer, nonce - 1); console.log("calculated future fastBridgeSender address for nonce %d: %s", nonce, fastBridgeSenderAddress); const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { From 55a2c50bc78a61e7e0103ed8ee37218c45a890f6 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 18 May 2022 11:19:38 +0100 Subject: [PATCH 13/23] fix: override view funciton --- contracts/src/bridge/FastBridgeReceiverOnEthereum.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index a76f552e0..5bf162d0a 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -39,7 +39,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid uint256 public immutable deposit; // The deposit required to submit a claim or challenge uint256 public immutable genesis; // Marks the beginning of the first epoch. - uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) mapping(uint256 => Claim) public claims; // epoch => claim From b56f372613fa6e529380bc9356f084d48ee18d75 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 18 May 2022 11:59:12 +0100 Subject: [PATCH 14/23] style: fix code lint --- contracts/deploy/01-foreign-chain.ts | 73 ++++++++++++++++++---------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index d1e854446..be3da572d 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -84,34 +84,27 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme ], log: true, }); + let foreignGateway = await deployForeignGateways( + "ForeignGatewayOnEthereum", + deploy, + deployer, + fastBridgeReceiver.address, + [ethers.BigNumber.from(10).pow(17)], + homeGatewayAddress, + homeChainIdAsBytes32, + chainIdAsBytes32 + ) - const foreignGateway = await deploy("ForeignGatewayOnEthereum", { - from: deployer, - contract: "ForeignGatewayOnEthereum", - args: [ - deployer, - fastBridgeReceiver.address, - [ethers.BigNumber.from(10).pow(17)], - homeGatewayAddress, - homeChainIdAsBytes32, - chainIdAsBytes32 - ], - log: true, - }); - - const foreignGatewayCentralizedArbitrator = await deploy("ForeignGatewayOnEthereumCentralizedArbitrator", { - from: deployer, - contract: "ForeignGatewayOnEthereum", - args: [ - deployer, - fastBridgeReceiver.address, - [ethers.BigNumber.from(10).pow(17)], - homeGatewayCentralizedArbitratorAddress, - homeChainIdAsBytes32, - chainIdAsBytes32 - ], - log: true, - }); + const foreignGatewayCentralizedArbitrator = await deployForeignGateways( + "ForeignGatewayOnEthereumCentralizedArbitrator", + deploy, + deployer, + fastBridgeReceiver.address, + [ethers.BigNumber.from(10).pow(17)], + homeGatewayCentralizedArbitratorAddress, + homeChainIdAsBytes32, + chainIdAsBytes32 + ) const metaEvidenceUri = "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; @@ -129,6 +122,32 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme }); }; +async function deployForeignGateways( + name, + deploy, + deployer, + fastBridgeReceiverAddress, + feeForJuror, + homeGatewayAddress, + homeChainIdAsBytes32, + chainIdAsBytes32 +){ + let foreignGateway = await deploy(name, { + from: deployer, + contract: "ForeignGatewayOnEthereum", + args: [ + deployer, + fastBridgeReceiverAddress, + feeForJuror, + homeGatewayAddress, + homeChainIdAsBytes32, + chainIdAsBytes32 + ], + log: true, + }); + return foreignGateway; +} + deployForeignGateway.tags = ["ForeignChain", "ForeignGateway"]; deployForeignGateway.skip = async ({ getChainId }) => { const chainId = Number(await getChainId()); From 49da2a428367b967b3f58f82090b3b80e20f617f Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 20 May 2022 10:18:20 +0100 Subject: [PATCH 15/23] feat: merkle tree refactoring --- contracts/src/bridge/merkle/MerkleProof.sol | 15 +--- .../src/bridge/merkle/MerkleProofExposed.sol | 33 +++++++++ .../{MerkleTreeHistory.sol => MerkleTree.sol} | 71 ++++++++----------- .../src/bridge/merkle/MerkleTreeExposed.sol | 28 ++++++++ contracts/test/bridge/merkle/MerkleTree.ts | 2 +- contracts/test/bridge/merkle/index.ts | 27 ++++--- 6 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 contracts/src/bridge/merkle/MerkleProofExposed.sol rename contracts/src/bridge/merkle/{MerkleTreeHistory.sol => MerkleTree.sol} (61%) create mode 100644 contracts/src/bridge/merkle/MerkleTreeExposed.sol diff --git a/contracts/src/bridge/merkle/MerkleProof.sol b/contracts/src/bridge/merkle/MerkleProof.sol index 09a92245e..fa91a3b35 100644 --- a/contracts/src/bridge/merkle/MerkleProof.sol +++ b/contracts/src/bridge/merkle/MerkleProof.sol @@ -29,24 +29,11 @@ contract MerkleProof { return (merkleRoot == calculateRoot(proof, leaf)); } - /** @dev Validates membership of leaf in merkle tree with merkle proof. - * @param proof The merkle proof. - * @param data The data to validate membership in merkle tree. - * @param merkleRoot The root of the merkle tree. - */ - function validateProof( - bytes32[] memory proof, - bytes memory data, - bytes32 merkleRoot - ) public pure returns (bool) { - return validateProof(proof, keccak256(data), merkleRoot); - } - /** @dev Calculates merkle root from proof. * @param proof The merkle proof. * @param leaf The leaf to validate membership in merkle tree.. */ - function calculateRoot(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { + function calculateRoot(bytes32[] memory proof, bytes32 leaf) private pure returns (bytes32) { uint256 proofLength = proof.length; require(proofLength <= 32, "Invalid Proof"); bytes32 h = leaf; diff --git a/contracts/src/bridge/merkle/MerkleProofExposed.sol b/contracts/src/bridge/merkle/MerkleProofExposed.sol new file mode 100644 index 000000000..e922c6f52 --- /dev/null +++ b/contracts/src/bridge/merkle/MerkleProofExposed.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./MerkleProof.sol"; + +/** + * @title MerkleProofExpose + * @author Shotaro N. - + * @dev A set of exposed funcitons to test the MerkleProof contract + */ +contract MerkleProofExposed is MerkleProof { + /** @dev Validates membership of leaf in merkle tree with merkle proof. + * @param proof The merkle proof. + * @param leaf The leaf to validate membership in merkle tree. + * @param merkleRoot The root of the merkle tree. + */ + function _validateProof( + bytes32[] memory proof, + bytes32 leaf, + bytes32 merkleRoot + ) public pure returns (bool) { + return validateProof(proof, leaf, merkleRoot); + } +} diff --git a/contracts/src/bridge/merkle/MerkleTreeHistory.sol b/contracts/src/bridge/merkle/MerkleTree.sol similarity index 61% rename from contracts/src/bridge/merkle/MerkleTreeHistory.sol rename to contracts/src/bridge/merkle/MerkleTree.sol index 43aa27ced..876a4028b 100644 --- a/contracts/src/bridge/merkle/MerkleTreeHistory.sol +++ b/contracts/src/bridge/merkle/MerkleTree.sol @@ -11,22 +11,19 @@ pragma solidity ^0.8.0; /** - * @title MerkleTreeHistory + * @title MerkleTree * @author Shotaro N. - - * @dev An efficient append only merkle tree with history. + * @dev An efficient append only merkle tree. */ -contract MerkleTreeHistory { +contract MerkleTree { // ***************************** // // * Storage * // // ***************************** // - // merkle tree representation - // supports 2^32-1 messages. - bytes32[32] public branch; - uint256 public count; - - // block number => merkle root history - mapping(uint256 => bytes32) private history; + // merkle tree representation of a batch of messages + // supports 2^64 messages. + bytes32[64] private batch; + uint256 internal batchSize; // ************************************* // // * State Modifiers * // @@ -37,74 +34,64 @@ contract MerkleTreeHistory { * `n` is the number of leaves. * Note: Although each insertion is O(log(n)), * Complexity of n insertions is O(n). - * @param data The data to insert in the merkle tree. + * @param leaf The leaf (already hashed) to insert in the merkle tree. */ - function append(bytes memory data) public { - bytes32 leaf = keccak256(data); - count += 1; - uint256 size = count; + function appendMessage(bytes32 leaf) internal { + // Differentiate leaves from interior nodes with different + // hash functions to prevent 2nd order pre-image attack. + // https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/ + uint256 size = batchSize + 1; + batchSize = size; uint256 hashBitField = (size ^ (size - 1)) & size; - - for (uint256 height = 0; height < 32; height++) { - if ((hashBitField & 1) == 1) { - branch[height] = leaf; - return; - } - bytes32 node = branch[height]; - // effecient hash + uint256 height; + while ((hashBitField & 1) == 0) { + bytes32 node = batch[height]; if (node > leaf) assembly { + // effecient hash mstore(0x00, leaf) mstore(0x20, node) leaf := keccak256(0x00, 0x40) } else assembly { + // effecient hash mstore(0x00, node) mstore(0x20, leaf) leaf := keccak256(0x00, 0x40) } hashBitField /= 2; + height = height + 1; } + batch[height] = leaf; } /** @dev Saves the merkle root state in history and resets. * `O(log(n))` where * `n` is the number of leaves. */ - function reset() internal { - history[block.number] = getMerkleRoot(); - count = 0; - } - - /** @dev Gets the merkle root history - * `O(log(n))` where - * `n` is the number of leaves. - * @param blocknumber requested blocknumber. - */ - function getMerkleRootHistory(uint256 blocknumber) public view returns (bytes32) { - if (blocknumber == block.number) return getMerkleRoot(); - - return history[blocknumber]; + function getMerkleRootAndReset() internal returns (bytes32 batchMerkleRoot) { + batchMerkleRoot = getMerkleRoot(); + batchSize = 0; } /** @dev Gets the current merkle root. * `O(log(n))` where * `n` is the number of leaves. */ - function getMerkleRoot() public view returns (bytes32) { + function getMerkleRoot() internal view returns (bytes32) { bytes32 node; - uint256 size = count; + uint256 size = batchSize; uint256 height = 0; bool isFirstHash = true; while (size > 0) { - // avoid redundant calculation if ((size & 1) == 1) { + // avoid redundant calculation if (isFirstHash) { - node = branch[height]; + node = batch[height]; isFirstHash = false; } else { - bytes32 hash = branch[height]; + bytes32 hash = batch[height]; // effecient hash if (hash > node) assembly { diff --git a/contracts/src/bridge/merkle/MerkleTreeExposed.sol b/contracts/src/bridge/merkle/MerkleTreeExposed.sol new file mode 100644 index 000000000..72e2ede11 --- /dev/null +++ b/contracts/src/bridge/merkle/MerkleTreeExposed.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./MerkleTree.sol"; + +/** + * @title MerkleTreeExposed + * @author Shotaro N. - + * @dev Exposes MerkleTree for testing + */ +contract MerkleTreeExposed is MerkleTree { + function _appendMessage(bytes memory _leaf) public { + appendMessage(sha256(_leaf)); + } + + function _getMerkleRoot() public view returns (bytes32 merkleroot) { + merkleroot = getMerkleRoot(); + } +} diff --git a/contracts/test/bridge/merkle/MerkleTree.ts b/contracts/test/bridge/merkle/MerkleTree.ts index 1bc941eca..2bb1756ed 100644 --- a/contracts/test/bridge/merkle/MerkleTree.ts +++ b/contracts/test/bridge/merkle/MerkleTree.ts @@ -31,7 +31,7 @@ export class MerkleTree { * @return node The `sha3` (A.K.A. `keccak256`) hash of `first, ...params` as a 32-byte hex string. */ public static makeLeafNode(data: string): string { - const result = ethers.utils.keccak256(data); + const result = ethers.utils.sha256(data); if (!result) { throw new Error("Leaf node must not be empty"); diff --git a/contracts/test/bridge/merkle/index.ts b/contracts/test/bridge/merkle/index.ts index c6af5edd6..1c80daeeb 100644 --- a/contracts/test/bridge/merkle/index.ts +++ b/contracts/test/bridge/merkle/index.ts @@ -30,17 +30,17 @@ import { MerkleTree } from "./MerkleTree"; describe("Merkle", function () { describe("Sanity tests", async () => { - let merkleTreeHistory, merkleProof; + let merkleTreeExposed, merkleProofExposed; let data,nodes,mt; let rootOnChain,rootOffChain, proof; before("Deploying", async () => { - const merkleTreeHistoryFactory = await ethers.getContractFactory("MerkleTreeHistory"); - const merkleProofFactory = await ethers.getContractFactory("MerkleProof"); - merkleTreeHistory = await merkleTreeHistoryFactory.deploy(); - merkleProof = await merkleProofFactory.deploy(); - await merkleTreeHistory.deployed(); - await merkleProof.deployed(); + const merkleTreeExposedFactory = await ethers.getContractFactory("MerkleTreeExposed"); + const merkleProofExposedFactory = await ethers.getContractFactory("MerkleProofExposed"); + merkleTreeExposed = await merkleTreeExposedFactory.deploy(); + merkleProofExposed = await merkleProofExposedFactory.deploy(); + await merkleTreeExposed.deployed(); + await merkleProofExposed.deployed(); }); it("Merkle Root verification", async function () { @@ -51,19 +51,24 @@ describe("Merkle", function () { ]; nodes = []; for (var message of data) { - await merkleTreeHistory.append(message); + await merkleTreeExposed._appendMessage(message); nodes.push(MerkleTree.makeLeafNode(message)); } mt = new MerkleTree(nodes); rootOffChain = mt.getHexRoot(); - rootOnChain = await merkleTreeHistory.getMerkleRoot(); + rootOnChain = await merkleTreeExposed._getMerkleRoot(); + console.log("######"); + console.log(rootOffChain); + console.log(rootOnChain); + console.log("########################"); + expect(rootOffChain == rootOnChain).equal(true); }); it("Should correctly verify all nodes in the tree", async () => { for (var message of data) { - const leaf = ethers.utils.keccak256(message); + const leaf = ethers.utils.sha256(message); proof = mt.getHexProof(leaf); - const validation = await merkleProof.validateProof(proof, message,rootOnChain); + const validation = await merkleProofExposed._validateProof(proof, ethers.utils.sha256(message),rootOnChain); expect(validation).equal(true); expect(verify(proof, rootOffChain, leaf)).equal(true); } From eecd40518ef5f3d9da9366c46ea3f238ea06ac1c Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 20 May 2022 11:49:44 +0100 Subject: [PATCH 16/23] feat: refactoring and gas optimization --- .../bridge/FastBridgeReceiverOnEthereum.sol | 73 ++------- .../src/bridge/FastBridgeSenderToEthereum.sol | 141 +++--------------- .../bridge/interfaces/IFastBridgeReceiver.sol | 4 +- .../bridge/interfaces/IFastBridgeSender.sol | 7 +- .../src/gateway/HomeGatewayToEthereum.sol | 4 +- 5 files changed, 38 insertions(+), 191 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 5bf162d0a..7ba3c6535 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -12,12 +12,13 @@ pragma solidity ^0.8.0; import "./SafeBridgeReceiverOnEthereum.sol"; import "./interfaces/IFastBridgeReceiver.sol"; +import "./merkle/MerkleProof.sol"; /** * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToEthereum` */ -contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBridgeReceiver { +contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBridgeReceiver, MerkleProof { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -109,8 +110,10 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid */ function verify(uint256 _epoch) public { uint256 epochCount = (block.timestamp - genesis) / epochPeriod; - require(_epoch + 1 < epochCount, "Challenge period for epoch has not elapsed."); + + require(epochCount > _epoch + 1, "Challenge period for epoch has not elapsed."); require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); + Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); @@ -132,22 +135,13 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid fastInbox[_epoch] = _batchMerkleRoot; - Claim storage claim = claims[_epoch]; - Challenge storage challenge = challenges[_epoch]; - - if (_batchMerkleRoot == bytes32(0)) { - // in case of bridge sender lack of liveliness during epoch - // and dishonest claim on bridge receiver for the same epoch. - challenge.honest = true; - } else if (_batchMerkleRoot == claim.batchMerkleRoot) { - claim.honest = true; + if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { + challenges[_epoch].honest = true; } else { - challenge.honest = true; + claims[_epoch].honest = true; } } - bytes32 lastReplay; - /** * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. * @param _epoch The epoch in which the message was batched by the bridge. @@ -155,7 +149,7 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid * @param _message The data on the cross-domain chain for the message. * @param _nonce The nonce (index in the merkle tree) to avoid replay. */ - function verifyProofAndRelayMessage( + function verifyAndRelayMessage( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message, @@ -168,14 +162,13 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid uint256 offset = _nonce - index * 256; bytes32 replay = relayed[_epoch][index]; - lastReplay = replay; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); // Claim assessment if any bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); - require(_validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction } @@ -247,53 +240,9 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid // * Internal * // // ************************ // - function _relay(bytes calldata _messageData) public returns (bool success) { + function _relay(bytes calldata _messageData) internal returns (bool success) { // Decode the receiver address from the data encoded by the IFastBridgeSender (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); (success, ) = address(receiver).call(data); } - - // ******************************* // - // * Merkle Proof * // - // ******************************* // - - /** @dev Validates membership of leaf in merkle tree with merkle proof. - * @param proof The merkle proof. - * @param leaf The leaf to validate membership in merkle tree. - * @param merkleRoot The root of the merkle tree. - */ - function _validateProof( - bytes32[] memory proof, - bytes32 leaf, - bytes32 merkleRoot - ) internal pure returns (bool) { - return (merkleRoot == _calculateRoot(proof, leaf)); - } - - /** @dev Calculates merkle root from proof. - * @param proof The merkle proof. - * @param leaf The leaf to validate membership in merkle tree.. - */ - function _calculateRoot(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) { - uint256 proofLength = proof.length; - require(proofLength <= 32, "Invalid Proof"); - bytes32 h = leaf; - for (uint256 i = 0; i < proofLength; i++) { - bytes32 proofElement = proof[i]; - // effecient hash - if (proofElement > h) - assembly { - mstore(0x00, h) - mstore(0x20, proofElement) - h := keccak256(0x00, 0x40) - } - else - assembly { - mstore(0x00, proofElement) - mstore(0x20, h) - h := keccak256(0x00, 0x40) - } - } - return h; - } } diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index f478fd5e8..5881073e0 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -13,24 +13,22 @@ pragma solidity ^0.8.0; import "./SafeBridgeSenderToEthereum.sol"; import "./interfaces/IFastBridgeSender.sol"; import "./interfaces/IFastBridgeReceiver.sol"; +import "./merkle/MerkleTree.sol"; /** * Fast Bridge Sender to Ethereum from Arbitrum * Counterpart of `FastBridgeReceiverOnEthereum` */ -contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender { +contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender, MerkleTree { // ************************************* // // * Storage * // // ************************************* // IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. - uint256 public immutable genesis; // Marks the beginning of the first epoch. + uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. - - bytes32[64] public batch; // merkle tree, supports 2^64 outbound messages - uint256 public batchSize; // merkle tree leaf count - mapping(uint256 => bytes32) public fastOutbox; // epoch => merkle root of batched messages + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages // ************************************* // // * Events * // @@ -64,28 +62,32 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe /** * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. * @param _receiver The address of the contract on Ethereum which receives the calldata. - * @param _calldata The receiving domain encoded message data. + * @param _functionSelector The function to call. + * @param _calldata The receiving domain encoded message data / function arguments. */ - function sendFast(address _receiver, bytes memory _calldata) external override { - bytes32 messageHash = _encode(_receiver, _calldata); - insertInBatch(messageHash); - emit MessageReceived(_receiver, _calldata, batchSize); + function sendFast( + address _receiver, + bytes4 _functionSelector, + bytes memory _calldata + ) external override { + bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + appendMessage(fastMessageHash); // add message to merkle tree + emit MessageReceived(_receiver, _fastMessage, batchSize); } /** * Sends a batch of arbitrary message from one domain to another * via the fast bridge mechanism. */ - function sendBatch() public { + function sendEpoch() external { uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); require(batchSize > 0, "No messages to send."); - bytes32 batchMerkleRoot = getBatchMerkleRoot(); - // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRootAndReset(); fastOutbox[epochFinalized] = batchMerkleRoot; - batchSize = 0; emit SendBatch(epochFinalized, batchMerkleRoot); } @@ -103,113 +105,4 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed _sendSafe(address(fastBridgeReceiver), safeMessageData); } - - // ************************ // - // * Internal * // - // ************************ // - - function _encode(address _receiver, bytes memory _calldata) internal view returns (bytes32 messageHash) { - // Encode the receiver address with the function signature + _msgSender as the first argument, then the rest of the args - bytes4 functionSelector; - bytes memory _args; - assembly { - functionSelector := mload(add(_calldata, 32)) - mstore(add(_calldata, 4), mload(_calldata)) - _args := add(_calldata, 4) - } - bytes memory messageData = abi.encodePacked( - _receiver, - abi.encodeWithSelector(functionSelector, msg.sender, _args) - ); - - // Compute the hash over the message header (current batchSize acts as a nonce) and body (data). - messageHash = sha256(abi.encodePacked(messageData, batchSize)); - } - - // ************************************ // - // * MerkleTree * // - // ************************************ // - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /** @dev Append _messageHash leaf into merkle tree. - * `O(log(n))` where - * `n` is the number of leaves (batchSize). - * Note: Although each insertion is O(log(n)), - * Complexity of n insertions is O(n). - * @param _messageHash The leaf to insert in the merkle tree. - */ - function insertInBatch(bytes32 _messageHash) internal { - uint256 size = batchSize + 1; - batchSize = size; - uint256 hashBitField = (size ^ (size - 1)) & size; - uint256 height; - while ((hashBitField & 1) == 0) { - bytes32 node = batch[height]; - if (node > _messageHash) - assembly { - // effecient hash - mstore(0x00, _messageHash) - mstore(0x20, node) - _messageHash := keccak256(0x00, 0x40) - } - else - assembly { - // effecient hash - mstore(0x00, node) - mstore(0x20, _messageHash) - _messageHash := keccak256(0x00, 0x40) - } - hashBitField /= 2; - height = height + 1; - } - batch[height] = _messageHash; - } - - /** @dev Gets the history of merkle roots in the outbox - * @param _epoch requested epoch outbox history. - */ - function getBatchMerkleRootHistory(uint256 _epoch) public view returns (bytes32) { - return fastOutbox[_epoch]; - } - - /** @dev Gets the merkle root for the current epoch batch - * `O(log(n))` where - * `n` is the current number of leaves (batchSize) - */ - function getBatchMerkleRoot() public view returns (bytes32) { - bytes32 node; - uint256 size = batchSize; - uint256 height = 0; - bool isFirstHash = true; - while (size > 0) { - if ((size & 1) == 1) { - // avoid redundant calculation - if (isFirstHash) { - node = batch[height]; - isFirstHash = false; - } else { - bytes32 hash = batch[height]; - // effecient hash - if (hash > node) - assembly { - mstore(0x00, node) - mstore(0x20, hash) - node := keccak256(0x00, 0x40) - } - else - assembly { - mstore(0x00, hash) - mstore(0x20, node) - node := keccak256(0x00, 0x40) - } - } - } - size /= 2; - height++; - } - return node; - } } diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index fc5bec75b..85f4c88c8 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -44,13 +44,13 @@ interface IFastBridgeReceiver { function challenge() external payable; /** - * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. + * @dev Verifies merkle proof for the given message and associated nonce for the most recent possible epoch and relays the message. * @param _epoch The epoch in which the message was batched by the bridge. * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. * @param _message The data on the cross-domain chain for the message. * @param _nonce The nonce (index in the merkle tree) to avoid replay. */ - function verifyProofAndRelayMessage( + function verifyAndRelayMessage( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message, diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index b9ab5655e..89fbce530 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -24,9 +24,14 @@ interface IFastBridgeSender { * Message is sent with the message sender address as the first argument. * @dev Sends an arbitrary message across domain using the Fast Bridge. * @param _receiver The cross-domain contract address which receives the calldata. + * @param _functionSelector The function selector to call. * @param _calldata The receiving domain encoded message data. */ - function sendFast(address _receiver, bytes memory _calldata) external; + function sendFast( + address _receiver, + bytes4 _functionSelector, + bytes memory _calldata + ) external; /** * @dev Sends a markle root representing an arbitrary batch of messages across domain using the Safe Bridge, which relies on the chain's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGatewayToEthereum.sol index eac9020f8..b960ac229 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/HomeGatewayToEthereum.sol @@ -109,9 +109,9 @@ contract HomeGatewayToEthereum is IHomeGateway { RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; bytes4 methodSelector = IForeignGateway.relayRule.selector; - bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _ruling, relayedData.relayer); + bytes memory data = abi.encode(disputeHash, _ruling, relayedData.relayer); - fastbridge.sendFast(foreignGateway, data); + fastbridge.sendFast(foreignGateway, methodSelector, data); } /** @dev Changes the fastBridge, useful to increase the claim deposit. From 502692bf0b43ee152e874f941fa645e3c665166a Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 20 May 2022 14:00:21 +0100 Subject: [PATCH 17/23] feat: fix chainID constructor --- contracts/deploy/01-foreign-chain.ts | 4 ---- contracts/deploy/02-home-chain.ts | 6 ++---- contracts/src/gateway/ForeignGatewayOnEthereum.sol | 7 +++++-- contracts/src/gateway/HomeGatewayToEthereum.sol | 9 ++++++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index be3da572d..34a59430c 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -91,7 +91,6 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme fastBridgeReceiver.address, [ethers.BigNumber.from(10).pow(17)], homeGatewayAddress, - homeChainIdAsBytes32, chainIdAsBytes32 ) @@ -102,7 +101,6 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme fastBridgeReceiver.address, [ethers.BigNumber.from(10).pow(17)], homeGatewayCentralizedArbitratorAddress, - homeChainIdAsBytes32, chainIdAsBytes32 ) @@ -129,7 +127,6 @@ async function deployForeignGateways( fastBridgeReceiverAddress, feeForJuror, homeGatewayAddress, - homeChainIdAsBytes32, chainIdAsBytes32 ){ let foreignGateway = await deploy(name, { @@ -140,7 +137,6 @@ async function deployForeignGateways( fastBridgeReceiverAddress, feeForJuror, homeGatewayAddress, - homeChainIdAsBytes32, chainIdAsBytes32 ], log: true, diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index fefd6bef7..6aa7791cf 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -45,8 +45,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) klerosCore.address, fastBridgeSender.address, foreignGateway.address, - foreignChainId, - chainId + foreignChainId ], log: true, }); // nonce+1 @@ -59,8 +58,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) centralizedArbitrator.address, fastBridgeSender.address, foreignGateway.address, - foreignChainId, - chainId + foreignChainId ], log: true, }); // nonce+1 diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGatewayOnEthereum.sol index 7e1e1dd62..3d8f5dcc4 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/ForeignGatewayOnEthereum.sol @@ -77,14 +77,17 @@ contract ForeignGatewayOnEthereum is IForeignGateway { IFastBridgeReceiver _fastbridge, uint256[] memory _feeForJuror, address _homeGateway, - uint256 _homeChainID, uint256 _chainID ) { governor = _governor; fastbridge = _fastbridge; feeForJuror = _feeForJuror; homeGateway = _homeGateway; - homeChainID = _homeChainID; + uint256 id; + assembly { + id := chainid() + } + homeChainID = id; chainID = _chainID; } diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGatewayToEthereum.sol index b960ac229..c60f46460 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/HomeGatewayToEthereum.sol @@ -42,15 +42,18 @@ contract HomeGatewayToEthereum is IHomeGateway { IArbitrator _arbitrator, IFastBridgeSender _fastbridge, address _foreignGateway, - uint256 _foreignChainID, - uint256 _chainID + uint256 _foreignChainID ) { governor = _governor; arbitrator = _arbitrator; fastbridge = _fastbridge; foreignGateway = _foreignGateway; foreignChainID = _foreignChainID; - chainID = _chainID; + uint256 id; + assembly { + id := chainid() + } + chainID = id; emit MetaEvidence(0, "BRIDGE"); } From 5a879bf771ec5191ea1893e2666a4f868aa822f3 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 20 May 2022 17:10:09 +0100 Subject: [PATCH 18/23] feat: gnosis chain support --- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 235 +++++++++++++++++- .../src/bridge/FastBridgeSenderToGnosis.sol | 96 ++++++- .../src/bridge/SafeBridgeReceiverOnGnosis.sol | 45 ++++ .../SafeBridgeSenderToGnosisFromArbitrum.sol | 55 ++++ .../src/gateway/ForeignGatewayOnEthereum.sol | 27 +- .../src/gateway/ForeignGatewayOnGnosis.sol | 199 ++++++++++++++- contracts/src/gateway/HomeGatewayToGnosis.sol | 118 ++++++++- 7 files changed, 752 insertions(+), 23 deletions(-) create mode 100644 contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol create mode 100644 contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index ef8df9478..643cb5404 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,12 +10,241 @@ pragma solidity ^0.8.0; +import "./SafeBridgeReceiverOnGnosis.sol"; import "./interfaces/IFastBridgeReceiver.sol"; +import "./merkle/MerkleProof.sol"; /** * Fast Bridge Receiver on Gnosis from Arbitrum * Counterpart of `FastBridgeSenderToGnosis` */ -abstract contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver { - // TODO in prealpha-3 +contract FastBridgeReceiverOnGnosis is SafeBridgeReceiverOnGnosis, IFastBridgeReceiver, MerkleProof { + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + bool honest; + } + + struct Challenge { + address challenger; + bool honest; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable genesis; // Marks the beginning of the first epoch. + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + + mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) + mapping(uint256 => Claim) public claims; // epoch => claim + mapping(uint256 => Challenge) public challenges; // epoch => challenge + mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + + /** + * @dev Constructor. + * @param _amb The address of the AMB contract. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of the period allowing to challenge a claim. + * @param _safeBridgeSender The safe bridge sender on Arbitrum. + * @param _genesis The genesis time to synchronize epochs. + */ + constructor( + IAMB _amb, + uint256 _deposit, + uint256 _epochPeriod, + address _safeBridgeSender, + uint256 _genesis + ) SafeBridgeReceiverOnGnosis(_amb, _safeBridgeSender) { + deposit = _deposit; + epochPeriod = _epochPeriod; + genesis = _genesis; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + uint256 epochClaim = epochCount - 1; // Can only claim last completed epoch. + uint256 claimDeadline = genesis + epochCount * epochPeriod + epochPeriod / 2; + + require(block.timestamp < claimDeadline, "Claim period expired."); + require(claims[epochClaim].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[epochClaim] = Claim({batchMerkleRoot: _batchMerkleRoot, bridger: msg.sender, honest: false}); + + emit ClaimReceived(epochClaim, _batchMerkleRoot); + } + + /** + * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. + */ + function challenge() external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + require(claims[epochChallenge].bridger != address(0), "No claim to challenge."); + + challenges[epochChallenge] = Challenge({challenger: msg.sender, honest: false}); + + emit ClaimChallenged(epochChallenge); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + + require(epochCount > _epoch + 1, "Challenge period for epoch has not elapsed."); + require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); + + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + + if (challenges[_epoch].challenger == address(0)) { + // optimistic happy path + claim.honest = true; + fastInbox[_epoch] = claim.batchMerkleRoot; + } + } + + /** + * Note: Access restricted to the Safe Bridge. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param _epoch The epoch to verify. + * @param _batchMerkleRoot The true batch merkle root for the epoch. + */ + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { + challenges[_epoch].honest = true; + } else { + claims[_epoch].honest = true; + } + } + + /** + * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. + * @param _epoch The epoch in which the message was batched by the bridge. + * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. + * @param _message The data on the cross-domain chain for the message. + * @param _nonce The nonce (index in the merkle tree) to avoid replay. + */ + function verifyAndRelayMessage( + uint256 _epoch, + bytes32[] calldata _proof, + bytes calldata _message, + uint256 _nonce + ) external override { + bytes32 batchMerkleRoot = fastInbox[_epoch]; + require(batchMerkleRoot != bytes32(0), "Invalid epoch."); + + uint256 index = _nonce / 256; + uint256 offset = _nonce - index * 256; + + bytes32 replay = relayed[_epoch][index]; + require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); + relayed[_epoch][index] = replay | bytes32(1 << offset); + + // Claim assessment if any + bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); + + require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction + } + + /** + * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. + * @param _epoch The epoch associated with the claim deposit to withraw. + */ + function withdrawClaimDeposit(uint256 _epoch) external override { + Claim memory claim = claims[_epoch]; + Challenge memory challenge = challenges[_epoch]; + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + + uint256 amount = deposit; + if (challenge.challenger != address(0)) amount = (deposit * 3) / 2; // half burnt + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + /** + * @dev Sends the deposit back to the Challenger if their challenge is successful. Includes a portion of the Bridger's deposit. + * @param _epoch The epoch associated with the challenge deposit to withraw. + */ + function withdrawChallengeDeposit(uint256 _epoch) external override { + Challenge memory challenge = challenges[_epoch]; + require(challenge.challenger != address(0), "Claim does not exist"); + require(challenge.honest == true, "Claim not verified: deposit forfeited"); + + uint256 amount = (deposit * 3) / 2; + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function challengePeriod() external view override returns (uint256 start, uint256 end) { + // start begins latest after the claim deadline expiry + // however can begin as soon as a claim is made + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + start = genesis + epochChallenge * epochPeriod + epochPeriod / 2; + end = start + epochPeriod / 2; + return (start, end); + } + + /** + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function epochCount() external view returns (uint256 _epochCount) { + _epochCount = (block.timestamp - genesis) / epochPeriod; + } + + // ************************ // + // * Internal * // + // ************************ // + + function _relay(bytes calldata _messageData) internal returns (bool success) { + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); + (success, ) = address(receiver).call(data); + } } diff --git a/contracts/src/bridge/FastBridgeSenderToGnosis.sol b/contracts/src/bridge/FastBridgeSenderToGnosis.sol index c03379b0a..336e7e52c 100644 --- a/contracts/src/bridge/FastBridgeSenderToGnosis.sol +++ b/contracts/src/bridge/FastBridgeSenderToGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,12 +10,102 @@ pragma solidity ^0.8.0; +import "./SafeBridgeSenderToGnosisFromArbitrum.sol"; import "./interfaces/IFastBridgeSender.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; +import "./merkle/MerkleTree.sol"; /** * Fast Bridge Sender to Gnosis from Arbitrum * Counterpart of `FastBridgeReceiverOnGnosis` */ -abstract contract FastBridgeSenderToGnosis is IFastBridgeSender { - // TODO in prealpha-3 +contract FastBridgeSenderToGnosis is SafeBridgeSenderToGnosisFromArbitrum, IFastBridgeSender, MerkleTree { + // ************************************* // + // * Storage * // + // ************************************* // + + IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Gnosis Chain. + + uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + + // ************************************* // + // * Events * // + // ************************************* // + + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiverOnEthereum. + */ + event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + + /** + * @dev Constructor. + * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _epochPeriod The duration between epochs. + * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. + * @param _amb The address of the AMB contract on Ethereum + */ + constructor( + IFastBridgeReceiver _fastBridgeReceiver, + uint256 _epochPeriod, + uint256 _genesis, + IAMB _amb + ) SafeBridgeSenderToGnosisFromArbitrum(_amb) { + fastBridgeReceiver = _fastBridgeReceiver; + epochPeriod = _epochPeriod; + genesis = _genesis; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. + * @param _receiver The address of the contract on Ethereum which receives the calldata. + * @param _functionSelector The function to call. + * @param _calldata The receiving domain encoded message data / function arguments. + */ + function sendFast( + address _receiver, + bytes4 _functionSelector, + bytes memory _calldata + ) external override { + bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + appendMessage(fastMessageHash); // add message to merkle tree + emit MessageReceived(_receiver, _fastMessage, batchSize); + } + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendEpoch() external { + uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; + require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); + require(batchSize > 0, "No messages to send."); + + // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRootAndReset(); + fastOutbox[epochFinalized] = batchMerkleRoot; + + emit SendBatch(epochFinalized, batchMerkleRoot); + } + + /** + * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch The blocknumber of the batch + */ + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); + // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed + _sendSafe(address(fastBridgeReceiver), safeMessageData); + } } diff --git a/contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol b/contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol new file mode 100644 index 000000000..388d9e260 --- /dev/null +++ b/contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; + +/** + * Safe Bridge Receiver on Gnosis from Arbitrum + * Counterpart of `SafeBridgeSenderToGnosis` + */ +contract SafeBridgeReceiverOnGnosis is ISafeBridgeReceiver { + // ************************************* // + // * Storage * // + // ************************************* // + + // will be set as immutable in production deployment for gas optimization + address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. + IAMB public immutable amb; // The address of the AMB contract. + + /** + * @dev Constructor. + * @param _amb The address of the AMB contract. + */ + constructor(IAMB _amb, address _safeBridgeSender) { + amb = _amb; + safeBridgeSender = _safeBridgeSender; + } + + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + return amb.messageSender() == safeBridgeSender; + } +} diff --git a/contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol b/contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol new file mode 100644 index 000000000..c6142f3b5 --- /dev/null +++ b/contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shalzz, @shtoaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/arbitrum/IArbSys.sol"; +import "./interfaces/arbitrum/AddressAliasHelper.sol"; +import "./interfaces/ISafeBridgeSender.sol"; + +/** + * Safe Bridge Sender to Gnosis from Ethereum + * Counterpart of `SafeBridgeReceiverOnGnosis` + */ +contract SafeBridgeSenderToGnosisFromArbitrum is ISafeBridgeSender { + IAMB public immutable amb; + + constructor(IAMB _amb) { + amb = _amb; + } + + // ************************************* // + // * Events * // + // ************************************* // + + event L2ToL1TxCreated(uint256 indexed withdrawalId); + + // ************************************* // + // * Storage * // + // ************************************* // + + IArbSys public constant ARB_SYS = IArbSys(address(100)); + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { + // Safe Bridge message envelope + bytes4 methodSelector = IAMB.requireToPassMessage.selector; + // 4000000 is the max gas fee, set at a resonable level for deployment + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _receiver, _calldata, 4000000); + uint256 withdrawalId = ARB_SYS.sendTxToL1(address(amb), safeMessageData); + + emit L2ToL1TxCreated(withdrawalId); + return withdrawalId; + } +} diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGatewayOnEthereum.sol index 3d8f5dcc4..e11b756fa 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/ForeignGatewayOnEthereum.sol @@ -31,8 +31,8 @@ contract ForeignGatewayOnEthereum is IForeignGateway { // feeForJuror by subcourtID uint256[] internal feeForJuror; - uint256 public immutable chainID; - uint256 public immutable homeChainID; + uint256 public immutable override chainID; + uint256 public immutable override homeChainID; struct DisputeData { uint248 id; @@ -47,7 +47,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { IFastBridgeReceiver public fastbridge; IFastBridgeReceiver public depreciatedFastbridge; uint256 public fastbridgeExpiration; - address public immutable homeGateway; + address public immutable override homeGateway; event OutgoingDispute( bytes32 disputeHash, @@ -77,7 +77,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { IFastBridgeReceiver _fastbridge, uint256[] memory _feeForJuror, address _homeGateway, - uint256 _chainID + uint256 _homeChainID ) { governor = _governor; fastbridge = _fastbridge; @@ -87,8 +87,8 @@ contract ForeignGatewayOnEthereum is IForeignGateway { assembly { id := chainid() } - homeChainID = id; - chainID = _chainID; + chainID = id; + homeChainID = _homeChainID; } /** @dev Changes the fastBridge, useful to increase the claim deposit. @@ -117,7 +117,12 @@ contract ForeignGatewayOnEthereum is IForeignGateway { feeForJuror.push(_feeForJuror); } - function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) { + function createDispute(uint256 _choices, bytes calldata _extraData) + external + payable + override + returns (uint256 disputeID) + { require(msg.value >= arbitrationCost(_extraData), "Not paid enough for arbitration"); disputeID = localDisputeID++; @@ -145,7 +150,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { emit DisputeCreation(disputeID, IArbitrable(msg.sender)); } - function arbitrationCost(bytes calldata _extraData) public view returns (uint256 cost) { + function arbitrationCost(bytes calldata _extraData) public view override returns (uint256 cost) { (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDMinJurors(_extraData); cost = feeForJuror[subcourtID] * minJurors; @@ -159,7 +164,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { bytes32 _disputeHash, uint256 _ruling, address _relayer - ) external onlyFromFastBridge { + ) external override onlyFromFastBridge { require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; @@ -173,7 +178,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { arbitrable.rule(dispute.id, _ruling); } - function withdrawFees(bytes32 _disputeHash) external { + function withdrawFees(bytes32 _disputeHash) external override { DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); require(dispute.ruled, "Not ruled yet"); @@ -183,7 +188,7 @@ contract ForeignGatewayOnEthereum is IForeignGateway { payable(dispute.relayer).transfer(amount); } - function disputeHashToForeignID(bytes32 _disputeHash) external view returns (uint256) { + function disputeHashToForeignID(bytes32 _disputeHash) external view override returns (uint256) { return disputeHashtoDisputeData[_disputeHash].id; } diff --git a/contracts/src/gateway/ForeignGatewayOnGnosis.sol b/contracts/src/gateway/ForeignGatewayOnGnosis.sol index 6dc1cfd0e..c8c658400 100644 --- a/contracts/src/gateway/ForeignGatewayOnGnosis.sol +++ b/contracts/src/gateway/ForeignGatewayOnGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,12 +10,205 @@ pragma solidity ^0.8.0; +import "../arbitration/IArbitrable.sol"; +import "../bridge/interfaces/IFastBridgeReceiver.sol"; + import "./interfaces/IForeignGateway.sol"; /** * Foreign Gateway on Gnosis chain * Counterpart of `HomeGatewayToGnosis` */ -abstract contract ForeignGatewayOnGnosis is IForeignGateway { - // TODO in prealpha-3 +contract ForeignGatewayOnGnosis is IForeignGateway { + // The global default minimum number of jurors in a dispute. + uint256 public constant MIN_JURORS = 3; + + // @dev Note the disputeID needs to start from one as + // the KlerosV1 proxy governor depends on this implementation. + // We now also depend on localDisputeID not being zero + // at any point. + uint256 internal localDisputeID = 1; + + // feeForJuror by subcourtID + uint256[] internal feeForJuror; + uint256 public immutable override chainID; + uint256 public immutable override homeChainID; + + struct DisputeData { + uint248 id; + bool ruled; + address arbitrable; + uint256 paid; + address relayer; + } + mapping(bytes32 => DisputeData) public disputeHashtoDisputeData; + + address public governor; + IFastBridgeReceiver public fastbridge; + IFastBridgeReceiver public depreciatedFastbridge; + uint256 public fastbridgeExpiration; + address public immutable override homeGateway; + + event OutgoingDispute( + bytes32 disputeHash, + bytes32 blockhash, + uint256 localDisputeID, + uint256 _choices, + bytes _extraData, + address arbitrable + ); + + modifier onlyFromFastBridge() { + require( + address(fastbridge) == msg.sender || + ((block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender), + "Access not allowed: Fast Bridge only." + ); + _; + } + + modifier onlyByGovernor() { + require(governor == msg.sender, "Access not allowed: Governor only."); + _; + } + + constructor( + address _governor, + IFastBridgeReceiver _fastbridge, + uint256[] memory _feeForJuror, + address _homeGateway, + uint256 _homeChainID + ) { + governor = _governor; + fastbridge = _fastbridge; + feeForJuror = _feeForJuror; + homeGateway = _homeGateway; + uint256 id; + assembly { + id := chainid() + } + chainID = id; + homeChainID = _homeChainID; + } + + /** @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastbridge The address of the new fastBridge. + * @param _gracePeriod The duration to accept messages from the deprecated bridge (if at all). + */ + function changeFastbridge(IFastBridgeReceiver _fastbridge, uint256 _gracePeriod) external onlyByGovernor { + // grace period to relay remaining messages in the relay / bridging process + fastbridgeExpiration = block.timestamp + _fastbridge.epochPeriod() + _gracePeriod; // 2 weeks + depreciatedFastbridge = fastbridge; + fastbridge = _fastbridge; + } + + /** @dev Changes the `feeForJuror` property value of a specified subcourt. + * @param _subcourtID The ID of the subcourt. + * @param _feeForJuror The new value for the `feeForJuror` property value. + */ + function changeSubcourtJurorFee(uint96 _subcourtID, uint256 _feeForJuror) external onlyByGovernor { + feeForJuror[_subcourtID] = _feeForJuror; + } + + /** @dev Creates the `feeForJuror` property value for a new subcourt. + * @param _feeForJuror The new value for the `feeForJuror` property value. + */ + function createSubcourtJurorFee(uint256 _feeForJuror) external onlyByGovernor { + feeForJuror.push(_feeForJuror); + } + + function createDispute(uint256 _choices, bytes calldata _extraData) + external + payable + override + 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, + _choices, + _extraData, + msg.sender + ) + ); + + disputeHashtoDisputeData[disputeHash] = DisputeData({ + id: uint248(disputeID), + arbitrable: msg.sender, + paid: msg.value, + relayer: address(0), + ruled: false + }); + + emit OutgoingDispute(disputeHash, blockhash(block.number - 1), disputeID, _choices, _extraData, msg.sender); + emit DisputeCreation(disputeID, IArbitrable(msg.sender)); + } + + function arbitrationCost(bytes calldata _extraData) public view override returns (uint256 cost) { + (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDMinJurors(_extraData); + + cost = feeForJuror[subcourtID] * minJurors; + } + + /** + * Relay the rule call from the home gateway to the arbitrable. + */ + function relayRule( + address _messageOrigin, + bytes32 _disputeHash, + uint256 _ruling, + address _relayer + ) external override onlyFromFastBridge { + require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); + DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; + + require(dispute.id != 0, "Dispute does not exist"); + require(!dispute.ruled, "Cannot rule twice"); + + dispute.ruled = true; + dispute.relayer = _relayer; + + IArbitrable arbitrable = IArbitrable(dispute.arbitrable); + arbitrable.rule(dispute.id, _ruling); + } + + function withdrawFees(bytes32 _disputeHash) external override { + DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; + require(dispute.id != 0, "Dispute does not exist"); + require(dispute.ruled, "Not ruled yet"); + + uint256 amount = dispute.paid; + dispute.paid = 0; + payable(dispute.relayer).transfer(amount); + } + + function disputeHashToForeignID(bytes32 _disputeHash) external view override returns (uint256) { + return disputeHashtoDisputeData[_disputeHash].id; + } + + function extraDataToSubcourtIDMinJurors(bytes memory _extraData) + internal + view + returns (uint96 subcourtID, uint256 minJurors) + { + // Note that here we ignore DisputeKitID + if (_extraData.length >= 64) { + assembly { + // solium-disable-line security/no-inline-assembly + subcourtID := mload(add(_extraData, 0x20)) + minJurors := mload(add(_extraData, 0x40)) + } + if (subcourtID >= feeForJuror.length) subcourtID = 0; + if (minJurors == 0) minJurors = MIN_JURORS; + } else { + subcourtID = 0; + minJurors = MIN_JURORS; + } + } } diff --git a/contracts/src/gateway/HomeGatewayToGnosis.sol b/contracts/src/gateway/HomeGatewayToGnosis.sol index 10a5b7858..397fcb767 100644 --- a/contracts/src/gateway/HomeGatewayToGnosis.sol +++ b/contracts/src/gateway/HomeGatewayToGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -12,10 +12,122 @@ pragma solidity ^0.8.0; import "./interfaces/IHomeGateway.sol"; +import "../arbitration/IArbitrator.sol"; +import "../bridge/interfaces/IFastBridgeSender.sol"; + +import "./interfaces/IForeignGateway.sol"; +import "./interfaces/IHomeGateway.sol"; + /** * Home Gateway to Gnosis chain * Counterpart of `ForeignGatewayOnGnosis` */ -abstract contract HomeGatewayToGnosis is IHomeGateway { - // TODO in prealpha-3 +contract HomeGatewayToGnosis is IHomeGateway { + mapping(uint256 => bytes32) public disputeIDtoHash; + mapping(bytes32 => uint256) public disputeHashtoID; + + address public governor; + IArbitrator public immutable arbitrator; + IFastBridgeSender public fastbridge; + address public override foreignGateway; + uint256 public immutable override chainID; + uint256 public immutable override foreignChainID; + + struct RelayedData { + uint256 arbitrationCost; + address relayer; + } + mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; + + constructor( + address _governor, + IArbitrator _arbitrator, + IFastBridgeSender _fastbridge, + address _foreignGateway, + uint256 _foreignChainID + ) { + governor = _governor; + arbitrator = _arbitrator; + fastbridge = _fastbridge; + foreignGateway = _foreignGateway; + foreignChainID = _foreignChainID; + uint256 id; + assembly { + id := chainid() + } + chainID = id; + + emit MetaEvidence(0, "BRIDGE"); + } + + /** + * @dev Provide the same parameters as on the originalChain while creating a + * dispute. Providing incorrect parameters will create a different hash + * than on the originalChain and will not affect the actual dispute/arbitrable's + * ruling. + * + * @param _originalChainID originalChainId + * @param _originalBlockHash originalBlockHash + * @param _originalDisputeID originalDisputeID + * @param _choices number of ruling choices + * @param _extraData extraData + * @param _arbitrable arbitrable + */ + function relayCreateDispute( + uint256 _originalChainID, + bytes32 _originalBlockHash, + uint256 _originalDisputeID, + uint256 _choices, + bytes calldata _extraData, + address _arbitrable + ) external payable override { + bytes32 disputeHash = keccak256( + abi.encodePacked( + _originalChainID, + _originalBlockHash, + "createDispute", + _originalDisputeID, + _choices, + _extraData, + _arbitrable + ) + ); + RelayedData storage relayedData = disputeHashtoRelayedData[disputeHash]; + require(relayedData.relayer == address(0), "Dispute already relayed"); + + // TODO: will mostly be replaced by the actual arbitrationCost paid on the foreignChain. + relayedData.arbitrationCost = arbitrator.arbitrationCost(_extraData); + require(msg.value >= relayedData.arbitrationCost, "Not enough arbitration cost paid"); + + uint256 disputeID = arbitrator.createDispute{value: msg.value}(_choices, _extraData); + disputeIDtoHash[disputeID] = disputeHash; + disputeHashtoID[disputeHash] = disputeID; + relayedData.relayer = msg.sender; + + emit Dispute(arbitrator, disputeID, 0, 0); + } + + function rule(uint256 _disputeID, uint256 _ruling) external override { + require(msg.sender == address(arbitrator), "Only Arbitrator"); + + bytes32 disputeHash = disputeIDtoHash[_disputeID]; + RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; + + bytes4 methodSelector = IForeignGateway.relayRule.selector; + bytes memory data = abi.encode(disputeHash, _ruling, relayedData.relayer); + + fastbridge.sendFast(foreignGateway, methodSelector, data); + } + + /** @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastbridge The address of the new fastBridge. + */ + function changeFastbridge(IFastBridgeSender _fastbridge) external { + require(governor == msg.sender, "Access not allowed: Governor only."); + fastbridge = _fastbridge; + } + + function disputeHashToHomeID(bytes32 _disputeHash) external view override returns (uint256) { + return disputeHashtoID[_disputeHash]; + } } From 07f7c2f089a5277fc943da02e5b6857009718af4 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 23 May 2022 19:19:57 +0100 Subject: [PATCH 19/23] feat: refactoring and gc fallback fix --- .../bridge/FastBridgeReceiverOnEthereum.sol | 229 ++-------------- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 231 ++--------------- .../src/bridge/FastBridgeSenderToEthereum.sol | 81 +----- .../src/bridge/FastBridgeSenderToGnosis.sol | 87 ++----- .../interfaces/FastBridgeReceiverBase.sol | 245 ++++++++++++++++++ .../interfaces/FastBridgeSenderBase.sol | 109 ++++++++ .../SafeBridgeReceiverOnEthereum.sol | 7 +- .../SafeBridgeReceiverOnGnosis.sol | 4 +- ...SafeBridgeSenderToArbitrumFromEthereum.sol | 8 +- .../SafeBridgeSenderToEthereum.sol | 7 +- .../SafeBridgeSenderToGnosis.sol | 4 +- .../SafeBridgeSenderToGnosisFromArbitrum.sol | 8 +- ...tewayOnEthereum.sol => ForeignGateway.sol} | 0 .../src/gateway/ForeignGatewayOnGnosis.sol | 214 --------------- ...eGatewayToEthereum.sol => HomeGateway.sol} | 0 contracts/src/gateway/HomeGatewayToGnosis.sol | 133 ---------- 16 files changed, 454 insertions(+), 913 deletions(-) create mode 100644 contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol create mode 100644 contracts/src/bridge/interfaces/FastBridgeSenderBase.sol rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeReceiverOnEthereum.sol (85%) rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeReceiverOnGnosis.sol (92%) rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeSenderToArbitrumFromEthereum.sol (93%) rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeSenderToEthereum.sol (88%) rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeSenderToGnosis.sol (87%) rename contracts/src/bridge/{ => safe-bridges}/SafeBridgeSenderToGnosisFromArbitrum.sol (89%) rename contracts/src/gateway/{ForeignGatewayOnEthereum.sol => ForeignGateway.sol} (100%) delete mode 100644 contracts/src/gateway/ForeignGatewayOnGnosis.sol rename contracts/src/gateway/{HomeGatewayToEthereum.sol => HomeGateway.sol} (100%) delete mode 100644 contracts/src/gateway/HomeGatewayToGnosis.sol diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 7ba3c6535..b699c8fc8 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -10,239 +10,66 @@ pragma solidity ^0.8.0; -import "./SafeBridgeReceiverOnEthereum.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; -import "./merkle/MerkleProof.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/FastBridgeReceiverBase.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/arbitrum/IInbox.sol"; +import "./interfaces/arbitrum/IOutbox.sol"; /** * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToEthereum` */ -contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBridgeReceiver, MerkleProof { - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - struct Claim { - bytes32 batchMerkleRoot; - address bridger; - bool honest; - } - - struct Challenge { - address challenger; - bool honest; - } - +contract FastBridgeReceiverOnEthereum is ISafeBridgeReceiver, FastBridgeReceiverBase { // ************************************* // // * Storage * // // ************************************* // - uint256 public immutable deposit; // The deposit required to submit a claim or challenge - uint256 public immutable genesis; // Marks the beginning of the first epoch. - uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. - - mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) - mapping(uint256 => Claim) public claims; // epoch => claim - mapping(uint256 => Challenge) public challenges; // epoch => challenge - mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. + address public immutable fastBridgeReceiverOnGC; // The address of the Fast Bridge Receiver on Gnosis Chain. + IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. + IAMB public immutable amb; // The address of the AMB contract on Ethereum. /** * @dev Constructor. - * @param _inbox The address of the Arbitrum Inbox contract. * @param _deposit The deposit amount to submit a claim in wei. * @param _epochPeriod The duration of the period allowing to challenge a claim. */ constructor( - address _inbox, uint256 _deposit, uint256 _epochPeriod, + uint256 _genesis, + address _inbox, address _safeBridgeSender, - uint256 _genesis - ) SafeBridgeReceiverOnEthereum(_inbox, _safeBridgeSender) { - deposit = _deposit; - epochPeriod = _epochPeriod; - genesis = _genesis; - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /** - * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. - * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. - */ - function claim(bytes32 _batchMerkleRoot) external payable override { - require(msg.value >= deposit, "Insufficient claim deposit."); - require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - - uint256 epochCount = (block.timestamp - genesis) / epochPeriod; - uint256 epochClaim = epochCount - 1; // Can only claim last completed epoch. - uint256 claimDeadline = genesis + epochCount * epochPeriod + epochPeriod / 2; - - require(block.timestamp < claimDeadline, "Claim period expired."); - require(claims[epochClaim].bridger == address(0), "Claim already made for most recent finalized epoch."); - - claims[epochClaim] = Claim({batchMerkleRoot: _batchMerkleRoot, bridger: msg.sender, honest: false}); - - emit ClaimReceived(epochClaim, _batchMerkleRoot); - } - - /** - * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. - */ - function challenge() external payable override { - require(msg.value >= deposit, "Not enough claim deposit"); - - // can only challenge the only active claim, about the previous epoch - uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; - require(claims[epochChallenge].bridger != address(0), "No claim to challenge."); - - challenges[epochChallenge] = Challenge({challenger: msg.sender, honest: false}); - - emit ClaimChallenged(epochChallenge); - } - - /** - * @dev Resolves the optimistic claim for '_epoch'. - * @param _epoch The epoch of the optimistic claim. - */ - function verify(uint256 _epoch) public { - uint256 epochCount = (block.timestamp - genesis) / epochPeriod; - - require(epochCount > _epoch + 1, "Challenge period for epoch has not elapsed."); - require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); - - Claim storage claim = claims[_epoch]; - require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); - - if (challenges[_epoch].challenger == address(0)) { - // optimistic happy path - claim.honest = true; - fastInbox[_epoch] = claim.batchMerkleRoot; - } + IAMB _amb, + address _fastBridgeReceiverOnGC + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { + inbox = IInbox(_inbox); + safeBridgeSender = _safeBridgeSender; + amb = _amb; + fastBridgeReceiverOnGC = _fastBridgeReceiverOnGC; } /** * Note: Access restricted to the Safe Bridge. - * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @dev Relays safe epoch merkle root state to Gnosis Chain to resolve challenges. * @param _epoch The epoch to verify. * @param _batchMerkleRoot The true batch merkle root for the epoch. */ - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { + function verifySafeGnosisRelay(uint256 _epoch, bytes32 _batchMerkleRoot) external { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { - challenges[_epoch].honest = true; - } else { - claims[_epoch].honest = true; - } - } - - /** - * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. - * @param _epoch The epoch in which the message was batched by the bridge. - * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. - * @param _message The data on the cross-domain chain for the message. - * @param _nonce The nonce (index in the merkle tree) to avoid replay. - */ - function verifyAndRelayMessage( - uint256 _epoch, - bytes32[] calldata _proof, - bytes calldata _message, - uint256 _nonce - ) external override { - bytes32 batchMerkleRoot = fastInbox[_epoch]; - require(batchMerkleRoot != bytes32(0), "Invalid epoch."); - - uint256 index = _nonce / 256; - uint256 offset = _nonce - index * 256; - - bytes32 replay = relayed[_epoch][index]; - require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); - relayed[_epoch][index] = replay | bytes32(1 << offset); - - // Claim assessment if any - bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); - - require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); - require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction - } - - /** - * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. - * @param _epoch The epoch associated with the claim deposit to withraw. - */ - function withdrawClaimDeposit(uint256 _epoch) external override { - Claim memory claim = claims[_epoch]; - Challenge memory challenge = challenges[_epoch]; - require(claim.bridger != address(0), "Claim does not exist"); - require(claim.honest == true, "Claim not verified."); - - uint256 amount = deposit; - if (challenge.challenger != address(0)) amount = (deposit * 3) / 2; // half burnt - - delete claims[_epoch]; - delete challenges[_epoch]; - payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. - // Checks-Effects-Interaction - } - - /** - * @dev Sends the deposit back to the Challenger if their challenge is successful. Includes a portion of the Bridger's deposit. - * @param _epoch The epoch associated with the challenge deposit to withraw. - */ - function withdrawChallengeDeposit(uint256 _epoch) external override { - Challenge memory challenge = challenges[_epoch]; - require(challenge.challenger != address(0), "Claim does not exist"); - require(challenge.honest == true, "Claim not verified: deposit forfeited"); - - uint256 amount = (deposit * 3) / 2; - - delete claims[_epoch]; - delete challenges[_epoch]; - payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. - // Checks-Effects-Interaction + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, _batchMerkleRoot); + amb.requireToPassMessage(fastBridgeReceiverOnGC, safeMessageData, amb.maxGasPerTx()); } // ************************************* // - // * Public Views * // + // * Views * // // ************************************* // - /** - * Returns the `start` and `end` time of challenge period for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. - */ - function challengePeriod() external view override returns (uint256 start, uint256 end) { - // start begins latest after the claim deadline expiry - // however can begin as soon as a claim is made - // can only challenge the only active claim, about the previous epoch - uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; - start = genesis + epochChallenge * epochPeriod + epochPeriod / 2; - end = start + epochPeriod / 2; - return (start, end); - } - - /** - * Returns the `start` and `end` time of challenge period for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. - */ - function epochCount() external view returns (uint256 _epochCount) { - _epochCount = (block.timestamp - genesis) / epochPeriod; - } - - // ************************ // - // * Internal * // - // ************************ // - - function _relay(bytes calldata _messageData) internal returns (bool success) { - // Decode the receiver address from the data encoded by the IFastBridgeSender - (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); - (success, ) = address(receiver).call(data); + function isSentBySafeBridge() internal view override returns (bool) { + IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); + return outbox.l2ToL1Sender() == safeBridgeSender; } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index 643cb5404..b757e891e 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shotaronowhere] + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,241 +10,48 @@ pragma solidity ^0.8.0; -import "./SafeBridgeReceiverOnGnosis.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/FastBridgeReceiverBase.sol"; import "./merkle/MerkleProof.sol"; /** - * Fast Bridge Receiver on Gnosis from Arbitrum + * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToGnosis` */ -contract FastBridgeReceiverOnGnosis is SafeBridgeReceiverOnGnosis, IFastBridgeReceiver, MerkleProof { - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - struct Claim { - bytes32 batchMerkleRoot; - address bridger; - bool honest; - } - - struct Challenge { - address challenger; - bool honest; - } - +contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBase { // ************************************* // // * Storage * // // ************************************* // - uint256 public immutable deposit; // The deposit required to submit a claim or challenge - uint256 public immutable genesis; // Marks the beginning of the first epoch. - uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. - - mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) - mapping(uint256 => Claim) public claims; // epoch => claim - mapping(uint256 => Challenge) public challenges; // epoch => challenge - mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + address public immutable safeBridgeRelayer; // The address of the Safe Bridge relayer on Ethereum. + IAMB public immutable amb; // The address of the AMB contract on GC. /** * @dev Constructor. - * @param _amb The address of the AMB contract. * @param _deposit The deposit amount to submit a claim in wei. * @param _epochPeriod The duration of the period allowing to challenge a claim. - * @param _safeBridgeSender The safe bridge sender on Arbitrum. * @param _genesis The genesis time to synchronize epochs. + * @param _amb The address of the AMB contract on GC. + * @param _safeBridgeRelayer The safe bridge relayer on Ethereum. */ constructor( - IAMB _amb, uint256 _deposit, uint256 _epochPeriod, - address _safeBridgeSender, - uint256 _genesis - ) SafeBridgeReceiverOnGnosis(_amb, _safeBridgeSender) { - deposit = _deposit; - epochPeriod = _epochPeriod; - genesis = _genesis; - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /** - * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. - * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. - */ - function claim(bytes32 _batchMerkleRoot) external payable override { - require(msg.value >= deposit, "Insufficient claim deposit."); - require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - - uint256 epochCount = (block.timestamp - genesis) / epochPeriod; - uint256 epochClaim = epochCount - 1; // Can only claim last completed epoch. - uint256 claimDeadline = genesis + epochCount * epochPeriod + epochPeriod / 2; - - require(block.timestamp < claimDeadline, "Claim period expired."); - require(claims[epochClaim].bridger == address(0), "Claim already made for most recent finalized epoch."); - - claims[epochClaim] = Claim({batchMerkleRoot: _batchMerkleRoot, bridger: msg.sender, honest: false}); - - emit ClaimReceived(epochClaim, _batchMerkleRoot); - } - - /** - * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. - */ - function challenge() external payable override { - require(msg.value >= deposit, "Not enough claim deposit"); - - // can only challenge the only active claim, about the previous epoch - uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; - require(claims[epochChallenge].bridger != address(0), "No claim to challenge."); - - challenges[epochChallenge] = Challenge({challenger: msg.sender, honest: false}); - - emit ClaimChallenged(epochChallenge); - } - - /** - * @dev Resolves the optimistic claim for '_epoch'. - * @param _epoch The epoch of the optimistic claim. - */ - function verify(uint256 _epoch) public { - uint256 epochCount = (block.timestamp - genesis) / epochPeriod; - - require(epochCount > _epoch + 1, "Challenge period for epoch has not elapsed."); - require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); - - Claim storage claim = claims[_epoch]; - require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); - - if (challenges[_epoch].challenger == address(0)) { - // optimistic happy path - claim.honest = true; - fastInbox[_epoch] = claim.batchMerkleRoot; - } - } - - /** - * Note: Access restricted to the Safe Bridge. - * @dev Resolves any challenge of the optimistic claim for '_epoch'. - * @param _epoch The epoch to verify. - * @param _batchMerkleRoot The true batch merkle root for the epoch. - */ - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { - challenges[_epoch].honest = true; - } else { - claims[_epoch].honest = true; - } - } - - /** - * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. - * @param _epoch The epoch in which the message was batched by the bridge. - * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. - * @param _message The data on the cross-domain chain for the message. - * @param _nonce The nonce (index in the merkle tree) to avoid replay. - */ - function verifyAndRelayMessage( - uint256 _epoch, - bytes32[] calldata _proof, - bytes calldata _message, - uint256 _nonce - ) external override { - bytes32 batchMerkleRoot = fastInbox[_epoch]; - require(batchMerkleRoot != bytes32(0), "Invalid epoch."); - - uint256 index = _nonce / 256; - uint256 offset = _nonce - index * 256; - - bytes32 replay = relayed[_epoch][index]; - require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); - relayed[_epoch][index] = replay | bytes32(1 << offset); - - // Claim assessment if any - bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); - - require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); - require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction - } - - /** - * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. - * @param _epoch The epoch associated with the claim deposit to withraw. - */ - function withdrawClaimDeposit(uint256 _epoch) external override { - Claim memory claim = claims[_epoch]; - Challenge memory challenge = challenges[_epoch]; - require(claim.bridger != address(0), "Claim does not exist"); - require(claim.honest == true, "Claim not verified."); - - uint256 amount = deposit; - if (challenge.challenger != address(0)) amount = (deposit * 3) / 2; // half burnt - - delete claims[_epoch]; - delete challenges[_epoch]; - payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. - // Checks-Effects-Interaction - } - - /** - * @dev Sends the deposit back to the Challenger if their challenge is successful. Includes a portion of the Bridger's deposit. - * @param _epoch The epoch associated with the challenge deposit to withraw. - */ - function withdrawChallengeDeposit(uint256 _epoch) external override { - Challenge memory challenge = challenges[_epoch]; - require(challenge.challenger != address(0), "Claim does not exist"); - require(challenge.honest == true, "Claim not verified: deposit forfeited"); - - uint256 amount = (deposit * 3) / 2; - - delete claims[_epoch]; - delete challenges[_epoch]; - payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. - // Checks-Effects-Interaction + uint256 _genesis, + address _amb, + address _safeBridgeRelayer + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { + amb = IAMB(_amb); + safeBridgeRelayer = _safeBridgeRelayer; } // ************************************* // - // * Public Views * // + // * Views * // // ************************************* // - /** - * Returns the `start` and `end` time of challenge period for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. - */ - function challengePeriod() external view override returns (uint256 start, uint256 end) { - // start begins latest after the claim deadline expiry - // however can begin as soon as a claim is made - // can only challenge the only active claim, about the previous epoch - uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; - start = genesis + epochChallenge * epochPeriod + epochPeriod / 2; - end = start + epochPeriod / 2; - return (start, end); - } - - /** - * Returns the `start` and `end` time of challenge period for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. - */ - function epochCount() external view returns (uint256 _epochCount) { - _epochCount = (block.timestamp - genesis) / epochPeriod; - } - - // ************************ // - // * Internal * // - // ************************ // - - function _relay(bytes calldata _messageData) internal returns (bool success) { - // Decode the receiver address from the data encoded by the IFastBridgeSender - (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); - (success, ) = address(receiver).call(data); + function isSentBySafeBridge() internal view override returns (bool) { + return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeRelayer); } } diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index 5881073e0..172a8b9a5 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -10,35 +10,26 @@ pragma solidity ^0.8.0; -import "./SafeBridgeSenderToEthereum.sol"; -import "./interfaces/IFastBridgeSender.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; -import "./merkle/MerkleTree.sol"; +import "./interfaces/FastBridgeSenderBase.sol"; +import "./interfaces/arbitrum/IArbSys.sol"; +import "./interfaces/arbitrum/AddressAliasHelper.sol"; /** * Fast Bridge Sender to Ethereum from Arbitrum * Counterpart of `FastBridgeReceiverOnEthereum` */ -contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender, MerkleTree { +contract FastBridgeSenderToEthereum is FastBridgeSenderBase { // ************************************* // - // * Storage * // + // * Events * // // ************************************* // - IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. - - uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). - uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. - mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + event L2ToL1TxCreated(uint256 indexed withdrawalId); // ************************************* // - // * Events * // + // * Storage * // // ************************************* // - /** - * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiverOnEthereum. - */ - event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + IArbSys public constant ARB_SYS = IArbSys(address(100)); /** * @dev Constructor. @@ -49,60 +40,16 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe IFastBridgeReceiver _fastBridgeReceiver, uint256 _epochPeriod, uint256 _genesis - ) SafeBridgeSenderToEthereum() { - fastBridgeReceiver = _fastBridgeReceiver; - epochPeriod = _epochPeriod; - genesis = _genesis; - } + ) FastBridgeSenderBase(_fastBridgeReceiver, _epochPeriod, _genesis) {} // ************************************* // - // * State Modifiers * // + // * Function Modifiers * // // ************************************* // - /** - * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. - * @param _receiver The address of the contract on Ethereum which receives the calldata. - * @param _functionSelector The function to call. - * @param _calldata The receiving domain encoded message data / function arguments. - */ - function sendFast( - address _receiver, - bytes4 _functionSelector, - bytes memory _calldata - ) external override { - bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); - bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); - appendMessage(fastMessageHash); // add message to merkle tree - emit MessageReceived(_receiver, _fastMessage, batchSize); - } - - /** - * Sends a batch of arbitrary message from one domain to another - * via the fast bridge mechanism. - */ - function sendEpoch() external { - uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; - require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); - require(batchSize > 0, "No messages to send."); - - // set merkle root in outbox and reset merkle tree - bytes32 batchMerkleRoot = getMerkleRootAndReset(); - fastOutbox[epochFinalized] = batchMerkleRoot; - - emit SendBatch(epochFinalized, batchMerkleRoot); - } - - /** - * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _epoch The blocknumber of the batch - */ - function sendSafeFallback(uint256 _epoch) external payable override { - bytes32 batchMerkleRoot = fastOutbox[_epoch]; + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { + uint256 withdrawalId = ARB_SYS.sendTxToL1(_receiver, _calldata); - // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafe(address(fastBridgeReceiver), safeMessageData); + emit L2ToL1TxCreated(withdrawalId); + return withdrawalId; } } diff --git a/contracts/src/bridge/FastBridgeSenderToGnosis.sol b/contracts/src/bridge/FastBridgeSenderToGnosis.sol index 336e7e52c..9746f4903 100644 --- a/contracts/src/bridge/FastBridgeSenderToGnosis.sol +++ b/contracts/src/bridge/FastBridgeSenderToGnosis.sol @@ -10,35 +10,28 @@ pragma solidity ^0.8.0; -import "./SafeBridgeSenderToGnosisFromArbitrum.sol"; -import "./interfaces/IFastBridgeSender.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; -import "./merkle/MerkleTree.sol"; +import "./interfaces/FastBridgeSenderBase.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/arbitrum/IArbSys.sol"; +import "./interfaces/arbitrum/AddressAliasHelper.sol"; /** * Fast Bridge Sender to Gnosis from Arbitrum * Counterpart of `FastBridgeReceiverOnGnosis` */ -contract FastBridgeSenderToGnosis is SafeBridgeSenderToGnosisFromArbitrum, IFastBridgeSender, MerkleTree { +contract FastBridgeSenderToGnosis is FastBridgeSenderBase { // ************************************* // - // * Storage * // + // * Events * // // ************************************* // - IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Gnosis Chain. - - uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). - uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. - mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + event L2ToL1TxCreated(uint256 indexed withdrawalId); // ************************************* // - // * Events * // + // * Storage * // // ************************************* // - /** - * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiverOnEthereum. - */ - event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + IArbSys public constant ARB_SYS = IArbSys(address(100)); + IAMB public immutable amb; /** * @dev Constructor. @@ -52,60 +45,22 @@ contract FastBridgeSenderToGnosis is SafeBridgeSenderToGnosisFromArbitrum, IFast uint256 _epochPeriod, uint256 _genesis, IAMB _amb - ) SafeBridgeSenderToGnosisFromArbitrum(_amb) { - fastBridgeReceiver = _fastBridgeReceiver; - epochPeriod = _epochPeriod; - genesis = _genesis; + ) FastBridgeSenderBase(_fastBridgeReceiver, _epochPeriod, _genesis) { + amb = _amb; } // ************************************* // - // * State Modifiers * // + // * Function Modifiers * // // ************************************* // - /** - * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. - * @param _receiver The address of the contract on Ethereum which receives the calldata. - * @param _functionSelector The function to call. - * @param _calldata The receiving domain encoded message data / function arguments. - */ - function sendFast( - address _receiver, - bytes4 _functionSelector, - bytes memory _calldata - ) external override { - bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); - bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); - appendMessage(fastMessageHash); // add message to merkle tree - emit MessageReceived(_receiver, _fastMessage, batchSize); - } - - /** - * Sends a batch of arbitrary message from one domain to another - * via the fast bridge mechanism. - */ - function sendEpoch() external { - uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; - require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); - require(batchSize > 0, "No messages to send."); - - // set merkle root in outbox and reset merkle tree - bytes32 batchMerkleRoot = getMerkleRootAndReset(); - fastOutbox[epochFinalized] = batchMerkleRoot; - - emit SendBatch(epochFinalized, batchMerkleRoot); - } - - /** - * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _epoch The blocknumber of the batch - */ - function sendSafeFallback(uint256 _epoch) external payable override { - bytes32 batchMerkleRoot = fastOutbox[_epoch]; - + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafe(address(fastBridgeReceiver), safeMessageData); + bytes4 methodSelector = IAMB.requireToPassMessage.selector; + // 4000000 is the max gas fee, set at a resonable level for deployment + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _receiver, _calldata, 4000000); + uint256 withdrawalId = ARB_SYS.sendTxToL1(address(amb), safeMessageData); + + emit L2ToL1TxCreated(withdrawalId); + return withdrawalId; } } diff --git a/contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol b/contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol new file mode 100644 index 000000000..10c2657c3 --- /dev/null +++ b/contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./IFastBridgeReceiver.sol"; +import "./ISafeBridgeReceiver.sol"; +import "../merkle/MerkleProof.sol"; + +/** + * Fast Bridge Receiver Base + * Counterpart of `FastBridgeSenderBase` + */ +abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, MerkleProof, ISafeBridgeReceiver { + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + bool honest; + } + + struct Challenge { + address challenger; + bool honest; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable genesis; // Marks the beginning of the first epoch. + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + + mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) + mapping(uint256 => Claim) public claims; // epoch => claim + mapping(uint256 => Challenge) public challenges; // epoch => challenge + mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + + /** + * @dev Constructor. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of the period allowing to challenge a claim. + */ + constructor( + uint256 _deposit, + uint256 _epochPeriod, + uint256 _genesis + ) { + deposit = _deposit; + epochPeriod = _epochPeriod; + genesis = _genesis; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * @dev Submit a claim about the `_batchMerkleRoot` for the last completed epoch from the Fast Bridge and submit a deposit. The `_batchMerkleRoot` should match the one on the sending side otherwise the sender will lose his deposit. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + uint256 epochClaim = epochCount - 1; // Can only claim last completed epoch. + uint256 claimDeadline = genesis + epochCount * epochPeriod + epochPeriod / 2; + + require(block.timestamp < claimDeadline, "Claim period expired."); + require(claims[epochClaim].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[epochClaim] = Claim({batchMerkleRoot: _batchMerkleRoot, bridger: msg.sender, honest: false}); + + emit ClaimReceived(epochClaim, _batchMerkleRoot); + } + + /** + * @dev Submit a challenge for the claim of the current epoch's Fast Bridge batch merkleroot state and submit a deposit. The `batchMerkleRoot` in the claim already made for the last finalized epoch should be different from the one on the sending side, otherwise the sender will lose his deposit. + */ + function challenge() external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + require(claims[epochChallenge].bridger != address(0), "No claim to challenge."); + + challenges[epochChallenge] = Challenge({challenger: msg.sender, honest: false}); + + emit ClaimChallenged(epochChallenge); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + uint256 epochCount = (block.timestamp - genesis) / epochPeriod; + + require(epochCount > _epoch + 1, "Challenge period for epoch has not elapsed."); + require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); + + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + + if (challenges[_epoch].challenger == address(0)) { + // optimistic happy path + claim.honest = true; + fastInbox[_epoch] = claim.batchMerkleRoot; + } + } + + /** + * @dev Verifies merkle proof for the given message and associated nonce for the epoch and relays the message. + * @param _epoch The epoch in which the message was batched by the bridge. + * @param _proof The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. + * @param _message The data on the cross-domain chain for the message. + * @param _nonce The nonce (index in the merkle tree) to avoid replay. + */ + function verifyAndRelayMessage( + uint256 _epoch, + bytes32[] calldata _proof, + bytes calldata _message, + uint256 _nonce + ) external override { + bytes32 batchMerkleRoot = fastInbox[_epoch]; + require(batchMerkleRoot != bytes32(0), "Invalid epoch."); + + uint256 index = _nonce / 256; + uint256 offset = _nonce - index * 256; + + bytes32 replay = relayed[_epoch][index]; + require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); + relayed[_epoch][index] = replay | bytes32(1 << offset); + + // Claim assessment if any + bytes32 messageHash = sha256(abi.encodePacked(_message, _nonce)); + + require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction + } + + /** + * Note: Access restricted to the Safe Bridge. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param _epoch The epoch to verify. + * @param _batchMerkleRoot The true batch merkle root for the epoch. + */ + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { + challenges[_epoch].honest = true; + } else { + claims[_epoch].honest = true; + } + } + + /** + * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. + * @param _epoch The epoch associated with the claim deposit to withraw. + */ + function withdrawClaimDeposit(uint256 _epoch) external override { + Claim memory claim = claims[_epoch]; + Challenge memory challenge = challenges[_epoch]; + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + + uint256 amount = deposit; + if (challenge.challenger != address(0)) amount = (deposit * 3) / 2; // half burnt + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + /** + * @dev Sends the deposit back to the Challenger if their challenge is successful. Includes a portion of the Bridger's deposit. + * @param _epoch The epoch associated with the challenge deposit to withraw. + */ + function withdrawChallengeDeposit(uint256 _epoch) external override { + Challenge memory challenge = challenges[_epoch]; + require(challenge.challenger != address(0), "Claim does not exist"); + require(challenge.honest == true, "Claim not verified: deposit forfeited"); + + uint256 amount = (deposit * 3) / 2; + + delete claims[_epoch]; + delete challenges[_epoch]; + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function challengePeriod() external view override returns (uint256 start, uint256 end) { + // start begins latest after the claim deadline expiry + // however can begin as soon as a claim is made + // can only challenge the only active claim, about the previous epoch + uint256 epochChallenge = (block.timestamp - genesis) / epochPeriod - 1; + start = genesis + epochChallenge * epochPeriod + epochPeriod / 2; + end = start + epochPeriod / 2; + return (start, end); + } + + /** + * Returns the `start` and `end` time of challenge period for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function epochCount() external view returns (uint256 _epochCount) { + _epochCount = (block.timestamp - genesis) / epochPeriod; + } + + // ************************ // + // * Internal * // + // ************************ // + + function _relay(bytes calldata _messageData) internal returns (bool success) { + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes)); + (success, ) = address(receiver).call(data); + } +} diff --git a/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol b/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol new file mode 100644 index 000000000..664aeca63 --- /dev/null +++ b/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./IFastBridgeSender.sol"; +import "./ISafeBridgeSender.sol"; +import "./IFastBridgeReceiver.sol"; +import "../merkle/MerkleTree.sol"; + +/** + * Fast Bridge Sender Base + * Counterpart of `FastBridgeReceiverBase` + */ +abstract contract FastBridgeSenderBase is IFastBridgeSender, MerkleTree, ISafeBridgeSender { + // ************************************* // + // * Storage * // + // ************************************* // + + IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Gnosis Chain. + + uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + + // ************************************* // + // * Events * // + // ************************************* // + + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiverOnEthereum. + */ + event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + + /** + * @dev Constructor. + * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _epochPeriod The duration between epochs. + * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. + */ + constructor( + IFastBridgeReceiver _fastBridgeReceiver, + uint256 _epochPeriod, + uint256 _genesis + ) { + fastBridgeReceiver = _fastBridgeReceiver; + epochPeriod = _epochPeriod; + genesis = _genesis; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. + * @param _receiver The address of the contract on Ethereum which receives the calldata. + * @param _functionSelector The function to call. + * @param _calldata The receiving domain encoded message data / function arguments. + */ + function sendFast( + address _receiver, + bytes4 _functionSelector, + bytes memory _calldata + ) external override { + bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + appendMessage(fastMessageHash); // add message to merkle tree + emit MessageReceived(_receiver, _fastMessage, batchSize); + } + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendEpoch() external { + uint256 epochFinalized = (block.timestamp - genesis) / epochPeriod; + require(fastOutbox[epochFinalized] == 0, "Batch already sent for most recent finalized epoch."); + require(batchSize > 0, "No messages to send."); + + // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRootAndReset(); + fastOutbox[epochFinalized] = batchMerkleRoot; + + emit SendBatch(epochFinalized, batchMerkleRoot); + } + + /** + * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch The blocknumber of the batch + */ + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); + // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed + _sendSafe(address(fastBridgeReceiver), safeMessageData); + } +} diff --git a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol similarity index 85% rename from contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol index 86f6cabb7..59dc2f2e0 100644 --- a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol @@ -10,9 +10,9 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/arbitrum/IInbox.sol"; -import "./interfaces/arbitrum/IOutbox.sol"; +import "../interfaces/ISafeBridgeReceiver.sol"; +import "../interfaces/arbitrum/IInbox.sol"; +import "../interfaces/arbitrum/IOutbox.sol"; /** * Safe Bridge Receiver on Ethereum from Arbitrum @@ -23,7 +23,6 @@ contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { // * Storage * // // ************************************* // - // will be set as immutable in production deployment for gas optimization address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. diff --git a/contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol similarity index 92% rename from contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol index 388d9e260..a8121cfa2 100644 --- a/contracts/src/bridge/SafeBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol @@ -10,8 +10,8 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; +import "../interfaces/ISafeBridgeReceiver.sol"; +import "../interfaces/gnosis-chain/IAMB.sol"; /** * Safe Bridge Receiver on Gnosis from Arbitrum diff --git a/contracts/src/bridge/SafeBridgeSenderToArbitrumFromEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol similarity index 93% rename from contracts/src/bridge/SafeBridgeSenderToArbitrumFromEthereum.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol index d10baf8d9..388fecf71 100644 --- a/contracts/src/bridge/SafeBridgeSenderToArbitrumFromEthereum.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol @@ -10,10 +10,10 @@ pragma solidity ^0.8.0; -import "./interfaces/arbitrum/IInbox.sol"; -import "./interfaces/arbitrum/IOutbox.sol"; -import "./interfaces/arbitrum/IArbRetryableTx.sol"; -import "./interfaces/ISafeBridgeSender.sol"; +import "../interfaces/arbitrum/IInbox.sol"; +import "../interfaces/arbitrum/IOutbox.sol"; +import "../interfaces/arbitrum/IArbRetryableTx.sol"; +import "../interfaces/ISafeBridgeSender.sol"; /** * Safe Bridge Sender to Arbitrum from Ethereum diff --git a/contracts/src/bridge/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol similarity index 88% rename from contracts/src/bridge/SafeBridgeSenderToEthereum.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol index 46ae5e3dc..cc800c8c1 100644 --- a/contracts/src/bridge/SafeBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol @@ -10,10 +10,9 @@ pragma solidity ^0.8.0; -import "./interfaces/arbitrum/IArbSys.sol"; -import "./interfaces/arbitrum/AddressAliasHelper.sol"; - -import "./interfaces/ISafeBridgeSender.sol"; +import "../interfaces/arbitrum/IArbSys.sol"; +import "../interfaces/arbitrum/AddressAliasHelper.sol"; +import "../interfaces/ISafeBridgeSender.sol"; /** * Safe Bridge Sender to Ethereum from Arbitrum diff --git a/contracts/src/bridge/SafeBridgeSenderToGnosis.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol similarity index 87% rename from contracts/src/bridge/SafeBridgeSenderToGnosis.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol index 41f9189d8..c53bc535a 100644 --- a/contracts/src/bridge/SafeBridgeSenderToGnosis.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol @@ -10,8 +10,8 @@ pragma solidity ^0.8.0; -import "./interfaces/gnosis-chain/IAMB.sol"; -import "./interfaces/ISafeBridgeSender.sol"; +import "../interfaces/gnosis-chain/IAMB.sol"; +import "../interfaces/ISafeBridgeSender.sol"; /** * Safe Bridge Sender to Gnosis from Ethereum diff --git a/contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol similarity index 89% rename from contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol rename to contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol index c6142f3b5..15cc03369 100644 --- a/contracts/src/bridge/SafeBridgeSenderToGnosisFromArbitrum.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol @@ -10,10 +10,10 @@ pragma solidity ^0.8.0; -import "./interfaces/gnosis-chain/IAMB.sol"; -import "./interfaces/arbitrum/IArbSys.sol"; -import "./interfaces/arbitrum/AddressAliasHelper.sol"; -import "./interfaces/ISafeBridgeSender.sol"; +import "../interfaces/gnosis-chain/IAMB.sol"; +import "../interfaces/arbitrum/IArbSys.sol"; +import "../interfaces/arbitrum/AddressAliasHelper.sol"; +import "../interfaces/ISafeBridgeSender.sol"; /** * Safe Bridge Sender to Gnosis from Ethereum diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/ForeignGateway.sol similarity index 100% rename from contracts/src/gateway/ForeignGatewayOnEthereum.sol rename to contracts/src/gateway/ForeignGateway.sol diff --git a/contracts/src/gateway/ForeignGatewayOnGnosis.sol b/contracts/src/gateway/ForeignGatewayOnGnosis.sol deleted file mode 100644 index c8c658400..000000000 --- a/contracts/src/gateway/ForeignGatewayOnGnosis.sol +++ /dev/null @@ -1,214 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../arbitration/IArbitrable.sol"; -import "../bridge/interfaces/IFastBridgeReceiver.sol"; - -import "./interfaces/IForeignGateway.sol"; - -/** - * Foreign Gateway on Gnosis chain - * Counterpart of `HomeGatewayToGnosis` - */ -contract ForeignGatewayOnGnosis is IForeignGateway { - // The global default minimum number of jurors in a dispute. - uint256 public constant MIN_JURORS = 3; - - // @dev Note the disputeID needs to start from one as - // the KlerosV1 proxy governor depends on this implementation. - // We now also depend on localDisputeID not being zero - // at any point. - uint256 internal localDisputeID = 1; - - // feeForJuror by subcourtID - uint256[] internal feeForJuror; - uint256 public immutable override chainID; - uint256 public immutable override homeChainID; - - struct DisputeData { - uint248 id; - bool ruled; - address arbitrable; - uint256 paid; - address relayer; - } - mapping(bytes32 => DisputeData) public disputeHashtoDisputeData; - - address public governor; - IFastBridgeReceiver public fastbridge; - IFastBridgeReceiver public depreciatedFastbridge; - uint256 public fastbridgeExpiration; - address public immutable override homeGateway; - - event OutgoingDispute( - bytes32 disputeHash, - bytes32 blockhash, - uint256 localDisputeID, - uint256 _choices, - bytes _extraData, - address arbitrable - ); - - modifier onlyFromFastBridge() { - require( - address(fastbridge) == msg.sender || - ((block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender), - "Access not allowed: Fast Bridge only." - ); - _; - } - - modifier onlyByGovernor() { - require(governor == msg.sender, "Access not allowed: Governor only."); - _; - } - - constructor( - address _governor, - IFastBridgeReceiver _fastbridge, - uint256[] memory _feeForJuror, - address _homeGateway, - uint256 _homeChainID - ) { - governor = _governor; - fastbridge = _fastbridge; - feeForJuror = _feeForJuror; - homeGateway = _homeGateway; - uint256 id; - assembly { - id := chainid() - } - chainID = id; - homeChainID = _homeChainID; - } - - /** @dev Changes the fastBridge, useful to increase the claim deposit. - * @param _fastbridge The address of the new fastBridge. - * @param _gracePeriod The duration to accept messages from the deprecated bridge (if at all). - */ - function changeFastbridge(IFastBridgeReceiver _fastbridge, uint256 _gracePeriod) external onlyByGovernor { - // grace period to relay remaining messages in the relay / bridging process - fastbridgeExpiration = block.timestamp + _fastbridge.epochPeriod() + _gracePeriod; // 2 weeks - depreciatedFastbridge = fastbridge; - fastbridge = _fastbridge; - } - - /** @dev Changes the `feeForJuror` property value of a specified subcourt. - * @param _subcourtID The ID of the subcourt. - * @param _feeForJuror The new value for the `feeForJuror` property value. - */ - function changeSubcourtJurorFee(uint96 _subcourtID, uint256 _feeForJuror) external onlyByGovernor { - feeForJuror[_subcourtID] = _feeForJuror; - } - - /** @dev Creates the `feeForJuror` property value for a new subcourt. - * @param _feeForJuror The new value for the `feeForJuror` property value. - */ - function createSubcourtJurorFee(uint256 _feeForJuror) external onlyByGovernor { - feeForJuror.push(_feeForJuror); - } - - function createDispute(uint256 _choices, bytes calldata _extraData) - external - payable - override - 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, - _choices, - _extraData, - msg.sender - ) - ); - - disputeHashtoDisputeData[disputeHash] = DisputeData({ - id: uint248(disputeID), - arbitrable: msg.sender, - paid: msg.value, - relayer: address(0), - ruled: false - }); - - emit OutgoingDispute(disputeHash, blockhash(block.number - 1), disputeID, _choices, _extraData, msg.sender); - emit DisputeCreation(disputeID, IArbitrable(msg.sender)); - } - - function arbitrationCost(bytes calldata _extraData) public view override returns (uint256 cost) { - (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDMinJurors(_extraData); - - cost = feeForJuror[subcourtID] * minJurors; - } - - /** - * Relay the rule call from the home gateway to the arbitrable. - */ - function relayRule( - address _messageOrigin, - bytes32 _disputeHash, - uint256 _ruling, - address _relayer - ) external override onlyFromFastBridge { - require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); - DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; - - require(dispute.id != 0, "Dispute does not exist"); - require(!dispute.ruled, "Cannot rule twice"); - - dispute.ruled = true; - dispute.relayer = _relayer; - - IArbitrable arbitrable = IArbitrable(dispute.arbitrable); - arbitrable.rule(dispute.id, _ruling); - } - - function withdrawFees(bytes32 _disputeHash) external override { - DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; - require(dispute.id != 0, "Dispute does not exist"); - require(dispute.ruled, "Not ruled yet"); - - uint256 amount = dispute.paid; - dispute.paid = 0; - payable(dispute.relayer).transfer(amount); - } - - function disputeHashToForeignID(bytes32 _disputeHash) external view override returns (uint256) { - return disputeHashtoDisputeData[_disputeHash].id; - } - - function extraDataToSubcourtIDMinJurors(bytes memory _extraData) - internal - view - returns (uint96 subcourtID, uint256 minJurors) - { - // Note that here we ignore DisputeKitID - if (_extraData.length >= 64) { - assembly { - // solium-disable-line security/no-inline-assembly - subcourtID := mload(add(_extraData, 0x20)) - minJurors := mload(add(_extraData, 0x40)) - } - if (subcourtID >= feeForJuror.length) subcourtID = 0; - if (minJurors == 0) minJurors = MIN_JURORS; - } else { - subcourtID = 0; - minJurors = MIN_JURORS; - } - } -} diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/HomeGateway.sol similarity index 100% rename from contracts/src/gateway/HomeGatewayToEthereum.sol rename to contracts/src/gateway/HomeGateway.sol diff --git a/contracts/src/gateway/HomeGatewayToGnosis.sol b/contracts/src/gateway/HomeGatewayToGnosis.sol deleted file mode 100644 index 397fcb767..000000000 --- a/contracts/src/gateway/HomeGatewayToGnosis.sol +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/IHomeGateway.sol"; - -import "../arbitration/IArbitrator.sol"; -import "../bridge/interfaces/IFastBridgeSender.sol"; - -import "./interfaces/IForeignGateway.sol"; -import "./interfaces/IHomeGateway.sol"; - -/** - * Home Gateway to Gnosis chain - * Counterpart of `ForeignGatewayOnGnosis` - */ -contract HomeGatewayToGnosis is IHomeGateway { - mapping(uint256 => bytes32) public disputeIDtoHash; - mapping(bytes32 => uint256) public disputeHashtoID; - - address public governor; - IArbitrator public immutable arbitrator; - IFastBridgeSender public fastbridge; - address public override foreignGateway; - uint256 public immutable override chainID; - uint256 public immutable override foreignChainID; - - struct RelayedData { - uint256 arbitrationCost; - address relayer; - } - mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; - - constructor( - address _governor, - IArbitrator _arbitrator, - IFastBridgeSender _fastbridge, - address _foreignGateway, - uint256 _foreignChainID - ) { - governor = _governor; - arbitrator = _arbitrator; - fastbridge = _fastbridge; - foreignGateway = _foreignGateway; - foreignChainID = _foreignChainID; - uint256 id; - assembly { - id := chainid() - } - chainID = id; - - emit MetaEvidence(0, "BRIDGE"); - } - - /** - * @dev Provide the same parameters as on the originalChain while creating a - * dispute. Providing incorrect parameters will create a different hash - * than on the originalChain and will not affect the actual dispute/arbitrable's - * ruling. - * - * @param _originalChainID originalChainId - * @param _originalBlockHash originalBlockHash - * @param _originalDisputeID originalDisputeID - * @param _choices number of ruling choices - * @param _extraData extraData - * @param _arbitrable arbitrable - */ - function relayCreateDispute( - uint256 _originalChainID, - bytes32 _originalBlockHash, - uint256 _originalDisputeID, - uint256 _choices, - bytes calldata _extraData, - address _arbitrable - ) external payable override { - bytes32 disputeHash = keccak256( - abi.encodePacked( - _originalChainID, - _originalBlockHash, - "createDispute", - _originalDisputeID, - _choices, - _extraData, - _arbitrable - ) - ); - RelayedData storage relayedData = disputeHashtoRelayedData[disputeHash]; - require(relayedData.relayer == address(0), "Dispute already relayed"); - - // TODO: will mostly be replaced by the actual arbitrationCost paid on the foreignChain. - relayedData.arbitrationCost = arbitrator.arbitrationCost(_extraData); - require(msg.value >= relayedData.arbitrationCost, "Not enough arbitration cost paid"); - - uint256 disputeID = arbitrator.createDispute{value: msg.value}(_choices, _extraData); - disputeIDtoHash[disputeID] = disputeHash; - disputeHashtoID[disputeHash] = disputeID; - relayedData.relayer = msg.sender; - - emit Dispute(arbitrator, disputeID, 0, 0); - } - - function rule(uint256 _disputeID, uint256 _ruling) external override { - require(msg.sender == address(arbitrator), "Only Arbitrator"); - - bytes32 disputeHash = disputeIDtoHash[_disputeID]; - RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; - - bytes4 methodSelector = IForeignGateway.relayRule.selector; - bytes memory data = abi.encode(disputeHash, _ruling, relayedData.relayer); - - fastbridge.sendFast(foreignGateway, methodSelector, data); - } - - /** @dev Changes the fastBridge, useful to increase the claim deposit. - * @param _fastbridge The address of the new fastBridge. - */ - function changeFastbridge(IFastBridgeSender _fastbridge) external { - require(governor == msg.sender, "Access not allowed: Governor only."); - fastbridge = _fastbridge; - } - - function disputeHashToHomeID(bytes32 _disputeHash) external view override returns (uint256) { - return disputeHashtoID[_disputeHash]; - } -} From 6babe570490524c9948346fa1f828594195b6f8d Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Tue, 24 May 2022 17:04:24 +0100 Subject: [PATCH 20/23] feat: refactoring safe router into new contract --- .../bridge/FastBridgeReceiverOnEthereum.sol | 26 +----- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 14 ++-- .../src/bridge/FastBridgeSenderToEthereum.sol | 35 +++++--- .../src/bridge/FastBridgeSenderToGnosis.sol | 45 +++++++---- contracts/src/bridge/SafeBridgeRouter.sol | 79 +++++++++++++++++++ .../interfaces/FastBridgeSenderBase.sol | 26 +----- .../bridge/interfaces/ISafeBridgeRouter.sol | 15 ++++ .../bridge/interfaces/ISafeBridgeSender.sol | 4 +- .../SafeBridgeSenderToEthereum.sol | 1 - 9 files changed, 160 insertions(+), 85 deletions(-) create mode 100644 contracts/src/bridge/SafeBridgeRouter.sol create mode 100644 contracts/src/bridge/interfaces/ISafeBridgeRouter.sol diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index b699c8fc8..d9f945c6a 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -12,7 +12,6 @@ pragma solidity ^0.8.0; import "./interfaces/ISafeBridgeReceiver.sol"; import "./interfaces/FastBridgeReceiverBase.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; import "./interfaces/arbitrum/IInbox.sol"; import "./interfaces/arbitrum/IOutbox.sol"; @@ -26,42 +25,25 @@ contract FastBridgeReceiverOnEthereum is ISafeBridgeReceiver, FastBridgeReceiver // ************************************* // address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. - address public immutable fastBridgeReceiverOnGC; // The address of the Fast Bridge Receiver on Gnosis Chain. IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. - IAMB public immutable amb; // The address of the AMB contract on Ethereum. /** * @dev Constructor. * @param _deposit The deposit amount to submit a claim in wei. * @param _epochPeriod The duration of the period allowing to challenge a claim. + * @param _genesis The genesis time to synchronize epochs. + * @param _inbox The address of the inbox contract on Ethereum. + * @param _safeBridgeSender The safe bridge sender on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _genesis, address _inbox, - address _safeBridgeSender, - IAMB _amb, - address _fastBridgeReceiverOnGC + address _safeBridgeSender ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { inbox = IInbox(_inbox); safeBridgeSender = _safeBridgeSender; - amb = _amb; - fastBridgeReceiverOnGC = _fastBridgeReceiverOnGC; - } - - /** - * Note: Access restricted to the Safe Bridge. - * @dev Relays safe epoch merkle root state to Gnosis Chain to resolve challenges. - * @param _epoch The epoch to verify. - * @param _batchMerkleRoot The true batch merkle root for the epoch. - */ - function verifySafeGnosisRelay(uint256 _epoch, bytes32 _batchMerkleRoot) external { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, _batchMerkleRoot); - amb.requireToPassMessage(fastBridgeReceiverOnGC, safeMessageData, amb.maxGasPerTx()); } // ************************************* // diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index b757e891e..6563665cc 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -11,10 +11,8 @@ pragma solidity ^0.8.0; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/gnosis-chain/IAMB.sol"; import "./interfaces/FastBridgeReceiverBase.sol"; -import "./merkle/MerkleProof.sol"; /** * Fast Bridge Receiver on Ethereum from Arbitrum @@ -25,7 +23,7 @@ contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBa // * Storage * // // ************************************* // - address public immutable safeBridgeRelayer; // The address of the Safe Bridge relayer on Ethereum. + address public immutable safeBridgeRouter; // The address of the Safe Bridge Router on Ethereum. IAMB public immutable amb; // The address of the AMB contract on GC. /** @@ -33,18 +31,18 @@ contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBa * @param _deposit The deposit amount to submit a claim in wei. * @param _epochPeriod The duration of the period allowing to challenge a claim. * @param _genesis The genesis time to synchronize epochs. - * @param _amb The address of the AMB contract on GC. - * @param _safeBridgeRelayer The safe bridge relayer on Ethereum. + * @param _amb The the AMB contract on Gnosis Chain. + * @param _safeBridgeRouter The safe bridge relayer on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _genesis, address _amb, - address _safeBridgeRelayer + address _safeBridgeRouter ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { amb = IAMB(_amb); - safeBridgeRelayer = _safeBridgeRelayer; + safeBridgeRouter = _safeBridgeRouter; } // ************************************* // @@ -52,6 +50,6 @@ contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBa // ************************************* // function isSentBySafeBridge() internal view override returns (bool) { - return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeRelayer); + return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeRouter); } } diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol index 172a8b9a5..fe5b98542 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSenderToEthereum.sol @@ -12,7 +12,6 @@ pragma solidity ^0.8.0; import "./interfaces/FastBridgeSenderBase.sol"; import "./interfaces/arbitrum/IArbSys.sol"; -import "./interfaces/arbitrum/AddressAliasHelper.sol"; /** * Fast Bridge Sender to Ethereum from Arbitrum @@ -23,33 +22,47 @@ contract FastBridgeSenderToEthereum is FastBridgeSenderBase { // * Events * // // ************************************* // - event L2ToL1TxCreated(uint256 indexed withdrawalId); + event L2ToL1TxCreated(uint256 indexed txID); // ************************************* // // * Storage * // // ************************************* // IArbSys public constant ARB_SYS = IArbSys(address(100)); + IFastBridgeReceiver public immutable fastBridgeReceiver; /** * @dev Constructor. * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _epochPeriod The immutable period between epochs. * @param _genesis The immutable genesis state variable from the FastBridgeSeneder. */ constructor( - IFastBridgeReceiver _fastBridgeReceiver, uint256 _epochPeriod, - uint256 _genesis - ) FastBridgeSenderBase(_fastBridgeReceiver, _epochPeriod, _genesis) {} + uint256 _genesis, + IFastBridgeReceiver _fastBridgeReceiver + ) FastBridgeSenderBase(_epochPeriod, _genesis) { + fastBridgeReceiver = _fastBridgeReceiver; + } - // ************************************* // - // * Function Modifiers * // - // ************************************* // + /** + * @dev Sends the merkle root state for _epoch to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch The blocknumber of the batch + */ + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); + + _sendSafe(address(fastBridgeReceiver), safeMessageData); + } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - uint256 withdrawalId = ARB_SYS.sendTxToL1(_receiver, _calldata); + uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); - emit L2ToL1TxCreated(withdrawalId); - return withdrawalId; + emit L2ToL1TxCreated(txID); + return txID; } } diff --git a/contracts/src/bridge/FastBridgeSenderToGnosis.sol b/contracts/src/bridge/FastBridgeSenderToGnosis.sol index 9746f4903..be64368ac 100644 --- a/contracts/src/bridge/FastBridgeSenderToGnosis.sol +++ b/contracts/src/bridge/FastBridgeSenderToGnosis.sol @@ -11,9 +11,8 @@ pragma solidity ^0.8.0; import "./interfaces/FastBridgeSenderBase.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; import "./interfaces/arbitrum/IArbSys.sol"; -import "./interfaces/arbitrum/AddressAliasHelper.sol"; +import "./interfaces/ISafeBridgeRouter.sol"; /** * Fast Bridge Sender to Gnosis from Arbitrum @@ -24,43 +23,55 @@ contract FastBridgeSenderToGnosis is FastBridgeSenderBase { // * Events * // // ************************************* // - event L2ToL1TxCreated(uint256 indexed withdrawalId); + event L2ToL1TxCreated(uint256 indexed txID); // ************************************* // // * Storage * // // ************************************* // IArbSys public constant ARB_SYS = IArbSys(address(100)); - IAMB public immutable amb; + address public immutable safeBridgeRouter; /** * @dev Constructor. - * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. * @param _epochPeriod The duration between epochs. * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. - * @param _amb The address of the AMB contract on Ethereum + * @param _safeBridgeRouter The the Safe Bridge Router on Ethereum from Arbitrum to Gnosis Chain. */ constructor( - IFastBridgeReceiver _fastBridgeReceiver, uint256 _epochPeriod, uint256 _genesis, - IAMB _amb - ) FastBridgeSenderBase(_fastBridgeReceiver, _epochPeriod, _genesis) { - amb = _amb; + address _safeBridgeRouter + ) FastBridgeSenderBase(_epochPeriod, _genesis) { + safeBridgeRouter = _safeBridgeRouter; } // ************************************* // // * Function Modifiers * // // ************************************* // + /** + * @dev Sends the merkle root state for _epoch to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch The blocknumber of the batch. + */ + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; + + // Safe Bridge message envelope. + bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); + + // Safe Router message envelope. + bytes4 methodSelectorRelay = ISafeBridgeRouter.safeRelay.selector; + bytes memory safeRelayData = abi.encodeWithSelector(methodSelectorRelay, safeMessageData); + + _sendSafe(safeBridgeRouter, safeRelayData); + } + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - // Safe Bridge message envelope - bytes4 methodSelector = IAMB.requireToPassMessage.selector; - // 4000000 is the max gas fee, set at a resonable level for deployment - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _receiver, _calldata, 4000000); - uint256 withdrawalId = ARB_SYS.sendTxToL1(address(amb), safeMessageData); + uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); - emit L2ToL1TxCreated(withdrawalId); - return withdrawalId; + emit L2ToL1TxCreated(txID); + return txID; } } diff --git a/contracts/src/bridge/SafeBridgeRouter.sol b/contracts/src/bridge/SafeBridgeRouter.sol new file mode 100644 index 000000000..e1b47a2ba --- /dev/null +++ b/contracts/src/bridge/SafeBridgeRouter.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/ISafeBridgeRouter.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/arbitrum/IInbox.sol"; +import "./interfaces/arbitrum/IOutbox.sol"; + +/** + * Router on Ethereum from Arbitrum to Gnosis Chain. + */ +contract SafeBridgeRouter is ISafeBridgeRouter { + // ************************************* // + // * Events * // + // ************************************* // + + event safeRelayed(bytes32 indexed txID); + + // ************************************* // + // * Storage * // + // ************************************* // + + IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. + IAMB public immutable amb; // The address of the AMB contract on Ethereum. + address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. + address public immutable fastBridgeReceiverOnGnosisChain; // The address of the Fast Bridge Receiver on Gnosis Chain. + + /** + * @dev Constructor. + * @param _inbox The address of the inbox contract on Ethereum. + * @param _amb The duration of the period allowing to challenge a claim. + * @param _safeBridgeSender The safe bridge sender on Arbitrum. + * @param _fastBridgeReceiverOnGnosisChain The fast bridge receiver on Gnosis Chain. + */ + constructor( + IInbox _inbox, + IAMB _amb, + address _safeBridgeSender, + address _fastBridgeReceiverOnGnosisChain + ) { + inbox = _inbox; + amb = _amb; + safeBridgeSender = _safeBridgeSender; + fastBridgeReceiverOnGnosisChain = _fastBridgeReceiverOnGnosisChain; + } + + /** + * Routes an arbitrary message from one domain to another. + * Note: Access restricted to the Safe Bridge. + * @param _calldata The home chain encoded message data including function selector. + * @return Unique id to track the message request/transaction. + */ + function safeRelay(bytes memory _calldata) external override returns (bytes32) { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + // replace maxGasPerTx with safe level for production deployment + bytes32 txID = amb.requireToPassMessage(fastBridgeReceiverOnGnosisChain, _calldata, amb.maxGasPerTx()); + emit safeRelayed(txID); + return txID; + } + + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); + return outbox.l2ToL1Sender() == safeBridgeSender; + } +} diff --git a/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol b/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol index 664aeca63..2a8d8b484 100644 --- a/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol @@ -19,13 +19,11 @@ import "../merkle/MerkleTree.sol"; * Fast Bridge Sender Base * Counterpart of `FastBridgeReceiverBase` */ -abstract contract FastBridgeSenderBase is IFastBridgeSender, MerkleTree, ISafeBridgeSender { +abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBridgeSender { // ************************************* // // * Storage * // // ************************************* // - IFastBridgeReceiver public immutable fastBridgeReceiver; // The address of the Fast Bridge on Gnosis Chain. - uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages @@ -42,16 +40,10 @@ abstract contract FastBridgeSenderBase is IFastBridgeSender, MerkleTree, ISafeBr /** * @dev Constructor. - * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. * @param _epochPeriod The duration between epochs. * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. */ - constructor( - IFastBridgeReceiver _fastBridgeReceiver, - uint256 _epochPeriod, - uint256 _genesis - ) { - fastBridgeReceiver = _fastBridgeReceiver; + constructor(uint256 _epochPeriod, uint256 _genesis) { epochPeriod = _epochPeriod; genesis = _genesis; } @@ -92,18 +84,4 @@ abstract contract FastBridgeSenderBase is IFastBridgeSender, MerkleTree, ISafeBr emit SendBatch(epochFinalized, batchMerkleRoot); } - - /** - * @dev Sends an arbitrary message to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _epoch The blocknumber of the batch - */ - function sendSafeFallback(uint256 _epoch) external payable override { - bytes32 batchMerkleRoot = fastOutbox[_epoch]; - - // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafe(address(fastBridgeReceiver), safeMessageData); - } } diff --git a/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol b/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol new file mode 100644 index 000000000..073d675a0 --- /dev/null +++ b/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./ISafeBridgeReceiver.sol"; + +abstract contract ISafeBridgeRouter is ISafeBridgeReceiver { + /** + * Routes a message from one domain to another. + * Note: Access restricted to the Safe Bridge. + * @param _calldata The home chain encoded message data. + * @return Unique id to track the message request/transaction. + */ + function safeRelay(bytes memory _calldata) external virtual returns (bytes32); +} diff --git a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol index 97cb8f9b4..8cd3a06a0 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol @@ -6,8 +6,8 @@ abstract contract ISafeBridgeSender { /** * Sends an arbitrary message from one domain to another. * - * @param _receiver The L1 contract address who will receive the calldata - * @param _calldata The L2 encoded message data. + * @param _receiver The foreign chain contract address who will receive the calldata + * @param _calldata The home chain encoded message data. * @return Unique id to track the message request/transaction. */ function _sendSafe(address _receiver, bytes memory _calldata) internal virtual returns (uint256); diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol index cc800c8c1..bc0ec7b1c 100644 --- a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol @@ -11,7 +11,6 @@ pragma solidity ^0.8.0; import "../interfaces/arbitrum/IArbSys.sol"; -import "../interfaces/arbitrum/AddressAliasHelper.sol"; import "../interfaces/ISafeBridgeSender.sol"; /** From e0c80e0253e3ff60e5308a95500955b3b69e2451 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 27 May 2022 17:56:58 +0100 Subject: [PATCH 21/23] feat: refactoring --- contracts/deploy/02-foreign-chain-gnosis.ts | 99 ++++++++++++++++ contracts/deploy/04-foreign-chain-test.ts | 108 ++++++++++++++++++ contracts/deploy/05-home-chain-test.ts | 65 +++++++++++ contracts/hardhat.config.ts | 19 ++- .../generateDeploymentsMarkdownLite.sh | 23 ++++ .../FastBridgeReceiverBase.sol | 28 +++-- .../bridge/FastBridgeReceiverOnEthereum.sol | 15 +-- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 15 +-- ...derToEthereum.sol => FastBridgeSender.sol} | 21 ++-- .../{interfaces => }/FastBridgeSenderBase.sol | 45 +++++--- .../src/bridge/FastBridgeSenderToGnosis.sol | 77 ------------- contracts/src/bridge/SafeBridgeRouter.sol | 21 ++-- .../bridge/interfaces/IFastBridgeReceiver.sol | 8 -- .../bridge/interfaces/IFastBridgeSender.sol | 15 +++ .../bridge/interfaces/ISafeBridgeReceiver.sol | 13 +++ .../bridge/interfaces/ISafeBridgeRouter.sol | 15 --- .../bridge/interfaces/ISafeBridgeSender.sol | 2 +- .../bridge/interfaces/gnosis-chain/IAMB.sol | 27 ++++- .../merkle/{ => test}/MerkleProofExposed.sol | 2 +- .../merkle/{ => test}/MerkleTreeExposed.sol | 2 +- .../SafeBridgeReceiverOnEthereum.sol | 46 -------- .../SafeBridgeReceiverOnGnosis.sol | 45 -------- ...SafeBridgeSenderToArbitrumFromEthereum.sol | 89 --------------- .../SafeBridgeSenderToEthereum.sol | 43 ------- .../safe-bridges/SafeBridgeSenderToGnosis.sol | 31 ----- .../SafeBridgeSenderToGnosisFromArbitrum.sol | 55 --------- .../src/bridge/test/FastBridgeSenderMock.sol | 68 +++++++++++ .../src/bridge/test/arbitrum/ArbSysMock.sol | 25 ++++ .../src/bridge/test/arbitrum/BridgeMock.sol | 52 +++++++++ .../src/bridge/test/arbitrum/InboxMock.sol | 72 ++++++++++++ .../src/bridge/test/arbitrum/OutboxMock.sol | 33 ++++++ .../src/bridge/test/gnosis-chian/MockAMB.sol | 103 +++++++++++++++++ contracts/src/gateway/ForeignGateway.sol | 10 +- contracts/src/gateway/HomeGateway.sol | 6 +- .../gateway/interfaces/IForeignGateway.sol | 2 +- .../interfaces/IForeignGatewayBase.sol | 18 +++ .../gateway/interfaces/IHomeGatewayBase.sol | 18 +++ .../src/gateway/test/ForeignGatewayMock.sol | 66 +++++++++++ .../src/gateway/test/HomeGatewayMock.sol | 54 +++++++++ 39 files changed, 972 insertions(+), 484 deletions(-) create mode 100644 contracts/deploy/02-foreign-chain-gnosis.ts create mode 100644 contracts/deploy/04-foreign-chain-test.ts create mode 100644 contracts/deploy/05-home-chain-test.ts create mode 100755 contracts/scripts/generateDeploymentsMarkdownLite.sh rename contracts/src/bridge/{interfaces => }/FastBridgeReceiverBase.sol (93%) rename contracts/src/bridge/{FastBridgeSenderToEthereum.sol => FastBridgeSender.sol} (74%) rename contracts/src/bridge/{interfaces => }/FastBridgeSenderBase.sol (64%) delete mode 100644 contracts/src/bridge/FastBridgeSenderToGnosis.sol delete mode 100644 contracts/src/bridge/interfaces/ISafeBridgeRouter.sol rename contracts/src/bridge/merkle/{ => test}/MerkleProofExposed.sol (96%) rename contracts/src/bridge/merkle/{ => test}/MerkleTreeExposed.sol (95%) delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol delete mode 100644 contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol create mode 100644 contracts/src/bridge/test/FastBridgeSenderMock.sol create mode 100644 contracts/src/bridge/test/arbitrum/ArbSysMock.sol create mode 100644 contracts/src/bridge/test/arbitrum/BridgeMock.sol create mode 100644 contracts/src/bridge/test/arbitrum/InboxMock.sol create mode 100644 contracts/src/bridge/test/arbitrum/OutboxMock.sol create mode 100644 contracts/src/bridge/test/gnosis-chian/MockAMB.sol create mode 100644 contracts/src/gateway/interfaces/IForeignGatewayBase.sol create mode 100644 contracts/src/gateway/interfaces/IHomeGatewayBase.sol create mode 100644 contracts/src/gateway/test/ForeignGatewayMock.sol create mode 100644 contracts/src/gateway/test/HomeGatewayMock.sol diff --git a/contracts/deploy/02-foreign-chain-gnosis.ts b/contracts/deploy/02-foreign-chain-gnosis.ts new file mode 100644 index 000000000..55bca31ff --- /dev/null +++ b/contracts/deploy/02-foreign-chain-gnosis.ts @@ -0,0 +1,99 @@ +import { parseEther } from "ethers/lib/utils"; + +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +import getContractAddress from "../deploy-helpers/getContractAddress"; + +enum ForeignChains { + GNOSIS = 100, +} +const paramsByChainId = { + 100: { + deposit: parseEther("0.1"), + epochPeriod: 86400, // 1 day + homeChainId: 42161, + amb: "0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59" + } +}; + +const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { ethers, deployments, getNamedAccounts, getChainId, config } = hre; + const { deploy } = deployments; + const { providers } = ethers; + const { hexZeroPad } = hre.ethers.utils; + + // fallback to hardhat node signers on local network + const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; + const chainId = Number(await getChainId()); + console.log("deploying to chainId %s with deployer %s", chainId, deployer); + + const homeNetworks = { + 1: config.networks.arbitrum, + }; + + // Hack to predict the deployment address on the home chain. + // TODO: use deterministic deployments + let nonce; + const homeChainProvider = new providers.JsonRpcProvider(homeNetworks[chainId].url); + nonce = await homeChainProvider.getTransactionCount(deployer); + nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. + + const { deposit, epochPeriod, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; + const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); + + const homeGatewayAddress = getContractAddress(deployer, nonce); + console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); + nonce -= 1; + + const fastBridgeSenderAddress = getContractAddress(deployer, nonce); + console.log("calculated future FastSender for nonce %d: %s", nonce, fastBridgeSenderAddress); + + nonce += 5; + + console.log("calculated future inboxAddress for nonce %d: %s", nonce, arbitrumInbox); + const genesis = 1652709415 // sample genesis time + + const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { + from: deployer, + args: [ + deposit, + epochPeriod, + genesis, + arbitrumInbox, + fastBridgeSenderAddress, + ], + log: true, + }); + + const foreignGateway = await deploy("ForeignGatewayOnEthereum", { + from: deployer, + contract: "ForeignGateway", + args: [ + deployer, + fastBridgeReceiver.address, + [ethers.BigNumber.from(10).pow(17)], + homeGatewayAddress, + homeChainIdAsBytes32, + ], + gasLimit: 4000000, + log: true, + }); + + const metaEvidenceUri = + "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; + + await deploy("ArbitrableExample", { + from: deployer, + args: [foreignGateway.address, metaEvidenceUri], + log: true, + }); +}; + +deployForeignGateway.tags = ["ForeignChain", "ForeignGateway"]; +deployForeignGateway.skip = async ({ getChainId }) => { + const chainId = Number(await getChainId()); + return !ForeignChains[chainId]; +}; + +export default deployForeignGateway; diff --git a/contracts/deploy/04-foreign-chain-test.ts b/contracts/deploy/04-foreign-chain-test.ts new file mode 100644 index 000000000..a979f0f6b --- /dev/null +++ b/contracts/deploy/04-foreign-chain-test.ts @@ -0,0 +1,108 @@ +import { parseEther } from "ethers/lib/utils"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { ethers } from "hardhat"; +import getContractAddress from "../deploy-helpers/getContractAddress"; + +enum ForeignChains { + ETHEREUM_RINKEBY = 4, + HARDHAT = 31337, +} +const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts, getChainId, config } = hre; + const { deploy } = deployments; + const { hexZeroPad } = hre.ethers.utils; + + const deployer = (await getNamedAccounts()).deployer; + const chainId = Number(await getChainId()); + console.log("deploying to chainId %s with deployer %s", chainId, deployer); + + const deposit = parseEther("0.1") + const epochPeriod = 120 // 2 min + const homeChainId = 421611 // arbitrum testnet + const arbInbox = "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e" // https://developer.offchainlabs.com/docs/useful_addresses + const genesis = 1652709415 // sample genesis time + + // TODO: use deterministic deployments + let nonce = await ethers.provider.getTransactionCount(deployer)+7; + + const fastBridgeSenderToEthereumAddress = getContractAddress(deployer, nonce ); + console.log("calculated future fastBridgeSenderToEthereum address for nonce %d: %s", nonce, fastBridgeSenderToEthereumAddress); + const fastBridgeSenderToGnosisAddress = getContractAddress(deployer, nonce + 1); + console.log("calculated future fastBridgeSenderToGnosis address for nonce %d: %s", nonce + 1, fastBridgeSenderToGnosisAddress); + const homeGatewayOnEthereumAddress = getContractAddress(deployer, nonce + 2); + console.log("calculated future HomeGatewayOnEthereum address for nonce %d: %s", nonce + 2, homeGatewayOnEthereumAddress); + const homeGatewayOnGnosisAddress = getContractAddress(deployer, nonce + 3); + console.log("calculated future HomeGatewayOnGnosis address for nonce %d: %s", nonce + 3, homeGatewayOnGnosisAddress); + const fastBridgeReceiverOnEthereum = await deploy("FastBridgeReceiverOnEthereum", { + from: deployer, + args: [ + deposit, + epochPeriod, + genesis, + arbInbox, // should be Arbitrum Inbox + fastBridgeSenderToEthereumAddress, + ], + log: true, + }); + + const ForeignGatewayOnEthereum = await deploy("ForeignGatewayMockOnEthereum", { + from: deployer, + contract: "ForeignGatewayMock", + args: [ + fastBridgeReceiverOnEthereum.address, + homeGatewayOnEthereumAddress, + homeChainId + ], + log: true, + }); + + const mockAMB = await deploy("MockAMB", { + from: deployer, + log: true, + }); // nonce+0 + + nonce = await ethers.provider.getTransactionCount(deployer) + 1; + const fastBridgeReceiverOnGnosisChainAddress = getContractAddress(deployer, nonce); + console.log("calculated future HomeGatewayOnEthereum address for nonce %d: %s", nonce, homeGatewayOnEthereumAddress); + + const safeBridgeRouter = await deploy("SafeBridgeRouter", { + from: deployer, + args: [ + arbInbox, + mockAMB.address, + fastBridgeSenderToGnosisAddress, + fastBridgeReceiverOnGnosisChainAddress + ], + log: true, + }); // nonce+0 + + const fastBridgeReceiverOnGnosisChain = await deploy("FastBridgeReceiverOnGnosis", { + from: deployer, + args: [ + deposit, + epochPeriod, + genesis, + mockAMB.address, // should be Arbitrum Inbox + safeBridgeRouter.address, + ], + log: true, + }); + + const ForeignGatewayOnGnosis = await deploy("ForeignGatewayMockOnGnosis", { + from: deployer, + contract: "ForeignGatewayMock", + args: [ + fastBridgeReceiverOnGnosisChain.address, + homeGatewayOnGnosisAddress, + homeChainId + ], + log: true, + }); +}; +deployForeignGateway.tags = ["Test", "ForeignChain"]; +deployForeignGateway.skip = async ({ getChainId }) => { + const chainId = Number(await getChainId()); + return !ForeignChains[chainId]; +}; +export default deployForeignGateway; diff --git a/contracts/deploy/05-home-chain-test.ts b/contracts/deploy/05-home-chain-test.ts new file mode 100644 index 000000000..d9c95b9f0 --- /dev/null +++ b/contracts/deploy/05-home-chain-test.ts @@ -0,0 +1,65 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { Address } from "ethereumjs-util"; +import { ethers } from "hardhat"; + +const HOME_CHAIN_ID = 421611; // ArbRinkeby + +const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { deployments, getNamedAccounts, getChainId } = hre; + const { deploy, execute } = deployments; + const chainId = Number(await getChainId()); + + // fallback to hardhat node signers on local network + const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; + console.log("deployer: %s", deployer); + // TODO: use deterministic deployments + const fastBridgeReceiverOnEthereum = await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); + const fastBridgeReceiverOnGnosis = await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnGnosis"); + + const genesis = 1652709415; // sample genesis time + const epochPeriod = 120; + + const fastBridgeSenderToEthereum = await deploy("FastBridgeSenderToEthereum", { + from: deployer, + contract: "FastBridgeSender", + args: [epochPeriod, genesis, fastBridgeReceiverOnEthereum.address], + log: true, + }); + + const fastBridgeSenderToGnosis = await deploy("FastBridgeSenderToGnosis", { + from: deployer, + contract: "FastBridgeSender", + args: [epochPeriod, genesis, fastBridgeReceiverOnGnosis.address], + log: true, + }); + + const foreignGatewayOnEthereum = await hre.companionNetworks.foreign.deployments.get("ForeignGatewayMockOnEthereum"); + const foreignGatewayOnGnosis = await hre.companionNetworks.foreign.deployments.get("ForeignGatewayMockOnGnosis"); + const foreignChainId = 4; + const homeGatewayToEthereum = await deploy("homeGatewayMockToEthereum", { + from: deployer, + contract: "HomeGatewayMock", + args: [ + fastBridgeSenderToEthereum.address, + foreignGatewayOnEthereum.address, + foreignChainId + ], + log: true, + }); // nonce+1 + const homeGatewayToGnosis = await deploy("homeGatewayMockToGnosis", { + from: deployer, + contract: "HomeGatewayMock", + args: [ + fastBridgeSenderToGnosis.address, + foreignGatewayOnGnosis.address, + foreignChainId + ], + log: true, + }); // nonce+1 +}; + +deployHomeGateway.tags = ["HomeChain", "Test"]; +deployHomeGateway.skip = async ({ getChainId }) => (HOME_CHAIN_ID != Number(await getChainId())); + +export default deployHomeGateway; diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 6288be0ec..fa0b571af 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -11,7 +11,6 @@ import "hardhat-deploy-ethers"; import "hardhat-watcher"; import "hardhat-docgen"; import "hardhat-contract-sizer"; - dotenv.config(); const config: HardhatUserConfig = { @@ -102,6 +101,17 @@ const config: HardhatUserConfig = { }, }, // Foreign chain --------------------------------------------------------------------------------- + gnosischain: { + chainId: 100, + url: `http://rpc.gnosischain.com/`, + accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], + live: true, + saveDeployments: true, + tags: ["staging", "foreign", "layer2"], + companionNetworks: { + home: "arbitrum", + }, + }, rinkeby: { chainId: 4, url: `https://rinkeby.infura.io/v3/${process.env.INFURA_API_KEY}`, @@ -163,6 +173,13 @@ const config: HardhatUserConfig = { ], files: ["./test/**/*", "./src/**/*"], }, + testBridge: { + tasks: [ + { command: "compile", params: { quiet: true } }, + { command: "test", params: { noCompile: true, testFiles: ["./test/bridge/index.ts"] } }, + ], + files: ["./test/**/*", "./src/**/*"], + }, }, docgen: { path: './docs', diff --git a/contracts/scripts/generateDeploymentsMarkdownLite.sh b/contracts/scripts/generateDeploymentsMarkdownLite.sh new file mode 100755 index 000000000..595597b4f --- /dev/null +++ b/contracts/scripts/generateDeploymentsMarkdownLite.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +function generate() { #deploymentDir #explorerUrl + deploymentDir=$1 + explorerUrl=$2 + for f in $(ls -1 $deploymentDir/*.json); do + contractName=$(basename $f .json) + address=$(cat $f | jq -r .address) + echo "- [$contractName]($explorerUrl$address)" + done +} + +echo "### Rinkeby" +echo +echo "- [PNK](https://rinkeby.etherscan.io/token/0x14aba1fa8a31a8649e8098ad067b739cc5708f30)" +generate "$SCRIPT_DIR/../deployments/rinkeby" "" +echo +echo "### Arbitrum Rinkeby" +echo +echo "- [PNK](https://testnet.arbiscan.io/token/0x364530164a2338cdba211f72c1438eb811b5c639)" +generate "$SCRIPT_DIR/../deployments/arbitrumRinkeby" "" \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol b/contracts/src/bridge/FastBridgeReceiverBase.sol similarity index 93% rename from contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol rename to contracts/src/bridge/FastBridgeReceiverBase.sol index 10c2657c3..5062de5da 100644 --- a/contracts/src/bridge/interfaces/FastBridgeReceiverBase.sol +++ b/contracts/src/bridge/FastBridgeReceiverBase.sol @@ -10,15 +10,15 @@ pragma solidity ^0.8.0; -import "./IFastBridgeReceiver.sol"; -import "./ISafeBridgeReceiver.sol"; -import "../merkle/MerkleProof.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./merkle/MerkleProof.sol"; /** - * Fast Bridge Receiver Base - * Counterpart of `FastBridgeSenderBase` + * Fast Receiver Base + * Counterpart of `FastSenderBase` */ -abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, MerkleProof, ISafeBridgeReceiver { +abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, ISafeBridgeReceiver { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -42,6 +42,8 @@ abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, MerkleProof, IS uint256 public immutable genesis; // Marks the beginning of the first epoch. uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + address public immutable safeRouter; // The address of the Safe Router on the connecting chain. + mapping(uint256 => bytes32) public fastInbox; // epoch => validated batch merkle root(optimistically, or challenged and verified with the safe bridge) mapping(uint256 => Claim) public claims; // epoch => claim mapping(uint256 => Challenge) public challenges; // epoch => challenge @@ -51,15 +53,19 @@ abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, MerkleProof, IS * @dev Constructor. * @param _deposit The deposit amount to submit a claim in wei. * @param _epochPeriod The duration of the period allowing to challenge a claim. + * @param _genesis The genesis time to synchronize epochs. + * @param _safeRouter The address of the Safe Router on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, - uint256 _genesis + uint256 _genesis, + address _safeRouter ) { deposit = _deposit; epochPeriod = _epochPeriod; genesis = _genesis; + safeRouter = _safeRouter; } // ************************************* // @@ -157,15 +163,15 @@ abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, MerkleProof, IS * @param _epoch The epoch to verify. * @param _batchMerkleRoot The true batch merkle root for the epoch. */ - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override { + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); fastInbox[_epoch] = _batchMerkleRoot; - if (_batchMerkleRoot != claims[_epoch].batchMerkleRoot) { - challenges[_epoch].honest = true; - } else { + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { claims[_epoch].honest = true; + } else { + challenges[_epoch].honest = true; } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index d9f945c6a..8aa844d28 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -10,8 +10,7 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/FastBridgeReceiverBase.sol"; +import "./FastBridgeReceiverBase.sol"; import "./interfaces/arbitrum/IInbox.sol"; import "./interfaces/arbitrum/IOutbox.sol"; @@ -19,12 +18,11 @@ import "./interfaces/arbitrum/IOutbox.sol"; * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToEthereum` */ -contract FastBridgeReceiverOnEthereum is ISafeBridgeReceiver, FastBridgeReceiverBase { +contract FastBridgeReceiverOnEthereum is FastBridgeReceiverBase { // ************************************* // // * Storage * // // ************************************* // - address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. /** @@ -33,17 +31,16 @@ contract FastBridgeReceiverOnEthereum is ISafeBridgeReceiver, FastBridgeReceiver * @param _epochPeriod The duration of the period allowing to challenge a claim. * @param _genesis The genesis time to synchronize epochs. * @param _inbox The address of the inbox contract on Ethereum. - * @param _safeBridgeSender The safe bridge sender on Ethereum. + * @param _safeRouter The address of the Safe Router on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _genesis, address _inbox, - address _safeBridgeSender - ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { + address _safeRouter + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis, _safeRouter) { inbox = IInbox(_inbox); - safeBridgeSender = _safeBridgeSender; } // ************************************* // @@ -52,6 +49,6 @@ contract FastBridgeReceiverOnEthereum is ISafeBridgeReceiver, FastBridgeReceiver function isSentBySafeBridge() internal view override returns (bool) { IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); - return outbox.l2ToL1Sender() == safeBridgeSender; + return outbox.l2ToL1Sender() == safeRouter; } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index 6563665cc..0f2a6524f 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -10,20 +10,18 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; +import "./FastBridgeReceiverBase.sol"; import "./interfaces/gnosis-chain/IAMB.sol"; -import "./interfaces/FastBridgeReceiverBase.sol"; /** * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToGnosis` */ -contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBase { +contract FastBridgeReceiverOnGnosis is FastBridgeReceiverBase { // ************************************* // // * Storage * // // ************************************* // - address public immutable safeBridgeRouter; // The address of the Safe Bridge Router on Ethereum. IAMB public immutable amb; // The address of the AMB contract on GC. /** @@ -32,17 +30,16 @@ contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBa * @param _epochPeriod The duration of the period allowing to challenge a claim. * @param _genesis The genesis time to synchronize epochs. * @param _amb The the AMB contract on Gnosis Chain. - * @param _safeBridgeRouter The safe bridge relayer on Ethereum. + * @param _safeRouter The address of the Safe Bridge Router on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _genesis, address _amb, - address _safeBridgeRouter - ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis) { + address _safeRouter + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _genesis, _safeRouter) { amb = IAMB(_amb); - safeBridgeRouter = _safeBridgeRouter; } // ************************************* // @@ -50,6 +47,6 @@ contract FastBridgeReceiverOnGnosis is ISafeBridgeReceiver, FastBridgeReceiverBa // ************************************* // function isSentBySafeBridge() internal view override returns (bool) { - return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeRouter); + return (msg.sender == address(amb)) && (amb.messageSender() == safeRouter); } } diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSender.sol similarity index 74% rename from contracts/src/bridge/FastBridgeSenderToEthereum.sol rename to contracts/src/bridge/FastBridgeSender.sol index fe5b98542..1b62f5bce 100644 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -10,14 +10,14 @@ pragma solidity ^0.8.0; -import "./interfaces/FastBridgeSenderBase.sol"; +import "./FastBridgeSenderBase.sol"; import "./interfaces/arbitrum/IArbSys.sol"; /** * Fast Bridge Sender to Ethereum from Arbitrum * Counterpart of `FastBridgeReceiverOnEthereum` */ -contract FastBridgeSenderToEthereum is FastBridgeSenderBase { +contract FastBridgeSender is FastBridgeSenderBase { // ************************************* // // * Events * // // ************************************* // @@ -29,21 +29,18 @@ contract FastBridgeSenderToEthereum is FastBridgeSenderBase { // ************************************* // IArbSys public constant ARB_SYS = IArbSys(address(100)); - IFastBridgeReceiver public immutable fastBridgeReceiver; /** * @dev Constructor. - * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. * @param _epochPeriod The immutable period between epochs. * @param _genesis The immutable genesis state variable from the FastBridgeSeneder. + * @param _safeRouter The address of the Safe Router on Ethereum. */ constructor( uint256 _epochPeriod, uint256 _genesis, - IFastBridgeReceiver _fastBridgeReceiver - ) FastBridgeSenderBase(_epochPeriod, _genesis) { - fastBridgeReceiver = _fastBridgeReceiver; - } + address _safeRouter + ) FastBridgeSenderBase(_epochPeriod, _genesis, _safeRouter) {} /** * @dev Sends the merkle root state for _epoch to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. @@ -53,16 +50,16 @@ contract FastBridgeSenderToEthereum is FastBridgeSenderBase { bytes32 batchMerkleRoot = fastOutbox[_epoch]; // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; + bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - _sendSafe(address(fastBridgeReceiver), safeMessageData); + _sendSafe(safeRouter, safeMessageData); } - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); emit L2ToL1TxCreated(txID); - return txID; + return bytes32(txID); } } diff --git a/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol similarity index 64% rename from contracts/src/bridge/interfaces/FastBridgeSenderBase.sol rename to contracts/src/bridge/FastBridgeSenderBase.sol index 2a8d8b484..df8b3d22b 100644 --- a/contracts/src/bridge/interfaces/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -10,23 +10,24 @@ pragma solidity ^0.8.0; -import "./IFastBridgeSender.sol"; -import "./ISafeBridgeSender.sol"; -import "./IFastBridgeReceiver.sol"; -import "../merkle/MerkleTree.sol"; +import "./merkle/MerkleTree.sol"; +import "./interfaces/IFastBridgeSender.sol"; +import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; /** * Fast Bridge Sender Base - * Counterpart of `FastBridgeReceiverBase` + * Counterpart of `FastReceiverBase` */ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBridgeSender { // ************************************* // // * Storage * // // ************************************* // - uint256 public immutable genesis; // Marks the beginning of the genesis epoch (epoch 0). + uint256 public immutable genesis; // Marks the beginning of epoch 0. uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + address public immutable safeRouter; // ************************************* // // * Events * // @@ -34,18 +35,24 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr /** * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiverOnEthereum. + * batchMerkleRoot on the FastBridgeReceiver. */ - event SendBatch(uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + event SendEpoch(uint256 indexed epoch, bytes32 indexed epochMerkleRoot); /** * @dev Constructor. * @param _epochPeriod The duration between epochs. - * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. + * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiver. + * @param _safeRouter The the Safe Bridge Router on Ethereum to the foreign chain. */ - constructor(uint256 _epochPeriod, uint256 _genesis) { + constructor( + uint256 _epochPeriod, + uint256 _genesis, + address _safeRouter + ) { epochPeriod = _epochPeriod; genesis = _genesis; + safeRouter = _safeRouter; } // ************************************* // @@ -69,6 +76,18 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr emit MessageReceived(_receiver, _fastMessage, batchSize); } + /** + * @dev Sends an arbitrary message to Ethereum using the Fast Bridge. + * @param _receiver The address of the contract on Ethereum which receives the calldata. + * @param _functionSelector The function to call. + */ + function sendFast(address _receiver, bytes4 _functionSelector) external override { + bytes memory _fastMessage = abi.encodeWithSelector(_functionSelector, msg.sender); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + appendMessage(fastMessageHash); // add message to merkle tree + emit MessageReceived(_receiver, _fastMessage, batchSize); + } + /** * Sends a batch of arbitrary message from one domain to another * via the fast bridge mechanism. @@ -79,9 +98,9 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr require(batchSize > 0, "No messages to send."); // set merkle root in outbox and reset merkle tree - bytes32 batchMerkleRoot = getMerkleRootAndReset(); - fastOutbox[epochFinalized] = batchMerkleRoot; + bytes32 epochMerkleRoot = getMerkleRootAndReset(); + fastOutbox[epochFinalized] = epochMerkleRoot; - emit SendBatch(epochFinalized, batchMerkleRoot); + emit SendEpoch(epochFinalized, epochMerkleRoot); } } diff --git a/contracts/src/bridge/FastBridgeSenderToGnosis.sol b/contracts/src/bridge/FastBridgeSenderToGnosis.sol deleted file mode 100644 index be64368ac..000000000 --- a/contracts/src/bridge/FastBridgeSenderToGnosis.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/FastBridgeSenderBase.sol"; -import "./interfaces/arbitrum/IArbSys.sol"; -import "./interfaces/ISafeBridgeRouter.sol"; - -/** - * Fast Bridge Sender to Gnosis from Arbitrum - * Counterpart of `FastBridgeReceiverOnGnosis` - */ -contract FastBridgeSenderToGnosis is FastBridgeSenderBase { - // ************************************* // - // * Events * // - // ************************************* // - - event L2ToL1TxCreated(uint256 indexed txID); - - // ************************************* // - // * Storage * // - // ************************************* // - - IArbSys public constant ARB_SYS = IArbSys(address(100)); - address public immutable safeBridgeRouter; - - /** - * @dev Constructor. - * @param _epochPeriod The duration between epochs. - * @param _genesis The genesis time to synchronize epochs with the FastBridgeReceiverOnGnosis. - * @param _safeBridgeRouter The the Safe Bridge Router on Ethereum from Arbitrum to Gnosis Chain. - */ - constructor( - uint256 _epochPeriod, - uint256 _genesis, - address _safeBridgeRouter - ) FastBridgeSenderBase(_epochPeriod, _genesis) { - safeBridgeRouter = _safeBridgeRouter; - } - - // ************************************* // - // * Function Modifiers * // - // ************************************* // - - /** - * @dev Sends the merkle root state for _epoch to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. - * @param _epoch The blocknumber of the batch. - */ - function sendSafeFallback(uint256 _epoch) external payable override { - bytes32 batchMerkleRoot = fastOutbox[_epoch]; - - // Safe Bridge message envelope. - bytes4 methodSelector = IFastBridgeReceiver.verifySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - - // Safe Router message envelope. - bytes4 methodSelectorRelay = ISafeBridgeRouter.safeRelay.selector; - bytes memory safeRelayData = abi.encodeWithSelector(methodSelectorRelay, safeMessageData); - - _sendSafe(safeBridgeRouter, safeRelayData); - } - - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); - - emit L2ToL1TxCreated(txID); - return txID; - } -} diff --git a/contracts/src/bridge/SafeBridgeRouter.sol b/contracts/src/bridge/SafeBridgeRouter.sol index e1b47a2ba..64e12c60f 100644 --- a/contracts/src/bridge/SafeBridgeRouter.sol +++ b/contracts/src/bridge/SafeBridgeRouter.sol @@ -10,7 +10,8 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeRouter.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeSender.sol"; import "./interfaces/gnosis-chain/IAMB.sol"; import "./interfaces/arbitrum/IInbox.sol"; import "./interfaces/arbitrum/IOutbox.sol"; @@ -18,7 +19,7 @@ import "./interfaces/arbitrum/IOutbox.sol"; /** * Router on Ethereum from Arbitrum to Gnosis Chain. */ -contract SafeBridgeRouter is ISafeBridgeRouter { +contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender { // ************************************* // // * Events * // // ************************************* // @@ -56,16 +57,22 @@ contract SafeBridgeRouter is ISafeBridgeRouter { /** * Routes an arbitrary message from one domain to another. * Note: Access restricted to the Safe Bridge. - * @param _calldata The home chain encoded message data including function selector. - * @return Unique id to track the message request/transaction. + * @param _epoch The epoch associated with the _batchmerkleRoot. + * @param _batchMerkleRoot The true batch merkle root for the epoch sent by the safe bridge. * @return Unique id to track the message request/transaction. */ - function safeRelay(bytes memory _calldata) external override returns (bytes32) { + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, _batchMerkleRoot); + // replace maxGasPerTx with safe level for production deployment - bytes32 txID = amb.requireToPassMessage(fastBridgeReceiverOnGnosisChain, _calldata, amb.maxGasPerTx()); + bytes32 txID = _sendSafe(fastBridgeReceiverOnGnosisChain, safeMessageData); emit safeRelayed(txID); - return txID; + } + + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { + return amb.requireToPassMessage(_receiver, _calldata, amb.maxGasPerTx()); } // ************************************* // diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 85f4c88c8..0f434894e 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -57,14 +57,6 @@ interface IFastBridgeReceiver { uint256 _nonce ) external; - /** - * Note: Access restricted to the Safe Bridge. - * @dev Resolves any challenge of the optimistic claim for '_epoch'. - * @param _epoch The epoch associated with the _batchmerkleRoot. - * @param _batchMerkleRoot The true batch merkle root for the epoch sent by the safe bridge. - */ - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external; - /** * @dev Sends the deposit back to the Bridger if their claim is not successfully challenged. Includes a portion of the Challenger's deposit if unsuccessfully challenged. * @param _epoch The epoch associated with the claim deposit to withraw. diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index 89fbce530..da95c6d24 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -33,6 +33,21 @@ interface IFastBridgeSender { bytes memory _calldata ) external; + /** + * Note: Access must be restricted by the receiving contract. + * Message is sent with the message sender address as the first argument. + * @dev Sends an arbitrary message across domain using the Fast Bridge. + * @param _receiver The cross-domain contract address which receives the calldata. + * @param _functionSelector The function selector to call. + */ + function sendFast(address _receiver, bytes4 _functionSelector) external; + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendEpoch() external; + /** * @dev Sends a markle root representing an arbitrary batch of messages across domain using the Safe Bridge, which relies on the chain's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. * @param _epoch block number of batch diff --git a/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol b/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol index bb3916563..58e11a186 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol @@ -3,5 +3,18 @@ pragma solidity ^0.8.0; abstract contract ISafeBridgeReceiver { + /** + * Note: Access restricted to the Safe Bridge. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param _epoch The epoch associated with the _batchmerkleRoot. + * @param _batchMerkleRoot The true batch merkle root for the epoch sent by the safe bridge. + */ + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external virtual; + function isSentBySafeBridge() internal view virtual returns (bool); + + modifier onlyFromSafeBridge() { + require(isSentBySafeBridge(), "Safe Bridge only."); + _; + } } diff --git a/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol b/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol deleted file mode 100644 index 073d675a0..000000000 --- a/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./ISafeBridgeReceiver.sol"; - -abstract contract ISafeBridgeRouter is ISafeBridgeReceiver { - /** - * Routes a message from one domain to another. - * Note: Access restricted to the Safe Bridge. - * @param _calldata The home chain encoded message data. - * @return Unique id to track the message request/transaction. - */ - function safeRelay(bytes memory _calldata) external virtual returns (bytes32); -} diff --git a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol index 8cd3a06a0..ddcc8df06 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol @@ -10,5 +10,5 @@ abstract contract ISafeBridgeSender { * @param _calldata The home chain encoded message data. * @return Unique id to track the message request/transaction. */ - function _sendSafe(address _receiver, bytes memory _calldata) internal virtual returns (uint256); + function _sendSafe(address _receiver, bytes memory _calldata) internal virtual returns (bytes32); } diff --git a/contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol b/contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol index 3f5ca3d5e..da6a6efe1 100644 --- a/contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol +++ b/contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol @@ -1,4 +1,7 @@ // SPDX-License-Identifier: MIT +// Complete IAMB Interface +// https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/interfaces/IAMB.sol + pragma solidity ^0.8.0; interface IAMB { @@ -12,7 +15,29 @@ interface IAMB { function messageSender() external view returns (address); - function messageSourceChainId() external view returns (bytes32); + function messageSourceChainId() external view returns (uint256); function messageId() external view returns (bytes32); + + function transactionHash() external view returns (bytes32); + + function messageCallStatus(bytes32 _messageId) external view returns (bool); + + function failedMessageDataHash(bytes32 _messageId) external view returns (bytes32); + + function failedMessageReceiver(bytes32 _messageId) external view returns (address); + + function failedMessageSender(bytes32 _messageId) external view returns (address); + + function requireToConfirmMessage( + address _contract, + bytes memory _data, + uint256 _gas + ) external returns (bytes32); + + function requireToGetInformation(bytes32 _requestSelector, bytes memory _data) external returns (bytes32); + + function sourceChainId() external view returns (uint256); + + function destinationChainId() external view returns (uint256); } diff --git a/contracts/src/bridge/merkle/MerkleProofExposed.sol b/contracts/src/bridge/merkle/test/MerkleProofExposed.sol similarity index 96% rename from contracts/src/bridge/merkle/MerkleProofExposed.sol rename to contracts/src/bridge/merkle/test/MerkleProofExposed.sol index e922c6f52..ed97d88ab 100644 --- a/contracts/src/bridge/merkle/MerkleProofExposed.sol +++ b/contracts/src/bridge/merkle/test/MerkleProofExposed.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "./MerkleProof.sol"; +import "../MerkleProof.sol"; /** * @title MerkleProofExpose diff --git a/contracts/src/bridge/merkle/MerkleTreeExposed.sol b/contracts/src/bridge/merkle/test/MerkleTreeExposed.sol similarity index 95% rename from contracts/src/bridge/merkle/MerkleTreeExposed.sol rename to contracts/src/bridge/merkle/test/MerkleTreeExposed.sol index 72e2ede11..912b37eb9 100644 --- a/contracts/src/bridge/merkle/MerkleTreeExposed.sol +++ b/contracts/src/bridge/merkle/test/MerkleTreeExposed.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "./MerkleTree.sol"; +import "../MerkleTree.sol"; /** * @title MerkleTreeExposed diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol deleted file mode 100644 index 59dc2f2e0..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnEthereum.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shalzz] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/ISafeBridgeReceiver.sol"; -import "../interfaces/arbitrum/IInbox.sol"; -import "../interfaces/arbitrum/IOutbox.sol"; - -/** - * Safe Bridge Receiver on Ethereum from Arbitrum - * Counterpart of `SafeBridgeSenderToEthereum` - */ -contract SafeBridgeReceiverOnEthereum is ISafeBridgeReceiver { - // ************************************* // - // * Storage * // - // ************************************* // - - address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. - IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. - - /** - * @dev Constructor. - * @param _inbox The address of the Arbitrum Inbox contract. - */ - constructor(address _inbox, address _safeBridgeSender) { - inbox = IInbox(_inbox); - safeBridgeSender = _safeBridgeSender; - } - - // ************************************* // - // * Views * // - // ************************************* // - - function isSentBySafeBridge() internal view override returns (bool) { - IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); - return outbox.l2ToL1Sender() == safeBridgeSender; - } -} diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol b/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol deleted file mode 100644 index a8121cfa2..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeReceiverOnGnosis.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/ISafeBridgeReceiver.sol"; -import "../interfaces/gnosis-chain/IAMB.sol"; - -/** - * Safe Bridge Receiver on Gnosis from Arbitrum - * Counterpart of `SafeBridgeSenderToGnosis` - */ -contract SafeBridgeReceiverOnGnosis is ISafeBridgeReceiver { - // ************************************* // - // * Storage * // - // ************************************* // - - // will be set as immutable in production deployment for gas optimization - address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. - IAMB public immutable amb; // The address of the AMB contract. - - /** - * @dev Constructor. - * @param _amb The address of the AMB contract. - */ - constructor(IAMB _amb, address _safeBridgeSender) { - amb = _amb; - safeBridgeSender = _safeBridgeSender; - } - - // ************************************* // - // * Views * // - // ************************************* // - - function isSentBySafeBridge() internal view override returns (bool) { - return amb.messageSender() == safeBridgeSender; - } -} diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol deleted file mode 100644 index 388fecf71..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToArbitrumFromEthereum.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shalzz] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/arbitrum/IInbox.sol"; -import "../interfaces/arbitrum/IOutbox.sol"; -import "../interfaces/arbitrum/IArbRetryableTx.sol"; -import "../interfaces/ISafeBridgeSender.sol"; - -/** - * Safe Bridge Sender to Arbitrum from Ethereum - * Counterpart of `SafeBridgeReceiverOnArbitrumFromEthereum` if any - */ -contract SafeBridgeSenderToArbitrumFromEthereum is ISafeBridgeSender { - IArbRetryableTx public constant ARBITRUM_RETRYABLE_TX = IArbRetryableTx(address(110)); - address public immutable safeBridgeSender; - IInbox public immutable inbox; - uint256 public immutable maxGas; - uint256 public immutable gasPriceBid; - - event RetryableTicketCreated(uint256 indexed ticketId); - - /** - * @param _inbox The Arbitrum Inbox address on Ethereum. - * @param _maxGas Gas limit for immediate L2 execution attempt. - * @param _gasPriceBid L2 Gas price bid for immediate L2 execution attempt. - */ - constructor( - address _safeBridgeSender, - address _inbox, - uint256 _maxGas, - uint256 _gasPriceBid - ) { - safeBridgeSender = _safeBridgeSender; - inbox = IInbox(_inbox); - maxGas = _maxGas; - gasPriceBid = _gasPriceBid; - } - - /** - * 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 _receiver The cross-domain target on Arbitrum. - * @param _calldata The encoded message data. - * @return Unique id to track the message request/transaction. - */ - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - require(msg.sender == safeBridgeSender, "Access not allowed: Safe Bridge Sender only."); - - uint256 baseSubmissionCost = bridgingCost(_calldata.length); - require(msg.value >= baseSubmissionCost + (maxGas * gasPriceBid)); - - uint256 ticketID = inbox.createRetryableTicket{value: msg.value}( - _receiver, - 0, - baseSubmissionCost, - msg.sender, - msg.sender, - maxGas, - gasPriceBid, - _calldata - ); - - emit RetryableTicketCreated(ticketID); - return ticketID; - } - - function bridgingCost(uint256 _calldatasize) internal view returns (uint256) { - (uint256 submissionCost, ) = ARBITRUM_RETRYABLE_TX.getSubmissionPrice(_calldatasize); - return submissionCost; - } -} diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol deleted file mode 100644 index bc0ec7b1c..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToEthereum.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@shalzz, @jaybuidl] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/arbitrum/IArbSys.sol"; -import "../interfaces/ISafeBridgeSender.sol"; - -/** - * Safe Bridge Sender to Ethereum from Arbitrum - * Counterpart of `SafeBridgeReceiverOnEthereum` - */ -contract SafeBridgeSenderToEthereum is ISafeBridgeSender { - // ************************************* // - // * Events * // - // ************************************* // - - event L2ToL1TxCreated(uint256 indexed withdrawalId); - - // ************************************* // - // * Storage * // - // ************************************* // - - IArbSys public constant ARB_SYS = IArbSys(address(100)); - - // ************************************* // - // * Function Modifiers * // - // ************************************* // - - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - uint256 withdrawalId = ARB_SYS.sendTxToL1(_receiver, _calldata); - - emit L2ToL1TxCreated(withdrawalId); - return withdrawalId; - } -} diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol deleted file mode 100644 index c53bc535a..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosis.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@shalzz] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/gnosis-chain/IAMB.sol"; -import "../interfaces/ISafeBridgeSender.sol"; - -/** - * Safe Bridge Sender to Gnosis from Ethereum - * Counterpart of `SafeBridgeReceiverOnGnosis` if any - */ -contract SafeBridgeSenderToGnosis is ISafeBridgeSender { - IAMB public immutable amb; - - constructor(IAMB _amb) { - amb = _amb; - } - - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - bytes32 id = amb.requireToPassMessage(_receiver, _calldata, amb.maxGasPerTx()); - return uint256(id); - } -} diff --git a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol b/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol deleted file mode 100644 index 15cc03369..000000000 --- a/contracts/src/bridge/safe-bridges/SafeBridgeSenderToGnosisFromArbitrum.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@shalzz, @shtoaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/gnosis-chain/IAMB.sol"; -import "../interfaces/arbitrum/IArbSys.sol"; -import "../interfaces/arbitrum/AddressAliasHelper.sol"; -import "../interfaces/ISafeBridgeSender.sol"; - -/** - * Safe Bridge Sender to Gnosis from Ethereum - * Counterpart of `SafeBridgeReceiverOnGnosis` - */ -contract SafeBridgeSenderToGnosisFromArbitrum is ISafeBridgeSender { - IAMB public immutable amb; - - constructor(IAMB _amb) { - amb = _amb; - } - - // ************************************* // - // * Events * // - // ************************************* // - - event L2ToL1TxCreated(uint256 indexed withdrawalId); - - // ************************************* // - // * Storage * // - // ************************************* // - - IArbSys public constant ARB_SYS = IArbSys(address(100)); - - // ************************************* // - // * Function Modifiers * // - // ************************************* // - - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (uint256) { - // Safe Bridge message envelope - bytes4 methodSelector = IAMB.requireToPassMessage.selector; - // 4000000 is the max gas fee, set at a resonable level for deployment - bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _receiver, _calldata, 4000000); - uint256 withdrawalId = ARB_SYS.sendTxToL1(address(amb), safeMessageData); - - emit L2ToL1TxCreated(withdrawalId); - return withdrawalId; - } -} diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol new file mode 100644 index 000000000..f099a1b87 --- /dev/null +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../FastBridgeSenderBase.sol"; +import "../interfaces/arbitrum/IArbSys.sol"; + +/** + * Fast Sender from Arbitrum Mock + * Counterpart of `FastReceiverOnEthereum` + */ +contract FastBridgeSenderMock is FastBridgeSenderBase { + // ************************************* // + // * Events * // + // ************************************* // + + event L2ToL1TxCreated(uint256 indexed txID); + + // ************************************* // + // * Storage * // + // ************************************* // + + IArbSys public immutable arbsys; + + /** + * @dev Constructor. + * @param _epochPeriod The immutable period between epochs. + * @param _genesis The immutable genesis state variable from the FastBridgeSeneder. + * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + */ + constructor( + uint256 _epochPeriod, + uint256 _genesis, + address _fastBridgeReceiver, + address _arbsys + ) FastBridgeSenderBase(_epochPeriod, _genesis, _fastBridgeReceiver) { + arbsys = IArbSys(address(_arbsys)); + } + + /** + * @dev Sends the merkle root state for _epoch to Ethereum using the Safe Bridge, which relies on Arbitrum's canonical bridge. It is unnecessary during normal operations but essential only in case of challenge. + * @param _epoch The blocknumber of the batch + */ + function sendSafeFallback(uint256 _epoch) external payable override { + bytes32 batchMerkleRoot = fastOutbox[_epoch]; + + // Safe Bridge message envelope + bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); + + _sendSafe(safeRouter, safeMessageData); + } + + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { + uint256 txID = arbsys.sendTxToL1(_receiver, _calldata); + + emit L2ToL1TxCreated(txID); + return bytes32(txID); + } +} diff --git a/contracts/src/bridge/test/arbitrum/ArbSysMock.sol b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol new file mode 100644 index 000000000..7febe655b --- /dev/null +++ b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../interfaces/arbitrum/IArbSys.sol"; + +contract ArbSysMock { + function sendTxToL1(address destination, bytes calldata calldataForL1) + external + payable + returns (uint256 _withdrawal_ID) + { + (bool success, ) = address(destination).call(calldataForL1); + require(success, "Failed TxToL1"); + return _withdrawal_ID; + } +} diff --git a/contracts/src/bridge/test/arbitrum/BridgeMock.sol b/contracts/src/bridge/test/arbitrum/BridgeMock.sol new file mode 100644 index 000000000..f98b04c36 --- /dev/null +++ b/contracts/src/bridge/test/arbitrum/BridgeMock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../interfaces/arbitrum/IInbox.sol"; + +contract BridgeMock is IBridge { + address public outbox; + + constructor(address _outbox) { + outbox = _outbox; + } + + function activeOutbox() external view returns (address _outbox) { + return address(outbox); + } + + function deliverMessageToInbox( + uint8 kind, + address sender, + bytes32 messageDataHash + ) external payable returns (uint256) {} + + function executeCall( + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (bool success, bytes memory returnData) {} + + // These are only callable by the admin + function setInbox(address inbox, bool enabled) external {} + + function setOutbox(address inbox, bool enabled) external {} + + // View functions + + function allowedInboxes(address inbox) external view returns (bool) {} + + function allowedOutboxes(address outbox) external view returns (bool) {} + + function inboxAccs(uint256 index) external view returns (bytes32) {} + + function messageCount() external view returns (uint256) {} +} diff --git a/contracts/src/bridge/test/arbitrum/InboxMock.sol b/contracts/src/bridge/test/arbitrum/InboxMock.sol new file mode 100644 index 000000000..682cfb25e --- /dev/null +++ b/contracts/src/bridge/test/arbitrum/InboxMock.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../interfaces/arbitrum/IInbox.sol"; + +contract InboxMock is IInbox { + IBridge public arbBridge; + + constructor(address _bridge) { + arbBridge = IBridge(_bridge); + } + + function bridge() external view returns (IBridge) { + return arbBridge; + } + + function sendL2Message(bytes calldata messageData) external returns (uint256) {} + + function sendUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256) {} + + function sendContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256) {} + + function sendL1FundedUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + bytes calldata data + ) external payable returns (uint256) {} + + function sendL1FundedContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + bytes calldata data + ) external payable returns (uint256) {} + + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256) {} + + function depositEth(uint256 maxSubmissionCost) external payable returns (uint256) {} +} diff --git a/contracts/src/bridge/test/arbitrum/OutboxMock.sol b/contracts/src/bridge/test/arbitrum/OutboxMock.sol new file mode 100644 index 000000000..684c87ea6 --- /dev/null +++ b/contracts/src/bridge/test/arbitrum/OutboxMock.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../interfaces/arbitrum/IOutbox.sol"; + +contract OutboxMock is IOutbox { + address public safeBridgeSender; + + constructor(address _safeBridgeSender) { + safeBridgeSender = _safeBridgeSender; + } + + function l2ToL1Sender() external view returns (address) { + return address(safeBridgeSender); + } + + function l2ToL1Block() external view returns (uint256) {} + + function l2ToL1EthBlock() external view returns (uint256) {} + + function l2ToL1Timestamp() external view returns (uint256) {} + + function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) external {} +} diff --git a/contracts/src/bridge/test/gnosis-chian/MockAMB.sol b/contracts/src/bridge/test/gnosis-chian/MockAMB.sol new file mode 100644 index 000000000..944bd1f0b --- /dev/null +++ b/contracts/src/bridge/test/gnosis-chian/MockAMB.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +// https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/mocks/AMBMock.sol +pragma solidity ^0.8.0; + +import "../../interfaces/gnosis-chain/IAMB.sol"; +import "../../../libraries/gnosis-chain/Bytes.sol"; + +contract MockAMB is IAMB { + event MockedEvent(bytes32 indexed messageId, bytes encodedData); + + address public messageSender; + uint256 public maxGasPerTx; + bytes32 public transactionHash; + bytes32 public messageId; + uint64 public nonce; + uint256 public messageSourceChainId; + mapping(bytes32 => bool) public messageCallStatus; + mapping(bytes32 => address) public failedMessageSender; + mapping(bytes32 => address) public failedMessageReceiver; + mapping(bytes32 => bytes32) public failedMessageDataHash; + + event MessagePassed(address _contract, bytes _data, uint256 _gas); + + function setMaxGasPerTx(uint256 _value) public { + maxGasPerTx = _value; + } + + function executeMessageCall( + address _contract, + address _sender, + bytes memory _data, + bytes32 _messageId, + uint256 _gas + ) public { + messageSender = _sender; + messageId = _messageId; + transactionHash = _messageId; + messageSourceChainId = 1337; + (bool status, ) = _contract.call{gas: _gas}(_data); + messageSender = address(0); + messageId = bytes32(0); + transactionHash = bytes32(0); + messageSourceChainId = 0; + + messageCallStatus[_messageId] = status; + if (!status) { + failedMessageDataHash[_messageId] = keccak256(_data); + failedMessageReceiver[_messageId] = _contract; + failedMessageSender[_messageId] = _sender; + } + } + + function requireToPassMessage( + address _contract, + bytes memory _data, + uint256 _gas + ) external returns (bytes32) { + return _sendMessage(_contract, _data, _gas, 0x00); + } + + function requireToConfirmMessage( + address _contract, + bytes memory _data, + uint256 _gas + ) external returns (bytes32) { + return _sendMessage(_contract, _data, _gas, 0x80); + } + + function _sendMessage( + address _contract, + bytes memory _data, + uint256 _gas, + uint256 _dataType + ) internal returns (bytes32) { + require(messageId == bytes32(0)); + bytes32 bridgeId = keccak256(abi.encodePacked(uint16(1337), address(this))) & + 0x00000000ffffffffffffffffffffffffffffffffffffffff0000000000000000; + + bytes32 _messageId = bytes32(uint256(0x11223344 << 224)) | bridgeId | bytes32(uint256(nonce)); + nonce += 1; + bytes memory eventData = abi.encodePacked( + _messageId, + msg.sender, + _contract, + uint32(_gas), + uint8(2), + uint8(2), + uint8(_dataType), + uint16(1337), + uint16(1338), + _data + ); + + emit MockedEvent(_messageId, eventData); + return _messageId; + } + + function requireToGetInformation(bytes32 _requestSelector, bytes memory _data) external returns (bytes32) {} + + function sourceChainId() external view returns (uint256) {} + + function destinationChainId() external view returns (uint256) {} +} diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index e11b756fa..ea440c6d0 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -16,10 +16,10 @@ import "../bridge/interfaces/IFastBridgeReceiver.sol"; import "./interfaces/IForeignGateway.sol"; /** - * Foreign Gateway on Ethereum - * Counterpart of `HomeGatewayToEthereum` + * Foreign Gateway + * Counterpart of `HomeGateway` */ -contract ForeignGatewayOnEthereum is IForeignGateway { +contract ForeignGateway is IForeignGateway { // The global default minimum number of jurors in a dispute. uint256 public constant MIN_JURORS = 3; @@ -160,12 +160,12 @@ contract ForeignGatewayOnEthereum is IForeignGateway { * Relay the rule call from the home gateway to the arbitrable. */ function relayRule( - address _messageOrigin, + address _messageSender, bytes32 _disputeHash, uint256 _ruling, address _relayer ) external override onlyFromFastBridge { - require(_messageOrigin == homeGateway, "Only the homegateway is allowed."); + require(_messageSender == homeGateway, "Only the homegateway is allowed."); DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index c60f46460..431d0c8ea 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -17,10 +17,10 @@ import "./interfaces/IForeignGateway.sol"; import "./interfaces/IHomeGateway.sol"; /** - * Home Gateway to Ethereum - * Counterpart of `ForeignGatewayOnEthereum` + * Home Gateway + * Counterpart of `ForeignGateway` */ -contract HomeGatewayToEthereum is IHomeGateway { +contract HomeGateway is IHomeGateway { mapping(uint256 => bytes32) public disputeIDtoHash; mapping(bytes32 => uint256) public disputeHashtoID; diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol index 1277dd435..c2e55ab99 100644 --- a/contracts/src/gateway/interfaces/IForeignGateway.sol +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -11,7 +11,7 @@ interface IForeignGateway is IArbitrator { * Relay the rule call from the home gateway to the arbitrable. */ function relayRule( - address _messageOrigin, + address _messageSender, bytes32 _disputeHash, uint256 _ruling, address _forwarder diff --git a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol new file mode 100644 index 000000000..d61baefb0 --- /dev/null +++ b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeReceiver.sol"; + +interface IForeignGatewayBase { + /** + * Receive the message from the home gateway. + */ + function receiveMessage(address _messageSender) external; + + function fastBridgeReceiver() external view returns (address); + + function homeChainID() external view returns (uint256); + + function homeGateway() external view returns (address); +} diff --git a/contracts/src/gateway/interfaces/IHomeGatewayBase.sol b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol new file mode 100644 index 000000000..02e06337d --- /dev/null +++ b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeSender.sol"; + +interface IHomeGatewayBase { + /** + * Send fast message to foreign gateway. + */ + function sendFastMessage() external; + + function fastBridgeSender() external view returns (IFastBridgeSender); + + function foreignChainID() external view returns (uint256); + + function foreignGateway() external view returns (address); +} diff --git a/contracts/src/gateway/test/ForeignGatewayMock.sol b/contracts/src/gateway/test/ForeignGatewayMock.sol new file mode 100644 index 000000000..1c9337c1a --- /dev/null +++ b/contracts/src/gateway/test/ForeignGatewayMock.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/IForeignGatewayBase.sol"; + +/** + * Foreign Gateway Mock + * Counterpart of `HomeGatewayMock` + */ +contract ForeignGatewayMock is IForeignGatewayBase { + address public immutable fastBridgeReceiver; + address public immutable override homeGateway; + uint256 public immutable override homeChainID; + + uint256 public messageCount; + uint256 public data; + + constructor( + address _fastBridgeReceiver, + address _homeGateway, + uint256 _homeChainID + ) { + fastBridgeReceiver = _fastBridgeReceiver; + homeGateway = _homeGateway; + homeChainID = _homeChainID; + } + + modifier onlyFromFastBridge() { + require(address(fastBridgeReceiver) == msg.sender, "Fast Bridge only."); + _; + } + + /** + * Receive the message from the home gateway. + */ + function receiveMessage(address _messageSender) external onlyFromFastBridge { + require(_messageSender == homeGateway, "Only the homegateway is allowed."); + _receiveMessage(); + } + + /** + * Receive the message from the home gateway. + */ + function receiveMessage(address _messageSender, uint256 _data) external onlyFromFastBridge { + require(_messageSender == homeGateway, "Only the homegateway is allowed."); + _receiveMessage(_data); + } + + function _receiveMessage() internal { + messageCount++; + } + + function _receiveMessage(uint256 _data) internal { + messageCount++; + data = _data; + } +} diff --git a/contracts/src/gateway/test/HomeGatewayMock.sol b/contracts/src/gateway/test/HomeGatewayMock.sol new file mode 100644 index 000000000..c23a85c1e --- /dev/null +++ b/contracts/src/gateway/test/HomeGatewayMock.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/IForeignGatewayBase.sol"; +import "../interfaces/IHomeGatewayBase.sol"; + +/** + * Home Gateway + * Counterpart of `ForeignGatewayMock` + */ +contract HomeGatewayMock is IHomeGatewayBase { + IFastBridgeSender public immutable fastBridgeSender; + address public override foreignGateway; + uint256 public immutable override foreignChainID; + + struct RelayedData { + uint256 arbitrationCost; + address relayer; + } + mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; + + constructor( + IFastBridgeSender _fastBridgeSender, + address _foreignGateway, + uint256 _foreignChainID + ) { + fastBridgeSender = _fastBridgeSender; + foreignGateway = _foreignGateway; + foreignChainID = _foreignChainID; + } + + function sendFastMessage() external { + bytes4 methodSelector = IForeignGatewayBase.receiveMessage.selector; + bytes memory data; + + fastBridgeSender.sendFast(foreignGateway, methodSelector, data); + } + + function sendFastMessage(uint256 _data) external { + bytes4 methodSelector = IForeignGatewayBase.receiveMessage.selector; + bytes memory data = abi.encode(_data); + + fastBridgeSender.sendFast(foreignGateway, methodSelector, data); + } +} From f5b677afcb373b9064c1d2d2e4027776c4c00d45 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 27 May 2022 18:48:21 +0100 Subject: [PATCH 22/23] feat: update tests --- contracts/deploy/01-foreign-chain.ts | 110 ++-- contracts/deploy/02-foreign-chain-gnosis.ts | 99 ---- contracts/deploy/02-home-chain.ts | 159 ++++-- contracts/deploy/04-foreign-chain-test.ts | 2 +- contracts/deploy/05-home-chain-test.ts | 2 +- .../src/libraries/gnosis-chain/Bytes.sol | 37 ++ contracts/test/pre-alpha1/index.ts | 511 ++++++++++++++++-- 7 files changed, 660 insertions(+), 260 deletions(-) delete mode 100644 contracts/deploy/02-foreign-chain-gnosis.ts create mode 100644 contracts/src/libraries/gnosis-chain/Bytes.sol diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index 34a59430c..a92bd1266 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -2,7 +2,7 @@ import { parseEther } from "ethers/lib/utils"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { ethers } from "hardhat"; + import getContractAddress from "../deploy-helpers/getContractAddress"; enum ForeignChains { @@ -14,28 +14,25 @@ const paramsByChainId = { 1: { deposit: parseEther("0.1"), epochPeriod: 86400, // 1 day - homeChainId: 42161, // arbitrum - arbInbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", // https://developer.offchainlabs.com/docs/useful_addresses - genesis: 1652709415 // sample genesis time + homeChainId: 42161, + arbitrumInbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", }, 4: { deposit: parseEther("0.1"), - epochPeriod: 120, // 2 min - homeChainId: 421611, // arbitrum testnet - arbInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", // https://developer.offchainlabs.com/docs/useful_addresses - genesis: 1652709415 // sample genesis time + epochPeriod: 86400, // 1 day + homeChainId: 421611, + arbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", }, 31337: { deposit: parseEther("0.1"), - epochPeriod: 120, // 2 min + epochPeriod: 86400, // 1 day homeChainId: 31337, - arbInbox: ethers.constants.AddressZero, - genesis: 1652709415 // sample genesis time - }, + arbitrumInbox: "0x00", + } }; const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { deployments, getNamedAccounts, getChainId, config } = hre; + const { ethers, deployments, getNamedAccounts, getChainId, config } = hre; const { deploy } = deployments; const { providers } = ethers; const { hexZeroPad } = hre.ethers.utils; @@ -56,94 +53,65 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme let nonce; if (chainId === ForeignChains.HARDHAT) { nonce = await ethers.provider.getTransactionCount(deployer); - nonce += 4; // HomeGatewayToEthereum deploy tx will be the 6th after this, same network for both home/foreign. + nonce += 5; // HomeGatewayToEthereum deploy tx will be the 6th after this, same network for both home/foreign. } else { const homeChainProvider = new providers.JsonRpcProvider(homeNetworks[chainId].url); nonce = await homeChainProvider.getTransactionCount(deployer); nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } - const { deposit, epochPeriod, homeChainId, arbInbox, genesis } = paramsByChainId[chainId]; + const { deposit, epochPeriod, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); - const chainIdAsBytes32 = hexZeroPad("0x" + chainId.toString(16), 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - const homeGatewayCentralizedArbitratorAddress = getContractAddress(deployer, nonce + 1); - console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - const fastBridgeSenderAddress = getContractAddress(deployer, nonce - 1); - console.log("calculated future fastBridgeSender address for nonce %d: %s", nonce, fastBridgeSenderAddress); + nonce -= 1; + + const fastBridgeSenderAddress = getContractAddress(deployer, nonce); + console.log("calculated future FastSender for nonce %d: %s", nonce, fastBridgeSenderAddress); + + nonce += 5; + + const inboxAddress = chainId === ForeignChains.HARDHAT ? getContractAddress(deployer, nonce) : arbitrumInbox; + console.log("calculated future inboxAddress for nonce %d: %s", nonce, inboxAddress); + + const genesis = 1652709415 // sample genesis time const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { from: deployer, args: [ - arbInbox, // should be Arbitrum Inbox deposit, epochPeriod, + genesis, + inboxAddress, fastBridgeSenderAddress, - genesis ], log: true, }); - let foreignGateway = await deployForeignGateways( - "ForeignGatewayOnEthereum", - deploy, - deployer, - fastBridgeReceiver.address, - [ethers.BigNumber.from(10).pow(17)], - homeGatewayAddress, - chainIdAsBytes32 - ) - - const foreignGatewayCentralizedArbitrator = await deployForeignGateways( - "ForeignGatewayOnEthereumCentralizedArbitrator", - deploy, - deployer, - fastBridgeReceiver.address, - [ethers.BigNumber.from(10).pow(17)], - homeGatewayCentralizedArbitratorAddress, - chainIdAsBytes32 - ) - const metaEvidenceUri = - "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; - const arbitrable = await deploy("ArbitrableExample", { + const foreignGateway = await deploy("ForeignGatewayOnEthereum", { from: deployer, - args: [foreignGateway.address, metaEvidenceUri], + contract: "ForeignGateway", + args: [ + deployer, + fastBridgeReceiver.address, + [ethers.BigNumber.from(10).pow(17)], + homeGatewayAddress, + homeChainIdAsBytes32, + ], + gasLimit: 4000000, log: true, }); - const arbitrableCentralizedArbitrator = await deploy("ArbitrableExampleCentralizedArbitrator", { + const metaEvidenceUri = + "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; + + await deploy("ArbitrableExample", { from: deployer, - contract: "ArbitrableExample", args: [foreignGateway.address, metaEvidenceUri], log: true, }); }; -async function deployForeignGateways( - name, - deploy, - deployer, - fastBridgeReceiverAddress, - feeForJuror, - homeGatewayAddress, - chainIdAsBytes32 -){ - let foreignGateway = await deploy(name, { - from: deployer, - contract: "ForeignGatewayOnEthereum", - args: [ - deployer, - fastBridgeReceiverAddress, - feeForJuror, - homeGatewayAddress, - chainIdAsBytes32 - ], - log: true, - }); - return foreignGateway; -} - deployForeignGateway.tags = ["ForeignChain", "ForeignGateway"]; deployForeignGateway.skip = async ({ getChainId }) => { const chainId = Number(await getChainId()); diff --git a/contracts/deploy/02-foreign-chain-gnosis.ts b/contracts/deploy/02-foreign-chain-gnosis.ts deleted file mode 100644 index 55bca31ff..000000000 --- a/contracts/deploy/02-foreign-chain-gnosis.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { parseEther } from "ethers/lib/utils"; - -import { HardhatRuntimeEnvironment } from "hardhat/types"; -import { DeployFunction } from "hardhat-deploy/types"; - -import getContractAddress from "../deploy-helpers/getContractAddress"; - -enum ForeignChains { - GNOSIS = 100, -} -const paramsByChainId = { - 100: { - deposit: parseEther("0.1"), - epochPeriod: 86400, // 1 day - homeChainId: 42161, - amb: "0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59" - } -}; - -const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { - const { ethers, deployments, getNamedAccounts, getChainId, config } = hre; - const { deploy } = deployments; - const { providers } = ethers; - const { hexZeroPad } = hre.ethers.utils; - - // fallback to hardhat node signers on local network - const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; - const chainId = Number(await getChainId()); - console.log("deploying to chainId %s with deployer %s", chainId, deployer); - - const homeNetworks = { - 1: config.networks.arbitrum, - }; - - // Hack to predict the deployment address on the home chain. - // TODO: use deterministic deployments - let nonce; - const homeChainProvider = new providers.JsonRpcProvider(homeNetworks[chainId].url); - nonce = await homeChainProvider.getTransactionCount(deployer); - nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. - - const { deposit, epochPeriod, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; - const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); - - const homeGatewayAddress = getContractAddress(deployer, nonce); - console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); - nonce -= 1; - - const fastBridgeSenderAddress = getContractAddress(deployer, nonce); - console.log("calculated future FastSender for nonce %d: %s", nonce, fastBridgeSenderAddress); - - nonce += 5; - - console.log("calculated future inboxAddress for nonce %d: %s", nonce, arbitrumInbox); - const genesis = 1652709415 // sample genesis time - - const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { - from: deployer, - args: [ - deposit, - epochPeriod, - genesis, - arbitrumInbox, - fastBridgeSenderAddress, - ], - log: true, - }); - - const foreignGateway = await deploy("ForeignGatewayOnEthereum", { - from: deployer, - contract: "ForeignGateway", - args: [ - deployer, - fastBridgeReceiver.address, - [ethers.BigNumber.from(10).pow(17)], - homeGatewayAddress, - homeChainIdAsBytes32, - ], - gasLimit: 4000000, - log: true, - }); - - const metaEvidenceUri = - "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; - - await deploy("ArbitrableExample", { - from: deployer, - args: [foreignGateway.address, metaEvidenceUri], - log: true, - }); -}; - -deployForeignGateway.tags = ["ForeignChain", "ForeignGateway"]; -deployForeignGateway.skip = async ({ getChainId }) => { - const chainId = Number(await getChainId()); - return !ForeignChains[chainId]; -}; - -export default deployForeignGateway; diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 6aa7791cf..8526b26b8 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -1,10 +1,11 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { Address } from "ethereumjs-util"; import { ethers } from "hardhat"; const HOME_CHAIN_IDS = [42161, 421611, 31337]; // ArbOne, ArbRinkeby, Hardhat +// TODO: use deterministic deployments + const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployments, getNamedAccounts, getChainId } = hre; const { deploy, execute } = deployments; @@ -14,63 +15,107 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; console.log("deployer: %s", deployer); - // The object below is not available when launching the hardhat node. - // TODO: use deterministic deployments - const fastBridgeReceiver = - chainId === 31337 - ? await deployments.get("FastBridgeReceiverOnEthereum") - : await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); - - const genesisSynchronization = 1652709415; // sample genesis time - const epochPeriod = 120; - - const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { - from: deployer, - args: [fastBridgeReceiver.address, epochPeriod, genesisSynchronization], - log: true, - }); // nonce+0 - - const klerosCore = await deployments.get("KlerosCore"); - const centralizedArbitrator = await deployments.get("CentralizedArbitrator"); - const foreignGateway = - chainId === 31337 - ? await deployments.get("ForeignGatewayOnEthereum") - : await hre.companionNetworks.foreign.deployments.get("ForeignGatewayOnEthereum"); - const foreignChainId = chainId === 31337 ? 31337 : Number(await hre.companionNetworks.foreign.getChainId()); - const homeGateway = await deploy("HomeGatewayToEthereum", { - from: deployer, - contract: "HomeGatewayToEthereum", - args: [ - deployer, - klerosCore.address, - fastBridgeSender.address, - foreignGateway.address, - foreignChainId - ], - log: true, - }); // nonce+1 - - const homeGatewayCentralizedArbitrator = await deploy("HomeGatewayToEthereumCentralizedArbitrator", { - from: deployer, - contract: "HomeGatewayToEthereum", - args: [ - deployer, - centralizedArbitrator.address, - fastBridgeSender.address, - foreignGateway.address, - foreignChainId - ], - log: true, - }); // nonce+1 - - // comment out and call manually if gas calculation errors - const safeBridgeSender = await hre.ethers - .getContractAt("FastBridgeReceiverOnEthereum", fastBridgeReceiver.address) - .then((contract) => contract.safeBridgeSender()); - if (safeBridgeSender === ethers.constants.AddressZero) { - await execute("FastBridgeReceiverOnEthereum", { from: deployer, log: true }, "setSafeBridgeSender", fastBridgeSender.address); - } + // ---------------------------------------------------------------------------------------------- + const hardhatDeployer = async () => { + const fastBridgeReceiver = await deployments.get("FastBridgeReceiverOnEthereum"); + const arbSysMock = await deploy("ArbSysMock", { from: deployer, log: true }); + const epochPeriod = 86400; // 1 day + const genesis = 1652709415 // sample genesis time + + const fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { + from: deployer, + contract: "FastBridgeSenderMock", + args: [epochPeriod, genesis, fastBridgeReceiver.address, arbSysMock.address], + log: true, + }); // nonce+0 + + const klerosCore = await deployments.get("KlerosCore"); + const foreignGateway = await deployments.get("ForeignGatewayOnEthereum"); + let foreignChainId = 1; + if (chainId === 31337){ + foreignChainId = 31337; + } else if (chainId === 421611){ + foreignChainId = 4; + } + const homeGatewayToEthereum = await deploy("HomeGatewayToEthereum", { + from: deployer, + contract: "HomeGateway", + args: [deployer, klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + gasLimit: 4000000, + log: true, + }); // nonce+1 + foreignChainId = 100; + if (chainId === 31337){ + foreignChainId = 31337; + } + const homeGatewayToGnosis = await deploy("HomeGatewayToGnosis", { + from: deployer, + contract: "HomeGateway", + args: [deployer, klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + gasLimit: 4000000, + log: true, + }); // nonce+1 + + + const outbox = await deploy("OutboxMock", { + from: deployer, + args: [fastBridgeSender.address], + log: true, + }); + + const bridge = await deploy("BridgeMock", { + from: deployer, + args: [outbox.address], + log: true, + }); + await deploy("InboxMock", { + from: deployer, + args: [bridge.address], + log: true, + }); + }; + + // ---------------------------------------------------------------------------------------------- + const liveDeployer = async () => { + const fastBridgeReceiver = await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); + + const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { + from: deployer, + args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero], + log: true, + }); // nonce+0 + + const klerosCore = await deployments.get("KlerosCore"); + const foreignGateway = await hre.companionNetworks.foreign.deployments.get("ForeignGatewayOnEthereum"); + const foreignChainId = Number(await hre.companionNetworks.foreign.getChainId()); + const homeGateway = await deploy("HomeGatewayToEthereum", { + from: deployer, + contract: "HomeGateway", + args: [deployer, klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + log: true, + }); // nonce+1 + + const fastSender = await hre.ethers + .getContractAt("FastBridgeSenderToEthereum", fastBridgeSender.address) + .then((contract) => contract.fastBridgeSender()); + + if (fastSender === ethers.constants.AddressZero) { + await execute( + "FastBridgeSenderToEthereum", + { from: deployer, log: true }, + "changeFastSender", + homeGateway.address + ); + } + }; + + // ---------------------------------------------------------------------------------------------- + if (chainId === 31337) { + await hardhatDeployer(); + } else { + await liveDeployer(); + } }; deployHomeGateway.tags = ["HomeChain", "HomeGateway"]; diff --git a/contracts/deploy/04-foreign-chain-test.ts b/contracts/deploy/04-foreign-chain-test.ts index a979f0f6b..fd7739085 100644 --- a/contracts/deploy/04-foreign-chain-test.ts +++ b/contracts/deploy/04-foreign-chain-test.ts @@ -100,7 +100,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme log: true, }); }; -deployForeignGateway.tags = ["Test", "ForeignChain"]; +deployForeignGateway.tags = ["BridgeTest"]; deployForeignGateway.skip = async ({ getChainId }) => { const chainId = Number(await getChainId()); return !ForeignChains[chainId]; diff --git a/contracts/deploy/05-home-chain-test.ts b/contracts/deploy/05-home-chain-test.ts index d9c95b9f0..92aff3167 100644 --- a/contracts/deploy/05-home-chain-test.ts +++ b/contracts/deploy/05-home-chain-test.ts @@ -59,7 +59,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) }); // nonce+1 }; -deployHomeGateway.tags = ["HomeChain", "Test"]; +deployHomeGateway.tags = ["BridgeTest"]; deployHomeGateway.skip = async ({ getChainId }) => (HOME_CHAIN_ID != Number(await getChainId())); export default deployHomeGateway; diff --git a/contracts/src/libraries/gnosis-chain/Bytes.sol b/contracts/src/libraries/gnosis-chain/Bytes.sol new file mode 100644 index 000000000..a87e6e30a --- /dev/null +++ b/contracts/src/libraries/gnosis-chain/Bytes.sol @@ -0,0 +1,37 @@ +//https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/libraries/Bytes.sol + +pragma solidity ^0.8.0; + +/** + * @title Bytes + * @dev Helper methods to transform bytes to other solidity types. + */ +library Bytes { + /** + * @dev Converts bytes array to bytes32. + * Truncates bytes array if its size is more than 32 bytes. + * NOTE: This function does not perform any checks on the received parameter. + * Make sure that the _bytes argument has a correct length, not less than 32 bytes. + * A case when _bytes has length less than 32 will lead to the undefined behaviour, + * since assembly will read data from memory that is not related to the _bytes argument. + * @param _bytes to be converted to bytes32 type + * @return result bytes32 type of the firsts 32 bytes array in parameter. + */ + function bytesToBytes32(bytes memory _bytes) internal pure returns (bytes32 result) { + assembly { + result := mload(add(_bytes, 32)) + } + } + + /** + * @dev Truncate bytes array if its size is more than 20 bytes. + * NOTE: Similar to the bytesToBytes32 function, make sure that _bytes is not shorter than 20 bytes. + * @param _bytes to be converted to address type + * @return addr address included in the firsts 20 bytes of the bytes array in parameter. + */ + function bytesToAddress(bytes memory _bytes) internal pure returns (address addr) { + assembly { + addr := mload(add(_bytes, 20)) + } + } +} diff --git a/contracts/test/pre-alpha1/index.ts b/contracts/test/pre-alpha1/index.ts index a86d4548b..a34c59736 100644 --- a/contracts/test/pre-alpha1/index.ts +++ b/contracts/test/pre-alpha1/index.ts @@ -1,18 +1,21 @@ import { expect } from "chai"; import { deployments, ethers, getNamedAccounts, network } from "hardhat"; -import { BigNumber } from "ethers"; +import { BigNumber, utils } from "ethers"; import { IncrementalNG, PNK, KlerosCore, FastBridgeReceiverOnEthereum, - ForeignGatewayOnEthereum, + ForeignGateway, ArbitrableExample, - FastBridgeSenderToEthereum, - HomeGatewayToEthereum, + FastBridgeSender, + HomeGateway, + DisputeKitClassic, + InboxMock, } from "../../typechain-types"; /* eslint-disable no-unused-vars */ +/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482 describe("Demo pre-alpha1", function () { const ONE_TENTH_ETH = BigNumber.from(10).pow(17); @@ -21,35 +24,48 @@ describe("Demo pre-alpha1", function () { const ONE_THOUSAND_PNK = BigNumber.from(10).pow(21); const enum Period { - evidence, - commit, - vote, - appeal, - execution, + evidence, // Evidence can be submitted. This is also when drawing has to take place. + commit, // Jurors commit a hashed vote. This is skipped for courts without hidden votes. + vote, // Jurors reveal/cast their vote depending on whether the court has hidden votes or not. + appeal, // The dispute can be appealed. + execution, // Tokens are redistributed and the ruling is executed. + } + + const enum Phase { + staking, // Stake can be updated during this phase. + freezing, // Phase during which the dispute kits can undergo the drawing process. Staking is not allowed during this phase. + } + + const enum DisputeKitPhase { + resolving, // No disputes that need drawing. + generating, // Waiting for a random number. Pass as soon as it is ready. + drawing, // Jurors can be drawn. } let deployer, relayer, bridger, challenger, innocentBystander; - let ng, disputeKit, pnk, core, fastBridgeReceiver, foreignGateway, arbitrable, fastBridgeSender, homeGateway; + let ng, disputeKit, pnk, core, fastBridgeReceiver, foreignGateway, arbitrable, fastBridgeSender, homeGateway, inbox; - before("Setup", async () => { + beforeEach("Setup", async () => { deployer = (await getNamedAccounts()).deployer; relayer = (await getNamedAccounts()).relayer; + console.log("deployer:%s", deployer); console.log("named accounts: %O", await getNamedAccounts()); await deployments.fixture(["Arbitration", "ForeignGateway", "HomeGateway"], { fallbackToGlobal: true, - keepExistingDeployments: true, + keepExistingDeployments: false, }); ng = await ethers.getContract("IncrementalNG"); - disputeKit = await ethers.getContract("DisputeKitClassic"); + disputeKit = await ethers.getContract("DisputeKitClassic"); pnk = await ethers.getContract("PNK"); core = await ethers.getContract("KlerosCore"); fastBridgeReceiver = await ethers.getContract("FastBridgeReceiverOnEthereum"); - foreignGateway = await ethers.getContract("ForeignGatewayOnEthereum"); + foreignGateway = await ethers.getContract("ForeignGatewayOnEthereum"); arbitrable = await ethers.getContract("ArbitrableExample"); - fastBridgeSender = await ethers.getContract("FastBridgeSenderToEthereum"); - homeGateway = await ethers.getContract("HomeGatewayToEthereum"); + fastBridgeSender = await ethers.getContract("FastBridgeSenderToEthereumMock"); + homeGateway = await ethers.getContract("HomeGatewayToEthereum"); + inbox = await ethers.getContract("InboxMock"); }); it("RNG", async () => { @@ -65,8 +81,9 @@ describe("Demo pre-alpha1", function () { expect(rn).to.equal(rnOld.add(1)); }); - it("Demo", async () => { + it("Demo - Honest Claim - No Challenge - Bridger paid", async () => { const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); @@ -97,19 +114,161 @@ describe("Demo pre-alpha1", function () { expect(result.locked).to.equal(0); logJurorBalance(result); }); - - const tx = await foreignGateway.createDispute(2, "0x00", { value: arbitrationCost }); + const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); + console.log("Dispute Created"); expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); console.log(`disputeId: ${disputeId}`); + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); + const disputeHash = ethers.utils.solidityKeccak256( + ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] + ); + const events = (await tx.wait()).events; + + // Relayer tx + const tx2 = await homeGateway + .connect(await ethers.getSigner(relayer)) + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { + value: arbitrationCost, + }); + expect(tx2).to.emit(homeGateway, "Dispute"); + const events2 = (await tx2.wait()).events; + + await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime + await network.provider.send("evm_mine"); + + expect(await core.phase()).to.equal(Phase.staking); + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); + expect(await disputeKit.disputesWithoutJurors()).to.equal(1); + expect(await disputeKit.isResolving()).to.equal(true); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + const disputesKitIDsThatNeedFreezing = await core.getDisputesKitIDsThatNeedFreezing(); + expect(disputesKitIDsThatNeedFreezing).to.be.deep.equal([BigNumber.from("1")]); + await core.passPhase(); // Staking -> Freezing + expect(await core.phase()).to.equal(Phase.freezing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await mineNBlocks(20); // Wait for 20 blocks finality + await disputeKit.passPhase(); // Resolving -> Generating + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.generating); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await disputeKit.passPhase(); // Generating -> Drawing + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.drawing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + const tx3 = await core.draw(0, 1000); + console.log("draw successful"); + const events3 = (await tx3.wait()).events; + + const roundInfo = await core.getRoundInfo(0, 0); + expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); + expect(roundInfo.tokensAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2)); + expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost); + + expect((await core.disputes(0)).period).to.equal(Period.evidence); + + await core.passPeriod(0); + expect((await core.disputes(0)).period).to.equal(Period.vote); + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0); + await core.passPeriod(0); + await core.passPeriod(0); + expect((await core.disputes(0)).period).to.equal(Period.execution); + await core.execute(0, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(1); + + const tx4 = await core.executeRuling(0); + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + const OutgoingMessage = fastBridgeSender.filters.OutgoingMessage(); + const event5 = await fastBridgeSender.queryFilter(OutgoingMessage); + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(2); + + const ticketID = event5[0].args.ticketID; + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + + const bridgerBalance = await ethers.provider.getBalance(bridger.address); + // bridger tx starts - Honest Bridger + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, messageHash, { value: ONE_TENTH_ETH }); + const blockNumBefore = await ethers.provider.getBlockNumber(); + const blockBefore = await ethers.provider.getBlock(blockNumBefore); + const timestampBefore = blockBefore.timestamp; + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, messageHash, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + const tx7 = await fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData); + expect(tx7).to.emit(arbitrable, "Ruling"); + + const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(ticketID); + }); + + it("Demo - Honest Claim - Challenged - Bridger Paid, Challenger deposit forfeited", async () => { + const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); + + await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); + + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await core.setStake(0, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, 0); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(0); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); + const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); + const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); + console.log("Dispute Created"); + expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); + expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); + console.log(`disputeId: ${disputeId}`); + + const eventOutgoingDispute = foreignGateway.filters.OutgoingDispute(); + const events = await foreignGateway.queryFilter(eventOutgoingDispute, "latest"); + const eventDisputeCreation = foreignGateway.filters.DisputeCreation(); + const events2 = await foreignGateway.queryFilter(eventDisputeCreation, "latest"); + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); const disputeHash = ethers.utils.solidityKeccak256( ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], - [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", deployer] + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] ); expect(events[0].event).to.equal("OutgoingDispute"); @@ -118,27 +277,47 @@ describe("Demo pre-alpha1", function () { expect(events[0].args.localDisputeID).to.equal(disputeId); expect(events[0].args._choices).to.equal(2); expect(events[0].args._extraData).to.equal("0x00"); - expect(events[0].args.arbitrable).to.equal(deployer); - - expect(events[1].event).to.equal("DisputeCreation"); - expect(events[1].args._arbitrable).to.equal(deployer); - expect(events[1].args._disputeID).to.equal(disputeId); + expect(events[0].args.arbitrable).to.equal(arbitrable.address); + expect(events2[0].event).to.equal("DisputeCreation"); + expect(events2[0].args._arbitrable).to.equal(arbitrable.address); + expect(events2[0].args._disputeID).to.equal(disputeId); // Relayer tx const tx2 = await homeGateway .connect(await ethers.getSigner(relayer)) - .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", deployer, { + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { value: arbitrationCost, }); + expect(tx2).to.emit(homeGateway, "Dispute"); - const events2 = (await tx2.wait()).events; - // console.log("event=%O", events2); + + await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime + await network.provider.send("evm_mine"); + + expect(await core.phase()).to.equal(Phase.staking); + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); + expect(await disputeKit.disputesWithoutJurors()).to.equal(1); + expect(await disputeKit.isResolving()).to.equal(true); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + let disputesKitIDsThatNeedFreezing = await core.getDisputesKitIDsThatNeedFreezing(); + expect(disputesKitIDsThatNeedFreezing).to.be.deep.equal([BigNumber.from("1")]); + await core.passPhase(); // Staking -> Freezing + expect(await core.phase()).to.equal(Phase.freezing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await mineNBlocks(20); // Wait for 20 blocks finality + await disputeKit.passPhase(); // Resolving -> Generating + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.generating); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await disputeKit.passPhase(); // Generating -> Drawing + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.drawing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); const tx3 = await core.draw(0, 1000); + console.log("draw successful"); const events3 = (await tx3.wait()).events; - console.log("event=%O", events3[0].args); - console.log("event=%O", events3[1].args); - console.log("event=%O", events3[2].args); const roundInfo = await core.getRoundInfo(0, 0); expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); @@ -148,7 +327,277 @@ describe("Demo pre-alpha1", function () { expect((await core.disputes(0)).period).to.equal(Period.evidence); await core.passPeriod(0); expect((await core.disputes(0)).period).to.equal(Period.vote); + + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await disputeKit.passPhase(); // Drawing -> Resolving + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); + expect(await disputeKit.disputesWithoutJurors()).to.equal(0); + expect(await disputeKit.isResolving()).to.equal(true); + + disputesKitIDsThatNeedFreezing = await core.getDisputesKitIDsThatNeedFreezing(); + expect(disputesKitIDsThatNeedFreezing).to.be.deep.equal([BigNumber.from("1")]); + await core.passPhase(); // Freezing -> Staking + expect(await core.phase()).to.equal(Phase.staking); + + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0); + await core.passPeriod(0); + await core.passPeriod(0); + expect((await core.disputes(0)).period).to.equal(Period.execution); + await core.execute(0, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(1); + + const tx4 = await core.executeRuling(0); + + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(2); + const eventFilter = fastBridgeSender.filters.OutgoingMessage(); + const event5 = await fastBridgeSender.queryFilter(eventFilter, "latest"); + const event6 = await ethers.provider.getLogs(eventFilter); + + const ticketID = event5[0].args.ticketID.toNumber(); + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + console.log("TicketID: %d", ticketID); + console.log("Block: %d", blockNumber); + console.log("Message Data: %s", messageData); + console.log("Message Hash: %s", messageHash); + const expectedHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, messageData]) + ); + expect(messageHash).to.equal(expectedHash); + + const currentID = await fastBridgeSender.currentTicketID(); + expect(currentID).to.equal(2); + + // bridger tx starts + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, messageHash, { value: ONE_TENTH_ETH }); + let blockNumBefore = await ethers.provider.getBlockNumber(); + let blockBefore = await ethers.provider.getBlock(blockNumBefore); + let timestampBefore = blockBefore.timestamp; + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, messageHash, timestampBefore); + + // Challenger tx starts + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(ticketID, { value: ONE_TENTH_ETH }); + blockNumBefore = await ethers.provider.getBlockNumber(); + blockBefore = await ethers.provider.getBlock(blockNumBefore); + timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(ticketID, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + await expect( + fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData) + ).to.be.revertedWith("Claim is challenged"); + + const data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], messageData); + const tx7 = await fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }); + expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx7).to.emit(arbitrable, "Ruling"); + + await expect(fastBridgeReceiver.withdrawChallengeDeposit(ticketID)).to.be.revertedWith( + "Claim verified: deposit forfeited" + ); + }); + + it("Demo - Dishonest Claim - Challenged - Bridger deposit forfeited, Challenger paid", async () => { + const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); + + await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); + + await core.setStake(0, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, 0); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(0); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); + const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); + const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); + console.log("Dispute Created"); + expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); + expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); + console.log(`disputeId: ${disputeId}`); + const coreId = disputeId - 1; + // let events = await foreignGateway.queryFilter(OutgoingMessage); + + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); + const disputeHash = ethers.utils.solidityKeccak256( + ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] + ); + + // Relayer tx + const tx2 = await homeGateway + .connect(await ethers.getSigner(relayer)) + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { + value: arbitrationCost, + }); + expect(tx2).to.emit(homeGateway, "Dispute"); + + await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime + await network.provider.send("evm_mine"); + + expect(await core.phase()).to.equal(Phase.staking); + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.resolving); + expect(await disputeKit.disputesWithoutJurors()).to.equal(1); + expect(await disputeKit.isResolving()).to.equal(true); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + const disputesKitIDsThatNeedFreezing = await core.getDisputesKitIDsThatNeedFreezing(); + expect(disputesKitIDsThatNeedFreezing).to.be.deep.equal([BigNumber.from("1")]); + await core.passPhase(); // Staking -> Freezing + expect(await core.phase()).to.equal(Phase.freezing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await mineNBlocks(20); // Wait for 20 blocks finality + await disputeKit.passPhase(); // Resolving -> Generating + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.generating); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + await disputeKit.passPhase(); // Generating -> Drawing + expect(await disputeKit.phase()).to.equal(DisputeKitPhase.drawing); + console.log("KC phase: %d, DK phase: ", await core.phase(), await disputeKit.phase()); + + const tx3 = await core.draw(0, 1000); + console.log("draw successful"); + const events3 = (await tx3.wait()).events; + + const roundInfo = await core.getRoundInfo(coreId, 0); + expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); + expect(roundInfo.tokensAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2)); + expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost); + + expect((await core.disputes(coreId)).period).to.equal(Period.evidence); + await core.passPeriod(coreId); + expect((await core.disputes(coreId)).period).to.equal(Period.vote); + + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(coreId, [0, 1, 2], 0, 0); + await core.passPeriod(coreId); + await core.passPeriod(coreId); + expect((await core.disputes(coreId)).period).to.equal(Period.execution); + await core.execute(coreId, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(1); + + const tx4 = await core.executeRuling(coreId); + + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(2); + const eventFilter = fastBridgeSender.filters.OutgoingMessage(); + const event5 = await fastBridgeSender.queryFilter(eventFilter, "latest"); + const event6 = await ethers.provider.getLogs(eventFilter); + + const ticketID = event5[0].args.ticketID.toNumber(); + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + console.log("TicketID: %d", ticketID); + console.log("Block: %d", blockNumber); + console.log("Message Data: %s", messageData); + console.log("Message Hash: %s", messageHash); + const expectedHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, messageData]) + ); + expect(messageHash).to.equal(expectedHash); + + const currentID = await fastBridgeSender.currentTicketID(); + expect(currentID).to.equal(2); + + // bridger tx starts - bridger creates fakeData & fakeHash for dishonest ruling + const fakeData = "0x0000000000000000000000009a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000643496987923bd6a8aa2bdce6c5b15551665079e7acfb1b4d2149ac7e2f72260417d541b7f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000"; + const fakeHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, fakeData]) + ); + + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, fakeHash, { value: ONE_TENTH_ETH }); + let blockNumBefore = await ethers.provider.getBlockNumber(); + let blockBefore = await ethers.provider.getBlock(blockNumBefore); + let timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, fakeHash, timestampBefore); + + // Challenger tx starts + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(ticketID, { value: ONE_TENTH_ETH }); + blockNumBefore = await ethers.provider.getBlockNumber(); + blockBefore = await ethers.provider.getBlock(blockNumBefore); + timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(ticketID, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + await expect( + fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, fakeData) + ).to.be.revertedWith("Claim is challenged"); + + let data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], fakeData); + + await expect( + fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }) + ).to.be.revertedWith("Invalid message for ticketID."); + + data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], messageData); + const tx8 = await fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }); + expect(tx8).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx8).to.emit(arbitrable, "Ruling"); + + await expect(fastBridgeReceiver.withdrawClaimDeposit(ticketID)).to.be.revertedWith( + "Claim not verified: deposit forfeited" + ); + await fastBridgeReceiver.withdrawChallengeDeposit(ticketID); }); + + async function mineNBlocks(n) { + for (let index = 0; index < n; index++) { + await network.provider.send("evm_mine"); + } + } }); const logJurorBalance = function (result) { From 61df188cf197b7f065725fa03dc4dae6822b2e26 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 27 May 2022 18:48:37 +0100 Subject: [PATCH 23/23] feat: deploy script --- contracts/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/package.json b/contracts/package.json index 71f8fc435..6f6ba50ad 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -14,7 +14,8 @@ "build": "hardhat compile", "clean": "hardhat clean", "deploy": "hardhat deploy", - "deploy:staging": "run-s \"deploy --network rinkeby {@}\" \"deploy --network arbitrumRinkeby {@}\" --", + "deploy:staging": "run-s \"deploy --network rinkeby --tags ForeignChain {@}\" \"deploy --network arbitrumRinkeby --tags HomeChain {@}\" --", + "deploy:staging-bridge-only": "run-s \"deploy --network rinkeby --tags BridgeTest{@}\" \"deploy --network arbitrumRinkeby --tags BridgeTest {@}\" --", "test": "hardhat test", "watch": "hardhat watch", "docgen": "hardhat docgen"