@layerzerolabs/protocol-v2-messagelib-evm-contracts 1.2.95

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 (88) hide show
  1. package/README.md +15 -0
  2. package/contracts/Executor.sol +339 -0
  3. package/contracts/ExecutorFeeLib.sol +241 -0
  4. package/contracts/MessageLibBase.sol +21 -0
  5. package/contracts/PriceFeed.sol +304 -0
  6. package/contracts/ReceiveLibBaseE2.sol +28 -0
  7. package/contracts/SendLibBase.sol +258 -0
  8. package/contracts/SendLibBaseE2.sol +140 -0
  9. package/contracts/SimpleReadExecutor.sol +110 -0
  10. package/contracts/Treasury.sol +74 -0
  11. package/contracts/Worker.sol +167 -0
  12. package/contracts/decompressor/DVNDecoder.sol +52 -0
  13. package/contracts/decompressor/DVNDecompressor.sol +70 -0
  14. package/contracts/decompressor/DecompressorExtension.sol +204 -0
  15. package/contracts/decompressor/ExecutorDecoder.sol +147 -0
  16. package/contracts/decompressor/ExecutorDecompressor.sol +58 -0
  17. package/contracts/interfaces/IExecutor.sol +47 -0
  18. package/contracts/interfaces/IExecutorFeeLib.sol +56 -0
  19. package/contracts/interfaces/ILayerZeroExecutor.sol +29 -0
  20. package/contracts/interfaces/ILayerZeroPriceFeed.sol +61 -0
  21. package/contracts/interfaces/ILayerZeroReadExecutor.sol +15 -0
  22. package/contracts/interfaces/ILayerZeroTreasury.sol +19 -0
  23. package/contracts/interfaces/IWorker.sol +29 -0
  24. package/contracts/libs/ExecutorOptions.sol +104 -0
  25. package/contracts/libs/SafeCall.sol +123 -0
  26. package/contracts/lowlat/EssenceDVNWrapper.sol +77 -0
  27. package/contracts/lowlat/MultiCall.sol +42 -0
  28. package/contracts/mocks/DVNMock.sol +28 -0
  29. package/contracts/mocks/ExecutorMock.sol +95 -0
  30. package/contracts/uln/LzExecutor.sol +129 -0
  31. package/contracts/uln/ReceiveUlnBase.sol +125 -0
  32. package/contracts/uln/SendUlnBase.sol +129 -0
  33. package/contracts/uln/UlnBase.sol +195 -0
  34. package/contracts/uln/dvn/DVN.sol +393 -0
  35. package/contracts/uln/dvn/DVNFeeLib.sol +288 -0
  36. package/contracts/uln/dvn/DeadDVN.sol +30 -0
  37. package/contracts/uln/dvn/MultiSig.sol +135 -0
  38. package/contracts/uln/dvn/SimpleReadDVN.sol +112 -0
  39. package/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapter.sol +151 -0
  40. package/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapterFeeLib.sol +71 -0
  41. package/contracts/uln/dvn/adapters/DVNAdapterBase.sol +114 -0
  42. package/contracts/uln/dvn/adapters/arbitrum/ArbitrumDVNAdapterL1.sol +126 -0
  43. package/contracts/uln/dvn/adapters/arbitrum/ArbitrumDVNAdapterL2.sol +50 -0
  44. package/contracts/uln/dvn/adapters/avalanche/NOTES.md +4 -0
  45. package/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapter.sol +152 -0
  46. package/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapterFeeLib.sol +127 -0
  47. package/contracts/uln/dvn/adapters/libs/DVNAdapterMessageCodec.sol +41 -0
  48. package/contracts/uln/dvn/adapters/optimism/OptimismDVNAdapterL1.sol +86 -0
  49. package/contracts/uln/dvn/adapters/optimism/OptimismDVNAdapterL2.sol +58 -0
  50. package/contracts/uln/interfaces/IDVN.sol +26 -0
  51. package/contracts/uln/interfaces/IDVNFeeLib.sol +56 -0
  52. package/contracts/uln/interfaces/ILayerZeroDVN.sol +34 -0
  53. package/contracts/uln/interfaces/ILayerZeroReadDVN.sol +27 -0
  54. package/contracts/uln/interfaces/IReceiveUlnE2.sol +13 -0
  55. package/contracts/uln/interfaces/adapters/IAxelarDVNAdapter.sol +50 -0
  56. package/contracts/uln/interfaces/adapters/IAxelarDVNAdapterFeeLib.sol +49 -0
  57. package/contracts/uln/interfaces/adapters/ICCIPDVNAdapter.sol +34 -0
  58. package/contracts/uln/interfaces/adapters/ICCIPDVNAdapterFeeLib.sol +46 -0
  59. package/contracts/uln/libs/DVNOptions.sol +181 -0
  60. package/contracts/uln/libs/ReadCmdCodecV1.sol +136 -0
  61. package/contracts/uln/libs/SupportedCmdTypes.sol +29 -0
  62. package/contracts/uln/libs/UlnOptions.sol +176 -0
  63. package/contracts/uln/readlib/ReadLib1002.sol +518 -0
  64. package/contracts/uln/readlib/ReadLib1002View.sol +98 -0
  65. package/contracts/uln/readlib/ReadLibBase.sol +187 -0
  66. package/contracts/uln/uln301/AddressSizeConfig.sol +22 -0
  67. package/contracts/uln/uln301/ReceiveLibBaseE1.sol +103 -0
  68. package/contracts/uln/uln301/ReceiveUln301.sol +83 -0
  69. package/contracts/uln/uln301/ReceiveUln301View.sol +89 -0
  70. package/contracts/uln/uln301/SendLibBaseE1.sol +194 -0
  71. package/contracts/uln/uln301/SendUln301.sol +99 -0
  72. package/contracts/uln/uln301/TreasuryFeeHandler.sol +41 -0
  73. package/contracts/uln/uln301/interfaces/IMessageLibE1.sol +22 -0
  74. package/contracts/uln/uln301/interfaces/INonceContract.sol +7 -0
  75. package/contracts/uln/uln301/interfaces/ITreasuryFeeHandler.sol +13 -0
  76. package/contracts/uln/uln301/interfaces/IUltraLightNode301.sol +7 -0
  77. package/contracts/uln/uln301/mocks/NonceContractMock.sol +23 -0
  78. package/contracts/uln/uln302/ReceiveUln302.sol +85 -0
  79. package/contracts/uln/uln302/ReceiveUln302View.sol +89 -0
  80. package/contracts/uln/uln302/SendUln302.sol +86 -0
  81. package/contracts/upgradeable/WorkerUpgradeable.sol +186 -0
  82. package/contracts/upgradeable/proxy/ProxyAdmin.sol +96 -0
  83. package/contracts/upgradeable/proxy/TransparentUpgradeableProxy.sol +131 -0
  84. package/dist/index.d.ts +7 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +6 -0
  87. package/dist/index.js.map +1 -0
  88. package/package.json +70 -0
@@ -0,0 +1,393 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { ILayerZeroUltraLightNodeV2 } from "@layerzerolabs/lz-evm-v1-0.7/contracts/interfaces/ILayerZeroUltraLightNodeV2.sol";
6
+
7
+ import { Worker } from "../../Worker.sol";
8
+ import { MultiSig } from "./MultiSig.sol";
9
+ import { ReadLib1002 } from "../readlib/ReadLib1002.sol";
10
+ import { IDVN } from "../interfaces/IDVN.sol";
11
+ import { IDVNFeeLib } from "../interfaces/IDVNFeeLib.sol";
12
+ import { IReceiveUlnE2 } from "../interfaces/IReceiveUlnE2.sol";
13
+
14
+ struct ExecuteParam {
15
+ uint32 vid;
16
+ address target;
17
+ bytes callData;
18
+ uint256 expiration;
19
+ bytes signatures;
20
+ }
21
+
22
+ contract DVN is Worker, MultiSig, IDVN {
23
+ // to uniquely identify this DVN instance
24
+ // set to endpoint v1 eid if available OR endpoint v2 eid % 30_000
25
+ uint32 public immutable vid;
26
+ uint32 public immutable localEidV2; // endpoint-v2 only, for read call
27
+
28
+ mapping(uint32 dstEid => DstConfig) public dstConfig;
29
+ mapping(bytes32 executableHash => bool used) public usedHashes;
30
+
31
+ error DVN_OnlySelf();
32
+ error DVN_InvalidRole(bytes32 role);
33
+ error DVN_InstructionExpired();
34
+ error DVN_InvalidTarget(address target);
35
+ error DVN_InvalidVid(uint32 vid);
36
+ error DVN_InvalidSignatures();
37
+ error DVN_DuplicatedHash(bytes32 executableHash);
38
+
39
+ event VerifySignaturesFailed(uint256 idx);
40
+ event ExecuteFailed(uint256 _index, bytes _data);
41
+ event HashAlreadyUsed(ExecuteParam param, bytes32 _hash);
42
+ // same as DVNFeePaid, but for ULNv2
43
+ event VerifierFeePaid(uint256 fee);
44
+
45
+ // ========================= Constructor =========================
46
+
47
+ /// @dev DVN doesn't have a roleAdmin (address(0x0))
48
+ /// @dev Supports all of ULNv2, ULN301, ULN302 and more
49
+ /// @param _localEidV2 local endpoint-v2 eid
50
+ /// @param _vid unique identifier for this DVN instance
51
+ /// @param _messageLibs array of message lib addresses that are granted the MESSAGE_LIB_ROLE
52
+ /// @param _priceFeed price feed address
53
+ /// @param _signers array of signer addresses for multisig
54
+ /// @param _quorum quorum for multisig
55
+ /// @param _admins array of admin addresses that are granted the ADMIN_ROLE
56
+ constructor(
57
+ uint32 _localEidV2,
58
+ uint32 _vid,
59
+ address[] memory _messageLibs,
60
+ address _priceFeed,
61
+ address[] memory _signers,
62
+ uint64 _quorum,
63
+ address[] memory _admins
64
+ ) Worker(_messageLibs, _priceFeed, 12000, address(0x0), _admins) MultiSig(_signers, _quorum) {
65
+ vid = _vid;
66
+ localEidV2 = _localEidV2;
67
+ }
68
+
69
+ // ========================= Modifier =========================
70
+
71
+ /// @dev depending on role, restrict access to only self or admin
72
+ /// @dev ALLOWLIST, DENYLIST, MESSAGE_LIB_ROLE can only be granted/revoked by self
73
+ /// @dev ADMIN_ROLE can only be granted/revoked by admin
74
+ /// @dev reverts if not one of the above roles
75
+ /// @param _role role to check
76
+ modifier onlySelfOrAdmin(bytes32 _role) {
77
+ if (_role == ALLOWLIST || _role == DENYLIST || _role == MESSAGE_LIB_ROLE) {
78
+ // self required
79
+ if (address(this) != msg.sender) {
80
+ revert DVN_OnlySelf();
81
+ }
82
+ } else if (_role == ADMIN_ROLE) {
83
+ // admin required
84
+ _checkRole(ADMIN_ROLE);
85
+ } else {
86
+ revert DVN_InvalidRole(_role);
87
+ }
88
+ _;
89
+ }
90
+
91
+ modifier onlySelf() {
92
+ if (address(this) != msg.sender) {
93
+ revert DVN_OnlySelf();
94
+ }
95
+ _;
96
+ }
97
+
98
+ // ========================= OnlySelf =========================
99
+
100
+ /// @dev set signers for multisig
101
+ /// @dev function sig 0x31cb6105
102
+ /// @param _signer signer address
103
+ /// @param _active true to add, false to remove
104
+ function setSigner(address _signer, bool _active) external onlySelf {
105
+ _setSigner(_signer, _active);
106
+ }
107
+
108
+ /// @dev set quorum for multisig
109
+ /// @dev function sig 0x8585c945
110
+ /// @param _quorum to set
111
+ function setQuorum(uint64 _quorum) external onlySelf {
112
+ _setQuorum(_quorum);
113
+ }
114
+
115
+ // ========================= OnlySelf / OnlyAdmin =========================
116
+
117
+ /// @dev overrides AccessControl to allow self/admin to grant role'
118
+ /// @dev function sig 0x2f2ff15d
119
+ /// @param _role role to grant
120
+ /// @param _account account to grant role to
121
+ function grantRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) {
122
+ _grantRole(_role, _account);
123
+ }
124
+
125
+ /// @dev overrides AccessControl to allow self/admin to revoke role
126
+ /// @dev function sig 0xd547741f
127
+ /// @param _role role to revoke
128
+ /// @param _account account to revoke role from
129
+ function revokeRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) {
130
+ _revokeRole(_role, _account);
131
+ }
132
+
133
+ // ========================= OnlyQuorum =========================
134
+
135
+ /// @notice function for quorum to change admin without going through execute function
136
+ /// @dev calldata in the case is abi.encode new admin address
137
+ function quorumChangeAdmin(ExecuteParam calldata _param) external {
138
+ if (_param.expiration <= block.timestamp) {
139
+ revert DVN_InstructionExpired();
140
+ }
141
+ if (_param.target != address(this)) {
142
+ revert DVN_InvalidTarget(_param.target);
143
+ }
144
+ if (_param.vid != vid) {
145
+ revert DVN_InvalidVid(_param.vid);
146
+ }
147
+
148
+ // generate and validate hash
149
+ bytes32 hash = hashCallData(_param.vid, _param.target, _param.callData, _param.expiration);
150
+ (bool sigsValid, ) = verifySignatures(hash, _param.signatures);
151
+ if (!sigsValid) {
152
+ revert DVN_InvalidSignatures();
153
+ }
154
+ if (usedHashes[hash]) {
155
+ revert DVN_DuplicatedHash(hash);
156
+ }
157
+
158
+ usedHashes[hash] = true;
159
+ _grantRole(ADMIN_ROLE, abi.decode(_param.callData, (address)));
160
+ }
161
+
162
+ // ========================= OnlyAdmin =========================
163
+
164
+ /// @param _params array of DstConfigParam
165
+ function setDstConfig(DstConfigParam[] calldata _params) external onlyRole(ADMIN_ROLE) {
166
+ for (uint256 i = 0; i < _params.length; ++i) {
167
+ DstConfigParam calldata param = _params[i];
168
+ dstConfig[param.dstEid] = DstConfig(param.gas, param.multiplierBps, param.floorMarginUSD);
169
+ }
170
+ emit SetDstConfig(_params);
171
+ }
172
+
173
+ /// @dev takes a list of instructions and executes them in order
174
+ /// @dev if any of the instructions fail, it will emit an error event and continue to execute the rest of the instructions
175
+ /// @param _params array of ExecuteParam, includes target, callData, expiration, signatures
176
+ function execute(ExecuteParam[] calldata _params) external onlyRole(ADMIN_ROLE) {
177
+ for (uint256 i = 0; i < _params.length; ++i) {
178
+ ExecuteParam calldata param = _params[i];
179
+ // 1. skip if invalid vid
180
+ if (param.vid != vid) {
181
+ continue;
182
+ }
183
+
184
+ // 2. skip if expired
185
+ if (param.expiration <= block.timestamp) {
186
+ continue;
187
+ }
188
+
189
+ // generate and validate hash
190
+ bytes32 hash = hashCallData(param.vid, param.target, param.callData, param.expiration);
191
+
192
+ // 3. check signatures
193
+ (bool sigsValid, ) = verifySignatures(hash, param.signatures);
194
+ if (!sigsValid) {
195
+ emit VerifySignaturesFailed(i);
196
+ continue;
197
+ }
198
+
199
+ // 4. should check hash
200
+ bool shouldCheckHash = _shouldCheckHash(bytes4(param.callData));
201
+ if (shouldCheckHash) {
202
+ if (usedHashes[hash]) {
203
+ emit HashAlreadyUsed(param, hash);
204
+ continue;
205
+ } else {
206
+ usedHashes[hash] = true; // prevent reentry and replay attack
207
+ }
208
+ }
209
+
210
+ (bool success, bytes memory rtnData) = param.target.call(param.callData);
211
+ if (!success) {
212
+ if (shouldCheckHash) {
213
+ // need to unset the usedHash otherwise it cant be used
214
+ usedHashes[hash] = false;
215
+ }
216
+ // emit an event in any case
217
+ emit ExecuteFailed(i, rtnData);
218
+ }
219
+ }
220
+ }
221
+
222
+ /// @dev to support ULNv2
223
+ /// @dev the withdrawFee function for ULN30X is built in the Worker contract
224
+ /// @param _lib message lib address
225
+ /// @param _to address to withdraw to
226
+ /// @param _amount amount to withdraw
227
+ function withdrawFeeFromUlnV2(address _lib, address payable _to, uint256 _amount) external onlyRole(ADMIN_ROLE) {
228
+ if (!hasRole(MESSAGE_LIB_ROLE, _lib)) {
229
+ revert Worker_OnlyMessageLib();
230
+ }
231
+ ILayerZeroUltraLightNodeV2(_lib).withdrawNative(_to, _amount);
232
+ }
233
+
234
+ // ========================= OnlyMessageLib =========================
235
+
236
+ /// @dev for ULN301, ULN302 and more to assign job
237
+ /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist
238
+ /// @param _param assign job param
239
+ /// @param _options dvn options
240
+ function assignJob(
241
+ AssignJobParam calldata _param,
242
+ bytes calldata _options
243
+ ) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_param.sender) returns (uint256 totalFee) {
244
+ IDVNFeeLib.FeeParams memory feeParams = IDVNFeeLib.FeeParams(
245
+ priceFeed,
246
+ _param.dstEid,
247
+ _param.confirmations,
248
+ _param.sender,
249
+ quorum,
250
+ defaultMultiplierBps
251
+ );
252
+ totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[_param.dstEid], _options);
253
+ }
254
+
255
+ /// @dev to support ULNv2
256
+ /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist
257
+ /// @param _dstEid destination EndpointId
258
+ /// @param //_outboundProofType outbound proof type
259
+ /// @param _confirmations block confirmations
260
+ /// @param _sender message sender address
261
+ function assignJob(
262
+ uint16 _dstEid,
263
+ uint16 /*_outboundProofType*/,
264
+ uint64 _confirmations,
265
+ address _sender
266
+ ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 totalFee) {
267
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
268
+ priceFeed,
269
+ _dstEid,
270
+ _confirmations,
271
+ _sender,
272
+ quorum,
273
+ defaultMultiplierBps
274
+ );
275
+ // ULNV2 does not have dvn options
276
+ totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], bytes(""));
277
+ emit VerifierFeePaid(totalFee);
278
+ }
279
+
280
+ /// @dev to support ReadLib
281
+ // @param _packetHeader - version + nonce + path
282
+ // @param _cmd - the command to be executed to obtain the payload
283
+ // @param _options - options
284
+ function assignJob(
285
+ address _sender,
286
+ bytes calldata /*_packetHeader*/,
287
+ bytes calldata _cmd,
288
+ bytes calldata _options
289
+ ) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 fee) {
290
+ IDVNFeeLib.FeeParamsForRead memory feeParams = IDVNFeeLib.FeeParamsForRead(
291
+ priceFeed,
292
+ _sender,
293
+ quorum,
294
+ defaultMultiplierBps
295
+ );
296
+ fee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[localEidV2], _cmd, _options);
297
+ }
298
+
299
+ // ========================= View =========================
300
+
301
+ /// @dev getFee can revert if _sender doesn't pass ACL
302
+ /// @param _dstEid destination EndpointId
303
+ /// @param _confirmations block confirmations
304
+ /// @param _sender message sender address
305
+ /// @param _options dvn options
306
+ /// @return fee fee in native amount
307
+ function getFee(
308
+ uint32 _dstEid,
309
+ uint64 _confirmations,
310
+ address _sender,
311
+ bytes calldata _options
312
+ ) external view onlyAcl(_sender) returns (uint256 fee) {
313
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
314
+ priceFeed,
315
+ _dstEid,
316
+ _confirmations,
317
+ _sender,
318
+ quorum,
319
+ defaultMultiplierBps
320
+ );
321
+ fee = IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], _options);
322
+ }
323
+
324
+ /// @dev to support ULNv2
325
+ /// @dev getFee can revert if _sender doesn't pass ACL
326
+ /// @param _dstEid destination EndpointId
327
+ /// @param //_outboundProofType outbound proof type
328
+ /// @param _confirmations block confirmations
329
+ /// @param _sender message sender address
330
+ function getFee(
331
+ uint16 _dstEid,
332
+ uint16 /*_outboundProofType*/,
333
+ uint64 _confirmations,
334
+ address _sender
335
+ ) public view onlyAcl(_sender) returns (uint256 fee) {
336
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
337
+ priceFeed,
338
+ _dstEid,
339
+ _confirmations,
340
+ _sender,
341
+ quorum,
342
+ defaultMultiplierBps
343
+ );
344
+ fee = IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], bytes(""));
345
+ }
346
+
347
+ /// @dev to support ReadLib
348
+ // @param _packetHeader - version + nonce + path
349
+ // @param _cmd - the command to be executed to obtain the payload
350
+ // @param _options - options
351
+ function getFee(
352
+ address _sender,
353
+ bytes calldata /*_packetHeader*/,
354
+ bytes calldata _cmd,
355
+ bytes calldata _options
356
+ ) external view onlyAcl(_sender) returns (uint256 fee) {
357
+ IDVNFeeLib.FeeParamsForRead memory feeParams = IDVNFeeLib.FeeParamsForRead(
358
+ priceFeed,
359
+ _sender,
360
+ quorum,
361
+ defaultMultiplierBps
362
+ );
363
+ fee = IDVNFeeLib(workerFeeLib).getFee(feeParams, dstConfig[localEidV2], _cmd, _options);
364
+ }
365
+
366
+ /// @param _target target address
367
+ /// @param _callData call data
368
+ /// @param _expiration expiration timestamp
369
+ /// @return hash of above
370
+ function hashCallData(
371
+ uint32 _vid,
372
+ address _target,
373
+ bytes calldata _callData,
374
+ uint256 _expiration
375
+ ) public pure returns (bytes32) {
376
+ return keccak256(abi.encodePacked(_vid, _target, _expiration, _callData));
377
+ }
378
+
379
+ // ========================= Internal =========================
380
+
381
+ /// @dev to save gas, we don't check hash for some functions (where replaying won't change the state)
382
+ /// @dev for example, some administrative functions like changing signers, the contract should check hash to double spending
383
+ /// @dev should ensure that all onlySelf functions have unique functionSig
384
+ /// @param _functionSig function signature
385
+ /// @return true if should check hash
386
+ function _shouldCheckHash(bytes4 _functionSig) internal pure returns (bool) {
387
+ // never check for these selectors to save gas
388
+ return
389
+ _functionSig != IReceiveUlnE2.verify.selector && // 0x0223536e, replaying won't change the state
390
+ _functionSig != ReadLib1002.verify.selector && // 0xab750e75, replaying won't change the state
391
+ _functionSig != ILayerZeroUltraLightNodeV2.updateHash.selector; // 0x704316e5, replaying will be revert at uln
392
+ }
393
+ }
@@ -0,0 +1,288 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
6
+ import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol";
7
+
8
+ import { ILayerZeroPriceFeed } from "../../interfaces/ILayerZeroPriceFeed.sol";
9
+ import { IDVN } from "../interfaces/IDVN.sol";
10
+ import { IDVNFeeLib } from "../interfaces/IDVNFeeLib.sol";
11
+ import { DVNOptions } from "../libs/DVNOptions.sol";
12
+ import { ReadCmdCodecV1 } from "../libs/ReadCmdCodecV1.sol";
13
+ import { SupportedCmdTypesLib, SupportedCmdTypes, BitMap256 } from "../libs/SupportedCmdTypes.sol";
14
+
15
+ contract DVNFeeLib is Ownable, IDVNFeeLib {
16
+ using DVNOptions for bytes;
17
+
18
+ struct SetSupportedCmdTypesParam {
19
+ uint32 targetEid;
20
+ BitMap256 types;
21
+ }
22
+
23
+ struct BlockTimeConfig {
24
+ uint32 avgBlockTime; // milliseconds
25
+ uint64 blockNum; // the block number of the reference timestamp
26
+ uint64 timestamp; // second, the reference timestamp of the block number
27
+ uint32 maxPastRetention; // second, the max retention time the DVN will accept read requests/compute from the past time
28
+ uint32 maxFutureRetention; // second, the max retention time the DVN will accept read requests/compute from the future time
29
+ }
30
+
31
+ uint16 internal constant BPS_BASE = 10000;
32
+
33
+ // encoded( execute(ExecuteParam[]) ): funcSigHash + params -> 4 + 32(Offset of the array) + 32(array size) + 32(first element start offset)\
34
+ // + 32(vid) + 32(target) + 32(calldata-offset) + 32(expiration) + 32(signatures-offset) = 260
35
+ uint16 internal constant EXECUTE_FIXED_BYTES = 260;
36
+ uint16 internal constant SIGNATURE_RAW_BYTES = 65; // not encoded
37
+ // verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations)\
38
+ // 4 + 32(header offset) + 32(payloadHash) + 32(confirmations, 8 -> 32 padded) + 32(header-size) + 96(81 -> header-padded) = 228,
39
+ // padded to multiples of 32 = 256, encoded as bytes with an 32 byte for the bytes size = 288
40
+ uint16 internal constant VERIFY_BYTES_ULN = 288;
41
+ // verify(bytes calldata _packetHeader, bytes32 _cmdHash, bytes32 _payloadHash)\
42
+ // 4 + 32(header offset) + 32(cmdHash) + 32(payloadHash) + 32(header-size) + 96(81 -> header-padded) = 228,
43
+ // padded to multiples of 32 = 256, encoded as bytes with an 32 byte for the bytes size = 288
44
+ uint16 internal constant VERIFY_BYTES_CMD_LIB = 288;
45
+
46
+ uint256 internal immutable nativeDecimalsRate;
47
+ uint32 internal immutable localEidV2; // endpoint-v2 only, for read call
48
+
49
+ SupportedCmdTypes internal supportedCmdTypes;
50
+
51
+ uint120 internal evmCallRequestV1FeeUSD;
52
+ uint120 internal evmCallComputeV1ReduceFeeUSD;
53
+ uint16 internal evmCallComputeV1MapBps;
54
+
55
+ mapping(uint32 dstEid => BlockTimeConfig) public dstBlockTimeConfigs;
56
+
57
+ constructor(uint32 _localEidV2, uint256 _nativeDecimalsRate) {
58
+ localEidV2 = _localEidV2;
59
+ nativeDecimalsRate = _nativeDecimalsRate;
60
+ }
61
+
62
+ // ================================ OnlyOwner ================================
63
+ function setSupportedCmdTypes(SetSupportedCmdTypesParam[] calldata _params) external onlyOwner {
64
+ for (uint256 i = 0; i < _params.length; i++) {
65
+ supportedCmdTypes.cmdTypes[_params[i].targetEid] = _params[i].types;
66
+ }
67
+ }
68
+
69
+ function getSupportedCmdTypes(uint32 _targetEid) external view returns (BitMap256) {
70
+ return supportedCmdTypes.cmdTypes[_targetEid];
71
+ }
72
+
73
+ function setDstBlockTimeConfigs(
74
+ uint32[] calldata dstEids,
75
+ BlockTimeConfig[] calldata _blockConfigs
76
+ ) external onlyOwner {
77
+ if (dstEids.length != _blockConfigs.length) revert DVN_INVALID_INPUT_LENGTH();
78
+ for (uint256 i = 0; i < dstEids.length; i++) {
79
+ dstBlockTimeConfigs[dstEids[i]] = _blockConfigs[i];
80
+ }
81
+ }
82
+
83
+ function withdrawToken(address _token, address _to, uint256 _amount) external onlyOwner {
84
+ // transfers native if _token is address(0x0)
85
+ Transfer.nativeOrToken(_token, _to, _amount);
86
+ }
87
+
88
+ function setCmdFees(
89
+ uint120 _evmCallRequestV1FeeUSD,
90
+ uint120 _evmCallComputeV1ReduceFeeUSD,
91
+ uint16 _evmCallComputeV1MapBps
92
+ ) external onlyOwner {
93
+ evmCallRequestV1FeeUSD = _evmCallRequestV1FeeUSD;
94
+ evmCallComputeV1ReduceFeeUSD = _evmCallComputeV1ReduceFeeUSD;
95
+ evmCallComputeV1MapBps = _evmCallComputeV1MapBps;
96
+ }
97
+
98
+ function getCmdFees() external view returns (uint120, uint120, uint16) {
99
+ return (evmCallRequestV1FeeUSD, evmCallComputeV1ReduceFeeUSD, evmCallComputeV1MapBps);
100
+ }
101
+
102
+ // ========================= External =========================
103
+ /// @dev get fee function that can change state. e.g. paying priceFeed
104
+ /// @param _params fee params
105
+ /// @param _dstConfig dst config
106
+ /// @param //_options options
107
+ function getFeeOnSend(
108
+ FeeParams calldata _params,
109
+ IDVN.DstConfig calldata _dstConfig,
110
+ bytes calldata _options
111
+ ) external payable returns (uint256) {
112
+ return getFee(_params, _dstConfig, _options);
113
+ }
114
+
115
+ function getFeeOnSend(
116
+ FeeParamsForRead calldata _params,
117
+ IDVN.DstConfig calldata _dstConfig,
118
+ bytes calldata _cmd,
119
+ bytes calldata _options
120
+ ) external payable returns (uint256 fee) {
121
+ fee = getFee(_params, _dstConfig, _cmd, _options);
122
+ }
123
+
124
+ // ========================= View =========================
125
+ /// @dev get fee view function
126
+ /// @param _params fee params
127
+ /// @param _dstConfig dst config
128
+ /// @param //_options options
129
+ function getFee(
130
+ FeeParams calldata _params,
131
+ IDVN.DstConfig calldata _dstConfig,
132
+ bytes calldata _options
133
+ ) public view returns (uint256) {
134
+ if (_dstConfig.gas == 0) revert DVN_EidNotSupported(_params.dstEid);
135
+
136
+ _decodeDVNOptions(_options); // validate options
137
+
138
+ uint256 callDataSize = _getCallDataSize(_params.quorum);
139
+ (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeByEid(
140
+ _params.dstEid,
141
+ callDataSize,
142
+ _dstConfig.gas
143
+ );
144
+ return
145
+ _applyPremium(
146
+ fee,
147
+ _dstConfig.multiplierBps,
148
+ _params.defaultMultiplierBps,
149
+ _dstConfig.floorMarginUSD,
150
+ nativePriceUSD
151
+ );
152
+ }
153
+
154
+ function getFee(
155
+ FeeParamsForRead calldata _params,
156
+ IDVN.DstConfig calldata _dstConfig,
157
+ bytes calldata _cmd,
158
+ bytes calldata _options
159
+ ) public view returns (uint256) {
160
+ if (_dstConfig.gas == 0) revert DVN_EidNotSupported(localEidV2);
161
+
162
+ _decodeDVNOptions(_options); // validate options
163
+
164
+ uint256 callDataSize = _getReadCallDataSize(_params.quorum);
165
+ (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeByEid(
166
+ localEidV2,
167
+ callDataSize,
168
+ _dstConfig.gas
169
+ );
170
+
171
+ // cmdFeeUSD -> cmdFee native final
172
+ uint256 cmdFeeUSD = _estimateCmdFee(_cmd);
173
+ uint256 cmdFee = (cmdFeeUSD * nativeDecimalsRate) / nativePriceUSD;
174
+
175
+ return
176
+ _applyPremium(
177
+ fee + cmdFee,
178
+ _dstConfig.multiplierBps,
179
+ _params.defaultMultiplierBps,
180
+ _dstConfig.floorMarginUSD,
181
+ nativePriceUSD
182
+ );
183
+ }
184
+
185
+ // ========================= Internal =========================
186
+ function _getCallDataSize(uint256 _quorum) internal pure returns (uint256) {
187
+ return _getCallDataSizeByQuorumAndVerifyBytes(_quorum, VERIFY_BYTES_ULN);
188
+ }
189
+
190
+ function _getReadCallDataSize(uint256 _quorum) internal pure returns (uint256) {
191
+ return _getCallDataSizeByQuorumAndVerifyBytes(_quorum, VERIFY_BYTES_CMD_LIB);
192
+ }
193
+
194
+ function _getCallDataSizeByQuorumAndVerifyBytes(
195
+ uint256 _quorum,
196
+ uint256 verifyBytes
197
+ ) internal pure returns (uint256) {
198
+ uint256 totalSignatureBytes = _quorum * SIGNATURE_RAW_BYTES;
199
+ if (totalSignatureBytes % 32 != 0) {
200
+ totalSignatureBytes = totalSignatureBytes - (totalSignatureBytes % 32) + 32;
201
+ }
202
+ // getFee should charge on execute(updateHash)
203
+ // totalSignatureBytesPadded also has 32 as size of the bytes
204
+ return uint256(EXECUTE_FIXED_BYTES) + verifyBytes + totalSignatureBytes + 32;
205
+ }
206
+
207
+ function _applyPremium(
208
+ uint256 _fee,
209
+ uint16 _bps,
210
+ uint16 _defaultBps,
211
+ uint128 _marginUSD,
212
+ uint128 _nativePriceUSD
213
+ ) internal view returns (uint256) {
214
+ uint16 multiplierBps = _bps == 0 ? _defaultBps : _bps;
215
+
216
+ uint256 feeWithMultiplier = (_fee * multiplierBps) / 10000;
217
+ if (_nativePriceUSD == 0 || _marginUSD == 0) {
218
+ return feeWithMultiplier;
219
+ }
220
+
221
+ uint256 feeWithFloorMargin = _fee + (_marginUSD * nativeDecimalsRate) / _nativePriceUSD;
222
+
223
+ return feeWithFloorMargin > feeWithMultiplier ? feeWithFloorMargin : feeWithMultiplier;
224
+ }
225
+
226
+ function _decodeDVNOptions(bytes calldata _options) internal pure returns (uint256) {
227
+ uint256 cursor;
228
+ while (cursor < _options.length) {
229
+ (uint8 optionType, , uint256 newCursor) = _options.nextDVNOption(cursor);
230
+ cursor = newCursor;
231
+ revert DVN_UnsupportedOptionType(optionType);
232
+ }
233
+ if (cursor != _options.length) revert DVNOptions.DVN_InvalidDVNOptions(cursor);
234
+
235
+ return 0; // todo: precrime fee model
236
+ }
237
+
238
+ function _estimateCmdFee(bytes calldata _cmd) internal view returns (uint256 fee) {
239
+ ReadCmdCodecV1.Cmd memory cmd = ReadCmdCodecV1.decode(_cmd, _assertCmdTypeSupported);
240
+ fee = cmd.numEvmCallRequestV1 * evmCallRequestV1FeeUSD;
241
+ if (cmd.evmCallComputeV1Map) {
242
+ fee += (fee * evmCallComputeV1MapBps) / BPS_BASE;
243
+ }
244
+ if (cmd.evmCallComputeV1Reduce) {
245
+ fee += evmCallComputeV1ReduceFeeUSD;
246
+ }
247
+ }
248
+
249
+ function _assertCmdTypeSupported(
250
+ uint32 _targetEid,
251
+ bool _isBlockNum,
252
+ uint64 _blockNumOrTimestamp,
253
+ uint8 _cmdType
254
+ ) internal view {
255
+ supportedCmdTypes.assertSupported(_targetEid, _cmdType);
256
+ if (supportedCmdTypes.isSupported(_targetEid, SupportedCmdTypesLib.CMD_V1__TIMESTAMP_VALIDATE)) {
257
+ BlockTimeConfig memory blockCnf = dstBlockTimeConfigs[_targetEid];
258
+ uint64 timestamp = _blockNumOrTimestamp;
259
+ if (_isBlockNum) {
260
+ // convert the blockNum to the timestamp
261
+ if (_blockNumOrTimestamp > blockCnf.blockNum) {
262
+ timestamp =
263
+ blockCnf.timestamp +
264
+ ((_blockNumOrTimestamp - blockCnf.blockNum) * blockCnf.avgBlockTime) /
265
+ 1000;
266
+ } else {
267
+ timestamp =
268
+ blockCnf.timestamp -
269
+ ((blockCnf.blockNum - _blockNumOrTimestamp) * blockCnf.avgBlockTime) /
270
+ 1000;
271
+ }
272
+ }
273
+ if (
274
+ timestamp + blockCnf.maxPastRetention < block.timestamp ||
275
+ timestamp > block.timestamp + blockCnf.maxFutureRetention
276
+ ) {
277
+ revert DVN_TimestampOutOfRange(_targetEid, timestamp);
278
+ }
279
+ }
280
+ }
281
+
282
+ function version() external pure returns (uint64 major, uint8 minor) {
283
+ return (1, 1);
284
+ }
285
+
286
+ // send funds here to pay for price feed directly
287
+ receive() external payable {}
288
+ }