From 89d25188684f4bdade591b4c95c318081a53fc90 Mon Sep 17 00:00:00 2001 From: Hrishikesh Bhat Date: Mon, 28 Feb 2022 10:43:09 +0530 Subject: [PATCH 1/3] added challenge for FastBridgeReceiver created challenge path & required modifications. --- contracts/src/bridge/FastBridgeReceiver.sol | 122 ++++++++++++++++-- contracts/src/bridge/FastBridgeSender.sol | 6 +- contracts/src/bridge/SafeBridgeArbitrum.sol | 9 +- .../bridge/interfaces/IFastBridgeReceiver.sol | 2 + .../src/bridge/interfaces/ISafeBridge.sol | 2 + .../src/bridge/interfaces/arbitrum/Inbox.sol | 103 +++++++++++++++ .../src/bridge/interfaces/arbitrum/Outbox.sol | 39 ++++++ 7 files changed, 271 insertions(+), 12 deletions(-) create mode 100644 contracts/src/bridge/interfaces/arbitrum/Inbox.sol create mode 100644 contracts/src/bridge/interfaces/arbitrum/Outbox.sol diff --git a/contracts/src/bridge/FastBridgeReceiver.sol b/contracts/src/bridge/FastBridgeReceiver.sol index 7c1fd9715..cebb24f51 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] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -11,8 +11,13 @@ 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 challengeDuration; @@ -22,12 +27,23 @@ contract FastBridgeReceiver is IFastBridgeReceiver { uint256 claimedAt; uint256 claimDeposit; bool relayed; + bool honest; } + struct Challenge { + address challenge; + uint256 challengedAt; + uint256 challengeDeposit; + bool challenged; + bool honest; + } + // messageHash => Claim mapping(bytes32 => Claim) public claims; + mapping(bytes32 => Challenge) public challenges; event ClaimReceived(bytes32 messageHash, uint256 claimedAt); + event ClaimChallenged(bytes32 messageHash, uint256 challengedAt); modifier onlyByGovernor() { require(governor == msg.sender, "Access not allowed: Governor only."); @@ -35,10 +51,16 @@ 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; @@ -52,6 +74,7 @@ contract FastBridgeReceiver is IFastBridgeReceiver { bridger: msg.sender, claimedAt: block.timestamp, claimDeposit: msg.value, + honest: false, relayed: false }); @@ -59,35 +82,116 @@ contract FastBridgeReceiver is IFastBridgeReceiver { } function verifyAndRelay(bytes32 _messageHash, bytes memory _encodedData) external { - require(keccak256(_encodedData) == _messageHash, "Invalid hash"); 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"); - // Decode the receiver address from the data encoded by the IFastBridgeSender - (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); - (bool success, ) = address(receiver).call(data); - require(success, "Failed to call contract"); + Challenge storage challenge = challenges[_messageHash]; + if(challenge.challenged == true){ + if(keccak256(_encodedData) == _messageHash){ + challenge.honest == false; + claim.honest == true; + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); + (bool success, ) = address(receiver).call(data); + require(success, "Failed to call contract"); + + claim.relayed = true; + } + else{ + challenge.honest == true; + claim.honest == false; + } + } + } + + function relayRule(bytes memory _encodedData) external { + IBridge bridge = inbox.bridge(); + require(msg.sender == address(bridge), "Not called by the Bridge"); - claim.relayed = true; + IOutbox outbox = IOutbox(inbox.bridge().activeOutbox()); + address l2Sender = outbox.l2ToL1Sender(); + require(l2Sender == safebridge, "Can be relayed only by Safe Bridge"); + + Challenge storage challenge = challenges[_messageHash]; + Claim storage claim = claims[_messageHash]; + + require(challenge.honest == true, "This claim is not challenged"); + + if(keccak256(_encodedData) == _messageHash){ + challenge.honest == false; + claim.honest == true; + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); + (bool success, ) = address(receiver).call(data); + require(success, "Failed to call contract"); + + claim.relayed = true; + } } function withdrawClaimDeposit(bytes32 _messageHash) external { 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 == true, "Claim not relayed"); + Challenge storage challenge = challenges[_messageHash]; uint256 amount = claim.claimDeposit; + if(challenge.honest == false){ + amount += challenge.challengeDeposit; + challenge.challengeDeposit = 0; + } + claim.claimDeposit = 0; payable(claim.bridger).send(amount); } - function challenge() external { - revert("Not Implemented"); + function challenge(bytes32 _messageHash) external payable { + Claim storage claim = claims[_messageHash]; + require(claim.bridger != address(0), "Claim does not exist"); + require(block.timestamp - claim.claimedAt < challengeDuration, "Challenge period over"); + require(msg.value >= challengeDeposit, "Not enough challenge deposit"); + require(challenges[_messageHash].challenger == address(0), "Claim already challenged"); + + challenges[_messageHash] = Challenge({ + challenger: msg.sender, + challengedAt: block.timestamp, + challengeDeposit: msg.value, + challenged: true, + honest: false + }); + + emit ClaimChallenged(_messageHash, block.timestamp); + } + function withdrawChallengeDeposit(bytes32 _messageHash) external { + Challenge storage challenge = challenges[_messageHash]; + require(challenge.challenger != address(0), "Challenge does not exist"); + require(challenge.honest == true, "Challenge not honest"); + + Claim storage claim = claims[_messageHash]; + require(block.timestamp > claim.claimedAt + challengeDuration, "Challenge period not over"); + require(claim.relayed == true, "Claim not relayed"); + + uint256 amount = challenge.challengeDeposit; + if(claim.honest == false){ + amount += claim.claimDeposit; + claim.claimDeposit = 0; + } + + challenge.challengeDeposit = 0; + payable(challenge.challenger).send(amount); + } + //**** View Functions ****// + function challengePeriod(bytes messageHash) public view returns(uint start, uint end) { + start = claims[_messageHash].claimedAt; + end = start + challengeDuration; + return (start, end); + } //**** Governor functions ****// function setClaimDeposit(uint256 _claimDeposit) external onlyByGovernor { diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 3d855a357..9999b7710 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] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -72,7 +72,9 @@ 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); + bytes memory encodedTxData = abi.encodeWithSelector(fastBridgeReceiver.relayRule.selector, encodedData); + safebridge.sendSafe{value: msg.value}(address(fastBridgeReceiver), encodedTxData); } } diff --git a/contracts/src/bridge/SafeBridgeArbitrum.sol b/contracts/src/bridge/SafeBridgeArbitrum.sol index fd168137f..fd146f993 100644 --- a/contracts/src/bridge/SafeBridgeArbitrum.sol +++ b/contracts/src/bridge/SafeBridgeArbitrum.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz] + * @authors: [@shalzz, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -14,13 +14,20 @@ import "./interfaces/arbitrum/IArbSys.sol"; import "./interfaces/arbitrum/AddressAliasHelper.sol"; import "./interfaces/ISafeBridge.sol"; +import "./interfaces/IFastBridgeSender.sol"; contract SafeBridgeArbitrum is ISafeBridge { IArbSys constant arbsys = IArbSys(address(100)); + IFastBridgeSender public fastBridgeSender; event L2ToL1TxCreated(uint256 indexed withdrawalId); + constructor(IFastBridgeSender _fastBridgeSender) { + fastBridgeSender = _fastBridgeSender; + } function sendSafe(address _receiver, bytes memory _calldata) external payable override returns (uint256) { + require(msg.sender == address(fastBridgeSender), "Access not allowed: Fast Bridge Sender only."); + uint256 withdrawalId = arbsys.sendTxToL1(_receiver, _calldata); emit L2ToL1TxCreated(withdrawalId); diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index bbb50572c..81772cfa2 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -7,6 +7,8 @@ interface IFastBridgeReceiver { function verifyAndRelay(bytes32 _messageHash, bytes memory _calldata) external; + function relayRule(bytes memory _calldata) external; + function withdrawClaimDeposit(bytes32 _messageHash) external; function claimDeposit() external view returns (uint256 amount); diff --git a/contracts/src/bridge/interfaces/ISafeBridge.sol b/contracts/src/bridge/interfaces/ISafeBridge.sol index b3aef94c4..4f7a226b6 100644 --- a/contracts/src/bridge/interfaces/ISafeBridge.sol +++ b/contracts/src/bridge/interfaces/ISafeBridge.sol @@ -11,4 +11,6 @@ interface ISafeBridge { * @return Unique id to track the message request/transaction. */ function sendSafe(address _receiver, bytes memory _calldata) external payable returns (uint256); + + function bridgingCost() external view returns(uint256 amount); } diff --git a/contracts/src/bridge/interfaces/arbitrum/Inbox.sol b/contracts/src/bridge/interfaces/arbitrum/Inbox.sol new file mode 100644 index 000000000..9216d9b6c --- /dev/null +++ b/contracts/src/bridge/interfaces/arbitrum/Inbox.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=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 From cddc2b7af73c23caedb6a82d4e6aeb15aa158ca4 Mon Sep 17 00:00:00 2001 From: Hrishikesh Bhat Date: Mon, 7 Mar 2022 00:14:23 +0530 Subject: [PATCH 2/3] changes in the fastBridgeReceiver --- contracts/src/bridge/FastBridgeReceiver.sol | 65 +++++++++---------- contracts/src/bridge/FastBridgeSender.sol | 2 +- contracts/src/bridge/SafeBridgeArbitrum.sol | 2 +- .../bridge/interfaces/IFastBridgeReceiver.sol | 2 +- 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiver.sol b/contracts/src/bridge/FastBridgeReceiver.sol index cebb24f51..628bec132 100644 --- a/contracts/src/bridge/FastBridgeReceiver.sol +++ b/contracts/src/bridge/FastBridgeReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz, @hrishibhat] + * @authors: [@shalzz*, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -20,6 +20,7 @@ contract FastBridgeReceiver is IFastBridgeReceiver { address public fastBridgeSender; address public governor; uint256 public claimDeposit; + uint256 public challengeDeposit; uint256 public challengeDuration; struct Claim { @@ -31,10 +32,9 @@ contract FastBridgeReceiver is IFastBridgeReceiver { } struct Challenge { - address challenge; + address challenger; uint256 challengedAt; uint256 challengeDeposit; - bool challenged; bool honest; } @@ -89,22 +89,18 @@ contract FastBridgeReceiver is IFastBridgeReceiver { require(claim.relayed == false, "Message already relayed"); Challenge storage challenge = challenges[_messageHash]; - if(challenge.challenged == true){ - if(keccak256(_encodedData) == _messageHash){ - challenge.honest == false; - claim.honest == true; - // Decode the receiver address from the data encoded by the IFastBridgeSender - (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); - (bool success, ) = address(receiver).call(data); - require(success, "Failed to call contract"); - - claim.relayed = true; - } - else{ - challenge.honest == true; - claim.honest == false; - } + require(challenge.challenger == address(0), "This claim is Challenged"); + + if(keccak256(_encodedData) == _messageHash){ + claim.honest = true; + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); + (bool success, ) = address(receiver).call(data); + require(success, "Failed to call contract"); + + claim.relayed = true; } + } function relayRule(bytes memory _encodedData) external { @@ -115,21 +111,26 @@ contract FastBridgeReceiver is IFastBridgeReceiver { address l2Sender = outbox.l2ToL1Sender(); require(l2Sender == safebridge, "Can be relayed only by Safe Bridge"); + bytes32 _messageHash = keccak256(_encodedData); Challenge storage challenge = challenges[_messageHash]; Claim storage claim = claims[_messageHash]; + require(claim.relayed != true, "Claim already relayed"); + + if (claim.bridger != address(0) && challenge.challenger != address(0)) { + challenge.honest = false; + claim.honest = true; + } else { + challenge.honest = true; + claim.honest = false; + } - require(challenge.honest == true, "This claim is not challenged"); - - if(keccak256(_encodedData) == _messageHash){ - challenge.honest == false; - claim.honest == true; - // Decode the receiver address from the data encoded by the IFastBridgeSender - (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); - (bool success, ) = address(receiver).call(data); - require(success, "Failed to call contract"); + // Decode the receiver address from the data encoded by the IFastBridgeSender + (address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes)); + (bool success, ) = address(receiver).call(data); + require(success, "Failed to call contract"); - claim.relayed = true; - } + claim.relayed = true; + } function withdrawClaimDeposit(bytes32 _messageHash) external { @@ -150,7 +151,7 @@ contract FastBridgeReceiver is IFastBridgeReceiver { } function challenge(bytes32 _messageHash) external payable { - Claim storage claim = claims[_messageHash]; + Claim memory claim = claims[_messageHash]; require(claim.bridger != address(0), "Claim does not exist"); require(block.timestamp - claim.claimedAt < challengeDuration, "Challenge period over"); require(msg.value >= challengeDeposit, "Not enough challenge deposit"); @@ -160,12 +161,10 @@ contract FastBridgeReceiver is IFastBridgeReceiver { challenger: msg.sender, challengedAt: block.timestamp, challengeDeposit: msg.value, - challenged: true, honest: false }); emit ClaimChallenged(_messageHash, block.timestamp); - } function withdrawChallengeDeposit(bytes32 _messageHash) external { @@ -187,7 +186,7 @@ contract FastBridgeReceiver is IFastBridgeReceiver { payable(challenge.challenger).send(amount); } //**** View Functions ****// - function challengePeriod(bytes messageHash) public view returns(uint start, uint end) { + function challengePeriod(bytes32 _messageHash) public view returns(uint start, uint end) { start = claims[_messageHash].claimedAt; end = start + challengeDuration; return (start, end); diff --git a/contracts/src/bridge/FastBridgeSender.sol b/contracts/src/bridge/FastBridgeSender.sol index 9999b7710..67710245c 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz, @hrishibhat] + * @authors: [@shalzz*, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] diff --git a/contracts/src/bridge/SafeBridgeArbitrum.sol b/contracts/src/bridge/SafeBridgeArbitrum.sol index fd146f993..43f1af389 100644 --- a/contracts/src/bridge/SafeBridgeArbitrum.sol +++ b/contracts/src/bridge/SafeBridgeArbitrum.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz, @hrishibhat] + * @authors: [@shalzz*, @hrishibhat] * @reviewers: [] * @auditors: [] * @bounties: [] diff --git a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol index 81772cfa2..a87bd71bf 100644 --- a/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol +++ b/contracts/src/bridge/interfaces/IFastBridgeReceiver.sol @@ -11,5 +11,5 @@ interface IFastBridgeReceiver { function withdrawClaimDeposit(bytes32 _messageHash) external; - function claimDeposit() external view returns (uint256 amount); + function viewClaimDeposit() external view returns (uint256 amount); } From 00d6d3664a7d0aa6c2481e88b7aaeb8dc9fe6b56 Mon Sep 17 00:00:00 2001 From: shotaro Date: Mon, 14 Mar 2022 13:12:20 +0000 Subject: [PATCH 3/3] implemented safe bridge receiver/sender logic for unhappy path --- contracts/src/bridge/FastBridgeReceiver.sol | 193 +++++++++++------- contracts/src/bridge/FastBridgeSender.sol | 22 +- .../bridge/interfaces/IFastBridgeReceiver.sol | 8 +- 3 files changed, 131 insertions(+), 92 deletions(-) diff --git a/contracts/src/bridge/FastBridgeReceiver.sol b/contracts/src/bridge/FastBridgeReceiver.sol index 628bec132..e5497721d 100644 --- a/contracts/src/bridge/FastBridgeReceiver.sol +++ b/contracts/src/bridge/FastBridgeReceiver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz*, @hrishibhat] + * @authors: [@shalzz*, @hrishibhat*, @shotaronowhere] * @reviewers: [] * @auditors: [] * @bounties: [] @@ -25,9 +25,9 @@ contract FastBridgeReceiver is IFastBridgeReceiver { struct Claim { address bridger; + bytes32 messageHash; uint256 claimedAt; uint256 claimDeposit; - bool relayed; bool honest; } @@ -38,12 +38,13 @@ contract FastBridgeReceiver is IFastBridgeReceiver { bool honest; } - // messageHash => Claim - mapping(bytes32 => Claim) public claims; - mapping(bytes32 => Challenge) public challenges; + // fastMessageIndex => Claim[] + mapping(uint256 => Claim[]) public claims; + mapping(uint256 => Challenge[]) public challenges; + mapping(uint256 => bool) public relayed; - event ClaimReceived(bytes32 messageHash, uint256 claimedAt); - event ClaimChallenged(bytes32 messageHash, uint256 challengedAt); + 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."); @@ -66,44 +67,50 @@ contract FastBridgeReceiver is IFastBridgeReceiver { 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, - honest: false, - relayed: false - }); + honest: false + })); - emit ClaimReceived(_messageHash, block.timestamp); + emit ClaimReceived(_fastMessageIndex, _messageHash, block.timestamp); } - function verifyAndRelay(bytes32 _messageHash, bytes memory _encodedData) external { + function verifyAndRelay(uint _fastMessageIndex, bytes memory _encodedData) external override{ + + Claim[] storage claimsForIndex = claims[_fastMessageIndex]; + require(claimsForIndex.length>0, "Claim does not exist"); + - 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(claimsForIndex[claimsForIndex.length-1].claimedAt + challengeDuration < block.timestamp, "Challenge period not over"); + require(relayed[_fastMessageIndex] == false, "Message already relayed"); - Challenge storage challenge = challenges[_messageHash]; - require(challenge.challenger == address(0), "This claim is Challenged"); + Challenge[] storage challengesForIndex = challenges[_fastMessageIndex]; - if(keccak256(_encodedData) == _messageHash){ - claim.honest = true; + 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(bytes32 _messageHash) external { - 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 == true, "Claim not relayed"); - - Challenge storage challenge = challenges[_messageHash]; - uint256 amount = claim.claimDeposit; - if(challenge.honest == false){ - amount += challenge.challengeDeposit; - challenge.challengeDeposit = 0; + 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 = 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; + } + } } - claim.claimDeposit = 0; - payable(claim.bridger).send(amount); + claimsForIndex[claimsForIndex.length-1].claimDeposit = 0; + payable(claimsForIndex[claimsForIndex.length-1].bridger).send(amount); } - function challenge(bytes32 _messageHash) external payable { - Claim memory claim = claims[_messageHash]; - require(claim.bridger != address(0), "Claim does not exist"); - require(block.timestamp - claim.claimedAt < challengeDuration, "Challenge period over"); + 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[_messageHash].challenger == address(0), "Claim already challenged"); + require(challenges[_fastMessageIndex].length < claimsForIndex.length, "Claim already challenged"); - challenges[_messageHash] = Challenge({ + challenges[_fastMessageIndex].push(Challenge({ challenger: msg.sender, challengedAt: block.timestamp, challengeDeposit: msg.value, honest: false - }); + })); - emit ClaimChallenged(_messageHash, block.timestamp); + emit ClaimChallenged(_fastMessageIndex,claimsForIndex.length-1, claimsForIndex[claimsForIndex.length-1].messageHash, block.timestamp); } - function withdrawChallengeDeposit(bytes32 _messageHash) external { - Challenge storage challenge = challenges[_messageHash]; - require(challenge.challenger != address(0), "Challenge does not exist"); - require(challenge.honest == true, "Challenge not honest"); + 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 claim = claims[_messageHash]; - require(block.timestamp > claim.claimedAt + challengeDuration, "Challenge period not over"); - require(claim.relayed == true, "Claim not relayed"); + Claim[] storage claimsForIndex = claims[_fastMessageIndex]; - uint256 amount = challenge.challengeDeposit; - if(claim.honest == false){ - amount += claim.claimDeposit; - claim.claimDeposit = 0; + 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; + } } - challenge.challengeDeposit = 0; - payable(challenge.challenger).send(amount); + + challengesForIndex[challengeIndex].challengeDeposit = 0; + payable(challengesForIndex[challengeIndex].challenger).send(amount); } //**** View Functions ****// - function challengePeriod(bytes32 _messageHash) public view returns(uint start, uint end) { - start = claims[_messageHash].claimedAt; - end = start + challengeDuration; + 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 ****// 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 67710245c..5ce7d65bb 100644 --- a/contracts/src/bridge/FastBridgeSender.sol +++ b/contracts/src/bridge/FastBridgeSender.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT /** - * @authors: [@shalzz*, @hrishibhat] + * @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,8 +75,9 @@ 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() + require(_fastMessageIndex