diff --git a/contracts/src/bridge/FastBridgeReceiver.sol b/contracts/src/bridge/FastBridgeReceiver.sol index 7c1fd9715..e5497721d 100644 --- a/contracts/src/bridge/FastBridgeReceiver.sol +++ b/contracts/src/bridge/FastBridgeReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz] + * @authors: [@shalzz*, @hrishibhat*, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -11,23 +11,40 @@ pragma solidity ^0.8.0; import "./interfaces/IFastBridgeReceiver.sol"; +import "./interfaces/arbitrum/Inbox.sol"; +import "./interfaces/arbitrum/Outbox.sol"; contract FastBridgeReceiver is IFastBridgeReceiver { + IInbox public inbox; + address public safebridge; + address public fastBridgeSender; address public governor; uint256 public claimDeposit; + uint256 public challengeDeposit; uint256 public challengeDuration; struct Claim { address bridger; + bytes32 messageHash; uint256 claimedAt; uint256 claimDeposit; - bool relayed; + bool honest; } - // messageHash => Claim - mapping(bytes32 => Claim) public claims; + struct Challenge { + address challenger; + uint256 challengedAt; + uint256 challengeDeposit; + bool honest; + } - event ClaimReceived(bytes32 messageHash, uint256 claimedAt); + // fastMessageIndex => Claim[] + mapping(uint256 => Claim[]) public claims; + mapping(uint256 => Challenge[]) public challenges; + mapping(uint256 => bool) public relayed; + + event ClaimReceived(uint256 _fastMessageIndex, bytes32 _messageHash, uint256 claimedAt); + event ClaimChallenged(uint256 _fastMessageIndex, uint256 _claimIndex, bytes32 _messageHash, uint256 challengedAt); modifier onlyByGovernor() { require(governor == msg.sender, "Access not allowed: Governor only."); @@ -35,57 +52,174 @@ contract FastBridgeReceiver is IFastBridgeReceiver { } constructor( + address _inbox, + address _safebridge, + address _fastBridgeSender, address _governor, uint256 _claimDeposit, uint256 _challengeDuration ) { + inbox = IInbox(_inbox); + safebridge = _safebridge; + fastBridgeSender = _fastBridgeSender; governor = _governor; claimDeposit = _claimDeposit; challengeDuration = _challengeDuration; } - function claim(bytes32 _messageHash) external payable { + function claim(uint256 _fastMessageIndex, bytes32 _messageHash) external payable override { require(msg.value >= claimDeposit, "Not enough claim deposit"); - require(claims[_messageHash].bridger == address(0), "Claimed already made"); + require(relayed[_fastMessageIndex] == false, "Message already relayed."); + // only accept additional claims if the previous claims are successfully challenged + // AND the message is not yet relayed + require(claims[_fastMessageIndex].length == 0 || challenges[_fastMessageIndex][claims[_fastMessageIndex].length-1].honest, "There is a previous unresolved claim"); - claims[_messageHash] = Claim({ + claims[_fastMessageIndex].push( Claim({ bridger: msg.sender, + messageHash: _messageHash, claimedAt: block.timestamp, claimDeposit: msg.value, - relayed: false - }); + honest: false + })); - emit ClaimReceived(_messageHash, block.timestamp); + emit ClaimReceived(_fastMessageIndex, _messageHash, block.timestamp); } - function verifyAndRelay(bytes32 _messageHash, bytes memory _encodedData) external { - require(keccak256(_encodedData) == _messageHash, "Invalid hash"); + function verifyAndRelay(uint _fastMessageIndex, bytes memory _encodedData) external override{ + + Claim[] storage claimsForIndex = claims[_fastMessageIndex]; + require(claimsForIndex.length>0, "Claim does not exist"); + + + require(claimsForIndex[claimsForIndex.length-1].claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); + require(relayed[_fastMessageIndex] == false, "Message already relayed"); + + Challenge[] storage challengesForIndex = challenges[_fastMessageIndex]; - Claim storage claim = claims[_messageHash]; - require(claim.bridger != address(0), "Claim does not exist"); - require(claim.claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); - require(claim.relayed == false, "Message already relayed"); + require(challengesForIndex.length 0){ + // has claims + if (claimsForIndex[claimsForIndex.length-1].messageHash == messageHash){ + claimsForIndex[claimsForIndex.length-1].honest = true; + } else if(challengesForIndex.length > 0){ + // has challenges + challengesForIndex[challengesForIndex.length - 1].honest = true; + } + } else{ + // no claims, no challenges + } + // dispute ruled + if (dataWithReceiver.length != 0){ + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(dataWithReceiver, (address, bytes)); + (bool success, ) = address(receiver).call(data); + require(success, "Failed to call contract"); + + relayed[_fastMessageIndex] = true; + } + + + } + + function withdrawClaimDeposit(uint256 _fastMessageIndex) external override{ + Claim[] storage claimsForIndex = claims[_fastMessageIndex]; + require(claimsForIndex.length>0, "Claim does not exist"); + + + require(claimsForIndex[claimsForIndex.length-1].claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); + require(relayed[_fastMessageIndex] == true, "Claim not relayed"); + + Challenge[] storage challengesForIndex = challenges[_fastMessageIndex]; - uint256 amount = claim.claimDeposit; - claim.claimDeposit = 0; - payable(claim.bridger).send(amount); + uint256 amount = 0; + if(claimsForIndex[claimsForIndex.length-1].honest == true){ + amount += claimsForIndex[claimsForIndex.length-1].claimDeposit; + if(challengesForIndex.length == claimsForIndex.length){ + if(challengesForIndex[challengesForIndex.length - 1].honest == false){ + amount += challengesForIndex[challengesForIndex.length - 1].challengeDeposit; + challengesForIndex[challengesForIndex.length - 1].challengeDeposit = 0; + } + } + } + + claimsForIndex[claimsForIndex.length-1].claimDeposit = 0; + payable(claimsForIndex[claimsForIndex.length-1].bridger).send(amount); } - function challenge() external { - revert("Not Implemented"); + function challenge(uint256 _fastMessageIndex) external payable { + Claim[] memory claimsForIndex = claims[_fastMessageIndex]; + require(claimsForIndex.length > 0, "Claim does not exist"); + require(relayed[_fastMessageIndex] == false, "Message already relayed"); + require(block.timestamp - claimsForIndex[claimsForIndex.length-1].claimedAt < challengeDuration, "Challenge period over"); + require(msg.value >= challengeDeposit, "Not enough challenge deposit"); + require(challenges[_fastMessageIndex].length < claimsForIndex.length, "Claim already challenged"); + + challenges[_fastMessageIndex].push(Challenge({ + challenger: msg.sender, + challengedAt: block.timestamp, + challengeDeposit: msg.value, + honest: false + })); + + emit ClaimChallenged(_fastMessageIndex,claimsForIndex.length-1, claimsForIndex[claimsForIndex.length-1].messageHash, block.timestamp); + } + + function withdrawChallengeDeposit(uint256 _fastMessageIndex, uint256 challengeIndex) external { + Challenge[] storage challengesForIndex = challenges[_fastMessageIndex]; + require(challengesForIndex.length>0 && challengeIndex < challengesForIndex.length, "Challenge does not exist"); + + Claim[] storage claimsForIndex = claims[_fastMessageIndex]; + + uint256 amount = 0; + if(challengesForIndex[challengeIndex].honest == true){ + amount += challengesForIndex[challengeIndex].challengeDeposit; + if(claimsForIndex[challengeIndex].honest == false){ + amount += claimsForIndex[challengeIndex].claimDeposit; + claimsForIndex[challengeIndex].claimDeposit = 0; + } + } + + + challengesForIndex[challengeIndex].challengeDeposit = 0; + payable(challengesForIndex[challengeIndex].challenger).send(amount); + } + //**** View Functions ****// + function challengePeriod(uint256 _fastMessageIndex) public view returns(uint start, uint end) { + if (claims[_fastMessageIndex].length > 0){ + start = claims[_fastMessageIndex][claims[_fastMessageIndex].length-1].claimedAt; + end = start + challengeDuration; + } + + return (start, end); } //**** Governor functions ****// @@ -93,6 +227,10 @@ contract FastBridgeReceiver is IFastBridgeReceiver { function setClaimDeposit(uint256 _claimDeposit) external onlyByGovernor { claimDeposit = _claimDeposit; } + //TODO + function viewClaimDeposit() external override view returns (uint256 amount){ + return 0; + } function setChallengePeriodDuration(uint256 _challengeDuration) external onlyByGovernor { challengeDuration = _challengeDuration; diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 3d855a357..5ce7d65bb 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz] + * @authors: [@shalzz*, @hrishibhat*, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -18,12 +18,13 @@ contract FastBridgeSender is IFastBridgeSender { ISafeBridge public safebridge; IFastBridgeReceiver public fastBridgeReceiver; address public fastSender; - + mapping(uint256 => bytes) public fastMessages; + uint256 fastMessageIndex; /** * The bridgers need to watch for these events and * relay the messageHash on the FastBridgeReceiver. */ - event OutgoingMessage(address target, bytes32 messageHash, bytes message); + event OutgoingMessage(address target, bytes32 messageHash, uint256 fastMessageIndex, bytes message); constructor(ISafeBridge _safebridge, IFastBridgeReceiver _fastBridgeReceiver) { safebridge = _safebridge; @@ -42,13 +43,15 @@ contract FastBridgeSender is IFastBridgeSender { * @param _receiver The L1 contract address who will receive the calldata * @param _calldata The receiving domain encoded message data. */ - function sendFast(address _receiver, bytes memory _calldata) external { + function sendFast(address _receiver, bytes memory _calldata) external override { require(msg.sender == fastSender, "Access not allowed: Fast Sender only."); // Encode the receiver address with the function signature + arguments i.e calldata bytes memory encodedData = abi.encode(_receiver, _calldata); + fastMessages[fastMessageIndex] = encodedData; + fastMessageIndex += 1; - emit OutgoingMessage(_receiver, keccak256(encodedData), encodedData); + emit OutgoingMessage(_receiver, keccak256(encodedData), fastMessageIndex-1, encodedData); } /** @@ -60,10 +63,10 @@ contract FastBridgeSender is IFastBridgeSender { * It may require some ETH (or whichever native token) to pay for the bridging cost, * depending on the underlying safe bridge. * - * @param _receiver The L1 contract address who will receive the calldata - * @param _calldata The receiving domain encoded message data. + * @param _fastMessageIndex The index of messageHash to send */ - function sendSafe(address _receiver, bytes memory _calldata) external payable { + function sendSafe(uint256 _fastMessageIndex) external payable { + // The safe bridge sends the encoded data to the FastBridgeReceiver // in order for the FastBridgeReceiver to resolve any potential // challenges and then forwards the message to the actual @@ -72,7 +75,10 @@ contract FastBridgeSender is IFastBridgeSender { // IFastBridgeReceiver function. // TODO: add access checks for this on the FastBridgeReceiver. // TODO: how much ETH should be provided for bridging? add an ISafeBridge.bridgingCost() - bytes memory encodedData = abi.encode(_receiver, _calldata); - safebridge.sendSafe{value: msg.value}(address(fastBridgeReceiver), encodedData); + require(_fastMessageIndex=0.7.0; + + +interface IInbox { + + + function sendL2Message(bytes calldata messageData) external returns (uint256); + + function sendUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256); + + function sendL1FundedUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function sendL1FundedContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + bytes calldata data + ) external payable returns (uint256); + + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256); + + function depositEth(uint256 maxSubmissionCost) external payable returns (uint256); + + function bridge() external view returns (IBridge); +} + + +interface IBridge { + event MessageDelivered( + uint256 indexed messageIndex, + bytes32 indexed beforeInboxAcc, + address inbox, + uint8 kind, + address sender, + bytes32 messageDataHash + ); + + function deliverMessageToInbox( + uint8 kind, + address sender, + bytes32 messageDataHash + ) external payable returns (uint256); + + function executeCall( + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (bool success, bytes memory returnData); + + // These are only callable by the admin + function setInbox(address inbox, bool enabled) external; + + function setOutbox(address inbox, bool enabled) external; + + // View functions + + function activeOutbox() external view returns (address); + + function allowedInboxes(address inbox) external view returns (bool); + + function allowedOutboxes(address outbox) external view returns (bool); + + function inboxAccs(uint256 index) external view returns (bytes32); + + function messageCount() external view returns (uint256); +} + +interface IMessageProvider { + event InboxMessageDelivered(uint256 indexed messageNum, bytes data); + + event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum); +} \ No newline at end of file diff --git a/contracts/src/bridge/interfaces/arbitrum/Outbox.sol b/contracts/src/bridge/interfaces/arbitrum/Outbox.sol new file mode 100644 index 000000000..d4f35c4dc --- /dev/null +++ b/contracts/src/bridge/interfaces/arbitrum/Outbox.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +/* + * Copyright 2021, Offchain Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +pragma solidity >=0.7.0; + +interface IOutbox { + event OutboxEntryCreated( + uint256 indexed batchNum, + uint256 outboxIndex, + bytes32 outputRoot, + uint256 numInBatch + ); + + function l2ToL1Sender() external view returns (address); + + function l2ToL1Block() external view returns (uint256); + + function l2ToL1EthBlock() external view returns (uint256); + + function l2ToL1Timestamp() external view returns (uint256); + + function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) + external; +} \ No newline at end of file diff --git a/contracts/src/gateway/ForeignGateway.sol b/contracts/src/gateway/ForeignGateway.sol index 0b5114dc0..9f9d947a0 100644 --- a/contracts/src/gateway/ForeignGateway.sol +++ b/contracts/src/gateway/ForeignGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz] + * @authors: [@shalzz*, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -27,8 +27,8 @@ contract ForeignGateway is IForeignGateway { // feeForJuror by subcourtID uint256[] internal feeForJuror; - uint256 public chainID; - uint256 public homeChainID; + uint256 public override chainID; + uint256 public override homeChainID; struct DisputeData { uint248 id; @@ -41,7 +41,7 @@ contract ForeignGateway is IForeignGateway { address public governor; IFastBridgeReceiver public fastbridge; - address public homeGateway; + address public override homeGateway; event OutgoingDispute( bytes32 disputeHash, @@ -88,7 +88,7 @@ contract ForeignGateway is IForeignGateway { feeForJuror[_subcourtID] = _feeForJuror; } - function createDispute(uint256 _choices, bytes calldata _extraData) external payable returns (uint256 disputeID) { + function createDispute(uint256 _choices, bytes calldata _extraData) external payable override returns (uint256 disputeID) { require(msg.value >= arbitrationCost(_extraData), "Not paid enough for arbitration"); (uint96 subcourtID, ) = extraDataToSubcourtIDMinJurors(_extraData); @@ -121,7 +121,7 @@ contract ForeignGateway is IForeignGateway { emit DisputeCreation(disputeID, IArbitrable(msg.sender)); } - function arbitrationCost(bytes calldata _extraData) public view returns (uint256 cost) { + function arbitrationCost(bytes calldata _extraData) public view override returns (uint256 cost) { (uint96 subcourtID, uint256 minJurors) = extraDataToSubcourtIDMinJurors(_extraData); cost = feeForJuror[subcourtID] * minJurors; @@ -134,7 +134,7 @@ contract ForeignGateway is IForeignGateway { bytes32 _disputeHash, uint256 _ruling, address _relayer - ) external onlyFromFastBridge { + ) external override onlyFromFastBridge { DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); @@ -147,7 +147,7 @@ contract ForeignGateway is IForeignGateway { arbitrable.rule(dispute.id, _ruling); } - function withdrawFees(bytes32 _disputeHash) external { + function withdrawFees(bytes32 _disputeHash) external override { DisputeData storage dispute = disputeHashtoDisputeData[_disputeHash]; require(dispute.id != 0, "Dispute does not exist"); require(dispute.ruled, "Not ruled yet"); @@ -157,7 +157,7 @@ contract ForeignGateway is IForeignGateway { payable(dispute.relayer).transfer(amount); } - function disputeHashToForeignID(bytes32 _disputeHash) external view returns (uint256) { + function disputeHashToForeignID(bytes32 _disputeHash) external view override returns (uint256) { return disputeHashtoDisputeData[_disputeHash].id; } diff --git a/contracts/src/gateway/HomeGateway.sol b/contracts/src/gateway/HomeGateway.sol index 3fdba3775..135050d8e 100644 --- a/contracts/src/gateway/HomeGateway.sol +++ b/contracts/src/gateway/HomeGateway.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz] + * @authors: [@shalzz*, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -22,9 +22,9 @@ contract HomeGateway is IHomeGateway { IArbitrator public arbitrator; IFastBridgeSender public fastbridge; - address public foreignGateway; - uint256 public chainID; - uint256 public foreignChainID; + address public override foreignGateway; + uint256 public override chainID; + uint256 public override foreignChainID; struct RelayedData { uint256 arbitrationCost; @@ -70,7 +70,7 @@ contract HomeGateway is IHomeGateway { uint256 _choices, bytes calldata _extraData, address _arbitrable - ) external payable { + ) external payable override{ bytes32 disputeHash = keccak256( abi.encodePacked( _originalChainID, @@ -97,19 +97,20 @@ contract HomeGateway is IHomeGateway { emit Dispute(arbitrator, disputeID, 0, 0); } - function rule(uint256 _disputeID, uint256 _ruling) external { + function rule(uint256 _disputeID, uint256 _ruling) external override{ require(msg.sender == address(arbitrator), "Only Arbitrator"); bytes32 disputeHash = disputeIDtoHash[_disputeID]; RelayedData memory relayedData = disputeHashtoRelayedData[disputeHash]; + bytes4 methodSelector = IForeignGateway.relayRule.selector; - bytes memory data = abi.encodeWithSelector(methodSelector, disputeHash, _ruling, relayedData.relayer); + bytes memory data = abi.encodeWithSelector( methodSelector, disputeHash, _ruling, relayedData.relayer); fastbridge.sendFast(foreignGateway, data); } - function disputeHashToHomeID(bytes32 _disputeHash) external view returns (uint256) { + function disputeHashToHomeID(bytes32 _disputeHash) external view override returns (uint256) { return disputeHashtoID[_disputeHash]; } }