@layerzerolabs/lz-evm-messagelib-v2 2.0.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.
Files changed (106) hide show
  1. package/artifacts/contracts/Executor.sol/Executor.json +1156 -0
  2. package/artifacts/contracts/ExecutorFeeLib.sol/ExecutorFeeLib.json +336 -0
  3. package/artifacts/contracts/MessageLibBase.sol/MessageLibBase.json +16 -0
  4. package/artifacts/contracts/PriceFeed.sol/PriceFeed.json +650 -0
  5. package/artifacts/contracts/ReceiveLibBaseE2.sol/ReceiveLibBaseE2.json +154 -0
  6. package/artifacts/contracts/SendLibBase.sol/SendLibBase.json +381 -0
  7. package/artifacts/contracts/SendLibBaseE2.sol/SendLibBaseE2.json +819 -0
  8. package/artifacts/contracts/Treasury.sol/Treasury.json +309 -0
  9. package/artifacts/contracts/Worker.sol/Worker.json +579 -0
  10. package/artifacts/contracts/interfaces/IExecutor.sol/IExecutor.json +429 -0
  11. package/artifacts/contracts/interfaces/IExecutorFeeLib.sol/IExecutorFeeLib.json +210 -0
  12. package/artifacts/contracts/interfaces/ILayerZeroExecutor.sol/ILayerZeroExecutor.json +79 -0
  13. package/artifacts/contracts/interfaces/ILayerZeroPriceFeed.sol/ILayerZeroPriceFeed.json +222 -0
  14. package/artifacts/contracts/interfaces/ILayerZeroTreasury.sol/ILayerZeroTreasury.json +79 -0
  15. package/artifacts/contracts/interfaces/IWorker.sol/IWorker.json +221 -0
  16. package/artifacts/contracts/libs/SafeCall.sol/SafeCall.json +10 -0
  17. package/artifacts/contracts/uln/LzExecutor.sol/LzExecutor.json +256 -0
  18. package/artifacts/contracts/uln/ReceiveUlnBase.sol/ReceiveUlnBase.json +472 -0
  19. package/artifacts/contracts/uln/SendUlnBase.sol/SendUlnBase.json +412 -0
  20. package/artifacts/contracts/uln/UlnBase.sol/UlnBase.json +387 -0
  21. package/artifacts/contracts/uln/dvn/DVN.sol/DVN.json +1370 -0
  22. package/artifacts/contracts/uln/dvn/DVNFeeLib.sol/DVNFeeLib.json +300 -0
  23. package/artifacts/contracts/uln/dvn/MultiSig.sol/MultiSig.json +164 -0
  24. package/artifacts/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapter.sol/CCIPDVNAdapter.json +742 -0
  25. package/artifacts/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapterFeeLib.sol/CCIPDVNAdapterFeeLib.json +50 -0
  26. package/artifacts/contracts/uln/dvn/adapters/DVNAdapterBase.sol/DVNAdapterBase.json +463 -0
  27. package/artifacts/contracts/uln/dvn/adapters/DVNAdapterFeeLibBase.sol/DVNAdapterFeeLibBase.json +50 -0
  28. package/artifacts/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapter.sol/AxelarDVNAdapter.json +804 -0
  29. package/artifacts/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapterFeeLib.sol/AxelarDVNAdapterFeeLib.json +50 -0
  30. package/artifacts/contracts/uln/interfaces/IDVN.sol/IDVN.json +370 -0
  31. package/artifacts/contracts/uln/interfaces/IDVNAdapterFeeLib.sol/IDVNAdapterFeeLib.json +50 -0
  32. package/artifacts/contracts/uln/interfaces/IDVNFeeLib.sol/IDVNFeeLib.json +178 -0
  33. package/artifacts/contracts/uln/interfaces/ILayerZeroDVN.sol/ILayerZeroDVN.json +96 -0
  34. package/artifacts/contracts/uln/interfaces/IReceiveUlnE2.sol/IReceiveUlnE2.json +76 -0
  35. package/artifacts/contracts/uln/libs/DVNOptions.sol/DVNOptions.json +27 -0
  36. package/artifacts/contracts/uln/libs/UlnOptions.sol/UlnOptions.json +54 -0
  37. package/artifacts/contracts/uln/uln301/AddressSizeConfig.sol/AddressSizeConfig.json +129 -0
  38. package/artifacts/contracts/uln/uln301/ReceiveLibBaseE1.sol/ILayerZeroReceiveLibrary.json +68 -0
  39. package/artifacts/contracts/uln/uln301/ReceiveLibBaseE1.sol/ReceiveLibBaseE1.json +416 -0
  40. package/artifacts/contracts/uln/uln301/ReceiveUln301.sol/ReceiveUln301.json +969 -0
  41. package/artifacts/contracts/uln/uln301/SendLibBaseE1.sol/SendLibBaseE1.json +804 -0
  42. package/artifacts/contracts/uln/uln301/SendUln301.sol/SendUln301.json +1278 -0
  43. package/artifacts/contracts/uln/uln301/TreasuryFeeHandler.sol/TreasuryFeeHandler.json +94 -0
  44. package/artifacts/contracts/uln/uln301/interfaces/IMessageLibE1.sol/IMessageLibE1.json +247 -0
  45. package/artifacts/contracts/uln/uln301/interfaces/INonceContract.sol/INonceContract.json +40 -0
  46. package/artifacts/contracts/uln/uln301/interfaces/ITreasuryFeeHandler.sol/ITreasuryFeeHandler.json +44 -0
  47. package/artifacts/contracts/uln/uln301/interfaces/IUltraLightNode301.sol/IUltraLightNode301.json +29 -0
  48. package/artifacts/contracts/uln/uln301/mocks/NonceContractMock.sol/NonceContractMock.json +93 -0
  49. package/artifacts/contracts/uln/uln302/ReceiveUln302.sol/ReceiveUln302.json +702 -0
  50. package/artifacts/contracts/uln/uln302/SendUln302.sol/SendUln302.json +1259 -0
  51. package/artifacts/contracts/upgradeable/WorkerUpgradeable.sol/WorkerUpgradeable.json +592 -0
  52. package/artifacts/contracts/upgradeable/proxy/ProxyAdmin.sol/ProxyAdmin.json +181 -0
  53. package/artifacts/contracts/upgradeable/proxy/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json +160 -0
  54. package/contracts/Executor.sol +162 -0
  55. package/contracts/ExecutorFeeLib.sol +190 -0
  56. package/contracts/MessageLibBase.sol +21 -0
  57. package/contracts/PriceFeed.sol +257 -0
  58. package/contracts/ReceiveLibBaseE2.sol +54 -0
  59. package/contracts/SendLibBase.sol +258 -0
  60. package/contracts/SendLibBaseE2.sol +140 -0
  61. package/contracts/Treasury.sol +74 -0
  62. package/contracts/Worker.sol +167 -0
  63. package/contracts/interfaces/IExecutor.sol +44 -0
  64. package/contracts/interfaces/IExecutorFeeLib.sol +32 -0
  65. package/contracts/interfaces/ILayerZeroExecutor.sol +29 -0
  66. package/contracts/interfaces/ILayerZeroPriceFeed.sol +61 -0
  67. package/contracts/interfaces/ILayerZeroTreasury.sol +19 -0
  68. package/contracts/interfaces/IWorker.sol +29 -0
  69. package/contracts/libs/SafeCall.sol +123 -0
  70. package/contracts/uln/LzExecutor.sol +96 -0
  71. package/contracts/uln/ReceiveUlnBase.sol +118 -0
  72. package/contracts/uln/SendUlnBase.sol +129 -0
  73. package/contracts/uln/UlnBase.sol +195 -0
  74. package/contracts/uln/dvn/DVN.sol +349 -0
  75. package/contracts/uln/dvn/DVNFeeLib.sol +141 -0
  76. package/contracts/uln/dvn/MultiSig.sol +104 -0
  77. package/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapter.sol +152 -0
  78. package/contracts/uln/dvn/adapters/CCIP/CCIPDVNAdapterFeeLib.sol +7 -0
  79. package/contracts/uln/dvn/adapters/DVNAdapterBase.sol +162 -0
  80. package/contracts/uln/dvn/adapters/DVNAdapterFeeLibBase.sol +20 -0
  81. package/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapter.sol +156 -0
  82. package/contracts/uln/dvn/adapters/axelar/AxelarDVNAdapterFeeLib.sol +7 -0
  83. package/contracts/uln/interfaces/IDVN.sol +25 -0
  84. package/contracts/uln/interfaces/IDVNAdapterFeeLib.sol +13 -0
  85. package/contracts/uln/interfaces/IDVNFeeLib.sol +30 -0
  86. package/contracts/uln/interfaces/ILayerZeroDVN.sol +34 -0
  87. package/contracts/uln/interfaces/IReceiveUlnE2.sol +18 -0
  88. package/contracts/uln/libs/DVNOptions.sol +181 -0
  89. package/contracts/uln/libs/UlnOptions.sol +176 -0
  90. package/contracts/uln/uln301/AddressSizeConfig.sol +22 -0
  91. package/contracts/uln/uln301/ReceiveLibBaseE1.sol +103 -0
  92. package/contracts/uln/uln301/ReceiveUln301.sol +120 -0
  93. package/contracts/uln/uln301/SendLibBaseE1.sol +194 -0
  94. package/contracts/uln/uln301/SendUln301.sol +99 -0
  95. package/contracts/uln/uln301/TreasuryFeeHandler.sol +41 -0
  96. package/contracts/uln/uln301/interfaces/IMessageLibE1.sol +22 -0
  97. package/contracts/uln/uln301/interfaces/INonceContract.sol +7 -0
  98. package/contracts/uln/uln301/interfaces/ITreasuryFeeHandler.sol +13 -0
  99. package/contracts/uln/uln301/interfaces/IUltraLightNode301.sol +7 -0
  100. package/contracts/uln/uln301/mocks/NonceContractMock.sol +23 -0
  101. package/contracts/uln/uln302/ReceiveUln302.sol +108 -0
  102. package/contracts/uln/uln302/SendUln302.sol +86 -0
  103. package/contracts/upgradeable/WorkerUpgradeable.sol +186 -0
  104. package/contracts/upgradeable/proxy/ProxyAdmin.sol +96 -0
  105. package/contracts/upgradeable/proxy/TransparentUpgradeableProxy.sol +131 -0
  106. package/package.json +51 -0
@@ -0,0 +1,195 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.22;
4
+
5
+ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
6
+
7
+ // the formal properties are documented in the setter functions
8
+ struct UlnConfig {
9
+ uint64 confirmations;
10
+ // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas
11
+ uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
12
+ uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
13
+ uint8 optionalDVNThreshold; // (0, optionalDVNCount]
14
+ address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs
15
+ address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs
16
+ }
17
+
18
+ struct SetDefaultUlnConfigParam {
19
+ uint32 eid;
20
+ UlnConfig config;
21
+ }
22
+
23
+ /// @dev includes the utility functions for checking ULN states and logics
24
+ abstract contract UlnBase is Ownable {
25
+ address private constant DEFAULT_CONFIG = address(0);
26
+ // reserved values for
27
+ uint8 internal constant DEFAULT = 0;
28
+ uint8 internal constant NIL_DVN_COUNT = type(uint8).max;
29
+ uint64 internal constant NIL_CONFIRMATIONS = type(uint64).max;
30
+ // 127 to prevent total number of DVNs (127 * 2) exceeding uint8.max (255)
31
+ // by limiting the total size, it would help constraint the design of DVNOptions
32
+ uint8 private constant MAX_COUNT = (type(uint8).max - 1) / 2;
33
+
34
+ mapping(address oapp => mapping(uint32 eid => UlnConfig)) internal ulnConfigs;
35
+
36
+ error Unsorted();
37
+ error InvalidRequiredDVNCount();
38
+ error InvalidOptionalDVNCount();
39
+ error AtLeastOneDVN();
40
+ error InvalidOptionalDVNThreshold();
41
+ error InvalidConfirmations();
42
+ error UnsupportedEid(uint32 eid);
43
+
44
+ event DefaultUlnConfigsSet(SetDefaultUlnConfigParam[] params);
45
+ event UlnConfigSet(address oapp, uint32 eid, UlnConfig config);
46
+
47
+ // ============================ OnlyOwner ===================================
48
+
49
+ /// @dev about the DEFAULT ULN config
50
+ /// 1) its values are all LITERAL (e.g. 0 is 0). whereas in the oapp ULN config, 0 (default value) points to the default ULN config
51
+ /// this design enables the oapp to point to DEFAULT config without explicitly setting the config
52
+ /// 2) its configuration is more restrictive than the oapp ULN config that
53
+ /// a) it must not use NIL value, where NIL is used only by oapps to indicate the LITERAL 0
54
+ /// b) it must have at least one DVN
55
+ function setDefaultUlnConfigs(SetDefaultUlnConfigParam[] calldata _params) external onlyOwner {
56
+ for (uint256 i = 0; i < _params.length; ++i) {
57
+ SetDefaultUlnConfigParam calldata param = _params[i];
58
+
59
+ // 2.a must not use NIL
60
+ if (param.config.requiredDVNCount == NIL_DVN_COUNT) revert InvalidRequiredDVNCount();
61
+ if (param.config.optionalDVNCount == NIL_DVN_COUNT) revert InvalidOptionalDVNCount();
62
+ if (param.config.confirmations == NIL_CONFIRMATIONS) revert InvalidConfirmations();
63
+
64
+ // 2.b must have at least one dvn
65
+ _assertAtLeastOneDVN(param.config);
66
+
67
+ _setConfig(DEFAULT_CONFIG, param.eid, param.config);
68
+ }
69
+ emit DefaultUlnConfigsSet(_params);
70
+ }
71
+
72
+ // ============================ View ===================================
73
+ // @dev assuming most oapps use default, we get default as memory and custom as storage to save gas
74
+ function getUlnConfig(address _oapp, uint32 _remoteEid) public view returns (UlnConfig memory rtnConfig) {
75
+ UlnConfig storage defaultConfig = ulnConfigs[DEFAULT_CONFIG][_remoteEid];
76
+ UlnConfig storage customConfig = ulnConfigs[_oapp][_remoteEid];
77
+
78
+ // if confirmations is 0, use default
79
+ uint64 confirmations = customConfig.confirmations;
80
+ if (confirmations == DEFAULT) {
81
+ rtnConfig.confirmations = defaultConfig.confirmations;
82
+ } else if (confirmations != NIL_CONFIRMATIONS) {
83
+ // if confirmations is uint64.max, no block confirmations required
84
+ rtnConfig.confirmations = confirmations;
85
+ } // else do nothing, rtnConfig.confirmation is 0
86
+
87
+ if (customConfig.requiredDVNCount == DEFAULT) {
88
+ if (defaultConfig.requiredDVNCount > 0) {
89
+ // copy only if count > 0. save gas
90
+ rtnConfig.requiredDVNs = defaultConfig.requiredDVNs;
91
+ rtnConfig.requiredDVNCount = defaultConfig.requiredDVNCount;
92
+ } // else, do nothing
93
+ } else {
94
+ if (customConfig.requiredDVNCount != NIL_DVN_COUNT) {
95
+ rtnConfig.requiredDVNs = customConfig.requiredDVNs;
96
+ rtnConfig.requiredDVNCount = customConfig.requiredDVNCount;
97
+ } // else, do nothing
98
+ }
99
+
100
+ if (customConfig.optionalDVNCount == DEFAULT) {
101
+ if (defaultConfig.optionalDVNCount > 0) {
102
+ // copy only if count > 0. save gas
103
+ rtnConfig.optionalDVNs = defaultConfig.optionalDVNs;
104
+ rtnConfig.optionalDVNCount = defaultConfig.optionalDVNCount;
105
+ rtnConfig.optionalDVNThreshold = defaultConfig.optionalDVNThreshold;
106
+ }
107
+ } else {
108
+ if (customConfig.optionalDVNCount != NIL_DVN_COUNT) {
109
+ rtnConfig.optionalDVNs = customConfig.optionalDVNs;
110
+ rtnConfig.optionalDVNCount = customConfig.optionalDVNCount;
111
+ rtnConfig.optionalDVNThreshold = customConfig.optionalDVNThreshold;
112
+ }
113
+ }
114
+
115
+ // the final value must have at least one dvn
116
+ // it is possible that some default config result into 0 dvns
117
+ _assertAtLeastOneDVN(rtnConfig);
118
+ }
119
+
120
+ /// @dev Get the uln config without the default config for the given remoteEid.
121
+ function getAppUlnConfig(address _oapp, uint32 _remoteEid) external view returns (UlnConfig memory) {
122
+ return ulnConfigs[_oapp][_remoteEid];
123
+ }
124
+
125
+ // ============================ Internal ===================================
126
+ function _setUlnConfig(uint32 _remoteEid, address _oapp, UlnConfig memory _param) internal {
127
+ _setConfig(_oapp, _remoteEid, _param);
128
+
129
+ // get ULN config again as a catch all to ensure the config is valid
130
+ getUlnConfig(_oapp, _remoteEid);
131
+ emit UlnConfigSet(_oapp, _remoteEid, _param);
132
+ }
133
+
134
+ /// @dev a supported Eid must have a valid default uln config, which has at least one dvn
135
+ function _isSupportedEid(uint32 _remoteEid) internal view returns (bool) {
136
+ UlnConfig storage defaultConfig = ulnConfigs[DEFAULT_CONFIG][_remoteEid];
137
+ return defaultConfig.requiredDVNCount > 0 || defaultConfig.optionalDVNThreshold > 0;
138
+ }
139
+
140
+ function _assertSupportedEid(uint32 _remoteEid) internal view {
141
+ if (!_isSupportedEid(_remoteEid)) revert UnsupportedEid(_remoteEid);
142
+ }
143
+
144
+ // ============================ Private ===================================
145
+
146
+ function _assertAtLeastOneDVN(UlnConfig memory _config) private pure {
147
+ if (_config.requiredDVNCount == 0 && _config.optionalDVNThreshold == 0) revert AtLeastOneDVN();
148
+ }
149
+
150
+ /// @dev this private function is used in both setDefaultUlnConfigs and setUlnConfig
151
+ function _setConfig(address _oapp, uint32 _eid, UlnConfig memory _param) private {
152
+ // @dev required dvns
153
+ // if dvnCount == NONE, dvns list must be empty
154
+ // if dvnCount == DEFAULT, dvn list must be empty
155
+ // otherwise, dvnList.length == dvnCount and assert the list is valid
156
+ if (_param.requiredDVNCount == NIL_DVN_COUNT || _param.requiredDVNCount == DEFAULT) {
157
+ if (_param.requiredDVNs.length != 0) revert InvalidRequiredDVNCount();
158
+ } else {
159
+ if (_param.requiredDVNs.length != _param.requiredDVNCount || _param.requiredDVNCount > MAX_COUNT)
160
+ revert InvalidRequiredDVNCount();
161
+ _assertNoDuplicates(_param.requiredDVNs);
162
+ }
163
+
164
+ // @dev optional dvns
165
+ // if optionalDVNCount == NONE, optionalDVNs list must be empty and threshold must be 0
166
+ // if optionalDVNCount == DEFAULT, optionalDVNs list must be empty and threshold must be 0
167
+ // otherwise, optionalDVNs.length == optionalDVNCount, threshold > 0 && threshold <= optionalDVNCount and assert the list is valid
168
+
169
+ // example use case: an oapp uses the DEFAULT 'required' but
170
+ // a) use a custom 1/1 dvn (practically a required dvn), or
171
+ // b) use a custom 2/3 dvn
172
+ if (_param.optionalDVNCount == NIL_DVN_COUNT || _param.optionalDVNCount == DEFAULT) {
173
+ if (_param.optionalDVNs.length != 0) revert InvalidOptionalDVNCount();
174
+ if (_param.optionalDVNThreshold != 0) revert InvalidOptionalDVNThreshold();
175
+ } else {
176
+ if (_param.optionalDVNs.length != _param.optionalDVNCount || _param.optionalDVNCount > MAX_COUNT)
177
+ revert InvalidOptionalDVNCount();
178
+ if (_param.optionalDVNThreshold == 0 || _param.optionalDVNThreshold > _param.optionalDVNCount)
179
+ revert InvalidOptionalDVNThreshold();
180
+ _assertNoDuplicates(_param.optionalDVNs);
181
+ }
182
+ // don't assert valid count here, as it needs to be validated along side default config
183
+
184
+ ulnConfigs[_oapp][_eid] = _param;
185
+ }
186
+
187
+ function _assertNoDuplicates(address[] memory _dvns) private pure {
188
+ address lastDVN = address(0);
189
+ for (uint256 i = 0; i < _dvns.length; i++) {
190
+ address dvn = _dvns[i];
191
+ if (dvn <= lastDVN) revert Unsorted(); // to ensure no duplicates
192
+ lastDVN = dvn;
193
+ }
194
+ }
195
+ }
@@ -0,0 +1,349 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity 0.8.22;
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 { IDVN } from "../interfaces/IDVN.sol";
10
+ import { IDVNFeeLib } from "../interfaces/IDVNFeeLib.sol";
11
+ import { IReceiveUlnE2 } from "../interfaces/IReceiveUlnE2.sol";
12
+
13
+ struct ExecuteParam {
14
+ uint32 vid;
15
+ address target;
16
+ bytes callData;
17
+ uint256 expiration;
18
+ bytes signatures;
19
+ }
20
+
21
+ contract DVN is Worker, MultiSig, IDVN {
22
+ // to uniquely identify this DVN instance
23
+ // set to endpoint v1 eid if available OR endpoint v2 eid % 30_000
24
+ uint32 public immutable vid;
25
+
26
+ mapping(uint32 dstEid => DstConfig) public dstConfig;
27
+ mapping(bytes32 executableHash => bool used) public usedHashes;
28
+
29
+ error OnlySelf();
30
+ error InvalidRole(bytes32 role);
31
+ error InstructionExpired();
32
+ error InvalidTarget(address target);
33
+ error InvalidVid(uint32 vid);
34
+ error InvalidSignatures();
35
+ error DuplicatedHash(bytes32 executableHash);
36
+
37
+ event VerifySignaturesFailed(uint256 idx);
38
+ event ExecuteFailed(uint256 _index, bytes _data);
39
+ event HashAlreadyUsed(ExecuteParam param, bytes32 _hash);
40
+ // same as DVNFeePaid, but for ULNv2
41
+ event VerifierFeePaid(uint256 fee);
42
+
43
+ // ========================= Constructor =========================
44
+
45
+ /// @dev DVN doesn't have a roleAdmin (address(0x0))
46
+ /// @dev Supports all of ULNv2, ULN301, ULN302 and more
47
+ /// @param _vid unique identifier for this DVN instance
48
+ /// @param _messageLibs array of message lib addresses that are granted the MESSAGE_LIB_ROLE
49
+ /// @param _priceFeed price feed address
50
+ /// @param _signers array of signer addresses for multisig
51
+ /// @param _quorum quorum for multisig
52
+ /// @param _admins array of admin addresses that are granted the ADMIN_ROLE
53
+ constructor(
54
+ uint32 _vid,
55
+ address[] memory _messageLibs,
56
+ address _priceFeed,
57
+ address[] memory _signers,
58
+ uint64 _quorum,
59
+ address[] memory _admins
60
+ ) Worker(_messageLibs, _priceFeed, 12000, address(0x0), _admins) MultiSig(_signers, _quorum) {
61
+ vid = _vid;
62
+ }
63
+
64
+ // ========================= Modifier =========================
65
+
66
+ /// @dev depending on role, restrict access to only self or admin
67
+ /// @dev ALLOWLIST, DENYLIST, MESSAGE_LIB_ROLE can only be granted/revoked by self
68
+ /// @dev ADMIN_ROLE can only be granted/revoked by admin
69
+ /// @dev reverts if not one of the above roles
70
+ /// @param _role role to check
71
+ modifier onlySelfOrAdmin(bytes32 _role) {
72
+ if (_role == ALLOWLIST || _role == DENYLIST || _role == MESSAGE_LIB_ROLE) {
73
+ // self required
74
+ if (address(this) != msg.sender) {
75
+ revert OnlySelf();
76
+ }
77
+ } else if (_role == ADMIN_ROLE) {
78
+ // admin required
79
+ _checkRole(ADMIN_ROLE);
80
+ } else {
81
+ revert InvalidRole(_role);
82
+ }
83
+ _;
84
+ }
85
+
86
+ modifier onlySelf() {
87
+ if (address(this) != msg.sender) {
88
+ revert OnlySelf();
89
+ }
90
+ _;
91
+ }
92
+
93
+ // ========================= OnlySelf =========================
94
+
95
+ /// @dev set signers for multisig
96
+ /// @dev function sig 0x31cb6105
97
+ /// @param _signer signer address
98
+ /// @param _active true to add, false to remove
99
+ function setSigner(address _signer, bool _active) external onlySelf {
100
+ _setSigner(_signer, _active);
101
+ }
102
+
103
+ /// @dev set quorum for multisig
104
+ /// @dev function sig 0x8585c945
105
+ /// @param _quorum to set
106
+ function setQuorum(uint64 _quorum) external onlySelf {
107
+ _setQuorum(_quorum);
108
+ }
109
+
110
+ // ========================= OnlySelf / OnlyAdmin =========================
111
+
112
+ /// @dev overrides AccessControl to allow self/admin to grant role'
113
+ /// @dev function sig 0x2f2ff15d
114
+ /// @param _role role to grant
115
+ /// @param _account account to grant role to
116
+ function grantRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) {
117
+ _grantRole(_role, _account);
118
+ }
119
+
120
+ /// @dev overrides AccessControl to allow self/admin to revoke role
121
+ /// @dev function sig 0xd547741f
122
+ /// @param _role role to revoke
123
+ /// @param _account account to revoke role from
124
+ function revokeRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) {
125
+ _revokeRole(_role, _account);
126
+ }
127
+
128
+ // ========================= OnlyQuorum =========================
129
+
130
+ /// @notice function for quorum to change admin without going through execute function
131
+ /// @dev calldata in the case is abi.encode new admin address
132
+ function quorumChangeAdmin(ExecuteParam calldata _param) external {
133
+ if (_param.expiration <= block.timestamp) {
134
+ revert InstructionExpired();
135
+ }
136
+ if (_param.target != address(this)) {
137
+ revert InvalidTarget(_param.target);
138
+ }
139
+ if (_param.vid != vid) {
140
+ revert InvalidVid(_param.vid);
141
+ }
142
+
143
+ // generate and validate hash
144
+ bytes32 hash = hashCallData(_param.vid, _param.target, _param.callData, _param.expiration);
145
+ (bool sigsValid, ) = verifySignatures(hash, _param.signatures);
146
+ if (!sigsValid) {
147
+ revert InvalidSignatures();
148
+ }
149
+ if (usedHashes[hash]) {
150
+ revert DuplicatedHash(hash);
151
+ }
152
+
153
+ usedHashes[hash] = true;
154
+ _grantRole(ADMIN_ROLE, abi.decode(_param.callData, (address)));
155
+ }
156
+
157
+ // ========================= OnlyAdmin =========================
158
+
159
+ /// @param _params array of DstConfigParam
160
+ function setDstConfig(DstConfigParam[] calldata _params) external onlyRole(ADMIN_ROLE) {
161
+ for (uint256 i = 0; i < _params.length; ++i) {
162
+ DstConfigParam calldata param = _params[i];
163
+ dstConfig[param.dstEid] = DstConfig(param.gas, param.multiplierBps, param.floorMarginUSD);
164
+ }
165
+ emit SetDstConfig(_params);
166
+ }
167
+
168
+ /// @dev takes a list of instructions and executes them in order
169
+ /// @dev if any of the instructions fail, it will emit an error event and continue to execute the rest of the instructions
170
+ /// @param _params array of ExecuteParam, includes target, callData, expiration, signatures
171
+ function execute(ExecuteParam[] calldata _params) external onlyRole(ADMIN_ROLE) {
172
+ for (uint256 i = 0; i < _params.length; ++i) {
173
+ ExecuteParam calldata param = _params[i];
174
+ // 1. skip if invalid vid
175
+ if (param.vid != vid) {
176
+ continue;
177
+ }
178
+
179
+ // 2. skip if expired
180
+ if (param.expiration <= block.timestamp) {
181
+ continue;
182
+ }
183
+
184
+ // generate and validate hash
185
+ bytes32 hash = hashCallData(param.vid, param.target, param.callData, param.expiration);
186
+
187
+ // 3. check signatures
188
+ (bool sigsValid, ) = verifySignatures(hash, param.signatures);
189
+ if (!sigsValid) {
190
+ emit VerifySignaturesFailed(i);
191
+ continue;
192
+ }
193
+
194
+ // 4. should check hash
195
+ bool shouldCheckHash = _shouldCheckHash(bytes4(param.callData));
196
+ if (shouldCheckHash) {
197
+ if (usedHashes[hash]) {
198
+ emit HashAlreadyUsed(param, hash);
199
+ continue;
200
+ } else {
201
+ usedHashes[hash] = true; // prevent reentry and replay attack
202
+ }
203
+ }
204
+
205
+ (bool success, bytes memory rtnData) = param.target.call(param.callData);
206
+ if (!success) {
207
+ if (shouldCheckHash) {
208
+ // need to unset the usedHash otherwise it cant be used
209
+ usedHashes[hash] = false;
210
+ }
211
+ // emit an event in any case
212
+ emit ExecuteFailed(i, rtnData);
213
+ }
214
+ }
215
+ }
216
+
217
+ /// @dev to support ULNv2
218
+ /// @dev the withdrawFee function for ULN30X is built in the Worker contract
219
+ /// @param _lib message lib address
220
+ /// @param _to address to withdraw to
221
+ /// @param _amount amount to withdraw
222
+ function withdrawFeeFromUlnV2(address _lib, address payable _to, uint256 _amount) external onlyRole(ADMIN_ROLE) {
223
+ if (!hasRole(MESSAGE_LIB_ROLE, _lib)) {
224
+ revert OnlyMessageLib();
225
+ }
226
+ ILayerZeroUltraLightNodeV2(_lib).withdrawNative(_to, _amount);
227
+ }
228
+
229
+ // ========================= OnlyMessageLib =========================
230
+
231
+ /// @dev for ULN301, ULN302 and more to assign job
232
+ /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist
233
+ /// @param _param assign job param
234
+ /// @param _options dvn options
235
+ function assignJob(
236
+ AssignJobParam calldata _param,
237
+ bytes calldata _options
238
+ ) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_param.sender) returns (uint256 totalFee) {
239
+ IDVNFeeLib.FeeParams memory feeParams = IDVNFeeLib.FeeParams(
240
+ priceFeed,
241
+ _param.dstEid,
242
+ _param.confirmations,
243
+ _param.sender,
244
+ quorum,
245
+ defaultMultiplierBps
246
+ );
247
+ totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[_param.dstEid], _options);
248
+ }
249
+
250
+ /// @dev to support ULNv2
251
+ /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist
252
+ /// @param _dstEid destination EndpointId
253
+ /// @param //_outboundProofType outbound proof type
254
+ /// @param _confirmations block confirmations
255
+ /// @param _sender message sender address
256
+ function assignJob(
257
+ uint16 _dstEid,
258
+ uint16 /*_outboundProofType*/,
259
+ uint64 _confirmations,
260
+ address _sender
261
+ ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 totalFee) {
262
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
263
+ priceFeed,
264
+ _dstEid,
265
+ _confirmations,
266
+ _sender,
267
+ quorum,
268
+ defaultMultiplierBps
269
+ );
270
+ // ULNV2 does not have dvn options
271
+ totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], bytes(""));
272
+ emit VerifierFeePaid(totalFee);
273
+ }
274
+
275
+ // ========================= View =========================
276
+
277
+ /// @dev getFee can revert if _sender doesn't pass ACL
278
+ /// @param _dstEid destination EndpointId
279
+ /// @param _confirmations block confirmations
280
+ /// @param _sender message sender address
281
+ /// @param _options dvn options
282
+ /// @return fee fee in native amount
283
+ function getFee(
284
+ uint32 _dstEid,
285
+ uint64 _confirmations,
286
+ address _sender,
287
+ bytes calldata _options
288
+ ) external view onlyAcl(_sender) returns (uint256 fee) {
289
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
290
+ priceFeed,
291
+ _dstEid,
292
+ _confirmations,
293
+ _sender,
294
+ quorum,
295
+ defaultMultiplierBps
296
+ );
297
+ return IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], _options);
298
+ }
299
+
300
+ /// @dev to support ULNv2
301
+ /// @dev getFee can revert if _sender doesn't pass ACL
302
+ /// @param _dstEid destination EndpointId
303
+ /// @param //_outboundProofType outbound proof type
304
+ /// @param _confirmations block confirmations
305
+ /// @param _sender message sender address
306
+ function getFee(
307
+ uint16 _dstEid,
308
+ uint16 /*_outboundProofType*/,
309
+ uint64 _confirmations,
310
+ address _sender
311
+ ) public view onlyAcl(_sender) returns (uint256 fee) {
312
+ IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams(
313
+ priceFeed,
314
+ _dstEid,
315
+ _confirmations,
316
+ _sender,
317
+ quorum,
318
+ defaultMultiplierBps
319
+ );
320
+ return IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], bytes(""));
321
+ }
322
+
323
+ /// @param _target target address
324
+ /// @param _callData call data
325
+ /// @param _expiration expiration timestamp
326
+ /// @return hash of above
327
+ function hashCallData(
328
+ uint32 _vid,
329
+ address _target,
330
+ bytes calldata _callData,
331
+ uint256 _expiration
332
+ ) public pure returns (bytes32) {
333
+ return keccak256(abi.encodePacked(_vid, _target, _expiration, _callData));
334
+ }
335
+
336
+ // ========================= Internal =========================
337
+
338
+ /// @dev to save gas, we don't check hash for some functions (where replaying won't change the state)
339
+ /// @dev for example, some administrative functions like changing signers, the contract should check hash to double spending
340
+ /// @dev should ensure that all onlySelf functions have unique functionSig
341
+ /// @param _functionSig function signature
342
+ /// @return true if should check hash
343
+ function _shouldCheckHash(bytes4 _functionSig) internal pure returns (bool) {
344
+ // never check for these selectors to save gas
345
+ return
346
+ _functionSig != IReceiveUlnE2.verify.selector && // 0x0223536e, replaying won't change the state
347
+ _functionSig != ILayerZeroUltraLightNodeV2.updateHash.selector; // 0x704316e5, replaying will be revert at uln
348
+ }
349
+ }
@@ -0,0 +1,141 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity 0.8.22;
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
+
13
+ contract DVNFeeLib is Ownable, IDVNFeeLib {
14
+ using DVNOptions for bytes;
15
+
16
+ uint16 internal constant EXECUTE_FIXED_BYTES = 68; // encoded: funcSigHash + params -> 4 + (32 * 2)
17
+ uint16 internal constant SIGNATURE_RAW_BYTES = 65; // not encoded
18
+ // callData(updateHash) = 132 (4 + 32 * 4), padded to 32 = 160 and encoded as bytes with an 64 byte overhead = 224
19
+ uint16 internal constant UPDATE_HASH_BYTES = 224;
20
+
21
+ uint256 private immutable nativeDecimalsRate;
22
+
23
+ constructor(uint256 _nativeDecimalsRate) {
24
+ nativeDecimalsRate = _nativeDecimalsRate;
25
+ }
26
+
27
+ // ================================ OnlyOwner ================================
28
+ function withdrawToken(address _token, address _to, uint256 _amount) external onlyOwner {
29
+ // transfers native if _token is address(0x0)
30
+ Transfer.nativeOrToken(_token, _to, _amount);
31
+ }
32
+
33
+ // ========================= External =========================
34
+ /// @dev get fee function that can change state. e.g. paying priceFeed
35
+ /// @param _params fee params
36
+ /// @param _dstConfig dst config
37
+ /// @param //_options options
38
+ function getFeeOnSend(
39
+ FeeParams calldata _params,
40
+ IDVN.DstConfig calldata _dstConfig,
41
+ bytes calldata _options
42
+ ) external payable returns (uint256) {
43
+ _decodeDVNOptions(_options); // todo: validate options
44
+
45
+ uint256 callDataSize = _getCallDataSize(_params.quorum);
46
+
47
+ // for future versions where priceFeed charges a fee
48
+ // uint256 priceFeedFee = ILayerZeroPriceFeed(_params.priceFeed).getFee(_params.dstEid, callDataSize, _dstConfig.gas);
49
+ // (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeOnSend{
50
+ // value: priceFeedFee
51
+ // }(_params.dstEid, callDataSize, _dstConfig.gas);
52
+
53
+ (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeOnSend(
54
+ _params.dstEid,
55
+ callDataSize,
56
+ _dstConfig.gas
57
+ );
58
+
59
+ return
60
+ _applyPremium(
61
+ fee,
62
+ _dstConfig.multiplierBps,
63
+ _params.defaultMultiplierBps,
64
+ _dstConfig.floorMarginUSD,
65
+ nativePriceUSD
66
+ );
67
+ }
68
+
69
+ // ========================= View =========================
70
+ /// @dev get fee view function
71
+ /// @param _params fee params
72
+ /// @param _dstConfig dst config
73
+ /// @param //_options options
74
+ function getFee(
75
+ FeeParams calldata _params,
76
+ IDVN.DstConfig calldata _dstConfig,
77
+ bytes calldata _options
78
+ ) external view returns (uint256) {
79
+ _decodeDVNOptions(_options); // validate options
80
+
81
+ uint256 callDataSize = _getCallDataSize(_params.quorum);
82
+ (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeByEid(
83
+ _params.dstEid,
84
+ callDataSize,
85
+ _dstConfig.gas
86
+ );
87
+ return
88
+ _applyPremium(
89
+ fee,
90
+ _dstConfig.multiplierBps,
91
+ _params.defaultMultiplierBps,
92
+ _dstConfig.floorMarginUSD,
93
+ nativePriceUSD
94
+ );
95
+ }
96
+
97
+ // ========================= Internal =========================
98
+ function _getCallDataSize(uint256 _quorum) internal pure returns (uint256) {
99
+ uint256 totalSignatureBytes = _quorum * SIGNATURE_RAW_BYTES;
100
+ if (totalSignatureBytes % 32 != 0) {
101
+ totalSignatureBytes = totalSignatureBytes - (totalSignatureBytes % 32) + 32;
102
+ }
103
+ // getFee should charge on execute(updateHash)
104
+ // totalSignatureBytesPadded also has 64 overhead for bytes
105
+ return uint256(EXECUTE_FIXED_BYTES) + UPDATE_HASH_BYTES + totalSignatureBytes + 64;
106
+ }
107
+
108
+ function _applyPremium(
109
+ uint256 _fee,
110
+ uint16 _bps,
111
+ uint16 _defaultBps,
112
+ uint128 _marginUSD,
113
+ uint128 _nativePriceUSD
114
+ ) internal view returns (uint256) {
115
+ uint16 multiplierBps = _bps == 0 ? _defaultBps : _bps;
116
+
117
+ uint256 feeWithMultiplier = (_fee * multiplierBps) / 10000;
118
+ if (_nativePriceUSD == 0 || _marginUSD == 0) {
119
+ return feeWithMultiplier;
120
+ }
121
+
122
+ uint256 feeWithFloorMargin = _fee + (_marginUSD * nativeDecimalsRate) / _nativePriceUSD;
123
+
124
+ return feeWithFloorMargin > feeWithMultiplier ? feeWithFloorMargin : feeWithMultiplier;
125
+ }
126
+
127
+ function _decodeDVNOptions(bytes calldata _options) internal pure returns (uint256) {
128
+ uint256 cursor;
129
+ while (cursor < _options.length) {
130
+ (uint8 optionType, , uint256 newCursor) = _options.nextDVNOption(cursor);
131
+ cursor = newCursor;
132
+ revert UnsupportedOptionType(optionType);
133
+ }
134
+ if (cursor != _options.length) revert DVNOptions.InvalidDVNOptions(cursor);
135
+
136
+ return 0; // todo: precrime fee model
137
+ }
138
+
139
+ // send funds here to pay for price feed directly
140
+ receive() external payable {}
141
+ }