-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathInbox.sol
497 lines (453 loc) · 16.8 KB
/
Inbox.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {IMailbox, IPostDispatchHook} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
import {Eco7683DestinationSettler} from "./Eco7683DestinationSettler.sol";
import {TypeCasts} from "@hyperlane-xyz/core/contracts/libs/TypeCasts.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IInbox} from "./interfaces/IInbox.sol";
import {Intent, Route, Call, TokenAmount} from "./types/Intent.sol";
import {Semver} from "./libs/Semver.sol";
/**
* @title Inbox
* @notice Main entry point for fulfilling intents
* @dev Validates intent hash authenticity and executes calldata. Enables provers
* to claim rewards on the source chain by checking the fulfilled mapping
*/
contract Inbox is IInbox, Eco7683DestinationSettler, Ownable, Semver {
using TypeCasts for address;
using SafeERC20 for IERC20;
// Mapping of intent hash on the src chain to its fulfillment
mapping(bytes32 => ClaimantAndBatcherReward) public fulfilled;
// Mapping of solvers to if they are whitelisted
mapping(address => bool) public solverWhitelist;
// address of local hyperlane mailbox
address public mailbox;
// Is solving public
bool public isSolvingPublic;
// minimum reward to be included in a fulfillHyperBatched tx, to be paid out to the sender of the batch
uint96 public minBatcherReward;
/**
* @notice Initializes the Inbox contract
* @param _owner Address with access to privileged functions
* @param _isSolvingPublic Whether solving is public at start
* @param _solvers Initial whitelist of solvers (only relevant if solving is not public)
*/
constructor(
address _owner,
bool _isSolvingPublic,
uint96 _minBatcherReward,
address[] memory _solvers
) Ownable(_owner) {
isSolvingPublic = _isSolvingPublic;
minBatcherReward = _minBatcherReward;
for (uint256 i = 0; i < _solvers.length; ++i) {
solverWhitelist[_solvers[i]] = true;
emit SolverWhitelistChanged(_solvers[i], true);
}
}
/**
* @notice Fulfills an intent to be proven via storage proofs
* @param _route The route of the intent
* @param _rewardHash The hash of the reward
* @param _claimant The address that will receive the reward on the source chain
* @param _expectedHash The hash of the intent as created on the source chain
* @return Array of execution results from each call
*/
function fulfillStorage(
Route memory _route,
bytes32 _rewardHash,
address _claimant,
bytes32 _expectedHash
)
public
payable
override(IInbox, Eco7683DestinationSettler)
returns (bytes[] memory)
{
(bytes[] memory result, ) = _fulfill(
_route,
_rewardHash,
_claimant,
_expectedHash
);
fulfilled[_expectedHash] = ClaimantAndBatcherReward(
_claimant,
uint96(0)
);
emit ToBeProven(_expectedHash, _route.source, _claimant);
return result;
}
/**
* @notice Fulfills an intent to be proven immediately via Hyperlane's mailbox
* @dev More expensive but faster than hyperbatched. Requires fee for Hyperlane infrastructure
* @param _route The route of the intent
* @param _rewardHash The hash of the reward
* @param _claimant The address that will receive the reward on the source chain
* @param _expectedHash The hash of the intent as created on the source chain
* @param _prover The address of the hyperprover on the source chain
* @return Array of execution results from each call
*/
function fulfillHyperInstant(
Route memory _route,
bytes32 _rewardHash,
address _claimant,
bytes32 _expectedHash,
address _prover
) external payable returns (bytes[] memory) {
return
fulfillHyperInstantWithRelayer(
_route,
_rewardHash,
_claimant,
_expectedHash,
_prover,
bytes(""),
address(0)
);
}
/**
* @notice Fulfills an intent to be proven immediately via Hyperlane's mailbox with relayer support
* @dev More expensive but faster than hyperbatched. Requires fee for Hyperlane infrastructure
* @param _route The route of the intent
* @param _rewardHash The hash of the reward
* @param _claimant The address that will receive the reward on the source chain
* @param _expectedHash The hash of the intent as created on the source chain
* @param _prover The address of the hyperprover on the source chain
* @param _metadata Metadata for postDispatchHook (empty bytes if not applicable)
* @param _postDispatchHook Address of postDispatchHook (zero address if not applicable)
* @return Array of execution results from each call
*/
function fulfillHyperInstantWithRelayer(
Route memory _route,
bytes32 _rewardHash,
address _claimant,
bytes32 _expectedHash,
address _prover,
bytes memory _metadata,
address _postDispatchHook
)
public
payable
override(IInbox, Eco7683DestinationSettler)
returns (bytes[] memory)
{
bytes32[] memory hashes = new bytes32[](1);
address[] memory claimants = new address[](1);
hashes[0] = _expectedHash;
claimants[0] = _claimant;
bytes memory messageBody = abi.encode(hashes, claimants);
bytes32 _prover32 = _prover.addressToBytes32();
emit HyperInstantFulfillment(_expectedHash, _route.source, _claimant);
uint256 fee = fetchFee(
_route.source,
_prover32,
messageBody,
_metadata,
_postDispatchHook
);
(bytes[] memory results, uint256 currentBalance) = _fulfill(
_route,
_rewardHash,
_claimant,
_expectedHash
);
fulfilled[_expectedHash] = ClaimantAndBatcherReward(
_claimant,
uint96(0)
);
if (currentBalance < fee) {
revert InsufficientFee(fee);
}
if (currentBalance > fee) {
(bool success, ) = payable(msg.sender).call{
value: currentBalance - fee
}("");
if (!success) {
revert NativeTransferFailed();
}
}
if (_postDispatchHook == address(0)) {
IMailbox(mailbox).dispatch{value: fee}(
uint32(_route.source),
_prover32,
messageBody
);
} else {
IMailbox(mailbox).dispatch{value: fee}(
uint32(_route.source),
_prover32,
messageBody,
_metadata,
IPostDispatchHook(_postDispatchHook)
);
}
return results;
}
/**
* @notice Fulfills an intent to be proven in a batch via Hyperlane's mailbox
* @dev Less expensive but slower than hyperinstant. Batch dispatched when sendBatch is called.
* @param _route The route of the intent
* @param _rewardHash The hash of the reward
* @param _claimant The address that will receive the reward on the source chain
* @param _expectedHash The hash of the intent as created on the source chain
* @param _prover The address of the hyperprover on the source chain
* @return Array of execution results from each call
*/
function fulfillHyperBatched(
Route calldata _route,
bytes32 _rewardHash,
address _claimant,
bytes32 _expectedHash,
address _prover
) external payable returns (bytes[] memory) {
emit AddToBatch(_expectedHash, _route.source, _claimant, _prover);
(bytes[] memory results, uint256 remainingValue) = _fulfill(
_route,
_rewardHash,
_claimant,
_expectedHash
);
require(
remainingValue >= minBatcherReward,
InsufficientBatcherReward(minBatcherReward)
);
fulfilled[_expectedHash] = ClaimantAndBatcherReward(
_claimant,
uint96(remainingValue)
);
return results;
}
/**
* @notice Sends a batch of fulfilled intents to the mailbox
* @dev Intent hashes must correspond to fulfilled intents from specified source chain
* @param _sourceChainID Chain ID of the source chain
* @param _prover Address of the hyperprover on the source chain
* @param _intentHashes Hashes of the intents to be proven
*/
function sendBatch(
uint256 _sourceChainID,
address _prover,
bytes32[] calldata _intentHashes
) external payable {
sendBatchWithRelayer(
_sourceChainID,
_prover,
_intentHashes,
bytes(""),
address(0)
);
}
/**
* @notice Sends a batch of fulfilled intents to the mailbox with relayer support
* @dev Intent hashes must correspond to fulfilled intents from specified source chain
* @param _sourceChainID Chain ID of the source chain
* @param _prover Address of the hyperprover on the source chain
* @param _intentHashes Hashes of the intents to be proven
* @param _metadata Metadata for postDispatchHook
* @param _postDispatchHook Address of postDispatchHook
*/
function sendBatchWithRelayer(
uint256 _sourceChainID,
address _prover,
bytes32[] calldata _intentHashes,
bytes memory _metadata,
address _postDispatchHook
) public payable {
uint256 size = _intentHashes.length;
address[] memory claimants = new address[](size);
uint256 reward = 0;
for (uint256 i = 0; i < size; ++i) {
address claimant = fulfilled[_intentHashes[i]].claimant;
reward += fulfilled[_intentHashes[i]].reward;
if (claimant == address(0)) {
revert IntentNotFulfilled(_intentHashes[i]);
}
claimants[i] = claimant;
}
emit BatchSent(_intentHashes, _sourceChainID);
bytes memory messageBody = abi.encode(_intentHashes, claimants);
bytes32 _prover32 = _prover.addressToBytes32();
uint256 fee = fetchFee(
_sourceChainID,
_prover32,
messageBody,
_metadata,
_postDispatchHook
);
if (msg.value < fee) {
revert InsufficientFee(fee);
}
(bool success, ) = payable(msg.sender).call{
value: msg.value + reward - fee
}("");
if (!success) {
revert NativeTransferFailed();
}
if (_postDispatchHook == address(0)) {
IMailbox(mailbox).dispatch{value: fee}(
uint32(_sourceChainID),
_prover32,
messageBody
);
} else {
IMailbox(mailbox).dispatch{value: fee}(
uint32(_sourceChainID),
_prover32,
messageBody,
_metadata,
IPostDispatchHook(_postDispatchHook)
);
}
}
/**
* @notice Quotes the fee required for message dispatch
* @dev Used to determine fees for fulfillHyperInstant or sendBatch
* @param _sourceChainID Chain ID of the source chain
* @param _prover Address of the hyperprover on the source chain
* @param _messageBody Message being sent over the bridge
* @param _metadata Metadata for postDispatchHook
* @param _postDispatchHook Address of postDispatchHook
* @return fee The required fee amount
*/
function fetchFee(
uint256 _sourceChainID,
bytes32 _prover,
bytes memory _messageBody,
bytes memory _metadata,
address _postDispatchHook
) public view returns (uint256 fee) {
return (
_postDispatchHook == address(0)
? IMailbox(mailbox).quoteDispatch(
uint32(_sourceChainID),
_prover,
_messageBody
)
: IMailbox(mailbox).quoteDispatch(
uint32(_sourceChainID),
_prover,
_messageBody,
_metadata,
IPostDispatchHook(_postDispatchHook)
)
);
}
/**
* @notice Sets the mailbox address
* @dev Can only be called when mailbox is not set
* @param _mailbox Address of the Hyperlane mailbox
*/
function setMailbox(address _mailbox) public onlyOwner {
if (mailbox == address(0)) {
mailbox = _mailbox;
emit MailboxSet(_mailbox);
}
}
/**
* @notice Makes solving public if currently restricted
* @dev Cannot be reversed once made public
*/
function makeSolvingPublic() public onlyOwner {
if (!isSolvingPublic) {
isSolvingPublic = true;
emit SolvingIsPublic();
}
}
/**
* @notice Changes minimum reward for batcher
* @param _minBatcherReward New minimum reward
*/
function setMinBatcherReward(uint96 _minBatcherReward) public onlyOwner {
minBatcherReward = _minBatcherReward;
emit MinBatcherRewardSet(_minBatcherReward);
}
/**
* @notice Updates the solver whitelist
* @dev Whitelist is ignored if solving is public
* @param _solver Address of the solver
* @param _canSolve Whether solver should be whitelisted
*/
function changeSolverWhitelist(
address _solver,
bool _canSolve
) public onlyOwner {
solverWhitelist[_solver] = _canSolve;
emit SolverWhitelistChanged(_solver, _canSolve);
}
/**
* @notice Internal function to fulfill intents
* @dev Validates intent and executes calls
* @param _route The route of the intent
* @param _rewardHash The hash of the reward
* @param _claimant The reward recipient address
* @param _expectedHash The expected intent hash
* @return Array of execution results
*/
function _fulfill(
Route memory _route,
bytes32 _rewardHash,
address _claimant,
bytes32 _expectedHash
) internal returns (bytes[] memory, uint256) {
if (_route.destination != block.chainid) {
revert WrongChain(_route.destination);
}
if (!isSolvingPublic && !solverWhitelist[msg.sender]) {
revert UnauthorizedSolveAttempt(msg.sender);
}
bytes32 routeHash = keccak256(abi.encode(_route));
bytes32 intentHash = keccak256(
abi.encodePacked(routeHash, _rewardHash)
);
require(_route.inbox == address(this), InvalidInbox(_route.inbox));
require(intentHash == _expectedHash, InvalidHash(_expectedHash));
require(
fulfilled[intentHash].claimant == address(0),
IntentAlreadyFulfilled(intentHash)
);
require(_claimant != address(0), ZeroClaimant());
emit Fulfillment(_expectedHash, _route.source, _claimant);
uint256 routeTokenCount = _route.tokens.length;
// Transfer ERC20 tokens to the inbox
for (uint256 i = 0; i < routeTokenCount; ++i) {
TokenAmount memory approval = _route.tokens[i];
IERC20(approval.token).safeTransferFrom(
msg.sender,
address(this),
approval.amount
);
}
// Store the results of the calls
bytes[] memory results = new bytes[](_route.calls.length);
// Remaining value after executing calls
uint256 remainingValue = msg.value;
for (uint256 i = 0; i < _route.calls.length; ++i) {
Call memory call = _route.calls[i];
if (call.target.code.length == 0 && call.data.length > 0) {
// no code at this address
revert CallToEOA(call.target);
}
if (call.target == mailbox) {
// no executing calls on the mailbox
revert CallToMailbox();
}
(bool success, bytes memory result) = call.target.call{
value: call.value
}(call.data);
if (!success) {
revert IntentCallFailed(
call.target,
call.data,
call.value,
result
);
}
remainingValue -= call.value;
results[i] = result;
}
return (results, remainingValue);
}
receive() external payable {}
}