// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;libraryAddressCast{
errorAddressCast_InvalidSizeForAddress();
errorAddressCast_InvalidAddress();
functiontoBytes32(bytescalldata _addressBytes) internalpurereturns (bytes32 result) {
if (_addressBytes.length>32) revert AddressCast_InvalidAddress();
result =bytes32(_addressBytes);
unchecked {
uint256 offset =32- _addressBytes.length;
result = result >> (offset *8);
}
}
functiontoBytes32(address _address) internalpurereturns (bytes32 result) {
result =bytes32(uint256(uint160(_address)));
}
functiontoBytes(bytes32 _addressBytes32, uint256 _size) internalpurereturns (bytesmemory result) {
if (_size ==0|| _size >32) revert AddressCast_InvalidSizeForAddress();
result =newbytes(_size);
unchecked {
uint256 offset =256- _size *8;
assembly {
mstore(add(result, 32), shl(offset, _addressBytes32))
}
}
}
functiontoAddress(bytes32 _addressBytes32) internalpurereturns (address result) {
result =address(uint160(uint256(_addressBytes32)));
}
functiontoAddress(bytescalldata _addressBytes) internalpurereturns (address result) {
if (_addressBytes.length!=20) revert AddressCast_InvalidAddress();
result =address(bytes20(_addressBytes));
}
}
Contract Source Code
File 2 of 19: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)pragmasolidity ^0.8.0;/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
function_contextSuffixLength() internalviewvirtualreturns (uint256) {
return0;
}
}
Contract Source Code
File 3 of 19: ERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)pragmasolidity ^0.8.0;import"./IERC165.sol";
/**
* @dev Implementation of the {IERC165} interface.
*
* Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
* for the additional interface id that will be supported. For example:
*
* ```solidity
* function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
* return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
* }
* ```
*
* Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
*/abstractcontractERC165isIERC165{
/**
* @dev See {IERC165-supportsInterface}.
*/functionsupportsInterface(bytes4 interfaceId) publicviewvirtualoverridereturns (bool) {
return interfaceId ==type(IERC165).interfaceId;
}
}
Contract Source Code
File 4 of 19: IERC165.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/interfaceIERC165{
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
// SPDX-License-Identifier: MITpragmasolidity >=0.8.0;/// @dev should be implemented by the ReceiveUln302 contract and future ReceiveUln contracts on EndpointV2interfaceIReceiveUlnE2{
/// @notice for each dvn to verify the payload/// @dev this function signature 0x0223536efunctionverify(bytescalldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external;
/// @notice verify the payload at endpoint, will check if all DVNs verifiedfunctioncommitVerification(bytescalldata _packetHeader, bytes32 _payloadHash) external;
}
// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;/// @dev simply a container of endpoint address and local eidabstractcontractMessageLibBase{
addressinternalimmutable endpoint;
uint32internalimmutable localEid;
errorLZ_MessageLib_OnlyEndpoint();
modifieronlyEndpoint() {
if (endpoint !=msg.sender) revert LZ_MessageLib_OnlyEndpoint();
_;
}
constructor(address _endpoint, uint32 _localEid) {
endpoint = _endpoint;
localEid = _localEid;
}
}
Contract Source Code
File 14 of 19: Ownable.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)pragmasolidity ^0.8.0;import"../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/abstractcontractOwnableisContext{
addressprivate _owner;
eventOwnershipTransferred(addressindexed previousOwner, addressindexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/modifieronlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/function_checkOwner() internalviewvirtual{
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/functionrenounceOwnership() publicvirtualonlyOwner{
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
require(newOwner !=address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/function_transferOwnership(address newOwner) internalvirtual{
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;import { IERC165 } from"@openzeppelin/contracts/utils/introspection/IERC165.sol";
import { ERC165 } from"@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { ILayerZeroEndpointV2, Origin } from"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { IMessageLib, MessageLibType } from"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol";
import { PacketV1Codec } from"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
import { MessageLibBase } from"./MessageLibBase.sol";
/// @dev receive-side message library base contract on endpoint v2./// it does not have the complication as the one of endpoint v1, such as nonce, executor whitelist, etc.abstractcontractReceiveLibBaseE2isMessageLibBase, ERC165, IMessageLib{
usingPacketV1Codecforbytes;
constructor(address _endpoint) MessageLibBase(_endpoint, ILayerZeroEndpointV2(_endpoint).eid()) {}
functionsupportsInterface(bytes4 _interfaceId) publicviewvirtualoverride(ERC165, IERC165) returns (bool) {
return _interfaceId ==type(IMessageLib).interfaceId||super.supportsInterface(_interfaceId);
}
functionmessageLibType() externalpurevirtualoverridereturns (MessageLibType) {
return MessageLibType.Receive;
}
}
Contract Source Code
File 17 of 19: ReceiveUln302.sol
// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;import { PacketV1Codec } from"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
import { SetConfigParam } from"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol";
import { ILayerZeroEndpointV2, Origin } from"@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import { IReceiveUlnE2 } from"../interfaces/IReceiveUlnE2.sol";
import { ReceiveUlnBase } from"../ReceiveUlnBase.sol";
import { ReceiveLibBaseE2 } from"../../ReceiveLibBaseE2.sol";
import { UlnConfig } from"../UlnBase.sol";
/// @dev This is a gluing contract. It simply parses the requests and forward to the super.impl() accordingly./// @dev In this case, it combines the logic of ReceiveUlnBase and ReceiveLibBaseE2contractReceiveUln302isIReceiveUlnE2, ReceiveUlnBase, ReceiveLibBaseE2{
usingPacketV1Codecforbytes;
/// @dev CONFIG_TYPE_ULN=2 here to align with SendUln302/ReceiveUln302/ReceiveUln301uint32internalconstant CONFIG_TYPE_ULN =2;
errorLZ_ULN_InvalidConfigType(uint32 configType);
constructor(address _endpoint) ReceiveLibBaseE2(_endpoint) {}
functionsupportsInterface(bytes4 _interfaceId) publicviewoverridereturns (bool) {
return _interfaceId ==type(IReceiveUlnE2).interfaceId||super.supportsInterface(_interfaceId);
}
// ============================ OnlyEndpoint ===================================// only the ULN config on the receive sidefunctionsetConfig(address _oapp, SetConfigParam[] calldata _params) externaloverrideonlyEndpoint{
for (uint256 i =0; i < _params.length; i++) {
SetConfigParam calldata param = _params[i];
_assertSupportedEid(param.eid);
if (param.configType == CONFIG_TYPE_ULN) {
_setUlnConfig(param.eid, _oapp, abi.decode(param.config, (UlnConfig)));
} else {
revert LZ_ULN_InvalidConfigType(param.configType);
}
}
}
// ============================ External ===================================/// @dev dont need to check endpoint verifiable here to save gas, as it will reverts if not verifiable.functioncommitVerification(bytescalldata _packetHeader, bytes32 _payloadHash) external{
_assertHeader(_packetHeader, localEid);
// cache these values to save gasaddress receiver = _packetHeader.receiverB20();
uint32 srcEid = _packetHeader.srcEid();
UlnConfig memory config = getUlnConfig(receiver, srcEid);
_verifyAndReclaimStorage(config, keccak256(_packetHeader), _payloadHash);
Origin memory origin = Origin(srcEid, _packetHeader.sender(), _packetHeader.nonce());
// endpoint will revert if nonce <= lazyInboundNonce
ILayerZeroEndpointV2(endpoint).verify(origin, receiver, _payloadHash);
}
/// @dev for dvn to verify the payloadfunctionverify(bytescalldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external{
_verify(_packetHeader, _payloadHash, _confirmations);
}
// ============================ View ===================================functiongetConfig(uint32 _eid, address _oapp, uint32 _configType) externalviewoverridereturns (bytesmemory) {
if (_configType == CONFIG_TYPE_ULN) {
returnabi.encode(getUlnConfig(_oapp, _eid));
} else {
revert LZ_ULN_InvalidConfigType(_configType);
}
}
functionisSupportedEid(uint32 _eid) externalviewoverridereturns (bool) {
return _isSupportedEid(_eid);
}
functionversion() externalpureoverridereturns (uint64 major, uint8 minor, uint8 endpointVersion) {
return (3, 0, 2);
}
}
Contract Source Code
File 18 of 19: ReceiveUlnBase.sol
// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;import { PacketV1Codec } from"@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
import { UlnBase, UlnConfig } from"./UlnBase.sol";
structVerification {
bool submitted;
uint64 confirmations;
}
/// @dev includes the utility functions for checking ULN states and logicsabstractcontractReceiveUlnBaseisUlnBase{
usingPacketV1Codecforbytes;
mapping(bytes32 headerHash =>mapping(bytes32 payloadHash =>mapping(address dvn => Verification)))
public hashLookup;
eventPayloadVerified(address dvn, bytes header, uint256 confirmations, bytes32 proofHash);
errorLZ_ULN_InvalidPacketHeader();
errorLZ_ULN_InvalidPacketVersion();
errorLZ_ULN_InvalidEid();
errorLZ_ULN_Verifying();
// ============================ External ===================================functionverifiable(
UlnConfig memory _config,
bytes32 _headerHash,
bytes32 _payloadHash
) externalviewreturns (bool) {
return _checkVerifiable(_config, _headerHash, _payloadHash);
}
functionassertHeader(bytescalldata _packetHeader, uint32 _localEid) externalpure{
_assertHeader(_packetHeader, _localEid);
}
// ============================ Internal ===================================/// @dev per DVN signing functionfunction_verify(bytescalldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) internal{
hashLookup[keccak256(_packetHeader)][_payloadHash][msg.sender] = Verification(true, _confirmations);
emit PayloadVerified(msg.sender, _packetHeader, _confirmations, _payloadHash);
}
function_verified(address _dvn,
bytes32 _headerHash,
bytes32 _payloadHash,
uint64 _requiredConfirmation
) internalviewreturns (bool verified) {
Verification memory verification = hashLookup[_headerHash][_payloadHash][_dvn];
// return true if the dvn has signed enough confirmations
verified = verification.submitted && verification.confirmations >= _requiredConfirmation;
}
function_verifyAndReclaimStorage(UlnConfig memory _config, bytes32 _headerHash, bytes32 _payloadHash) internal{
if (!_checkVerifiable(_config, _headerHash, _payloadHash)) {
revert LZ_ULN_Verifying();
}
// iterate the required DVNsif (_config.requiredDVNCount >0) {
for (uint8 i =0; i < _config.requiredDVNCount; ++i) {
delete hashLookup[_headerHash][_payloadHash][_config.requiredDVNs[i]];
}
}
// iterate the optional DVNsif (_config.optionalDVNCount >0) {
for (uint8 i =0; i < _config.optionalDVNCount; ++i) {
delete hashLookup[_headerHash][_payloadHash][_config.optionalDVNs[i]];
}
}
}
function_assertHeader(bytescalldata _packetHeader, uint32 _localEid) internalpure{
// assert packet header is of right size 81if (_packetHeader.length!=81) revert LZ_ULN_InvalidPacketHeader();
// assert packet header version is the same as ULNif (_packetHeader.version() != PacketV1Codec.PACKET_VERSION) revert LZ_ULN_InvalidPacketVersion();
// assert the packet is for this endpointif (_packetHeader.dstEid() != _localEid) revert LZ_ULN_InvalidEid();
}
/// @dev for verifiable view function/// @dev checks if this verification is ready to be committed to the endpointfunction_checkVerifiable(
UlnConfig memory _config,
bytes32 _headerHash,
bytes32 _payloadHash
) internalviewreturns (bool) {
// iterate the required DVNsif (_config.requiredDVNCount >0) {
for (uint8 i =0; i < _config.requiredDVNCount; ++i) {
if (!_verified(_config.requiredDVNs[i], _headerHash, _payloadHash, _config.confirmations)) {
// return if any of the required DVNs haven't signedreturnfalse;
}
}
if (_config.optionalDVNCount ==0) {
// returns early if all required DVNs have signed and there are no optional DVNsreturntrue;
}
}
// then it must require optional validationsuint8 threshold = _config.optionalDVNThreshold;
for (uint8 i =0; i < _config.optionalDVNCount; ++i) {
if (_verified(_config.optionalDVNs[i], _headerHash, _payloadHash, _config.confirmations)) {
// increment the optional count if the optional DVN has signed
threshold--;
if (threshold ==0) {
// early return if the optional threshold has hitreturntrue;
}
}
}
// return false as a catch-allreturnfalse;
}
}
Contract Source Code
File 19 of 19: UlnBase.sol
// SPDX-License-Identifier: LZBL-1.2pragmasolidity ^0.8.20;import { Ownable } from"@openzeppelin/contracts/access/Ownable.sol";
// the formal properties are documented in the setter functionsstructUlnConfig {
uint64 confirmations;
// we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gasuint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)uint8 optionalDVNThreshold; // (0, optionalDVNCount]address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNsaddress[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs
}
structSetDefaultUlnConfigParam {
uint32 eid;
UlnConfig config;
}
/// @dev includes the utility functions for checking ULN states and logicsabstractcontractUlnBaseisOwnable{
addressprivateconstant DEFAULT_CONFIG =address(0);
// reserved values foruint8internalconstant DEFAULT =0;
uint8internalconstant NIL_DVN_COUNT =type(uint8).max;
uint64internalconstant NIL_CONFIRMATIONS =type(uint64).max;
// 127 to prevent total number of DVNs (127 * 2) exceeding uint8.max (255)// by limiting the total size, it would help constraint the design of DVNOptionsuint8privateconstant MAX_COUNT = (type(uint8).max-1) /2;
mapping(address oapp =>mapping(uint32 eid => UlnConfig)) internal ulnConfigs;
errorLZ_ULN_Unsorted();
errorLZ_ULN_InvalidRequiredDVNCount();
errorLZ_ULN_InvalidOptionalDVNCount();
errorLZ_ULN_AtLeastOneDVN();
errorLZ_ULN_InvalidOptionalDVNThreshold();
errorLZ_ULN_InvalidConfirmations();
errorLZ_ULN_UnsupportedEid(uint32 eid);
eventDefaultUlnConfigsSet(SetDefaultUlnConfigParam[] params);
eventUlnConfigSet(address oapp, uint32 eid, UlnConfig config);
// ============================ OnlyOwner ===================================/// @dev about the DEFAULT ULN config/// 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/// this design enables the oapp to point to DEFAULT config without explicitly setting the config/// 2) its configuration is more restrictive than the oapp ULN config that/// a) it must not use NIL value, where NIL is used only by oapps to indicate the LITERAL 0/// b) it must have at least one DVNfunctionsetDefaultUlnConfigs(SetDefaultUlnConfigParam[] calldata _params) externalonlyOwner{
for (uint256 i =0; i < _params.length; ++i) {
SetDefaultUlnConfigParam calldata param = _params[i];
// 2.a must not use NILif (param.config.requiredDVNCount == NIL_DVN_COUNT) revert LZ_ULN_InvalidRequiredDVNCount();
if (param.config.optionalDVNCount == NIL_DVN_COUNT) revert LZ_ULN_InvalidOptionalDVNCount();
if (param.config.confirmations == NIL_CONFIRMATIONS) revert LZ_ULN_InvalidConfirmations();
// 2.b must have at least one dvn
_assertAtLeastOneDVN(param.config);
_setConfig(DEFAULT_CONFIG, param.eid, param.config);
}
emit DefaultUlnConfigsSet(_params);
}
// ============================ View ===================================// @dev assuming most oapps use default, we get default as memory and custom as storage to save gasfunctiongetUlnConfig(address _oapp, uint32 _remoteEid) publicviewreturns (UlnConfig memory rtnConfig) {
UlnConfig storage defaultConfig = ulnConfigs[DEFAULT_CONFIG][_remoteEid];
UlnConfig storage customConfig = ulnConfigs[_oapp][_remoteEid];
// if confirmations is 0, use defaultuint64 confirmations = customConfig.confirmations;
if (confirmations == DEFAULT) {
rtnConfig.confirmations = defaultConfig.confirmations;
} elseif (confirmations != NIL_CONFIRMATIONS) {
// if confirmations is uint64.max, no block confirmations required
rtnConfig.confirmations = confirmations;
} // else do nothing, rtnConfig.confirmation is 0if (customConfig.requiredDVNCount == DEFAULT) {
if (defaultConfig.requiredDVNCount >0) {
// copy only if count > 0. save gas
rtnConfig.requiredDVNs = defaultConfig.requiredDVNs;
rtnConfig.requiredDVNCount = defaultConfig.requiredDVNCount;
} // else, do nothing
} else {
if (customConfig.requiredDVNCount != NIL_DVN_COUNT) {
rtnConfig.requiredDVNs = customConfig.requiredDVNs;
rtnConfig.requiredDVNCount = customConfig.requiredDVNCount;
} // else, do nothing
}
if (customConfig.optionalDVNCount == DEFAULT) {
if (defaultConfig.optionalDVNCount >0) {
// copy only if count > 0. save gas
rtnConfig.optionalDVNs = defaultConfig.optionalDVNs;
rtnConfig.optionalDVNCount = defaultConfig.optionalDVNCount;
rtnConfig.optionalDVNThreshold = defaultConfig.optionalDVNThreshold;
}
} else {
if (customConfig.optionalDVNCount != NIL_DVN_COUNT) {
rtnConfig.optionalDVNs = customConfig.optionalDVNs;
rtnConfig.optionalDVNCount = customConfig.optionalDVNCount;
rtnConfig.optionalDVNThreshold = customConfig.optionalDVNThreshold;
}
}
// the final value must have at least one dvn// it is possible that some default config result into 0 dvns
_assertAtLeastOneDVN(rtnConfig);
}
/// @dev Get the uln config without the default config for the given remoteEid.functiongetAppUlnConfig(address _oapp, uint32 _remoteEid) externalviewreturns (UlnConfig memory) {
return ulnConfigs[_oapp][_remoteEid];
}
// ============================ Internal ===================================function_setUlnConfig(uint32 _remoteEid, address _oapp, UlnConfig memory _param) internal{
_setConfig(_oapp, _remoteEid, _param);
// get ULN config again as a catch all to ensure the config is valid
getUlnConfig(_oapp, _remoteEid);
emit UlnConfigSet(_oapp, _remoteEid, _param);
}
/// @dev a supported Eid must have a valid default uln config, which has at least one dvnfunction_isSupportedEid(uint32 _remoteEid) internalviewreturns (bool) {
UlnConfig storage defaultConfig = ulnConfigs[DEFAULT_CONFIG][_remoteEid];
return defaultConfig.requiredDVNCount >0|| defaultConfig.optionalDVNThreshold >0;
}
function_assertSupportedEid(uint32 _remoteEid) internalview{
if (!_isSupportedEid(_remoteEid)) revert LZ_ULN_UnsupportedEid(_remoteEid);
}
// ============================ Private ===================================function_assertAtLeastOneDVN(UlnConfig memory _config) privatepure{
if (_config.requiredDVNCount ==0&& _config.optionalDVNThreshold ==0) revert LZ_ULN_AtLeastOneDVN();
}
/// @dev this private function is used in both setDefaultUlnConfigs and setUlnConfigfunction_setConfig(address _oapp, uint32 _eid, UlnConfig memory _param) private{
// @dev required dvns// if dvnCount == NONE, dvns list must be empty// if dvnCount == DEFAULT, dvn list must be empty// otherwise, dvnList.length == dvnCount and assert the list is validif (_param.requiredDVNCount == NIL_DVN_COUNT || _param.requiredDVNCount == DEFAULT) {
if (_param.requiredDVNs.length!=0) revert LZ_ULN_InvalidRequiredDVNCount();
} else {
if (_param.requiredDVNs.length!= _param.requiredDVNCount || _param.requiredDVNCount > MAX_COUNT)
revert LZ_ULN_InvalidRequiredDVNCount();
_assertNoDuplicates(_param.requiredDVNs);
}
// @dev optional dvns// if optionalDVNCount == NONE, optionalDVNs list must be empty and threshold must be 0// if optionalDVNCount == DEFAULT, optionalDVNs list must be empty and threshold must be 0// otherwise, optionalDVNs.length == optionalDVNCount, threshold > 0 && threshold <= optionalDVNCount and assert the list is valid// example use case: an oapp uses the DEFAULT 'required' but// a) use a custom 1/1 dvn (practically a required dvn), or// b) use a custom 2/3 dvnif (_param.optionalDVNCount == NIL_DVN_COUNT || _param.optionalDVNCount == DEFAULT) {
if (_param.optionalDVNs.length!=0) revert LZ_ULN_InvalidOptionalDVNCount();
if (_param.optionalDVNThreshold !=0) revert LZ_ULN_InvalidOptionalDVNThreshold();
} else {
if (_param.optionalDVNs.length!= _param.optionalDVNCount || _param.optionalDVNCount > MAX_COUNT)
revert LZ_ULN_InvalidOptionalDVNCount();
if (_param.optionalDVNThreshold ==0|| _param.optionalDVNThreshold > _param.optionalDVNCount)
revert LZ_ULN_InvalidOptionalDVNThreshold();
_assertNoDuplicates(_param.optionalDVNs);
}
// don't assert valid count here, as it needs to be validated along side default config
ulnConfigs[_oapp][_eid] = _param;
}
function_assertNoDuplicates(address[] memory _dvns) privatepure{
address lastDVN =address(0);
for (uint256 i =0; i < _dvns.length; i++) {
address dvn = _dvns[i];
if (dvn <= lastDVN) revert LZ_ULN_Unsorted(); // to ensure no duplicates
lastDVN = dvn;
}
}
}