Skip to content

feat: Fast Bridge unhappy path with Merkle tree #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 169 additions & 31 deletions contracts/src/bridge/FastBridgeReceiver.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT

/**
* @authors: [@shalzz]
* @authors: [@shalzz*, @hrishibhat*, @shotaronowhere]
* @reviewers: []
* @auditors: []
* @bounties: []
Expand All @@ -11,88 +11,226 @@
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.");
_;
}

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<claimsForIndex.length, "This claim is Challenged");

// 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");
if(keccak256(_encodedData) == claimsForIndex[claimsForIndex.length-1].messageHash){
claimsForIndex[claimsForIndex.length-1].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;
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");
function relayRule(bytes memory _encodedData) external override{
IBridge bridge = inbox.bridge();
require(msg.sender == address(bridge), "Not called by the Bridge");

IOutbox outbox = IOutbox(inbox.bridge().activeOutbox());
address l2Sender = outbox.l2ToL1Sender();
require(l2Sender == safebridge, "Can be relayed only by Safe Bridge");

(uint256 _fastMessageIndex, bytes memory dataWithReceiver) = abi.decode(_encodedData, (uint256, bytes));
bytes32 messageHash = keccak256(dataWithReceiver);

require(relayed[_fastMessageIndex] != true, "Claim already relayed");

Claim[] storage claimsForIndex = claims[_fastMessageIndex];
Challenge[] storage challengesForIndex = challenges[_fastMessageIndex];

if (claimsForIndex.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 ****//

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;
Expand Down
26 changes: 16 additions & 10 deletions contracts/src/bridge/FastBridgeSender.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT

/**
* @authors: [@shalzz]
* @authors: [@shalzz*, @hrishibhat*, @shotaronowhere]
* @reviewers: []
* @auditors: []
* @bounties: []
Expand All @@ -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;
Expand All @@ -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);
}

/**
Expand All @@ -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
Expand All @@ -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<fastMessageIndex, "Fast message does not exit.");

bytes memory encodedData = abi.encode(_fastMessageIndex, fastMessages[_fastMessageIndex]);
bytes memory encodedTxData = abi.encodeWithSelector(fastBridgeReceiver.relayRule.selector, encodedData);
safebridge.sendSafe{value: msg.value}(address(fastBridgeReceiver), encodedTxData);
}
}
9 changes: 8 additions & 1 deletion contracts/src/bridge/SafeBridgeArbitrum.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT

/**
* @authors: [@shalzz]
* @authors: [@shalzz*, @hrishibhat]
* @reviewers: []
* @auditors: []
* @bounties: []
Expand All @@ -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);
Expand Down
10 changes: 6 additions & 4 deletions contracts/src/bridge/interfaces/IFastBridgeReceiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
pragma solidity ^0.8.0;

interface IFastBridgeReceiver {
function claim(bytes32 _messageHash) external payable;
function claim(uint256 _fastMessageIndex, bytes32 _messageHash) external payable;

function verifyAndRelay(bytes32 _messageHash, bytes memory _calldata) external;
function verifyAndRelay(uint _fastMessageIndex, bytes memory _encodedData) external;

function withdrawClaimDeposit(bytes32 _messageHash) external;
function relayRule(bytes memory _encodedData) external;

function claimDeposit() external view returns (uint256 amount);
function withdrawClaimDeposit(uint256 _fastMessageIndex) external;

function viewClaimDeposit() external view returns (uint256 amount);
}
2 changes: 2 additions & 0 deletions contracts/src/bridge/interfaces/ISafeBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading