@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,176 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
6
+
7
+ import { ExecutorOptions } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/ExecutorOptions.sol";
8
+
9
+ import { DVNOptions } from "./DVNOptions.sol";
10
+
11
+ library UlnOptions {
12
+ using SafeCast for uint256;
13
+
14
+ uint16 internal constant TYPE_1 = 1; // legacy options type 1
15
+ uint16 internal constant TYPE_2 = 2; // legacy options type 2
16
+ uint16 internal constant TYPE_3 = 3;
17
+
18
+ error LZ_ULN_InvalidWorkerOptions(uint256 cursor);
19
+ error LZ_ULN_InvalidWorkerId(uint8 workerId);
20
+ error LZ_ULN_InvalidLegacyType1Option();
21
+ error LZ_ULN_InvalidLegacyType2Option();
22
+ error LZ_ULN_UnsupportedOptionType(uint16 optionType);
23
+
24
+ /// @dev decode the options into executorOptions and dvnOptions
25
+ /// @param _options the options can be either legacy options (type 1 or 2) or type 3 options
26
+ /// @return executorOptions the executor options, share the same format of type 3 options
27
+ /// @return dvnOptions the dvn options, share the same format of type 3 options
28
+ function decode(
29
+ bytes calldata _options
30
+ ) internal pure returns (bytes memory executorOptions, bytes memory dvnOptions) {
31
+ // at least 2 bytes for the option type, but can have no options
32
+ if (_options.length < 2) revert LZ_ULN_InvalidWorkerOptions(0);
33
+
34
+ uint16 optionsType = uint16(bytes2(_options[0:2]));
35
+ uint256 cursor = 2;
36
+
37
+ // type3 options: [worker_option][worker_option]...
38
+ // worker_option: [worker_id][option_size][option]
39
+ // worker_id: uint8, option_size: uint16, option: bytes
40
+ if (optionsType == TYPE_3) {
41
+ unchecked {
42
+ uint256 start = cursor;
43
+ uint8 lastWorkerId; // worker_id starts from 1, so 0 is an invalid worker_id
44
+
45
+ // heuristic: we assume that the options are mostly EXECUTOR options only
46
+ // checking the workerID can reduce gas usage for most cases
47
+ while (cursor < _options.length) {
48
+ uint8 workerId = uint8(bytes1(_options[cursor:cursor + 1]));
49
+ if (workerId == 0) revert LZ_ULN_InvalidWorkerId(0);
50
+
51
+ // workerId must equal to the lastWorkerId for the first option
52
+ // so it is always skipped in the first option
53
+ // this operation slices out options whenever the the scan finds a different workerId
54
+ if (lastWorkerId == 0) {
55
+ lastWorkerId = workerId;
56
+ } else if (workerId != lastWorkerId) {
57
+ bytes calldata op = _options[start:cursor]; // slice out the last worker's options
58
+ (executorOptions, dvnOptions) = _insertWorkerOptions(
59
+ executorOptions,
60
+ dvnOptions,
61
+ lastWorkerId,
62
+ op
63
+ );
64
+
65
+ // reset the start cursor and lastWorkerId
66
+ start = cursor;
67
+ lastWorkerId = workerId;
68
+ }
69
+
70
+ ++cursor; // for workerId
71
+
72
+ uint16 size = uint16(bytes2(_options[cursor:cursor + 2]));
73
+ if (size == 0) revert LZ_ULN_InvalidWorkerOptions(cursor);
74
+ cursor += size + 2;
75
+ }
76
+
77
+ // the options length must be the same as the cursor at the end
78
+ if (cursor != _options.length) revert LZ_ULN_InvalidWorkerOptions(cursor);
79
+
80
+ // if we have reached the end of the options and the options are not empty
81
+ // we need to process the last worker's options
82
+ if (_options.length > 2) {
83
+ bytes calldata op = _options[start:cursor];
84
+ (executorOptions, dvnOptions) = _insertWorkerOptions(executorOptions, dvnOptions, lastWorkerId, op);
85
+ }
86
+ }
87
+ } else {
88
+ executorOptions = decodeLegacyOptions(optionsType, _options);
89
+ }
90
+ }
91
+
92
+ function _insertWorkerOptions(
93
+ bytes memory _executorOptions,
94
+ bytes memory _dvnOptions,
95
+ uint8 _workerId,
96
+ bytes calldata _newOptions
97
+ ) private pure returns (bytes memory, bytes memory) {
98
+ if (_workerId == ExecutorOptions.WORKER_ID) {
99
+ _executorOptions = _executorOptions.length == 0
100
+ ? _newOptions
101
+ : abi.encodePacked(_executorOptions, _newOptions);
102
+ } else if (_workerId == DVNOptions.WORKER_ID) {
103
+ _dvnOptions = _dvnOptions.length == 0 ? _newOptions : abi.encodePacked(_dvnOptions, _newOptions);
104
+ } else {
105
+ revert LZ_ULN_InvalidWorkerId(_workerId);
106
+ }
107
+ return (_executorOptions, _dvnOptions);
108
+ }
109
+
110
+ /// @dev decode the legacy options (type 1 or 2) into executorOptions
111
+ /// @param _optionType the legacy option type
112
+ /// @param _options the legacy options, which still has the option type in the first 2 bytes
113
+ /// @return executorOptions the executor options, share the same format of type 3 options
114
+ /// Data format:
115
+ /// legacy type 1: [extraGas]
116
+ /// legacy type 2: [extraGas][dstNativeAmt][dstNativeAddress]
117
+ /// extraGas: uint256, dstNativeAmt: uint256, dstNativeAddress: bytes
118
+ function decodeLegacyOptions(
119
+ uint16 _optionType,
120
+ bytes calldata _options
121
+ ) internal pure returns (bytes memory executorOptions) {
122
+ if (_optionType == TYPE_1) {
123
+ if (_options.length != 34) revert LZ_ULN_InvalidLegacyType1Option();
124
+
125
+ // execution gas
126
+ uint128 executionGas = uint256(bytes32(_options[2:2 + 32])).toUint128();
127
+
128
+ // dont use the encode function in the ExecutorOptions lib for saving gas by calling abi.encodePacked once
129
+ // the result is a lzReceive option: [executor_id][option_size][option_type][execution_gas]
130
+ // option_type: uint8, execution_gas: uint128
131
+ // option_size = len(option_type) + len(execution_gas) = 1 + 16 = 17
132
+ executorOptions = abi.encodePacked(
133
+ ExecutorOptions.WORKER_ID,
134
+ uint16(17), // 16 + 1, 16 for option_length, + 1 for option_type
135
+ ExecutorOptions.OPTION_TYPE_LZRECEIVE,
136
+ executionGas
137
+ );
138
+ } else if (_optionType == TYPE_2) {
139
+ // receiver size <= 32
140
+ if (_options.length <= 66 || _options.length > 98) revert LZ_ULN_InvalidLegacyType2Option();
141
+
142
+ // execution gas
143
+ uint128 executionGas = uint256(bytes32(_options[2:2 + 32])).toUint128();
144
+
145
+ // nativeDrop (amount + receiver)
146
+ uint128 amount = uint256(bytes32(_options[34:34 + 32])).toUint128(); // offset 2 + 32
147
+ bytes32 receiver;
148
+ unchecked {
149
+ uint256 receiverLen = _options.length - 66; // offset 2 + 32 + 32
150
+ receiver = bytes32(_options[66:]);
151
+ receiver = receiver >> (8 * (32 - receiverLen)); // padding 0 to the left
152
+ }
153
+
154
+ // dont use the encode function in the ExecutorOptions lib for saving gas by calling abi.encodePacked once
155
+ // the result has one lzReceive option and one nativeDrop option:
156
+ // [executor_id][lzReceive_option_size][option_type][execution_gas] +
157
+ // [executor_id][nativeDrop_option_size][option_type][nativeDrop_amount][receiver]
158
+ // option_type: uint8, execution_gas: uint128, nativeDrop_amount: uint128, receiver: bytes32
159
+ // lzReceive_option_size = len(option_type) + len(execution_gas) = 1 + 16 = 17
160
+ // nativeDrop_option_size = len(option_type) + len(nativeDrop_amount) + len(receiver) = 1 + 16 + 32 = 49
161
+ executorOptions = abi.encodePacked(
162
+ ExecutorOptions.WORKER_ID,
163
+ uint16(17), // 16 + 1, 16 for option_length, + 1 for option_type
164
+ ExecutorOptions.OPTION_TYPE_LZRECEIVE,
165
+ executionGas,
166
+ ExecutorOptions.WORKER_ID,
167
+ uint16(49), // 48 + 1, 32 + 16 for option_length, + 1 for option_type
168
+ ExecutorOptions.OPTION_TYPE_NATIVE_DROP,
169
+ amount,
170
+ receiver
171
+ );
172
+ } else {
173
+ revert LZ_ULN_UnsupportedOptionType(_optionType);
174
+ }
175
+ }
176
+ }
@@ -0,0 +1,518 @@
1
+ // SPDX-License-Identifier: LZBL-1.2
2
+
3
+ pragma solidity ^0.8.20;
4
+
5
+ import { ERC165, IERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
6
+
7
+ import { ILayerZeroEndpointV2, MessagingFee, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
8
+ import { IMessageLib, MessageLibType, SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol";
9
+ import { ISendLib, Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol";
10
+ import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol";
11
+ import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol";
12
+ import { AddressCast } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/AddressCast.sol";
13
+
14
+ import { ILayerZeroReadExecutor } from "../../interfaces/ILayerZeroReadExecutor.sol";
15
+ import { ILayerZeroReadDVN } from "../interfaces/ILayerZeroReadDVN.sol";
16
+ import { ILayerZeroTreasury } from "../../interfaces/ILayerZeroTreasury.sol";
17
+
18
+ import { UlnOptions } from "../libs/UlnOptions.sol";
19
+ import { DVNOptions } from "../libs/DVNOptions.sol";
20
+ import { SafeCall } from "../../libs/SafeCall.sol";
21
+
22
+ import { MessageLibBase } from "../../MessageLibBase.sol";
23
+ import { ReadLibBase, ReadLibConfig } from "./ReadLibBase.sol";
24
+
25
+ contract ReadLib1002 is ISendLib, ERC165, ReadLibBase, MessageLibBase {
26
+ using PacketV1Codec for bytes;
27
+ using SafeCall for address;
28
+
29
+ uint32 internal constant CONFIG_TYPE_READ_LID_CONFIG = 1;
30
+
31
+ uint16 internal constant TREASURY_MAX_COPY = 32;
32
+
33
+ uint256 internal immutable treasuryGasLimit;
34
+
35
+ mapping(address oapp => mapping(uint32 eid => mapping(uint64 nonce => bytes32 cmdHash))) public cmdHashLookup;
36
+ mapping(bytes32 headerHash => mapping(bytes32 cmdHash => mapping(address dvn => bytes32 payloadHash)))
37
+ public hashLookup;
38
+
39
+ // accumulated fees for workers and treasury
40
+ mapping(address worker => uint256 fee) public fees;
41
+ uint256 internal treasuryNativeFeeCap;
42
+ address internal treasury;
43
+
44
+ event PayloadVerified(address dvn, bytes header, bytes32 cmdHash, bytes32 payloadHash);
45
+ event ExecutorFeePaid(address executor, uint256 fee);
46
+ event DVNFeePaid(address[] requiredDVNs, address[] optionalDVNs, uint256[] fees);
47
+ event NativeFeeWithdrawn(address worker, address receiver, uint256 amount);
48
+ event LzTokenFeeWithdrawn(address lzToken, address receiver, uint256 amount);
49
+ event TreasurySet(address treasury);
50
+ event TreasuryNativeFeeCapSet(uint256 newTreasuryNativeFeeCap);
51
+
52
+ error LZ_RL_InvalidReceiver();
53
+ error LZ_RL_InvalidPacketHeader();
54
+ error LZ_RL_InvalidCmdHash();
55
+ error LZ_RL_InvalidPacketVersion();
56
+ error LZ_RL_InvalidEid();
57
+ error LZ_RL_Verifying();
58
+ error LZ_RL_InvalidConfigType(uint32 configType);
59
+ error LZ_RL_InvalidAmount(uint256 requested, uint256 available);
60
+ error LZ_RL_NotTreasury();
61
+ error LZ_RL_CannotWithdrawAltToken();
62
+
63
+ constructor(
64
+ address _endpoint,
65
+ uint256 _treasuryGasLimit,
66
+ uint256 _treasuryGasForFeeCap
67
+ ) MessageLibBase(_endpoint, ILayerZeroEndpointV2(_endpoint).eid()) {
68
+ treasuryGasLimit = _treasuryGasLimit;
69
+ treasuryNativeFeeCap = _treasuryGasForFeeCap;
70
+ }
71
+
72
+ function supportsInterface(bytes4 _interfaceId) public view override(ERC165, IERC165) returns (bool) {
73
+ return
74
+ _interfaceId == type(IMessageLib).interfaceId ||
75
+ _interfaceId == type(ISendLib).interfaceId ||
76
+ super.supportsInterface(_interfaceId);
77
+ }
78
+
79
+ // ============================ OnlyOwner ===================================
80
+
81
+ function setTreasury(address _treasury) external onlyOwner {
82
+ treasury = _treasury;
83
+ emit TreasurySet(_treasury);
84
+ }
85
+
86
+ /// @dev the new value can not be greater than the old value, i.e. down only
87
+ function setTreasuryNativeFeeCap(uint256 _newTreasuryNativeFeeCap) external onlyOwner {
88
+ // assert the new value is no greater than the old value
89
+ if (_newTreasuryNativeFeeCap > treasuryNativeFeeCap)
90
+ revert LZ_RL_InvalidAmount(_newTreasuryNativeFeeCap, treasuryNativeFeeCap);
91
+ treasuryNativeFeeCap = _newTreasuryNativeFeeCap;
92
+ emit TreasuryNativeFeeCapSet(_newTreasuryNativeFeeCap);
93
+ }
94
+
95
+ // ============================ OnlyEndpoint ===================================
96
+
97
+ function send(
98
+ Packet calldata _packet,
99
+ bytes calldata _options,
100
+ bool _payInLzToken
101
+ ) external onlyEndpoint returns (MessagingFee memory, bytes memory) {
102
+ // the receiver must be the same as the sender
103
+ if (AddressCast.toBytes32(_packet.sender) != _packet.receiver) revert LZ_RL_InvalidReceiver();
104
+
105
+ // pay worker and treasury
106
+ (bytes memory encodedPacket, uint256 totalNativeFee) = _payWorkers(_packet, _options);
107
+ (uint256 treasuryNativeFee, uint256 lzTokenFee) = _payTreasury(
108
+ _packet.sender,
109
+ _packet.dstEid,
110
+ totalNativeFee,
111
+ _payInLzToken
112
+ );
113
+ totalNativeFee += treasuryNativeFee;
114
+
115
+ // store the cmdHash for verification in order to prevent reorg attack
116
+ cmdHashLookup[_packet.sender][_packet.dstEid][_packet.nonce] = keccak256(_packet.message);
117
+
118
+ return (MessagingFee(totalNativeFee, lzTokenFee), encodedPacket);
119
+ }
120
+
121
+ function setConfig(address _oapp, SetConfigParam[] calldata _params) external onlyEndpoint {
122
+ for (uint256 i = 0; i < _params.length; i++) {
123
+ SetConfigParam calldata param = _params[i];
124
+ _assertSupportedEid(param.eid);
125
+ if (param.configType == CONFIG_TYPE_READ_LID_CONFIG) {
126
+ _setReadLibConfig(param.eid, _oapp, abi.decode(param.config, (ReadLibConfig)));
127
+ } else {
128
+ revert LZ_RL_InvalidConfigType(param.configType);
129
+ }
130
+ }
131
+ }
132
+
133
+ // ============================ External ===================================
134
+ /// @dev The verification will be done in the same chain where the packet is sent.
135
+ /// @dev dont need to check endpoint verifiable here to save gas, as it will reverts if not verifiable.
136
+ /// @param _packetHeader - the srcEid should be the localEid and the dstEid should be the channel id.
137
+ /// The original packet header in PacketSent event should be processed to flip the srcEid and dstEid.
138
+ function commitVerification(bytes calldata _packetHeader, bytes32 _cmdHash, bytes32 _payloadHash) external {
139
+ // assert packet header is of right size 81
140
+ if (_packetHeader.length != 81) revert LZ_RL_InvalidPacketHeader();
141
+ // assert packet header version is the same
142
+ if (_packetHeader.version() != PacketV1Codec.PACKET_VERSION) revert LZ_RL_InvalidPacketVersion();
143
+ // assert the packet is for this endpoint
144
+ if (_packetHeader.dstEid() != localEid) revert LZ_RL_InvalidEid();
145
+
146
+ // cache these values to save gas
147
+ address receiver = _packetHeader.receiverB20();
148
+ uint32 srcEid = _packetHeader.srcEid(); // channel id
149
+ uint64 nonce = _packetHeader.nonce();
150
+
151
+ // reorg protection. to allow reverification, the cmdHash cant be removed
152
+ if (cmdHashLookup[receiver][srcEid][nonce] != _cmdHash) revert LZ_RL_InvalidCmdHash();
153
+
154
+ ReadLibConfig memory config = getReadLibConfig(receiver, srcEid);
155
+ _verifyAndReclaimStorage(config, keccak256(_packetHeader), _cmdHash, _payloadHash);
156
+
157
+ // endpoint will revert if nonce <= lazyInboundNonce
158
+ Origin memory origin = Origin(srcEid, _packetHeader.sender(), nonce);
159
+ ILayerZeroEndpointV2(endpoint).verify(origin, receiver, _payloadHash);
160
+ }
161
+
162
+ /// @dev DVN verifies the payload with the packet header and command hash
163
+ /// @param _packetHeader - the packet header is needed for event only, which can be conveniently for off-chain to track the packet state.
164
+ function verify(bytes calldata _packetHeader, bytes32 _cmdHash, bytes32 _payloadHash) external {
165
+ hashLookup[keccak256(_packetHeader)][_cmdHash][msg.sender] = _payloadHash;
166
+ emit PayloadVerified(msg.sender, _packetHeader, _cmdHash, _payloadHash);
167
+ }
168
+
169
+ function withdrawFee(address _to, uint256 _amount) external {
170
+ uint256 fee = fees[msg.sender];
171
+ if (_amount > fee) revert LZ_RL_InvalidAmount(_amount, fee);
172
+ unchecked {
173
+ fees[msg.sender] = fee - _amount;
174
+ }
175
+
176
+ // transfers native if nativeToken == address(0x0)
177
+ address nativeToken = ILayerZeroEndpointV2(endpoint).nativeToken();
178
+ Transfer.nativeOrToken(nativeToken, _to, _amount);
179
+ emit NativeFeeWithdrawn(msg.sender, _to, _amount);
180
+ }
181
+
182
+ // ============================ Treasury ===================================
183
+
184
+ /// @dev _lzToken is a user-supplied value because lzToken might change in the endpoint before all lzToken can be taken out
185
+ function withdrawLzTokenFee(address _lzToken, address _to, uint256 _amount) external {
186
+ if (msg.sender != treasury) revert LZ_RL_NotTreasury();
187
+
188
+ // lz token cannot be the same as the native token
189
+ if (ILayerZeroEndpointV2(endpoint).nativeToken() == _lzToken) revert LZ_RL_CannotWithdrawAltToken();
190
+
191
+ Transfer.token(_lzToken, _to, _amount);
192
+
193
+ emit LzTokenFeeWithdrawn(_lzToken, _to, _amount);
194
+ }
195
+
196
+ // ============================ View ===================================
197
+
198
+ function quote(
199
+ Packet calldata _packet,
200
+ bytes calldata _options,
201
+ bool _payInLzToken
202
+ ) external view returns (MessagingFee memory) {
203
+ // split workers options
204
+ (bytes memory executorOptions, bytes memory dvnOptions) = UlnOptions.decode(_options);
205
+
206
+ address sender = _packet.sender;
207
+ uint32 dstEid = _packet.dstEid;
208
+
209
+ // quote the executor and dvns
210
+ ReadLibConfig memory config = getReadLibConfig(sender, dstEid);
211
+ uint256 nativeFee = _quoteDVNs(
212
+ config,
213
+ sender,
214
+ PacketV1Codec.encodePacketHeader(_packet),
215
+ _packet.message,
216
+ dvnOptions
217
+ );
218
+ nativeFee += ILayerZeroReadExecutor(config.executor).getFee(sender, executorOptions);
219
+
220
+ // quote treasury
221
+ (uint256 treasuryNativeFee, uint256 lzTokenFee) = _quoteTreasury(sender, dstEid, nativeFee, _payInLzToken);
222
+ nativeFee += treasuryNativeFee;
223
+
224
+ return MessagingFee(nativeFee, lzTokenFee);
225
+ }
226
+
227
+ function verifiable(
228
+ ReadLibConfig calldata _config,
229
+ bytes32 _headerHash,
230
+ bytes32 _cmdHash,
231
+ bytes32 _payloadHash
232
+ ) external view returns (bool) {
233
+ return _checkVerifiable(_config, _headerHash, _cmdHash, _payloadHash);
234
+ }
235
+
236
+ function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view returns (bytes memory) {
237
+ if (_configType == CONFIG_TYPE_READ_LID_CONFIG) {
238
+ return abi.encode(getReadLibConfig(_oapp, _eid));
239
+ } else {
240
+ revert LZ_RL_InvalidConfigType(_configType);
241
+ }
242
+ }
243
+
244
+ function getTreasuryAndNativeFeeCap() external view returns (address, uint256) {
245
+ return (treasury, treasuryNativeFeeCap);
246
+ }
247
+
248
+ function isSupportedEid(uint32 _eid) external view returns (bool) {
249
+ return _isSupportedEid(_eid);
250
+ }
251
+
252
+ function messageLibType() external pure returns (MessageLibType) {
253
+ return MessageLibType.SendAndReceive;
254
+ }
255
+
256
+ function version() external pure returns (uint64 major, uint8 minor, uint8 endpointVersion) {
257
+ return (10, 0, 2);
258
+ }
259
+
260
+ // ============================ Internal ===================================
261
+
262
+ /// 1/ handle executor
263
+ /// 2/ handle other workers
264
+ function _payWorkers(
265
+ Packet calldata _packet,
266
+ bytes calldata _options
267
+ ) internal returns (bytes memory encodedPacket, uint256 totalNativeFee) {
268
+ // split workers options
269
+ (bytes memory executorOptions, bytes memory dvnOptions) = UlnOptions.decode(_options);
270
+
271
+ // handle executor
272
+ ReadLibConfig memory config = getReadLibConfig(_packet.sender, _packet.dstEid);
273
+ totalNativeFee = _payExecutor(config.executor, _packet.sender, executorOptions);
274
+
275
+ // handle dvns
276
+ (uint256 dvnFee, bytes memory packetBytes) = _payDVNs(config, _packet, dvnOptions);
277
+ totalNativeFee += dvnFee;
278
+
279
+ encodedPacket = packetBytes;
280
+ }
281
+
282
+ function _payDVNs(
283
+ ReadLibConfig memory _config,
284
+ Packet calldata _packet,
285
+ bytes memory _options
286
+ ) internal returns (uint256 totalFee, bytes memory encodedPacket) {
287
+ bytes memory packetHeader = PacketV1Codec.encodePacketHeader(_packet);
288
+ bytes memory payload = PacketV1Codec.encodePayload(_packet);
289
+
290
+ uint256[] memory dvnFees;
291
+ (totalFee, dvnFees) = _assignDVNJobs(_config, _packet.sender, packetHeader, _packet.message, _options);
292
+
293
+ encodedPacket = abi.encodePacked(packetHeader, payload);
294
+ emit DVNFeePaid(_config.requiredDVNs, _config.optionalDVNs, dvnFees);
295
+ }
296
+
297
+ function _assignDVNJobs(
298
+ ReadLibConfig memory _config,
299
+ address _sender,
300
+ bytes memory _packetHeader,
301
+ bytes calldata _cmd,
302
+ bytes memory _options
303
+ ) internal returns (uint256 totalFee, uint256[] memory dvnFees) {
304
+ (bytes[] memory optionsArray, uint8[] memory dvnIds) = DVNOptions.groupDVNOptionsByIdx(_options);
305
+
306
+ uint8 dvnsLength = _config.requiredDVNCount + _config.optionalDVNCount;
307
+ dvnFees = new uint256[](dvnsLength);
308
+ for (uint8 i = 0; i < dvnsLength; ++i) {
309
+ address dvn = i < _config.requiredDVNCount
310
+ ? _config.requiredDVNs[i]
311
+ : _config.optionalDVNs[i - _config.requiredDVNCount];
312
+
313
+ bytes memory options = "";
314
+ for (uint256 j = 0; j < dvnIds.length; ++j) {
315
+ if (dvnIds[j] == i) {
316
+ options = optionsArray[j];
317
+ break;
318
+ }
319
+ }
320
+
321
+ dvnFees[i] = ILayerZeroReadDVN(dvn).assignJob(_sender, _packetHeader, _cmd, options);
322
+ if (dvnFees[i] > 0) {
323
+ fees[dvn] += dvnFees[i];
324
+ totalFee += dvnFees[i];
325
+ }
326
+ }
327
+ }
328
+
329
+ function _quoteDVNs(
330
+ ReadLibConfig memory _config,
331
+ address _sender,
332
+ bytes memory _packetHeader,
333
+ bytes calldata _cmd,
334
+ bytes memory _options
335
+ ) internal view returns (uint256 totalFee) {
336
+ (bytes[] memory optionsArray, uint8[] memory dvnIndices) = DVNOptions.groupDVNOptionsByIdx(_options);
337
+
338
+ // here we merge 2 list of dvns into 1 to allocate the indexed dvn options to the right dvn
339
+ uint8 dvnsLength = _config.requiredDVNCount + _config.optionalDVNCount;
340
+ for (uint8 i = 0; i < dvnsLength; ++i) {
341
+ address dvn = i < _config.requiredDVNCount
342
+ ? _config.requiredDVNs[i]
343
+ : _config.optionalDVNs[i - _config.requiredDVNCount];
344
+
345
+ bytes memory options = "";
346
+ // it is a double loop here. however, if the list is short, the cost is very acceptable.
347
+ for (uint256 j = 0; j < dvnIndices.length; ++j) {
348
+ if (dvnIndices[j] == i) {
349
+ options = optionsArray[j];
350
+ break;
351
+ }
352
+ }
353
+ totalFee += ILayerZeroReadDVN(dvn).getFee(_sender, _packetHeader, _cmd, options);
354
+ }
355
+ }
356
+
357
+ function _payTreasury(
358
+ address _sender,
359
+ uint32 _dstEid,
360
+ uint256 _totalNativeFee,
361
+ bool _payInLzToken
362
+ ) internal returns (uint256 treasuryNativeFee, uint256 lzTokenFee) {
363
+ if (treasury != address(0x0)) {
364
+ bytes memory callData = abi.encodeCall(
365
+ ILayerZeroTreasury.payFee,
366
+ (_sender, _dstEid, _totalNativeFee, _payInLzToken)
367
+ );
368
+ (bool success, bytes memory result) = treasury.safeCall(treasuryGasLimit, 0, TREASURY_MAX_COPY, callData);
369
+
370
+ (treasuryNativeFee, lzTokenFee) = _parseTreasuryResult(_totalNativeFee, _payInLzToken, success, result);
371
+ // fee should be in lzTokenFee if payInLzToken, otherwise in native
372
+ if (treasuryNativeFee > 0) {
373
+ fees[treasury] += treasuryNativeFee;
374
+ }
375
+ }
376
+ }
377
+
378
+ /// @dev this interface should be DoS-free if the user is paying with native. properties
379
+ /// 1/ treasury can return an overly high lzToken fee
380
+ /// 2/ if treasury returns an overly high native fee, it will be capped by maxNativeFee,
381
+ /// which can be reasoned with the configurations
382
+ /// 3/ the owner can not configure the treasury in a way that force this function to revert
383
+ function _quoteTreasury(
384
+ address _sender,
385
+ uint32 _dstEid,
386
+ uint256 _totalNativeFee,
387
+ bool _payInLzToken
388
+ ) internal view returns (uint256 nativeFee, uint256 lzTokenFee) {
389
+ // treasury must be set, and it has to be a contract
390
+ if (treasury != address(0x0)) {
391
+ bytes memory callData = abi.encodeCall(
392
+ ILayerZeroTreasury.getFee,
393
+ (_sender, _dstEid, _totalNativeFee, _payInLzToken)
394
+ );
395
+ (bool success, bytes memory result) = treasury.safeStaticCall(
396
+ treasuryGasLimit,
397
+ TREASURY_MAX_COPY,
398
+ callData
399
+ );
400
+
401
+ return _parseTreasuryResult(_totalNativeFee, _payInLzToken, success, result);
402
+ }
403
+ }
404
+
405
+ function _parseTreasuryResult(
406
+ uint256 _totalNativeFee,
407
+ bool _payInLzToken,
408
+ bool _success,
409
+ bytes memory _result
410
+ ) internal view returns (uint256 nativeFee, uint256 lzTokenFee) {
411
+ // failure, charges nothing
412
+ if (!_success || _result.length < TREASURY_MAX_COPY) return (0, 0);
413
+
414
+ // parse the result
415
+ uint256 treasureFeeQuote = abi.decode(_result, (uint256));
416
+ if (_payInLzToken) {
417
+ lzTokenFee = treasureFeeQuote;
418
+ } else {
419
+ // pay in native
420
+ // we must prevent high-treasuryFee Dos attack
421
+ // nativeFee = min(treasureFeeQuote, maxNativeFee)
422
+ // opportunistically raise the maxNativeFee to be the same as _totalNativeFee
423
+ // can't use the _totalNativeFee alone because the oapp can use custom workers to force the fee to 0.
424
+ // maxNativeFee = max (_totalNativeFee, treasuryNativeFeeCap)
425
+ uint256 maxNativeFee = _totalNativeFee > treasuryNativeFeeCap ? _totalNativeFee : treasuryNativeFeeCap;
426
+
427
+ // min (treasureFeeQuote, nativeFeeCap)
428
+ nativeFee = treasureFeeQuote > maxNativeFee ? maxNativeFee : treasureFeeQuote;
429
+ }
430
+ }
431
+
432
+ function _verifyAndReclaimStorage(
433
+ ReadLibConfig memory _config,
434
+ bytes32 _headerHash,
435
+ bytes32 _cmdHash,
436
+ bytes32 _payloadHash
437
+ ) internal {
438
+ if (!_checkVerifiable(_config, _headerHash, _cmdHash, _payloadHash)) {
439
+ revert LZ_RL_Verifying();
440
+ }
441
+
442
+ // iterate the required DVNs
443
+ if (_config.requiredDVNCount > 0) {
444
+ for (uint8 i = 0; i < _config.requiredDVNCount; ++i) {
445
+ delete hashLookup[_headerHash][_cmdHash][_config.requiredDVNs[i]];
446
+ }
447
+ }
448
+
449
+ // iterate the optional DVNs
450
+ if (_config.optionalDVNCount > 0) {
451
+ for (uint8 i = 0; i < _config.optionalDVNCount; ++i) {
452
+ delete hashLookup[_headerHash][_cmdHash][_config.optionalDVNs[i]];
453
+ }
454
+ }
455
+ }
456
+
457
+ /// @dev for verifiable view function
458
+ /// @dev checks if this verification is ready to be committed to the endpoint
459
+ function _checkVerifiable(
460
+ ReadLibConfig memory _config,
461
+ bytes32 _headerHash,
462
+ bytes32 _cmdHash,
463
+ bytes32 _payloadHash
464
+ ) internal view returns (bool) {
465
+ // iterate the required DVNs
466
+ if (_config.requiredDVNCount > 0) {
467
+ for (uint8 i = 0; i < _config.requiredDVNCount; ++i) {
468
+ if (!_verified(_config.requiredDVNs[i], _headerHash, _cmdHash, _payloadHash)) {
469
+ // return if any of the required DVNs haven't signed
470
+ return false;
471
+ }
472
+ }
473
+ if (_config.optionalDVNCount == 0) {
474
+ // returns early if all required DVNs have signed and there are no optional DVNs
475
+ return true;
476
+ }
477
+ }
478
+
479
+ // then it must require optional validations
480
+ uint8 threshold = _config.optionalDVNThreshold;
481
+ for (uint8 i = 0; i < _config.optionalDVNCount; ++i) {
482
+ if (_verified(_config.optionalDVNs[i], _headerHash, _cmdHash, _payloadHash)) {
483
+ // increment the optional count if the optional DVN has signed
484
+ threshold--;
485
+ if (threshold == 0) {
486
+ // early return if the optional threshold has hit
487
+ return true;
488
+ }
489
+ }
490
+ }
491
+
492
+ // return false as a catch-all
493
+ return false;
494
+ }
495
+
496
+ function _verified(
497
+ address _dvn,
498
+ bytes32 _headerHash,
499
+ bytes32 _cmdHash,
500
+ bytes32 _expectedPayloadHash
501
+ ) internal view returns (bool verified) {
502
+ verified = hashLookup[_headerHash][_cmdHash][_dvn] == _expectedPayloadHash;
503
+ }
504
+
505
+ function _payExecutor(
506
+ address _executor,
507
+ address _sender,
508
+ bytes memory _executorOptions
509
+ ) internal returns (uint256 executorFee) {
510
+ executorFee = ILayerZeroReadExecutor(_executor).assignJob(_sender, _executorOptions);
511
+ if (executorFee > 0) {
512
+ fees[_executor] += executorFee;
513
+ }
514
+ emit ExecutorFeePaid(_executor, executorFee);
515
+ }
516
+
517
+ receive() external payable {}
518
+ }