Skip to content

Commit 182563c

Browse files
committed
feat(FastBridge): unhappy path introducing a ticketID
1 parent f9b40f4 commit 182563c

File tree

6 files changed

+153
-100
lines changed

6 files changed

+153
-100
lines changed

contracts/deploy/02-home-chain.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment)
4242
.getContractAt("FastBridgeSenderToEthereum", fastBridgeSender.address)
4343
.then((contract) => contract.fastSender());
4444
if (fastSender === ethers.constants.AddressZero) {
45-
await execute("FastBridgeSenderToEthereum", { from: deployer, log: true }, "setFastSender", homeGateway.address);
45+
await execute("FastBridgeSenderToEthereum", { from: deployer, log: true }, "changeFastSender", homeGateway.address);
4646
}
4747
};
4848

contracts/src/bridge/FastBridgeReceiverOnEthereum.sol

Lines changed: 100 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,24 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid
2323
// ************************************* //
2424

2525
struct Claim {
26+
bytes32 messageHash;
2627
address bridger;
2728
uint256 claimedAt;
2829
uint256 claimDeposit;
29-
bool relayed;
30+
bool verified;
3031
}
3132

3233
struct Challenge {
3334
address challenger;
3435
uint256 challengedAt;
3536
uint256 challengeDeposit;
37+
}
38+
39+
struct Ticket {
40+
Claim claim;
41+
Challenge challenge;
3642
bool relayed;
37-
}
43+
}
3844

3945
// ************************************* //
4046
// * Storage * //
@@ -43,129 +49,135 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid
4349
uint256 public override claimDeposit;
4450
uint256 public override challengeDeposit;
4551
uint256 public override challengeDuration;
46-
uint256 public override safeBridgeTimeout;
47-
mapping(bytes32 => Claim) public claims; // The claims by message hash.
48-
mapping(bytes32 => Challenge) public challenges; // The challenges by message hash.
52+
mapping(uint256 => Ticket) public tickets; // The tickets by ticketID.
4953

5054
// ************************************* //
5155
// * Events * //
5256
// ************************************* //
5357

54-
event ClaimReceived(bytes32 indexed messageHash, uint256 claimedAt);
55-
event ClaimChallenged(bytes32 indexed _messageHash, uint256 challengedAt);
58+
event ClaimReceived(uint256 indexed _ticketID, bytes32 indexed messageHash, uint256 claimedAt);
59+
event ClaimChallenged(uint256 indexed _ticketID, bytes32 indexed _messageHash, uint256 challengedAt);
5660

5761
constructor(
5862
address _governor,
5963
address _safeBridgeSender,
6064
address _inbox,
6165
uint256 _claimDeposit,
6266
uint256 _challengeDeposit,
63-
uint256 _challengeDuration,
64-
uint256 _safeBridgeTimeout
67+
uint256 _challengeDuration
6568
) SafeBridgeReceiverOnEthereum(_governor, _safeBridgeSender, _inbox) {
6669
claimDeposit = _claimDeposit;
6770
challengeDeposit = _challengeDeposit;
6871
challengeDuration = _challengeDuration;
69-
safeBridgeTimeout - _safeBridgeTimeout;
7072
}
7173

7274
// ************************************* //
7375
// * State Modifiers * //
7476
// ************************************* //
7577

76-
function claim(bytes32 _messageHash) external payable override {
78+
function claim(uint256 _ticketID, bytes32 _messageHash) external payable override {
7779
require(msg.value >= claimDeposit, "Not enough claim deposit");
78-
require(claims[_messageHash].bridger == address(0), "Claimed already made");
80+
require(tickets[_ticketID].claim.bridger == address(0), "Claim already made");
7981

80-
claims[_messageHash] = Claim({
82+
tickets[_ticketID].claim = Claim({
83+
messageHash: _messageHash,
8184
bridger: msg.sender,
8285
claimedAt: block.timestamp,
8386
claimDeposit: msg.value,
84-
relayed: false
87+
verified: false
8588
});
8689

87-
emit ClaimReceived(_messageHash, block.timestamp);
90+
emit ClaimReceived(_ticketID, _messageHash, block.timestamp);
8891
}
8992

90-
function challenge(bytes32 _messageHash) external payable override {
91-
Claim memory claim = claims[_messageHash];
92-
require(claim.bridger != address(0), "Claim does not exist");
93-
require(block.timestamp - claim.claimedAt < challengeDuration, "Challenge period over");
93+
function challenge(uint256 _ticketID) external payable override {
94+
Ticket memory ticket = tickets[_ticketID];
95+
require(ticket.claim.bridger != address(0), "Claim does not exist");
96+
require(block.timestamp - ticket.claim.claimedAt < challengeDuration, "Challenge period over");
9497
require(msg.value >= challengeDeposit, "Not enough challenge deposit");
95-
require(challenges[_messageHash].challenger == address(0), "Claim already challenged");
98+
require(ticket.challenge.challenger == address(0), "Claim already challenged");
9699

97-
challenges[_messageHash] = Challenge({
100+
ticket.challenge = Challenge({
98101
challenger: msg.sender,
99102
challengedAt: block.timestamp,
100-
challengeDeposit: msg.value,
101-
relayed: false
103+
challengeDeposit: msg.value
102104
});
103105

104-
emit ClaimChallenged(_messageHash, block.timestamp);
106+
emit ClaimChallenged(_ticketID, ticket.claim.messageHash, block.timestamp);
105107
}
106108

107-
function verifyAndRelay(bytes32 _messageHash, bytes memory _encodedData) external override {
108-
require(keccak256(_encodedData) == _messageHash, "Invalid hash");
109-
110-
Claim storage claim = claims[_messageHash];
111-
require(claim.bridger != address(0), "Claim does not exist");
112-
require(claim.claimedAt + challengeDuration < block.timestamp, "Challenge period not over");
113-
require(claim.relayed == false, "Message already relayed");
114-
require(challenges[_messageHash].challenger == address(0), "Claim is challenged");
115-
116-
// Decode the receiver address from the data encoded by the IFastBridgeSender
117-
(address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes));
118-
(bool success, ) = address(receiver).call(data);
119-
require(success, "Failed to call contract");
120-
121-
claim.relayed = true;
109+
function verifyAndRelay(
110+
uint256 _ticketID,
111+
bytes32 _messageHash,
112+
bytes memory _messageData
113+
) external override {
114+
require(_verify(_messageHash, _ticketID, _messageData), "Invalid hash");
115+
116+
Ticket memory ticket = tickets[_ticketID];
117+
require(ticket.claim.bridger != address(0), "Claim does not exist");
118+
require(ticket.claim.claimedAt + challengeDuration < block.timestamp, "Challenge period not over");
119+
require(ticket.challenge.challenger == address(0), "Claim is challenged");
120+
require(ticket.relayed == false, "Message already relayed");
121+
122+
ticket.relayed = true;
123+
require(_relay(_messageData), "Failed to call contract"); // Checks-Effects-Interaction
122124
}
123125

124-
function verifyAndRelaySafe(bytes32 _messageHash, bytes memory _encodedData) external override {
126+
function verifyAndRelaySafe(
127+
uint256 _ticketID,
128+
bytes32 _messageHash,
129+
bytes memory _messageData
130+
) external override {
125131
require(isSentBySafeBridge(), "Access not allowed: SafeBridgeSender only.");
132+
require(_verify(_messageHash, _ticketID, _messageData), "Invalid hash");
126133

127-
Challenge storage challenge = challenges[_messageHash];
128-
Claim storage claim = claims[_messageHash];
129-
require(claim.relayed != true, "Claim already relayed");
130-
require(challenge.relayed != true, "Challenge already relayed");
134+
Ticket memory ticket = tickets[_ticketID];
135+
require(ticket.relayed == false, "Message already relayed");
131136

132-
// Decode the receiver address from the data encoded by the SafeBridgeSenderToEthereum
133-
(address receiver, bytes memory data) = abi.decode(_encodedData, (address, bytes));
134-
(bool success, ) = address(receiver).call(data);
135-
require(success, "Failed to call contract");
137+
// Claim assessment if any
138+
if (ticket.claim.bridger != address(0) && ticket.claim.messageHash == _messageHash) {
139+
ticket.claim.verified = true;
140+
}
136141

137-
challenge.relayed == true;
142+
ticket.relayed = true;
143+
require(_relay(_messageData), "Failed to call contract"); // Checks-Effects-Interaction
138144
}
139145

140-
function withdrawClaimDeposit(bytes32 _messageHash) external override {
141-
Claim storage claim = claims[_messageHash];
142-
require(claim.bridger != address(0), "Claim does not exist");
143-
require(claim.relayed == true, "Claim not relayed yet");
144-
145-
uint256 amount = claim.claimDeposit;
146-
claim.claimDeposit = 0;
147-
payable(claim.bridger).send(amount);
146+
function withdrawClaimDeposit(uint256 _ticketID) external override {
147+
Ticket memory ticket = tickets[_ticketID];
148+
require(ticket.relayed == true, "Message not relayed yet");
149+
require(ticket.claim.bridger != address(0), "Claim does not exist");
150+
require(ticket.claim.verified == true, "Claim not verified: deposit forfeited");
151+
152+
uint256 amount = ticket.claim.claimDeposit + ticket.challenge.challengeDeposit;
153+
ticket.claim.claimDeposit = 0;
154+
ticket.challenge.challengeDeposit = 0;
155+
payable(ticket.claim.bridger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH.
156+
// Checks-Effects-Interaction
148157
}
149158

150-
function withdrawChallengeDeposit(bytes32 _messageHash) external override {
151-
Challenge storage challenge = challenges[_messageHash];
152-
require(challenge.challenger != address(0), "Challenge does not exist");
153-
require(challenge.relayed == true || block.timestamp > challenge.challengedAt + safeBridgeTimeout, "Challenge not relayed or timed out");
154-
155-
uint256 amount = challenge.challengeDeposit + claims[_messageHash].claimDeposit;
156-
challenge.challengeDeposit = 0;
157-
payable(challenge.challenger).send(amount);
159+
function withdrawChallengeDeposit(uint256 _ticketID) external override {
160+
Ticket memory ticket = tickets[_ticketID];
161+
require(ticket.relayed == true, "Message not relayed");
162+
require(ticket.challenge.challenger != address(0), "Challenge does not exist");
163+
require(ticket.claim.verified == false, "Claim verified: deposit forfeited");
164+
165+
uint256 amount = ticket.claim.claimDeposit + ticket.challenge.challengeDeposit;
166+
ticket.claim.claimDeposit = 0;
167+
ticket.challenge.challengeDeposit = 0;
168+
payable(ticket.challenge.challenger).send(amount); // Use of send to prevent reverting fallback. User is responsibility for accepting ETH.
169+
// Checks-Effects-Interaction
158170
}
159171

160172
// ************************************* //
161173
// * Public Views * //
162174
// ************************************* //
163175

164-
function challengePeriod(bytes32 _messageHash) public view returns (uint256 start, uint256 end) {
165-
Claim storage claim = claims[_messageHash];
166-
require(claim.bridger != address(0), "Claim does not exist");
176+
function challengePeriod(uint256 _ticketID) public view returns (uint256 start, uint256 end) {
177+
Ticket memory ticket = tickets[_ticketID];
178+
require(ticket.claim.bridger != address(0), "Claim does not exist");
167179

168-
start = claim.claimedAt;
180+
start = ticket.claim.claimedAt;
169181
end = start + challengeDuration;
170182
return (start, end);
171183
}
@@ -174,19 +186,33 @@ contract FastBridgeReceiverOnEthereum is SafeBridgeReceiverOnEthereum, IFastBrid
174186
// * Governance * //
175187
// ************************ //
176188

177-
function setClaimDeposit(uint256 _claimDeposit) external onlyByGovernor {
189+
function changeClaimDeposit(uint256 _claimDeposit) external onlyByGovernor {
178190
claimDeposit = _claimDeposit;
179191
}
180192

181-
function setChallengeDeposit(uint256 _challengeDeposit) external onlyByGovernor {
193+
function changeChallengeDeposit(uint256 _challengeDeposit) external onlyByGovernor {
182194
challengeDeposit = _challengeDeposit;
183195
}
184196

185-
function setChallengePeriodDuration(uint256 _challengeDuration) external onlyByGovernor {
197+
function changeChallengePeriodDuration(uint256 _challengeDuration) external onlyByGovernor {
186198
challengeDuration = _challengeDuration;
187199
}
188200

189-
function setSafeBridgeTimeout(uint256 _safeBridgeTimeout) external onlyByGovernor {
190-
safeBridgeTimeout = _safeBridgeTimeout;
201+
// ************************ //
202+
// * Internal * //
203+
// ************************ //
204+
205+
function _verify(
206+
bytes32 _expectedHash,
207+
uint256 _ticketID,
208+
bytes memory _messageData
209+
) internal pure returns (bool) {
210+
return _expectedHash == keccak256(abi.encode(_ticketID, _messageData));
211+
}
212+
213+
function _relay(bytes memory _messageData) internal returns (bool success) {
214+
// Decode the receiver address from the data encoded by the IFastBridgeSender
215+
(address receiver, bytes memory data) = abi.decode(_messageData, (address, bytes));
216+
(success, ) = address(receiver).call(data);
191217
}
192218
}

contracts/src/bridge/FastBridgeSenderToEthereum.sol

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe
2626
address public governor;
2727
IFastBridgeReceiver public fastBridgeReceiver;
2828
address public fastSender;
29+
uint256 public currentTicketID = 1; // Zero means not set, start at 1.
2930

3031
// ************************************* //
3132
// * Events * //
@@ -35,7 +36,7 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe
3536
* The bridgers need to watch for these events and
3637
* relay the messageHash on the FastBridgeReceiverOnEthereum.
3738
*/
38-
event OutgoingMessage(address indexed target, bytes32 indexed messageHash, bytes message);
39+
event OutgoingMessage(uint256 indexed ticketID, address indexed target, bytes32 indexed messageHash, bytes message);
3940

4041
// ************************************* //
4142
// * Function Modifiers * //
@@ -61,14 +62,14 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe
6162
*
6263
* @param _receiver The L1 contract address who will receive the calldata
6364
* @param _calldata The receiving domain encoded message data.
65+
* @return ticketID The identifier to provide to sendSafeFallback()
6466
*/
65-
function sendFast(address _receiver, bytes memory _calldata) external override {
67+
function sendFast(address _receiver, bytes memory _calldata) external override returns (uint256 ticketID) {
6668
require(msg.sender == fastSender, "Access not allowed: Fast Sender only.");
6769

68-
// Encode the receiver address with the function signature + arguments i.e calldata
69-
bytes memory messageData = abi.encode(_receiver, _calldata);
70-
71-
emit OutgoingMessage(_receiver, keccak256(messageData), messageData);
70+
ticketID = currentTicketID++;
71+
(bytes32 messageHash, bytes memory messageData) = _encode(ticketID, _receiver, _calldata);
72+
emit OutgoingMessage(ticketID, _receiver, messageHash, messageData);
7273
}
7374

7475
/**
@@ -80,15 +81,20 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe
8081
* It may require some ETH (or whichever native token) to pay for the bridging cost,
8182
* depending on the underlying safe bridge.
8283
*
84+
* @param _ticketID The ticketID as provided by `sendFast()` if any.
8385
* @param _receiver The L1 contract address who will receive the calldata
8486
* @param _calldata The receiving domain encoded message data.
8587
*/
86-
function sendSafeFallback(address _receiver, bytes memory _calldata) external payable override {
87-
bytes memory messageData = abi.encode(_receiver, _calldata);
88+
function sendSafeFallback(
89+
uint256 _ticketID,
90+
address _receiver,
91+
bytes memory _calldata
92+
) external payable override {
93+
(bytes32 messageHash, bytes memory messageData) = _encode(_ticketID, _receiver, _calldata);
8894

8995
// Safe Bridge message envelope
9096
bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector;
91-
bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, keccak256(messageData), messageData);
97+
bytes memory safeMessageData = abi.encodeWithSelector(methodSelector, _ticketID, messageHash, messageData);
9298

9399
// TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed
94100
_sendSafe(address(fastBridgeReceiver), safeMessageData);
@@ -98,8 +104,24 @@ contract FastBridgeSenderToEthereum is SafeBridgeSenderToEthereum, IFastBridgeSe
98104
// * Governance * //
99105
// ************************ //
100106

101-
function setFastSender(address _fastSender) external onlyByGovernor {
107+
function changeFastSender(address _fastSender) external onlyByGovernor {
102108
require(fastSender == address(0));
103109
fastSender = _fastSender;
104110
}
111+
112+
// ************************ //
113+
// * Internal * //
114+
// ************************ //
115+
116+
function _encode(
117+
uint256 _ticketID,
118+
address _receiver,
119+
bytes memory _calldata
120+
) internal pure returns (bytes32 messageHash, bytes memory messageData) {
121+
// Encode the receiver address with the function signature + arguments i.e calldata
122+
messageData = abi.encode(_receiver, _calldata);
123+
124+
// Compute the hash over the message header (ticketID) and body (data).
125+
messageHash = keccak256(abi.encode(_ticketID, messageData));
126+
}
105127
}

0 commit comments

Comments
 (0)