@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,125 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
6
+
7
+ import { UlnBase, UlnConfig } from "./UlnBase.sol";
8
+
9
+ struct Verification {
10
+ bool submitted;
11
+ uint64 confirmations;
12
+ }
13
+
14
+ /// @dev includes the utility functions for checking ULN states and logics
15
+ abstract contract ReceiveUlnBase is UlnBase {
16
+ using PacketV1Codec for bytes;
17
+
18
+ mapping(bytes32 headerHash => mapping(bytes32 payloadHash => mapping(address dvn => Verification)))
19
+ public hashLookup;
20
+
21
+ event PayloadVerified(address dvn, bytes header, uint256 confirmations, bytes32 proofHash);
22
+
23
+ error LZ_ULN_InvalidPacketHeader();
24
+ error LZ_ULN_InvalidPacketVersion();
25
+ error LZ_ULN_InvalidEid();
26
+ error LZ_ULN_Verifying();
27
+
28
+ // ============================ External ===================================
29
+ function verifiable(
30
+ UlnConfig memory _config,
31
+ bytes32 _headerHash,
32
+ bytes32 _payloadHash
33
+ ) external view returns (bool) {
34
+ return _checkVerifiable(_config, _headerHash, _payloadHash);
35
+ }
36
+
37
+ function assertHeader(bytes calldata _packetHeader, uint32 _localEid) external pure {
38
+ _assertHeader(_packetHeader, _localEid);
39
+ }
40
+
41
+ // ============================ Internal ===================================
42
+ /// @dev per DVN signing function
43
+ function _verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) internal {
44
+ hashLookup[keccak256(_packetHeader)][_payloadHash][msg.sender] = Verification(true, _confirmations);
45
+ emit PayloadVerified(msg.sender, _packetHeader, _confirmations, _payloadHash);
46
+ }
47
+
48
+ function _verified(
49
+ address _dvn,
50
+ bytes32 _headerHash,
51
+ bytes32 _payloadHash,
52
+ uint64 _requiredConfirmation
53
+ ) internal view returns (bool verified) {
54
+ Verification memory verification = hashLookup[_headerHash][_payloadHash][_dvn];
55
+ // return true if the dvn has signed enough confirmations
56
+ verified = verification.submitted && verification.confirmations >= _requiredConfirmation;
57
+ }
58
+
59
+ function _verifyAndReclaimStorage(UlnConfig memory _config, bytes32 _headerHash, bytes32 _payloadHash) internal {
60
+ if (!_checkVerifiable(_config, _headerHash, _payloadHash)) {
61
+ revert LZ_ULN_Verifying();
62
+ }
63
+
64
+ // iterate the required DVNs
65
+ if (_config.requiredDVNCount > 0) {
66
+ for (uint8 i = 0; i < _config.requiredDVNCount; ++i) {
67
+ delete hashLookup[_headerHash][_payloadHash][_config.requiredDVNs[i]];
68
+ }
69
+ }
70
+
71
+ // iterate the optional DVNs
72
+ if (_config.optionalDVNCount > 0) {
73
+ for (uint8 i = 0; i < _config.optionalDVNCount; ++i) {
74
+ delete hashLookup[_headerHash][_payloadHash][_config.optionalDVNs[i]];
75
+ }
76
+ }
77
+ }
78
+
79
+ function _assertHeader(bytes calldata _packetHeader, uint32 _localEid) internal pure {
80
+ // assert packet header is of right size 81
81
+ if (_packetHeader.length != 81) revert LZ_ULN_InvalidPacketHeader();
82
+ // assert packet header version is the same as ULN
83
+ if (_packetHeader.version() != PacketV1Codec.PACKET_VERSION) revert LZ_ULN_InvalidPacketVersion();
84
+ // assert the packet is for this endpoint
85
+ if (_packetHeader.dstEid() != _localEid) revert LZ_ULN_InvalidEid();
86
+ }
87
+
88
+ /// @dev for verifiable view function
89
+ /// @dev checks if this verification is ready to be committed to the endpoint
90
+ function _checkVerifiable(
91
+ UlnConfig memory _config,
92
+ bytes32 _headerHash,
93
+ bytes32 _payloadHash
94
+ ) internal view returns (bool) {
95
+ // iterate the required DVNs
96
+ if (_config.requiredDVNCount > 0) {
97
+ for (uint8 i = 0; i < _config.requiredDVNCount; ++i) {
98
+ if (!_verified(_config.requiredDVNs[i], _headerHash, _payloadHash, _config.confirmations)) {
99
+ // return if any of the required DVNs haven't signed
100
+ return false;
101
+ }
102
+ }
103
+ if (_config.optionalDVNCount == 0) {
104
+ // returns early if all required DVNs have signed and there are no optional DVNs
105
+ return true;
106
+ }
107
+ }
108
+
109
+ // then it must require optional validations
110
+ uint8 threshold = _config.optionalDVNThreshold;
111
+ for (uint8 i = 0; i < _config.optionalDVNCount; ++i) {
112
+ if (_verified(_config.optionalDVNs[i], _headerHash, _payloadHash, _config.confirmations)) {
113
+ // increment the optional count if the optional DVN has signed
114
+ threshold--;
115
+ if (threshold == 0) {
116
+ // early return if the optional threshold has hit
117
+ return true;
118
+ }
119
+ }
120
+ }
121
+
122
+ // return false as a catch-all
123
+ return false;
124
+ }
125
+ }
@@ -0,0 +1,129 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol";
6
+ import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
7
+
8
+ import { ILayerZeroDVN } from "./interfaces/ILayerZeroDVN.sol";
9
+ import { DVNOptions } from "./libs/DVNOptions.sol";
10
+ import { UlnOptions } from "./libs/UlnOptions.sol";
11
+ import { WorkerOptions } from "../SendLibBase.sol";
12
+ import { UlnConfig, UlnBase } from "./UlnBase.sol";
13
+
14
+ /// @dev includes the utility functions for checking ULN states and logics
15
+ abstract contract SendUlnBase is UlnBase {
16
+ event DVNFeePaid(address[] requiredDVNs, address[] optionalDVNs, uint256[] fees);
17
+
18
+ function _splitUlnOptions(bytes calldata _options) internal pure returns (bytes memory, WorkerOptions[] memory) {
19
+ (bytes memory executorOpts, bytes memory dvnOpts) = UlnOptions.decode(_options);
20
+
21
+ if (dvnOpts.length == 0) {
22
+ return (executorOpts, new WorkerOptions[](0));
23
+ }
24
+
25
+ WorkerOptions[] memory workerOpts = new WorkerOptions[](1);
26
+ workerOpts[0] = WorkerOptions(DVNOptions.WORKER_ID, dvnOpts);
27
+ return (executorOpts, workerOpts);
28
+ }
29
+
30
+ /// ---------- pay and assign jobs ----------
31
+
32
+ function _payDVNs(
33
+ mapping(address => uint256) storage _fees,
34
+ Packet memory _packet,
35
+ WorkerOptions[] memory _options
36
+ ) internal returns (uint256 totalFee, bytes memory encodedPacket) {
37
+ bytes memory packetHeader = PacketV1Codec.encodePacketHeader(_packet);
38
+ bytes memory payload = PacketV1Codec.encodePayload(_packet);
39
+ bytes32 payloadHash = keccak256(payload);
40
+ uint32 dstEid = _packet.dstEid;
41
+ address sender = _packet.sender;
42
+ UlnConfig memory config = getUlnConfig(sender, dstEid);
43
+
44
+ // if options is not empty, it must be dvn options
45
+ bytes memory dvnOptions = _options.length == 0 ? bytes("") : _options[0].options;
46
+ uint256[] memory dvnFees;
47
+ (totalFee, dvnFees) = _assignJobs(
48
+ _fees,
49
+ config,
50
+ ILayerZeroDVN.AssignJobParam(dstEid, packetHeader, payloadHash, config.confirmations, sender),
51
+ dvnOptions
52
+ );
53
+ encodedPacket = abi.encodePacked(packetHeader, payload);
54
+
55
+ emit DVNFeePaid(config.requiredDVNs, config.optionalDVNs, dvnFees);
56
+ }
57
+
58
+ function _assignJobs(
59
+ mapping(address => uint256) storage _fees,
60
+ UlnConfig memory _ulnConfig,
61
+ ILayerZeroDVN.AssignJobParam memory _param,
62
+ bytes memory dvnOptions
63
+ ) internal returns (uint256 totalFee, uint256[] memory dvnFees) {
64
+ (bytes[] memory optionsArray, uint8[] memory dvnIds) = DVNOptions.groupDVNOptionsByIdx(dvnOptions);
65
+
66
+ uint8 dvnsLength = _ulnConfig.requiredDVNCount + _ulnConfig.optionalDVNCount;
67
+ dvnFees = new uint256[](dvnsLength);
68
+ for (uint8 i = 0; i < dvnsLength; ++i) {
69
+ address dvn = i < _ulnConfig.requiredDVNCount
70
+ ? _ulnConfig.requiredDVNs[i]
71
+ : _ulnConfig.optionalDVNs[i - _ulnConfig.requiredDVNCount];
72
+
73
+ bytes memory options = "";
74
+ for (uint256 j = 0; j < dvnIds.length; ++j) {
75
+ if (dvnIds[j] == i) {
76
+ options = optionsArray[j];
77
+ break;
78
+ }
79
+ }
80
+
81
+ dvnFees[i] = ILayerZeroDVN(dvn).assignJob(_param, options);
82
+ if (dvnFees[i] > 0) {
83
+ _fees[dvn] += dvnFees[i];
84
+ totalFee += dvnFees[i];
85
+ }
86
+ }
87
+ }
88
+
89
+ /// ---------- quote ----------
90
+ function _quoteDVNs(
91
+ address _sender,
92
+ uint32 _dstEid,
93
+ WorkerOptions[] memory _options
94
+ ) internal view returns (uint256 totalFee) {
95
+ UlnConfig memory config = getUlnConfig(_sender, _dstEid);
96
+
97
+ // if options is not empty, it must be dvn options
98
+ bytes memory dvnOptions = _options.length == 0 ? bytes("") : _options[0].options;
99
+ (bytes[] memory optionsArray, uint8[] memory dvnIndices) = DVNOptions.groupDVNOptionsByIdx(dvnOptions);
100
+
101
+ totalFee = _getFees(config, _dstEid, _sender, optionsArray, dvnIndices);
102
+ }
103
+
104
+ function _getFees(
105
+ UlnConfig memory _config,
106
+ uint32 _dstEid,
107
+ address _sender,
108
+ bytes[] memory _optionsArray,
109
+ uint8[] memory _dvnIds
110
+ ) internal view returns (uint256 totalFee) {
111
+ // here we merge 2 list of dvns into 1 to allocate the indexed dvn options to the right dvn
112
+ uint8 dvnsLength = _config.requiredDVNCount + _config.optionalDVNCount;
113
+ for (uint8 i = 0; i < dvnsLength; ++i) {
114
+ address dvn = i < _config.requiredDVNCount
115
+ ? _config.requiredDVNs[i]
116
+ : _config.optionalDVNs[i - _config.requiredDVNCount];
117
+
118
+ bytes memory options = "";
119
+ // it is a double loop here. however, if the list is short, the cost is very acceptable.
120
+ for (uint256 j = 0; j < _dvnIds.length; ++j) {
121
+ if (_dvnIds[j] == i) {
122
+ options = _optionsArray[j];
123
+ break;
124
+ }
125
+ }
126
+ totalFee += ILayerZeroDVN(dvn).getFee(_dstEid, _config.confirmations, _sender, options);
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,195 @@
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
+
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 LZ_ULN_Unsorted();
37
+ error LZ_ULN_InvalidRequiredDVNCount();
38
+ error LZ_ULN_InvalidOptionalDVNCount();
39
+ error LZ_ULN_AtLeastOneDVN();
40
+ error LZ_ULN_InvalidOptionalDVNThreshold();
41
+ error LZ_ULN_InvalidConfirmations();
42
+ error LZ_ULN_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 LZ_ULN_InvalidRequiredDVNCount();
61
+ if (param.config.optionalDVNCount == NIL_DVN_COUNT) revert LZ_ULN_InvalidOptionalDVNCount();
62
+ if (param.config.confirmations == NIL_CONFIRMATIONS) revert LZ_ULN_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 LZ_ULN_UnsupportedEid(_remoteEid);
142
+ }
143
+
144
+ // ============================ Private ===================================
145
+
146
+ function _assertAtLeastOneDVN(UlnConfig memory _config) private pure {
147
+ if (_config.requiredDVNCount == 0 && _config.optionalDVNThreshold == 0) revert LZ_ULN_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 LZ_ULN_InvalidRequiredDVNCount();
158
+ } else {
159
+ if (_param.requiredDVNs.length != _param.requiredDVNCount || _param.requiredDVNCount > MAX_COUNT)
160
+ revert LZ_ULN_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 LZ_ULN_InvalidOptionalDVNCount();
174
+ if (_param.optionalDVNThreshold != 0) revert LZ_ULN_InvalidOptionalDVNThreshold();
175
+ } else {
176
+ if (_param.optionalDVNs.length != _param.optionalDVNCount || _param.optionalDVNCount > MAX_COUNT)
177
+ revert LZ_ULN_InvalidOptionalDVNCount();
178
+ if (_param.optionalDVNThreshold == 0 || _param.optionalDVNThreshold > _param.optionalDVNCount)
179
+ revert LZ_ULN_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 LZ_ULN_Unsorted(); // to ensure no duplicates
192
+ lastDVN = dvn;
193
+ }
194
+ }
195
+ }