-
Notifications
You must be signed in to change notification settings - Fork 28
/
BridgeRouter.sol
354 lines (325 loc) · 13.2 KB
/
BridgeRouter.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
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.7.6;
// ============ Internal Imports ============
import {BridgeMessage} from "./BridgeMessage.sol";
import {IBridgeToken} from "./interfaces/IBridgeToken.sol";
import {ITokenRegistry} from "./interfaces/ITokenRegistry.sol";
// ============ External Imports ============
import {XAppConnectionClient} from "@nomad-xyz/contracts-router/contracts/XAppConnectionClient.sol";
import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol";
import {Home} from "@nomad-xyz/contracts-core/contracts/Home.sol";
import {Version0} from "@nomad-xyz/contracts-core/contracts/Version0.sol";
import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
/**
* @title BridgeRouter
*/
contract BridgeRouter is Version0, Router {
// ============ Libraries ============
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
using SafeERC20 for IERC20;
// ============ Constants ============
// the amount transferred to bridgoors without gas funds
uint256 public constant DUST_AMOUNT = 0.06 ether;
// ============ Public Storage ============
// contract that manages registry representation tokens
ITokenRegistry public tokenRegistry;
// token transfer prefill ID => LP that pre-filled message to provide fast liquidity
mapping(bytes32 => address) public liquidityProvider;
// ============ Upgrade Gap ============
// gap for upgrade safety
uint256[49] private __GAP;
// ======== Events =========
/**
* @notice emitted when tokens are sent from this domain to another domain
* @param token the address of the token contract
* @param from the address sending tokens
* @param toDomain the domain of the chain the tokens are being sent to
* @param toId the bytes32 address of the recipient of the tokens
* @param amount the amount of tokens sent
* @param fastLiquidityEnabled True if fast liquidity is enabled, False otherwise
*/
event Send(
address indexed token,
address indexed from,
uint32 indexed toDomain,
bytes32 toId,
uint256 amount,
bool fastLiquidityEnabled
);
/**
* @notice emitted when tokens are dispensed to an account on this domain
* emitted both when fast liquidity is provided, and when the transfer ultimately settles
* @param originAndNonce Domain where the transfer originated and the unique identifier
* for the message from origin to destination, combined in a single field ((origin << 32) & nonce)
* @param token The address of the local token contract being received
* @param recipient The address receiving the tokens; the original recipient of the transfer
* @param liquidityProvider The account providing liquidity
* @param amount The amount of tokens being received
*/
event Receive(
uint64 indexed originAndNonce,
address indexed token,
address indexed recipient,
address liquidityProvider,
uint256 amount
);
// ======== Receive =======
receive() external payable {}
// ======== Initializer ========
function initialize(address _tokenRegistry, address _xAppConnectionManager)
public
initializer
{
tokenRegistry = ITokenRegistry(_tokenRegistry);
__XAppConnectionClient_initialize(_xAppConnectionManager);
}
// ======== External: Handle =========
/**
* @notice Handles an incoming message
* @param _origin The origin domain
* @param _nonce The unique identifier for the message from origin to destination
* @param _sender The sender address
* @param _message The message
*/
function handle(
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
bytes memory _message
) external override onlyReplica onlyRemoteRouter(_origin, _sender) {
// parse tokenId and action from message
bytes29 _msg = _message.ref(0).mustBeMessage();
bytes29 _tokenId = _msg.tokenId();
bytes29 _action = _msg.action();
// handle message based on the intended action
if (_action.isTransfer()) {
_handleTransfer(_origin, _nonce, _tokenId, _action, false);
} else if (_action.isFastTransfer()) {
_handleTransfer(_origin, _nonce, _tokenId, _action, true);
} else {
require(false, "!valid action");
}
}
// ======== External: Send Token =========
/**
* @notice Send tokens to a recipient on a remote chain
* @param _token The token address
* @param _amount The token amount
* @param _destination The destination domain
* @param _recipient The recipient address
*/
function send(
address _token,
uint256 _amount,
uint32 _destination,
bytes32 _recipient,
bool /*_enableFast - deprecated field, left argument for backwards compatibility */
) external {
require(_amount > 0, "!amnt");
require(_recipient != bytes32(0), "!recip");
// get remote BridgeRouter address; revert if not found
bytes32 _remote = _mustHaveRemote(_destination);
// Setup vars used in both if branches
IBridgeToken _t = IBridgeToken(_token);
bytes32 _detailsHash;
// remove tokens from circulation on this chain
if (tokenRegistry.isLocalOrigin(_token)) {
// if the token originates on this chain,
// hold the tokens in escrow in the Router
IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount);
// query token contract for details and calculate detailsHash
_detailsHash = BridgeMessage.getDetailsHash(
_t.name(),
_t.symbol(),
_t.decimals()
);
} else {
// if the token originates on a remote chain,
// burn the representation tokens on this chain
_t.burn(msg.sender, _amount);
_detailsHash = _t.detailsHash();
}
// format Transfer Tokens action
bytes29 _action = BridgeMessage.formatTransfer(
_recipient,
_amount,
_detailsHash
);
// get the tokenID
(uint32 _domain, bytes32 _id) = tokenRegistry.getTokenId(_token);
bytes29 _tokenId = BridgeMessage.formatTokenId(_domain, _id);
// send message to remote chain via Nomad
Home(xAppConnectionManager.home()).dispatch(
_destination,
_remote,
BridgeMessage.formatMessage(_tokenId, _action)
);
// emit Send event to record token sender
emit Send(
_token,
msg.sender,
_destination,
_recipient,
_amount,
false
);
}
// ======== External: Custom Tokens =========
/**
* @notice Enroll a custom token. This allows projects to work with
* governance to specify a custom representation.
* @param _domain the domain of the canonical Token to enroll
* @param _id the bytes32 ID of the canonical of the Token to enroll
* @param _custom the address of the custom implementation to use.
*/
function enrollCustom(
uint32 _domain,
bytes32 _id,
address _custom
) external onlyOwner {
// Sanity check. Ensures that human error doesn't cause an
// unpermissioned contract to be enrolled.
IBridgeToken(_custom).mint(address(this), 1);
IBridgeToken(_custom).burn(address(this), 1);
tokenRegistry.enrollCustom(_domain, _id, _custom);
}
/**
* @notice Migrate all tokens in a previous representation to the latest
* custom representation. This works by looking up local mappings and then
* burning old tokens and minting new tokens.
* @dev This is explicitly opt-in to allow dapps to decide when and how to
* upgrade to the new representation.
* @param _oldRepr The address of the old token to migrate
*/
function migrate(address _oldRepr) external {
address _currentRepr = tokenRegistry.oldReprToCurrentRepr(_oldRepr);
require(_currentRepr != _oldRepr, "!different");
// burn the total balance of old tokens & mint the new ones
IBridgeToken _old = IBridgeToken(_oldRepr);
uint256 _bal = _old.balanceOf(msg.sender);
_old.burn(msg.sender, _bal);
IBridgeToken(_currentRepr).mint(msg.sender, _bal);
}
// ============ Internal: Handle ============
/**
* @notice Handles an incoming Transfer message.
*
* If the token is of local origin, the amount is sent from escrow.
* Otherwise, a representation token is minted.
*
* @param _origin The domain of the chain from which the transfer originated
* @param _nonce The unique identifier for the message from origin to destination
* @param _tokenId The token ID
* @param _action The action
* @param _fastEnabled True if fast liquidity was enabled, False otherwise
*/
function _handleTransfer(
uint32 _origin,
uint32 _nonce,
bytes29 _tokenId,
bytes29 _action,
bool _fastEnabled
) internal {
// get the token contract for the given tokenId on this chain;
// (if the token is of remote origin and there is
// no existing representation token contract, the TokenRegistry will
// deploy a new one)
address _token = tokenRegistry.ensureLocalToken(
_tokenId.domain(),
_tokenId.id()
);
// load the original recipient of the tokens
address _recipient = _action.evmRecipient();
if (_fastEnabled) {
// If an LP has prefilled this token transfer,
// send the tokens to the LP instead of the recipient
bytes32 _id = BridgeMessage.getPreFillId(
_origin,
_nonce,
_tokenId,
_action
);
address _lp = liquidityProvider[_id];
if (_lp != address(0)) {
_recipient = _lp;
delete liquidityProvider[_id];
}
}
// load amount once
uint256 _amount = _action.amnt();
// send the tokens into circulation on this chain
if (tokenRegistry.isLocalOrigin(_token)) {
// if the token is of local origin, the tokens have been held in
// escrow in this contract
// while they have been circulating on remote chains;
// transfer the tokens to the recipient
IERC20(_token).safeTransfer(_recipient, _amount);
} else {
// if the token is of remote origin, mint the tokens to the
// recipient on this chain
IBridgeToken(_token).mint(_recipient, _amount);
// Tell the token what its detailsHash is
IBridgeToken(_token).setDetailsHash(_action.detailsHash());
}
// dust the recipient if appropriate
_dust(_recipient);
// emit Receive event
emit Receive(
_originAndNonce(_origin, _nonce),
_token,
_recipient,
address(0),
_amount
);
}
// ============ Internal: Dust with Gas ============
/**
* @notice Dust the recipient. This feature allows chain operators to use
* the Bridge as a faucet if so desired. Any gas asset held by the
* bridge will be slowly sent to users who need initial gas bootstrapping
* @dev Does not dust if insufficient funds, or if user has funds already
*/
function _dust(address _recipient) internal {
if (
_recipient.balance < DUST_AMOUNT &&
address(this).balance >= DUST_AMOUNT
) {
// `send` gives execution 2300 gas and returns a `success` boolean.
// however, we do not care if the call fails. A failed call
// indicates a smart contract attempting to execute logic, which we
// specifically do not want.
// While we could check EXTCODESIZE, it seems sufficient to rely on
// the 2300 gas stipend to ensure that no state change logic can
// be executed.
payable(_recipient).send(DUST_AMOUNT);
}
}
// ============ Internal: Utils ============
/**
* @notice Internal utility function that combines
* `_origin` and `_nonce`.
* @dev Both origin and nonce should be less than 2^32 - 1
* @param _origin Domain of chain where the transfer originated
* @param _nonce The unique identifier for the message from origin to destination
* @return Returns (`_origin` << 32) & `_nonce`
*/
function _originAndNonce(uint32 _origin, uint32 _nonce)
internal
pure
returns (uint64)
{
return (uint64(_origin) << 32) | _nonce;
}
/**
* @dev should be impossible to renounce ownership;
* we override OpenZeppelin OwnableUpgradeable's
* implementation of renounceOwnership to make it a no-op
*/
function renounceOwnership() public override onlyOwner {
// do nothing
}
}