-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathFuelMessagePortal.sol
338 lines (280 loc) · 12.7 KB
/
FuelMessagePortal.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {verifyBinaryTree} from "../lib/VerifyBinaryTree/VerifyBinaryTree.sol";
import {FuelChainState} from "./FuelChainState.sol";
import {FuelBlockHeader, FuelBlockHeaderLib} from "./types/FuelBlockHeader.sol";
import {FuelBlockHeaderLite, FuelBlockHeaderLiteLib} from "./types/FuelBlockHeaderLite.sol";
import {CryptographyLib} from "../lib/Cryptography.sol";
import {CommonPredicates} from "../lib/CommonPredicates.sol";
import {ReentrancyGuardTransientUpgradable} from "../security/ReentrancyGuardTransientUpgradable.sol";
/// @notice Structure for proving an element in a merkle tree
struct MerkleProof {
uint256 key;
bytes32[] proof;
}
/// @notice Structure containing all message details
struct Message {
bytes32 sender;
bytes32 recipient;
bytes32 nonce;
uint64 amount;
bytes data;
}
/// @title FuelMessagePortal
/// @notice The Fuel Message Portal contract sends messages to and from Fuel
/// @custom:deprecation THIS CONTRACT IS DEPRECATED. CHECK FuelMessagePortalV3
contract FuelMessagePortal is
Initializable,
PausableUpgradeable,
AccessControlUpgradeable,
ReentrancyGuardTransientUpgradable,
UUPSUpgradeable
{
using FuelBlockHeaderLib for FuelBlockHeader;
using FuelBlockHeaderLiteLib for FuelBlockHeaderLite;
////////////
// Events //
////////////
/// @dev Emitted when a message is sent from Ethereum to Fuel
event MessageSent(
bytes32 indexed sender,
bytes32 indexed recipient,
uint256 indexed nonce,
uint64 amount,
bytes data
);
/// @dev Emitted when a message is successfully relayed to Ethereum from Fuel
event MessageRelayed(bytes32 indexed messageId, bytes32 indexed sender, bytes32 indexed recipient, uint64 amount);
////////////
// Errors //
////////////
error UnfinalizedBlock();
error InvalidBlockInHistoryProof();
error InvalidMessageInBlockProof();
error CurrentMessageSenderNotSet();
error MessageDataTooLarge();
error AmountPrecisionIncompatibility();
error AmountTooBig();
error AlreadyRelayed();
///////////////
// Constants //
///////////////
/// @dev The admin related contract roles
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
/// @dev The number of decimals that the base Fuel asset uses
uint256 public constant FUEL_BASE_ASSET_DECIMALS = 9;
uint256 public constant ETH_DECIMALS = 18;
uint256 public constant PRECISION = 10 ** (ETH_DECIMALS - FUEL_BASE_ASSET_DECIMALS);
/// @dev The max message data size in bytes
uint256 public constant MAX_MESSAGE_DATA_SIZE = 2 ** 16;
/// @dev Non-zero null value to optimize gas costs
bytes32 internal constant NULL_MESSAGE_SENDER = 0x000000000000000000000000000000000000000000000000000000000000dead;
/////////////
// Storage //
/////////////
/// @notice Current message sender for other contracts to reference
bytes32 internal _incomingMessageSender;
/// @notice The Fuel chain state contract
FuelChainState internal _fuelChainState;
/// @notice Nonce for the next message to be sent
uint256 internal _outgoingMessageNonce;
/// @notice Mapping of message hash to boolean success value
mapping(bytes32 => bool) internal _incomingMessageSuccessful;
/////////////////////////////
// Constructor/Initializer //
/////////////////////////////
/// @notice Constructor disables initialization for the implementation contract
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Contract initializer to setup starting values
/// @param fuelChainState Chain state contract
function initialize(FuelChainState fuelChainState) public virtual initializer {
initializerV1(fuelChainState);
}
function initializerV1(FuelChainState fuelChainState) internal virtual onlyInitializing {
__Pausable_init();
__AccessControl_init();
__ReentrancyGuardTransient_init();
__UUPSUpgradeable_init();
//grant initial roles
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
//chain state contract
_fuelChainState = fuelChainState;
//outgoing message data
_outgoingMessageNonce = 0;
//incoming message data
_incomingMessageSender = NULL_MESSAGE_SENDER;
}
/////////////////////
// Admin Functions //
/////////////////////
/// @notice Pause outbound messages
function pause() external virtual onlyRole(PAUSER_ROLE) {
_pause();
}
/// @notice Unpause outbound messages
function unpause() external virtual onlyRole(DEFAULT_ADMIN_ROLE) {
_unpause();
}
//////////////////////
// Public Functions //
//////////////////////
/// @notice Gets the number of decimals used in the Fuel base asset
/// @return decimals of the Fuel base asset
function fuelBaseAssetDecimals() public pure virtual returns (uint8) {
return uint8(FUEL_BASE_ASSET_DECIMALS);
}
/// @notice Gets the set Fuel chain state contract
/// @return fuel chain state contract
function fuelChainStateContract() public view virtual returns (address) {
return address(_fuelChainState);
}
function getNextOutgoingMessageNonce() public view virtual returns (uint256) {
return _outgoingMessageNonce;
}
///////////////////////////////////////
// Incoming Message Public Functions //
///////////////////////////////////////
/// @notice Relays a message published on Fuel from a given block
/// @param message The message to relay
/// @param rootBlockHeader The root block for proving chain history
/// @param blockHeader The block containing the message
/// @param blockInHistoryProof Proof that the message block exists in the history of the root block
/// @param messageInBlockProof Proof that message exists in block
/// @dev Made payable to reduce gas costs
function relayMessage(
Message calldata message,
FuelBlockHeaderLite calldata rootBlockHeader,
FuelBlockHeader calldata blockHeader,
MerkleProof calldata blockInHistoryProof,
MerkleProof calldata messageInBlockProof
) external payable virtual whenNotPaused {
//verify root block header
if (!_fuelChainState.finalized(rootBlockHeader.computeConsensusHeaderHash(), rootBlockHeader.height)) {
revert UnfinalizedBlock();
}
//verify block in history
if (
!verifyBinaryTree(
rootBlockHeader.prevRoot,
abi.encodePacked(blockHeader.computeConsensusHeaderHash()),
blockInHistoryProof.proof,
blockInHistoryProof.key,
rootBlockHeader.height
)
) revert InvalidBlockInHistoryProof();
//verify message in block
bytes32 messageId = CryptographyLib.hash(
abi.encodePacked(message.sender, message.recipient, message.nonce, message.amount, message.data)
);
if (
!verifyBinaryTree(
blockHeader.outputMessagesRoot,
abi.encodePacked(messageId),
messageInBlockProof.proof,
messageInBlockProof.key,
blockHeader.outputMessagesCount
)
) revert InvalidMessageInBlockProof();
//execute message
_executeMessage(messageId, message);
}
/// @notice Gets if the given message ID has been relayed successfully
/// @param messageId Message ID
/// @return true if message has been relayed successfully
function incomingMessageSuccessful(bytes32 messageId) public view virtual returns (bool) {
return _incomingMessageSuccessful[messageId];
}
/// @notice Used by message receiving contracts to get the address on Fuel that sent the message
/// @return sender the address of the sender on Fuel
function messageSender() external view virtual returns (bytes32) {
if (_incomingMessageSender == NULL_MESSAGE_SENDER) revert CurrentMessageSenderNotSet();
return _incomingMessageSender;
}
///////////////////////////////////////
// Outgoing Message Public Functions //
///////////////////////////////////////
/// @notice Send a message to a recipient on Fuel
/// @param recipient The target message receiver address or predicate root
/// @param data The message data to be sent to the receiver
function sendMessage(bytes32 recipient, bytes calldata data) external payable virtual whenNotPaused {
_sendOutgoingMessage(recipient, data);
}
/// @notice Send only ETH to the given recipient
/// @param recipient The target message receiver
function depositETH(bytes32 recipient) external payable virtual whenNotPaused {
_sendOutgoingMessage(recipient, new bytes(0));
}
////////////////////////
// Internal Functions //
////////////////////////
/// @notice Performs all necessary logic to send a message to a target on Fuel
/// @param recipient The message receiver address or predicate root
/// @param data The message data to be sent to the receiver
function _sendOutgoingMessage(bytes32 recipient, bytes memory data) internal virtual {
bytes32 sender = bytes32(uint256(uint160(msg.sender)));
unchecked {
//make sure data size is not too large
if (data.length >= MAX_MESSAGE_DATA_SIZE) revert MessageDataTooLarge();
//make sure amount fits into the Fuel base asset decimal level
uint256 precision = 10 ** (ETH_DECIMALS - FUEL_BASE_ASSET_DECIMALS);
uint256 amount = msg.value / precision;
if (msg.value > 0) {
if (amount * PRECISION != msg.value) revert AmountPrecisionIncompatibility();
if (amount > type(uint64).max) revert AmountTooBig();
}
//emit message for Fuel clients to pickup (messageID calculated offchain)
uint256 nonce = _outgoingMessageNonce;
emit MessageSent(sender, recipient, nonce, uint64(amount), data);
// increment nonce for next message
_outgoingMessageNonce = nonce + 1;
}
}
/// @notice Executes a message in the given header
/// @param messageId The id of message to execute
/// @param message The message to execute
function _executeMessage(bytes32 messageId, Message calldata message) internal virtual nonReentrant {
if (_incomingMessageSuccessful[messageId]) revert AlreadyRelayed();
//set message sender for receiving contract to reference
_incomingMessageSender = message.sender;
(bool success, bytes memory result) = address(uint160(uint256(message.recipient))).call{
value: message.amount * (10 ** (ETH_DECIMALS - FUEL_BASE_ASSET_DECIMALS))
}(message.data);
if (!success) {
// Look for revert reason and bubble it up if present
if (result.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(result)
revert(add(32, result), returndata_size)
}
}
revert("Message relay failed");
}
//unset message sender reference
_incomingMessageSender = NULL_MESSAGE_SENDER;
//keep track of successfully relayed messages
_incomingMessageSuccessful[messageId] = true;
//emit event for successful message relay
emit MessageRelayed(messageId, message.sender, message.recipient, message.amount);
}
/// @notice Executes a message in the given header
// solhint-disable-next-line no-empty-blocks
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {
//should revert if msg.sender is not authorized to upgrade the contract (currently only admin)
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}