From d7662532b8129a3f1a14e237aa06bd942d450c90 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 17:59:47 +0100 Subject: [PATCH 01/21] feat: merkle tree refactoring --- contracts/src/bridge/merkle/MerkleProof.sol | 17 +----- .../{MerkleTreeHistory.sol => MerkleTree.sol} | 53 +++++++------------ .../bridge/merkle/test/MerkleProofExposed.sol | 33 ++++++++++++ .../bridge/merkle/test/MerkleTreeExposed.sol | 28 ++++++++++ contracts/test/bridge/merkle/index.ts | 25 +++++---- 5 files changed, 97 insertions(+), 59 deletions(-) rename contracts/src/bridge/merkle/{MerkleTreeHistory.sol => MerkleTree.sol} (69%) create mode 100644 contracts/src/bridge/merkle/test/MerkleProofExposed.sol create mode 100644 contracts/src/bridge/merkle/test/MerkleTreeExposed.sol diff --git a/contracts/src/bridge/merkle/MerkleProof.sol b/contracts/src/bridge/merkle/MerkleProof.sol index c01647408..fa91a3b35 100644 --- a/contracts/src/bridge/merkle/MerkleProof.sol +++ b/contracts/src/bridge/merkle/MerkleProof.sol @@ -29,26 +29,13 @@ 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, sha256(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 <= 64, "Invalid Proof"); + require(proofLength <= 32, "Invalid Proof"); bytes32 h = leaf; for (uint256 i = 0; i < proofLength; i++) { bytes32 proofElement = proof[i]; diff --git a/contracts/src/bridge/merkle/MerkleTreeHistory.sol b/contracts/src/bridge/merkle/MerkleTree.sol similarity index 69% rename from contracts/src/bridge/merkle/MerkleTreeHistory.sol rename to contracts/src/bridge/merkle/MerkleTree.sol index 01f6260b0..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 + // merkle tree representation of a batch of messages // supports 2^64 messages. - bytes32[64] public branch; - uint256 public count; - - // block number => merkle root history - mapping(uint256 => bytes32) private history; + bytes32[64] private batch; + uint256 internal batchSize; // ************************************* // // * State Modifiers * // @@ -37,19 +34,18 @@ 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 { + 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/ - bytes32 leaf = sha256(data); - uint256 size = count + 1; - count = size; + uint256 size = batchSize + 1; + batchSize = size; uint256 hashBitField = (size ^ (size - 1)) & size; uint256 height; while ((hashBitField & 1) == 0) { - bytes32 node = branch[height]; + bytes32 node = batch[height]; if (node > leaf) assembly { // effecient hash @@ -67,46 +63,35 @@ contract MerkleTreeHistory { hashBitField /= 2; height = height + 1; } - branch[height] = leaf; + 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) { 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/test/MerkleProofExposed.sol b/contracts/src/bridge/merkle/test/MerkleProofExposed.sol new file mode 100644 index 000000000..ed97d88ab --- /dev/null +++ b/contracts/src/bridge/merkle/test/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/test/MerkleTreeExposed.sol b/contracts/src/bridge/merkle/test/MerkleTreeExposed.sol new file mode 100644 index 000000000..912b37eb9 --- /dev/null +++ b/contracts/src/bridge/merkle/test/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/index.ts b/contracts/test/bridge/merkle/index.ts index e766c4c31..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.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 b8a4a773e21fee87222258b76391bfac174c12ca Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 18:00:54 +0100 Subject: [PATCH 02/21] feat: updated mock AMB --- .../bridge/interfaces/gnosis-chain/IAMB.sol | 27 ++++- .../src/bridge/test/gnosis-chain/MockAMB.sol | 103 ++++++++++++++++++ .../src/libraries/gnosis-chain/Bytes.sol | 37 +++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 contracts/src/bridge/test/gnosis-chain/MockAMB.sol create mode 100644 contracts/src/libraries/gnosis-chain/Bytes.sol 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/test/gnosis-chain/MockAMB.sol b/contracts/src/bridge/test/gnosis-chain/MockAMB.sol new file mode 100644 index 000000000..944bd1f0b --- /dev/null +++ b/contracts/src/bridge/test/gnosis-chain/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/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)) + } + } +} From c37fd1cb1a2773e508d9d7051e41a38b54eb419a Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 18:02:02 +0100 Subject: [PATCH 03/21] feat: refactoring single-message bridge --- ...tBridgeReceiverOnEthereumSingleMessage.sol | 261 ++++++++++++++++++ ...astBridgeReceiverOnGnosisSingleMessage.sol | 21 ++ .../FastBridgeSenderToEthereum.sol | 145 ++++++++++ .../FastBridgeSenderToGnosis.sol} | 8 +- .../SafeBridgeReceiverOnEthereum.sol | 75 +++++ ...SafeBridgeSenderToArbitrumFromEthereum.sol | 89 ++++++ .../SafeBridgeSenderToEthereum.sol | 45 +++ .../SafeBridgeSenderToGnosis.sol | 31 +++ .../interfaces/IFastBridgeReceiver.sol | 117 ++++++++ .../interfaces/IFastBridgeSender.sol | 50 ++++ .../interfaces/ISafeBridgeReceiver.sol | 7 + .../interfaces/ISafeBridgeSender.sol | 14 + .../mock/FastBridgeSenderToEthereumMock.sol | 107 +++++++ contracts/src/gateway/HomeGatewayToGnosis.sol | 21 -- .../ForeignGatewayOnEthereum.sol | 8 +- .../HomeGatewayToEthereum.sol | 12 +- .../IForeignGatewaySingleMessage.sol | 28 ++ .../interfaces/IHomeGatewaySingleMessage.sol | 27 ++ 18 files changed, 1031 insertions(+), 35 deletions(-) create mode 100644 contracts/src/bridge/single-message/FastBridgeReceiverOnEthereumSingleMessage.sol create mode 100644 contracts/src/bridge/single-message/FastBridgeReceiverOnGnosisSingleMessage.sol create mode 100644 contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol rename contracts/src/{gateway/ForeignGatewayOnGnosis.sol => bridge/single-message/FastBridgeSenderToGnosis.sol} (50%) create mode 100644 contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol create mode 100644 contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol create mode 100644 contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol create mode 100644 contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol create mode 100644 contracts/src/bridge/single-message/interfaces/IFastBridgeReceiver.sol create mode 100644 contracts/src/bridge/single-message/interfaces/IFastBridgeSender.sol create mode 100644 contracts/src/bridge/single-message/interfaces/ISafeBridgeReceiver.sol create mode 100644 contracts/src/bridge/single-message/interfaces/ISafeBridgeSender.sol create mode 100644 contracts/src/bridge/single-message/mock/FastBridgeSenderToEthereumMock.sol delete mode 100644 contracts/src/gateway/HomeGatewayToGnosis.sol rename contracts/src/gateway/{ => single-message}/ForeignGatewayOnEthereum.sol (95%) rename contracts/src/gateway/{ => single-message}/HomeGatewayToEthereum.sol (90%) create mode 100644 contracts/src/gateway/single-message/interfaces/IForeignGatewaySingleMessage.sol create mode 100644 contracts/src/gateway/single-message/interfaces/IHomeGatewaySingleMessage.sol diff --git a/contracts/src/bridge/single-message/FastBridgeReceiverOnEthereumSingleMessage.sol b/contracts/src/bridge/single-message/FastBridgeReceiverOnEthereumSingleMessage.sol new file mode 100644 index 000000000..001f6bf1d --- /dev/null +++ b/contracts/src/bridge/single-message/FastBridgeReceiverOnEthereumSingleMessage.sol @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./SafeBridgeReceiverOnEthereum.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; + +/** + * Fast Bridge Receiver on Ethereum from Arbitrum + * Counterpart of `FastBridgeSenderToEthereum` + */ +contract FastBridgeReceiverOnEthereumSingleMessage is SafeBridgeReceiverOnEthereum, IFastBridgeReceiver { + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 messageHash; + address bridger; + uint256 claimedAt; + uint256 claimDeposit; + bool verified; + } + + struct Challenge { + address challenger; + uint256 challengedAt; + uint256 challengeDeposit; + } + + struct Ticket { + Claim claim; + Challenge challenge; + bool relayed; + } + + // ************************************* // + // * 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) { + claimDeposit = _claimDeposit; + challengeDeposit = _challengeDeposit; + challengeDuration = _challengeDuration; + alpha = _alpha; + } + + // ************************************* // + // * State 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. + */ + 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); + } + + /** + * @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. + */ + 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); + } + + /** + * @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. + */ + 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 + } + + /** + * 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. + */ + function verifyAndRelaySafe( + uint256 _ticketID, + uint256 _blockNumber, + bytes calldata _messageData + ) external override { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + Ticket storage ticket = tickets[_ticketID]; + require(ticket.relayed == false, "Message already relayed"); + + // 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; + } + + ticket.relayed = true; + require(_relay(_messageData), "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. + */ + 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 * alpha) / ONE_BASIS_POINT; + 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. + // 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. + */ + 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 * alpha) / ONE_BASIS_POINT; + 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. + // Checks-Effects-Interaction + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * @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. + */ + 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; + 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 * // + // ************************ // + + 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/single-message/FastBridgeReceiverOnGnosisSingleMessage.sol b/contracts/src/bridge/single-message/FastBridgeReceiverOnGnosisSingleMessage.sol new file mode 100644 index 000000000..3d09f90a1 --- /dev/null +++ b/contracts/src/bridge/single-message/FastBridgeReceiverOnGnosisSingleMessage.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/IFastBridgeReceiver.sol"; + +/** + * Fast Bridge Receiver on Gnosis from Arbitrum + * Counterpart of `FastBridgeSenderToGnosis` + */ +abstract contract FastBridgeReceiverOnGnosisSingleMessage is IFastBridgeReceiver { + // TODO in prealpha-3 +} diff --git a/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol new file mode 100644 index 000000000..1f879268e --- /dev/null +++ b/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./SafeBridgeSenderToEthereum.sol"; +import "./interfaces/IFastBridgeSender.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; + +/** + * Fast Bridge Sender to Ethereum from Arbitrum + * Counterpart of `FastBridgeReceiverOnEthereum` + */ +contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender { + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Ticket { + bytes32 messageHash; + uint256 blockNumber; + bool sentSafe; + } + + // ************************************* // + // * 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 + ) SafeBridgeSenderToEthereum() { + governor = _governor; + fastBridgeReceiver = _fastBridgeReceiver; + fastBridgeSender = _fastBridgeSender; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * 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."); + + ticketID = currentTicketID++; + + (bytes32 messageHash, bytes memory messageData) = _encode(ticketID, block.number, _receiver, _calldata); + emit OutgoingMessage(ticketID, block.number, _receiver, messageHash, messageData); + + 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 _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 _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 + 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, _receiver, _calldata); + require(ticket.messageHash == messageHash, "Invalid message for ticketID."); + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector( + methodSelector, + _ticketID, + ticket.blockNumber, + messageData + ); + + // 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; + } + + // ************************ // + // * Internal * // + // ************************ // + + function _encode( + uint256 _ticketID, + uint256 _blockNumber, + address _receiver, + bytes memory _calldata + ) internal pure returns (bytes32 messageHash, bytes memory messageData) { + // Encode the receiver address with the function signature + arguments i.e calldata + messageData = abi.encode(_receiver, _calldata); + + // Compute the hash over the message header (ticketID, blockNumber) and body (data). + messageHash = keccak256(abi.encode(_ticketID, _blockNumber, messageData)); + } +} diff --git a/contracts/src/gateway/ForeignGatewayOnGnosis.sol b/contracts/src/bridge/single-message/FastBridgeSenderToGnosis.sol similarity index 50% rename from contracts/src/gateway/ForeignGatewayOnGnosis.sol rename to contracts/src/bridge/single-message/FastBridgeSenderToGnosis.sol index 6dc1cfd0e..c03379b0a 100644 --- a/contracts/src/gateway/ForeignGatewayOnGnosis.sol +++ b/contracts/src/bridge/single-message/FastBridgeSenderToGnosis.sol @@ -10,12 +10,12 @@ pragma solidity ^0.8.0; -import "./interfaces/IForeignGateway.sol"; +import "./interfaces/IFastBridgeSender.sol"; /** - * Foreign Gateway on Gnosis chain - * Counterpart of `HomeGatewayToGnosis` + * Fast Bridge Sender to Gnosis from Arbitrum + * Counterpart of `FastBridgeReceiverOnGnosis` */ -abstract contract ForeignGatewayOnGnosis is IForeignGateway { +abstract contract FastBridgeSenderToGnosis is IFastBridgeSender { // TODO in prealpha-3 } diff --git a/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol new file mode 100644 index 000000000..13698fc07 --- /dev/null +++ b/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol @@ -0,0 +1,75 @@ +// 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 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; + } + + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + 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/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol new file mode 100644 index 000000000..494d19880 --- /dev/null +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol @@ -0,0 +1,89 @@ +// 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/single-message/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol new file mode 100644 index 000000000..b7c856575 --- /dev/null +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shalzz, @jaybuidl] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/arbitrum/IArbSys.sol"; +import "../interfaces/arbitrum/AddressAliasHelper.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/single-message/SafeBridgeSenderToGnosis.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol new file mode 100644 index 000000000..c43f63647 --- /dev/null +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol @@ -0,0 +1,31 @@ +// 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/single-message/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/single-message/interfaces/IFastBridgeReceiver.sol new file mode 100644 index 000000000..0b508f8d1 --- /dev/null +++ b/contracts/src/bridge/single-message/interfaces/IFastBridgeReceiver.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +interface IFastBridgeReceiver { + // ************************************* // + // * Events * // + // ************************************* // + + /** + * @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. + */ + event ClaimReceived(uint256 indexed ticketID, bytes32 indexed messageHash, uint256 claimedAt); + + /** + * @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. + */ + event ClaimChallenged(uint256 indexed ticketID, uint256 challengedAt); + + // ************************************* // + // * 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. + */ + function claim(uint256 _ticketID, bytes32 _messageHash) 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. + */ + function challenge(uint256 _ticketID) 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. + */ + function verifyAndRelay( + uint256 _ticketID, + uint256 _blockNumber, + bytes calldata _messageData + ) 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. + */ + function verifyAndRelaySafe( + uint256 _ticketID, + uint256 _blockNumber, + bytes calldata _messageData + ) 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. + */ + function withdrawClaimDeposit(uint256 _ticketID) 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. + */ + function withdrawChallengeDeposit(uint256 _ticketID) external; + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * @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. + */ + 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); + + /** + * @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/single-message/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/single-message/interfaces/IFastBridgeSender.sol new file mode 100644 index 000000000..c5ed12ab8 --- /dev/null +++ b/contracts/src/bridge/single-message/interfaces/IFastBridgeSender.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IFastBridgeSender { + // ************************************* // + // * Events * // + // ************************************* // + + /** + * @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 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. + */ + event OutgoingMessage( + uint256 indexed ticketID, + uint256 blockNumber, + address target, + bytes32 indexed messageHash, + bytes message + ); + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + + /** + * Note: Access must be restricted to the sending application. + * @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); + + /** + * @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 _receiver The cross-domain contract address which receives the calldata. + * @param _calldata The receiving domain encoded message data. + */ + function sendSafeFallback( + uint256 _ticketID, + address _receiver, + bytes memory _calldata + ) external payable; +} diff --git a/contracts/src/bridge/single-message/interfaces/ISafeBridgeReceiver.sol b/contracts/src/bridge/single-message/interfaces/ISafeBridgeReceiver.sol new file mode 100644 index 000000000..bb3916563 --- /dev/null +++ b/contracts/src/bridge/single-message/interfaces/ISafeBridgeReceiver.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +abstract contract ISafeBridgeReceiver { + function isSentBySafeBridge() internal view virtual returns (bool); +} diff --git a/contracts/src/bridge/single-message/interfaces/ISafeBridgeSender.sol b/contracts/src/bridge/single-message/interfaces/ISafeBridgeSender.sol new file mode 100644 index 000000000..97cb8f9b4 --- /dev/null +++ b/contracts/src/bridge/single-message/interfaces/ISafeBridgeSender.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +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. + * @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/single-message/mock/FastBridgeSenderToEthereumMock.sol b/contracts/src/bridge/single-message/mock/FastBridgeSenderToEthereumMock.sol new file mode 100644 index 000000000..fdd9a44f2 --- /dev/null +++ b/contracts/src/bridge/single-message/mock/FastBridgeSenderToEthereumMock.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../FastBridgeSenderToEthereum.sol"; + +/** + * Fast Bridge Sender to Ethereum from Arbitrum + * Counterpart of `FastBridgeReceiverOnEthereum` + */ +contract FastBridgeSenderToEthereumMock is FastBridgeSenderToEthereum { + IArbSys public arbsys; + + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + // struct Ticket { + // bytes32 messageHash; + // uint256 blockNumber; + // bool sentSafe; + // } + + // ************************************* // + // * Storage * // + // ************************************* // + + // address public override governor; // The governor of the contract. + // IFastBridgeReceiver public override 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, + address _arbsys + ) FastBridgeSenderToEthereum(_governor, _fastBridgeReceiver, _fastBridgeSender) { + arbsys = IArbSys(address(_arbsys)); + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + function sendSafeFallbackMock( + uint256 _ticketID, + address _receiver, + bytes memory _calldata + ) external payable { + // TODO: check if keeping _calldata in storage in sendFast() is cheaper than passing it again as a parameter here + 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, _receiver, _calldata); + require(ticket.messageHash == messageHash, "Invalid message for ticketID."); + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector( + methodSelector, + _ticketID, + ticket.blockNumber, + messageData + ); + + // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed + _sendSafeMock(address(fastBridgeReceiver), safeMessageData); + } + + // ************************ // + // * Governance * // + // ************************ // + + // ************************ // + // * Internal * // + // ************************ // + function _sendSafeMock(address _receiver, bytes memory _calldata) internal returns (uint256) { + uint256 withdrawalId = arbsys.sendTxToL1(_receiver, _calldata); + emit L2ToL1TxCreated(withdrawalId); + return withdrawalId; + } +} diff --git a/contracts/src/gateway/HomeGatewayToGnosis.sol b/contracts/src/gateway/HomeGatewayToGnosis.sol deleted file mode 100644 index 10a5b7858..000000000 --- a/contracts/src/gateway/HomeGatewayToGnosis.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/IHomeGateway.sol"; - -/** - * Home Gateway to Gnosis chain - * Counterpart of `ForeignGatewayOnGnosis` - */ -abstract contract HomeGatewayToGnosis is IHomeGateway { - // TODO in prealpha-3 -} diff --git a/contracts/src/gateway/ForeignGatewayOnEthereum.sol b/contracts/src/gateway/single-message/ForeignGatewayOnEthereum.sol similarity index 95% rename from contracts/src/gateway/ForeignGatewayOnEthereum.sol rename to contracts/src/gateway/single-message/ForeignGatewayOnEthereum.sol index c0614bc72..fd570c0f2 100644 --- a/contracts/src/gateway/ForeignGatewayOnEthereum.sol +++ b/contracts/src/gateway/single-message/ForeignGatewayOnEthereum.sol @@ -10,16 +10,16 @@ pragma solidity ^0.8.0; -import "../arbitration/IArbitrable.sol"; -import "../bridge/interfaces/IFastBridgeReceiver.sol"; +import "../../arbitration/IArbitrable.sol"; +import "../../bridge/interfaces/IFastBridgeReceiver.sol"; -import "./interfaces/IForeignGateway.sol"; +import "./interfaces/IForeignGatewaySingleMessage.sol"; /** * Foreign Gateway on Ethereum * Counterpart of `HomeGatewayToEthereum` */ -contract ForeignGatewayOnEthereum is IForeignGateway { +contract ForeignGatewayOnEthereum is IForeignGatewaySingleMessage { // The global default minimum number of jurors in a dispute. uint256 public constant MIN_JURORS = 3; diff --git a/contracts/src/gateway/HomeGatewayToEthereum.sol b/contracts/src/gateway/single-message/HomeGatewayToEthereum.sol similarity index 90% rename from contracts/src/gateway/HomeGatewayToEthereum.sol rename to contracts/src/gateway/single-message/HomeGatewayToEthereum.sol index 6ce6cff60..ec337f1a9 100644 --- a/contracts/src/gateway/HomeGatewayToEthereum.sol +++ b/contracts/src/gateway/single-message/HomeGatewayToEthereum.sol @@ -10,17 +10,17 @@ pragma solidity ^0.8.0; -import "../arbitration/IArbitrator.sol"; -import "../bridge/interfaces/IFastBridgeSender.sol"; +import "../../arbitration/IArbitrator.sol"; +import "../../bridge/single-message/interfaces/IFastBridgeSender.sol"; -import "./interfaces/IForeignGateway.sol"; -import "./interfaces/IHomeGateway.sol"; +import "./interfaces/IForeignGatewaySingleMessage.sol"; +import "./interfaces/IHomeGatewaySingleMessage.sol"; /** * Home Gateway to Ethereum * Counterpart of `ForeignGatewayOnEthereum` */ -contract HomeGatewayToEthereum is IHomeGateway { +contract HomeGatewayToEthereum is IHomeGatewaySingleMessage { mapping(uint256 => bytes32) public disputeIDtoHash; mapping(bytes32 => uint256) public disputeHashtoID; @@ -107,7 +107,7 @@ contract HomeGatewayToEthereum is IHomeGateway { bytes32 disputeHash = disputeIDtoHash[_disputeID]; RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; - bytes4 methodSelector = IForeignGateway.relayRule.selector; + bytes4 methodSelector = IForeignGatewaySingleMessage.relayRule.selector; bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _ruling, relayedData.relayer); fastbridge.sendFast(foreignGateway, data); diff --git a/contracts/src/gateway/single-message/interfaces/IForeignGatewaySingleMessage.sol b/contracts/src/gateway/single-message/interfaces/IForeignGatewaySingleMessage.sol new file mode 100644 index 000000000..a1a80298b --- /dev/null +++ b/contracts/src/gateway/single-message/interfaces/IForeignGatewaySingleMessage.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../../arbitration/IArbitrator.sol"; + +interface IForeignGatewaySingleMessage is IArbitrator { + function chainID() external view returns (uint256); + + /** + * Relay the rule call from the home gateway to the arbitrable. + */ + function relayRule( + bytes32 _disputeHash, + uint256 _ruling, + address _forwarder + ) external; + + function withdrawFees(bytes32 _disputeHash) external; + + // For cross-chain Evidence standard + + function disputeHashToForeignID(bytes32 _disputeHash) external view returns (uint256); + + function homeChainID() external view returns (uint256); + + function homeGateway() external view returns (address); +} diff --git a/contracts/src/gateway/single-message/interfaces/IHomeGatewaySingleMessage.sol b/contracts/src/gateway/single-message/interfaces/IHomeGatewaySingleMessage.sol new file mode 100644 index 000000000..eda279afa --- /dev/null +++ b/contracts/src/gateway/single-message/interfaces/IHomeGatewaySingleMessage.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../../arbitration/IArbitrable.sol"; +import "../../../evidence/IMetaEvidence.sol"; + +interface IHomeGatewaySingleMessage is IArbitrable, IMetaEvidence { + function chainID() external view returns (uint256); + + function relayCreateDispute( + uint256 _originalChainID, + bytes32 _originalBlockHash, + uint256 _originalDisputeID, + uint256 _choices, + bytes calldata _extraData, + address _arbitrable + ) external payable; + + // For cross-chain Evidence standard + + function disputeHashToHomeID(bytes32 _disputeHash) external view returns (uint256); + + function foreignChainID() external view returns (uint256); + + function foreignGateway() external view returns (address); +} From fdf31a118978fede408fa8c0dc6930a7972f8419 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 18:03:27 +0100 Subject: [PATCH 04/21] feat: updated arbitrum mock bridge --- .../mock/FastBridgeSenderToEthereumMock.sol | 107 ------------------ .../src/bridge/test/FastBridgeSenderMock.sol | 67 +++++++++++ .../{mock => test/arbitrum}/ArbSysMock.sol | 2 +- .../{mock => test/arbitrum}/BridgeMock.sol | 2 +- .../{mock => test/arbitrum}/InboxMock.sol | 2 +- .../{mock => test/arbitrum}/OutboxMock.sol | 2 +- 6 files changed, 71 insertions(+), 111 deletions(-) delete mode 100644 contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol create mode 100644 contracts/src/bridge/test/FastBridgeSenderMock.sol rename contracts/src/bridge/{mock => test/arbitrum}/ArbSysMock.sol (91%) rename contracts/src/bridge/{mock => test/arbitrum}/BridgeMock.sol (96%) rename contracts/src/bridge/{mock => test/arbitrum}/InboxMock.sol (97%) rename contracts/src/bridge/{mock => test/arbitrum}/OutboxMock.sol (94%) diff --git a/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol b/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol deleted file mode 100644 index fdd9a44f2..000000000 --- a/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../FastBridgeSenderToEthereum.sol"; - -/** - * Fast Bridge Sender to Ethereum from Arbitrum - * Counterpart of `FastBridgeReceiverOnEthereum` - */ -contract FastBridgeSenderToEthereumMock is FastBridgeSenderToEthereum { - IArbSys public arbsys; - - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - // struct Ticket { - // bytes32 messageHash; - // uint256 blockNumber; - // bool sentSafe; - // } - - // ************************************* // - // * Storage * // - // ************************************* // - - // address public override governor; // The governor of the contract. - // IFastBridgeReceiver public override 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, - address _arbsys - ) FastBridgeSenderToEthereum(_governor, _fastBridgeReceiver, _fastBridgeSender) { - arbsys = IArbSys(address(_arbsys)); - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - function sendSafeFallbackMock( - uint256 _ticketID, - address _receiver, - bytes memory _calldata - ) external payable { - // TODO: check if keeping _calldata in storage in sendFast() is cheaper than passing it again as a parameter here - 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, _receiver, _calldata); - require(ticket.messageHash == messageHash, "Invalid message for ticketID."); - - // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector( - methodSelector, - _ticketID, - ticket.blockNumber, - messageData - ); - - // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed - _sendSafeMock(address(fastBridgeReceiver), safeMessageData); - } - - // ************************ // - // * Governance * // - // ************************ // - - // ************************ // - // * Internal * // - // ************************ // - function _sendSafeMock(address _receiver, bytes memory _calldata) internal returns (uint256) { - uint256 withdrawalId = arbsys.sendTxToL1(_receiver, _calldata); - 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..acdb851b8 --- /dev/null +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -0,0 +1,67 @@ +// 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 _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _arbsys The address of the mock ArbSys contract. + */ + constructor( + uint256 _epochPeriod, + address _fastBridgeReceiver, + address _arbsys + ) FastBridgeSenderBase(_epochPeriod, _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/mock/ArbSysMock.sol b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol similarity index 91% rename from contracts/src/bridge/mock/ArbSysMock.sol rename to contracts/src/bridge/test/arbitrum/ArbSysMock.sol index 1b657a6cc..7febe655b 100644 --- a/contracts/src/bridge/mock/ArbSysMock.sol +++ b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IArbSys.sol"; +import "../../interfaces/arbitrum/IArbSys.sol"; contract ArbSysMock { function sendTxToL1(address destination, bytes calldata calldataForL1) diff --git a/contracts/src/bridge/mock/BridgeMock.sol b/contracts/src/bridge/test/arbitrum/BridgeMock.sol similarity index 96% rename from contracts/src/bridge/mock/BridgeMock.sol rename to contracts/src/bridge/test/arbitrum/BridgeMock.sol index 89cb34cc9..f98b04c36 100644 --- a/contracts/src/bridge/mock/BridgeMock.sol +++ b/contracts/src/bridge/test/arbitrum/BridgeMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IInbox.sol"; +import "../../interfaces/arbitrum/IInbox.sol"; contract BridgeMock is IBridge { address public outbox; diff --git a/contracts/src/bridge/mock/InboxMock.sol b/contracts/src/bridge/test/arbitrum/InboxMock.sol similarity index 97% rename from contracts/src/bridge/mock/InboxMock.sol rename to contracts/src/bridge/test/arbitrum/InboxMock.sol index 5d7b91edc..682cfb25e 100644 --- a/contracts/src/bridge/mock/InboxMock.sol +++ b/contracts/src/bridge/test/arbitrum/InboxMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IInbox.sol"; +import "../../interfaces/arbitrum/IInbox.sol"; contract InboxMock is IInbox { IBridge public arbBridge; diff --git a/contracts/src/bridge/mock/OutboxMock.sol b/contracts/src/bridge/test/arbitrum/OutboxMock.sol similarity index 94% rename from contracts/src/bridge/mock/OutboxMock.sol rename to contracts/src/bridge/test/arbitrum/OutboxMock.sol index ed23d9709..684c87ea6 100644 --- a/contracts/src/bridge/mock/OutboxMock.sol +++ b/contracts/src/bridge/test/arbitrum/OutboxMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IOutbox.sol"; +import "../../interfaces/arbitrum/IOutbox.sol"; contract OutboxMock is IOutbox { address public safeBridgeSender; From 025e49ac63759d91eb29df87c2f20cf40e98bc07 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 18:04:46 +0100 Subject: [PATCH 05/21] chore: removing single message bridge artifacts --- .../src/bridge/FastBridgeSenderToEthereum.sol | 145 ------------------ .../src/bridge/FastBridgeSenderToGnosis.sol | 21 --- .../bridge/SafeBridgeReceiverOnEthereum.sol | 75 --------- ...SafeBridgeSenderToArbitrumFromEthereum.sol | 89 ----------- .../src/bridge/SafeBridgeSenderToEthereum.sol | 45 ------ .../src/bridge/SafeBridgeSenderToGnosis.sol | 31 ---- 6 files changed, 406 deletions(-) delete mode 100644 contracts/src/bridge/FastBridgeSenderToEthereum.sol delete mode 100644 contracts/src/bridge/FastBridgeSenderToGnosis.sol delete mode 100644 contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol delete mode 100644 contracts/src/bridge/SafeBridgeSenderToArbitrumFromEthereum.sol delete mode 100644 contracts/src/bridge/SafeBridgeSenderToEthereum.sol delete mode 100644 contracts/src/bridge/SafeBridgeSenderToGnosis.sol diff --git a/contracts/src/bridge/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/FastBridgeSenderToEthereum.sol deleted file mode 100644 index 1f879268e..000000000 --- a/contracts/src/bridge/FastBridgeSenderToEthereum.sol +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./SafeBridgeSenderToEthereum.sol"; -import "./interfaces/IFastBridgeSender.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; - -/** - * Fast Bridge Sender to Ethereum from Arbitrum - * Counterpart of `FastBridgeReceiverOnEthereum` - */ -contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSender { - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - struct Ticket { - bytes32 messageHash; - uint256 blockNumber; - bool sentSafe; - } - - // ************************************* // - // * 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 - ) SafeBridgeSenderToEthereum() { - governor = _governor; - fastBridgeReceiver = _fastBridgeReceiver; - fastBridgeSender = _fastBridgeSender; - } - - // ************************************* // - // * State Modifiers * // - // ************************************* // - - /** - * 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."); - - ticketID = currentTicketID++; - - (bytes32 messageHash, bytes memory messageData) = _encode(ticketID, block.number, _receiver, _calldata); - emit OutgoingMessage(ticketID, block.number, _receiver, messageHash, messageData); - - 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 _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 _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 - 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, _receiver, _calldata); - require(ticket.messageHash == messageHash, "Invalid message for ticketID."); - - // Safe Bridge message envelope - bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; - bytes memory safeMessageData = abi.encodeWithSelector( - methodSelector, - _ticketID, - ticket.blockNumber, - messageData - ); - - // 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; - } - - // ************************ // - // * Internal * // - // ************************ // - - function _encode( - uint256 _ticketID, - uint256 _blockNumber, - address _receiver, - bytes memory _calldata - ) internal pure returns (bytes32 messageHash, bytes memory messageData) { - // Encode the receiver address with the function signature + arguments i.e calldata - messageData = abi.encode(_receiver, _calldata); - - // Compute the hash over the message header (ticketID, blockNumber) and body (data). - messageHash = keccak256(abi.encode(_ticketID, _blockNumber, messageData)); - } -} diff --git a/contracts/src/bridge/FastBridgeSenderToGnosis.sol b/contracts/src/bridge/FastBridgeSenderToGnosis.sol deleted file mode 100644 index c03379b0a..000000000 --- a/contracts/src/bridge/FastBridgeSenderToGnosis.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/IFastBridgeSender.sol"; - -/** - * Fast Bridge Sender to Gnosis from Arbitrum - * Counterpart of `FastBridgeReceiverOnGnosis` - */ -abstract contract FastBridgeSenderToGnosis is IFastBridgeSender { - // TODO in prealpha-3 -} diff --git a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol deleted file mode 100644 index 07a43f833..000000000 --- a/contracts/src/bridge/SafeBridgeReceiverOnEthereum.sol +++ /dev/null @@ -1,75 +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 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; - } - - // ************************************* // - // * Views * // - // ************************************* // - - function isSentBySafeBridge() internal view override returns (bool) { - 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/SafeBridgeSenderToArbitrumFromEthereum.sol b/contracts/src/bridge/SafeBridgeSenderToArbitrumFromEthereum.sol deleted file mode 100644 index d10baf8d9..000000000 --- a/contracts/src/bridge/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/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/SafeBridgeSenderToEthereum.sol deleted file mode 100644 index 46ae5e3dc..000000000 --- a/contracts/src/bridge/SafeBridgeSenderToEthereum.sol +++ /dev/null @@ -1,45 +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/arbitrum/AddressAliasHelper.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/SafeBridgeSenderToGnosis.sol b/contracts/src/bridge/SafeBridgeSenderToGnosis.sol deleted file mode 100644 index 41f9189d8..000000000 --- a/contracts/src/bridge/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); - } -} From cc04d2702302f531744f0457eb347d620d65437a Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 30 May 2022 18:05:13 +0100 Subject: [PATCH 06/21] feat: batched message fast bridge --- contracts/deploy/01-foreign-chain.ts | 30 +-- contracts/deploy/02-home-chain.ts | 60 ++--- .../src/bridge/FastBridgeReceiverBase.sol | 254 ++++++++++++++++++ .../bridge/FastBridgeReceiverOnEthereum.sol | 249 ++--------------- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 41 ++- contracts/src/bridge/FastBridgeSender.sol | 60 +++++ contracts/src/bridge/FastBridgeSenderBase.sol | 109 ++++++++ contracts/src/bridge/SafeBridgeRouter.sol | 86 ++++++ .../bridge/interfaces/IFastBridgeReceiver.sol | 90 +++---- .../bridge/interfaces/IFastBridgeSender.sol | 52 ++-- .../bridge/interfaces/ISafeBridgeReceiver.sol | 13 + .../bridge/interfaces/ISafeBridgeSender.sol | 6 +- contracts/src/gateway/ForeignGateway.sol | 212 +++++++++++++++ contracts/src/gateway/HomeGateway.sol | 131 +++++++++ .../gateway/interfaces/IForeignGateway.sol | 11 +- .../interfaces/IForeignGatewayBase.sol | 15 ++ .../interfaces/IForeignGatewayMock.sol | 12 + .../src/gateway/interfaces/IHomeGateway.sol | 10 +- .../gateway/interfaces/IHomeGatewayBase.sol | 15 ++ .../gateway/interfaces/IHomeGatewayMock.sol | 7 + .../src/gateway/test/ForeignGatewayMock.sol | 72 +++++ .../src/gateway/test/HomeGatewayMock.sol | 60 +++++ 22 files changed, 1210 insertions(+), 385 deletions(-) create mode 100644 contracts/src/bridge/FastBridgeReceiverBase.sol create mode 100644 contracts/src/bridge/FastBridgeSender.sol create mode 100644 contracts/src/bridge/FastBridgeSenderBase.sol create mode 100644 contracts/src/bridge/SafeBridgeRouter.sol create mode 100644 contracts/src/gateway/ForeignGateway.sol create mode 100644 contracts/src/gateway/HomeGateway.sol create mode 100644 contracts/src/gateway/interfaces/IForeignGatewayBase.sol create mode 100644 contracts/src/gateway/interfaces/IForeignGatewayMock.sol create mode 100644 contracts/src/gateway/interfaces/IHomeGatewayBase.sol create mode 100644 contracts/src/gateway/interfaces/IHomeGatewayMock.sol create mode 100644 contracts/src/gateway/test/ForeignGatewayMock.sol create mode 100644 contracts/src/gateway/test/HomeGatewayMock.sol diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index 71d07070a..f1b74d757 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -12,20 +12,23 @@ enum ForeignChains { } const paramsByChainId = { 1: { - claimDeposit: parseEther("0.1"), - challengeDuration: 86400, // 1 day + deposit: parseEther("0.1"), + epochPeriod: 86400, // 24 hours + challengePeriod: 14400, // 4 hours homeChainId: 42161, arbitrumInbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", }, 4: { - claimDeposit: parseEther("0.1"), - challengeDuration: 120, // 2 min + deposit: parseEther("0.1"), + epochPeriod: 86400, // 24 hours + challengePeriod: 14400, // 4 hours homeChainId: 421611, arbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", }, 31337: { - claimDeposit: parseEther("0.1"), - challengeDuration: 120, // 2 min + deposit: parseEther("0.1"), + epochPeriod: 86400, // 24 hours + challengePeriod: 14400, // 4 hours homeChainId: 31337, arbitrumInbox: "0x00", }, @@ -59,9 +62,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 { claimDeposit, challengeDuration, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; - const challengeDeposit = claimDeposit; - const bridgeAlpha = 5000; + const { deposit, epochPeriod, challengePeriod, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); const homeGatewayAddress = getContractAddress(deployer, nonce); @@ -79,19 +80,18 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { from: deployer, args: [ - deployer, + deposit, + epochPeriod, + challengePeriod, fastBridgeSenderAddress, - inboxAddress, - claimDeposit, - challengeDeposit, - challengeDuration, - bridgeAlpha, + inboxAddress ], log: true, }); const foreignGateway = await deploy("ForeignGatewayOnEthereum", { from: deployer, + contract: "ForeignGateway", args: [ deployer, fastBridgeReceiver.address, diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 8ba5d4f32..e5af383f2 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -3,6 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types"; import { ethers } from "hardhat"; const HOME_CHAIN_IDS = [42161, 421611, 31337]; // ArbOne, ArbRinkeby, Hardhat +const epochPeriod = 86400; // 24 hours // TODO: use deterministic deployments @@ -19,12 +20,14 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const hardhatDeployer = async () => { const fastBridgeReceiver = await deployments.get("FastBridgeReceiverOnEthereum"); const arbSysMock = await deploy("ArbSysMock", { from: deployer, log: true }); - - const fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { - from: deployer, - args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero, arbSysMock.address], - log: true, - }); // nonce+0 + let fastBridgeSender; + + fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { + from: deployer, + contract: "FastBridgeSenderMock", + args: [epochPeriod, fastBridgeReceiver.address, arbSysMock.address], + log: true, + }); // nonce+0 const klerosCore = await deployments.get("KlerosCore"); const foreignGateway = await deployments.get("ForeignGatewayOnEthereum"); @@ -32,26 +35,12 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const homeGateway = await deploy("HomeGatewayToEthereum", { from: deployer, - args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + contract: "HomeGateway", + args: [deployer, klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], gasLimit: 4000000, log: true, }); // nonce+1 - const fastSender = await hre.ethers - .getContractAt("FastBridgeSenderToEthereumMock", fastBridgeSender.address) - .then((contract) => contract.fastBridgeSender()); - - if (fastSender === ethers.constants.AddressZero) { - await execute( - "FastBridgeSenderToEthereumMock", - { - from: deployer, - log: true, - }, - "changeFastSender", - homeGateway.address - ); - const outbox = await deploy("OutboxMock", { from: deployer, args: [fastBridgeSender.address], @@ -69,7 +58,6 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) args: [bridge.address], log: true, }); - } }; // ---------------------------------------------------------------------------------------------- @@ -78,7 +66,8 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { from: deployer, - args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero], + contract: "FastBridgeSender", + args: [epochPeriod, fastBridgeReceiver.address ], log: true, }); // nonce+0 @@ -87,22 +76,15 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const foreignChainId = Number(await hre.companionNetworks.foreign.getChainId()); const homeGateway = await deploy("HomeGatewayToEthereum", { from: deployer, - args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + 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 - ); - } + }); // nonce+ }; // ---------------------------------------------------------------------------------------------- diff --git a/contracts/src/bridge/FastBridgeReceiverBase.sol b/contracts/src/bridge/FastBridgeReceiverBase.sol new file mode 100644 index 000000000..2ad7c0084 --- /dev/null +++ b/contracts/src/bridge/FastBridgeReceiverBase.sol @@ -0,0 +1,254 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./merkle/MerkleProof.sol"; + +/** + * Fast Receiver Base + * Counterpart of `FastSenderBase` + */ +abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, ISafeBridgeReceiver { + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + uint32 timestamp; + bool honest; + bool depositAndRewardWithdrawn; + } + + struct Challenge { + address challenger; + bool honest; + bool depositAndRewardWithdrawn; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public immutable override challengePeriod; // 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 + 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 each epoch. + * @param _challengePeriod The duration of the period allowing to challenge a claim. + * @param _safeRouter The address of the Safe Router on Ethereum. + */ + constructor( + uint256 _deposit, + uint256 _epochPeriod, + uint256 _challengePeriod, + address _safeRouter + ) { + deposit = _deposit; + epochPeriod = _epochPeriod; + challengePeriod = _challengePeriod; + safeRouter = _safeRouter; + } + + // ************************************* // + // * 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 _epoch The epoch of the claim to claim. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epoch = block.timestamp / epochPeriod; + // allow claim about previous epoch + require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); + + require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[_epoch] = Claim({ + batchMerkleRoot: _batchMerkleRoot, + bridger: msg.sender, + timestamp: uint32(block.timestamp), + honest: false, + depositAndRewardWithdrawn: false + }); + + emit ClaimReceived(_epoch, _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. + * @param _epoch The epoch of the claim to challenge. + */ + function challenge(uint256 _epoch) external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + require(claims[_epoch].bridger != address(0), "No claim to challenge."); + require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); + + challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); + + emit ClaimChallenged(_epoch); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); + + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require( + block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, + "Challenge period has not yet elapsed." + ); + + 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.encode(_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 onlyFromSafeBridge { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + challenges[_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 storage claim = claims[_epoch]; + + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + require(claim.depositAndRewardWithdrawn == false, "Claim deposit already withdrawn."); + + uint256 amount = deposit; + if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + + claim.depositAndRewardWithdrawn = true; + + 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 storage challenge = challenges[_epoch]; + + require(challenge.challenger != address(0), "Claim does not exist"); + require(challenge.honest == true, "Claim not verified: deposit forfeited"); + require(challenge.depositAndRewardWithdrawn == false, "Claim deposit already withdrawn."); + + uint256 amount = deposit + deposit / 2; + challenge.depositAndRewardWithdrawn = true; + + 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 the claim for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function claimChallengePeriod(uint256 _epoch) 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 + start = claims[_epoch].timestamp; + end = start + challengePeriod; + return (start, end); + } + + // ************************ // + // * 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/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index e70cd9f07..ae6ee8051 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -10,252 +10,45 @@ pragma solidity ^0.8.0; -import "./SafeBridgeReceiverOnEthereum.sol"; -import "./interfaces/IFastBridgeReceiver.sol"; +import "./FastBridgeReceiverBase.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 { - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - struct Claim { - bytes32 messageHash; - address bridger; - uint256 claimedAt; - uint256 claimDeposit; - bool verified; - } - - struct Challenge { - address challenger; - uint256 challengedAt; - uint256 challengeDeposit; - } - - struct Ticket { - Claim claim; - Challenge challenge; - bool relayed; - } - +contract FastBridgeReceiverOnEthereum is FastBridgeReceiverBase { // ************************************* // // * 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. + IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. /** * @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. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of the period allowing to challenge a claim. + * @param _challengePeriod The duration of the period allowing to challenge a claim. + * @param _safeRouter The address of the Safe Router on Ethereum. + * @param _inbox The address of the inbox contract on Ethereum. */ constructor( - address _governor, - address _safeBridgeSender, - address _inbox, - uint256 _claimDeposit, - uint256 _challengeDeposit, - uint256 _challengeDuration, - uint256 _alpha - ) SafeBridgeReceiverOnEthereum(_governor, _safeBridgeSender, _inbox) { - claimDeposit = _claimDeposit; - challengeDeposit = _challengeDeposit; - challengeDuration = _challengeDuration; - alpha = _alpha; + uint256 _deposit, + uint256 _epochPeriod, + uint256 _challengePeriod, + address _safeRouter, + address _inbox + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _challengePeriod, _safeRouter) { + inbox = IInbox(_inbox); } // ************************************* // - // * State Modifiers * // + // * Views * // // ************************************* // - /** - * @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. - */ - 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); - } - - /** - * @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. - */ - 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); - } - - /** - * @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. - */ - 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 - } - - /** - * 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. - */ - function verifyAndRelaySafe( - uint256 _ticketID, - uint256 _blockNumber, - bytes calldata _messageData - ) external override { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - Ticket storage ticket = tickets[_ticketID]; - require(ticket.relayed == false, "Message already relayed"); - - // 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; - } - - ticket.relayed = true; - require(_relay(_messageData), "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. - */ - 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 * alpha) / ONE_BASIS_POINT; - 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. - // 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. - */ - 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 * alpha) / ONE_BASIS_POINT; - 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. - // Checks-Effects-Interaction - } - - // ************************************* // - // * Public Views * // - // ************************************* // - - /** - * @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. - */ - 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; - 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 * // - // ************************ // - - 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() == safeRouter; } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index ef8df9478..3eabeea2a 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, @shalzz, @hrishibhat, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,12 +10,43 @@ pragma solidity ^0.8.0; -import "./interfaces/IFastBridgeReceiver.sol"; +import "./FastBridgeReceiverBase.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; /** - * Fast Bridge Receiver on Gnosis from Arbitrum + * Fast Bridge Receiver on Ethereum from Arbitrum * Counterpart of `FastBridgeSenderToGnosis` */ -abstract contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver { - // TODO in prealpha-3 +contract FastBridgeReceiverOnGnosis is FastBridgeReceiverBase { + // ************************************* // + // * Storage * // + // ************************************* // + + IAMB public immutable amb; // The address of the AMB contract on GC. + + /** + * @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 _challengePeriod The duration of the period allowing to challenge a claim. + * @param _safeRouter The address of the Safe Bridge Router on Ethereum. + * @param _amb The the AMB contract on Gnosis Chain. + */ + constructor( + uint256 _deposit, + uint256 _epochPeriod, + uint256 _challengePeriod, + address _safeRouter, + address _amb + ) FastBridgeReceiverBase(_deposit, _epochPeriod, _challengePeriod, _safeRouter) { + amb = IAMB(_amb); + } + + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + return (msg.sender == address(amb)) && (amb.messageSender() == safeRouter); + } } diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol new file mode 100644 index 000000000..ad85f832c --- /dev/null +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -0,0 +1,60 @@ +// 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 Bridge Sender to Ethereum from Arbitrum + * Counterpart of `FastBridgeReceiverOnEthereum` + */ +contract FastBridgeSender is FastBridgeSenderBase { + // ************************************* // + // * Events * // + // ************************************* // + + event L2ToL1TxCreated(uint256 indexed txID); + + // ************************************* // + // * Storage * // + // ************************************* // + + IArbSys public constant ARB_SYS = IArbSys(address(100)); + + /** + * @dev Constructor. + * @param _epochPeriod The immutable period between epochs. + * @param _safeRouter The address of the Safe Router on Ethereum. + */ + constructor(uint256 _epochPeriod, address _safeRouter) FastBridgeSenderBase(_epochPeriod, _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. + * @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 = ARB_SYS.sendTxToL1(_receiver, _calldata); + + emit L2ToL1TxCreated(txID); + return bytes32(txID); + } +} diff --git a/contracts/src/bridge/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol new file mode 100644 index 000000000..d34297042 --- /dev/null +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./merkle/MerkleTree.sol"; +import "./interfaces/IFastBridgeSender.sol"; +import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; + +/** + * Fast Bridge Sender Base + * Counterpart of `FastReceiverBase` + */ +abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBridgeSender { + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public currentBatchID; + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + address public immutable safeRouter; + + // ************************************* // + // * Events * // + // ************************************* // + + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiver. + */ + event SendBatch(uint256 indexed currentBatchID, bytes32 indexed batchMerkleRoot); + + /** + * @dev Constructor. + * @param _epochPeriod The duration between epochs. + * @param _safeRouter The the Safe Bridge Router on Ethereum to the foreign chain. + */ + constructor(uint256 _epochPeriod, address _safeRouter) { + epochPeriod = _epochPeriod; + safeRouter = _safeRouter; + currentBatchID = block.timestamp / epochPeriod; + } + + // ************************************* // + // * 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 _fastMessageCalldata = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); + bytes memory _fastMessage = abi.encode(_receiver, _fastMessageCalldata); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + + appendMessage(fastMessageHash); // add message to merkle tree + + emit MessageReceived(currentBatchID, fastMessageHash, 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 _fastMessageCalldata = abi.encodeWithSelector(_functionSelector, msg.sender); + bytes memory _fastMessage = abi.encode(_receiver, _fastMessageCalldata); + bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + + appendMessage(fastMessageHash); // add message to merkle tree + + emit MessageReceived(currentBatchID, fastMessageHash, batchSize); + } + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendEpoch() external { + uint256 epoch = block.timestamp / epochPeriod; + require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); + require(batchSize > 0, "No messages to send."); + + // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRootAndReset(); + fastOutbox[epoch] = batchMerkleRoot; + + emit SendBatch(currentBatchID, batchMerkleRoot); + + currentBatchID = epoch; + } +} diff --git a/contracts/src/bridge/SafeBridgeRouter.sol b/contracts/src/bridge/SafeBridgeRouter.sol new file mode 100644 index 000000000..64e12c60f --- /dev/null +++ b/contracts/src/bridge/SafeBridgeRouter.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeSender.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 ISafeBridgeReceiver, ISafeBridgeSender { + // ************************************* // + // * 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 _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 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 = _sendSafe(fastBridgeReceiverOnGnosisChain, safeMessageData); + emit safeRelayed(txID); + } + + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { + return amb.requireToPassMessage(_receiver, _calldata, amb.maxGasPerTx()); + } + + // ************************************* // + // * 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/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 0b508f8d1..17101a730 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -17,101 +17,79 @@ 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 for which 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 _epoch The epoch of the claim to claim. + * @param _batchMerkleRoot The hash claimed for the ticket. */ - function claim(uint256 _ticketID, bytes32 _messageHash) external payable; + function claim(uint256 _epoch, 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. + * @param _epoch The epoch of the claim to challenge. */ - function challenge(uint256 _ticketID) external payable; + function challenge(uint256 _epoch) 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 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 verifyAndRelay( - uint256 _ticketID, - uint256 _blockNumber, - bytes calldata _messageData + function verifyAndRelayMessage( + 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 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 verifyAndRelaySafe( - uint256 _ticketID, - uint256 _blockNumber, - bytes calldata _messageData - ) 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. - */ - 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`. + * @param _epoch The epoch of the claim to request the challenge period. * @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); + function claimChallengePeriod(uint256 _epoch) external view returns (uint256 start, uint256 end); /** - * @return amount The duration of the period allowing to challenge a claim. + * @dev Returns the epoch period. */ - function challengeDuration() external view returns (uint256 amount); + function epochPeriod() external view returns (uint256 epochPeriod); /** - * @return amount Basis point of claim or challenge deposit that are lost when dishonest. + * @dev Returns the challenge period. */ - function alpha() external view returns (uint256 amount); + function challengePeriod() external view returns (uint256 challengePeriod); } diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index c5ed12ab8..7b11cf13b 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -9,42 +9,48 @@ 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 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 lastSentEpoch The last sent epoch. + * @param fastMessageHash The fast message data. + * @param nonce The message nonce. */ - event OutgoingMessage( - uint256 indexed ticketID, - uint256 blockNumber, - address target, - bytes32 indexed messageHash, - bytes message - ); + event MessageReceived(uint256 lastSentEpoch, bytes32 fastMessageHash, 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 _functionSelector The function selector to call. * @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, + bytes4 _functionSelector, + 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()`. + * 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. + * @param _functionSelector The function selector to call. */ - function sendSafeFallback( - uint256 _ticketID, - address _receiver, - bytes memory _calldata - ) external payable; + 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 + */ + function sendSafeFallback(uint256 _epoch) external payable; } 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/ISafeBridgeSender.sol b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol index 97cb8f9b4..ddcc8df06 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol @@ -6,9 +6,9 @@ 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); + function _sendSafe(address _receiver, bytes memory _calldata) internal virtual returns (bytes32); } diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol new file mode 100644 index 000000000..c77790256 --- /dev/null +++ b/contracts/src/gateway/ForeignGateway.sol @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shalzz, @jaybuidl] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../arbitration/IArbitrable.sol"; +import "./interfaces/IForeignGateway.sol"; + +/** + * Foreign Gateway + * Counterpart of `HomeGateway` + */ +contract ForeignGateway 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 _messageSender, + bytes32 _disputeHash, + uint256 _ruling, + address _relayer + ) external override onlyFromFastBridge { + require(_messageSender == 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/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol new file mode 100644 index 000000000..431d0c8ea --- /dev/null +++ b/contracts/src/gateway/HomeGateway.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shalzz, @jaybuidl] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../arbitration/IArbitrator.sol"; +import "../bridge/interfaces/IFastBridgeSender.sol"; + +import "./interfaces/IForeignGateway.sol"; +import "./interfaces/IHomeGateway.sol"; + +/** + * Home Gateway + * Counterpart of `ForeignGateway` + */ +contract HomeGateway 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]; + } +} diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol index a507b0ed3..3872e82b2 100644 --- a/contracts/src/gateway/interfaces/IForeignGateway.sol +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.0; import "../../arbitration/IArbitrator.sol"; +import "./IForeignGatewayBase.sol"; -interface IForeignGateway is IArbitrator { - function chainID() external view returns (uint256); - +interface IForeignGateway is IArbitrator, IForeignGatewayBase { /** * Relay the rule call from the home gateway to the arbitrable. */ function relayRule( + address _messageSender, bytes32 _disputeHash, uint256 _ruling, address _forwarder @@ -19,10 +19,5 @@ interface IForeignGateway is IArbitrator { function withdrawFees(bytes32 _disputeHash) external; // For cross-chain Evidence standard - function disputeHashToForeignID(bytes32 _disputeHash) external view returns (uint256); - - function homeChainID() external view returns (uint256); - - function homeGateway() external view returns (address); } diff --git a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol new file mode 100644 index 000000000..682e7d95c --- /dev/null +++ b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeReceiver.sol"; + +interface IForeignGatewayBase { + function fastbridge() external view returns (IFastBridgeReceiver); + + function chainID() external view returns (uint256); + + function homeChainID() external view returns (uint256); + + function homeGateway() external view returns (address); +} diff --git a/contracts/src/gateway/interfaces/IForeignGatewayMock.sol b/contracts/src/gateway/interfaces/IForeignGatewayMock.sol new file mode 100644 index 000000000..58fde077b --- /dev/null +++ b/contracts/src/gateway/interfaces/IForeignGatewayMock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IForeignGatewayBase.sol"; + +interface IForeignGatewayMock is IForeignGatewayBase { + /** + * Receive the message from the home gateway. + */ + function receiveMessage(address _messageSender) external; +} diff --git a/contracts/src/gateway/interfaces/IHomeGateway.sol b/contracts/src/gateway/interfaces/IHomeGateway.sol index 99eae4d6e..1e0f8c8cd 100644 --- a/contracts/src/gateway/interfaces/IHomeGateway.sol +++ b/contracts/src/gateway/interfaces/IHomeGateway.sol @@ -4,10 +4,9 @@ pragma solidity ^0.8.0; import "../../arbitration/IArbitrable.sol"; import "../../evidence/IMetaEvidence.sol"; +import "./IHomeGatewayBase.sol"; -interface IHomeGateway is IArbitrable, IMetaEvidence { - function chainID() external view returns (uint256); - +interface IHomeGateway is IArbitrable, IMetaEvidence, IHomeGatewayBase { function relayCreateDispute( uint256 _originalChainID, bytes32 _originalBlockHash, @@ -18,10 +17,5 @@ interface IHomeGateway is IArbitrable, IMetaEvidence { ) external payable; // For cross-chain Evidence standard - function disputeHashToHomeID(bytes32 _disputeHash) external view returns (uint256); - - function foreignChainID() external view returns (uint256); - - function foreignGateway() 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..bb11e83f2 --- /dev/null +++ b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeSender.sol"; + +interface IHomeGatewayBase { + function fastbridge() external view returns (IFastBridgeSender); + + function chainID() external view returns (uint256); + + function foreignChainID() external view returns (uint256); + + function foreignGateway() external view returns (address); +} diff --git a/contracts/src/gateway/interfaces/IHomeGatewayMock.sol b/contracts/src/gateway/interfaces/IHomeGatewayMock.sol new file mode 100644 index 000000000..16c6c151c --- /dev/null +++ b/contracts/src/gateway/interfaces/IHomeGatewayMock.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "./IHomeGatewayBase.sol"; + +interface IHomeGatewayMock is IHomeGatewayBase {} diff --git a/contracts/src/gateway/test/ForeignGatewayMock.sol b/contracts/src/gateway/test/ForeignGatewayMock.sol new file mode 100644 index 000000000..75f90cdf7 --- /dev/null +++ b/contracts/src/gateway/test/ForeignGatewayMock.sol @@ -0,0 +1,72 @@ +// 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 { + IFastBridgeReceiver public immutable fastbridge; + address public immutable override homeGateway; + uint256 public immutable override homeChainID; + uint256 public immutable override chainID; + + uint256 public messageCount; + uint256 public data; + + constructor( + IFastBridgeReceiver _fastbridge, + address _homeGateway, + uint256 _homeChainID + ) { + fastbridge = _fastbridge; + homeGateway = _homeGateway; + homeChainID = _homeChainID; + uint256 id; + assembly { + id := chainid() + } + chainID = id; + } + + modifier onlyFromFastBridge() { + require(address(fastbridge) == 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..0076d1da7 --- /dev/null +++ b/contracts/src/gateway/test/HomeGatewayMock.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/IForeignGatewayMock.sol"; +import "../interfaces/IHomeGatewayBase.sol"; + +/** + * Home Gateway + * Counterpart of `ForeignGatewayMock` + */ +contract HomeGatewayMock is IHomeGatewayBase { + IFastBridgeSender public immutable fastbridge; + address public override foreignGateway; + uint256 public immutable override foreignChainID; + uint256 public immutable override chainID; + + struct RelayedData { + uint256 arbitrationCost; + address relayer; + } + mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; + + constructor( + IFastBridgeSender _fastbridge, + address _foreignGateway, + uint256 _foreignChainID + ) { + fastbridge = _fastbridge; + foreignGateway = _foreignGateway; + foreignChainID = _foreignChainID; + uint256 id; + assembly { + id := chainid() + } + chainID = id; + } + + function sendFastMessage() external { + bytes4 methodSelector = IForeignGatewayMock.receiveMessage.selector; + bytes memory data; + + fastbridge.sendFast(foreignGateway, methodSelector, data); + } + + function sendFastMessage(uint256 _data) external { + bytes4 methodSelector = IForeignGatewayMock.receiveMessage.selector; + bytes memory data = abi.encode(_data); + + fastbridge.sendFast(foreignGateway, methodSelector, data); + } +} From a36713cc5cbefc874e8916dc20bc21e151afab8b Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Tue, 31 May 2022 18:30:47 +0100 Subject: [PATCH 07/21] feat: batched fast bridge tests --- contracts/deploy/01-foreign-chain.ts | 2 +- .../src/bridge/FastBridgeReceiverBase.sol | 18 +- contracts/src/bridge/FastBridgeSenderBase.sol | 82 ++++--- .../bridge/interfaces/IFastBridgeReceiver.sol | 4 +- .../bridge/interfaces/IFastBridgeSender.sol | 24 +- contracts/src/gateway/HomeGateway.sol | 5 +- .../src/gateway/test/HomeGatewayMock.sol | 11 +- contracts/test/integration/index.ts | 226 +++++++----------- 8 files changed, 166 insertions(+), 206 deletions(-) diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index f1b74d757..93130c624 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -72,7 +72,7 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const fastBridgeSenderAddress = getContractAddress(deployer, nonce); console.log("calculated future FastSender for nonce %d: %s", nonce, fastBridgeSenderAddress); - nonce += 5; + nonce += 4; const inboxAddress = chainId === ForeignChains.HARDHAT ? getContractAddress(deployer, nonce) : arbitrumInbox; console.log("calculated future inboxAddress for nonce %d: %s", nonce, inboxAddress); diff --git a/contracts/src/bridge/FastBridgeReceiverBase.sol b/contracts/src/bridge/FastBridgeReceiverBase.sol index 2ad7c0084..4f636871e 100644 --- a/contracts/src/bridge/FastBridgeReceiverBase.sol +++ b/contracts/src/bridge/FastBridgeReceiverBase.sol @@ -77,7 +77,7 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS /** * @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 _epoch The epoch of the claim to claim. + * @param _epoch The epoch in which the batch to claim. * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. */ function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { @@ -85,7 +85,7 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS require(_batchMerkleRoot != bytes32(0), "Invalid claim."); uint256 epoch = block.timestamp / epochPeriod; - // allow claim about previous epoch + // allow claim about current or previous epoch require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); @@ -122,8 +122,6 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS * @param _epoch The epoch of the optimistic claim. */ function verify(uint256 _epoch) public { - require(fastInbox[_epoch] == bytes32(0), "Epoch already verified."); - Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); require( @@ -145,7 +143,7 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS * @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( + function verifyAndRelay( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message, @@ -195,7 +193,7 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS require(claim.bridger != address(0), "Claim does not exist"); require(claim.honest == true, "Claim not verified."); - require(claim.depositAndRewardWithdrawn == false, "Claim deposit already withdrawn."); + require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); uint256 amount = deposit; if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt @@ -213,9 +211,9 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS function withdrawChallengeDeposit(uint256 _epoch) external override { Challenge storage challenge = challenges[_epoch]; - require(challenge.challenger != address(0), "Claim does not exist"); - require(challenge.honest == true, "Claim not verified: deposit forfeited"); - require(challenge.depositAndRewardWithdrawn == false, "Claim deposit already withdrawn."); + require(challenge.challenger != address(0), "Challenge does not exist"); + require(challenge.honest == true, "Challenge not verified."); + require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); uint256 amount = deposit + deposit / 2; challenge.depositAndRewardWithdrawn = true; @@ -249,6 +247,6 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS 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); + (success, ) = receiver.call(data); } } diff --git a/contracts/src/bridge/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol index d34297042..38236713e 100644 --- a/contracts/src/bridge/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -37,7 +37,7 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr * The bridgers need to watch for these events and relay the * batchMerkleRoot on the FastBridgeReceiver. */ - event SendBatch(uint256 indexed currentBatchID, bytes32 indexed batchMerkleRoot); + event SendBatch(uint256 indexed batchID, uint256 indexed epoch, bytes32 indexed batchMerkleRoot); /** * @dev Constructor. @@ -57,43 +57,21 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr /** * @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 _fastMessageCalldata = abi.encodeWithSelector(_functionSelector, msg.sender, _calldata); - bytes memory _fastMessage = abi.encode(_receiver, _fastMessageCalldata); - bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + function sendFast(address _receiver, bytes memory _calldata) external override { + (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); - appendMessage(fastMessageHash); // add message to merkle tree - - emit MessageReceived(currentBatchID, fastMessageHash, 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 _fastMessageCalldata = abi.encodeWithSelector(_functionSelector, msg.sender); - bytes memory _fastMessage = abi.encode(_receiver, _fastMessageCalldata); - bytes32 fastMessageHash = sha256(abi.encode(_fastMessage, batchSize)); + emit MessageReceived(currentBatchID, fastMessage, batchSize); appendMessage(fastMessageHash); // add message to merkle tree - - emit MessageReceived(currentBatchID, fastMessageHash, batchSize); } /** * Sends a batch of arbitrary message from one domain to another * via the fast bridge mechanism. */ - function sendEpoch() external { + function sendBatch() external { uint256 epoch = block.timestamp / epochPeriod; require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); require(batchSize > 0, "No messages to send."); @@ -102,8 +80,56 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr bytes32 batchMerkleRoot = getMerkleRootAndReset(); fastOutbox[epoch] = batchMerkleRoot; - emit SendBatch(currentBatchID, batchMerkleRoot); + emit SendBatch(currentBatchID, epoch, batchMerkleRoot); currentBatchID = epoch; } + + // ************************ // + // * Internal * // + // ************************ // + + function _encode(address _receiver, bytes memory _calldata) + internal + view + returns (bytes32 fastMessageHash, bytes memory fastMessage) + { + // Encode the receiver address with the function signature + arguments i.e calldata + bytes32 sender = bytes32(bytes20(msg.sender)); + bytes32 receiver = bytes32(bytes20(_receiver)); + // add sender and receiver with proper function selector formatting + // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] + assembly { + fastMessage := mload(0x40) // free memory pointer + let lengthCallData := mload(_calldata) // calldata length + let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender + let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage + mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length + mstore(add(fastMessage, 0x2c), receiver) // receiver + mstore(add(fastMessage, 0x40), 0x40) // offset + mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length + mstore( + add(fastMessage, 0x80), + and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + ) // function selector + mstore(add(fastMessage, 0x90), sender) // sender + + let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call + let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments + + // copy all arguments + for { + let j := 0x00 + } lt(j, lengthCallData) { + j := add(j, 0x20) + } { + mstore(_cursor, mload(add(_cursorCalldata, j))) + _cursor := add(_cursor, 0x20) + } + // update free pointer + mstore(0x40, _cursor) + } + // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). + fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); + } } diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 17101a730..9c612ff6f 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -17,7 +17,7 @@ interface IFastBridgeReceiver { /** * @dev The Fast Bridge participants watch for these events to decide if a challenge should be submitted. - * @param _epoch The epoch in for which the the claim was made. + * @param _epoch The epoch for which the the claim was made. * @param _batchMerkleRoot The timestamp of the claim creation. */ event ClaimReceived(uint256 _epoch, bytes32 indexed _batchMerkleRoot); @@ -52,7 +52,7 @@ interface IFastBridgeReceiver { * @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( + function verifyAndRelay( 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 7b11cf13b..b90b8fc1c 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -9,11 +9,11 @@ interface IFastBridgeSender { /** * @dev The Fast Bridge participants need to watch for these events and relay the messageHash on the FastBridgeReceiverOnEthereum. - * @param lastSentEpoch The last sent epoch. - * @param fastMessageHash The fast message data. + * @param currentBatchID Unique ID for each batch is the epoch of the last sent batch. + * @param fastMessage The fast message data. * @param nonce The message nonce. */ - event MessageReceived(uint256 lastSentEpoch, bytes32 fastMessageHash, uint256 nonce); + event MessageReceived(uint256 currentBatchID, bytes fastMessage, uint256 nonce); // ************************************* // // * Function Modifiers * // @@ -24,29 +24,15 @@ 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, - bytes4 _functionSelector, - 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; + function sendFast(address _receiver, bytes memory _calldata) external; /** * Sends a batch of arbitrary message from one domain to another * via the fast bridge mechanism. */ - function sendEpoch() external; + function sendBatch() 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/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index 431d0c8ea..5d2d3a889 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -12,7 +12,6 @@ pragma solidity ^0.8.0; import "../arbitration/IArbitrator.sol"; import "../bridge/interfaces/IFastBridgeSender.sol"; - import "./interfaces/IForeignGateway.sol"; import "./interfaces/IHomeGateway.sol"; @@ -112,9 +111,9 @@ contract HomeGateway is IHomeGateway { RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; bytes4 methodSelector = IForeignGateway.relayRule.selector; - bytes memory data = abi.encode(disputeHash, _ruling, relayedData.relayer); + bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _ruling, relayedData.relayer); - fastbridge.sendFast(foreignGateway, methodSelector, data); + fastbridge.sendFast(foreignGateway, data); } /** @dev Changes the fastBridge, useful to increase the claim deposit. diff --git a/contracts/src/gateway/test/HomeGatewayMock.sol b/contracts/src/gateway/test/HomeGatewayMock.sol index 0076d1da7..7dc12d20e 100644 --- a/contracts/src/gateway/test/HomeGatewayMock.sol +++ b/contracts/src/gateway/test/HomeGatewayMock.sol @@ -44,17 +44,10 @@ contract HomeGatewayMock is IHomeGatewayBase { chainID = id; } - function sendFastMessage() external { - bytes4 methodSelector = IForeignGatewayMock.receiveMessage.selector; - bytes memory data; - - fastbridge.sendFast(foreignGateway, methodSelector, data); - } - function sendFastMessage(uint256 _data) external { bytes4 methodSelector = IForeignGatewayMock.receiveMessage.selector; - bytes memory data = abi.encode(_data); + bytes memory data = abi.encodeWithSelector(methodSelector, _data); - fastbridge.sendFast(foreignGateway, methodSelector, data); + fastbridge.sendFast(foreignGateway, data); } } diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 4fbbc0bd9..87b5345ce 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -181,40 +181,45 @@ describe("Demo pre-alpha1", function () { 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; + expect(tx4).to.emit(fastBridgeSender, "MessageReceived"); + + const MessageReceived = fastBridgeSender.filters.MessageReceived(); + const event5 = await fastBridgeSender.queryFilter(MessageReceived); + + + const nonce = event5[0].args.nonce; + const fastMessage = event5[0].args.fastMessage; + const currentBatchID = event5[0].args.currentBatchID; + + const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); + expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); + + const SendBatch = fastBridgeSender.filters.SendBatch(); + const event5a = await fastBridgeSender.queryFilter(SendBatch); + const batchID = event5a[0].args.batchID; + const batchMerkleRoot = event5a[0].args.batchMerkleRoot; - 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]); + const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, batchMerkleRoot, { value: ONE_TENTH_ETH }); + + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(batchID, batchMerkleRoot); + + // wait for challenge period (and epoch) to pass + await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); - const tx7 = await fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData); - expect(tx7).to.emit(arbitrable, "Ruling"); + const tx7a = await fastBridgeReceiver.connect(bridger).verify(batchID); + const tx7 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); - const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(ticketID); + const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); + + expect(tx7).to.emit(arbitrable, "Ruling"); + }); it("Demo - Honest Claim - Challenged - Bridger Paid, Challenger deposit forfeited", async () => { @@ -347,69 +352,60 @@ describe("Demo pre-alpha1", function () { 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); + console.log("Executed ruling"); - expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + expect(tx4).to.emit(fastBridgeSender, "MessageReceived"); - console.log("Executed ruling"); + const MessageReceived = fastBridgeSender.filters.MessageReceived(); + const event4 = await fastBridgeSender.queryFilter(MessageReceived); - 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 nonce = event4[0].args.nonce; + const fastMessage = event4[0].args.fastMessage; + const currentBatchID = event4[0].args.currentBatchID; + + + const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); + expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); - const currentID = await fastBridgeSender.currentTicketID(); - expect(currentID).to.equal(2); + const SendBatch = fastBridgeSender.filters.SendBatch(); + const event4a = await fastBridgeSender.queryFilter(SendBatch); + const batchID = event4a[0].args.batchID; + const batchMerkleRoot = event4a[0].args.batchMerkleRoot; + + // bridger tx starts - Honest Bridger + + console.log("Executed ruling"); // 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); + const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, batchMerkleRoot, { value: ONE_TENTH_ETH }); + + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(batchID, batchMerkleRoot); // 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]); + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(batchID, { value: ONE_TENTH_ETH }); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(batchID); + + // wait for challenge period (and epoch) to pass + await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); await expect( - fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData) - ).to.be.revertedWith("Claim is challenged"); + fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce) + ).to.be.revertedWith("Invalid epoch."); - const data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], messageData); const tx7 = await fastBridgeSender .connect(bridger) - .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }); + .sendSafeFallback(batchID, { 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" + const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); + + expect(tx8).to.emit(arbitrable, "Ruling"); + + await expect(fastBridgeReceiver.withdrawChallengeDeposit(batchID)).to.be.revertedWith( + "Challenge not verified." ); }); @@ -512,85 +508,47 @@ describe("Demo pre-alpha1", function () { 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"); + const tx4 = await core.executeRuling(coreId); 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); + expect(tx4).to.emit(fastBridgeSender, "MessageReceived"); - const currentID = await fastBridgeSender.currentTicketID(); - expect(currentID).to.equal(2); + const MessageReceived = fastBridgeSender.filters.MessageReceived(); + const event4 = await fastBridgeSender.queryFilter(MessageReceived); - // 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 nonce = event4[0].args.nonce; + const fastMessage = event4[0].args.fastMessage; + const currentBatchID = event4[0].args.currentBatchID; - 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); + const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); + expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); - // 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"); + const SendBatch = fastBridgeSender.filters.SendBatch(); + const event4a = await fastBridgeSender.queryFilter(SendBatch); + const batchID = event4a[0].args.batchID; + const batchMerkleRoot = event4a[0].args.batchMerkleRoot; - await expect( - fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, fakeData) - ).to.be.revertedWith("Claim is challenged"); + // bridger tx starts - bridger creates fakeData & fakeHash for dishonest ruling - let data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], fakeData); + const fakeData = "KlerosToTheMoon"; + const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"],[fakeData])); + const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, fakeHash, { value: ONE_TENTH_ETH }); + // Challenger tx starts + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(batchID, { value: ONE_TENTH_ETH }); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(batchID); - await expect( - fastBridgeSender - .connect(bridger) - .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }) - ).to.be.revertedWith("Invalid message for ticketID."); + const tx7 = await fastBridgeSender + .connect(bridger) + .sendSafeFallback(batchID, { gasLimit: 1000000 }); + expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); - 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"); + const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); - await expect(fastBridgeReceiver.withdrawClaimDeposit(ticketID)).to.be.revertedWith( - "Claim not verified: deposit forfeited" - ); - await fastBridgeReceiver.withdrawChallengeDeposit(ticketID); + expect(tx8).to.emit(arbitrable, "Ruling"); + await fastBridgeReceiver.withdrawChallengeDeposit(batchID); }); async function mineNBlocks(n) { From d90126dddd5678184a8d0476df06397f4978f19c Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Tue, 31 May 2022 19:53:03 +0100 Subject: [PATCH 08/21] style: clear variable names --- contracts/src/bridge/FastBridgeSender.sol | 8 +++++--- contracts/src/bridge/FastBridgeSenderBase.sol | 8 ++++---- contracts/src/bridge/test/FastBridgeSenderMock.sol | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index ad85f832c..034368ab1 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -33,9 +33,11 @@ contract FastBridgeSender is FastBridgeSenderBase { /** * @dev Constructor. * @param _epochPeriod The immutable period between epochs. - * @param _safeRouter The address of the Safe Router on Ethereum. + * @param _safeBridgeReceiver The address of the Safe Receiver on Ethereum. */ - constructor(uint256 _epochPeriod, address _safeRouter) FastBridgeSenderBase(_epochPeriod, _safeRouter) {} + constructor(uint256 _epochPeriod, address _safeBridgeReceiver) + FastBridgeSenderBase(_epochPeriod, _safeBridgeReceiver) + {} /** * @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. @@ -48,7 +50,7 @@ contract FastBridgeSender is FastBridgeSenderBase { bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - _sendSafe(safeRouter, safeMessageData); + _sendSafe(safeBridgeReceiver, safeMessageData); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { diff --git a/contracts/src/bridge/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol index 38236713e..d5cc6f2cf 100644 --- a/contracts/src/bridge/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -27,7 +27,7 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. uint256 public currentBatchID; mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages - address public immutable safeRouter; + address public immutable safeBridgeReceiver; // ************************************* // // * Events * // @@ -42,11 +42,11 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr /** * @dev Constructor. * @param _epochPeriod The duration between epochs. - * @param _safeRouter The the Safe Bridge Router on Ethereum to the foreign chain. + * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. */ - constructor(uint256 _epochPeriod, address _safeRouter) { + constructor(uint256 _epochPeriod, address _safeBridgeReceiver) { epochPeriod = _epochPeriod; - safeRouter = _safeRouter; + safeBridgeReceiver = _safeBridgeReceiver; currentBatchID = block.timestamp / epochPeriod; } diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index acdb851b8..1a52b629c 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -55,7 +55,7 @@ contract FastBridgeSenderMock is FastBridgeSenderBase { bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - _sendSafe(safeRouter, safeMessageData); + _sendSafe(safeBridgeReceiver, safeMessageData); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { From 52c75ea1ec0d8aecef80fd593d3f8ab32655053f Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Tue, 31 May 2022 20:04:32 +0100 Subject: [PATCH 09/21] style: alligned statements --- contracts/deploy/02-home-chain.ts | 46 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index e5af383f2..d560dd65f 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -22,12 +22,12 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const arbSysMock = await deploy("ArbSysMock", { from: deployer, log: true }); let fastBridgeSender; - fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { - from: deployer, - contract: "FastBridgeSenderMock", - args: [epochPeriod, fastBridgeReceiver.address, arbSysMock.address], - log: true, - }); // nonce+0 + fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { + from: deployer, + contract: "FastBridgeSenderMock", + args: [epochPeriod, fastBridgeReceiver.address, arbSysMock.address], + log: true, + }); // nonce+0 const klerosCore = await deployments.get("KlerosCore"); const foreignGateway = await deployments.get("ForeignGatewayOnEthereum"); @@ -41,23 +41,23 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) 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 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, + }); }; // ---------------------------------------------------------------------------------------------- From c05b7b000d08c4a8fe52834b6b808d9096a69781 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 1 Jun 2022 17:15:57 +0100 Subject: [PATCH 10/21] fix: no inheritance --- .../src/bridge/FastBridgeReceiverBase.sol | 47 ++++++++- contracts/src/bridge/FastBridgeSenderBase.sol | 98 ++++++++++++++++++- 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverBase.sol b/contracts/src/bridge/FastBridgeReceiverBase.sol index 4f636871e..d7b80d44d 100644 --- a/contracts/src/bridge/FastBridgeReceiverBase.sol +++ b/contracts/src/bridge/FastBridgeReceiverBase.sol @@ -12,13 +12,12 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./merkle/MerkleProof.sol"; /** * Fast Receiver Base * Counterpart of `FastSenderBase` */ -abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, ISafeBridgeReceiver { +abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, ISafeBridgeReceiver { // ************************************* // // * Enums / Structs * // // ************************************* // @@ -222,6 +221,50 @@ abstract contract FastBridgeReceiverBase is MerkleProof, IFastBridgeReceiver, IS // Checks-Effects-Interaction } + // ********************************** // + // * 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) private 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; + } + // ************************************* // // * Public Views * // // ************************************* // diff --git a/contracts/src/bridge/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol index d5cc6f2cf..3baef4961 100644 --- a/contracts/src/bridge/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -19,7 +19,7 @@ import "./interfaces/ISafeBridgeReceiver.sol"; * Fast Bridge Sender Base * Counterpart of `FastReceiverBase` */ -abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBridgeSender { +abstract contract FastBridgeSenderBase is IFastBridgeSender, ISafeBridgeSender { // ************************************* // // * Storage * // // ************************************* // @@ -29,6 +29,10 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages address public immutable safeBridgeReceiver; + // merkle tree representation of a batch of messages + // supports 2^64 messages. + bytes32[64] public batch; + uint256 public batchSize; // ************************************* // // * Events * // // ************************************* // @@ -37,7 +41,7 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr * The bridgers need to watch for these events and relay the * batchMerkleRoot on the FastBridgeReceiver. */ - event SendBatch(uint256 indexed batchID, uint256 indexed epoch, bytes32 indexed batchMerkleRoot); + event SendBatch(uint256 indexed batchID, uint256 epoch, bytes32 batchMerkleRoot); /** * @dev Constructor. @@ -62,7 +66,7 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr function sendFast(address _receiver, bytes memory _calldata) external override { (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); - emit MessageReceived(currentBatchID, fastMessage, batchSize); + emit MessageReceived(currentBatchID, batchSize, fastMessage, fastMessageHash); appendMessage(fastMessageHash); // add message to merkle tree } @@ -132,4 +136,92 @@ abstract contract FastBridgeSenderBase is MerkleTree, IFastBridgeSender, ISafeBr // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); } + + // ********************************* // + // * Merkle Tree * // + // ********************************* // + + /** @dev Append data into merkle tree. + * `O(log(n))` where + * `n` is the number of leaves. + * Note: Although each insertion is O(log(n)), + * Complexity of n insertions is O(n). + * @param leaf The leaf (already hashed) to insert in the merkle tree. + */ + 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; + 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 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() internal 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; + } } From 46e0fa5b8c0d46cd23f7ae6e8c11163fa5d4fff4 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Wed, 1 Jun 2022 17:42:51 +0100 Subject: [PATCH 11/21] fix(tests): updated events --- contracts/src/bridge/FastBridgeSenderBase.sol | 3 +-- contracts/src/bridge/interfaces/IFastBridgeSender.sol | 5 ++--- contracts/test/integration/index.ts | 10 +++------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/contracts/src/bridge/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol index 3baef4961..ec0292963 100644 --- a/contracts/src/bridge/FastBridgeSenderBase.sol +++ b/contracts/src/bridge/FastBridgeSenderBase.sol @@ -10,7 +10,6 @@ pragma solidity ^0.8.0; -import "./merkle/MerkleTree.sol"; import "./interfaces/IFastBridgeSender.sol"; import "./interfaces/ISafeBridgeSender.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; @@ -66,7 +65,7 @@ abstract contract FastBridgeSenderBase is IFastBridgeSender, ISafeBridgeSender { function sendFast(address _receiver, bytes memory _calldata) external override { (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); - emit MessageReceived(currentBatchID, batchSize, fastMessage, fastMessageHash); + emit MessageReceived(fastMessage, fastMessageHash); appendMessage(fastMessageHash); // add message to merkle tree } diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index b90b8fc1c..46bd8c4b3 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -9,11 +9,10 @@ interface IFastBridgeSender { /** * @dev The Fast Bridge participants need to watch for these events and relay the messageHash on the FastBridgeReceiverOnEthereum. - * @param currentBatchID Unique ID for each batch is the epoch of the last sent batch. * @param fastMessage The fast message data. - * @param nonce The message nonce. + * @param fastMessage The hash of the fast message data encoded with the nonce. */ - event MessageReceived(uint256 currentBatchID, bytes fastMessage, uint256 nonce); + event MessageReceived(bytes fastMessage, bytes32 fastMessageHash); // ************************************* // // * Function Modifiers * // diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 87b5345ce..1517cdb28 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -192,9 +192,8 @@ describe("Demo pre-alpha1", function () { const event5 = await fastBridgeSender.queryFilter(MessageReceived); - const nonce = event5[0].args.nonce; + const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event5[0].args.fastMessage; - const currentBatchID = event5[0].args.currentBatchID; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); @@ -219,7 +218,6 @@ describe("Demo pre-alpha1", function () { const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); expect(tx7).to.emit(arbitrable, "Ruling"); - }); it("Demo - Honest Claim - Challenged - Bridger Paid, Challenger deposit forfeited", async () => { @@ -361,9 +359,8 @@ describe("Demo pre-alpha1", function () { const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event4 = await fastBridgeSender.queryFilter(MessageReceived); - const nonce = event4[0].args.nonce; + const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event4[0].args.fastMessage; - const currentBatchID = event4[0].args.currentBatchID; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); @@ -519,9 +516,8 @@ describe("Demo pre-alpha1", function () { const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event4 = await fastBridgeSender.queryFilter(MessageReceived); - const nonce = event4[0].args.nonce; + const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event4[0].args.fastMessage; - const currentBatchID = event4[0].args.currentBatchID; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); From 823861fa4a99c54eb8c5e9c5ceaa4afffe13a696 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Mon, 6 Jun 2022 17:58:44 +0100 Subject: [PATCH 12/21] feat(bridge): removed inheritance --- .../src/bridge/FastBridgeReceiverBase.sol | 295 ----------------- .../bridge/FastBridgeReceiverOnEthereum.sol | 306 +++++++++++++++++- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 300 ++++++++++++++++- contracts/src/bridge/FastBridgeSender.sol | 232 ++++++++++++- contracts/src/bridge/FastBridgeSenderBase.sol | 226 ------------- .../src/bridge/test/FastBridgeSenderMock.sol | 244 ++++++++++++-- 6 files changed, 1016 insertions(+), 587 deletions(-) delete mode 100644 contracts/src/bridge/FastBridgeReceiverBase.sol delete mode 100644 contracts/src/bridge/FastBridgeSenderBase.sol diff --git a/contracts/src/bridge/FastBridgeReceiverBase.sol b/contracts/src/bridge/FastBridgeReceiverBase.sol deleted file mode 100644 index d7b80d44d..000000000 --- a/contracts/src/bridge/FastBridgeReceiverBase.sol +++ /dev/null @@ -1,295 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/IFastBridgeReceiver.sol"; -import "./interfaces/ISafeBridgeReceiver.sol"; - -/** - * Fast Receiver Base - * Counterpart of `FastSenderBase` - */ -abstract contract FastBridgeReceiverBase is IFastBridgeReceiver, ISafeBridgeReceiver { - // ************************************* // - // * Enums / Structs * // - // ************************************* // - - struct Claim { - bytes32 batchMerkleRoot; - address bridger; - uint32 timestamp; - bool honest; - bool depositAndRewardWithdrawn; - } - - struct Challenge { - address challenger; - bool honest; - bool depositAndRewardWithdrawn; - } - - // ************************************* // - // * Storage * // - // ************************************* // - - uint256 public immutable deposit; // The deposit required to submit a claim or challenge - uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. - uint256 public immutable override challengePeriod; // 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 - 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 each epoch. - * @param _challengePeriod The duration of the period allowing to challenge a claim. - * @param _safeRouter The address of the Safe Router on Ethereum. - */ - constructor( - uint256 _deposit, - uint256 _epochPeriod, - uint256 _challengePeriod, - address _safeRouter - ) { - deposit = _deposit; - epochPeriod = _epochPeriod; - challengePeriod = _challengePeriod; - safeRouter = _safeRouter; - } - - // ************************************* // - // * 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 _epoch The epoch in which the batch to claim. - * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. - */ - function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { - require(msg.value >= deposit, "Insufficient claim deposit."); - require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - - uint256 epoch = block.timestamp / epochPeriod; - // allow claim about current or previous epoch - require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); - - require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); - - claims[_epoch] = Claim({ - batchMerkleRoot: _batchMerkleRoot, - bridger: msg.sender, - timestamp: uint32(block.timestamp), - honest: false, - depositAndRewardWithdrawn: false - }); - - emit ClaimReceived(_epoch, _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. - * @param _epoch The epoch of the claim to challenge. - */ - function challenge(uint256 _epoch) external payable override { - require(msg.value >= deposit, "Not enough claim deposit"); - - // can only challenge the only active claim, about the previous epoch - require(claims[_epoch].bridger != address(0), "No claim to challenge."); - require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); - - challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); - - emit ClaimChallenged(_epoch); - } - - /** - * @dev Resolves the optimistic claim for '_epoch'. - * @param _epoch The epoch of the optimistic claim. - */ - function verify(uint256 _epoch) public { - Claim storage claim = claims[_epoch]; - require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); - require( - block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, - "Challenge period has not yet elapsed." - ); - - 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 verifyAndRelay( - 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.encode(_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 onlyFromSafeBridge { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { - claims[_epoch].honest = true; - } else { - challenges[_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 storage claim = claims[_epoch]; - - require(claim.bridger != address(0), "Claim does not exist"); - require(claim.honest == true, "Claim not verified."); - require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); - - uint256 amount = deposit; - if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt - - claim.depositAndRewardWithdrawn = true; - - 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 storage challenge = challenges[_epoch]; - - require(challenge.challenger != address(0), "Challenge does not exist"); - require(challenge.honest == true, "Challenge not verified."); - require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); - - uint256 amount = deposit + deposit / 2; - challenge.depositAndRewardWithdrawn = true; - - payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. - // Checks-Effects-Interaction - } - - // ********************************** // - // * 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) private 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; - } - - // ************************************* // - // * Public Views * // - // ************************************* // - - /** - * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. - */ - function claimChallengePeriod(uint256 _epoch) 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 - start = claims[_epoch].timestamp; - end = start + challengePeriod; - return (start, end); - } - - // ************************ // - // * 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, ) = receiver.call(data); - } -} diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index ae6ee8051..5cc322833 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -10,45 +10,317 @@ pragma solidity ^0.8.0; -import "./FastBridgeReceiverBase.sol"; -import "./interfaces/arbitrum/IInbox.sol"; -import "./interfaces/arbitrum/IOutbox.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/arbitrum/IInbox.sol"; // Ethereum Receiver Specific +import "./interfaces/arbitrum/IOutbox.sol"; // Ethereum Receiver Specific /** - * Fast Bridge Receiver on Ethereum from Arbitrum - * Counterpart of `FastBridgeSenderToEthereum` + * Fast Receiver On Ethereum + * Counterpart of `FastSenderFromArbitrum` */ -contract FastBridgeReceiverOnEthereum is FastBridgeReceiverBase { +contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceiver { + // **************************************** // + // * * // + // * Ethereum Receiver Specific * // + // * * // + // **************************************** // + // ************************************* // // * Storage * // // ************************************* // IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); + return outbox.l2ToL1Sender() == safeBridgeSender; + } + /** * @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 _epochPeriod The duration of each epoch. * @param _challengePeriod The duration of the period allowing to challenge a claim. - * @param _safeRouter The address of the Safe Router on Ethereum. - * @param _inbox The address of the inbox contract on Ethereum. + * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. + * @param _inbox Ethereum receiver specific: The address of the inbox contract on Ethereum. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _challengePeriod, - address _safeRouter, - address _inbox - ) FastBridgeReceiverBase(_deposit, _epochPeriod, _challengePeriod, _safeRouter) { - inbox = IInbox(_inbox); + address _safeBridgeSender, + address _inbox // Ethereum receiver specific + ) { + deposit = _deposit; + epochPeriod = _epochPeriod; + challengePeriod = _challengePeriod; + safeBridgeSender = _safeBridgeSender; + inbox = IInbox(_inbox); // Ethereum receiver specific } + // ************************************** // + // * * // + // * General Receiver * // + // * * // + // ************************************** // + // ************************************* // - // * Views * // + // * Enums / Structs * // // ************************************* // - function isSentBySafeBridge() internal view override returns (bool) { - IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); - return outbox.l2ToL1Sender() == safeRouter; + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + uint32 timestamp; + bool honest; + bool depositAndRewardWithdrawn; + } + + struct Challenge { + address challenger; + bool honest; + bool depositAndRewardWithdrawn; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public immutable override challengePeriod; // Epochs mark the period between potential batches of messages. + address public immutable safeBridgeSender; // The address of the Safe Bridge Sender 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 + mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + + // ************************************* // + // * 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 _epoch The epoch in which the batch to claim. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epoch = block.timestamp / epochPeriod; + // allow claim about current or previous epoch + require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); + + require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[_epoch] = Claim({ + batchMerkleRoot: _batchMerkleRoot, + bridger: msg.sender, + timestamp: uint32(block.timestamp), + honest: false, + depositAndRewardWithdrawn: false + }); + + emit ClaimReceived(_epoch, _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. + * @param _epoch The epoch of the claim to challenge. + */ + function challenge(uint256 _epoch) external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + require(claims[_epoch].bridger != address(0), "No claim to challenge."); + require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); + + challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); + + emit ClaimChallenged(_epoch); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require( + block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, + "Challenge period has not yet elapsed." + ); + + 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 verifyAndRelay( + 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.encode(_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 onlyFromSafeBridge { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + challenges[_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 storage claim = claims[_epoch]; + + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); + + uint256 amount = deposit; + if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + + claim.depositAndRewardWithdrawn = true; + + 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 storage challenge = challenges[_epoch]; + + require(challenge.challenger != address(0), "Challenge does not exist"); + require(challenge.honest == true, "Challenge not verified."); + require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); + + uint256 amount = deposit + deposit / 2; + challenge.depositAndRewardWithdrawn = true; + + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + // ********************************** // + // * 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) private 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; + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function claimChallengePeriod(uint256 _epoch) 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 + start = claims[_epoch].timestamp; + end = start + challengePeriod; + return (start, end); + } + + // ************************ // + // * 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, ) = receiver.call(data); } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index 3eabeea2a..9010321c0 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -10,43 +10,315 @@ pragma solidity ^0.8.0; -import "./FastBridgeReceiverBase.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; +import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/gnosis-chain/IAMB.sol"; // Gnosis Receiver Specific /** - * Fast Bridge Receiver on Ethereum from Arbitrum - * Counterpart of `FastBridgeSenderToGnosis` + * Fast Receiver On Gnosis + * Counterpart of `FastSenderFromArbitrum` */ -contract FastBridgeReceiverOnGnosis is FastBridgeReceiverBase { +contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver { + // **************************************** // + // * * // + // * Gnosis Receiver Specific * // + // * * // + // **************************************** // + // ************************************* // // * Storage * // // ************************************* // IAMB public immutable amb; // The address of the AMB contract on GC. + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeSender); + } + + // ************************************** // + // * * // + // * General Receiver * // + // * * // + // ************************************** // + + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + uint32 timestamp; + bool honest; + bool depositAndRewardWithdrawn; + } + + struct Challenge { + address challenger; + bool honest; + bool depositAndRewardWithdrawn; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public immutable override challengePeriod; // Epochs mark the period between potential batches of messages. + address public immutable safeBridgeSender; // The address of the Safe Bridge Sender 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 + 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. + * @param _epochPeriod The duration of each epoch. * @param _challengePeriod The duration of the period allowing to challenge a claim. - * @param _safeRouter The address of the Safe Bridge Router on Ethereum. + * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. * @param _amb The the AMB contract on Gnosis Chain. */ constructor( uint256 _deposit, uint256 _epochPeriod, uint256 _challengePeriod, - address _safeRouter, - address _amb - ) FastBridgeReceiverBase(_deposit, _epochPeriod, _challengePeriod, _safeRouter) { - amb = IAMB(_amb); + address _safeBridgeSender, // Gnosis receiver specific + address _amb // Gnosis receiver specific + ) { + deposit = _deposit; + epochPeriod = _epochPeriod; + challengePeriod = _challengePeriod; + safeBridgeSender = _safeBridgeSender; + amb = IAMB(_amb); // Gnosis receiver specific } // ************************************* // - // * Views * // + // * State Modifiers * // // ************************************* // - function isSentBySafeBridge() internal view override returns (bool) { - return (msg.sender == address(amb)) && (amb.messageSender() == safeRouter); + /** + * @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 _epoch The epoch in which the batch to claim. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epoch = block.timestamp / epochPeriod; + // allow claim about current or previous epoch + require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); + + require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[_epoch] = Claim({ + batchMerkleRoot: _batchMerkleRoot, + bridger: msg.sender, + timestamp: uint32(block.timestamp), + honest: false, + depositAndRewardWithdrawn: false + }); + + emit ClaimReceived(_epoch, _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. + * @param _epoch The epoch of the claim to challenge. + */ + function challenge(uint256 _epoch) external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + require(claims[_epoch].bridger != address(0), "No claim to challenge."); + require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); + + challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); + + emit ClaimChallenged(_epoch); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require( + block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, + "Challenge period has not yet elapsed." + ); + + 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 verifyAndRelay( + 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.encode(_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 onlyFromSafeBridge { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + challenges[_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 storage claim = claims[_epoch]; + + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); + + uint256 amount = deposit; + if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + + claim.depositAndRewardWithdrawn = true; + + 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 storage challenge = challenges[_epoch]; + + require(challenge.challenger != address(0), "Challenge does not exist"); + require(challenge.honest == true, "Challenge not verified."); + require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); + + uint256 amount = deposit + deposit / 2; + challenge.depositAndRewardWithdrawn = true; + + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + // ********************************** // + // * 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) private 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; + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function claimChallengePeriod(uint256 _epoch) 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 + start = claims[_epoch].timestamp; + end = start + challengePeriod; + return (start, end); + } + + // ************************ // + // * 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, ) = receiver.call(data); } } diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 034368ab1..66ba284aa 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,14 +10,22 @@ pragma solidity ^0.8.0; -import "./FastBridgeSenderBase.sol"; -import "./interfaces/arbitrum/IArbSys.sol"; +import "./interfaces/IFastBridgeSender.sol"; +import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/arbitrum/IArbSys.sol"; // Arbiturm sender specific /** - * Fast Bridge Sender to Ethereum from Arbitrum - * Counterpart of `FastBridgeReceiverOnEthereum` + * Fast Bridge Sender + * Counterpart of `FastReceiver` */ -contract FastBridgeSender is FastBridgeSenderBase { +contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { + // **************************************** // + // * * // + // * Arbitrum Sender Specific * // + // * * // + // **************************************** // + // ************************************* // // * Events * // // ************************************* // @@ -30,15 +38,6 @@ contract FastBridgeSender is FastBridgeSenderBase { IArbSys public constant ARB_SYS = IArbSys(address(100)); - /** - * @dev Constructor. - * @param _epochPeriod The immutable period between epochs. - * @param _safeBridgeReceiver The address of the Safe Receiver on Ethereum. - */ - constructor(uint256 _epochPeriod, address _safeBridgeReceiver) - FastBridgeSenderBase(_epochPeriod, _safeBridgeReceiver) - {} - /** * @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 @@ -59,4 +58,207 @@ contract FastBridgeSender is FastBridgeSenderBase { emit L2ToL1TxCreated(txID); return bytes32(txID); } + + /** + * @dev Constructor. + * @param _epochPeriod The duration between epochs. + * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. + */ + constructor(uint256 _epochPeriod, address _safeBridgeReceiver) { + epochPeriod = _epochPeriod; + safeBridgeReceiver = _safeBridgeReceiver; + currentBatchID = block.timestamp / _epochPeriod; + } + + // ************************************** // + // * * // + // * General Sender * // + // * * // + // ************************************** // + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public currentBatchID; + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + address public immutable safeBridgeReceiver; + + // merkle tree representation of a batch of messages + // supports 2^64 messages. + bytes32[64] public batch; + uint256 public batchSize; + // ************************************* // + // * Events * // + // ************************************* // + + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiver. + */ + event SendBatch(uint256 indexed batchID, uint256 batchSize, uint256 epoch, bytes32 batchMerkleRoot); + + // ************************************* // + // * 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 _calldata The receiving domain encoded message data / function arguments. + */ + function sendFast(address _receiver, bytes memory _calldata) external override { + (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); + + emit MessageReceived(fastMessage, fastMessageHash); + + appendMessage(fastMessageHash); // add message to merkle tree + } + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendBatch() external override { + uint256 epoch = block.timestamp / epochPeriod; + require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); + require(batchSize > 0, "No messages to send."); + + // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRoot(); + fastOutbox[epoch] = batchMerkleRoot; + emit SendBatch(currentBatchID, batchSize, epoch, batchMerkleRoot); + + // reset + batchSize = 0; + currentBatchID = epoch; + } + + // ************************ // + // * Internal * // + // ************************ // + + function _encode(address _receiver, bytes memory _calldata) + internal + view + returns (bytes32 fastMessageHash, bytes memory fastMessage) + { + // Encode the receiver address with the function signature + arguments i.e calldata + bytes32 sender = bytes32(bytes20(msg.sender)); + bytes32 receiver = bytes32(bytes20(_receiver)); + // add sender and receiver with proper function selector formatting + // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] + assembly { + fastMessage := mload(0x40) // free memory pointer + let lengthCallData := mload(_calldata) // calldata length + let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender + let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage + mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length + mstore(add(fastMessage, 0x2c), receiver) // receiver + mstore(add(fastMessage, 0x40), 0x40) // offset + mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length + mstore( + add(fastMessage, 0x80), + and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + ) // function selector + mstore(add(fastMessage, 0x90), sender) // sender + + let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call + let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments + + // copy all arguments + for { + let j := 0x00 + } lt(j, lengthCallData) { + j := add(j, 0x20) + } { + mstore(_cursor, mload(add(_cursorCalldata, j))) + _cursor := add(_cursor, 0x20) + } + // update free pointer + mstore(0x40, _cursor) + } + // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). + fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); + } + + // ********************************* // + // * Merkle Tree * // + // ********************************* // + + /** @dev Append data into merkle tree. + * `O(log(n))` where + * `n` is the number of leaves. + * Note: Although each insertion is O(log(n)), + * Complexity of n insertions is O(n). + * @param leaf The leaf (already hashed) to insert in the merkle tree. + */ + 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; + 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 Gets the current merkle root. + * `O(log(n))` where + * `n` is the number of leaves. + */ + function getMerkleRoot() internal 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/FastBridgeSenderBase.sol b/contracts/src/bridge/FastBridgeSenderBase.sol deleted file mode 100644 index ec0292963..000000000 --- a/contracts/src/bridge/FastBridgeSenderBase.sol +++ /dev/null @@ -1,226 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "./interfaces/IFastBridgeSender.sol"; -import "./interfaces/ISafeBridgeSender.sol"; -import "./interfaces/ISafeBridgeReceiver.sol"; - -/** - * Fast Bridge Sender Base - * Counterpart of `FastReceiverBase` - */ -abstract contract FastBridgeSenderBase is IFastBridgeSender, ISafeBridgeSender { - // ************************************* // - // * Storage * // - // ************************************* // - - uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. - uint256 public currentBatchID; - mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages - address public immutable safeBridgeReceiver; - - // merkle tree representation of a batch of messages - // supports 2^64 messages. - bytes32[64] public batch; - uint256 public batchSize; - // ************************************* // - // * Events * // - // ************************************* // - - /** - * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiver. - */ - event SendBatch(uint256 indexed batchID, uint256 epoch, bytes32 batchMerkleRoot); - - /** - * @dev Constructor. - * @param _epochPeriod The duration between epochs. - * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. - */ - constructor(uint256 _epochPeriod, address _safeBridgeReceiver) { - epochPeriod = _epochPeriod; - safeBridgeReceiver = _safeBridgeReceiver; - currentBatchID = block.timestamp / epochPeriod; - } - - // ************************************* // - // * 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 _calldata The receiving domain encoded message data / function arguments. - */ - function sendFast(address _receiver, bytes memory _calldata) external override { - (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); - - emit MessageReceived(fastMessage, fastMessageHash); - - appendMessage(fastMessageHash); // add message to merkle tree - } - - /** - * Sends a batch of arbitrary message from one domain to another - * via the fast bridge mechanism. - */ - function sendBatch() external { - uint256 epoch = block.timestamp / epochPeriod; - require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); - require(batchSize > 0, "No messages to send."); - - // set merkle root in outbox and reset merkle tree - bytes32 batchMerkleRoot = getMerkleRootAndReset(); - fastOutbox[epoch] = batchMerkleRoot; - - emit SendBatch(currentBatchID, epoch, batchMerkleRoot); - - currentBatchID = epoch; - } - - // ************************ // - // * Internal * // - // ************************ // - - function _encode(address _receiver, bytes memory _calldata) - internal - view - returns (bytes32 fastMessageHash, bytes memory fastMessage) - { - // Encode the receiver address with the function signature + arguments i.e calldata - bytes32 sender = bytes32(bytes20(msg.sender)); - bytes32 receiver = bytes32(bytes20(_receiver)); - // add sender and receiver with proper function selector formatting - // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] - assembly { - fastMessage := mload(0x40) // free memory pointer - let lengthCallData := mload(_calldata) // calldata length - let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender - let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage - mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length - mstore(add(fastMessage, 0x2c), receiver) // receiver - mstore(add(fastMessage, 0x40), 0x40) // offset - mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length - mstore( - add(fastMessage, 0x80), - and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) - ) // function selector - mstore(add(fastMessage, 0x90), sender) // sender - - let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call - let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments - - // copy all arguments - for { - let j := 0x00 - } lt(j, lengthCallData) { - j := add(j, 0x20) - } { - mstore(_cursor, mload(add(_cursorCalldata, j))) - _cursor := add(_cursor, 0x20) - } - // update free pointer - mstore(0x40, _cursor) - } - // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). - fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); - } - - // ********************************* // - // * Merkle Tree * // - // ********************************* // - - /** @dev Append data into merkle tree. - * `O(log(n))` where - * `n` is the number of leaves. - * Note: Although each insertion is O(log(n)), - * Complexity of n insertions is O(n). - * @param leaf The leaf (already hashed) to insert in the merkle tree. - */ - 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; - 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 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() internal 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/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index 1a52b629c..a1f2d6e83 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,14 +10,22 @@ pragma solidity ^0.8.0; -import "../FastBridgeSenderBase.sol"; -import "../interfaces/arbitrum/IArbSys.sol"; +import "../interfaces/IFastBridgeSender.sol"; +import "../interfaces/ISafeBridgeSender.sol"; +import "../interfaces/ISafeBridgeReceiver.sol"; +import "../interfaces/arbitrum/IArbSys.sol"; // Arbiturm sender specific /** - * Fast Sender from Arbitrum Mock - * Counterpart of `FastReceiverOnEthereum` + * Fast Bridge Sender + * Counterpart of `FastReceiver` */ -contract FastBridgeSenderMock is FastBridgeSenderBase { +contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { + // **************************************** // + // * * // + // * Arbitrum Sender Specific * // + // * * // + // **************************************** // + // ************************************* // // * Events * // // ************************************* // @@ -28,22 +36,9 @@ contract FastBridgeSenderMock is FastBridgeSenderBase { // * Storage * // // ************************************* // + IArbSys public constant ARB_SYS = IArbSys(address(100)); IArbSys public immutable arbsys; - /** - * @dev Constructor. - * @param _epochPeriod The immutable period between epochs. - * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. - * @param _arbsys The address of the mock ArbSys contract. - */ - constructor( - uint256 _epochPeriod, - address _fastBridgeReceiver, - address _arbsys - ) FastBridgeSenderBase(_epochPeriod, _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 @@ -64,4 +59,213 @@ contract FastBridgeSenderMock is FastBridgeSenderBase { emit L2ToL1TxCreated(txID); return bytes32(txID); } + + /** + * @dev Constructor. + * @param _epochPeriod The duration between epochs. + * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. + * @param _arbsys The address of the mock ArbSys contract. + */ + constructor( + uint256 _epochPeriod, + address _safeBridgeReceiver, + address _arbsys + ) { + epochPeriod = _epochPeriod; + safeBridgeReceiver = _safeBridgeReceiver; + currentBatchID = block.timestamp / _epochPeriod; + arbsys = IArbSys(address(_arbsys)); + } + + // ************************************** // + // * * // + // * General Sender * // + // * * // + // ************************************** // + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public currentBatchID; + mapping(uint256 => bytes32) public fastOutbox; // epoch count => merkle root of batched messages + address public immutable safeBridgeReceiver; + + // merkle tree representation of a batch of messages + // supports 2^64 messages. + bytes32[64] public batch; + uint256 public batchSize; + // ************************************* // + // * Events * // + // ************************************* // + + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiver. + */ + event SendBatch(uint256 indexed batchID, uint256 batchSize, uint256 epoch, bytes32 batchMerkleRoot); + + // ************************************* // + // * 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 _calldata The receiving domain encoded message data / function arguments. + */ + function sendFast(address _receiver, bytes memory _calldata) external override { + (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); + + emit MessageReceived(fastMessage, fastMessageHash); + + appendMessage(fastMessageHash); // add message to merkle tree + } + + /** + * Sends a batch of arbitrary message from one domain to another + * via the fast bridge mechanism. + */ + function sendBatch() external override { + uint256 epoch = block.timestamp / epochPeriod; + require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); + require(batchSize > 0, "No messages to send."); + + // set merkle root in outbox and reset merkle tree + bytes32 batchMerkleRoot = getMerkleRoot(); + fastOutbox[epoch] = batchMerkleRoot; + emit SendBatch(currentBatchID, batchSize, epoch, batchMerkleRoot); + + // reset + batchSize = 0; + currentBatchID = epoch; + } + + // ************************ // + // * Internal * // + // ************************ // + + function _encode(address _receiver, bytes memory _calldata) + internal + view + returns (bytes32 fastMessageHash, bytes memory fastMessage) + { + // Encode the receiver address with the function signature + arguments i.e calldata + bytes32 sender = bytes32(bytes20(msg.sender)); + bytes32 receiver = bytes32(bytes20(_receiver)); + // add sender and receiver with proper function selector formatting + // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] + assembly { + fastMessage := mload(0x40) // free memory pointer + let lengthCallData := mload(_calldata) // calldata length + let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender + let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage + mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length + mstore(add(fastMessage, 0x2c), receiver) // receiver + mstore(add(fastMessage, 0x40), 0x40) // offset + mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length + mstore( + add(fastMessage, 0x80), + and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) + ) // function selector + mstore(add(fastMessage, 0x90), sender) // sender + + let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call + let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments + + // copy all arguments + for { + let j := 0x00 + } lt(j, lengthCallData) { + j := add(j, 0x20) + } { + mstore(_cursor, mload(add(_cursorCalldata, j))) + _cursor := add(_cursor, 0x20) + } + // update free pointer + mstore(0x40, _cursor) + } + // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). + fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); + } + + // ********************************* // + // * Merkle Tree * // + // ********************************* // + + /** @dev Append data into merkle tree. + * `O(log(n))` where + * `n` is the number of leaves. + * Note: Although each insertion is O(log(n)), + * Complexity of n insertions is O(n). + * @param leaf The leaf (already hashed) to insert in the merkle tree. + */ + 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; + 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 Gets the current merkle root. + * `O(log(n))` where + * `n` is the number of leaves. + */ + function getMerkleRoot() internal 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; + } } From 6905cb950a256e00d83183ae95b058c13d16f761 Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Thu, 9 Jun 2022 16:25:42 +0100 Subject: [PATCH 13/21] feat(bridge): updated message encoding --- .../bridge/FastBridgeReceiverOnEthereum.sol | 27 ++- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 73 ++++---- contracts/src/bridge/FastBridgeSender.sol | 157 +++++++++--------- .../bridge/interfaces/IFastBridgeReceiver.sol | 4 +- .../bridge/interfaces/IFastBridgeSender.sol | 7 + .../src/bridge/test/FastBridgeSenderMock.sol | 28 ++-- contracts/test/integration/index.ts | 17 +- 7 files changed, 156 insertions(+), 157 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 5cc322833..699da4172 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -171,29 +171,20 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive * @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 _epoch, bytes32[] calldata _proof, - bytes calldata _message, - uint256 _nonce + bytes calldata _message ) 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.encode(_message, _nonce)); + bytes32 messageHash = sha256(_message); require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); - require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction + require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction } /** @@ -318,9 +309,17 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive // * Internal * // // ************************ // - function _relay(bytes calldata _messageData) internal returns (bool success) { + function _checkReplayAndRelay(uint256 _epoch, 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)); + (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); + + 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); + (success, ) = receiver.call(data); } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index 9010321c0..848cb077e 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -21,7 +21,7 @@ import "./interfaces/gnosis-chain/IAMB.sol"; // Gnosis Receiver Specific contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver { // **************************************** // // * * // - // * Gnosis Receiver Specific * // + // * Gnosis Receiver Specific * // // * * // // **************************************** // @@ -39,6 +39,28 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver return (msg.sender == address(amb)) && (amb.messageSender() == safeBridgeSender); } + /** + * @dev Constructor. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of each epoch. + * @param _challengePeriod The duration of the period allowing to challenge a claim. + * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. + * @param _amb The the AMB contract on Gnosis Chain. + */ + constructor( + uint256 _deposit, + uint256 _epochPeriod, + uint256 _challengePeriod, + address _safeBridgeSender, // Gnosis receiver specific + address _amb // Gnosis receiver specific + ) { + deposit = _deposit; + epochPeriod = _epochPeriod; + challengePeriod = _challengePeriod; + safeBridgeSender = _safeBridgeSender; + amb = IAMB(_amb); // Gnosis receiver specific + } + // ************************************** // // * * // // * General Receiver * // @@ -77,28 +99,6 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver 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 each epoch. - * @param _challengePeriod The duration of the period allowing to challenge a claim. - * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. - * @param _amb The the AMB contract on Gnosis Chain. - */ - constructor( - uint256 _deposit, - uint256 _epochPeriod, - uint256 _challengePeriod, - address _safeBridgeSender, // Gnosis receiver specific - address _amb // Gnosis receiver specific - ) { - deposit = _deposit; - epochPeriod = _epochPeriod; - challengePeriod = _challengePeriod; - safeBridgeSender = _safeBridgeSender; - amb = IAMB(_amb); // Gnosis receiver specific - } - // ************************************* // // * State Modifiers * // // ************************************* // @@ -169,29 +169,20 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver * @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 _epoch, bytes32[] calldata _proof, - bytes calldata _message, - uint256 _nonce + bytes calldata _message ) 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.encode(_message, _nonce)); + bytes32 messageHash = sha256(_message); require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); - require(_relay(_message), "Failed to call contract"); // Checks-Effects-Interaction + require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction } /** @@ -316,9 +307,17 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver // * Internal * // // ************************ // - function _relay(bytes calldata _messageData) internal returns (bool success) { + function _checkReplayAndRelay(uint256 _epoch, 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)); + (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); + + 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); + (success, ) = receiver.call(data); } } diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 66ba284aa..94671bfb2 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -26,12 +26,6 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // * * // // **************************************** // - // ************************************* // - // * Events * // - // ************************************* // - - event L2ToL1TxCreated(uint256 indexed txID); - // ************************************* // // * Storage * // // ************************************* // @@ -43,19 +37,20 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { * @param _epoch The blocknumber of the batch */ function sendSafeFallback(uint256 _epoch) external payable override { + require(_epoch <= currentBatchID, "Invalid epoch."); bytes32 batchMerkleRoot = fastOutbox[_epoch]; // Safe Bridge message envelope bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - _sendSafe(safeBridgeReceiver, safeMessageData); + bytes32 txID = _sendSafe(safeBridgeReceiver, safeMessageData); + emit SentSafe(_epoch, txID); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); - emit L2ToL1TxCreated(txID); return bytes32(txID); } @@ -67,7 +62,9 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { constructor(uint256 _epochPeriod, address _safeBridgeReceiver) { epochPeriod = _epochPeriod; safeBridgeReceiver = _safeBridgeReceiver; - currentBatchID = block.timestamp / _epochPeriod; + unchecked { + currentBatchID = block.timestamp / _epochPeriod - 1; + } } // ************************************** // @@ -125,7 +122,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { require(fastOutbox[epoch] == 0, "Batch already sent for the current epoch."); require(batchSize > 0, "No messages to send."); - // set merkle root in outbox and reset merkle tree + // set merkle root in outbox bytes32 batchMerkleRoot = getMerkleRoot(); fastOutbox[epoch] = batchMerkleRoot; emit SendBatch(currentBatchID, batchSize, epoch, batchMerkleRoot); @@ -147,30 +144,32 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // Encode the receiver address with the function signature + arguments i.e calldata bytes32 sender = bytes32(bytes20(msg.sender)); bytes32 receiver = bytes32(bytes20(_receiver)); + uint256 nonce = batchSize; // add sender and receiver with proper function selector formatting - // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] + // [length][receiver: 1 slot padded][offset][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] assembly { fastMessage := mload(0x40) // free memory pointer - let lengthCallData := mload(_calldata) // calldata length - let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender - let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage - mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length - mstore(add(fastMessage, 0x2c), receiver) // receiver - mstore(add(fastMessage, 0x40), 0x40) // offset - mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length + let lengthCalldata := mload(_calldata) // calldata length + let lengthFastMesssageCalldata := add(lengthCalldata, 0x20) // add msg.sender + let lengthEncodedMessage := add(lengthFastMesssageCalldata, 0x80) // 1 offsets, receiver, and lengthFastMesssageCalldata + mstore(fastMessage, lengthEncodedMessage) // bytes length + mstore(add(fastMessage, 0x20), nonce) // nonce + mstore(add(fastMessage, 0x4c), receiver) // receiver + mstore(add(fastMessage, 0x60), 0x60) // offset + mstore(add(fastMessage, 0x80), lengthFastMesssageCalldata) // fast message length mstore( - add(fastMessage, 0x80), + add(fastMessage, 0xa0), and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) ) // function selector - mstore(add(fastMessage, 0x90), sender) // sender + mstore(add(fastMessage, 0xb0), sender) // sender - let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call + let _cursor := add(fastMessage, 0xc4) // begin copying arguments of function call let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments // copy all arguments for { let j := 0x00 - } lt(j, lengthCallData) { + } lt(j, lengthCalldata) { j := add(j, 0x20) } { mstore(_cursor, mload(add(_cursorCalldata, j))) @@ -180,7 +179,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { mstore(0x40, _cursor) } // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). - fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); + fastMessageHash = sha256(fastMessage); } // ********************************* // @@ -195,33 +194,35 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { * @param leaf The leaf (already hashed) to insert in the merkle tree. */ 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; - 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; + unchecked { + // 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; + 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++; + } + batch[height] = leaf; } - batch[height] = leaf; } /** @dev Gets the current merkle root. @@ -229,36 +230,38 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { * `n` is the number of leaves. */ function getMerkleRoot() internal 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) - } + unchecked { + 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++; } - size /= 2; - height++; + return node; } - return node; } } diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 9c612ff6f..46063d9b7 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -50,13 +50,11 @@ interface IFastBridgeReceiver { * @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 _epoch, bytes32[] calldata _proof, - bytes calldata _message, - uint256 _nonce + bytes calldata _message ) external; /** diff --git a/contracts/src/bridge/interfaces/IFastBridgeSender.sol b/contracts/src/bridge/interfaces/IFastBridgeSender.sol index 46bd8c4b3..9e6f6062e 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -14,6 +14,13 @@ interface IFastBridgeSender { */ event MessageReceived(bytes fastMessage, bytes32 fastMessageHash); + /** + * @dev The event is emitted when messages are sent through the canonical bridge. + * @param epoch The epoch of the batch requested to send. + * @param canonicalBridgeMessageID The unique identifier of the safe message returned by the canonical bridge. + */ + event SentSafe(uint256 indexed epoch, bytes32 canonicalBridgeMessageID); + // ************************************* // // * Function Modifiers * // // ************************************* // diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index a1f2d6e83..491658c95 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -154,30 +154,32 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { // Encode the receiver address with the function signature + arguments i.e calldata bytes32 sender = bytes32(bytes20(msg.sender)); bytes32 receiver = bytes32(bytes20(_receiver)); + uint256 nonce = batchSize; // add sender and receiver with proper function selector formatting - // [length][offset 1][offset 2][receiver: 1 slot padded][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] + // [length][receiver: 1 slot padded][offset][function selector: 4 bytes no padding][msg.sender: 1 slot padded][function arguments: 1 slot padded] assembly { fastMessage := mload(0x40) // free memory pointer - let lengthCallData := mload(_calldata) // calldata length - let lengthFastMesssage := add(lengthCallData, 0x20) // add msg.sender - let lengthFastMesssageWithReceiverAndOffset := add(lengthFastMesssage, 0x60) // 1 offsets, receiver, and lengthFastMesssage - mstore(fastMessage, lengthFastMesssageWithReceiverAndOffset) // bytes length - mstore(add(fastMessage, 0x2c), receiver) // receiver - mstore(add(fastMessage, 0x40), 0x40) // offset - mstore(add(fastMessage, 0x60), lengthFastMesssage) // fast message length + let lengthCalldata := mload(_calldata) // calldata length + let lengthFastMesssageCalldata := add(lengthCalldata, 0x20) // add msg.sender + let lengthEncodedMessage := add(lengthFastMesssageCalldata, 0x80) // 1 offsets, receiver, and lengthFastMesssageCalldata + mstore(fastMessage, lengthEncodedMessage) // bytes length + mstore(add(fastMessage, 0x20), nonce) // nonce + mstore(add(fastMessage, 0x4c), receiver) // receiver + mstore(add(fastMessage, 0x60), 0x60) // offset + mstore(add(fastMessage, 0x80), lengthFastMesssageCalldata) // fast message length mstore( - add(fastMessage, 0x80), + add(fastMessage, 0xa0), and(mload(add(_calldata, 0x20)), 0xFFFFFFFF00000000000000000000000000000000000000000000000000000000) ) // function selector - mstore(add(fastMessage, 0x90), sender) // sender + mstore(add(fastMessage, 0xb0), sender) // sender - let _cursor := add(fastMessage, 0xa4) // begin copying arguments of function call + let _cursor := add(fastMessage, 0xc4) // begin copying arguments of function call let _cursorCalldata := add(_calldata, 0x24) // beginning of arguments // copy all arguments for { let j := 0x00 - } lt(j, lengthCallData) { + } lt(j, lengthCalldata) { j := add(j, 0x20) } { mstore(_cursor, mload(add(_cursorCalldata, j))) @@ -187,7 +189,7 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { mstore(0x40, _cursor) } // Compute the hash over the message header (batchSize as nonce) and body (fastMessage). - fastMessageHash = sha256(abi.encode(fastMessage, batchSize)); + fastMessageHash = sha256(fastMessage); } // ********************************* // diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 1517cdb28..58ce67e83 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -191,8 +191,6 @@ describe("Demo pre-alpha1", function () { const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event5 = await fastBridgeSender.queryFilter(MessageReceived); - - const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event5[0].args.fastMessage; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); @@ -202,19 +200,16 @@ describe("Demo pre-alpha1", function () { const event5a = await fastBridgeSender.queryFilter(SendBatch); const batchID = event5a[0].args.batchID; const batchMerkleRoot = event5a[0].args.batchMerkleRoot; - // bridger tx starts - Honest Bridger const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, batchMerkleRoot, { value: ONE_TENTH_ETH }); expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(batchID, batchMerkleRoot); - // wait for challenge period (and epoch) to pass await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); const tx7a = await fastBridgeReceiver.connect(bridger).verify(batchID); - const tx7 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); - + const tx7 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage); const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); expect(tx7).to.emit(arbitrable, "Ruling"); @@ -358,8 +353,6 @@ describe("Demo pre-alpha1", function () { const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event4 = await fastBridgeSender.queryFilter(MessageReceived); - - const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event4[0].args.fastMessage; @@ -389,7 +382,7 @@ describe("Demo pre-alpha1", function () { await network.provider.send("evm_mine"); await expect( - fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce) + fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage) ).to.be.revertedWith("Invalid epoch."); const tx7 = await fastBridgeSender @@ -397,7 +390,7 @@ describe("Demo pre-alpha1", function () { .sendSafeFallback(batchID, { gasLimit: 1000000 }); expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); - const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); + const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage); expect(tx8).to.emit(arbitrable, "Ruling"); @@ -515,8 +508,6 @@ describe("Demo pre-alpha1", function () { const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event4 = await fastBridgeSender.queryFilter(MessageReceived); - - const nonce = await fastBridgeSender.batchSize() - 1 ; const fastMessage = event4[0].args.fastMessage; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); @@ -541,7 +532,7 @@ describe("Demo pre-alpha1", function () { .sendSafeFallback(batchID, { gasLimit: 1000000 }); expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); - const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage, nonce); + const tx8 = await fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage); expect(tx8).to.emit(arbitrable, "Ruling"); await fastBridgeReceiver.withdrawChallengeDeposit(batchID); From f74c50b6f31a1758bc84771738e914457184a40c Mon Sep 17 00:00:00 2001 From: shotaronowhere Date: Fri, 10 Jun 2022 11:57:31 +0100 Subject: [PATCH 14/21] style(tests): fixed code smell --- contracts/test/bridge/merkle/index.ts | 19 ++++++++++--------- contracts/test/integration/index.ts | 17 ++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/test/bridge/merkle/index.ts b/contracts/test/bridge/merkle/index.ts index 1c80daeeb..9dc581013 100644 --- a/contracts/test/bridge/merkle/index.ts +++ b/contracts/test/bridge/merkle/index.ts @@ -30,7 +30,8 @@ import { MerkleTree } from "./MerkleTree"; describe("Merkle", function () { describe("Sanity tests", async () => { - let merkleTreeExposed, merkleProofExposed; + let merkleTreeExposed; + let merkleProofExposed; let data,nodes,mt; let rootOnChain,rootOffChain, proof; @@ -64,14 +65,14 @@ describe("Merkle", function () { expect(rootOffChain == rootOnChain).equal(true); }); - it("Should correctly verify all nodes in the tree", async () => { - for (var message of data) { - const leaf = ethers.utils.sha256(message); - proof = mt.getHexProof(leaf); - const validation = await merkleProofExposed._validateProof(proof, ethers.utils.sha256(message),rootOnChain); - expect(validation).equal(true); - expect(verify(proof, rootOffChain, leaf)).equal(true); - } + it("Should correctly verify all nodes in the tree", async () => { + for (var message of data) { + const leaf = ethers.utils.sha256(message); + proof = mt.getHexProof(leaf); + const validation = await merkleProofExposed._validateProof(proof, ethers.utils.sha256(message),rootOnChain); + expect(validation).equal(true); + expect(verify(proof, rootOffChain, leaf)).equal(true); + } }); }); }); \ No newline at end of file diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 58ce67e83..97a81cff6 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -355,16 +355,15 @@ describe("Demo pre-alpha1", function () { const event4 = await fastBridgeSender.queryFilter(MessageReceived); const fastMessage = event4[0].args.fastMessage; + const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); + expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); - const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); - expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); - - const SendBatch = fastBridgeSender.filters.SendBatch(); - const event4a = await fastBridgeSender.queryFilter(SendBatch); - const batchID = event4a[0].args.batchID; - const batchMerkleRoot = event4a[0].args.batchMerkleRoot; + const SendBatch = fastBridgeSender.filters.SendBatch(); + const event4a = await fastBridgeSender.queryFilter(SendBatch); + const batchID = event4a[0].args.batchID; + const batchMerkleRoot = event4a[0].args.batchMerkleRoot; - // bridger tx starts - Honest Bridger + // bridger tx starts - Honest Bridger console.log("Executed ruling"); @@ -521,7 +520,7 @@ describe("Demo pre-alpha1", function () { // bridger tx starts - bridger creates fakeData & fakeHash for dishonest ruling const fakeData = "KlerosToTheMoon"; - const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"],[fakeData])); + const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"], [fakeData])); const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, fakeHash, { value: ONE_TENTH_ETH }); // Challenger tx starts const tx6 = await fastBridgeReceiver.connect(challenger).challenge(batchID, { value: ONE_TENTH_ETH }); From 2d54f50b6e86ae673106d9a7473911e5525f9213 Mon Sep 17 00:00:00 2001 From: Hrishikesh Bhat Date: Mon, 4 Jul 2022 00:48:47 +0530 Subject: [PATCH 15/21] feat(bridge): addition + changes for polygon --- .../bridge/FastBridgeReceiverOnPolygon.sol | 323 +++++++++++++++++ .../src/bridge/SafeBridgeRouterForPolygon.sol | 83 +++++ .../interfaces/polygon/FxBaseChildTunnel.sol | 70 ++++ .../interfaces/polygon/FxBaseRootTunnel.sol | 162 +++++++++ .../interfaces/polygon/ICheckpointManager.sol | 18 + .../polygon/IFxMessageProcessor.sol | 11 + .../interfaces/polygon/IFxStateSender.sol | 6 + .../libraries/polygon/ExitPayloadReader.sol | 160 +++++++++ contracts/src/libraries/polygon/Merkle.sol | 34 ++ .../libraries/polygon/MerklePatriciaProof.sol | 137 +++++++ contracts/src/libraries/polygon/RLPReader.sol | 340 ++++++++++++++++++ 11 files changed, 1344 insertions(+) create mode 100644 contracts/src/bridge/FastBridgeReceiverOnPolygon.sol create mode 100644 contracts/src/bridge/SafeBridgeRouterForPolygon.sol create mode 100644 contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol create mode 100644 contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol create mode 100644 contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol create mode 100644 contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol create mode 100644 contracts/src/bridge/interfaces/polygon/IFxStateSender.sol create mode 100644 contracts/src/libraries/polygon/ExitPayloadReader.sol create mode 100755 contracts/src/libraries/polygon/Merkle.sol create mode 100644 contracts/src/libraries/polygon/MerklePatriciaProof.sol create mode 100644 contracts/src/libraries/polygon/RLPReader.sol diff --git a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol new file mode 100644 index 000000000..dc23a682f --- /dev/null +++ b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/polygon/FxBaseChildTunnel.sol"; // Polygon Receiver Specific + +/** + * Fast Receiver On Polygon + * Counterpart of `FastSenderFromArbitrum` + */ +contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, ISafeBridgeReceiver { + // **************************************** // + // * * // + // * Polygon Receiver Specific * // + // * * // + // **************************************** // + + // ************************************* // + // * Views * // + // ************************************* // + + function isSentBySafeBridge() internal view override returns (bool) { + return (msg.sender == fxChild); + } + + /** + * @dev Constructor. + * @param _deposit The deposit amount to submit a claim in wei. + * @param _epochPeriod The duration of each epoch. + * @param _challengePeriod The duration of the period allowing to challenge a claim. + * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. fxRootTunnel contract in ethereum + * @param _fxChild The the fxChild contract on Polygon Chain. + */ + constructor( + uint256 _deposit, + uint256 _epochPeriod, + uint256 _challengePeriod, + address _safeBridgeSender, // Polygon receiver specific + address _fxChild // Polygon receiver specific + ) FxBaseChildTunnel(_fxChild) { + deposit = _deposit; + epochPeriod = _epochPeriod; + challengePeriod = _challengePeriod; + setFxRootTunnel(_safeBridgeSender); + } + + // ************************************** // + // * * // + // * General Receiver * // + // * * // + // ************************************** // + + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + struct Claim { + bytes32 batchMerkleRoot; + address bridger; + uint32 timestamp; + bool honest; + bool depositAndRewardWithdrawn; + } + + struct Challenge { + address challenger; + bool honest; + bool depositAndRewardWithdrawn; + } + + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public immutable deposit; // The deposit required to submit a claim or challenge + uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. + uint256 public immutable override challengePeriod; // Epochs mark the period between potential batches of messages. + address public immutable safeBridgeSender; // The address of the Safe Bridge Sender 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 + mapping(uint256 => mapping(uint256 => bytes32)) public relayed; // epoch => packed replay bitmap + + // ************************************* // + // * 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 _epoch The epoch in which the batch to claim. + * @param _batchMerkleRoot The batch merkle root claimed for the last completed epoch. + */ + function claim(uint256 _epoch, bytes32 _batchMerkleRoot) external payable override { + require(msg.value >= deposit, "Insufficient claim deposit."); + require(_batchMerkleRoot != bytes32(0), "Invalid claim."); + + uint256 epoch = block.timestamp / epochPeriod; + // allow claim about current or previous epoch + require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); + + require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); + + claims[_epoch] = Claim({ + batchMerkleRoot: _batchMerkleRoot, + bridger: msg.sender, + timestamp: uint32(block.timestamp), + honest: false, + depositAndRewardWithdrawn: false + }); + + emit ClaimReceived(_epoch, _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. + * @param _epoch The epoch of the claim to challenge. + */ + function challenge(uint256 _epoch) external payable override { + require(msg.value >= deposit, "Not enough claim deposit"); + + // can only challenge the only active claim, about the previous epoch + require(claims[_epoch].bridger != address(0), "No claim to challenge."); + require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); + + challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); + + emit ClaimChallenged(_epoch); + } + + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verify(uint256 _epoch) public { + Claim storage claim = claims[_epoch]; + require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require( + block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, + "Challenge period has not yet elapsed." + ); + + 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. + */ + function verifyAndRelay( + uint256 _epoch, + bytes32[] calldata _proof, + bytes calldata _message + ) external override { + bytes32 batchMerkleRoot = fastInbox[_epoch]; + require(batchMerkleRoot != bytes32(0), "Invalid epoch."); + + // Claim assessment if any + bytes32 messageHash = sha256(_message); + + require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction + } + + /** + * Note: This is an internal function in the fxStateChildTunnel that is called by fxChild along with osur data. + * @dev Resolves any challenge of the optimistic claim for '_epoch'. + * @param stateId + * @param sender The fxRootTunnel or SafeBridgeRouterForPolygon in this case + * @param data The data sent by batch merkle root for the epoch. + */ + + function _processMessageFromRoot( + uint256 stateId, + address sender, + bytes memory data + ) internal override validateSender(sender) { + + (uint256 _epoch, bytes32 _batchMerkleRoot) = abi.decode(data, (uint256, bytes32)); + + fastInbox[_epoch] = _batchMerkleRoot; + + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + challenges[_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 storage claim = claims[_epoch]; + + require(claim.bridger != address(0), "Claim does not exist"); + require(claim.honest == true, "Claim not verified."); + require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); + + uint256 amount = deposit; + if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + + claim.depositAndRewardWithdrawn = true; + + 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 storage challenge = challenges[_epoch]; + + require(challenge.challenger != address(0), "Challenge does not exist"); + require(challenge.honest == true, "Challenge not verified."); + require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); + + uint256 amount = deposit + deposit / 2; + challenge.depositAndRewardWithdrawn = true; + + payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. + // Checks-Effects-Interaction + } + + // ********************************** // + // * 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) private 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; + } + + // ************************************* // + // * Public Views * // + // ************************************* // + + /** + * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. + * start The start time of the challenge period. + * end The end time of the challenge period. + */ + function claimChallengePeriod(uint256 _epoch) 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 + start = claims[_epoch].timestamp; + end = start + challengePeriod; + return (start, end); + } + + // ************************ // + // * Internal * // + // ************************ // + + function _checkReplayAndRelay(uint256 _epoch, bytes calldata _messageData) internal returns (bool success) { + // Decode the receiver address from the data encoded by the IFastBridgeSender + (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); + + 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); + + (success, ) = receiver.call(data); + } +} diff --git a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol new file mode 100644 index 000000000..e3dabbf4b --- /dev/null +++ b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere @hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./interfaces/ISafeBridgeReceiver.sol"; +import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/polygon/FxBaseRootTunnel.sol"; +import "./interfaces/arbitrum/IInbox.sol"; +import "./interfaces/arbitrum/IOutbox.sol"; + +/** + * Router on Ethereum from Arbitrum to Polygon Chain. + */ +contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootTunnel { + // ************************************* // + // * Events * // + // ************************************* // + + event safeRelayed(bytes32 indexed txID); + + // ************************************* // + // * Storage * // + // ************************************* // + + IInbox public immutable inbox; // The address of the Arbitrum Inbox contract. + address public immutable safeBridgeSender; // The address of the Safe Bridge sender on Arbitrum. + + /** + * @dev Constructor. + * @param _inbox The address of the inbox contract on Ethereum. + * @param _fxRoot The address of the fxRoot contract in Ethereum. + * @param _safeBridgeSender The safe bridge sender on Arbitrum. + * @param _fastBridgeReceiverOnPolygon The fast bridge receiver on Polygon Chain. + */ + + constructor( + address _inbox, + address _checkpointManager, + address _fxRoot, + address _safeBridgeSender, + address _fastBridgeReceiverOnPolygon + ) FxBaseRootTunnel(_checkpointManager, _fxRoot) { + inbox = _inbox; + safeBridgeSender = _safeBridgeSender; + setFxChildTunnel(_fastBridgeReceiverOnPolygon); + } + + /** + * Routes an arbitrary message from one domain to another. + * Note: Access restricted to the Safe Bridge. + * @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 verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + // Note: fxRoot sends message directly to fxchild hence no need for encodeWithSelector + bytes memory safeMessageData = abi.encode(_epoch, _batchMerkleRoot); + + // replace maxGasPerTx with safe level for production deployment + _sendSafe(safeMessageData); + // TODO: Consider an event emit here + } + + function _sendSafe(bytes memory _calldata) internal override { + _sendMessageToChild(_calldata); + } + + // ************************************* // + // * 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/polygon/FxBaseChildTunnel.sol b/contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol new file mode 100644 index 000000000..75c8b28d9 --- /dev/null +++ b/contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./IFxMessageProcessor.sol"; +/** + * @notice Mock child tunnel contract to receive and send message from L2 + */ +abstract contract FxBaseChildTunnel is IFxMessageProcessor { + // MessageTunnel on L1 will get data from this event + event MessageSent(bytes message); + + // fx child + address public fxChild; + + // fx root tunnel + address public fxRootTunnel; + + constructor(address _fxChild) { + fxChild = _fxChild; + } + + // Sender must be fxRootTunnel in case of ERC20 tunnel + modifier validateSender(address sender) { + require(sender == fxRootTunnel, "FxBaseChildTunnel: INVALID_SENDER_FROM_ROOT"); + _; + } + + // set fxRootTunnel if not set already + function setFxRootTunnel(address _fxRootTunnel) public virtual { + require(fxRootTunnel == address(0x0), "FxBaseChildTunnel: ROOT_TUNNEL_ALREADY_SET"); + fxRootTunnel = _fxRootTunnel; + } + + function processMessageFromRoot( + uint256 stateId, + address rootMessageSender, + bytes calldata data + ) external override { + require(msg.sender == fxChild, "FxBaseChildTunnel: INVALID_SENDER"); + _processMessageFromRoot(stateId, rootMessageSender, data); + } + + /** + * @notice Emit message that can be received on Root Tunnel + * @dev Call the internal function when need to emit message + * @param message bytes message that will be sent to Root Tunnel + * some message examples - + * abi.encode(tokenId); + * abi.encode(tokenId, tokenMetadata); + * abi.encode(messageType, messageData); + */ + function _sendMessageToRoot(bytes memory message) internal { + emit MessageSent(message); + } + + /** + * @notice Process message received from Root Tunnel + * @dev function needs to be implemented to handle message as per requirement + * This is called by onStateReceive function. + * Since it is called via a system call, any event will not be emitted during its execution. + * @param stateId unique state id + * @param sender root message sender + * @param message bytes message that was sent from Root Tunnel + */ + function _processMessageFromRoot( + uint256 stateId, + address sender, + bytes memory message + ) internal virtual; +} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol b/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol new file mode 100644 index 000000000..e61c4f412 --- /dev/null +++ b/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {RLPReader} from "../../libraries/polygon/RLPReader.sol"; +import {MerklePatriciaProof} from "../../libraries/polygon/MerklePatriciaProof.sol"; +import {Merkle} from "../../libraries/polygon/Merkle.sol"; +import "../../libraries/polygon/ExitPayloadReader.sol"; +import "../interfaces/polygon/IFxStateSender.sol"; +import "../interfaces/polygon/ICheckpointManager.sol"; + + +abstract contract FxBaseRootTunnel { + using RLPReader for RLPReader.RLPItem; + using Merkle for bytes32; + using ExitPayloadReader for bytes; + using ExitPayloadReader for ExitPayloadReader.ExitPayload; + using ExitPayloadReader for ExitPayloadReader.Log; + using ExitPayloadReader for ExitPayloadReader.LogTopics; + using ExitPayloadReader for ExitPayloadReader.Receipt; + + // keccak256(MessageSent(bytes)) + bytes32 public constant SEND_MESSAGE_EVENT_SIG = 0x8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036; + + // state sender contract + IFxStateSender public fxRoot; + // root chain manager + ICheckpointManager public checkpointManager; + // child tunnel contract which receives and sends messages + address public fxChildTunnel; + + // storage to avoid duplicate exits + mapping(bytes32 => bool) public processedExits; + + constructor(address _checkpointManager, address _fxRoot) { + checkpointManager = ICheckpointManager(_checkpointManager); + fxRoot = IFxStateSender(_fxRoot); + } + + // set fxChildTunnel if not set already + function setFxChildTunnel(address _fxChildTunnel) public virtual { + require(fxChildTunnel == address(0x0), "FxBaseRootTunnel: CHILD_TUNNEL_ALREADY_SET"); + fxChildTunnel = _fxChildTunnel; + } + + /** + * @notice Send bytes message to Child Tunnel + * @param message bytes message that will be sent to Child Tunnel + * some message examples - + * abi.encode(tokenId); + * abi.encode(tokenId, tokenMetadata); + * abi.encode(messageType, messageData); + */ + function _sendMessageToChild(bytes memory message) internal { + fxRoot.sendMessageToChild(fxChildTunnel, message); + } + + function _validateAndExtractMessage(bytes memory inputData) internal returns (bytes memory) { + ExitPayloadReader.ExitPayload memory payload = inputData.toExitPayload(); + + bytes memory branchMaskBytes = payload.getBranchMaskAsBytes(); + uint256 blockNumber = payload.getBlockNumber(); + // checking if exit has already been processed + // unique exit is identified using hash of (blockNumber, branchMask, receiptLogIndex) + bytes32 exitHash = keccak256( + abi.encodePacked( + blockNumber, + // first 2 nibbles are dropped while generating nibble array + // this allows branch masks that are valid but bypass exitHash check (changing first 2 nibbles only) + // so converting to nibble array and then hashing it + MerklePatriciaProof._getNibbleArray(branchMaskBytes), + payload.getReceiptLogIndex() + ) + ); + require(processedExits[exitHash] == false, "FxRootTunnel: EXIT_ALREADY_PROCESSED"); + processedExits[exitHash] = true; + + ExitPayloadReader.Receipt memory receipt = payload.getReceipt(); + ExitPayloadReader.Log memory log = receipt.getLog(); + + // check child tunnel + require(fxChildTunnel == log.getEmitter(), "FxRootTunnel: INVALID_FX_CHILD_TUNNEL"); + + bytes32 receiptRoot = payload.getReceiptRoot(); + // verify receipt inclusion + require( + MerklePatriciaProof.verify(receipt.toBytes(), branchMaskBytes, payload.getReceiptProof(), receiptRoot), + "FxRootTunnel: INVALID_RECEIPT_PROOF" + ); + + // verify checkpoint inclusion + _checkBlockMembershipInCheckpoint( + blockNumber, + payload.getBlockTime(), + payload.getTxRoot(), + receiptRoot, + payload.getHeaderNumber(), + payload.getBlockProof() + ); + + ExitPayloadReader.LogTopics memory topics = log.getTopics(); + + require( + bytes32(topics.getField(0).toUint()) == SEND_MESSAGE_EVENT_SIG, // topic0 is event sig + "FxRootTunnel: INVALID_SIGNATURE" + ); + + // received message data + bytes memory message = abi.decode(log.getData(), (bytes)); // event decodes params again, so decoding bytes to get message + return message; + } + + function _checkBlockMembershipInCheckpoint( + uint256 blockNumber, + uint256 blockTime, + bytes32 txRoot, + bytes32 receiptRoot, + uint256 headerNumber, + bytes memory blockProof + ) private view returns (uint256) { + (bytes32 headerRoot, uint256 startBlock, , uint256 createdAt, ) = checkpointManager.headerBlocks(headerNumber); + + require( + keccak256(abi.encodePacked(blockNumber, blockTime, txRoot, receiptRoot)).checkMembership( + blockNumber - startBlock, + headerRoot, + blockProof + ), + "FxRootTunnel: INVALID_HEADER" + ); + return createdAt; + } + + /** + * @notice receive message from L2 to L1, validated by proof + * @dev This function verifies if the transaction actually happened on child chain + * + * @param inputData RLP encoded data of the reference tx containing following list of fields + * 0 - headerNumber - Checkpoint header block number containing the reference tx + * 1 - blockProof - Proof that the block header (in the child chain) is a leaf in the submitted merkle root + * 2 - blockNumber - Block number containing the reference tx on child chain + * 3 - blockTime - Reference tx block time + * 4 - txRoot - Transactions root of block + * 5 - receiptRoot - Receipts root of block + * 6 - receipt - Receipt of the reference transaction + * 7 - receiptProof - Merkle proof of the reference receipt + * 8 - branchMask - 32 bits denoting the path of receipt in merkle tree + * 9 - receiptLogIndex - Log Index to read from the receipt + */ + function receiveMessage(bytes memory inputData) public virtual { + bytes memory message = _validateAndExtractMessage(inputData); + _processMessageFromChild(message); + } + + /** + * @notice Process message received from Child Tunnel + * @dev function needs to be implemented to handle message as per requirement + * This is called by onStateReceive function. + * Since it is called via a system call, any event will not be emitted during its execution. + * @param message bytes message that was sent from Child Tunnel + */ + function _processMessageFromChild(bytes memory message) internal virtual; +} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol b/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol new file mode 100644 index 000000000..486c32871 --- /dev/null +++ b/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ICheckpointManager { + struct HeaderBlock { + bytes32 root; + uint256 start; + uint256 end; + uint256 createdAt; + address proposer; + } + + /** + * @notice mapping of checkpoint header numbers to block details + * @dev These checkpoints are submited by plasma contracts + */ + mapping(uint256 => HeaderBlock) public headerBlocks; +} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol b/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol new file mode 100644 index 000000000..8e1dbf640 --- /dev/null +++ b/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// IFxMessageProcessor represents interface to process message +interface IFxMessageProcessor { + function processMessageFromRoot( + uint256 stateId, + address rootMessageSender, + bytes calldata data + ) external; +} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol b/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol new file mode 100644 index 000000000..d0bb32b7e --- /dev/null +++ b/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IFxStateSender { + function sendMessageToChild(address _receiver, bytes calldata _data) external; +} \ No newline at end of file diff --git a/contracts/src/libraries/polygon/ExitPayloadReader.sol b/contracts/src/libraries/polygon/ExitPayloadReader.sol new file mode 100644 index 000000000..1136c2b6b --- /dev/null +++ b/contracts/src/libraries/polygon/ExitPayloadReader.sol @@ -0,0 +1,160 @@ +pragma solidity ^0.8.0; + +import {RLPReader} from "./RLPReader.sol"; + +library ExitPayloadReader { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + uint8 constant WORD_SIZE = 32; + + struct ExitPayload { + RLPReader.RLPItem[] data; + } + + struct Receipt { + RLPReader.RLPItem[] data; + bytes raw; + uint256 logIndex; + } + + struct Log { + RLPReader.RLPItem data; + RLPReader.RLPItem[] list; + } + + struct LogTopics { + RLPReader.RLPItem[] data; + } + + // copy paste of private copy() from RLPReader to avoid changing of existing contracts + function copy( + uint256 src, + uint256 dest, + uint256 len + ) private pure { + if (len == 0) return; + + // copy as many word sizes as possible + for (; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + // left over bytes. Mask is used to remove unwanted bytes from the word + uint256 mask = 256**(WORD_SIZE - len) - 1; + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + } + + function toExitPayload(bytes memory data) internal pure returns (ExitPayload memory) { + RLPReader.RLPItem[] memory payloadData = data.toRlpItem().toList(); + + return ExitPayload(payloadData); + } + + function getHeaderNumber(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[0].toUint(); + } + + function getBlockProof(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[1].toBytes(); + } + + function getBlockNumber(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[2].toUint(); + } + + function getBlockTime(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[3].toUint(); + } + + function getTxRoot(ExitPayload memory payload) internal pure returns (bytes32) { + return bytes32(payload.data[4].toUint()); + } + + function getReceiptRoot(ExitPayload memory payload) internal pure returns (bytes32) { + return bytes32(payload.data[5].toUint()); + } + + function getReceipt(ExitPayload memory payload) internal pure returns (Receipt memory receipt) { + receipt.raw = payload.data[6].toBytes(); + RLPReader.RLPItem memory receiptItem = receipt.raw.toRlpItem(); + + if (receiptItem.isList()) { + // legacy tx + receipt.data = receiptItem.toList(); + } else { + // pop first byte before parsting receipt + bytes memory typedBytes = receipt.raw; + bytes memory result = new bytes(typedBytes.length - 1); + uint256 srcPtr; + uint256 destPtr; + assembly { + srcPtr := add(33, typedBytes) + destPtr := add(0x20, result) + } + + copy(srcPtr, destPtr, result.length); + receipt.data = result.toRlpItem().toList(); + } + + receipt.logIndex = getReceiptLogIndex(payload); + return receipt; + } + + function getReceiptProof(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[7].toBytes(); + } + + function getBranchMaskAsBytes(ExitPayload memory payload) internal pure returns (bytes memory) { + return payload.data[8].toBytes(); + } + + function getBranchMaskAsUint(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[8].toUint(); + } + + function getReceiptLogIndex(ExitPayload memory payload) internal pure returns (uint256) { + return payload.data[9].toUint(); + } + + // Receipt methods + function toBytes(Receipt memory receipt) internal pure returns (bytes memory) { + return receipt.raw; + } + + function getLog(Receipt memory receipt) internal pure returns (Log memory) { + RLPReader.RLPItem memory logData = receipt.data[3].toList()[receipt.logIndex]; + return Log(logData, logData.toList()); + } + + // Log methods + function getEmitter(Log memory log) internal pure returns (address) { + return RLPReader.toAddress(log.list[0]); + } + + function getTopics(Log memory log) internal pure returns (LogTopics memory) { + return LogTopics(log.list[1].toList()); + } + + function getData(Log memory log) internal pure returns (bytes memory) { + return log.list[2].toBytes(); + } + + function toRlpBytes(Log memory log) internal pure returns (bytes memory) { + return log.data.toRlpBytes(); + } + + // LogTopics methods + function getField(LogTopics memory topics, uint256 index) internal pure returns (RLPReader.RLPItem memory) { + return topics.data[index]; + } +} diff --git a/contracts/src/libraries/polygon/Merkle.sol b/contracts/src/libraries/polygon/Merkle.sol new file mode 100755 index 000000000..2dd3a8667 --- /dev/null +++ b/contracts/src/libraries/polygon/Merkle.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +library Merkle { + function checkMembership( + bytes32 leaf, + uint256 index, + bytes32 rootHash, + bytes memory proof + ) internal pure returns (bool) { + require(proof.length % 32 == 0, "Invalid proof length"); + uint256 proofHeight = proof.length / 32; + // Proof of size n means, height of the tree is n+1. + // In a tree of height n+1, max #leafs possible is 2 ^ n + require(index < 2**proofHeight, "Leaf index is too big"); + + bytes32 proofElement; + bytes32 computedHash = leaf; + for (uint256 i = 32; i <= proof.length; i += 32) { + assembly { + proofElement := mload(add(proof, i)) + } + + if (index % 2 == 0) { + computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); + } else { + computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); + } + + index = index / 2; + } + return computedHash == rootHash; + } +} diff --git a/contracts/src/libraries/polygon/MerklePatriciaProof.sol b/contracts/src/libraries/polygon/MerklePatriciaProof.sol new file mode 100644 index 000000000..430d7dcd1 --- /dev/null +++ b/contracts/src/libraries/polygon/MerklePatriciaProof.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {RLPReader} from "./RLPReader.sol"; + +library MerklePatriciaProof { + /* + * @dev Verifies a merkle patricia proof. + * @param value The terminating value in the trie. + * @param encodedPath The path in the trie leading to value. + * @param rlpParentNodes The rlp encoded stack of nodes. + * @param root The root hash of the trie. + * @return The boolean validity of the proof. + */ + function verify( + bytes memory value, + bytes memory encodedPath, + bytes memory rlpParentNodes, + bytes32 root + ) internal pure returns (bool) { + RLPReader.RLPItem memory item = RLPReader.toRlpItem(rlpParentNodes); + RLPReader.RLPItem[] memory parentNodes = RLPReader.toList(item); + + bytes memory currentNode; + RLPReader.RLPItem[] memory currentNodeList; + + bytes32 nodeKey = root; + uint256 pathPtr = 0; + + bytes memory path = _getNibbleArray(encodedPath); + if (path.length == 0) { + return false; + } + + for (uint256 i = 0; i < parentNodes.length; i++) { + if (pathPtr > path.length) { + return false; + } + + currentNode = RLPReader.toRlpBytes(parentNodes[i]); + if (nodeKey != keccak256(currentNode)) { + return false; + } + currentNodeList = RLPReader.toList(parentNodes[i]); + + if (currentNodeList.length == 17) { + if (pathPtr == path.length) { + if (keccak256(RLPReader.toBytes(currentNodeList[16])) == keccak256(value)) { + return true; + } else { + return false; + } + } + + uint8 nextPathNibble = uint8(path[pathPtr]); + if (nextPathNibble > 16) { + return false; + } + nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[nextPathNibble])); + pathPtr += 1; + } else if (currentNodeList.length == 2) { + uint256 traversed = _nibblesToTraverse(RLPReader.toBytes(currentNodeList[0]), path, pathPtr); + if (pathPtr + traversed == path.length) { + //leaf node + if (keccak256(RLPReader.toBytes(currentNodeList[1])) == keccak256(value)) { + return true; + } else { + return false; + } + } + + //extension node + if (traversed == 0) { + return false; + } + + pathPtr += traversed; + nodeKey = bytes32(RLPReader.toUintStrict(currentNodeList[1])); + } else { + return false; + } + } + } + + function _nibblesToTraverse( + bytes memory encodedPartialPath, + bytes memory path, + uint256 pathPtr + ) private pure returns (uint256) { + uint256 len = 0; + // encodedPartialPath has elements that are each two hex characters (1 byte), but partialPath + // and slicedPath have elements that are each one hex character (1 nibble) + bytes memory partialPath = _getNibbleArray(encodedPartialPath); + bytes memory slicedPath = new bytes(partialPath.length); + + // pathPtr counts nibbles in path + // partialPath.length is a number of nibbles + for (uint256 i = pathPtr; i < pathPtr + partialPath.length; i++) { + bytes1 pathNibble = path[i]; + slicedPath[i - pathPtr] = pathNibble; + } + + if (keccak256(partialPath) == keccak256(slicedPath)) { + len = partialPath.length; + } else { + len = 0; + } + return len; + } + + // bytes b must be hp encoded + function _getNibbleArray(bytes memory b) internal pure returns (bytes memory) { + bytes memory nibbles = ""; + if (b.length > 0) { + uint8 offset; + uint8 hpNibble = uint8(_getNthNibbleOfBytes(0, b)); + if (hpNibble == 1 || hpNibble == 3) { + nibbles = new bytes(b.length * 2 - 1); + bytes1 oddNibble = _getNthNibbleOfBytes(1, b); + nibbles[0] = oddNibble; + offset = 1; + } else { + nibbles = new bytes(b.length * 2 - 2); + offset = 0; + } + + for (uint256 i = offset; i < nibbles.length; i++) { + nibbles[i] = _getNthNibbleOfBytes(i - offset + 2, b); + } + } + return nibbles; + } + + function _getNthNibbleOfBytes(uint256 n, bytes memory str) private pure returns (bytes1) { + return bytes1(n % 2 == 0 ? uint8(str[n / 2]) / 0x10 : uint8(str[n / 2]) % 0x10); + } +} diff --git a/contracts/src/libraries/polygon/RLPReader.sol b/contracts/src/libraries/polygon/RLPReader.sol new file mode 100644 index 000000000..e190e0046 --- /dev/null +++ b/contracts/src/libraries/polygon/RLPReader.sol @@ -0,0 +1,340 @@ +/* + * @author Hamdi Allam hamdi.allam97@gmail.com + * Please reach out with any questions or concerns + */ +pragma solidity ^0.8.0; + +library RLPReader { + uint8 constant STRING_SHORT_START = 0x80; + uint8 constant STRING_LONG_START = 0xb8; + uint8 constant LIST_SHORT_START = 0xc0; + uint8 constant LIST_LONG_START = 0xf8; + uint8 constant WORD_SIZE = 32; + + struct RLPItem { + uint256 len; + uint256 memPtr; + } + + struct Iterator { + RLPItem item; // Item that's being iterated over. + uint256 nextPtr; // Position of the next item in the list. + } + + /* + * @dev Returns the next element in the iteration. Reverts if it has not next element. + * @param self The iterator. + * @return The next element in the iteration. + */ + function next(Iterator memory self) internal pure returns (RLPItem memory) { + require(hasNext(self)); + + uint256 ptr = self.nextPtr; + uint256 itemLength = _itemLength(ptr); + self.nextPtr = ptr + itemLength; + + return RLPItem(itemLength, ptr); + } + + /* + * @dev Returns true if the iteration has more elements. + * @param self The iterator. + * @return true if the iteration has more elements. + */ + function hasNext(Iterator memory self) internal pure returns (bool) { + RLPItem memory item = self.item; + return self.nextPtr < item.memPtr + item.len; + } + + /* + * @param item RLP encoded bytes + */ + function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) { + uint256 memPtr; + assembly { + memPtr := add(item, 0x20) + } + + return RLPItem(item.length, memPtr); + } + + /* + * @dev Create an iterator. Reverts if item is not a list. + * @param self The RLP item. + * @return An 'Iterator' over the item. + */ + function iterator(RLPItem memory self) internal pure returns (Iterator memory) { + require(isList(self)); + + uint256 ptr = self.memPtr + _payloadOffset(self.memPtr); + return Iterator(self, ptr); + } + + /* + * @param item RLP encoded bytes + */ + function rlpLen(RLPItem memory item) internal pure returns (uint256) { + return item.len; + } + + /* + * @param item RLP encoded bytes + */ + function payloadLen(RLPItem memory item) internal pure returns (uint256) { + return item.len - _payloadOffset(item.memPtr); + } + + /* + * @param item RLP encoded list in bytes + */ + function toList(RLPItem memory item) internal pure returns (RLPItem[] memory) { + require(isList(item)); + + uint256 items = numItems(item); + RLPItem[] memory result = new RLPItem[](items); + + uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 dataLen; + for (uint256 i = 0; i < items; i++) { + dataLen = _itemLength(memPtr); + result[i] = RLPItem(dataLen, memPtr); + memPtr = memPtr + dataLen; + } + + return result; + } + + // @return indicator whether encoded payload is a list. negate this function call for isData. + function isList(RLPItem memory item) internal pure returns (bool) { + if (item.len == 0) return false; + + uint8 byte0; + uint256 memPtr = item.memPtr; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < LIST_SHORT_START) return false; + return true; + } + + /* + * @dev A cheaper version of keccak256(toRlpBytes(item)) that avoids copying memory. + * @return keccak256 hash of RLP encoded bytes. + */ + function rlpBytesKeccak256(RLPItem memory item) internal pure returns (bytes32) { + uint256 ptr = item.memPtr; + uint256 len = item.len; + bytes32 result; + assembly { + result := keccak256(ptr, len) + } + return result; + } + + function payloadLocation(RLPItem memory item) internal pure returns (uint256, uint256) { + uint256 offset = _payloadOffset(item.memPtr); + uint256 memPtr = item.memPtr + offset; + uint256 len = item.len - offset; // data length + return (memPtr, len); + } + + /* + * @dev A cheaper version of keccak256(toBytes(item)) that avoids copying memory. + * @return keccak256 hash of the item payload. + */ + function payloadKeccak256(RLPItem memory item) internal pure returns (bytes32) { + (uint256 memPtr, uint256 len) = payloadLocation(item); + bytes32 result; + assembly { + result := keccak256(memPtr, len) + } + return result; + } + + /** RLPItem conversions into data types **/ + + // @returns raw rlp encoding in bytes + function toRlpBytes(RLPItem memory item) internal pure returns (bytes memory) { + bytes memory result = new bytes(item.len); + if (result.length == 0) return result; + + uint256 ptr; + assembly { + ptr := add(0x20, result) + } + + copy(item.memPtr, ptr, item.len); + return result; + } + + // any non-zero byte is considered true + function toBoolean(RLPItem memory item) internal pure returns (bool) { + require(item.len == 1); + uint256 result; + uint256 memPtr = item.memPtr; + assembly { + result := byte(0, mload(memPtr)) + } + + return result == 0 ? false : true; + } + + function toAddress(RLPItem memory item) internal pure returns (address) { + // 1 byte for the length prefix + require(item.len == 21); + + return address(uint160(toUint(item))); + } + + function toUint(RLPItem memory item) internal pure returns (uint256) { + require(item.len > 0 && item.len <= 33); + + uint256 offset = _payloadOffset(item.memPtr); + uint256 len = item.len - offset; + + uint256 result; + uint256 memPtr = item.memPtr + offset; + assembly { + result := mload(memPtr) + + // shfit to the correct location if neccesary + if lt(len, 32) { + result := div(result, exp(256, sub(32, len))) + } + } + + return result; + } + + // enforces 32 byte length + function toUintStrict(RLPItem memory item) internal pure returns (uint256) { + // one byte prefix + require(item.len == 33); + + uint256 result; + uint256 memPtr = item.memPtr + 1; + assembly { + result := mload(memPtr) + } + + return result; + } + + function toBytes(RLPItem memory item) internal pure returns (bytes memory) { + require(item.len > 0); + + uint256 offset = _payloadOffset(item.memPtr); + uint256 len = item.len - offset; // data length + bytes memory result = new bytes(len); + + uint256 destPtr; + assembly { + destPtr := add(0x20, result) + } + + copy(item.memPtr + offset, destPtr, len); + return result; + } + + /* + * Private Helpers + */ + + // @return number of payload items inside an encoded list. + function numItems(RLPItem memory item) private pure returns (uint256) { + if (item.len == 0) return 0; + + uint256 count = 0; + uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); + uint256 endPtr = item.memPtr + item.len; + while (currPtr < endPtr) { + currPtr = currPtr + _itemLength(currPtr); // skip over an item + count++; + } + + return count; + } + + // @return entire rlp item byte length + function _itemLength(uint256 memPtr) private pure returns (uint256) { + uint256 itemLen; + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) itemLen = 1; + else if (byte0 < STRING_LONG_START) itemLen = byte0 - STRING_SHORT_START + 1; + else if (byte0 < LIST_SHORT_START) { + assembly { + let byteLen := sub(byte0, 0xb7) // # of bytes the actual length is + memPtr := add(memPtr, 1) // skip over the first byte + /* 32 byte word size */ + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to get the len + itemLen := add(dataLen, add(byteLen, 1)) + } + } else if (byte0 < LIST_LONG_START) { + itemLen = byte0 - LIST_SHORT_START + 1; + } else { + assembly { + let byteLen := sub(byte0, 0xf7) + memPtr := add(memPtr, 1) + + let dataLen := div(mload(memPtr), exp(256, sub(32, byteLen))) // right shifting to the correct length + itemLen := add(dataLen, add(byteLen, 1)) + } + } + + return itemLen; + } + + // @return number of bytes until the data + function _payloadOffset(uint256 memPtr) private pure returns (uint256) { + uint256 byte0; + assembly { + byte0 := byte(0, mload(memPtr)) + } + + if (byte0 < STRING_SHORT_START) return 0; + else if (byte0 < STRING_LONG_START || (byte0 >= LIST_SHORT_START && byte0 < LIST_LONG_START)) return 1; + else if (byte0 < LIST_SHORT_START) + // being explicit + return byte0 - (STRING_LONG_START - 1) + 1; + else return byte0 - (LIST_LONG_START - 1) + 1; + } + + /* + * @param src Pointer to source + * @param dest Pointer to destination + * @param len Amount of memory to copy from the source + */ + function copy( + uint256 src, + uint256 dest, + uint256 len + ) private pure { + if (len == 0) return; + + // copy as many word sizes as possible + for (; len >= WORD_SIZE; len -= WORD_SIZE) { + assembly { + mstore(dest, mload(src)) + } + + src += WORD_SIZE; + dest += WORD_SIZE; + } + + if (len == 0) return; + + // left over bytes. Mask is used to remove unwanted bytes from the word + uint256 mask = 256**(WORD_SIZE - len) - 1; + + assembly { + let srcpart := and(mload(src), not(mask)) // zero out src + let destpart := and(mload(dest), mask) // retrieve the bytes + mstore(dest, or(destpart, srcpart)) + } + } +} From 1103e7df03dd1dd74840f552721e3535940b6a35 Mon Sep 17 00:00:00 2001 From: Hrishikesh Bhat Date: Mon, 4 Jul 2022 01:10:43 +0530 Subject: [PATCH 16/21] fix: file imports correction --- .../bridge/interfaces/polygon/FxBaseRootTunnel.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol b/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol index e61c4f412..533f36e55 100644 --- a/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol +++ b/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import {RLPReader} from "../../libraries/polygon/RLPReader.sol"; -import {MerklePatriciaProof} from "../../libraries/polygon/MerklePatriciaProof.sol"; -import {Merkle} from "../../libraries/polygon/Merkle.sol"; -import "../../libraries/polygon/ExitPayloadReader.sol"; -import "../interfaces/polygon/IFxStateSender.sol"; -import "../interfaces/polygon/ICheckpointManager.sol"; +import {RLPReader} from "../../../libraries/polygon/RLPReader.sol"; +import {MerklePatriciaProof} from "../../../libraries/polygon/MerklePatriciaProof.sol"; +import {Merkle} from "../../../libraries/polygon/Merkle.sol"; +import "../../../libraries/polygon/ExitPayloadReader.sol"; +import "./IFxStateSender.sol"; +import "./ICheckpointManager.sol"; abstract contract FxBaseRootTunnel { From f179c1481f4b643712f781e69a77c5440a357b96 Mon Sep 17 00:00:00 2001 From: Hrishikesh Bhat Date: Mon, 4 Jul 2022 10:13:16 +0530 Subject: [PATCH 17/21] fix: minor changes --- .../src/bridge/FastBridgeReceiverOnPolygon.sol | 14 +++++--------- .../src/bridge/SafeBridgeRouterForPolygon.sol | 14 +++++++++++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol index dc23a682f..452a106fb 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol @@ -85,7 +85,6 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, uint256 public immutable deposit; // The deposit required to submit a claim or challenge uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. uint256 public immutable override challengePeriod; // Epochs mark the period between potential batches of messages. - address public immutable safeBridgeSender; // The address of the Safe Bridge Sender 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 @@ -178,20 +177,13 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction } - /** - * Note: This is an internal function in the fxStateChildTunnel that is called by fxChild along with osur data. - * @dev Resolves any challenge of the optimistic claim for '_epoch'. - * @param stateId - * @param sender The fxRootTunnel or SafeBridgeRouterForPolygon in this case - * @param data The data sent by batch merkle root for the epoch. - */ + // Note: This is an internal function in the fxStateChildTunnel that is called by fxChild along with osur data. function _processMessageFromRoot( uint256 stateId, address sender, bytes memory data ) internal override validateSender(sender) { - (uint256 _epoch, bytes32 _batchMerkleRoot) = abi.decode(data, (uint256, bytes32)); fastInbox[_epoch] = _batchMerkleRoot; @@ -203,6 +195,10 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, } } + function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + return; + } + /** * @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/SafeBridgeRouterForPolygon.sol b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol index e3dabbf4b..c6aa07e56 100644 --- a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol +++ b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol @@ -42,7 +42,7 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT */ constructor( - address _inbox, + IInbox _inbox, address _checkpointManager, address _fxRoot, address _safeBridgeSender, @@ -57,7 +57,7 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT * Routes an arbitrary message from one domain to another. * Note: Access restricted to the Safe Bridge. * @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. + * @param _batchMerkleRoot The true batchP merkle root for the epoch sent by the safe bridge. * @return Unique id to track the message request/transaction. */ function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { // Note: fxRoot sends message directly to fxchild hence no need for encodeWithSelector @@ -68,10 +68,18 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT // TODO: Consider an event emit here } - function _sendSafe(bytes memory _calldata) internal override { + function _sendSafe(bytes memory _calldata) internal { _sendMessageToChild(_calldata); } + function _processMessageFromChild(bytes memory message) internal override { + } + + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32){ + + } + + // ************************************* // // * Views * // // ************************************* // From 9acaf9d6134c13bc9814ee6baf62fe284a77c305 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 16 Jul 2022 12:39:10 +0200 Subject: [PATCH 18/21] refactor: moved the 3rd party code from the canonical bridges Moved to a dedicated "canonical" subfolder. The previous folder "interfaces" was inacurrate as there were abstracts and libraries as well. --- .../bridge/FastBridgeReceiverOnEthereum.sol | 4 +- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 4 +- .../bridge/FastBridgeReceiverOnPolygon.sol | 41 +++++++++++-------- contracts/src/bridge/FastBridgeSender.sol | 2 +- contracts/src/bridge/SafeBridgeRouter.sol | 6 +-- .../src/bridge/SafeBridgeRouterForPolygon.sol | 14 +++---- .../arbitrum/AddressAliasHelper.sol | 0 .../arbitrum/IArbRetryableTx.sol | 0 .../arbitrum/IArbSys.sol | 0 .../arbitrum/IInbox.sol | 0 .../arbitrum/IOutbox.sol | 0 .../gnosis-chain/IAMB.sol | 0 .../polygon/FxBaseChildTunnel.sol | 15 +++++-- .../polygon/FxBaseRootTunnel.sol | 35 ++++++++++++---- .../polygon/lib}/ExitPayloadReader.sol | 0 .../canonical/polygon/lib}/Merkle.sol | 0 .../polygon/lib}/MerklePatriciaProof.sol | 0 .../canonical/polygon/lib}/RLPReader.sol | 0 .../interfaces/polygon/ICheckpointManager.sol | 18 -------- .../polygon/IFxMessageProcessor.sol | 11 ----- .../interfaces/polygon/IFxStateSender.sol | 6 --- .../SafeBridgeReceiverOnEthereum.sol | 4 +- ...SafeBridgeSenderToArbitrumFromEthereum.sol | 6 +-- .../SafeBridgeSenderToEthereum.sol | 4 +- .../SafeBridgeSenderToGnosis.sol | 2 +- .../src/bridge/test/FastBridgeSenderMock.sol | 2 +- .../src/bridge/test/arbitrum/ArbSysMock.sol | 2 +- .../src/bridge/test/arbitrum/BridgeMock.sol | 2 +- .../src/bridge/test/arbitrum/InboxMock.sol | 2 +- .../src/bridge/test/arbitrum/OutboxMock.sol | 2 +- .../src/bridge/test/gnosis-chain/MockAMB.sol | 2 +- 31 files changed, 91 insertions(+), 93 deletions(-) rename contracts/src/bridge/{interfaces => canonical}/arbitrum/AddressAliasHelper.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/arbitrum/IArbRetryableTx.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/arbitrum/IArbSys.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/arbitrum/IInbox.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/arbitrum/IOutbox.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/gnosis-chain/IAMB.sol (100%) rename contracts/src/bridge/{interfaces => canonical}/polygon/FxBaseChildTunnel.sol (84%) rename contracts/src/bridge/{interfaces => canonical}/polygon/FxBaseRootTunnel.sol (88%) rename contracts/src/{libraries/polygon => bridge/canonical/polygon/lib}/ExitPayloadReader.sol (100%) rename contracts/src/{libraries/polygon => bridge/canonical/polygon/lib}/Merkle.sol (100%) mode change 100755 => 100644 rename contracts/src/{libraries/polygon => bridge/canonical/polygon/lib}/MerklePatriciaProof.sol (100%) rename contracts/src/{libraries/polygon => bridge/canonical/polygon/lib}/RLPReader.sol (100%) delete mode 100644 contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol delete mode 100644 contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol delete mode 100644 contracts/src/bridge/interfaces/polygon/IFxStateSender.sol diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 699da4172..73d1be35a 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -12,8 +12,8 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/arbitrum/IInbox.sol"; // Ethereum Receiver Specific -import "./interfaces/arbitrum/IOutbox.sol"; // Ethereum Receiver Specific +import "./canonical/arbitrum/IInbox.sol"; // Ethereum Receiver Specific +import "./canonical/arbitrum/IOutbox.sol"; // Ethereum Receiver Specific /** * Fast Receiver On Ethereum diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index 848cb077e..abc512cf8 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; // Gnosis Receiver Specific +import "./canonical/gnosis-chain/IAMB.sol"; // Gnosis Receiver Specific /** * Fast Receiver On Gnosis @@ -45,7 +45,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver * @param _epochPeriod The duration of each epoch. * @param _challengePeriod The duration of the period allowing to challenge a claim. * @param _safeBridgeSender The address of the Safe Bridge Sender on the connecting chain. - * @param _amb The the AMB contract on Gnosis Chain. + * @param _amb The AMB contract on Gnosis Chain. */ constructor( uint256 _deposit, diff --git a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol index 452a106fb..4594e23cf 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/polygon/FxBaseChildTunnel.sol"; // Polygon Receiver Specific +import "./canonical/polygon/FxBaseChildTunnel.sol"; // Polygon Receiver Specific /** * Fast Receiver On Polygon @@ -177,26 +177,33 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction } - // Note: This is an internal function in the fxStateChildTunnel that is called by fxChild along with osur data. - + /** + * @dev Handles incoming messages from Ethereum via the canonical Polygon bridge. + * @param _stateId The epoch in which the message was batched by the bridge. + * @param _sender The merkle proof to prove the membership of the message and nonce in the merkle tree for the epoch. + * @param _data The data on the cross-domain chain for the message. + */ function _processMessageFromRoot( - uint256 stateId, - address sender, - bytes memory data - ) internal override validateSender(sender) { - (uint256 _epoch, bytes32 _batchMerkleRoot) = abi.decode(data, (uint256, bytes32)); - - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { - claims[_epoch].honest = true; - } else { - challenges[_epoch].honest = true; - } + uint256 _stateId, + address _sender, + bytes memory _data + ) internal override validateSender(_sender) { + // TODO + revert("Not implemented"); + // (uint256 _epoch, bytes32 _batchMerkleRoot) = abi.decode(data, (uint256, bytes32)); + + // fastInbox[_epoch] = _batchMerkleRoot; + + // if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + // claims[_epoch].honest = true; + // } else { + // challenges[_epoch].honest = true; + // } } function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { - return; + // TODO + revert("Not implemented"); } /** diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 94671bfb2..3a6ce0c20 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -13,7 +13,7 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeSender.sol"; import "./interfaces/ISafeBridgeSender.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/arbitrum/IArbSys.sol"; // Arbiturm sender specific +import "./canonical/arbitrum/IArbSys.sol"; // Arbiturm sender specific /** * Fast Bridge Sender diff --git a/contracts/src/bridge/SafeBridgeRouter.sol b/contracts/src/bridge/SafeBridgeRouter.sol index 64e12c60f..36cd0feed 100644 --- a/contracts/src/bridge/SafeBridgeRouter.sol +++ b/contracts/src/bridge/SafeBridgeRouter.sol @@ -12,9 +12,9 @@ pragma solidity ^0.8.0; import "./interfaces/ISafeBridgeReceiver.sol"; import "./interfaces/ISafeBridgeSender.sol"; -import "./interfaces/gnosis-chain/IAMB.sol"; -import "./interfaces/arbitrum/IInbox.sol"; -import "./interfaces/arbitrum/IOutbox.sol"; +import "./canonical/gnosis-chain/IAMB.sol"; +import "./canonical/arbitrum/IInbox.sol"; +import "./canonical/arbitrum/IOutbox.sol"; /** * Router on Ethereum from Arbitrum to Gnosis Chain. diff --git a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol index c6aa07e56..4eeedad64 100644 --- a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol +++ b/contracts/src/bridge/SafeBridgeRouterForPolygon.sol @@ -12,9 +12,9 @@ pragma solidity ^0.8.0; import "./interfaces/ISafeBridgeReceiver.sol"; import "./interfaces/ISafeBridgeSender.sol"; -import "./interfaces/polygon/FxBaseRootTunnel.sol"; -import "./interfaces/arbitrum/IInbox.sol"; -import "./interfaces/arbitrum/IOutbox.sol"; +import "./canonical/polygon/FxBaseRootTunnel.sol"; +import "./canonical/arbitrum/IInbox.sol"; +import "./canonical/arbitrum/IOutbox.sol"; /** * Router on Ethereum from Arbitrum to Polygon Chain. @@ -72,13 +72,9 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT _sendMessageToChild(_calldata); } - function _processMessageFromChild(bytes memory message) internal override { - } - - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32){ - - } + function _processMessageFromChild(bytes memory message) internal override {} + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) {} // ************************************* // // * Views * // diff --git a/contracts/src/bridge/interfaces/arbitrum/AddressAliasHelper.sol b/contracts/src/bridge/canonical/arbitrum/AddressAliasHelper.sol similarity index 100% rename from contracts/src/bridge/interfaces/arbitrum/AddressAliasHelper.sol rename to contracts/src/bridge/canonical/arbitrum/AddressAliasHelper.sol diff --git a/contracts/src/bridge/interfaces/arbitrum/IArbRetryableTx.sol b/contracts/src/bridge/canonical/arbitrum/IArbRetryableTx.sol similarity index 100% rename from contracts/src/bridge/interfaces/arbitrum/IArbRetryableTx.sol rename to contracts/src/bridge/canonical/arbitrum/IArbRetryableTx.sol diff --git a/contracts/src/bridge/interfaces/arbitrum/IArbSys.sol b/contracts/src/bridge/canonical/arbitrum/IArbSys.sol similarity index 100% rename from contracts/src/bridge/interfaces/arbitrum/IArbSys.sol rename to contracts/src/bridge/canonical/arbitrum/IArbSys.sol diff --git a/contracts/src/bridge/interfaces/arbitrum/IInbox.sol b/contracts/src/bridge/canonical/arbitrum/IInbox.sol similarity index 100% rename from contracts/src/bridge/interfaces/arbitrum/IInbox.sol rename to contracts/src/bridge/canonical/arbitrum/IInbox.sol diff --git a/contracts/src/bridge/interfaces/arbitrum/IOutbox.sol b/contracts/src/bridge/canonical/arbitrum/IOutbox.sol similarity index 100% rename from contracts/src/bridge/interfaces/arbitrum/IOutbox.sol rename to contracts/src/bridge/canonical/arbitrum/IOutbox.sol diff --git a/contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol b/contracts/src/bridge/canonical/gnosis-chain/IAMB.sol similarity index 100% rename from contracts/src/bridge/interfaces/gnosis-chain/IAMB.sol rename to contracts/src/bridge/canonical/gnosis-chain/IAMB.sol diff --git a/contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol b/contracts/src/bridge/canonical/polygon/FxBaseChildTunnel.sol similarity index 84% rename from contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol rename to contracts/src/bridge/canonical/polygon/FxBaseChildTunnel.sol index 75c8b28d9..90612f85f 100644 --- a/contracts/src/bridge/interfaces/polygon/FxBaseChildTunnel.sol +++ b/contracts/src/bridge/canonical/polygon/FxBaseChildTunnel.sol @@ -1,9 +1,18 @@ // SPDX-License-Identifier: MIT +// https://github.com/fx-portal/contracts/blob/main/contracts/tunnel/FxBaseChildTunnel.sol pragma solidity ^0.8.0; -import "./IFxMessageProcessor.sol"; +// IFxMessageProcessor represents interface to process message +interface IFxMessageProcessor { + function processMessageFromRoot( + uint256 stateId, + address rootMessageSender, + bytes calldata data + ) external; +} + /** - * @notice Mock child tunnel contract to receive and send message from L2 + * @dev Polygon-side abstract contract of the bidirectional Polygon/Ethereum bridge */ abstract contract FxBaseChildTunnel is IFxMessageProcessor { // MessageTunnel on L1 will get data from this event @@ -67,4 +76,4 @@ abstract contract FxBaseChildTunnel is IFxMessageProcessor { address sender, bytes memory message ) internal virtual; -} \ No newline at end of file +} diff --git a/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol b/contracts/src/bridge/canonical/polygon/FxBaseRootTunnel.sol similarity index 88% rename from contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol rename to contracts/src/bridge/canonical/polygon/FxBaseRootTunnel.sol index 533f36e55..fd9f0f2ee 100644 --- a/contracts/src/bridge/interfaces/polygon/FxBaseRootTunnel.sol +++ b/contracts/src/bridge/canonical/polygon/FxBaseRootTunnel.sol @@ -1,14 +1,35 @@ // SPDX-License-Identifier: MIT +// https://github.com/fx-portal/contracts/blob/main/contracts/tunnel/FxBaseRootTunnel.sol pragma solidity ^0.8.0; -import {RLPReader} from "../../../libraries/polygon/RLPReader.sol"; -import {MerklePatriciaProof} from "../../../libraries/polygon/MerklePatriciaProof.sol"; -import {Merkle} from "../../../libraries/polygon/Merkle.sol"; -import "../../../libraries/polygon/ExitPayloadReader.sol"; -import "./IFxStateSender.sol"; -import "./ICheckpointManager.sol"; +import {RLPReader} from "./lib/RLPReader.sol"; +import {MerklePatriciaProof} from "./lib/MerklePatriciaProof.sol"; +import {Merkle} from "./lib/Merkle.sol"; +import "./lib/ExitPayloadReader.sol"; + +interface IFxStateSender { + function sendMessageToChild(address _receiver, bytes calldata _data) external; +} + +contract ICheckpointManager { + struct HeaderBlock { + bytes32 root; + uint256 start; + uint256 end; + uint256 createdAt; + address proposer; + } + /** + * @notice mapping of checkpoint header numbers to block details + * @dev These checkpoints are submited by plasma contracts + */ + mapping(uint256 => HeaderBlock) public headerBlocks; +} +/** + * @dev Ethereum-side abstract contract of the bidirectional Polygon/Ethereum bridge + */ abstract contract FxBaseRootTunnel { using RLPReader for RLPReader.RLPItem; using Merkle for bytes32; @@ -159,4 +180,4 @@ abstract contract FxBaseRootTunnel { * @param message bytes message that was sent from Child Tunnel */ function _processMessageFromChild(bytes memory message) internal virtual; -} \ No newline at end of file +} diff --git a/contracts/src/libraries/polygon/ExitPayloadReader.sol b/contracts/src/bridge/canonical/polygon/lib/ExitPayloadReader.sol similarity index 100% rename from contracts/src/libraries/polygon/ExitPayloadReader.sol rename to contracts/src/bridge/canonical/polygon/lib/ExitPayloadReader.sol diff --git a/contracts/src/libraries/polygon/Merkle.sol b/contracts/src/bridge/canonical/polygon/lib/Merkle.sol old mode 100755 new mode 100644 similarity index 100% rename from contracts/src/libraries/polygon/Merkle.sol rename to contracts/src/bridge/canonical/polygon/lib/Merkle.sol diff --git a/contracts/src/libraries/polygon/MerklePatriciaProof.sol b/contracts/src/bridge/canonical/polygon/lib/MerklePatriciaProof.sol similarity index 100% rename from contracts/src/libraries/polygon/MerklePatriciaProof.sol rename to contracts/src/bridge/canonical/polygon/lib/MerklePatriciaProof.sol diff --git a/contracts/src/libraries/polygon/RLPReader.sol b/contracts/src/bridge/canonical/polygon/lib/RLPReader.sol similarity index 100% rename from contracts/src/libraries/polygon/RLPReader.sol rename to contracts/src/bridge/canonical/polygon/lib/RLPReader.sol diff --git a/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol b/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol deleted file mode 100644 index 486c32871..000000000 --- a/contracts/src/bridge/interfaces/polygon/ICheckpointManager.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract ICheckpointManager { - struct HeaderBlock { - bytes32 root; - uint256 start; - uint256 end; - uint256 createdAt; - address proposer; - } - - /** - * @notice mapping of checkpoint header numbers to block details - * @dev These checkpoints are submited by plasma contracts - */ - mapping(uint256 => HeaderBlock) public headerBlocks; -} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol b/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol deleted file mode 100644 index 8e1dbf640..000000000 --- a/contracts/src/bridge/interfaces/polygon/IFxMessageProcessor.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -// IFxMessageProcessor represents interface to process message -interface IFxMessageProcessor { - function processMessageFromRoot( - uint256 stateId, - address rootMessageSender, - bytes calldata data - ) external; -} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol b/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol deleted file mode 100644 index d0bb32b7e..000000000 --- a/contracts/src/bridge/interfaces/polygon/IFxStateSender.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -interface IFxStateSender { - function sendMessageToChild(address _receiver, bytes calldata _data) external; -} \ No newline at end of file diff --git a/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol index 13698fc07..29f589c5b 100644 --- a/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/single-message/SafeBridgeReceiverOnEthereum.sol @@ -11,8 +11,8 @@ pragma solidity ^0.8.0; import "./interfaces/ISafeBridgeReceiver.sol"; -import "../interfaces/arbitrum/IInbox.sol"; -import "../interfaces/arbitrum/IOutbox.sol"; +import "../canonical/arbitrum/IInbox.sol"; +import "../canonical/arbitrum/IOutbox.sol"; /** * Safe Bridge Receiver on Ethereum from Arbitrum diff --git a/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol index 494d19880..2e452dc9a 100644 --- a/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToArbitrumFromEthereum.sol @@ -10,9 +10,9 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IInbox.sol"; -import "../interfaces/arbitrum/IOutbox.sol"; -import "../interfaces/arbitrum/IArbRetryableTx.sol"; +import "../canonical/arbitrum/IInbox.sol"; +import "../canonical/arbitrum/IOutbox.sol"; +import "../canonical/arbitrum/IArbRetryableTx.sol"; import "./interfaces/ISafeBridgeSender.sol"; /** diff --git a/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol index b7c856575..d03061e0a 100644 --- a/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToEthereum.sol @@ -10,8 +10,8 @@ pragma solidity ^0.8.0; -import "../interfaces/arbitrum/IArbSys.sol"; -import "../interfaces/arbitrum/AddressAliasHelper.sol"; +import "../canonical/arbitrum/IArbSys.sol"; +import "../canonical/arbitrum/AddressAliasHelper.sol"; import "./interfaces/ISafeBridgeSender.sol"; diff --git a/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol b/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol index c43f63647..722852767 100644 --- a/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol +++ b/contracts/src/bridge/single-message/SafeBridgeSenderToGnosis.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../interfaces/gnosis-chain/IAMB.sol"; +import "../canonical/gnosis-chain/IAMB.sol"; import "./interfaces/ISafeBridgeSender.sol"; /** diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index 491658c95..ee60dfd83 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -13,7 +13,7 @@ pragma solidity ^0.8.0; import "../interfaces/IFastBridgeSender.sol"; import "../interfaces/ISafeBridgeSender.sol"; import "../interfaces/ISafeBridgeReceiver.sol"; -import "../interfaces/arbitrum/IArbSys.sol"; // Arbiturm sender specific +import "../canonical/arbitrum/IArbSys.sol"; // Arbiturm sender specific /** * Fast Bridge Sender diff --git a/contracts/src/bridge/test/arbitrum/ArbSysMock.sol b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol index 7febe655b..7fd46627a 100644 --- a/contracts/src/bridge/test/arbitrum/ArbSysMock.sol +++ b/contracts/src/bridge/test/arbitrum/ArbSysMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../../interfaces/arbitrum/IArbSys.sol"; +import "../../canonical/arbitrum/IArbSys.sol"; contract ArbSysMock { function sendTxToL1(address destination, bytes calldata calldataForL1) diff --git a/contracts/src/bridge/test/arbitrum/BridgeMock.sol b/contracts/src/bridge/test/arbitrum/BridgeMock.sol index f98b04c36..74c0187a1 100644 --- a/contracts/src/bridge/test/arbitrum/BridgeMock.sol +++ b/contracts/src/bridge/test/arbitrum/BridgeMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../../interfaces/arbitrum/IInbox.sol"; +import "../../canonical/arbitrum/IInbox.sol"; contract BridgeMock is IBridge { address public outbox; diff --git a/contracts/src/bridge/test/arbitrum/InboxMock.sol b/contracts/src/bridge/test/arbitrum/InboxMock.sol index 682cfb25e..8986ce891 100644 --- a/contracts/src/bridge/test/arbitrum/InboxMock.sol +++ b/contracts/src/bridge/test/arbitrum/InboxMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../../interfaces/arbitrum/IInbox.sol"; +import "../../canonical/arbitrum/IInbox.sol"; contract InboxMock is IInbox { IBridge public arbBridge; diff --git a/contracts/src/bridge/test/arbitrum/OutboxMock.sol b/contracts/src/bridge/test/arbitrum/OutboxMock.sol index 684c87ea6..601e99ac5 100644 --- a/contracts/src/bridge/test/arbitrum/OutboxMock.sol +++ b/contracts/src/bridge/test/arbitrum/OutboxMock.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../../interfaces/arbitrum/IOutbox.sol"; +import "../../canonical/arbitrum/IOutbox.sol"; contract OutboxMock is IOutbox { address public safeBridgeSender; diff --git a/contracts/src/bridge/test/gnosis-chain/MockAMB.sol b/contracts/src/bridge/test/gnosis-chain/MockAMB.sol index 944bd1f0b..1f6a837c7 100644 --- a/contracts/src/bridge/test/gnosis-chain/MockAMB.sol +++ b/contracts/src/bridge/test/gnosis-chain/MockAMB.sol @@ -2,7 +2,7 @@ // https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/mocks/AMBMock.sol pragma solidity ^0.8.0; -import "../../interfaces/gnosis-chain/IAMB.sol"; +import "../../canonical/gnosis-chain/IAMB.sol"; import "../../../libraries/gnosis-chain/Bytes.sol"; contract MockAMB is IAMB { From 064cdab7fd5867d5d80f427393dcc756546742b9 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 16 Jul 2022 19:14:05 +0200 Subject: [PATCH 19/21] feat: guards against corner cases in FB receivers, refactors, typo fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made the receiver verify function names more explicit: - verify() ➝ verifyBatch() - verifySafe() ➝ verifyBatchSafe() - verifyAndRelay() ➝ verifyAndRelayMessage() Added new events in FB receiver: BatchVerified and BatchNotVerified. Added claim.verificationAttempted to prevent multiple executions of verifyBatch() Added guards in FB receiver withdraw() against potential ETH leaks (undiscovered corner cases). Renamed the FB sender event SendBatch ➝ BatchOutgoing for consistency. Introduced a SafeBridgeRouter abstraction. Moved 3rd party bridge code to a dedicated "canonical" folder. Consolidated the polygon-specific 3rd party dependencies. Moved Merkle*Exposed to bridge/test/merkle and flipped the "_" function prefix. Made it explicit that FastBridgeReceiverOnPolygon is WIP. Updated the @authors pragma where needed. --- .../bridge/FastBridgeReceiverOnEthereum.sol | 113 ++++++++++-------- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 111 ++++++++++------- .../bridge/FastBridgeReceiverOnPolygon.sol | 86 ++++++++----- contracts/src/bridge/FastBridgeSender.sol | 50 ++++---- ...outer.sol => SafeBridgeRouterToGnosis.sol} | 23 ++-- ...ygon.sol => SafeBridgeRouterToPolygon.sol} | 26 ++-- .../bridge/interfaces/IFastBridgeReceiver.sol | 26 +++- .../bridge/interfaces/IFastBridgeSender.sol | 17 ++- .../bridge/interfaces/ISafeBridgeReceiver.sol | 10 +- .../bridge/interfaces/ISafeBridgeRouter.sol | 29 +++++ .../bridge/interfaces/ISafeBridgeSender.sol | 8 ++ contracts/src/bridge/merkle/MerkleProof.sol | 6 +- contracts/src/bridge/merkle/MerkleTree.sol | 8 +- .../src/bridge/test/FastBridgeSenderMock.sol | 21 +--- .../merkle}/MerkleProofExposed.sol | 6 +- .../merkle}/MerkleTreeExposed.sol | 10 +- contracts/src/gateway/ForeignGateway.sol | 2 +- contracts/src/gateway/HomeGateway.sol | 2 +- .../gateway/interfaces/IForeignGateway.sol | 8 ++ .../interfaces/IForeignGatewayBase.sol | 8 ++ .../interfaces/IForeignGatewayMock.sol | 8 ++ .../src/gateway/interfaces/IHomeGateway.sol | 8 ++ .../gateway/interfaces/IHomeGatewayBase.sol | 8 ++ contracts/test/bridge/merkle/index.ts | 18 +-- contracts/test/integration/index.ts | 28 ++--- 25 files changed, 393 insertions(+), 247 deletions(-) rename contracts/src/bridge/{SafeBridgeRouter.sol => SafeBridgeRouterToGnosis.sol} (77%) rename contracts/src/bridge/{SafeBridgeRouterForPolygon.sol => SafeBridgeRouterToPolygon.sol} (82%) create mode 100644 contracts/src/bridge/interfaces/ISafeBridgeRouter.sol rename contracts/src/bridge/{merkle/test => test/merkle}/MerkleProofExposed.sol (85%) rename contracts/src/bridge/{merkle/test => test/merkle}/MerkleTreeExposed.sol (59%) diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 73d1be35a..4ab30dd92 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -12,8 +12,8 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./canonical/arbitrum/IInbox.sol"; // Ethereum Receiver Specific -import "./canonical/arbitrum/IOutbox.sol"; // Ethereum Receiver Specific +import "./canonical/arbitrum/IInbox.sol"; +import "./canonical/arbitrum/IOutbox.sol"; /** * Fast Receiver On Ethereum @@ -78,6 +78,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive address bridger; uint32 timestamp; bool honest; + bool verificationAttempted; bool depositAndRewardWithdrawn; } @@ -114,10 +115,9 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive require(msg.value >= deposit, "Insufficient claim deposit."); require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - uint256 epoch = block.timestamp / epochPeriod; + uint256 epochNow = block.timestamp / epochPeriod; // allow claim about current or previous epoch - require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); - + require(_epoch == epochNow || _epoch == epochNow + 1, "Invalid Claim"); require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); claims[_epoch] = Claim({ @@ -125,9 +125,9 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive bridger: msg.sender, timestamp: uint32(block.timestamp), honest: false, + verificationAttempted: false, depositAndRewardWithdrawn: false }); - emit ClaimReceived(_epoch, _batchMerkleRoot); } @@ -138,12 +138,11 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive function challenge(uint256 _epoch) external payable override { require(msg.value >= deposit, "Not enough claim deposit"); - // can only challenge the only active claim, about the previous epoch + // Can only challenge the only active claim, about the previous epoch require(claims[_epoch].bridger != address(0), "No claim to challenge."); require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); - emit ClaimChallenged(_epoch); } @@ -151,9 +150,10 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive * @dev Resolves the optimistic claim for '_epoch'. * @param _epoch The epoch of the optimistic claim. */ - function verify(uint256 _epoch) public { + function verifyBatch(uint256 _epoch) external override { Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require(claim.verificationAttempted == false, "Optimistic verification already attempted."); require( block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period has not yet elapsed." @@ -163,6 +163,36 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive // optimistic happy path claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; + emit BatchVerified(_epoch); + } else { + // unhappy path + emit BatchNotVerified(_epoch); + } + claim.verificationAttempted = true; + } + + /** + * 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 verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + // Corner cases: + // a) No claim submitted, + // b) Receiving the root of an empty batch, + // c) Batch root is zero. + if (claims[_epoch].bridger != address(0)) { + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + claims[_epoch].honest = false; + challenges[_epoch].honest = true; + } } } @@ -172,7 +202,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive * @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. */ - function verifyAndRelay( + function verifyAndRelayMessage( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message @@ -181,30 +211,10 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive require(batchMerkleRoot != bytes32(0), "Invalid epoch."); // Claim assessment if any - bytes32 messageHash = sha256(_message); - - require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(validateProof(_proof, sha256(_message), batchMerkleRoot) == true, "Invalid proof."); require(_checkReplayAndRelay(_epoch, _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 onlyFromSafeBridge { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { - claims[_epoch].honest = true; - } else { - challenges[_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. @@ -217,7 +227,9 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); uint256 amount = deposit; - if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + if (challenges[_epoch].challenger != address(0) && challenges[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } claim.depositAndRewardWithdrawn = true; @@ -236,7 +248,11 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive require(challenge.honest == true, "Challenge not verified."); require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); - uint256 amount = deposit + deposit / 2; + uint256 amount = deposit; + if (claims[_epoch].bridger != address(0) && claims[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } + challenge.depositAndRewardWithdrawn = true; payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. @@ -247,10 +263,12 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive // * 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. + /** + * @dev Validates membership of leaf in merkle tree with merkle proof. + * Note: Inlined from `merkle/MerkleProof.sol` for performance. + * @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, @@ -260,9 +278,10 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive 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.. + /** + * @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) private pure returns (bytes32) { uint256 proofLength = proof.length; @@ -292,9 +311,10 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive // ************************************* // /** - * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. + * @dev Returns the `start` and `end` time of challenge period for this `epoch`. + * @param _epoch The epoch of the claim to request the challenge period. + * @return start The start time of the challenge period. + * @return end The end time of the challenge period. */ function claimChallengePeriod(uint256 _epoch) external view override returns (uint256 start, uint256 end) { // start begins latest after the claim deadline expiry @@ -302,7 +322,6 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive // can only challenge the only active claim, about the previous epoch start = claims[_epoch].timestamp; end = start + challengePeriod; - return (start, end); } // ************************ // @@ -314,12 +333,12 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); uint256 index = nonce / 256; - uint256 offset = nonce - index * 256; - + uint256 offset = nonce % 256; bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); (success, ) = receiver.call(data); + // Checks-Effects-Interaction } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index abc512cf8..e1e3a8dd2 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./canonical/gnosis-chain/IAMB.sol"; // Gnosis Receiver Specific +import "./canonical/gnosis-chain/IAMB.sol"; /** * Fast Receiver On Gnosis @@ -76,6 +76,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver address bridger; uint32 timestamp; bool honest; + bool verificationAttempted; bool depositAndRewardWithdrawn; } @@ -112,10 +113,9 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver require(msg.value >= deposit, "Insufficient claim deposit."); require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - uint256 epoch = block.timestamp / epochPeriod; + uint256 epochNow = block.timestamp / epochPeriod; // allow claim about current or previous epoch - require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); - + require(_epoch == epochNow || _epoch == epochNow + 1, "Invalid Claim"); require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); claims[_epoch] = Claim({ @@ -123,9 +123,9 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver bridger: msg.sender, timestamp: uint32(block.timestamp), honest: false, + verificationAttempted: false, depositAndRewardWithdrawn: false }); - emit ClaimReceived(_epoch, _batchMerkleRoot); } @@ -136,12 +136,11 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver function challenge(uint256 _epoch) external payable override { require(msg.value >= deposit, "Not enough claim deposit"); - // can only challenge the only active claim, about the previous epoch + // Can only challenge the only active claim, about the previous epoch require(claims[_epoch].bridger != address(0), "No claim to challenge."); require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); - emit ClaimChallenged(_epoch); } @@ -149,9 +148,10 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver * @dev Resolves the optimistic claim for '_epoch'. * @param _epoch The epoch of the optimistic claim. */ - function verify(uint256 _epoch) public { + function verifyBatch(uint256 _epoch) external override { Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require(claim.verificationAttempted == false, "Optimistic verification already attempted."); require( block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period has not yet elapsed." @@ -161,6 +161,36 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver // optimistic happy path claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; + emit BatchVerified(_epoch); + } else { + // unhappy path + emit BatchNotVerified(_epoch); + } + claim.verificationAttempted = true; + } + + /** + * 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 verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); + + fastInbox[_epoch] = _batchMerkleRoot; + + // Corner cases: + // a) No claim submitted, + // b) Receiving the root of an empty batch, + // c) Batch root is zero. + if (claims[_epoch].bridger != address(0)) { + if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { + claims[_epoch].honest = true; + } else { + claims[_epoch].honest = false; + challenges[_epoch].honest = true; + } } } @@ -170,7 +200,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver * @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. */ - function verifyAndRelay( + function verifyAndRelayMessage( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message @@ -179,30 +209,10 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver require(batchMerkleRoot != bytes32(0), "Invalid epoch."); // Claim assessment if any - bytes32 messageHash = sha256(_message); - - require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(validateProof(_proof, sha256(_message), batchMerkleRoot) == true, "Invalid proof."); require(_checkReplayAndRelay(_epoch, _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 onlyFromSafeBridge { - require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - - fastInbox[_epoch] = _batchMerkleRoot; - - if (_batchMerkleRoot == claims[_epoch].batchMerkleRoot) { - claims[_epoch].honest = true; - } else { - challenges[_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. @@ -215,7 +225,9 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); uint256 amount = deposit; - if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + if (challenges[_epoch].challenger != address(0) && challenges[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } claim.depositAndRewardWithdrawn = true; @@ -234,7 +246,11 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver require(challenge.honest == true, "Challenge not verified."); require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); - uint256 amount = deposit + deposit / 2; + uint256 amount = deposit; + if (claims[_epoch].bridger != address(0) && claims[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } + challenge.depositAndRewardWithdrawn = true; payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. @@ -245,10 +261,12 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver // * 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. + /** + * @dev Validates membership of leaf in merkle tree with merkle proof. + * Note: Inlined from `merkle/MerkleProof.sol` for performance. + * @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, @@ -258,9 +276,10 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver 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.. + /** + * @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) private pure returns (bytes32) { uint256 proofLength = proof.length; @@ -290,9 +309,10 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver // ************************************* // /** - * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. + * @dev Returns the `start` and `end` time of challenge period for this `epoch`. + * @param _epoch The epoch of the claim to request the challenge period. + * @return start The start time of the challenge period. + * @return end The end time of the challenge period. */ function claimChallengePeriod(uint256 _epoch) external view override returns (uint256 start, uint256 end) { // start begins latest after the claim deadline expiry @@ -300,7 +320,6 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver // can only challenge the only active claim, about the previous epoch start = claims[_epoch].timestamp; end = start + challengePeriod; - return (start, end); } // ************************ // @@ -312,12 +331,12 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); uint256 index = nonce / 256; - uint256 offset = nonce - index * 256; - + uint256 offset = nonce % 256; bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); (success, ) = receiver.call(data); + // Checks-Effects-Interaction } } diff --git a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol index 4594e23cf..14594915a 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./canonical/polygon/FxBaseChildTunnel.sol"; // Polygon Receiver Specific +import "./canonical/polygon/FxBaseChildTunnel.sol"; /** * Fast Receiver On Polygon @@ -51,6 +51,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, deposit = _deposit; epochPeriod = _epochPeriod; challengePeriod = _challengePeriod; + safeBridgeSender = _safeBridgeSender; setFxRootTunnel(_safeBridgeSender); } @@ -69,6 +70,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, address bridger; uint32 timestamp; bool honest; + bool verificationAttempted; bool depositAndRewardWithdrawn; } @@ -85,6 +87,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, uint256 public immutable deposit; // The deposit required to submit a claim or challenge uint256 public immutable override epochPeriod; // Epochs mark the period between potential batches of messages. uint256 public immutable override challengePeriod; // Epochs mark the period between potential batches of messages. + address public immutable safeBridgeSender; // The address of the Safe Bridge Sender 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 @@ -104,10 +107,9 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(msg.value >= deposit, "Insufficient claim deposit."); require(_batchMerkleRoot != bytes32(0), "Invalid claim."); - uint256 epoch = block.timestamp / epochPeriod; + uint256 epochNow = block.timestamp / epochPeriod; // allow claim about current or previous epoch - require(_epoch == epoch || _epoch == epoch + 1, "Invalid Claim"); - + require(_epoch == epochNow || _epoch == epochNow + 1, "Invalid Claim"); require(claims[_epoch].bridger == address(0), "Claim already made for most recent finalized epoch."); claims[_epoch] = Claim({ @@ -115,9 +117,9 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, bridger: msg.sender, timestamp: uint32(block.timestamp), honest: false, + verificationAttempted: false, depositAndRewardWithdrawn: false }); - emit ClaimReceived(_epoch, _batchMerkleRoot); } @@ -128,12 +130,11 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, function challenge(uint256 _epoch) external payable override { require(msg.value >= deposit, "Not enough claim deposit"); - // can only challenge the only active claim, about the previous epoch + // Can only challenge the only active claim, about the previous epoch require(claims[_epoch].bridger != address(0), "No claim to challenge."); require(block.timestamp < uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period elapsed."); challenges[_epoch] = Challenge({challenger: msg.sender, honest: false, depositAndRewardWithdrawn: false}); - emit ClaimChallenged(_epoch); } @@ -141,9 +142,10 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, * @dev Resolves the optimistic claim for '_epoch'. * @param _epoch The epoch of the optimistic claim. */ - function verify(uint256 _epoch) public { + function verifyBatch(uint256 _epoch) external override { Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Invalid epoch, no claim to verify."); + require(claim.verificationAttempted == false, "Optimistic verification already attempted."); require( block.timestamp > uint256(claims[_epoch].timestamp) + challengePeriod, "Challenge period has not yet elapsed." @@ -153,7 +155,23 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, // optimistic happy path claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; + emit BatchVerified(_epoch); + } else { + // unhappy path + emit BatchNotVerified(_epoch); } + claim.verificationAttempted = true; + } + + /** + * 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 verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + // TODO + revert("Not implemented"); } /** @@ -162,7 +180,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, * @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. */ - function verifyAndRelay( + function verifyAndRelayMessage( uint256 _epoch, bytes32[] calldata _proof, bytes calldata _message @@ -171,9 +189,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(batchMerkleRoot != bytes32(0), "Invalid epoch."); // Claim assessment if any - bytes32 messageHash = sha256(_message); - - require(validateProof(_proof, messageHash, batchMerkleRoot) == true, "Invalid proof."); + require(validateProof(_proof, sha256(_message), batchMerkleRoot) == true, "Invalid proof."); require(_checkReplayAndRelay(_epoch, _message), "Failed to call contract"); // Checks-Effects-Interaction } @@ -201,11 +217,6 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, // } } - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { - // TODO - revert("Not implemented"); - } - /** * @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. @@ -218,7 +229,9 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); uint256 amount = deposit; - if (challenges[_epoch].challenger != address(0)) amount = amount + deposit / 2; // half burnt + if (challenges[_epoch].challenger != address(0) && challenges[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } claim.depositAndRewardWithdrawn = true; @@ -237,7 +250,11 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, require(challenge.honest == true, "Challenge not verified."); require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); - uint256 amount = deposit + deposit / 2; + uint256 amount = deposit; + if (claims[_epoch].bridger != address(0) && claims[_epoch].honest == false) { + amount += deposit / 2; // half burnt + } + challenge.depositAndRewardWithdrawn = true; payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. @@ -248,10 +265,12 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, // * 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. + /** + * @dev Validates membership of leaf in merkle tree with merkle proof. + * Note: Inlined from `merkle/MerkleProof.sol` for performance. + * @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, @@ -261,9 +280,10 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, 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.. + /** + * @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) private pure returns (bytes32) { uint256 proofLength = proof.length; @@ -293,9 +313,10 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, // ************************************* // /** - * Returns the `start` and `end` time of challenge period for the claim for this `_epoch`. - * start The start time of the challenge period. - * end The end time of the challenge period. + * @dev Returns the `start` and `end` time of challenge period for this `epoch`. + * @param _epoch The epoch of the claim to request the challenge period. + * @return start The start time of the challenge period. + * @return end The end time of the challenge period. */ function claimChallengePeriod(uint256 _epoch) external view override returns (uint256 start, uint256 end) { // start begins latest after the claim deadline expiry @@ -303,7 +324,6 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, // can only challenge the only active claim, about the previous epoch start = claims[_epoch].timestamp; end = start + challengePeriod; - return (start, end); } // ************************ // @@ -315,12 +335,12 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, (uint256 nonce, address receiver, bytes memory data) = abi.decode(_messageData, (uint256, address, bytes)); uint256 index = nonce / 256; - uint256 offset = nonce - index * 256; - + uint256 offset = nonce % 256; bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); (success, ) = receiver.call(data); + // Checks-Effects-Interaction } } diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 3a6ce0c20..8a20218df 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -13,11 +13,11 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeSender.sol"; import "./interfaces/ISafeBridgeSender.sol"; import "./interfaces/ISafeBridgeReceiver.sol"; -import "./canonical/arbitrum/IArbSys.sol"; // Arbiturm sender specific +import "./canonical/arbitrum/IArbSys.sol"; // Arbitrum sender specific /** * Fast Bridge Sender - * Counterpart of `FastReceiver` + * Counterpart of `FastBridgeReceiver` */ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // **************************************** // @@ -41,17 +41,17 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { bytes32 batchMerkleRoot = fastOutbox[_epoch]; // Safe Bridge message envelope - bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; + bytes4 methodSelector = ISafeBridgeReceiver.verifySafeBatch.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); - bytes32 txID = _sendSafe(safeBridgeReceiver, safeMessageData); - emit SentSafe(_epoch, txID); + bytes32 ticketID = _sendSafe(safeBridgeReceiver, safeMessageData); + emit SentSafe(_epoch, ticketID); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { - uint256 txID = ARB_SYS.sendTxToL1(_receiver, _calldata); + uint256 ticketID = ARB_SYS.sendTxToL1(_receiver, _calldata); - return bytes32(txID); + return bytes32(ticketID); } /** @@ -86,15 +86,6 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // supports 2^64 messages. bytes32[64] public batch; uint256 public batchSize; - // ************************************* // - // * Events * // - // ************************************* // - - /** - * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiver. - */ - event SendBatch(uint256 indexed batchID, uint256 batchSize, uint256 epoch, bytes32 batchMerkleRoot); // ************************************* // // * State Modifiers * // @@ -107,9 +98,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { */ function sendFast(address _receiver, bytes memory _calldata) external override { (bytes32 fastMessageHash, bytes memory fastMessage) = _encode(_receiver, _calldata); - emit MessageReceived(fastMessage, fastMessageHash); - appendMessage(fastMessageHash); // add message to merkle tree } @@ -125,7 +114,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // set merkle root in outbox bytes32 batchMerkleRoot = getMerkleRoot(); fastOutbox[epoch] = batchMerkleRoot; - emit SendBatch(currentBatchID, batchSize, epoch, batchMerkleRoot); + emit BatchOutgoing(currentBatchID, batchSize, epoch, batchMerkleRoot); // reset batchSize = 0; @@ -186,11 +175,11 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { // * Merkle Tree * // // ********************************* // - /** @dev Append data into merkle tree. - * `O(log(n))` where - * `n` is the number of leaves. - * Note: Although each insertion is O(log(n)), - * Complexity of n insertions is O(n). + /** + * @dev Append data into merkle tree. + * `O(log(n))` where `n` is the number of leaves. + * Note: Although each insertion is O(log(n)), complexity of n insertions is O(n). + * Note: Inlined from `merkle/MerkleTree.sol` for performance. * @param leaf The leaf (already hashed) to insert in the merkle tree. */ function appendMessage(bytes32 leaf) internal { @@ -206,14 +195,14 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { bytes32 node = batch[height]; if (node > leaf) assembly { - // effecient hash + // efficient hash mstore(0x00, leaf) mstore(0x20, node) leaf := keccak256(0x00, 0x40) } else assembly { - // effecient hash + // efficient hash mstore(0x00, node) mstore(0x20, leaf) leaf := keccak256(0x00, 0x40) @@ -225,9 +214,10 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { } } - /** @dev Gets the current merkle root. - * `O(log(n))` where - * `n` is the number of leaves. + /** + * @dev Gets the current merkle root. + * `O(log(n))` where `n` is the number of leaves. + * Note: Inlined from `merkle/MerkleTree.sol` for performance. */ function getMerkleRoot() internal view returns (bytes32) { unchecked { @@ -243,7 +233,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { isFirstHash = false; } else { bytes32 hash = batch[height]; - // effecient hash + // efficient hash if (hash > node) assembly { mstore(0x00, node) diff --git a/contracts/src/bridge/SafeBridgeRouter.sol b/contracts/src/bridge/SafeBridgeRouterToGnosis.sol similarity index 77% rename from contracts/src/bridge/SafeBridgeRouter.sol rename to contracts/src/bridge/SafeBridgeRouterToGnosis.sol index 36cd0feed..0b04ca4f1 100644 --- a/contracts/src/bridge/SafeBridgeRouter.sol +++ b/contracts/src/bridge/SafeBridgeRouterToGnosis.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shotaronowhere] + * @authors: [@shotaronowhere, @jaybuidl] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,8 +10,7 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/ISafeBridgeRouter.sol"; import "./canonical/gnosis-chain/IAMB.sol"; import "./canonical/arbitrum/IInbox.sol"; import "./canonical/arbitrum/IOutbox.sol"; @@ -19,13 +18,7 @@ import "./canonical/arbitrum/IOutbox.sol"; /** * Router on Ethereum from Arbitrum to Gnosis Chain. */ -contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender { - // ************************************* // - // * Events * // - // ************************************* // - - event safeRelayed(bytes32 indexed txID); - +contract SafeBridgeRouter is ISafeBridgeRouter { // ************************************* // // * Storage * // // ************************************* // @@ -38,7 +31,7 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender { /** * @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 _amb The address of the AMB contract on Ethereum. * @param _safeBridgeSender The safe bridge sender on Arbitrum. * @param _fastBridgeReceiverOnGnosisChain The fast bridge receiver on Gnosis Chain. */ @@ -60,15 +53,15 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender { * @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 verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + function verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only."); - bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; + bytes4 methodSelector = ISafeBridgeReceiver.verifySafeBatch.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, _batchMerkleRoot); // replace maxGasPerTx with safe level for production deployment - bytes32 txID = _sendSafe(fastBridgeReceiverOnGnosisChain, safeMessageData); - emit safeRelayed(txID); + bytes32 ticketID = _sendSafe(fastBridgeReceiverOnGnosisChain, safeMessageData); + emit SafeRelayed(ticketID); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { diff --git a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol b/contracts/src/bridge/SafeBridgeRouterToPolygon.sol similarity index 82% rename from contracts/src/bridge/SafeBridgeRouterForPolygon.sol rename to contracts/src/bridge/SafeBridgeRouterToPolygon.sol index 4eeedad64..56653e4bb 100644 --- a/contracts/src/bridge/SafeBridgeRouterForPolygon.sol +++ b/contracts/src/bridge/SafeBridgeRouterToPolygon.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shotaronowhere @hrishibhat] + * @authors: [@shotaronowhere, @hrishibhat, @jaybuidl] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -10,8 +10,7 @@ pragma solidity ^0.8.0; -import "./interfaces/ISafeBridgeReceiver.sol"; -import "./interfaces/ISafeBridgeSender.sol"; +import "./interfaces/ISafeBridgeRouter.sol"; import "./canonical/polygon/FxBaseRootTunnel.sol"; import "./canonical/arbitrum/IInbox.sol"; import "./canonical/arbitrum/IOutbox.sol"; @@ -19,13 +18,7 @@ import "./canonical/arbitrum/IOutbox.sol"; /** * Router on Ethereum from Arbitrum to Polygon Chain. */ -contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootTunnel { - // ************************************* // - // * Events * // - // ************************************* // - - event safeRelayed(bytes32 indexed txID); - +contract SafeBridgeRouterToPolygon is ISafeBridgeRouter, FxBaseRootTunnel { // ************************************* // // * Storage * // // ************************************* // @@ -40,7 +33,6 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT * @param _safeBridgeSender The safe bridge sender on Arbitrum. * @param _fastBridgeReceiverOnPolygon The fast bridge receiver on Polygon Chain. */ - constructor( IInbox _inbox, address _checkpointManager, @@ -59,7 +51,7 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT * @param _epoch The epoch associated with the _batchmerkleRoot. * @param _batchMerkleRoot The true batchP merkle root for the epoch sent by the safe bridge. * @return Unique id to track the message request/transaction. */ - function verifySafe(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { + function verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external override onlyFromSafeBridge { // Note: fxRoot sends message directly to fxchild hence no need for encodeWithSelector bytes memory safeMessageData = abi.encode(_epoch, _batchMerkleRoot); @@ -72,9 +64,15 @@ contract SafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender, FxBaseRootT _sendMessageToChild(_calldata); } - function _processMessageFromChild(bytes memory message) internal override {} + function _processMessageFromChild(bytes memory message) internal override { + // TODO + revert("Not implemented"); + } - function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) {} + function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { + // TODO + revert("Not implemented"); + } // ************************************* // // * Views * // diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 46063d9b7..8ee4a52e7 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @authors: [@jaybuidl, @shotaronowhere, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -20,13 +20,25 @@ interface IFastBridgeReceiver { * @param _epoch The epoch for which the the claim was made. * @param _batchMerkleRoot The timestamp of the claim creation. */ - event ClaimReceived(uint256 _epoch, bytes32 indexed _batchMerkleRoot); + event ClaimReceived(uint256 indexed _epoch, bytes32 indexed _batchMerkleRoot); /** * @dev The Fast Bridge participants watch for these events to call `sendSafeFallback()` on the sending side. * @param _epoch The epoch associated with the challenged claim. */ - event ClaimChallenged(uint256 _epoch); + event ClaimChallenged(uint256 indexed _epoch); + + /** + * @dev The Fast Bridge participants watch for these events to know optimistic verification has succeeded. The messages are ready to be relayed. + * @param _epoch The epoch associated with the batch. + */ + event BatchVerified(uint256 indexed _epoch); + + /** + * @dev The Fast Bridge users watch for these events to know that optimistic verification has failed. The Fast Bridge sender will fallback to the Safe Bridge. + * @param _epoch The epoch associated with the batch. + */ + event BatchNotVerified(uint256 indexed _epoch); // ************************************* // // * Function Modifiers * // @@ -45,13 +57,19 @@ interface IFastBridgeReceiver { */ function challenge(uint256 _epoch) external payable; + /** + * @dev Resolves the optimistic claim for '_epoch'. + * @param _epoch The epoch of the optimistic claim. + */ + function verifyBatch(uint256 _epoch) external; + /** * @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. */ - function verifyAndRelay( + 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 9e6f6062e..bfa3ab1a6 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeSender.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeSender.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; interface IFastBridgeSender { @@ -21,13 +29,18 @@ interface IFastBridgeSender { */ event SentSafe(uint256 indexed epoch, bytes32 canonicalBridgeMessageID); + /** + * The bridgers need to watch for these events and relay the + * batchMerkleRoot on the FastBridgeReceiver. + */ + event BatchOutgoing(uint256 indexed batchID, uint256 batchSize, uint256 epoch, bytes32 batchMerkleRoot); + // ************************************* // // * Function Modifiers * // // ************************************* // /** - * Note: Access must be restricted by the receiving contract. - * Message is sent with the message sender address as the first argument. + * Note: Access must be restricted by the receiving gateway by checking the sender 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. diff --git a/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol b/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol index 58e11a186..33941e792 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeReceiver.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; abstract contract ISafeBridgeReceiver { @@ -9,7 +17,7 @@ abstract contract ISafeBridgeReceiver { * @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 verifySafeBatch(uint256 _epoch, bytes32 _batchMerkleRoot) external virtual; function isSentBySafeBridge() internal view virtual returns (bool); diff --git a/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol b/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol new file mode 100644 index 000000000..de3cbd4e0 --- /dev/null +++ b/contracts/src/bridge/interfaces/ISafeBridgeRouter.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./ISafeBridgeReceiver.sol"; +import "./ISafeBridgeSender.sol"; + +/** + * Abstract Router to forward messages between Safe Bridges. + */ +abstract contract ISafeBridgeRouter is ISafeBridgeReceiver, ISafeBridgeSender { + // ************************************* // + // * Events * // + // ************************************* // + + /** + * @dev Event emitted when a message is relayed to another Safe Bridge. + * @param _ticketID The unique identifier provided by the underlying canonical bridge. + */ + event SafeRelayed(bytes32 indexed _ticketID); +} diff --git a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol index ddcc8df06..efba3113d 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; abstract contract ISafeBridgeSender { diff --git a/contracts/src/bridge/merkle/MerkleProof.sol b/contracts/src/bridge/merkle/MerkleProof.sol index fa91a3b35..a7a1ee369 100644 --- a/contracts/src/bridge/merkle/MerkleProof.sol +++ b/contracts/src/bridge/merkle/MerkleProof.sol @@ -21,19 +21,19 @@ contract MerkleProof { * @param leaf The leaf to validate membership in merkle tree. * @param merkleRoot The root of the merkle tree. */ - function validateProof( + function _validateProof( bytes32[] memory proof, bytes32 leaf, bytes32 merkleRoot ) internal pure returns (bool) { - return (merkleRoot == calculateRoot(proof, leaf)); + 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) private 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/MerkleTree.sol b/contracts/src/bridge/merkle/MerkleTree.sol index 876a4028b..67dde2ccf 100644 --- a/contracts/src/bridge/merkle/MerkleTree.sol +++ b/contracts/src/bridge/merkle/MerkleTree.sol @@ -36,7 +36,7 @@ contract MerkleTree { * Complexity of n insertions is O(n). * @param leaf The leaf (already hashed) to insert in the merkle tree. */ - function appendMessage(bytes32 leaf) internal { + 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/ @@ -70,8 +70,8 @@ contract MerkleTree { * `O(log(n))` where * `n` is the number of leaves. */ - function getMerkleRootAndReset() internal returns (bytes32 batchMerkleRoot) { - batchMerkleRoot = getMerkleRoot(); + function _getMerkleRootAndReset() internal returns (bytes32 batchMerkleRoot) { + batchMerkleRoot = _getMerkleRoot(); batchSize = 0; } @@ -79,7 +79,7 @@ contract MerkleTree { * `O(log(n))` where * `n` is the number of leaves. */ - function getMerkleRoot() internal view returns (bytes32) { + function _getMerkleRoot() internal view returns (bytes32) { bytes32 node; uint256 size = batchSize; uint256 height = 0; diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index ee60dfd83..a18c676e7 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -30,7 +30,7 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { // * Events * // // ************************************* // - event L2ToL1TxCreated(uint256 indexed txID); + event L2ToL1TxCreated(uint256 indexed ticketID); // ************************************* // // * Storage * // @@ -47,17 +47,17 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { bytes32 batchMerkleRoot = fastOutbox[_epoch]; // Safe Bridge message envelope - bytes4 methodSelector = ISafeBridgeReceiver.verifySafe.selector; + bytes4 methodSelector = ISafeBridgeReceiver.verifySafeBatch.selector; bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _epoch, batchMerkleRoot); _sendSafe(safeBridgeReceiver, safeMessageData); } function _sendSafe(address _receiver, bytes memory _calldata) internal override returns (bytes32) { - uint256 txID = arbsys.sendTxToL1(_receiver, _calldata); + uint256 ticketID = arbsys.sendTxToL1(_receiver, _calldata); - emit L2ToL1TxCreated(txID); - return bytes32(txID); + emit L2ToL1TxCreated(ticketID); + return bytes32(ticketID); } /** @@ -96,15 +96,6 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { // supports 2^64 messages. bytes32[64] public batch; uint256 public batchSize; - // ************************************* // - // * Events * // - // ************************************* // - - /** - * The bridgers need to watch for these events and relay the - * batchMerkleRoot on the FastBridgeReceiver. - */ - event SendBatch(uint256 indexed batchID, uint256 batchSize, uint256 epoch, bytes32 batchMerkleRoot); // ************************************* // // * State Modifiers * // @@ -135,7 +126,7 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { // set merkle root in outbox and reset merkle tree bytes32 batchMerkleRoot = getMerkleRoot(); fastOutbox[epoch] = batchMerkleRoot; - emit SendBatch(currentBatchID, batchSize, epoch, batchMerkleRoot); + emit BatchOutgoing(currentBatchID, batchSize, epoch, batchMerkleRoot); // reset batchSize = 0; diff --git a/contracts/src/bridge/merkle/test/MerkleProofExposed.sol b/contracts/src/bridge/test/merkle/MerkleProofExposed.sol similarity index 85% rename from contracts/src/bridge/merkle/test/MerkleProofExposed.sol rename to contracts/src/bridge/test/merkle/MerkleProofExposed.sol index ed97d88ab..0ac746c5c 100644 --- a/contracts/src/bridge/merkle/test/MerkleProofExposed.sol +++ b/contracts/src/bridge/test/merkle/MerkleProofExposed.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../MerkleProof.sol"; +import "../../merkle/MerkleProof.sol"; /** * @title MerkleProofExpose @@ -23,11 +23,11 @@ contract MerkleProofExposed is MerkleProof { * @param leaf The leaf to validate membership in merkle tree. * @param merkleRoot The root of the merkle tree. */ - function _validateProof( + function validateProof( bytes32[] memory proof, bytes32 leaf, bytes32 merkleRoot ) public pure returns (bool) { - return validateProof(proof, leaf, merkleRoot); + return _validateProof(proof, leaf, merkleRoot); } } diff --git a/contracts/src/bridge/merkle/test/MerkleTreeExposed.sol b/contracts/src/bridge/test/merkle/MerkleTreeExposed.sol similarity index 59% rename from contracts/src/bridge/merkle/test/MerkleTreeExposed.sol rename to contracts/src/bridge/test/merkle/MerkleTreeExposed.sol index 912b37eb9..db297d6b9 100644 --- a/contracts/src/bridge/merkle/test/MerkleTreeExposed.sol +++ b/contracts/src/bridge/test/merkle/MerkleTreeExposed.sol @@ -10,7 +10,7 @@ pragma solidity ^0.8.0; -import "../MerkleTree.sol"; +import "../../merkle/MerkleTree.sol"; /** * @title MerkleTreeExposed @@ -18,11 +18,11 @@ import "../MerkleTree.sol"; * @dev Exposes MerkleTree for testing */ contract MerkleTreeExposed is MerkleTree { - function _appendMessage(bytes memory _leaf) public { - appendMessage(sha256(_leaf)); + function appendMessage(bytes memory _leaf) public { + _appendMessage(sha256(_leaf)); } - function _getMerkleRoot() public view returns (bytes32 merkleroot) { - merkleroot = getMerkleRoot(); + function getMerkleRoot() public view returns (bytes32 merkleroot) { + merkleroot = _getMerkleRoot(); } } diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index c77790256..b70f029e8 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz, @jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere, @shalzz] * @reviewers: [] * @auditors: [] * @bounties: [] diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index 5d2d3a889..799109f12 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz, @jaybuidl] + * @authors: [@jaybuidl, @shotaronowhere, @shalzz] * @reviewers: [] * @auditors: [] * @bounties: [] diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol index 3872e82b2..aec065968 100644 --- a/contracts/src/gateway/interfaces/IForeignGateway.sol +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere, @shalzz] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; import "../../arbitration/IArbitrator.sol"; diff --git a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol index 682e7d95c..86f9d16dc 100644 --- a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol +++ b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; import "../../bridge/interfaces/IFastBridgeReceiver.sol"; diff --git a/contracts/src/gateway/interfaces/IForeignGatewayMock.sol b/contracts/src/gateway/interfaces/IForeignGatewayMock.sol index 58fde077b..bc9a809c4 100644 --- a/contracts/src/gateway/interfaces/IForeignGatewayMock.sol +++ b/contracts/src/gateway/interfaces/IForeignGatewayMock.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; import "./IForeignGatewayBase.sol"; diff --git a/contracts/src/gateway/interfaces/IHomeGateway.sol b/contracts/src/gateway/interfaces/IHomeGateway.sol index 1e0f8c8cd..def61ba0e 100644 --- a/contracts/src/gateway/interfaces/IHomeGateway.sol +++ b/contracts/src/gateway/interfaces/IHomeGateway.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere, @shalzz] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; import "../../arbitration/IArbitrable.sol"; diff --git a/contracts/src/gateway/interfaces/IHomeGatewayBase.sol b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol index bb11e83f2..1b664a5ca 100644 --- a/contracts/src/gateway/interfaces/IHomeGatewayBase.sol +++ b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol @@ -1,5 +1,13 @@ // SPDX-License-Identifier: MIT +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + pragma solidity ^0.8.0; import "../../bridge/interfaces/IFastBridgeSender.sol"; diff --git a/contracts/test/bridge/merkle/index.ts b/contracts/test/bridge/merkle/index.ts index f720e61b0..dda9d43c9 100644 --- a/contracts/test/bridge/merkle/index.ts +++ b/contracts/test/bridge/merkle/index.ts @@ -1,6 +1,5 @@ import { expect } from "chai"; import { ethers } from "hardhat"; -import { BigNumber } from "ethers"; import { toBuffer } from "ethereumjs-util"; import { soliditySha3 } from "web3-utils"; import { MerkleTree } from "./MerkleTree"; @@ -46,27 +45,28 @@ describe("Merkle", async () => { it("Merkle Root verification", async () => { data = ["0x00", "0x01", "0x03"]; nodes = []; - for (var message of data) { - await merkleTreeExposed._appendMessage(message); + for (const message of data) { + await merkleTreeExposed.appendMessage(message); nodes.push(MerkleTree.makeLeafNode(message)); } mt = new MerkleTree(nodes); rootOffChain = mt.getHexRoot(); - rootOnChain = await merkleTreeExposed._getMerkleRoot(); + rootOnChain = await merkleTreeExposed.getMerkleRoot(); console.log("######"); console.log(rootOffChain); console.log(rootOnChain); console.log("########################"); - expect(rootOffChain == rootOnChain).equal(true); + expect(rootOffChain).to.equal(rootOnChain); }); + it("Should correctly verify all nodes in the tree", async () => { - for (var message of data) { + for (const message of data) { const leaf = ethers.utils.sha256(message); proof = mt.getHexProof(leaf); - const validation = await merkleProofExposed._validateProof(proof, ethers.utils.sha256(message), rootOnChain); - expect(validation).equal(true); - expect(verify(proof, rootOffChain, leaf)).equal(true); + const validation = await merkleProofExposed.validateProof(proof, ethers.utils.sha256(message), rootOnChain); + expect(validation).to.equal(true); + expect(verify(proof, rootOffChain, leaf)).to.equal(true); } }); }); diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 62c4dc2ca..314bd271f 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -193,10 +193,10 @@ describe("Demo pre-alpha1", async () => { const fastMessage = event5[0].args.fastMessage; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); - expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); + expect(tx4a).to.emit(fastBridgeSender, "BatchOutgoing"); - const SendBatch = fastBridgeSender.filters.SendBatch(); - const event5a = await fastBridgeSender.queryFilter(SendBatch); + const BatchOutgoing = fastBridgeSender.filters.BatchOutgoing(); + const event5a = await fastBridgeSender.queryFilter(BatchOutgoing); const batchID = event5a[0].args.batchID; const batchMerkleRoot = event5a[0].args.batchMerkleRoot; // bridger tx starts - Honest Bridger @@ -207,10 +207,10 @@ describe("Demo pre-alpha1", async () => { await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); - const tx7a = await fastBridgeReceiver.connect(bridger).verify(batchID); + const tx7a = await fastBridgeReceiver.connect(bridger).verifyBatch(batchID); const tx7 = await fastBridgeReceiver .connect(await ethers.getSigner(relayer)) - .verifyAndRelay(batchID, [], fastMessage); + .verifyAndRelayMessage(batchID, [], fastMessage); const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); expect(tx7).to.emit(arbitrable, "Ruling"); @@ -357,10 +357,10 @@ describe("Demo pre-alpha1", async () => { const fastMessage = event4[0].args.fastMessage; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); - expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); + expect(tx4a).to.emit(fastBridgeSender, "BatchOutgoing"); - const SendBatch = fastBridgeSender.filters.SendBatch(); - const event4a = await fastBridgeSender.queryFilter(SendBatch); + const BatchOutgoing = fastBridgeSender.filters.BatchOutgoing(); + const event4a = await fastBridgeSender.queryFilter(BatchOutgoing); const batchID = event4a[0].args.batchID; const batchMerkleRoot = event4a[0].args.batchMerkleRoot; @@ -382,7 +382,7 @@ describe("Demo pre-alpha1", async () => { await network.provider.send("evm_mine"); await expect( - fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelay(batchID, [], fastMessage) + fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelayMessage(batchID, [], fastMessage) ).to.be.revertedWith("Invalid epoch."); const tx7 = await fastBridgeSender.connect(bridger).sendSafeFallback(batchID, { gasLimit: 1000000 }); @@ -390,7 +390,7 @@ describe("Demo pre-alpha1", async () => { const tx8 = await fastBridgeReceiver .connect(await ethers.getSigner(relayer)) - .verifyAndRelay(batchID, [], fastMessage); + .verifyAndRelayMessage(batchID, [], fastMessage); expect(tx8).to.emit(arbitrable, "Ruling"); @@ -507,10 +507,10 @@ describe("Demo pre-alpha1", async () => { const fastMessage = event4[0].args.fastMessage; const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); - expect(tx4a).to.emit(fastBridgeSender, "SendBatch"); + expect(tx4a).to.emit(fastBridgeSender, "BatchOutgoing"); - const SendBatch = fastBridgeSender.filters.SendBatch(); - const event4a = await fastBridgeSender.queryFilter(SendBatch); + const BatchOutgoing = fastBridgeSender.filters.BatchOutgoing(); + const event4a = await fastBridgeSender.queryFilter(BatchOutgoing); const batchID = event4a[0].args.batchID; const batchMerkleRoot = event4a[0].args.batchMerkleRoot; @@ -528,7 +528,7 @@ describe("Demo pre-alpha1", async () => { const tx8 = await fastBridgeReceiver .connect(await ethers.getSigner(relayer)) - .verifyAndRelay(batchID, [], fastMessage); + .verifyAndRelayMessage(batchID, [], fastMessage); expect(tx8).to.emit(arbitrable, "Ruling"); await fastBridgeReceiver.withdrawChallengeDeposit(batchID); From b8e50b4323c6b529625eb393b496eae9b804e9fa Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sun, 17 Jul 2022 00:39:18 +0200 Subject: [PATCH 20/21] feat: improved FastBridgeReceiver events, expanded the integration tests, found some testing issues Also disabled gas reporter by default. --- contracts/hardhat.config.ts | 10 +- .../bridge/FastBridgeReceiverOnEthereum.sol | 11 +- .../src/bridge/FastBridgeReceiverOnGnosis.sol | 7 +- .../bridge/FastBridgeReceiverOnPolygon.sol | 6 +- .../bridge/interfaces/IFastBridgeReceiver.sol | 31 ++++- contracts/test/integration/index.ts | 123 +++++++++++------- 6 files changed, 122 insertions(+), 66 deletions(-) diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 976d2b170..1a246ed37 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -141,9 +141,15 @@ const config: HardhatUserConfig = { relayer: { default: 1, }, + bridger: { + default: 2, + }, + challenger: { + default: 3, + }, }, gasReporter: { - enabled: process.env.REPORT_GAS !== undefined, + enabled: process.env.REPORT_GAS !== undefined ? process.env.REPORT_GAS === "true" : false, currency: "USD", }, verify: { @@ -166,7 +172,7 @@ const config: HardhatUserConfig = { }, }, docgen: { - path: './docs', + path: "./docs", clear: true, runOnCompile: false, }, diff --git a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol index 4ab30dd92..8058c6979 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnEthereum.sol @@ -164,9 +164,6 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; emit BatchVerified(_epoch); - } else { - // unhappy path - emit BatchNotVerified(_epoch); } claim.verificationAttempted = true; } @@ -194,6 +191,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive challenges[_epoch].honest = true; } } + emit BatchSafeVerified(_epoch, claims[_epoch].honest, challenges[_epoch].honest); } /** @@ -223,7 +221,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive Claim storage claim = claims[_epoch]; require(claim.bridger != address(0), "Claim does not exist"); - require(claim.honest == true, "Claim not verified."); + require(claim.honest == true, "Claim failed."); require(claim.depositAndRewardWithdrawn == false, "Claim deposit and any rewards already withdrawn."); uint256 amount = deposit; @@ -232,6 +230,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive } claim.depositAndRewardWithdrawn = true; + emit ClaimDepositWithdrawn(_epoch, claim.bridger); payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -245,7 +244,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive Challenge storage challenge = challenges[_epoch]; require(challenge.challenger != address(0), "Challenge does not exist"); - require(challenge.honest == true, "Challenge not verified."); + require(challenge.honest == true, "Challenge failed."); require(challenge.depositAndRewardWithdrawn == false, "Challenge deposit and rewards already withdrawn."); uint256 amount = deposit; @@ -254,6 +253,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive } challenge.depositAndRewardWithdrawn = true; + emit ChallengeDepositWithdrawn(_epoch, challenge.challenger); payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -337,6 +337,7 @@ contract FastBridgeReceiverOnEthereum is IFastBridgeReceiver, ISafeBridgeReceive bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); + emit MessageRelayed(_epoch, nonce); (success, ) = receiver.call(data); // Checks-Effects-Interaction diff --git a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol index e1e3a8dd2..879010969 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnGnosis.sol @@ -162,9 +162,6 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; emit BatchVerified(_epoch); - } else { - // unhappy path - emit BatchNotVerified(_epoch); } claim.verificationAttempted = true; } @@ -192,6 +189,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver challenges[_epoch].honest = true; } } + emit BatchSafeVerified(_epoch, claims[_epoch].honest, challenges[_epoch].honest); } /** @@ -230,6 +228,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver } claim.depositAndRewardWithdrawn = true; + emit ClaimDepositWithdrawn(_epoch, claim.bridger); payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -252,6 +251,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver } challenge.depositAndRewardWithdrawn = true; + emit ChallengeDepositWithdrawn(_epoch, challenge.challenger); payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -335,6 +335,7 @@ contract FastBridgeReceiverOnGnosis is IFastBridgeReceiver, ISafeBridgeReceiver bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); + emit MessageRelayed(_epoch, nonce); (success, ) = receiver.call(data); // Checks-Effects-Interaction diff --git a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol index 14594915a..6436fcc4a 100644 --- a/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol +++ b/contracts/src/bridge/FastBridgeReceiverOnPolygon.sol @@ -156,9 +156,6 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, claim.honest = true; fastInbox[_epoch] = claim.batchMerkleRoot; emit BatchVerified(_epoch); - } else { - // unhappy path - emit BatchNotVerified(_epoch); } claim.verificationAttempted = true; } @@ -234,6 +231,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, } claim.depositAndRewardWithdrawn = true; + emit ClaimDepositWithdrawn(_epoch, claim.bridger); payable(claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -256,6 +254,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, } challenge.depositAndRewardWithdrawn = true; + emit ChallengeDepositWithdrawn(_epoch, challenge.challenger); payable(challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH. // Checks-Effects-Interaction @@ -339,6 +338,7 @@ contract FastBridgeReceiverOnPolygon is FxBaseChildTunnel, IFastBridgeReceiver, bytes32 replay = relayed[_epoch][index]; require(((replay >> offset) & bytes32(uint256(1))) == 0, "Message already relayed"); relayed[_epoch][index] = replay | bytes32(1 << offset); + emit MessageRelayed(_epoch, nonce); (success, ) = receiver.call(data); // Checks-Effects-Interaction diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 8ee4a52e7..0ce42badf 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -23,22 +23,45 @@ interface IFastBridgeReceiver { event ClaimReceived(uint256 indexed _epoch, bytes32 indexed _batchMerkleRoot); /** - * @dev The Fast Bridge participants watch for these events to call `sendSafeFallback()` on the sending side. + * @dev This event indicates that `sendSafeFallback()` should be called on the sending side. * @param _epoch The epoch associated with the challenged claim. */ event ClaimChallenged(uint256 indexed _epoch); /** - * @dev The Fast Bridge participants watch for these events to know optimistic verification has succeeded. The messages are ready to be relayed. + * @dev This events indicates that optimistic verification has succeeded. The messages are ready to be relayed. * @param _epoch The epoch associated with the batch. */ event BatchVerified(uint256 indexed _epoch); /** - * @dev The Fast Bridge users watch for these events to know that optimistic verification has failed. The Fast Bridge sender will fallback to the Safe Bridge. + * @dev This event indicates that the batch has been received via the Safe Bridge. * @param _epoch The epoch associated with the batch. + * @param _isBridgerHonest Whether the bridger made an honest claim. + * @param _isChallengerHonest Whether the bridger made an honest challenge. */ - event BatchNotVerified(uint256 indexed _epoch); + event BatchSafeVerified(uint256 indexed _epoch, bool _isBridgerHonest, bool _isChallengerHonest); + + /** + * @dev This event indicates that the claim deposit has been withdrawn. + * @param _epoch The epoch associated with the batch. + * @param _bridger The recipient of the claim deposit. + */ + event ClaimDepositWithdrawn(uint256 indexed _epoch, address indexed _bridger); + + /** + * @dev This event indicates that the challenge deposit has been withdrawn. + * @param _epoch The epoch associated with the batch. + * @param _challenger The recipient of the challenge deposit. + */ + event ChallengeDepositWithdrawn(uint256 indexed _epoch, address indexed _challenger); + + /** + * @dev This event indicates that a message has been relayed for the batch in this `_epoch`. + * @param _epoch The epoch associated with the batch. + * @param _nonce The nonce of the message that was relayed. + */ + event MessageRelayed(uint256 indexed _epoch, uint256 indexed _nonce); // ************************************* // // * Function Modifiers * // diff --git a/contracts/test/integration/index.ts b/contracts/test/integration/index.ts index 314bd271f..402168a3a 100644 --- a/contracts/test/integration/index.ts +++ b/contracts/test/integration/index.ts @@ -8,7 +8,7 @@ import { FastBridgeReceiverOnEthereum, ForeignGatewayOnEthereum, ArbitrableExample, - FastBridgeSenderToEthereum, + FastBridgeSenderToEthereumMock, HomeGatewayToEthereum, DisputeKitClassic, InboxMock, @@ -17,7 +17,7 @@ import { /* eslint-disable no-unused-vars */ /* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482 -describe("Demo pre-alpha1", async () => { +describe("Integration tests", async () => { const ONE_TENTH_ETH = BigNumber.from(10).pow(17); const ONE_ETH = BigNumber.from(10).pow(18); const ONE_HUNDRED_PNK = BigNumber.from(10).pow(20); @@ -42,12 +42,11 @@ describe("Demo pre-alpha1", async () => { drawing, // Jurors can be drawn. } - let deployer, relayer, bridger, challenger, innocentBystander; + let deployer; let ng, disputeKit, pnk, core, fastBridgeReceiver, foreignGateway, arbitrable, fastBridgeSender, homeGateway, inbox; beforeEach("Setup", async () => { - deployer = (await getNamedAccounts()).deployer; - relayer = (await getNamedAccounts()).relayer; + ({ deployer } = await getNamedAccounts()); console.log("deployer:%s", deployer); console.log("named accounts: %O", await getNamedAccounts()); @@ -63,7 +62,7 @@ describe("Demo pre-alpha1", async () => { fastBridgeReceiver = (await ethers.getContract("FastBridgeReceiverOnEthereum")) as FastBridgeReceiverOnEthereum; foreignGateway = (await ethers.getContract("ForeignGatewayOnEthereum")) as ForeignGatewayOnEthereum; arbitrable = (await ethers.getContract("ArbitrableExample")) as ArbitrableExample; - fastBridgeSender = (await ethers.getContract("FastBridgeSenderToEthereumMock")) as FastBridgeSenderToEthereum; + fastBridgeSender = (await ethers.getContract("FastBridgeSenderToEthereumMock")) as FastBridgeSenderToEthereumMock; homeGateway = (await ethers.getContract("HomeGatewayToEthereum")) as HomeGatewayToEthereum; inbox = (await ethers.getContract("InboxMock")) as InboxMock; }); @@ -81,9 +80,9 @@ describe("Demo pre-alpha1", async () => { expect(rn).to.equal(rnOld.add(1)); }); - it("Demo - Honest Claim - No Challenge - Bridger paid", async () => { + it("Honest Claim - No Challenge - Bridger paid", async () => { const arbitrationCost = ONE_TENTH_ETH.mul(3); - const [bridger, challenger] = await ethers.getSigners(); + const [bridger, challenger, relayer] = await ethers.getSigners(); await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); @@ -132,7 +131,7 @@ describe("Demo pre-alpha1", async () => { // Relayer tx const tx2 = await homeGateway - .connect(await ethers.getSigner(relayer)) + .connect(relayer) .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { value: arbitrationCost, }); @@ -154,7 +153,7 @@ describe("Demo pre-alpha1", async () => { 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 mineBlocks(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()); @@ -180,45 +179,50 @@ describe("Demo pre-alpha1", async () => { 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 tx4 = await core.executeRuling(0); - - console.log("Executed ruling"); + expect(await core.execute(0, 0, 1000)).to.emit(core, "TokenAndETHShift"); + const tx4 = await core.executeRuling(0); expect(tx4).to.emit(fastBridgeSender, "MessageReceived"); - const MessageReceived = fastBridgeSender.filters.MessageReceived(); const event5 = await fastBridgeSender.queryFilter(MessageReceived); - const fastMessage = event5[0].args.fastMessage; + console.log("Executed ruling"); + + // relayer tx - send batch const tx4a = await fastBridgeSender.connect(bridger).sendBatch(); expect(tx4a).to.emit(fastBridgeSender, "BatchOutgoing"); + // expect(tx4a).to.emit(fastBridgeSender, "SentSafe"); // does not work because FastBridgeSender is just a (bad) mock. const BatchOutgoing = fastBridgeSender.filters.BatchOutgoing(); const event5a = await fastBridgeSender.queryFilter(BatchOutgoing); const batchID = event5a[0].args.batchID; const batchMerkleRoot = event5a[0].args.batchMerkleRoot; + // bridger tx starts - Honest Bridger const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, batchMerkleRoot, { value: ONE_TENTH_ETH }); - expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(batchID, batchMerkleRoot); + // wait for challenge period (and epoch) to pass await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); const tx7a = await fastBridgeReceiver.connect(bridger).verifyBatch(batchID); - const tx7 = await fastBridgeReceiver - .connect(await ethers.getSigner(relayer)) - .verifyAndRelayMessage(batchID, [], fastMessage); - const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); + expect(tx7a).to.emit(fastBridgeReceiver, "BatchVerified").withArgs(batchID); + const tx7 = await fastBridgeReceiver.connect(relayer).verifyAndRelayMessage(batchID, [], fastMessage); + expect(tx7).to.emit(fastBridgeReceiver, "MessageRelayed").withArgs(batchID, 0); expect(tx7).to.emit(arbitrable, "Ruling"); + + const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(batchID); + expect(tx8).to.emit(fastBridgeReceiver, "ClaimDepositWithdrawn").withArgs(batchID, bridger.address); + + expect(fastBridgeReceiver.withdrawChallengeDeposit(batchID)).to.be.revertedWith("Challenge does not exist"); }); - it("Demo - Honest Claim - Challenged - Bridger Paid, Challenger deposit forfeited", async () => { + it("Honest Claim - Dishonest Challenge - Bridger paid, Challenger deposit forfeited", async () => { const arbitrationCost = ONE_TENTH_ETH.mul(3); - const [bridger, challenger] = await ethers.getSigners(); + const [bridger, challenger, relayer] = await ethers.getSigners(); await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); @@ -283,7 +287,7 @@ describe("Demo pre-alpha1", async () => { expect(events2[0].args._disputeID).to.equal(disputeId); // Relayer tx const tx2 = await homeGateway - .connect(await ethers.getSigner(relayer)) + .connect(relayer) .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { value: arbitrationCost, }); @@ -305,7 +309,7 @@ describe("Demo pre-alpha1", async () => { 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 mineBlocks(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()); @@ -364,13 +368,10 @@ describe("Demo pre-alpha1", async () => { const batchID = event4a[0].args.batchID; const batchMerkleRoot = event4a[0].args.batchMerkleRoot; - // bridger tx starts - Honest Bridger - console.log("Executed ruling"); - // bridger tx starts + // bridger tx starts - Honest Bridger const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, batchMerkleRoot, { value: ONE_TENTH_ETH }); - expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(batchID, batchMerkleRoot); // Challenger tx starts @@ -381,25 +382,33 @@ describe("Demo pre-alpha1", async () => { await network.provider.send("evm_increaseTime", [86400]); await network.provider.send("evm_mine"); - await expect( - fastBridgeReceiver.connect(await ethers.getSigner(relayer)).verifyAndRelayMessage(batchID, [], fastMessage) - ).to.be.revertedWith("Invalid epoch."); + await expect(fastBridgeReceiver.connect(relayer).verifyBatch(batchID)).to.not.emit( + fastBridgeReceiver, + "BatchVerified" + ); const tx7 = await fastBridgeSender.connect(bridger).sendSafeFallback(batchID, { gasLimit: 1000000 }); - expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated").withArgs(0); + // expect(tx7).to.emit(fastBridgeSender, "SentSafe"); // does not work because FastBridgeSender is just a (bad) mock. + + const tx8 = await fastBridgeReceiver.connect(bridger).verifySafeBatch(batchID, batchMerkleRoot); + expect(tx8).to.emit(fastBridgeReceiver, "BatchSafeVerified").withArgs(batchID, true, false); - const tx8 = await fastBridgeReceiver - .connect(await ethers.getSigner(relayer)) - .verifyAndRelayMessage(batchID, [], fastMessage); + const tx9 = await fastBridgeReceiver.connect(relayer).verifyAndRelayMessage(batchID, [], fastMessage); + expect(tx9).to.emit(fastBridgeReceiver, "MessageRelayed").withArgs(batchID, 0); + expect(tx9).to.emit(arbitrable, "Ruling"); - expect(tx8).to.emit(arbitrable, "Ruling"); + const tx10 = await fastBridgeReceiver.connect(relayer).withdrawClaimDeposit(batchID); + expect(tx10).to.emit(fastBridgeReceiver, "ClaimDepositWithdrawn").withArgs(batchID, bridger.address); - await expect(fastBridgeReceiver.withdrawChallengeDeposit(batchID)).to.be.revertedWith("Challenge not verified."); + await expect(fastBridgeReceiver.connect(relayer).withdrawChallengeDeposit(batchID)).to.be.revertedWith( + "Challenge failed." + ); }); - it("Demo - Dishonest Claim - Challenged - Bridger deposit forfeited, Challenger paid", async () => { + it("Dishonest Claim - Honest Challenge - Bridger deposit forfeited, Challenger paid", async () => { const arbitrationCost = ONE_TENTH_ETH.mul(3); - const [bridger, challenger] = await ethers.getSigners(); + const [bridger, challenger, relayer] = await ethers.getSigners(); await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); @@ -447,7 +456,7 @@ describe("Demo pre-alpha1", async () => { // Relayer tx const tx2 = await homeGateway - .connect(await ethers.getSigner(relayer)) + .connect(relayer) .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { value: arbitrationCost, }); @@ -468,7 +477,7 @@ describe("Demo pre-alpha1", async () => { 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 mineBlocks(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()); @@ -519,22 +528,38 @@ describe("Demo pre-alpha1", async () => { const fakeData = "KlerosToTheMoon"; const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"], [fakeData])); const tx5 = await fastBridgeReceiver.connect(bridger).claim(batchID, fakeHash, { value: ONE_TENTH_ETH }); + // Challenger tx starts const tx6 = await fastBridgeReceiver.connect(challenger).challenge(batchID, { value: ONE_TENTH_ETH }); expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(batchID); + // wait for challenge period (and epoch) to pass + await network.provider.send("evm_increaseTime", [86400]); + await network.provider.send("evm_mine"); + + await expect(fastBridgeReceiver.connect(relayer).verifyBatch(batchID)).to.not.emit( + fastBridgeReceiver, + "BatchVerified" + ); + const tx7 = await fastBridgeSender.connect(bridger).sendSafeFallback(batchID, { gasLimit: 1000000 }); - expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated").withArgs(0); + // expect(tx7).to.emit(fastBridgeSender, "SentSafe"); // does not work because FastBridgeSender is just a (bad) mock. + + const tx8 = await fastBridgeReceiver.connect(bridger).verifySafeBatch(batchID, batchMerkleRoot); + expect(tx8).to.emit(fastBridgeReceiver, "BatchSafeVerified").withArgs(batchID, false, true); + + const tx9 = await fastBridgeReceiver.connect(relayer).verifyAndRelayMessage(batchID, [], fastMessage); + expect(tx9).to.emit(fastBridgeReceiver, "MessageRelayed").withArgs(batchID, 0); + expect(tx9).to.emit(arbitrable, "Ruling"); - const tx8 = await fastBridgeReceiver - .connect(await ethers.getSigner(relayer)) - .verifyAndRelayMessage(batchID, [], fastMessage); + expect(fastBridgeReceiver.connect(relayer).withdrawClaimDeposit(batchID)).to.be.revertedWith("Claim failed."); - expect(tx8).to.emit(arbitrable, "Ruling"); - await fastBridgeReceiver.withdrawChallengeDeposit(batchID); + const tx10 = await fastBridgeReceiver.connect(relayer).withdrawChallengeDeposit(batchID); + expect(tx10).to.emit(fastBridgeReceiver, "ChallengeDepositWithdrawn").withArgs(batchID, challenger.address); }); - async function mineNBlocks(n) { + async function mineBlocks(n) { for (let index = 0; index < n; index++) { await network.provider.send("evm_mine"); } From 45c123da3c4979e24bfe31e49b0f5263008dd759 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Wed, 27 Jul 2022 23:02:55 +0100 Subject: [PATCH 21/21] refactor: separation of the app-agnostic gateway interfaces and the Kleros-specific gateways --- contracts/src/bridge/FastBridgeSender.sol | 5 +- .../bridge/interfaces/IReceiverGateway.sol | 21 ++++ .../bridge/interfaces/ISafeBridgeSender.sol | 4 +- .../src/bridge/interfaces/ISenderGateway.sol | 21 ++++ .../FastBridgeSenderToEthereum.sol | 4 +- .../src/bridge/test/FastBridgeSenderMock.sol | 2 +- .../test/gateways/IReceiverGatewayMock.sol} | 4 +- .../test/gateways/ISenderGatewayMock.sol | 7 ++ .../test/gateways/ReceiverGatewayMock.sol | 66 ++++++++++ .../test/gateways/SenderGatewayMock.sol | 47 ++++++++ contracts/src/gateway/ForeignGateway.sol | 114 +++++++++++------- contracts/src/gateway/HomeGateway.sol | 74 ++++++------ .../gateway/interfaces/IForeignGateway.sol | 4 +- .../interfaces/IForeignGatewayBase.sol | 23 ---- .../src/gateway/interfaces/IHomeGateway.sol | 4 +- .../gateway/interfaces/IHomeGatewayBase.sol | 23 ---- .../gateway/interfaces/IHomeGatewayMock.sol | 7 -- .../src/gateway/test/ForeignGatewayMock.sol | 72 ----------- .../src/gateway/test/HomeGatewayMock.sol | 53 -------- 19 files changed, 284 insertions(+), 271 deletions(-) create mode 100644 contracts/src/bridge/interfaces/IReceiverGateway.sol create mode 100644 contracts/src/bridge/interfaces/ISenderGateway.sol rename contracts/src/{gateway/interfaces/IForeignGatewayMock.sol => bridge/test/gateways/IReceiverGatewayMock.sol} (75%) create mode 100644 contracts/src/bridge/test/gateways/ISenderGatewayMock.sol create mode 100644 contracts/src/bridge/test/gateways/ReceiverGatewayMock.sol create mode 100644 contracts/src/bridge/test/gateways/SenderGatewayMock.sol delete mode 100644 contracts/src/gateway/interfaces/IForeignGatewayBase.sol delete mode 100644 contracts/src/gateway/interfaces/IHomeGatewayBase.sol delete mode 100644 contracts/src/gateway/interfaces/IHomeGatewayMock.sol delete mode 100644 contracts/src/gateway/test/ForeignGatewayMock.sol delete mode 100644 contracts/src/gateway/test/HomeGatewayMock.sol diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 8a20218df..21ca6c549 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -57,7 +57,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { /** * @dev Constructor. * @param _epochPeriod The duration between epochs. - * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. + * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the receiving chain. */ constructor(uint256 _epochPeriod, address _safeBridgeReceiver) { epochPeriod = _epochPeriod; @@ -103,8 +103,7 @@ contract FastBridgeSender is IFastBridgeSender, ISafeBridgeSender { } /** - * Sends a batch of arbitrary message from one domain to another - * via the fast bridge mechanism. + * Sends a batch of arbitrary message from one domain to another via the fast bridge mechanism. */ function sendBatch() external override { uint256 epoch = block.timestamp / epochPeriod; diff --git a/contracts/src/bridge/interfaces/IReceiverGateway.sol b/contracts/src/bridge/interfaces/IReceiverGateway.sol new file mode 100644 index 000000000..e12949109 --- /dev/null +++ b/contracts/src/bridge/interfaces/IReceiverGateway.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeReceiver.sol"; + +interface IReceiverGateway { + function fastBridgeReceiver() external view returns (IFastBridgeReceiver); + + function senderChainID() external view returns (uint256); + + function senderGateway() external view returns (address); +} diff --git a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol index efba3113d..6cc063391 100644 --- a/contracts/src/bridge/interfaces/ISafeBridgeSender.sol +++ b/contracts/src/bridge/interfaces/ISafeBridgeSender.sol @@ -14,8 +14,8 @@ abstract contract ISafeBridgeSender { /** * Sends an arbitrary message from one domain to another. * - * @param _receiver The foreign chain contract address who will receive the calldata - * @param _calldata The home chain encoded message data. + * @param _receiver The contract address which will receive the calldata on the receiving chain. + * @param _calldata The encoded message data to send. * @return Unique id to track the message request/transaction. */ function _sendSafe(address _receiver, bytes memory _calldata) internal virtual returns (bytes32); diff --git a/contracts/src/bridge/interfaces/ISenderGateway.sol b/contracts/src/bridge/interfaces/ISenderGateway.sol new file mode 100644 index 000000000..f3c6520b8 --- /dev/null +++ b/contracts/src/bridge/interfaces/ISenderGateway.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../../bridge/interfaces/IFastBridgeSender.sol"; + +interface ISenderGateway { + function fastBridgeSender() external view returns (IFastBridgeSender); + + function receiverChainID() external view returns (uint256); + + function receiverGateway() external view returns (address); +} diff --git a/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol b/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol index 1f879268e..abcdc794d 100644 --- a/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol +++ b/contracts/src/bridge/single-message/FastBridgeSenderToEthereum.sol @@ -35,7 +35,7 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe 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. + address public fastBridgeSender; // The address of the Fast Bridge sender on Arbitrum, generally the Sender Gateway. uint256 public currentTicketID = 1; // Zero means not set, start at 1. mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. @@ -52,7 +52,7 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe * @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. + * @param _fastBridgeSender The address of the Fast Bridge sender on Arbitrum, generally the Sender Gateway. */ constructor( address _governor, diff --git a/contracts/src/bridge/test/FastBridgeSenderMock.sol b/contracts/src/bridge/test/FastBridgeSenderMock.sol index a18c676e7..226c5f459 100644 --- a/contracts/src/bridge/test/FastBridgeSenderMock.sol +++ b/contracts/src/bridge/test/FastBridgeSenderMock.sol @@ -63,7 +63,7 @@ contract FastBridgeSenderMock is IFastBridgeSender, ISafeBridgeSender { /** * @dev Constructor. * @param _epochPeriod The duration between epochs. - * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the foreign chain. + * @param _safeBridgeReceiver The the Safe Bridge Router on Ethereum to the receiving chain. * @param _arbsys The address of the mock ArbSys contract. */ constructor( diff --git a/contracts/src/gateway/interfaces/IForeignGatewayMock.sol b/contracts/src/bridge/test/gateways/IReceiverGatewayMock.sol similarity index 75% rename from contracts/src/gateway/interfaces/IForeignGatewayMock.sol rename to contracts/src/bridge/test/gateways/IReceiverGatewayMock.sol index bc9a809c4..6ab68a94d 100644 --- a/contracts/src/gateway/interfaces/IForeignGatewayMock.sol +++ b/contracts/src/bridge/test/gateways/IReceiverGatewayMock.sol @@ -10,9 +10,9 @@ pragma solidity ^0.8.0; -import "./IForeignGatewayBase.sol"; +import "../../interfaces/IReceiverGateway.sol"; -interface IForeignGatewayMock is IForeignGatewayBase { +interface IReceiverGatewayMock is IReceiverGateway { /** * Receive the message from the home gateway. */ diff --git a/contracts/src/bridge/test/gateways/ISenderGatewayMock.sol b/contracts/src/bridge/test/gateways/ISenderGatewayMock.sol new file mode 100644 index 000000000..815967273 --- /dev/null +++ b/contracts/src/bridge/test/gateways/ISenderGatewayMock.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../../interfaces/ISenderGateway.sol"; + +interface ISenderGatewayMock is ISenderGateway {} diff --git a/contracts/src/bridge/test/gateways/ReceiverGatewayMock.sol b/contracts/src/bridge/test/gateways/ReceiverGatewayMock.sol new file mode 100644 index 000000000..6caccfc00 --- /dev/null +++ b/contracts/src/bridge/test/gateways/ReceiverGatewayMock.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./IReceiverGatewayMock.sol"; + +/** + * Receiver Gateway Mock + * Counterpart of `SenderGatewayMock` + */ +contract ReceiverGatewayMock is IReceiverGateway { + IFastBridgeReceiver public immutable fastBridgeReceiver; + address public immutable override senderGateway; + uint256 public immutable override senderChainID; + + uint256 public messageCount; + uint256 public data; + + constructor( + IFastBridgeReceiver _fastBridgeReceiver, + address _senderGateway, + uint256 _senderChainID + ) { + fastBridgeReceiver = _fastBridgeReceiver; + senderGateway = _senderGateway; + senderChainID = _senderChainID; + } + + modifier onlyFromFastBridge() { + require(address(fastBridgeReceiver) == msg.sender, "Fast Bridge only."); + _; + } + + /** + * Receive the message from the sender gateway. + */ + function receiveMessage(address _messageSender) external onlyFromFastBridge { + require(_messageSender == senderGateway, "Only the sender gateway is allowed."); + _receiveMessage(); + } + + /** + * Receive the message from the sender gateway. + */ + function receiveMessage(address _messageSender, uint256 _data) external onlyFromFastBridge { + require(_messageSender == senderGateway, "Only the sender gateway is allowed."); + _receiveMessage(_data); + } + + function _receiveMessage() internal { + messageCount++; + } + + function _receiveMessage(uint256 _data) internal { + messageCount++; + data = _data; + } +} diff --git a/contracts/src/bridge/test/gateways/SenderGatewayMock.sol b/contracts/src/bridge/test/gateways/SenderGatewayMock.sol new file mode 100644 index 000000000..becd043bd --- /dev/null +++ b/contracts/src/bridge/test/gateways/SenderGatewayMock.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "./IReceiverGatewayMock.sol"; +import "../../interfaces/ISenderGateway.sol"; + +/** + * Sender Gateway + * Counterpart of `ReceiverGatewayMock` + */ +contract SenderGatewayMock is ISenderGateway { + IFastBridgeSender public immutable fastBridgeSender; + address public override receiverGateway; + uint256 public immutable override receiverChainID; + + struct RelayedData { + uint256 arbitrationCost; + address relayer; + } + mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; + + constructor( + IFastBridgeSender _fastBridgeSender, + address _receiverGateway, + uint256 _receiverChainID + ) { + fastBridgeSender = _fastBridgeSender; + receiverGateway = _receiverGateway; + receiverChainID = _receiverChainID; + } + + function sendFastMessage(uint256 _data) external { + bytes4 methodSelector = IReceiverGatewayMock.receiveMessage.selector; + bytes memory data = abi.encodeWithSelector(methodSelector, _data); + + fastBridgeSender.sendFast(receiverGateway, data); + } +} diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index b70f029e8..8d0f8ac62 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -18,19 +18,9 @@ import "./interfaces/IForeignGateway.sol"; * Counterpart of `HomeGateway` */ contract ForeignGateway 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; + // ************************************* // + // * Enums / Structs * // + // ************************************* // struct DisputeData { uint248 id; @@ -39,13 +29,10 @@ contract ForeignGateway is IForeignGateway { 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; + // ************************************* // + // * Events * // + // ************************************* // event OutgoingDispute( bytes32 disputeHash, @@ -56,10 +43,29 @@ contract ForeignGateway is IForeignGateway { address arbitrable ); + // ************************************* // + // * Storage * // + // ************************************* // + + uint256 public constant MIN_JURORS = 3; // The global default minimum number of jurors in a dispute. + uint256 public immutable override senderChainID; + address public immutable override senderGateway; + uint256 internal localDisputeID = 1; // The disputeID must start from 1 as the KlerosV1 proxy governor depends on this implementation. We now also depend on localDisputeID not ever being zero. + uint256[] internal feeForJuror; // feeForJuror[subcourtID] + address public governor; + IFastBridgeReceiver public fastBridgeReceiver; + IFastBridgeReceiver public depreciatedFastbridge; + uint256 public depreciatedFastBridgeExpiration; + mapping(bytes32 => DisputeData) public disputeHashtoDisputeData; + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + modifier onlyFromFastBridge() { require( - address(fastbridge) == msg.sender || - ((block.timestamp < fastbridgeExpiration) && address(depreciatedFastbridge) == msg.sender), + address(fastBridgeReceiver) == msg.sender || + ((block.timestamp < depreciatedFastBridgeExpiration) && address(depreciatedFastbridge) == msg.sender), "Access not allowed: Fast Bridge only." ); _; @@ -72,49 +78,55 @@ contract ForeignGateway is IForeignGateway { constructor( address _governor, - IFastBridgeReceiver _fastbridge, + IFastBridgeReceiver _fastBridgeReceiver, uint256[] memory _feeForJuror, - address _homeGateway, - uint256 _homeChainID + address _senderGateway, + uint256 _senderChainID ) { governor = _governor; - fastbridge = _fastbridge; + fastBridgeReceiver = _fastBridgeReceiver; feeForJuror = _feeForJuror; - homeGateway = _homeGateway; - uint256 id; - assembly { - id := chainid() - } - chainID = id; - homeChainID = _homeChainID; + senderGateway = _senderGateway; + senderChainID = _senderChainID; } - /** @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). + // ************************************* // + // * Governance * // + // ************************************* // + + /** + * @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastBridgeReceiver 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 { + function changeFastbridge(IFastBridgeReceiver _fastBridgeReceiver, 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; + depreciatedFastBridgeExpiration = block.timestamp + _fastBridgeReceiver.epochPeriod() + _gracePeriod; // 2 weeks + depreciatedFastbridge = fastBridgeReceiver; + fastBridgeReceiver = _fastBridgeReceiver; } - /** @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. + /** + * @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. + /** + * @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); } + // ************************************* // + // * State Modifiers * // + // ************************************* // + function createDispute(uint256 _choices, bytes calldata _extraData) external payable @@ -124,6 +136,10 @@ contract ForeignGateway is IForeignGateway { require(msg.value >= arbitrationCost(_extraData), "Not paid enough for arbitration"); disputeID = localDisputeID++; + uint256 chainID; + assembly { + chainID := chainid() + } bytes32 disputeHash = keccak256( abi.encodePacked( chainID, @@ -163,7 +179,7 @@ contract ForeignGateway is IForeignGateway { uint256 _ruling, address _relayer ) external override onlyFromFastBridge { - require(_messageSender == homeGateway, "Only the homegateway is allowed."); + require(_messageSender == senderGateway, "Only the homegateway is allowed."); DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); @@ -186,10 +202,18 @@ contract ForeignGateway is IForeignGateway { payable(dispute.relayer).transfer(amount); } + // ************************************* // + // * Public Views * // + // ************************************* // + function disputeHashToForeignID(bytes32 _disputeHash) external view override returns (uint256) { return disputeHashtoDisputeData[_disputeHash].id; } + // ************************ // + // * Internal * // + // ************************ // + function extraDataToSubcourtIDMinJurors(bytes memory _extraData) internal view diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index 799109f12..fb75ecb86 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -20,49 +20,63 @@ import "./interfaces/IHomeGateway.sol"; * Counterpart of `ForeignGateway` */ contract HomeGateway 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; + // ************************************* // + // * Enums / Structs * // + // ************************************* // struct RelayedData { uint256 arbitrationCost; address relayer; } + + // ************************************* // + // * Storage * // + // ************************************* // + + address public governor; + IArbitrator public immutable arbitrator; + IFastBridgeSender public fastBridgeSender; + address public override receiverGateway; + uint256 public immutable override receiverChainID; + mapping(uint256 => bytes32) public disputeIDtoHash; + mapping(bytes32 => uint256) public disputeHashtoID; mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; constructor( address _governor, IArbitrator _arbitrator, - IFastBridgeSender _fastbridge, - address _foreignGateway, - uint256 _foreignChainID + IFastBridgeSender _fastBridgeSender, + address _receiverGateway, + uint256 _receiverChainID ) { governor = _governor; arbitrator = _arbitrator; - fastbridge = _fastbridge; - foreignGateway = _foreignGateway; - foreignChainID = _foreignChainID; - uint256 id; - assembly { - id := chainid() - } - chainID = id; + fastBridgeSender = _fastBridgeSender; + receiverGateway = _receiverGateway; + receiverChainID = _receiverChainID; emit MetaEvidence(0, "BRIDGE"); } + // ************************************* // + // * Governance * // + // ************************************* // + /** - * @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. - * + * @dev Changes the fastBridge, useful to increase the claim deposit. + * @param _fastBridgeSender The address of the new fastBridge. + */ + function changeFastbridge(IFastBridgeSender _fastBridgeSender) external { + require(governor == msg.sender, "Access not allowed: Governor only."); + fastBridgeSender = _fastBridgeSender; + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + + /** + * @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 @@ -113,15 +127,7 @@ contract HomeGateway is IHomeGateway { bytes4 methodSelector = IForeignGateway.relayRule.selector; bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _ruling, relayedData.relayer); - 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; + fastBridgeSender.sendFast(receiverGateway, data); } function disputeHashToHomeID(bytes32 _disputeHash) external view override returns (uint256) { diff --git a/contracts/src/gateway/interfaces/IForeignGateway.sol b/contracts/src/gateway/interfaces/IForeignGateway.sol index aec065968..09700cd7a 100644 --- a/contracts/src/gateway/interfaces/IForeignGateway.sol +++ b/contracts/src/gateway/interfaces/IForeignGateway.sol @@ -11,9 +11,9 @@ pragma solidity ^0.8.0; import "../../arbitration/IArbitrator.sol"; -import "./IForeignGatewayBase.sol"; +import "../../bridge/interfaces/IReceiverGateway.sol"; -interface IForeignGateway is IArbitrator, IForeignGatewayBase { +interface IForeignGateway is IArbitrator, IReceiverGateway { /** * Relay the rule call from the home gateway to the arbitrable. */ diff --git a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol b/contracts/src/gateway/interfaces/IForeignGatewayBase.sol deleted file mode 100644 index 86f9d16dc..000000000 --- a/contracts/src/gateway/interfaces/IForeignGatewayBase.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../../bridge/interfaces/IFastBridgeReceiver.sol"; - -interface IForeignGatewayBase { - function fastbridge() external view returns (IFastBridgeReceiver); - - function chainID() external view returns (uint256); - - function homeChainID() external view returns (uint256); - - function homeGateway() external view returns (address); -} diff --git a/contracts/src/gateway/interfaces/IHomeGateway.sol b/contracts/src/gateway/interfaces/IHomeGateway.sol index def61ba0e..03c4550e5 100644 --- a/contracts/src/gateway/interfaces/IHomeGateway.sol +++ b/contracts/src/gateway/interfaces/IHomeGateway.sol @@ -12,9 +12,9 @@ pragma solidity ^0.8.0; import "../../arbitration/IArbitrable.sol"; import "../../evidence/IMetaEvidence.sol"; -import "./IHomeGatewayBase.sol"; +import "../../bridge/interfaces/ISenderGateway.sol"; -interface IHomeGateway is IArbitrable, IMetaEvidence, IHomeGatewayBase { +interface IHomeGateway is IArbitrable, IMetaEvidence, ISenderGateway { function relayCreateDispute( uint256 _originalChainID, bytes32 _originalBlockHash, diff --git a/contracts/src/gateway/interfaces/IHomeGatewayBase.sol b/contracts/src/gateway/interfaces/IHomeGatewayBase.sol deleted file mode 100644 index 1b664a5ca..000000000 --- a/contracts/src/gateway/interfaces/IHomeGatewayBase.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@jaybuidl, @shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../../bridge/interfaces/IFastBridgeSender.sol"; - -interface IHomeGatewayBase { - function fastbridge() external view returns (IFastBridgeSender); - - function chainID() external view returns (uint256); - - function foreignChainID() external view returns (uint256); - - function foreignGateway() external view returns (address); -} diff --git a/contracts/src/gateway/interfaces/IHomeGatewayMock.sol b/contracts/src/gateway/interfaces/IHomeGatewayMock.sol deleted file mode 100644 index 16c6c151c..000000000 --- a/contracts/src/gateway/interfaces/IHomeGatewayMock.sol +++ /dev/null @@ -1,7 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import "./IHomeGatewayBase.sol"; - -interface IHomeGatewayMock is IHomeGatewayBase {} diff --git a/contracts/src/gateway/test/ForeignGatewayMock.sol b/contracts/src/gateway/test/ForeignGatewayMock.sol deleted file mode 100644 index 75f90cdf7..000000000 --- a/contracts/src/gateway/test/ForeignGatewayMock.sol +++ /dev/null @@ -1,72 +0,0 @@ -// 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 { - IFastBridgeReceiver public immutable fastbridge; - address public immutable override homeGateway; - uint256 public immutable override homeChainID; - uint256 public immutable override chainID; - - uint256 public messageCount; - uint256 public data; - - constructor( - IFastBridgeReceiver _fastbridge, - address _homeGateway, - uint256 _homeChainID - ) { - fastbridge = _fastbridge; - homeGateway = _homeGateway; - homeChainID = _homeChainID; - uint256 id; - assembly { - id := chainid() - } - chainID = id; - } - - modifier onlyFromFastBridge() { - require(address(fastbridge) == 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 deleted file mode 100644 index 7dc12d20e..000000000 --- a/contracts/src/gateway/test/HomeGatewayMock.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT - -/** - * @authors: [@shotaronowhere] - * @reviewers: [] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.8.0; - -import "../interfaces/IForeignGatewayMock.sol"; -import "../interfaces/IHomeGatewayBase.sol"; - -/** - * Home Gateway - * Counterpart of `ForeignGatewayMock` - */ -contract HomeGatewayMock is IHomeGatewayBase { - IFastBridgeSender public immutable fastbridge; - address public override foreignGateway; - uint256 public immutable override foreignChainID; - uint256 public immutable override chainID; - - struct RelayedData { - uint256 arbitrationCost; - address relayer; - } - mapping(bytes32 => RelayedData) public disputeHashtoRelayedData; - - constructor( - IFastBridgeSender _fastbridge, - address _foreignGateway, - uint256 _foreignChainID - ) { - fastbridge = _fastbridge; - foreignGateway = _foreignGateway; - foreignChainID = _foreignChainID; - uint256 id; - assembly { - id := chainid() - } - chainID = id; - } - - function sendFastMessage(uint256 _data) external { - bytes4 methodSelector = IForeignGatewayMock.receiveMessage.selector; - bytes memory data = abi.encodeWithSelector(methodSelector, _data); - - fastbridge.sendFast(foreignGateway, data); - } -}