@evvm/testnet-contracts 2.1.0 → 2.1.2
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.
- package/contracts/p2pSwap/P2PSwap.sol +7 -10
- package/contracts/treasuryTwoChains/TreasuryExternalChainStation.sol +300 -50
- package/contracts/treasuryTwoChains/TreasuryHostChainStation.sol +286 -61
- package/contracts/treasuryTwoChains/lib/ErrorsLib.sol +31 -0
- package/contracts/treasuryTwoChains/lib/ExternalChainStationStructs.sol +50 -0
- package/contracts/treasuryTwoChains/lib/HostChainStationStructs.sol +47 -8
- package/contracts/treasuryTwoChains/lib/PayloadUtils.sol +42 -0
- package/contracts/treasuryTwoChains/lib/SignatureUtils.sol +41 -7
- package/interfaces/IP2PSwap.sol +1 -1
- package/interfaces/ITreasuryExternalChainStation.sol +34 -130
- package/interfaces/ITreasuryHostChainStation.sol +35 -124
- package/library/Erc191TestBuilder.sol +82 -0
- package/package.json +1 -1
|
@@ -559,7 +559,7 @@ contract P2PSwap is StakingServiceHooks {
|
|
|
559
559
|
* @param _nonce_Evvm Nonce for EVVM payment transaction
|
|
560
560
|
* @param _priority_Evvm Whether to use priority (async) processing
|
|
561
561
|
* @param _signature_Evvm Signature for EVVM payment authorization
|
|
562
|
-
* @param
|
|
562
|
+
* @param maxFillFixedFee Maximum output amount for fee calculation (testing parameter)
|
|
563
563
|
*/
|
|
564
564
|
function dispatchOrder_fillFixedFee(
|
|
565
565
|
address user,
|
|
@@ -568,7 +568,7 @@ contract P2PSwap is StakingServiceHooks {
|
|
|
568
568
|
uint256 _nonce_Evvm,
|
|
569
569
|
bool _priority_Evvm,
|
|
570
570
|
bytes memory _signature_Evvm,
|
|
571
|
-
uint256
|
|
571
|
+
uint256 maxFillFixedFee
|
|
572
572
|
) external {
|
|
573
573
|
if (
|
|
574
574
|
!SignatureUtils.verifyMessageSignedForDispatchOrder(
|
|
@@ -598,9 +598,8 @@ contract P2PSwap is StakingServiceHooks {
|
|
|
598
598
|
}
|
|
599
599
|
|
|
600
600
|
(uint256 fee, uint256 fee10) = calculateFillFixedFee(
|
|
601
|
-
metadata.tokenB,
|
|
602
601
|
ordersInsideMarket[market][metadata.orderId].amountB,
|
|
603
|
-
|
|
602
|
+
maxFillFixedFee
|
|
604
603
|
);
|
|
605
604
|
|
|
606
605
|
if (
|
|
@@ -704,19 +703,17 @@ contract P2PSwap is StakingServiceHooks {
|
|
|
704
703
|
/**
|
|
705
704
|
* @notice Calculates fixed trading fee with maximum limit constraints
|
|
706
705
|
* @dev Compares proportional fee with maximum output, applies 10% reduction if needed
|
|
707
|
-
* @param token Token address for fee calculation (currently unused)
|
|
708
706
|
* @param amount Order amount for proportional fee calculation
|
|
709
|
-
* @param
|
|
707
|
+
* @param maxFillFixedFee Maximum output amount for fee limiting
|
|
710
708
|
* @return fee The final calculated fee amount
|
|
711
709
|
* @return fee10 10% of the fee amount for specific calculations
|
|
712
710
|
*/
|
|
713
711
|
function calculateFillFixedFee(
|
|
714
|
-
address token,
|
|
715
712
|
uint256 amount,
|
|
716
|
-
uint256
|
|
713
|
+
uint256 maxFillFixedFee
|
|
717
714
|
) internal view returns (uint256 fee, uint256 fee10) {
|
|
718
|
-
if (calculateFillPropotionalFee(amount) >
|
|
719
|
-
fee =
|
|
715
|
+
if (calculateFillPropotionalFee(amount) > maxFillFixedFee) {
|
|
716
|
+
fee = maxFillFixedFee;
|
|
720
717
|
fee10 = (fee * 1000) / 10_000;
|
|
721
718
|
} else {
|
|
722
719
|
fee = calculateFillPropotionalFee(amount);
|
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
|
|
4
4
|
pragma solidity ^0.8.0;
|
|
5
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
|
+
* @title Treasury Cross-Chain Host Station Contract
|
|
31
|
+
* @author Mate labs
|
|
32
|
+
*/
|
|
33
|
+
|
|
6
34
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
7
35
|
import {ErrorsLib} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/ErrorsLib.sol";
|
|
8
36
|
import {ExternalChainStationStructs} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/ExternalChainStationStructs.sol";
|
|
@@ -10,9 +38,11 @@ import {ExternalChainStationStructs} from "@evvm/testnet-contracts/contracts/tre
|
|
|
10
38
|
import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
|
|
11
39
|
|
|
12
40
|
import {SignatureUtils} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/SignatureUtils.sol";
|
|
41
|
+
import {PayloadUtils} from "@evvm/testnet-contracts/contracts/treasuryTwoChains/lib/PayloadUtils.sol";
|
|
13
42
|
|
|
14
43
|
import {IMailbox} from "@hyperlane-xyz/core/contracts/interfaces/IMailbox.sol";
|
|
15
44
|
|
|
45
|
+
import {MessagingParams, MessagingReceipt} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
|
|
16
46
|
import {OApp, Origin, MessagingFee} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";
|
|
17
47
|
import {OAppOptionsType3} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
|
|
18
48
|
import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
|
|
@@ -29,27 +59,58 @@ contract TreasuryExternalChainStation is
|
|
|
29
59
|
OAppOptionsType3,
|
|
30
60
|
AxelarExecutable
|
|
31
61
|
{
|
|
62
|
+
/// @notice Admin address management with time-delayed proposals
|
|
63
|
+
/// @dev Stores current admin, proposed admin, and acceptance timestamp
|
|
32
64
|
AddressTypeProposal admin;
|
|
33
65
|
|
|
66
|
+
/// @notice Fisher executor address management with time-delayed proposals
|
|
67
|
+
/// @dev Fisher executor can process cross-chain bridge transactions
|
|
34
68
|
AddressTypeProposal fisherExecutor;
|
|
35
69
|
|
|
70
|
+
/// @notice Hyperlane protocol configuration for cross-chain messaging
|
|
71
|
+
/// @dev Contains domain ID, host chain address, and mailbox contract address
|
|
36
72
|
HyperlaneConfig hyperlane;
|
|
37
73
|
|
|
74
|
+
/// @notice LayerZero protocol configuration for omnichain messaging
|
|
75
|
+
/// @dev Contains endpoint ID, host chain address, and endpoint contract address
|
|
38
76
|
LayerZeroConfig layerZero;
|
|
39
77
|
|
|
78
|
+
/// @notice Axelar protocol configuration for cross-chain communication
|
|
79
|
+
/// @dev Contains chain name, host chain address, gas service, and gateway addresses
|
|
40
80
|
AxelarConfig axelar;
|
|
41
81
|
|
|
82
|
+
/// @notice Pending proposal for changing host chain addresses across all protocols
|
|
83
|
+
/// @dev Used for coordinated updates to host chain addresses with time delay
|
|
84
|
+
ChangeHostChainAddressParams hostChainAddressChangeProposal;
|
|
85
|
+
|
|
86
|
+
/// @notice Unique identifier for the EVVM instance this station belongs to
|
|
87
|
+
/// @dev Immutable value set at deployment for signature verification
|
|
42
88
|
uint256 immutable EVVM_ID;
|
|
43
89
|
|
|
90
|
+
/// @notice Tracks the next nonce for Fisher bridge operations per user address
|
|
91
|
+
/// @dev Prevents replay attacks in Fisher bridge transactions
|
|
44
92
|
mapping(address => uint256) nextFisherExecutionNonce;
|
|
45
93
|
|
|
46
|
-
|
|
94
|
+
/// @notice LayerZero execution options with gas limit configuration
|
|
95
|
+
/// @dev Pre-built options for LayerZero message execution (200k gas limit)
|
|
96
|
+
bytes options =
|
|
47
97
|
OptionsBuilder.addExecutorLzReceiveOption(
|
|
48
98
|
OptionsBuilder.newOptions(),
|
|
49
|
-
|
|
99
|
+
200_000,
|
|
50
100
|
0
|
|
51
101
|
);
|
|
52
102
|
|
|
103
|
+
/// @notice One-time fuse for setting initial host chain addresses
|
|
104
|
+
/// @dev Prevents multiple calls to _setHostChainAddress after initial setup
|
|
105
|
+
bytes1 fuseSetHostChainAddress = 0x01;
|
|
106
|
+
|
|
107
|
+
/// @notice Emitted when Fisher bridge sends tokens from external to host chain
|
|
108
|
+
/// @param from Original sender address on external chain
|
|
109
|
+
/// @param addressToReceive Recipient address on host chain
|
|
110
|
+
/// @param tokenAddress Token contract address (address(0) for ETH)
|
|
111
|
+
/// @param priorityFee Fee paid for priority processing
|
|
112
|
+
/// @param amount Amount of tokens transferred
|
|
113
|
+
/// @param nonce Sequential nonce for the Fisher bridge operation
|
|
53
114
|
event FisherBridgeSend(
|
|
54
115
|
address indexed from,
|
|
55
116
|
address indexed addressToReceive,
|
|
@@ -59,6 +120,8 @@ contract TreasuryExternalChainStation is
|
|
|
59
120
|
uint256 nonce
|
|
60
121
|
);
|
|
61
122
|
|
|
123
|
+
/// @notice Restricts function access to the current admin only
|
|
124
|
+
/// @dev Validates caller against admin.current address
|
|
62
125
|
modifier onlyAdmin() {
|
|
63
126
|
if (msg.sender != admin.current) {
|
|
64
127
|
revert();
|
|
@@ -66,6 +129,8 @@ contract TreasuryExternalChainStation is
|
|
|
66
129
|
_;
|
|
67
130
|
}
|
|
68
131
|
|
|
132
|
+
/// @notice Restricts function access to the current Fisher executor only
|
|
133
|
+
/// @dev Validates caller against fisherExecutor.current address for bridge operations
|
|
69
134
|
modifier onlyFisherExecutor() {
|
|
70
135
|
if (msg.sender != fisherExecutor.current) {
|
|
71
136
|
revert();
|
|
@@ -73,6 +138,11 @@ contract TreasuryExternalChainStation is
|
|
|
73
138
|
_;
|
|
74
139
|
}
|
|
75
140
|
|
|
141
|
+
/// @notice Initializes the External Chain Station with cross-chain protocol configurations
|
|
142
|
+
/// @dev Sets up Hyperlane, LayerZero, and Axelar configurations for multi-protocol support
|
|
143
|
+
/// @param _admin Initial admin address with full administrative privileges
|
|
144
|
+
/// @param _crosschainConfig Configuration struct containing all cross-chain protocol settings
|
|
145
|
+
/// @param _evvmId Unique identifier for the EVVM instance this station serves
|
|
76
146
|
constructor(
|
|
77
147
|
address _admin,
|
|
78
148
|
CrosschainConfig memory _crosschainConfig,
|
|
@@ -108,10 +178,16 @@ contract TreasuryExternalChainStation is
|
|
|
108
178
|
EVVM_ID = _evvmId;
|
|
109
179
|
}
|
|
110
180
|
|
|
111
|
-
|
|
181
|
+
/// @notice One-time setup of host chain station address across all protocols
|
|
182
|
+
/// @dev Can only be called once (protected by fuseSetHostChainAddress)
|
|
183
|
+
/// @param hostChainStationAddress Address-type representation for Hyperlane and LayerZero
|
|
184
|
+
/// @param hostChainStationAddressString String representation for Axelar protocol
|
|
185
|
+
function _setHostChainAddress(
|
|
112
186
|
address hostChainStationAddress,
|
|
113
187
|
string memory hostChainStationAddressString
|
|
114
188
|
) external onlyAdmin {
|
|
189
|
+
if (fuseSetHostChainAddress != 0x01) revert();
|
|
190
|
+
|
|
115
191
|
hyperlane.hostChainStationAddress = bytes32(
|
|
116
192
|
uint256(uint160(hostChainStationAddress))
|
|
117
193
|
);
|
|
@@ -123,20 +199,27 @@ contract TreasuryExternalChainStation is
|
|
|
123
199
|
layerZero.hostChainStationEid,
|
|
124
200
|
layerZero.hostChainStationAddress
|
|
125
201
|
);
|
|
202
|
+
|
|
203
|
+
fuseSetHostChainAddress = 0x00;
|
|
126
204
|
}
|
|
127
205
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
206
|
+
/// @notice Deposits ERC20 tokens and sends them to host chain via selected protocol
|
|
207
|
+
/// @dev Supports Hyperlane (0x01), LayerZero (0x02), and Axelar (0x03) protocols
|
|
208
|
+
/// @param toAddress Recipient address on the host chain
|
|
209
|
+
/// @param token ERC20 token contract address to deposit and transfer
|
|
210
|
+
/// @param amount Amount of tokens to deposit and send to host chain
|
|
211
|
+
/// @param protocolToExecute Protocol selector: 0x01=Hyperlane, 0x02=LayerZero, 0x03=Axelar
|
|
133
212
|
function depositERC20(
|
|
134
213
|
address toAddress,
|
|
135
214
|
address token,
|
|
136
215
|
uint256 amount,
|
|
137
216
|
bytes1 protocolToExecute
|
|
138
217
|
) external payable {
|
|
139
|
-
bytes memory payload = encodePayload(
|
|
218
|
+
bytes memory payload = PayloadUtils.encodePayload(
|
|
219
|
+
token,
|
|
220
|
+
toAddress,
|
|
221
|
+
amount
|
|
222
|
+
);
|
|
140
223
|
verifyAndDepositERC20(token, amount);
|
|
141
224
|
if (protocolToExecute == 0x01) {
|
|
142
225
|
// 0x01 = Hyperlane
|
|
@@ -150,12 +233,12 @@ contract TreasuryExternalChainStation is
|
|
|
150
233
|
);
|
|
151
234
|
} else if (protocolToExecute == 0x02) {
|
|
152
235
|
// 0x02 = LayerZero
|
|
153
|
-
uint256
|
|
236
|
+
uint256 quote = quoteLayerZero(toAddress, token, amount);
|
|
154
237
|
_lzSend(
|
|
155
238
|
layerZero.hostChainStationEid,
|
|
156
239
|
payload,
|
|
157
|
-
|
|
158
|
-
MessagingFee(
|
|
240
|
+
options,
|
|
241
|
+
MessagingFee(quote, 0),
|
|
159
242
|
msg.sender // Refund any excess fees to the sender.
|
|
160
243
|
);
|
|
161
244
|
} else if (protocolToExecute == 0x03) {
|
|
@@ -178,6 +261,11 @@ contract TreasuryExternalChainStation is
|
|
|
178
261
|
}
|
|
179
262
|
}
|
|
180
263
|
|
|
264
|
+
/// @notice Deposits native ETH and sends it to host chain via selected protocol
|
|
265
|
+
/// @dev msg.value must cover both the amount and protocol fees
|
|
266
|
+
/// @param toAddress Recipient address on the host chain
|
|
267
|
+
/// @param amount Amount of ETH to send to host chain (must be <= msg.value - fees)
|
|
268
|
+
/// @param protocolToExecute Protocol selector: 0x01=Hyperlane, 0x02=LayerZero, 0x03=Axelar
|
|
181
269
|
function depositCoin(
|
|
182
270
|
address toAddress,
|
|
183
271
|
uint256 amount,
|
|
@@ -185,7 +273,11 @@ contract TreasuryExternalChainStation is
|
|
|
185
273
|
) external payable {
|
|
186
274
|
if (msg.value < amount) revert ErrorsLib.InsufficientBalance();
|
|
187
275
|
|
|
188
|
-
bytes memory payload = encodePayload(
|
|
276
|
+
bytes memory payload = PayloadUtils.encodePayload(
|
|
277
|
+
address(0),
|
|
278
|
+
toAddress,
|
|
279
|
+
amount
|
|
280
|
+
);
|
|
189
281
|
|
|
190
282
|
if (protocolToExecute == 0x01) {
|
|
191
283
|
// 0x01 = Hyperlane
|
|
@@ -207,7 +299,7 @@ contract TreasuryExternalChainStation is
|
|
|
207
299
|
_lzSend(
|
|
208
300
|
layerZero.hostChainStationEid,
|
|
209
301
|
payload,
|
|
210
|
-
|
|
302
|
+
options,
|
|
211
303
|
MessagingFee(fee, 0),
|
|
212
304
|
msg.sender // Refund any excess fees to the sender.
|
|
213
305
|
);
|
|
@@ -231,6 +323,14 @@ contract TreasuryExternalChainStation is
|
|
|
231
323
|
}
|
|
232
324
|
}
|
|
233
325
|
|
|
326
|
+
/// @notice Receives and validates Fisher bridge transactions from host chain
|
|
327
|
+
/// @dev Verifies signature and increments nonce but doesn't transfer tokens (receive-only)
|
|
328
|
+
/// @param from Original sender address from host chain
|
|
329
|
+
/// @param addressToReceive Intended recipient address on this chain
|
|
330
|
+
/// @param tokenAddress Token contract address (address(0) for ETH)
|
|
331
|
+
/// @param priorityFee Fee amount for priority processing
|
|
332
|
+
/// @param amount Amount of tokens being received
|
|
333
|
+
/// @param signature ECDSA signature proving transaction authorization
|
|
234
334
|
function fisherBridgeReceive(
|
|
235
335
|
address from,
|
|
236
336
|
address addressToReceive,
|
|
@@ -255,6 +355,14 @@ contract TreasuryExternalChainStation is
|
|
|
255
355
|
nextFisherExecutionNonce[from]++;
|
|
256
356
|
}
|
|
257
357
|
|
|
358
|
+
/// @notice Processes Fisher bridge ERC20 token transfers to host chain
|
|
359
|
+
/// @dev Validates signature, deposits tokens, and emits tracking event
|
|
360
|
+
/// @param from Original sender address initiating the bridge transaction
|
|
361
|
+
/// @param addressToReceive Recipient address on the host chain
|
|
362
|
+
/// @param tokenAddress ERC20 token contract address to bridge
|
|
363
|
+
/// @param priorityFee Fee amount for priority processing
|
|
364
|
+
/// @param amount Amount of tokens to bridge to host chain
|
|
365
|
+
/// @param signature ECDSA signature proving transaction authorization
|
|
258
366
|
function fisherBridgeSendERC20(
|
|
259
367
|
address from,
|
|
260
368
|
address addressToReceive,
|
|
@@ -290,6 +398,13 @@ contract TreasuryExternalChainStation is
|
|
|
290
398
|
);
|
|
291
399
|
}
|
|
292
400
|
|
|
401
|
+
/// @notice Processes Fisher bridge ETH transfers to host chain
|
|
402
|
+
/// @dev Validates signature and exact payment (amount + priority fee)
|
|
403
|
+
/// @param from Original sender address initiating the bridge transaction
|
|
404
|
+
/// @param addressToReceive Recipient address on the host chain
|
|
405
|
+
/// @param priorityFee Fee amount for priority processing
|
|
406
|
+
/// @param amount Amount of ETH to bridge to host chain
|
|
407
|
+
/// @param signature ECDSA signature proving transaction authorization
|
|
293
408
|
function fisherBridgeSendCoin(
|
|
294
409
|
address from,
|
|
295
410
|
address addressToReceive,
|
|
@@ -326,6 +441,13 @@ contract TreasuryExternalChainStation is
|
|
|
326
441
|
}
|
|
327
442
|
|
|
328
443
|
// Hyperlane Specific Functions //
|
|
444
|
+
|
|
445
|
+
/// @notice Calculates the fee required for Hyperlane cross-chain message dispatch
|
|
446
|
+
/// @dev Queries the Hyperlane mailbox for accurate fee estimation
|
|
447
|
+
/// @param toAddress Recipient address on the destination chain
|
|
448
|
+
/// @param token Token contract address being transferred
|
|
449
|
+
/// @param amount Amount of tokens being transferred
|
|
450
|
+
/// @return Fee amount in native currency required for the Hyperlane message
|
|
329
451
|
function getQuoteHyperlane(
|
|
330
452
|
address toAddress,
|
|
331
453
|
address token,
|
|
@@ -335,10 +457,15 @@ contract TreasuryExternalChainStation is
|
|
|
335
457
|
IMailbox(hyperlane.mailboxAddress).quoteDispatch(
|
|
336
458
|
hyperlane.hostChainStationDomainId,
|
|
337
459
|
hyperlane.hostChainStationAddress,
|
|
338
|
-
encodePayload(token, toAddress, amount)
|
|
460
|
+
PayloadUtils.encodePayload(token, toAddress, amount)
|
|
339
461
|
);
|
|
340
462
|
}
|
|
341
463
|
|
|
464
|
+
/// @notice Handles incoming Hyperlane messages from the host chain
|
|
465
|
+
/// @dev Validates origin, sender authorization, and processes the payload
|
|
466
|
+
/// @param _origin Source chain domain ID where the message originated
|
|
467
|
+
/// @param _sender Address of the message sender (must be host chain station)
|
|
468
|
+
/// @param _data Encoded payload containing transfer instructions
|
|
342
469
|
function handle(
|
|
343
470
|
uint32 _origin,
|
|
344
471
|
bytes32 _sender,
|
|
@@ -358,6 +485,12 @@ contract TreasuryExternalChainStation is
|
|
|
358
485
|
|
|
359
486
|
// LayerZero Specific Functions //
|
|
360
487
|
|
|
488
|
+
/// @notice Calculates the fee required for LayerZero cross-chain message
|
|
489
|
+
/// @dev Queries LayerZero endpoint for accurate native fee estimation
|
|
490
|
+
/// @param toAddress Recipient address on the destination chain
|
|
491
|
+
/// @param token Token contract address being transferred
|
|
492
|
+
/// @param amount Amount of tokens being transferred
|
|
493
|
+
/// @return Native fee amount required for the LayerZero message
|
|
361
494
|
function quoteLayerZero(
|
|
362
495
|
address toAddress,
|
|
363
496
|
address token,
|
|
@@ -365,13 +498,17 @@ contract TreasuryExternalChainStation is
|
|
|
365
498
|
) public view returns (uint256) {
|
|
366
499
|
MessagingFee memory fee = _quote(
|
|
367
500
|
layerZero.hostChainStationEid,
|
|
368
|
-
encodePayload(token, toAddress, amount),
|
|
369
|
-
|
|
501
|
+
PayloadUtils.encodePayload(token, toAddress, amount),
|
|
502
|
+
options,
|
|
370
503
|
false
|
|
371
504
|
);
|
|
372
505
|
return fee.nativeFee;
|
|
373
506
|
}
|
|
374
507
|
|
|
508
|
+
/// @notice Handles incoming LayerZero messages from the host chain
|
|
509
|
+
/// @dev Validates origin chain and sender, then processes the transfer payload
|
|
510
|
+
/// @param _origin Origin information containing source endpoint ID and sender
|
|
511
|
+
/// @param message Encoded payload containing transfer instructions
|
|
375
512
|
function _lzReceive(
|
|
376
513
|
Origin calldata _origin,
|
|
377
514
|
bytes32 /*_guid*/,
|
|
@@ -389,8 +526,46 @@ contract TreasuryExternalChainStation is
|
|
|
389
526
|
decodeAndGive(message);
|
|
390
527
|
}
|
|
391
528
|
|
|
529
|
+
/// @notice Sends LayerZero messages to the destination chain
|
|
530
|
+
/// @dev Handles fee payment and message dispatch through LayerZero endpoint
|
|
531
|
+
/// @param _dstEid Destination endpoint ID (target chain)
|
|
532
|
+
/// @param _message Encoded message payload to send
|
|
533
|
+
/// @param _options Execution options for the destination chain
|
|
534
|
+
/// @param _fee Messaging fee structure (native + LZ token fees)
|
|
535
|
+
/// @param _refundAddress Address to receive excess fees
|
|
536
|
+
/// @return receipt Messaging receipt with transaction details
|
|
537
|
+
function _lzSend(
|
|
538
|
+
uint32 _dstEid,
|
|
539
|
+
bytes memory _message,
|
|
540
|
+
bytes memory _options,
|
|
541
|
+
MessagingFee memory _fee,
|
|
542
|
+
address _refundAddress
|
|
543
|
+
) internal override returns (MessagingReceipt memory receipt) {
|
|
544
|
+
// @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
|
|
545
|
+
uint256 messageValue = _fee.nativeFee;
|
|
546
|
+
if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);
|
|
547
|
+
|
|
548
|
+
return
|
|
549
|
+
// solhint-disable-next-line check-send-result
|
|
550
|
+
endpoint.send{value: messageValue}(
|
|
551
|
+
MessagingParams(
|
|
552
|
+
_dstEid,
|
|
553
|
+
_getPeerOrRevert(_dstEid),
|
|
554
|
+
_message,
|
|
555
|
+
_options,
|
|
556
|
+
_fee.lzTokenFee > 0
|
|
557
|
+
),
|
|
558
|
+
_refundAddress
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
392
562
|
// Axelar Specific Functions //
|
|
393
563
|
|
|
564
|
+
/// @notice Handles incoming Axelar messages from the host chain
|
|
565
|
+
/// @dev Validates source chain and address, then processes the transfer payload
|
|
566
|
+
/// @param _sourceChain Source blockchain name (must match configured host chain)
|
|
567
|
+
/// @param _sourceAddress Source contract address (must match host chain station)
|
|
568
|
+
/// @param _payload Encoded payload containing transfer instructions
|
|
394
569
|
function _execute(
|
|
395
570
|
bytes32 /*commandId*/,
|
|
396
571
|
string calldata _sourceChain,
|
|
@@ -406,11 +581,9 @@ contract TreasuryExternalChainStation is
|
|
|
406
581
|
decodeAndGive(_payload);
|
|
407
582
|
}
|
|
408
583
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
* @param _newOwner Address of the proposed new admin
|
|
413
|
-
*/
|
|
584
|
+
/// @notice Proposes a new admin address with 1-day time delay
|
|
585
|
+
/// @dev Part of the time-delayed governance system for admin changes
|
|
586
|
+
/// @param _newOwner Address of the proposed new admin (cannot be zero or current admin)
|
|
414
587
|
function proposeAdmin(address _newOwner) external onlyAdmin {
|
|
415
588
|
if (_newOwner == address(0) || _newOwner == admin.current) revert();
|
|
416
589
|
|
|
@@ -418,19 +591,15 @@ contract TreasuryExternalChainStation is
|
|
|
418
591
|
admin.timeToAccept = block.timestamp + 1 days;
|
|
419
592
|
}
|
|
420
593
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
* @dev Allows current admin to reject proposed admin changes
|
|
424
|
-
*/
|
|
594
|
+
/// @notice Cancels a pending admin change proposal
|
|
595
|
+
/// @dev Allows current admin to reject proposed admin changes and reset proposal state
|
|
425
596
|
function rejectProposalAdmin() external onlyAdmin {
|
|
426
597
|
admin.proposal = address(0);
|
|
427
598
|
admin.timeToAccept = 0;
|
|
428
599
|
}
|
|
429
600
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
* @dev Can only be called by the proposed admin after the time delay
|
|
433
|
-
*/
|
|
601
|
+
/// @notice Accepts a pending admin proposal and becomes the new admin
|
|
602
|
+
/// @dev Can only be called by the proposed admin after the 1-day time delay
|
|
434
603
|
function acceptAdmin() external {
|
|
435
604
|
if (block.timestamp < admin.timeToAccept) revert();
|
|
436
605
|
|
|
@@ -440,8 +609,13 @@ contract TreasuryExternalChainStation is
|
|
|
440
609
|
|
|
441
610
|
admin.proposal = address(0);
|
|
442
611
|
admin.timeToAccept = 0;
|
|
612
|
+
|
|
613
|
+
_transferOwnership(admin.current);
|
|
443
614
|
}
|
|
444
615
|
|
|
616
|
+
/// @notice Proposes a new Fisher executor address with 1-day time delay
|
|
617
|
+
/// @dev Fisher executor handles cross-chain bridge transaction processing
|
|
618
|
+
/// @param _newFisherExecutor Address of the proposed new Fisher executor
|
|
445
619
|
function proposeFisherExecutor(
|
|
446
620
|
address _newFisherExecutor
|
|
447
621
|
) external onlyAdmin {
|
|
@@ -454,11 +628,15 @@ contract TreasuryExternalChainStation is
|
|
|
454
628
|
fisherExecutor.timeToAccept = block.timestamp + 1 days;
|
|
455
629
|
}
|
|
456
630
|
|
|
631
|
+
/// @notice Cancels a pending Fisher executor change proposal
|
|
632
|
+
/// @dev Allows current admin to reject Fisher executor changes and reset proposal state
|
|
457
633
|
function rejectProposalFisherExecutor() external onlyAdmin {
|
|
458
634
|
fisherExecutor.proposal = address(0);
|
|
459
635
|
fisherExecutor.timeToAccept = 0;
|
|
460
636
|
}
|
|
461
637
|
|
|
638
|
+
/// @notice Accepts a pending Fisher executor proposal
|
|
639
|
+
/// @dev Can only be called by the proposed Fisher executor after the 1-day time delay
|
|
462
640
|
function acceptFisherExecutor() external {
|
|
463
641
|
if (block.timestamp < fisherExecutor.timeToAccept) revert();
|
|
464
642
|
|
|
@@ -470,11 +648,72 @@ contract TreasuryExternalChainStation is
|
|
|
470
648
|
fisherExecutor.timeToAccept = 0;
|
|
471
649
|
}
|
|
472
650
|
|
|
651
|
+
/// @notice Proposes new host chain addresses for all protocols with 1-day time delay
|
|
652
|
+
/// @dev Updates addresses across Hyperlane, LayerZero, and Axelar simultaneously
|
|
653
|
+
/// @param hostChainStationAddress Address-type representation for Hyperlane and LayerZero
|
|
654
|
+
/// @param hostChainStationAddressString String representation for Axelar protocol
|
|
655
|
+
function proposeHostChainAddress(
|
|
656
|
+
address hostChainStationAddress,
|
|
657
|
+
string memory hostChainStationAddressString
|
|
658
|
+
) external onlyAdmin {
|
|
659
|
+
if (fuseSetHostChainAddress == 0x01) revert();
|
|
660
|
+
|
|
661
|
+
hostChainAddressChangeProposal = ChangeHostChainAddressParams({
|
|
662
|
+
porposeAddress_AddressType: hostChainStationAddress,
|
|
663
|
+
porposeAddress_StringType: hostChainStationAddressString,
|
|
664
|
+
timeToAccept: block.timestamp + 1 days
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/// @notice Cancels a pending host chain address change proposal
|
|
669
|
+
/// @dev Resets the host chain address proposal to default state
|
|
670
|
+
function rejectProposalHostChainAddress() external onlyAdmin {
|
|
671
|
+
hostChainAddressChangeProposal = ChangeHostChainAddressParams({
|
|
672
|
+
porposeAddress_AddressType: address(0),
|
|
673
|
+
porposeAddress_StringType: "",
|
|
674
|
+
timeToAccept: 0
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/// @notice Accepts pending host chain address changes across all protocols
|
|
679
|
+
/// @dev Updates Hyperlane, LayerZero, and Axelar configurations simultaneously
|
|
680
|
+
function acceptHostChainAddress() external {
|
|
681
|
+
if (block.timestamp < hostChainAddressChangeProposal.timeToAccept)
|
|
682
|
+
revert();
|
|
683
|
+
|
|
684
|
+
hyperlane.hostChainStationAddress = bytes32(
|
|
685
|
+
uint256(
|
|
686
|
+
uint160(
|
|
687
|
+
hostChainAddressChangeProposal.porposeAddress_AddressType
|
|
688
|
+
)
|
|
689
|
+
)
|
|
690
|
+
);
|
|
691
|
+
layerZero.hostChainStationAddress = bytes32(
|
|
692
|
+
uint256(
|
|
693
|
+
uint160(
|
|
694
|
+
hostChainAddressChangeProposal.porposeAddress_AddressType
|
|
695
|
+
)
|
|
696
|
+
)
|
|
697
|
+
);
|
|
698
|
+
axelar.hostChainStationAddress = hostChainAddressChangeProposal
|
|
699
|
+
.porposeAddress_StringType;
|
|
700
|
+
|
|
701
|
+
_setPeer(
|
|
702
|
+
layerZero.hostChainStationEid,
|
|
703
|
+
layerZero.hostChainStationAddress
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
473
707
|
// Getter functions //
|
|
708
|
+
|
|
709
|
+
/// @notice Returns the complete admin configuration including proposals and timelock
|
|
710
|
+
/// @return Current admin address, proposed admin, and acceptance timestamp
|
|
474
711
|
function getAdmin() external view returns (AddressTypeProposal memory) {
|
|
475
712
|
return admin;
|
|
476
713
|
}
|
|
477
714
|
|
|
715
|
+
/// @notice Returns the complete Fisher executor configuration including proposals and timelock
|
|
716
|
+
/// @return Current Fisher executor address, proposed executor, and acceptance timestamp
|
|
478
717
|
function getFisherExecutor()
|
|
479
718
|
external
|
|
480
719
|
view
|
|
@@ -483,12 +722,18 @@ contract TreasuryExternalChainStation is
|
|
|
483
722
|
return fisherExecutor;
|
|
484
723
|
}
|
|
485
724
|
|
|
725
|
+
/// @notice Returns the next nonce for Fisher bridge operations for a specific user
|
|
726
|
+
/// @dev Used to prevent replay attacks in cross-chain bridge transactions
|
|
727
|
+
/// @param user Address to query the next Fisher execution nonce for
|
|
728
|
+
/// @return Next sequential nonce value for the user's Fisher bridge operations
|
|
486
729
|
function getNextFisherExecutionNonce(
|
|
487
730
|
address user
|
|
488
731
|
) external view returns (uint256) {
|
|
489
732
|
return nextFisherExecutionNonce[user];
|
|
490
733
|
}
|
|
491
734
|
|
|
735
|
+
/// @notice Returns the complete Hyperlane protocol configuration
|
|
736
|
+
/// @return Hyperlane configuration including domain ID, host chain address, and mailbox
|
|
492
737
|
function getHyperlaneConfig()
|
|
493
738
|
external
|
|
494
739
|
view
|
|
@@ -497,6 +742,8 @@ contract TreasuryExternalChainStation is
|
|
|
497
742
|
return hyperlane;
|
|
498
743
|
}
|
|
499
744
|
|
|
745
|
+
/// @notice Returns the complete LayerZero protocol configuration
|
|
746
|
+
/// @return LayerZero configuration including endpoint ID, host chain address, and endpoint
|
|
500
747
|
function getLayerZeroConfig()
|
|
501
748
|
external
|
|
502
749
|
view
|
|
@@ -505,25 +752,35 @@ contract TreasuryExternalChainStation is
|
|
|
505
752
|
return layerZero;
|
|
506
753
|
}
|
|
507
754
|
|
|
755
|
+
/// @notice Returns the complete Axelar protocol configuration
|
|
756
|
+
/// @return Axelar configuration including chain name, addresses, gas service, and gateway
|
|
508
757
|
function getAxelarConfig() external view returns (AxelarConfig memory) {
|
|
509
758
|
return axelar;
|
|
510
759
|
}
|
|
511
760
|
|
|
761
|
+
/// @notice Returns the LayerZero execution options configuration
|
|
762
|
+
/// @return Encoded options bytes for LayerZero message execution (200k gas limit)
|
|
512
763
|
function getOptions() external view returns (bytes memory) {
|
|
513
|
-
return
|
|
764
|
+
return options;
|
|
514
765
|
}
|
|
515
766
|
|
|
516
767
|
// Internal Functions //
|
|
517
768
|
|
|
769
|
+
/// @notice Decodes cross-chain payload and executes the token transfer
|
|
770
|
+
/// @dev Handles both ETH (address(0)) and ERC20 token transfers to recipients
|
|
771
|
+
/// @param payload Encoded transfer data containing token, recipient, and amount
|
|
518
772
|
function decodeAndGive(bytes memory payload) internal {
|
|
519
|
-
(address token, address toAddress, uint256 amount) =
|
|
520
|
-
payload
|
|
521
|
-
);
|
|
773
|
+
(address token, address toAddress, uint256 amount) = PayloadUtils
|
|
774
|
+
.decodePayload(payload);
|
|
522
775
|
if (token == address(0))
|
|
523
|
-
SafeTransferLib.safeTransferETH(
|
|
776
|
+
SafeTransferLib.safeTransferETH(toAddress, amount);
|
|
524
777
|
else IERC20(token).transfer(toAddress, amount);
|
|
525
778
|
}
|
|
526
779
|
|
|
780
|
+
/// @notice Validates and deposits ERC20 tokens from the caller
|
|
781
|
+
/// @dev Verifies token approval and executes transferFrom to this contract
|
|
782
|
+
/// @param token ERC20 token contract address (cannot be address(0))
|
|
783
|
+
/// @param amount Amount of tokens to deposit and hold in this contract
|
|
527
784
|
function verifyAndDepositERC20(address token, uint256 amount) internal {
|
|
528
785
|
if (token == address(0)) revert();
|
|
529
786
|
if (IERC20(token).allowance(msg.sender, address(this)) < amount)
|
|
@@ -532,20 +789,13 @@ contract TreasuryExternalChainStation is
|
|
|
532
789
|
IERC20(token).transferFrom(msg.sender, address(this), amount);
|
|
533
790
|
}
|
|
534
791
|
|
|
535
|
-
function
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
)
|
|
540
|
-
payload = abi.encode(token, toAddress, amount);
|
|
541
|
-
}
|
|
792
|
+
/// @notice Disabled ownership transfer function for security
|
|
793
|
+
/// @dev Ownership changes must go through the time-delayed admin proposal system
|
|
794
|
+
function transferOwnership(
|
|
795
|
+
address newOwner
|
|
796
|
+
) public virtual override onlyOwner {}
|
|
542
797
|
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
(token, toAddress, amount) = abi.decode(
|
|
547
|
-
payload,
|
|
548
|
-
(address, address, uint256)
|
|
549
|
-
);
|
|
550
|
-
}
|
|
798
|
+
/// @notice Disabled ownership renouncement function for security
|
|
799
|
+
/// @dev Prevents accidental loss of administrative control over the contract
|
|
800
|
+
function renounceOwnership() public virtual override onlyOwner {}
|
|
551
801
|
}
|