-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
L2ToL2CrossDomainMessenger.sol
250 lines (205 loc) · 11.8 KB
/
L2ToL2CrossDomainMessenger.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Libraries
import { Encoding } from "src/libraries/Encoding.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { TransientReentrancyAware } from "src/libraries/TransientContext.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IDependencySet } from "interfaces/L2/IDependencySet.sol";
import { ICrossL2Inbox, Identifier } from "interfaces/L2/ICrossL2Inbox.sol";
/// @notice Thrown when a non-written slot in transient storage is attempted to be read from.
error NotEntered();
/// @notice Thrown when attempting to relay a message where payload origin is not L2ToL2CrossDomainMessenger.
error IdOriginNotL2ToL2CrossDomainMessenger();
/// @notice Thrown when the payload provided to the relay is not a SentMessage event.
error EventPayloadNotSentMessage();
/// @notice Thrown when attempting to send a message to the chain that the message is being sent from.
error MessageDestinationSameChain();
/// @notice Thrown when attempting to relay a message whose destination chain is not the chain relaying it.
error MessageDestinationNotRelayChain();
/// @notice Thrown when attempting to relay a message whose target is CrossL2Inbox.
error MessageTargetCrossL2Inbox();
/// @notice Thrown when attempting to relay a message whose target is L2ToL2CrossDomainMessenger.
error MessageTargetL2ToL2CrossDomainMessenger();
/// @notice Thrown when attempting to relay a message that has already been relayed.
error MessageAlreadyRelayed();
/// @notice Thrown when a reentrant call is detected.
error ReentrantCall();
/// @notice Thrown when a call to the target contract during message relay fails.
error TargetCallFailed();
/// @notice Thrown when attempting to use a chain ID that is not in the dependency set.
error InvalidChainId();
/// @custom:proxied true
/// @custom:predeploy 0x4200000000000000000000000000000000000023
/// @title L2ToL2CrossDomainMessenger
/// @notice The L2ToL2CrossDomainMessenger is a higher level abstraction on top of the CrossL2Inbox that provides
/// features necessary for secure transfers ERC20 tokens between L2 chains. Messages sent through the
/// L2ToL2CrossDomainMessenger on the source chain receive both replay protection as well as domain binding.
contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware {
/// @notice Storage slot for the sender of the current cross domain message.
/// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.sender")) - 1)
bytes32 internal constant CROSS_DOMAIN_MESSAGE_SENDER_SLOT =
0xb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f3;
/// @notice Storage slot for the source of the current cross domain message.
/// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.source")) - 1)
bytes32 internal constant CROSS_DOMAIN_MESSAGE_SOURCE_SLOT =
0x711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee7;
/// @notice Event selector for the SentMessage event. Will be removed in favor of reading
// the `selector` property directly once crytic/slithe/#2566 is fixed.
bytes32 internal constant SENT_MESSAGE_EVENT_SELECTOR =
0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320;
/// @notice Current message version identifier.
uint16 public constant messageVersion = uint16(0);
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.14
string public constant version = "1.0.0-beta.14";
/// @notice Mapping of message hashes to boolean receipt values. Note that a message will only be present in this
/// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again.
mapping(bytes32 => bool) public successfulMessages;
/// @notice Nonce for the next message to be sent, without the message version applied. Use the messageNonce getter,
/// which will insert the message version into the nonce to give you the actual nonce to be used for the
/// message.
uint240 internal msgNonce;
/// @notice Emitted whenever a message is sent to a destination
/// @param destination Chain ID of the destination chain.
/// @param target Target contract or wallet address.
/// @param messageNonce Nonce associated with the messsage sent
/// @param sender Address initiating this message call
/// @param message Message payload to call target with.
event SentMessage(
uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message
);
/// @notice Emitted whenever a message is successfully relayed on this chain.
/// @param source Chain ID of the source chain.
/// @param messageNonce Nonce associated with the messsage sent
/// @param messageHash Hash of the message that was relayed.
event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash);
/// @notice Retrieves the sender of the current cross domain message. If not entered, reverts.
/// @return sender_ Address of the sender of the current cross domain message.
function crossDomainMessageSender() external view onlyEntered returns (address sender_) {
assembly {
sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT)
}
}
/// @notice Retrieves the source of the current cross domain message. If not entered, reverts.
/// @return source_ Chain ID of the source of the current cross domain message.
function crossDomainMessageSource() external view onlyEntered returns (uint256 source_) {
assembly {
source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT)
}
}
/// @notice Retrieves the context of the current cross domain message. If not entered, reverts.
/// @return sender_ Address of the sender of the current cross domain message.
/// @return source_ Chain ID of the source of the current cross domain message.
function crossDomainMessageContext() external view onlyEntered returns (address sender_, uint256 source_) {
assembly {
sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT)
source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT)
}
}
/// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts,
/// then the message will be unrelayable and any ETH sent will be permanently locked. The same will occur
/// if the target on the other chain is considered unsafe (see the _isUnsafeTarget() function).
/// @param _destination Chain ID of the destination chain.
/// @param _target Target contract or wallet address.
/// @param _message Message payload to call target with.
/// @return The hash of the message being sent, used to track whether the message has successfully been relayed.
function sendMessage(uint256 _destination, address _target, bytes calldata _message) external returns (bytes32) {
if (_destination == block.chainid) revert MessageDestinationSameChain();
if (_target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox();
if (_target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger();
if (!IDependencySet(Predeploys.L1_BLOCK_ATTRIBUTES).isInDependencySet(_destination)) revert InvalidChainId();
uint256 nonce = messageNonce();
emit SentMessage(_destination, _target, nonce, msg.sender, _message);
msgNonce++;
return Hashing.hashL2toL2CrossDomainMessage({
_destination: _destination,
_source: block.chainid,
_nonce: nonce,
_sender: msg.sender,
_target: _target,
_message: _message
});
}
/// @notice Relays a message that was sent by the other L2ToL2CrossDomainMessenger contract. Can only be executed
/// via cross chain call from the other messenger OR if the message was already received once and is
/// currently being replayed.
/// @param _id Identifier of the SentMessage event to be relayed
/// @param _sentMessage Message payload of the `SentMessage` event
/// @return returnData_ Return data from the target contract call.
function relayMessage(
Identifier calldata _id,
bytes calldata _sentMessage
)
external
payable
nonReentrant
returns (bytes memory returnData_)
{
// Ensure the log came from the messenger. Since the log origin is the CDM, there isn't a scenario where
// this can be invoked from the CrossL2Inbox as the SentMessage log is not calldata for this function
if (_id.origin != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert IdOriginNotL2ToL2CrossDomainMessenger();
}
// Signal that this is a cross chain call that needs to have the identifier validated
ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage));
// Decode the payload
(uint256 destination, address target, uint256 nonce, address sender, bytes memory message) =
_decodeSentMessagePayload(_sentMessage);
// Assert invariants on the message
if (destination != block.chainid) revert MessageDestinationNotRelayChain();
if (target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox();
if (target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger();
uint256 source = _id.chainId;
bytes32 messageHash = Hashing.hashL2toL2CrossDomainMessage({
_destination: destination,
_source: source,
_nonce: nonce,
_sender: sender,
_target: target,
_message: message
});
if (successfulMessages[messageHash]) {
revert MessageAlreadyRelayed();
}
_storeMessageMetadata(source, sender);
bool success;
(success, returnData_) = target.call{ value: msg.value }(message);
if (!success) {
revert TargetCallFailed();
}
successfulMessages[messageHash] = true;
emit RelayedMessage(source, nonce, messageHash);
_storeMessageMetadata(0, address(0));
}
/// @notice Retrieves the next message nonce. Message version will be added to the upper two bytes of the message
/// nonce. Message version allows us to treat messages as having different structures.
/// @return Nonce of the next message to be sent, with added message version.
function messageNonce() public view returns (uint256) {
return Encoding.encodeVersionedNonce(msgNonce, messageVersion);
}
/// @notice Stores message data such as sender and source in transient storage.
/// @param _source Chain ID of the source chain.
/// @param _sender Address of the sender of the message.
function _storeMessageMetadata(uint256 _source, address _sender) internal {
assembly {
tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender)
tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source)
}
}
function _decodeSentMessagePayload(bytes calldata _payload)
internal
pure
returns (uint256 destination_, address target_, uint256 nonce_, address sender_, bytes memory message_)
{
// Validate Selector (also reverts if LOG0 with no topics)
bytes32 selector = abi.decode(_payload[:32], (bytes32));
if (selector != SENT_MESSAGE_EVENT_SELECTOR) revert EventPayloadNotSentMessage();
// Topics
(destination_, target_, nonce_) = abi.decode(_payload[32:128], (uint256, address, uint256));
// Data
(sender_, message_) = abi.decode(_payload[128:], (address, bytes));
}
}