@evvm/testnet-contracts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +166 -0
  2. package/README.md +216 -0
  3. package/package.json +51 -0
  4. package/src/contracts/evvm/Evvm.sol +1327 -0
  5. package/src/contracts/evvm/EvvmLegacy.sol +1553 -0
  6. package/src/contracts/evvm/lib/ErrorsLib.sol +17 -0
  7. package/src/contracts/evvm/lib/EvvmStorage.sol +60 -0
  8. package/src/contracts/evvm/lib/EvvmStructs.sol +64 -0
  9. package/src/contracts/evvm/lib/SignatureUtils.sol +124 -0
  10. package/src/contracts/nameService/NameService.sol +1751 -0
  11. package/src/contracts/nameService/lib/ErrorsLib.sol +27 -0
  12. package/src/contracts/nameService/lib/SignatureUtils.sol +239 -0
  13. package/src/contracts/staking/Estimator.sol +358 -0
  14. package/src/contracts/staking/Staking.sol +1148 -0
  15. package/src/contracts/staking/lib/ErrorsLib.sol +19 -0
  16. package/src/contracts/staking/lib/SignatureUtils.sol +68 -0
  17. package/src/contracts/treasury/Treasury.sol +104 -0
  18. package/src/contracts/treasury/lib/ErrorsLib.sol +11 -0
  19. package/src/contracts/treasuryTwoChains/TreasuryExternalChainStation.sol +551 -0
  20. package/src/contracts/treasuryTwoChains/TreasuryHostChainStation.sol +512 -0
  21. package/src/contracts/treasuryTwoChains/lib/ErrorsLib.sol +15 -0
  22. package/src/contracts/treasuryTwoChains/lib/ExternalChainStationStructs.sol +41 -0
  23. package/src/contracts/treasuryTwoChains/lib/HostChainStationStructs.sol +52 -0
  24. package/src/contracts/treasuryTwoChains/lib/SignatureUtils.sol +47 -0
  25. package/src/interfaces/IEstimator.sol +102 -0
  26. package/src/interfaces/IEvvm.sol +195 -0
  27. package/src/interfaces/INameService.sol +283 -0
  28. package/src/interfaces/IStaking.sol +202 -0
  29. package/src/interfaces/ITreasury.sol +17 -0
  30. package/src/interfaces/ITreasuryExternalChainStation.sol +262 -0
  31. package/src/interfaces/ITreasuryHostChainStation.sol +251 -0
  32. package/src/lib/AdvancedStrings.sol +77 -0
  33. package/src/lib/Erc191TestBuilder.sol +402 -0
  34. package/src/lib/SignatureRecover.sol +56 -0
@@ -0,0 +1,551 @@
1
+ // SPDX-License-Identifier: EVVM-NONCOMMERCIAL-1.0
2
+ // Full license terms available at: https://www.evvm.info/docs/EVVMNoncommercialLicense
3
+
4
+ pragma solidity ^0.8.0;
5
+
6
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
+ import {ErrorsLib} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/ErrorsLib.sol";
8
+ import {ExternalChainStationStructs} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/ExternalChainStationStructs.sol";
9
+
10
+ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
11
+
12
+ import {SignatureUtils} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/SignatureUtils.sol";
13
+
14
+ import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
15
+
16
+ import {OApp, Origin, MessagingFee} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";
17
+ import {OAppOptionsType3} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
18
+ import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
19
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
20
+
21
+ import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
22
+ import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
23
+ import {IInterchainGasEstimation} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IInterchainGasEstimation.sol";
24
+ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
25
+
26
+ contract TreasuryExternalChainStation is
27
+ ExternalChainStationStructs,
28
+ OApp,
29
+ OAppOptionsType3,
30
+ AxelarExecutable
31
+ {
32
+ AddressTypeProposal admin;
33
+
34
+ AddressTypeProposal fisherExecutor;
35
+
36
+ HyperlaneConfig hyperlane;
37
+
38
+ LayerZeroConfig layerZero;
39
+
40
+ AxelarConfig axelar;
41
+
42
+ uint256 immutable EVVM_ID;
43
+
44
+ mapping(address => uint256) nextFisherExecutionNonce;
45
+
46
+ bytes _options =
47
+ OptionsBuilder.addExecutorLzReceiveOption(
48
+ OptionsBuilder.newOptions(),
49
+ 50000,
50
+ 0
51
+ );
52
+
53
+ event FisherBridgeSend(
54
+ address indexed from,
55
+ address indexed addressToReceive,
56
+ address indexed tokenAddress,
57
+ uint256 priorityFee,
58
+ uint256 amount,
59
+ uint256 nonce
60
+ );
61
+
62
+ modifier onlyAdmin() {
63
+ if (msg.sender != admin.current) {
64
+ revert();
65
+ }
66
+ _;
67
+ }
68
+
69
+ modifier onlyFisherExecutor() {
70
+ if (msg.sender != fisherExecutor.current) {
71
+ revert();
72
+ }
73
+ _;
74
+ }
75
+
76
+ constructor(
77
+ address _admin,
78
+ CrosschainConfig memory _crosschainConfig,
79
+ uint256 _evvmId
80
+ )
81
+ OApp(_crosschainConfig.endpointAddress, _admin)
82
+ Ownable(_admin)
83
+ AxelarExecutable(_crosschainConfig.gatewayAddress)
84
+ {
85
+ admin = AddressTypeProposal({
86
+ current: _admin,
87
+ proposal: address(0),
88
+ timeToAccept: 0
89
+ });
90
+ hyperlane = HyperlaneConfig({
91
+ hostChainStationDomainId: _crosschainConfig
92
+ .hostChainStationDomainId,
93
+ hostChainStationAddress: "",
94
+ mailboxAddress: _crosschainConfig.mailboxAddress
95
+ });
96
+ layerZero = LayerZeroConfig({
97
+ hostChainStationEid: _crosschainConfig.hostChainStationEid,
98
+ hostChainStationAddress: "",
99
+ endpointAddress: _crosschainConfig.endpointAddress
100
+ });
101
+ axelar = AxelarConfig({
102
+ hostChainStationChainName: _crosschainConfig
103
+ .hostChainStationChainName,
104
+ hostChainStationAddress: "",
105
+ gasServiceAddress: _crosschainConfig.gasServiceAddress,
106
+ gatewayAddress: _crosschainConfig.gatewayAddress
107
+ });
108
+ EVVM_ID = _evvmId;
109
+ }
110
+
111
+ function setHostChainAddress(
112
+ address hostChainStationAddress,
113
+ string memory hostChainStationAddressString
114
+ ) external onlyAdmin {
115
+ hyperlane.hostChainStationAddress = bytes32(
116
+ uint256(uint160(hostChainStationAddress))
117
+ );
118
+ layerZero.hostChainStationAddress = bytes32(
119
+ uint256(uint160(hostChainStationAddress))
120
+ );
121
+ axelar.hostChainStationAddress = hostChainStationAddressString;
122
+ _setPeer(
123
+ layerZero.hostChainStationEid,
124
+ layerZero.hostChainStationAddress
125
+ );
126
+ }
127
+
128
+ /**
129
+ * @notice Withdraw ETH or ERC20 tokens
130
+ * @param token Token address (address(0) for ETH)
131
+ * @param amount Amount to withdraw
132
+ */
133
+ function depositERC20(
134
+ address toAddress,
135
+ address token,
136
+ uint256 amount,
137
+ bytes1 protocolToExecute
138
+ ) external payable {
139
+ bytes memory payload = encodePayload(token, toAddress, amount);
140
+ verifyAndDepositERC20(token, amount);
141
+ if (protocolToExecute == 0x01) {
142
+ // 0x01 = Hyperlane
143
+ uint256 quote = getQuoteHyperlane(toAddress, token, amount);
144
+ /*messageId = */ IMailbox(hyperlane.mailboxAddress).dispatch{
145
+ value: quote
146
+ }(
147
+ hyperlane.hostChainStationDomainId,
148
+ hyperlane.hostChainStationAddress,
149
+ payload
150
+ );
151
+ } else if (protocolToExecute == 0x02) {
152
+ // 0x02 = LayerZero
153
+ uint256 fee = quoteLayerZero(toAddress, token, amount);
154
+ _lzSend(
155
+ layerZero.hostChainStationEid,
156
+ payload,
157
+ _options,
158
+ MessagingFee(fee, 0),
159
+ msg.sender // Refund any excess fees to the sender.
160
+ );
161
+ } else if (protocolToExecute == 0x03) {
162
+ // 0x03 = Axelar
163
+ IAxelarGasService(axelar.gasServiceAddress)
164
+ .payNativeGasForContractCall{value: msg.value}(
165
+ address(this),
166
+ axelar.hostChainStationChainName,
167
+ axelar.hostChainStationAddress,
168
+ payload,
169
+ msg.sender
170
+ );
171
+ gateway().callContract(
172
+ axelar.hostChainStationChainName,
173
+ axelar.hostChainStationAddress,
174
+ payload
175
+ );
176
+ } else {
177
+ revert();
178
+ }
179
+ }
180
+
181
+ function depositCoin(
182
+ address toAddress,
183
+ uint256 amount,
184
+ bytes1 protocolToExecute
185
+ ) external payable {
186
+ if (msg.value < amount) revert ErrorsLib.InsufficientBalance();
187
+
188
+ bytes memory payload = encodePayload(address(0), toAddress, amount);
189
+
190
+ if (protocolToExecute == 0x01) {
191
+ // 0x01 = Hyperlane
192
+ uint256 quote = getQuoteHyperlane(toAddress, address(0), amount);
193
+ if (msg.value < quote + amount)
194
+ revert ErrorsLib.InsufficientBalance();
195
+ /*messageId = */ IMailbox(hyperlane.mailboxAddress).dispatch{
196
+ value: quote
197
+ }(
198
+ hyperlane.hostChainStationDomainId,
199
+ hyperlane.hostChainStationAddress,
200
+ payload
201
+ );
202
+ } else if (protocolToExecute == 0x02) {
203
+ // 0x02 = LayerZero
204
+ uint256 fee = quoteLayerZero(toAddress, address(0), amount);
205
+ if (msg.value < fee + amount)
206
+ revert ErrorsLib.InsufficientBalance();
207
+ _lzSend(
208
+ layerZero.hostChainStationEid,
209
+ payload,
210
+ _options,
211
+ MessagingFee(fee, 0),
212
+ msg.sender // Refund any excess fees to the sender.
213
+ );
214
+ } else if (protocolToExecute == 0x03) {
215
+ // 0x03 = Axelar
216
+ IAxelarGasService(axelar.gasServiceAddress)
217
+ .payNativeGasForContractCall{value: msg.value - amount}(
218
+ address(this),
219
+ axelar.hostChainStationChainName,
220
+ axelar.hostChainStationAddress,
221
+ payload,
222
+ msg.sender
223
+ );
224
+ gateway().callContract(
225
+ axelar.hostChainStationChainName,
226
+ axelar.hostChainStationAddress,
227
+ payload
228
+ );
229
+ } else {
230
+ revert();
231
+ }
232
+ }
233
+
234
+ function fisherBridgeReceive(
235
+ address from,
236
+ address addressToReceive,
237
+ address tokenAddress,
238
+ uint256 priorityFee,
239
+ uint256 amount,
240
+ bytes memory signature
241
+ ) external onlyFisherExecutor {
242
+ if (
243
+ !SignatureUtils.verifyMessageSignedForFisherBridge(
244
+ EVVM_ID,
245
+ from,
246
+ addressToReceive,
247
+ nextFisherExecutionNonce[from],
248
+ tokenAddress,
249
+ priorityFee,
250
+ amount,
251
+ signature
252
+ )
253
+ ) revert ErrorsLib.InvalidSignature();
254
+
255
+ nextFisherExecutionNonce[from]++;
256
+ }
257
+
258
+ function fisherBridgeSendERC20(
259
+ address from,
260
+ address addressToReceive,
261
+ address tokenAddress,
262
+ uint256 priorityFee,
263
+ uint256 amount,
264
+ bytes memory signature
265
+ ) external onlyFisherExecutor {
266
+ if (
267
+ !SignatureUtils.verifyMessageSignedForFisherBridge(
268
+ EVVM_ID,
269
+ from,
270
+ addressToReceive,
271
+ nextFisherExecutionNonce[from],
272
+ tokenAddress,
273
+ priorityFee,
274
+ amount,
275
+ signature
276
+ )
277
+ ) revert ErrorsLib.InvalidSignature();
278
+
279
+ verifyAndDepositERC20(tokenAddress, amount);
280
+
281
+ nextFisherExecutionNonce[from]++;
282
+
283
+ emit FisherBridgeSend(
284
+ from,
285
+ addressToReceive,
286
+ tokenAddress,
287
+ priorityFee,
288
+ amount,
289
+ nextFisherExecutionNonce[from] - 1
290
+ );
291
+ }
292
+
293
+ function fisherBridgeSendCoin(
294
+ address from,
295
+ address addressToReceive,
296
+ uint256 priorityFee,
297
+ uint256 amount,
298
+ bytes memory signature
299
+ ) external payable onlyFisherExecutor {
300
+ if (
301
+ !SignatureUtils.verifyMessageSignedForFisherBridge(
302
+ EVVM_ID,
303
+ from,
304
+ addressToReceive,
305
+ nextFisherExecutionNonce[from],
306
+ address(0),
307
+ priorityFee,
308
+ amount,
309
+ signature
310
+ )
311
+ ) revert ErrorsLib.InvalidSignature();
312
+
313
+ if (msg.value != amount + priorityFee)
314
+ revert ErrorsLib.InsufficientBalance();
315
+
316
+ nextFisherExecutionNonce[from]++;
317
+
318
+ emit FisherBridgeSend(
319
+ from,
320
+ addressToReceive,
321
+ address(0),
322
+ priorityFee,
323
+ amount,
324
+ nextFisherExecutionNonce[from] - 1
325
+ );
326
+ }
327
+
328
+ // Hyperlane Specific Functions //
329
+ function getQuoteHyperlane(
330
+ address toAddress,
331
+ address token,
332
+ uint256 amount
333
+ ) public view returns (uint256) {
334
+ return
335
+ IMailbox(hyperlane.mailboxAddress).quoteDispatch(
336
+ hyperlane.hostChainStationDomainId,
337
+ hyperlane.hostChainStationAddress,
338
+ encodePayload(token, toAddress, amount)
339
+ );
340
+ }
341
+
342
+ function handle(
343
+ uint32 _origin,
344
+ bytes32 _sender,
345
+ bytes calldata _data
346
+ ) external payable virtual {
347
+ if (msg.sender != hyperlane.mailboxAddress)
348
+ revert ErrorsLib.MailboxNotAuthorized();
349
+
350
+ if (_sender != hyperlane.hostChainStationAddress)
351
+ revert ErrorsLib.SenderNotAuthorized();
352
+
353
+ if (_origin != hyperlane.hostChainStationDomainId)
354
+ revert ErrorsLib.ChainIdNotAuthorized();
355
+
356
+ decodeAndGive(_data);
357
+ }
358
+
359
+ // LayerZero Specific Functions //
360
+
361
+ function quoteLayerZero(
362
+ address toAddress,
363
+ address token,
364
+ uint256 amount
365
+ ) public view returns (uint256) {
366
+ MessagingFee memory fee = _quote(
367
+ layerZero.hostChainStationEid,
368
+ encodePayload(token, toAddress, amount),
369
+ _options,
370
+ false
371
+ );
372
+ return fee.nativeFee;
373
+ }
374
+
375
+ function _lzReceive(
376
+ Origin calldata _origin,
377
+ bytes32 /*_guid*/,
378
+ bytes calldata message,
379
+ address /*executor*/, // Executor address as specified by the OApp.
380
+ bytes calldata /*_extraData*/ // Any extra data or options to trigger on receipt.
381
+ ) internal override {
382
+ // Decode the payload to get the message
383
+ if (_origin.srcEid != layerZero.hostChainStationEid)
384
+ revert ErrorsLib.ChainIdNotAuthorized();
385
+
386
+ if (_origin.sender != layerZero.hostChainStationAddress)
387
+ revert ErrorsLib.SenderNotAuthorized();
388
+
389
+ decodeAndGive(message);
390
+ }
391
+
392
+ // Axelar Specific Functions //
393
+
394
+ function _execute(
395
+ bytes32 /*commandId*/,
396
+ string calldata _sourceChain,
397
+ string calldata _sourceAddress,
398
+ bytes calldata _payload
399
+ ) internal override {
400
+ if (!Strings.equal(_sourceChain, axelar.hostChainStationChainName))
401
+ revert ErrorsLib.ChainIdNotAuthorized();
402
+
403
+ if (!Strings.equal(_sourceAddress, axelar.hostChainStationAddress))
404
+ revert ErrorsLib.SenderNotAuthorized();
405
+
406
+ decodeAndGive(_payload);
407
+ }
408
+
409
+ /**
410
+ * @notice Proposes a new admin address with 1-day time delay
411
+ * @dev Part of the time-delayed governance system for admin changes
412
+ * @param _newOwner Address of the proposed new admin
413
+ */
414
+ function proposeAdmin(address _newOwner) external onlyAdmin {
415
+ if (_newOwner == address(0) || _newOwner == admin.current) revert();
416
+
417
+ admin.proposal = _newOwner;
418
+ admin.timeToAccept = block.timestamp + 1 days;
419
+ }
420
+
421
+ /**
422
+ * @notice Cancels a pending admin change proposal
423
+ * @dev Allows current admin to reject proposed admin changes
424
+ */
425
+ function rejectProposalAdmin() external onlyAdmin {
426
+ admin.proposal = address(0);
427
+ admin.timeToAccept = 0;
428
+ }
429
+
430
+ /**
431
+ * @notice Accepts a pending admin proposal and becomes the new admin
432
+ * @dev Can only be called by the proposed admin after the time delay
433
+ */
434
+ function acceptAdmin() external {
435
+ if (block.timestamp < admin.timeToAccept) revert();
436
+
437
+ if (msg.sender != admin.proposal) revert();
438
+
439
+ admin.current = admin.proposal;
440
+
441
+ admin.proposal = address(0);
442
+ admin.timeToAccept = 0;
443
+ }
444
+
445
+ function proposeFisherExecutor(
446
+ address _newFisherExecutor
447
+ ) external onlyAdmin {
448
+ if (
449
+ _newFisherExecutor == address(0) ||
450
+ _newFisherExecutor == fisherExecutor.current
451
+ ) revert();
452
+
453
+ fisherExecutor.proposal = _newFisherExecutor;
454
+ fisherExecutor.timeToAccept = block.timestamp + 1 days;
455
+ }
456
+
457
+ function rejectProposalFisherExecutor() external onlyAdmin {
458
+ fisherExecutor.proposal = address(0);
459
+ fisherExecutor.timeToAccept = 0;
460
+ }
461
+
462
+ function acceptFisherExecutor() external {
463
+ if (block.timestamp < fisherExecutor.timeToAccept) revert();
464
+
465
+ if (msg.sender != fisherExecutor.proposal) revert();
466
+
467
+ fisherExecutor.current = fisherExecutor.proposal;
468
+
469
+ fisherExecutor.proposal = address(0);
470
+ fisherExecutor.timeToAccept = 0;
471
+ }
472
+
473
+ // Getter functions //
474
+ function getAdmin() external view returns (AddressTypeProposal memory) {
475
+ return admin;
476
+ }
477
+
478
+ function getFisherExecutor()
479
+ external
480
+ view
481
+ returns (AddressTypeProposal memory)
482
+ {
483
+ return fisherExecutor;
484
+ }
485
+
486
+ function getNextFisherExecutionNonce(
487
+ address user
488
+ ) external view returns (uint256) {
489
+ return nextFisherExecutionNonce[user];
490
+ }
491
+
492
+ function getHyperlaneConfig()
493
+ external
494
+ view
495
+ returns (HyperlaneConfig memory)
496
+ {
497
+ return hyperlane;
498
+ }
499
+
500
+ function getLayerZeroConfig()
501
+ external
502
+ view
503
+ returns (LayerZeroConfig memory)
504
+ {
505
+ return layerZero;
506
+ }
507
+
508
+ function getAxelarConfig() external view returns (AxelarConfig memory) {
509
+ return axelar;
510
+ }
511
+
512
+ function getOptions() external view returns (bytes memory) {
513
+ return _options;
514
+ }
515
+
516
+ // Internal Functions //
517
+
518
+ function decodeAndGive(bytes memory payload) internal {
519
+ (address token, address toAddress, uint256 amount) = decodePayload(
520
+ payload
521
+ );
522
+ if (token == address(0))
523
+ SafeTransferLib.safeTransferETH(msg.sender, amount);
524
+ else IERC20(token).transfer(toAddress, amount);
525
+ }
526
+
527
+ function verifyAndDepositERC20(address token, uint256 amount) internal {
528
+ if (token == address(0)) revert();
529
+ if (IERC20(token).allowance(msg.sender, address(this)) < amount)
530
+ revert ErrorsLib.InsufficientBalance();
531
+
532
+ IERC20(token).transferFrom(msg.sender, address(this), amount);
533
+ }
534
+
535
+ function encodePayload(
536
+ address token,
537
+ address toAddress,
538
+ uint256 amount
539
+ ) internal pure returns (bytes memory payload) {
540
+ payload = abi.encode(token, toAddress, amount);
541
+ }
542
+
543
+ function decodePayload(
544
+ bytes memory payload
545
+ ) internal pure returns (address token, address toAddress, uint256 amount) {
546
+ (token, toAddress, amount) = abi.decode(
547
+ payload,
548
+ (address, address, uint256)
549
+ );
550
+ }
551
+ }