@layerzerolabs/oapp-upgradeable-evm-contracts 0.2.74

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.
@@ -0,0 +1,135 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppReceiver, Origin } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppReceiver.sol";
5
+ import { OAppCoreBaseUpgradeable } from "./OAppCoreBaseUpgradeable.sol";
6
+
7
+ /**
8
+ * @title OAppReceiverUpgradeable
9
+ * @author LayerZero Labs
10
+ * @custom:version 1.0.0
11
+ * @notice Abstract contract implementing the ILayerZeroReceiver interface and extending OAppCoreBase for OApp receivers.
12
+ */
13
+ abstract contract OAppReceiverUpgradeable is IOAppReceiver, OAppCoreBaseUpgradeable {
14
+ // Custom error message for when the caller is not the registered endpoint/
15
+ error OnlyEndpoint(address addr);
16
+
17
+ // @dev The version of the OAppReceiver implementation.
18
+ // @dev Version is bumped when changes are made to this contract.
19
+ uint64 internal constant RECEIVER_VERSION = 2;
20
+
21
+ /**
22
+ * @notice Initializes the contract.
23
+ * @dev This function is empty on purpose, as no custom logic is needed.
24
+ */
25
+ function __OAppReceiver_init() internal onlyInitializing {}
26
+
27
+ /**
28
+ * @notice Unchained initialization function for the contract.
29
+ * @dev This function is empty on purpose, as no custom logic is needed.
30
+ */
31
+ function __OAppReceiver_init_unchained() internal onlyInitializing {}
32
+
33
+ /**
34
+ * @notice Retrieves the OApp version information.
35
+ * @return senderVersion The version of the OAppSender.sol contract.
36
+ * @return receiverVersion The version of the OAppReceiver.sol contract.
37
+ *
38
+ * @dev Providing 0 as the default for OAppSender version. Indicates that the OAppSender is not implemented.
39
+ * ie. this is a RECEIVE only OApp.
40
+ * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions.
41
+ */
42
+ function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
43
+ return (0, RECEIVER_VERSION);
44
+ }
45
+
46
+ /**
47
+ * @notice Indicates whether an address is an approved composeMsg sender to the Endpoint.
48
+ * @dev _origin The origin information containing the source endpoint and sender address.
49
+ * - srcEid: The source chain endpoint ID.
50
+ * - sender: The sender address on the src chain.
51
+ * - nonce: The nonce of the message.
52
+ * @dev _message The lzReceive payload.
53
+ * @param _sender The sender address.
54
+ * @return isSender Is a valid sender.
55
+ *
56
+ * @dev Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.
57
+ * @dev The default sender IS the OAppReceiver implementer.
58
+ */
59
+ function isComposeMsgSender(
60
+ Origin calldata /*_origin*/,
61
+ bytes calldata /*_message*/,
62
+ address _sender
63
+ ) public view virtual returns (bool isSender) {
64
+ return _sender == address(this);
65
+ }
66
+
67
+ /**
68
+ * @notice Checks if the path initialization is allowed based on the provided origin.
69
+ * @param origin The origin information containing the source endpoint and sender address.
70
+ * @return isAllowed Whether the path has been initialized.
71
+ *
72
+ * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
73
+ * @dev This defaults to assuming if a peer has been set, its initialized.
74
+ * Can be overridden by the OApp if there is other logic to determine this.
75
+ */
76
+ function allowInitializePath(Origin calldata origin) public view virtual returns (bool isAllowed) {
77
+ return peers(origin.srcEid) == origin.sender;
78
+ }
79
+
80
+ /**
81
+ * @notice Retrieves the next nonce for a given source endpoint and sender address.
82
+ * @dev _srcEid The source endpoint ID.
83
+ * @dev _sender The sender address.
84
+ * @return nonce The next nonce.
85
+ *
86
+ * @dev The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
87
+ * @dev Is required by the off-chain executor to determine the OApp expects msg execution is ordered.
88
+ * @dev This is also enforced by the OApp.
89
+ * @dev By default this is NOT enabled. ie. nextNonce is hardcoded to return 0.
90
+ */
91
+ function nextNonce(uint32, /*_srcEid*/ bytes32 /*_sender*/) public view virtual returns (uint64 nonce) {
92
+ return 0;
93
+ }
94
+
95
+ /**
96
+ * @dev Entry point for receiving messages or packets from the endpoint.
97
+ * @param _origin The origin information containing the source endpoint and sender address.
98
+ * - srcEid: The source chain endpoint ID.
99
+ * - sender: The sender address on the src chain.
100
+ * - nonce: The nonce of the message.
101
+ * @param _guid The unique identifier for the received LayerZero message.
102
+ * @param _message The payload of the received message.
103
+ * @param _executor The address of the executor for the received message.
104
+ * @param _extraData Additional arbitrary data provided by the corresponding executor.
105
+ *
106
+ * @dev Entry point for receiving msg/packet from the LayerZero endpoint.
107
+ */
108
+ function lzReceive(
109
+ Origin calldata _origin,
110
+ bytes32 _guid,
111
+ bytes calldata _message,
112
+ address _executor,
113
+ bytes calldata _extraData
114
+ ) public payable virtual {
115
+ // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
116
+ if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);
117
+
118
+ // Ensure that the sender matches the expected peer for the source endpoint.
119
+ if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);
120
+
121
+ // Call the internal OApp implementation of lzReceive.
122
+ _lzReceive(_origin, _guid, _message, _executor, _extraData);
123
+ }
124
+
125
+ /**
126
+ * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
127
+ */
128
+ function _lzReceive(
129
+ Origin calldata _origin,
130
+ bytes32 _guid,
131
+ bytes calldata _message,
132
+ address _executor,
133
+ bytes calldata _extraData
134
+ ) internal virtual;
135
+ }
@@ -0,0 +1,141 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import {
5
+ MessagingParams,
6
+ MessagingFee,
7
+ MessagingReceipt
8
+ } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
9
+ import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
10
+ import { OAppCoreBaseUpgradeable } from "./OAppCoreBaseUpgradeable.sol";
11
+
12
+ /**
13
+ * @title OAppSenderUpgradeable
14
+ * @author LayerZero Labs
15
+ * @custom:version 1.0.0
16
+ * @notice Abstract contract implementing the OAppSender functionality for sending messages to a LayerZero endpoint.
17
+ */
18
+ abstract contract OAppSenderUpgradeable is OAppCoreBaseUpgradeable {
19
+ using SafeERC20 for IERC20;
20
+
21
+ // Custom error messages
22
+ error NotEnoughNative(uint256 msgValue);
23
+ error LzTokenUnavailable();
24
+
25
+ // @dev The version of the OAppSender implementation.
26
+ // @dev Version is bumped when changes are made to this contract.
27
+ uint64 internal constant SENDER_VERSION = 1;
28
+
29
+ /**
30
+ * @notice Initializes the contract.
31
+ * @dev This function is empty on purpose, as no custom logic is needed.
32
+ */
33
+ function __OAppSender_init() internal onlyInitializing {}
34
+
35
+ /**
36
+ * @notice Unchained initialization function for the contract.
37
+ * @dev This function is empty on purpose, as no custom logic is needed.
38
+ */
39
+ function __OAppSender_init_unchained() internal onlyInitializing {}
40
+
41
+ /**
42
+ * @notice Retrieves the OApp version information.
43
+ * @return senderVersion The version of the OAppSender.sol contract.
44
+ * @return receiverVersion The version of the OAppReceiver.sol contract.
45
+ *
46
+ * @dev Providing 0 as the default for OAppReceiver version. Indicates that the OAppReceiver is not implemented.
47
+ * ie. this is a SEND only OApp.
48
+ * @dev If the OApp uses both OAppSender and OAppReceiver, then this needs to be override returning the correct versions
49
+ */
50
+ function oAppVersion() public view virtual returns (uint64 senderVersion, uint64 receiverVersion) {
51
+ return (SENDER_VERSION, 0);
52
+ }
53
+
54
+ /**
55
+ * @dev Internal function to interact with the LayerZero EndpointV2.quote() for fee calculation.
56
+ * @param _dstEid The destination endpoint ID.
57
+ * @param _message The message payload.
58
+ * @param _options Additional options for the message.
59
+ * @param _payInLzToken Flag indicating whether to pay the fee in LZ tokens.
60
+ * @return fee The calculated MessagingFee for the message.
61
+ * - nativeFee: The native fee for the message.
62
+ * - lzTokenFee: The LZ token fee for the message.
63
+ */
64
+ function _quote(
65
+ uint32 _dstEid,
66
+ bytes memory _message,
67
+ bytes memory _options,
68
+ bool _payInLzToken
69
+ ) internal view virtual returns (MessagingFee memory fee) {
70
+ return
71
+ endpoint.quote(
72
+ MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _payInLzToken),
73
+ address(this)
74
+ );
75
+ }
76
+
77
+ /**
78
+ * @dev Internal function to interact with the LayerZero EndpointV2.send() for sending a message.
79
+ * @param _dstEid The destination endpoint ID.
80
+ * @param _message The message payload.
81
+ * @param _options Additional options for the message.
82
+ * @param _fee The calculated LayerZero fee for the message.
83
+ * - nativeFee: The native fee.
84
+ * - lzTokenFee: The lzToken fee.
85
+ * @param _refundAddress The address to receive any excess fee values sent to the endpoint.
86
+ * @return receipt The receipt for the sent message.
87
+ * - guid: The unique identifier for the sent message.
88
+ * - nonce: The nonce of the sent message.
89
+ * - fee: The LayerZero fee incurred for the message.
90
+ */
91
+ function _lzSend(
92
+ uint32 _dstEid,
93
+ bytes memory _message,
94
+ bytes memory _options,
95
+ MessagingFee memory _fee,
96
+ address _refundAddress
97
+ ) internal virtual returns (MessagingReceipt memory receipt) {
98
+ // @dev Push corresponding fees to the endpoint, any excess is sent back to the _refundAddress from the endpoint.
99
+ uint256 messageValue = _payNative(_fee.nativeFee);
100
+ if (_fee.lzTokenFee > 0) _payLzToken(_fee.lzTokenFee);
101
+
102
+ return
103
+ // solhint-disable-next-line check-send-result
104
+ endpoint.send{ value: messageValue }(
105
+ MessagingParams(_dstEid, _getPeerOrRevert(_dstEid), _message, _options, _fee.lzTokenFee > 0),
106
+ _refundAddress
107
+ );
108
+ }
109
+
110
+ /**
111
+ * @dev Internal function to pay the native fee associated with the message.
112
+ * @param _nativeFee The native fee to be paid.
113
+ * @return nativeFee The amount of native currency paid.
114
+ *
115
+ * @dev If the OApp needs to initiate MULTIPLE LayerZero messages in a single transaction,
116
+ * this will need to be overridden because msg.value would contain multiple lzFees.
117
+ * @dev Should be overridden in the event the LayerZero endpoint requires a different native currency.
118
+ * @dev Some EVMs use an ERC20 as a method for paying transactions/gasFees.
119
+ * @dev The endpoint is EITHER/OR, ie. it will NOT support both types of native payment at a time.
120
+ */
121
+ function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
122
+ if (msg.value != _nativeFee) revert NotEnoughNative(msg.value);
123
+ return _nativeFee;
124
+ }
125
+
126
+ /**
127
+ * @dev Internal function to pay the LZ token fee associated with the message.
128
+ * @param _lzTokenFee The LZ token fee to be paid.
129
+ *
130
+ * @dev If the caller is trying to pay in the specified lzToken, then the lzTokenFee is passed to the endpoint.
131
+ * @dev Any excess sent, is passed back to the specified _refundAddress in the _lzSend().
132
+ */
133
+ function _payLzToken(uint256 _lzTokenFee) internal virtual {
134
+ // @dev Cannot cache the token because it is not immutable in the endpoint.
135
+ address lzToken = endpoint.lzToken();
136
+ if (lzToken == address(0)) revert LzTokenUnavailable();
137
+
138
+ // Pay LZ token fee by sending tokens to the endpoint.
139
+ IERC20(lzToken).safeTransferFrom(msg.sender, address(endpoint), _lzTokenFee);
140
+ }
141
+ }
@@ -0,0 +1,51 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { OAppCoreBaseUpgradeable } from "./OAppCoreBaseUpgradeable.sol";
5
+ // @dev Import the 'Origin' so it's exposed to OApp implementers
6
+ // solhint-disable-next-line no-unused-import
7
+ import { OAppReceiverUpgradeable, Origin } from "./OAppReceiverUpgradeable.sol";
8
+ // @dev Import the 'MessagingFee' and 'MessagingReceipt' so it's exposed to OApp implementers
9
+ // solhint-disable-next-line no-unused-import
10
+ import { OAppSenderUpgradeable, MessagingFee, MessagingReceipt } from "./OAppSenderUpgradeable.sol";
11
+
12
+ /**
13
+ * @title OAppUpgradeable
14
+ * @author LayerZero Labs
15
+ * @custom:version 1.0.0
16
+ * @notice Abstract contract serving as the base for OApp implementation, combining OAppSender and OAppReceiver functionality.
17
+ */
18
+ abstract contract OAppUpgradeable is OAppSenderUpgradeable, OAppReceiverUpgradeable {
19
+ /**
20
+ * @dev Constructor to initialize the OApp with the provided endpoint.
21
+ * @param _endpoint The address of the LOCAL LayerZero endpoint.
22
+ */
23
+ constructor(address _endpoint) OAppCoreBaseUpgradeable(_endpoint) {}
24
+
25
+ /**
26
+ * @notice Initializes the contract.
27
+ * @dev This function is empty on purpose, as no custom logic is needed.
28
+ */
29
+ function __OApp_init() internal onlyInitializing {}
30
+
31
+ /**
32
+ * @notice Unchained initialization function for the contract.
33
+ * @dev This function is empty on purpose, as no custom logic is needed.
34
+ */
35
+ function __OApp_init_unchained() internal onlyInitializing {}
36
+
37
+ /**
38
+ * @notice Retrieves the OApp version information.
39
+ * @return senderVersion The version of the OAppSender.sol implementation.
40
+ * @return receiverVersion The version of the OAppReceiver.sol implementation.
41
+ */
42
+ function oAppVersion()
43
+ public
44
+ pure
45
+ virtual
46
+ override(OAppSenderUpgradeable, OAppReceiverUpgradeable)
47
+ returns (uint64 senderVersion, uint64 receiverVersion)
48
+ {
49
+ return (SENDER_VERSION, RECEIVER_VERSION);
50
+ }
51
+ }
@@ -0,0 +1,48 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppAlt } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppAlt.sol";
5
+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
6
+ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
7
+ import { OAppCoreBaseUpgradeable } from "./../OAppCoreBaseUpgradeable.sol";
8
+
9
+ /**
10
+ * @title OAppAltUpgradeable
11
+ * @author LayerZero Labs (@TRileySchwarz, tinom.eth)
12
+ * @custom:version 1.0.0
13
+ * @notice OApp extension that pays native fees using an ERC20 token instead of `msg.value`.
14
+ * @dev For chains where gas/native fees are paid via an ERC20 token (e.g., some L2s using `EndpointV2Alt`).
15
+ * @dev When using multiple inheritance, inherit from `OAppAltUpgradeable` after OApp or OFT contracts to ensure
16
+ * `endpoint` is already set when this constructor runs.
17
+ * @dev Overrides `OAppCoreBaseUpgradeable` instead of `OAppSenderUpgradeable` to avoid inheritance conflicts.
18
+ */
19
+ abstract contract OAppAltUpgradeable is IOAppAlt, OAppCoreBaseUpgradeable {
20
+ using SafeERC20 for IERC20;
21
+
22
+ /// @dev ERC20 token used to pay native fees, cached from the endpoint.
23
+ address internal immutable NATIVE_TOKEN;
24
+
25
+ /**
26
+ * @dev Sets immutable variables.
27
+ * @dev Reverts if the endpoint has a zero address native token.
28
+ */
29
+ constructor() {
30
+ /// @dev `endpoint` should already be set at this point by `OAppCoreUpgradeable`.
31
+ NATIVE_TOKEN = endpoint.nativeToken();
32
+ if (NATIVE_TOKEN == address(0)) revert InvalidNativeToken();
33
+ }
34
+
35
+ /**
36
+ * @dev Overrides native fee payment to use an ERC20 token instead of `msg.value`, always returns 0.
37
+ * @dev Implicitly overrides `OAppSenderUpgradeable._payNative` to return 0.
38
+ * @param _nativeFee Native fee to be paid
39
+ * @return nativeFee Always 0 since the fee is paid via ERC20 transfer
40
+ */
41
+ function _payNative(uint256 _nativeFee) internal virtual returns (uint256 nativeFee) {
42
+ if (msg.value > 0) revert OnlyAltToken();
43
+ if (_nativeFee > 0) {
44
+ IERC20(NATIVE_TOKEN).safeTransferFrom(msg.sender, address(endpoint), _nativeFee);
45
+ }
46
+ return 0;
47
+ }
48
+ }
@@ -0,0 +1,67 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppMsgInspection } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppMsgInspection.sol";
5
+ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6
+
7
+ /**
8
+ * @title OAppMsgInspectionBaseUpgradeable
9
+ * @author LayerZero Labs (@TRileySchwarz, tinom.eth)
10
+ * @custom:version 1.0.0
11
+ * @notice Abstract upgradeable contract that implements message inspection functionality.
12
+ * @dev No public management functions are exposed by this contract, wrappers should be used with access control.
13
+ * Alternatively, refer to `OAppMsgInspectionRBACUpgradeable` for a permissioned implementation.
14
+ */
15
+ abstract contract OAppMsgInspectionBaseUpgradeable is IOAppMsgInspection, Initializable {
16
+ /// @custom:storage-location erc7201:layerzerov2.storage.oappmsginspection
17
+ struct OAppMsgInspectionStorage {
18
+ address msgInspector;
19
+ }
20
+
21
+ // keccak256(abi.encode(uint256(keccak256("layerzerov2.storage.oappmsginspection")) - 1)) & ~bytes32(uint256(0xff))
22
+ bytes32 private constant OAPP_MSG_INSPECTION_STORAGE_LOCATION =
23
+ 0x24c2aca717bd6504b7874a40f547315f719b854b33e7ff8940b1b271c2deaf00;
24
+
25
+ /**
26
+ * @notice Internal function to get the message inspection storage.
27
+ * @return $ Storage pointer
28
+ */
29
+ function _getOAppMsgInspectionStorage() internal pure returns (OAppMsgInspectionStorage storage $) {
30
+ assembly {
31
+ $.slot := OAPP_MSG_INSPECTION_STORAGE_LOCATION
32
+ }
33
+ }
34
+
35
+ /**
36
+ * @notice Initializes the contract.
37
+ * @dev This function is empty on purpose, as no custom logic is needed.
38
+ */
39
+ function __OAppMsgInspectionBase_init() internal onlyInitializing {}
40
+
41
+ /**
42
+ * @notice Unchained initialization function for the contract.
43
+ * @dev This function is empty on purpose, as no custom logic is needed.
44
+ */
45
+ function __OAppMsgInspectionBase_init_unchained() internal onlyInitializing {}
46
+
47
+ /**
48
+ * @inheritdoc IOAppMsgInspection
49
+ */
50
+ function msgInspector() public view virtual returns (address inspector) {
51
+ OAppMsgInspectionStorage storage $ = _getOAppMsgInspectionStorage();
52
+ return $.msgInspector;
53
+ }
54
+
55
+ // ============ Internal Functions to Wrap with Access Control ============
56
+
57
+ /**
58
+ * @notice Internal function to set the message inspector address.
59
+ * @dev To be wrapped with access control.
60
+ * @param _msgInspector Address of the new message inspector
61
+ */
62
+ function _setMsgInspector(address _msgInspector) internal virtual {
63
+ OAppMsgInspectionStorage storage $ = _getOAppMsgInspectionStorage();
64
+ $.msgInspector = _msgInspector;
65
+ emit MsgInspectorSet(_msgInspector);
66
+ }
67
+ }
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppMsgInspection } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppMsgInspection.sol";
5
+ import { AccessControl2StepUpgradeable } from "@layerzerolabs/utils-upgradeable-evm-contracts/contracts/access/AccessControl2StepUpgradeable.sol";
6
+ import { OAppMsgInspectionBaseUpgradeable } from "./OAppMsgInspectionBaseUpgradeable.sol";
7
+
8
+ /**
9
+ * @title OAppMsgInspectionRBACUpgradeable
10
+ * @author LayerZero Labs (@TRileySchwarz, tinom.eth)
11
+ * @custom:version 1.0.0
12
+ * @notice Abstract upgradeable contract that implements message inspector functionality.
13
+ * @dev Exposes public management functions through `AccessControl2StepUpgradeable`.
14
+ */
15
+ abstract contract OAppMsgInspectionRBACUpgradeable is OAppMsgInspectionBaseUpgradeable, AccessControl2StepUpgradeable {
16
+ /**
17
+ * @notice Initializes the contract.
18
+ * @dev This function is empty on purpose, as no custom logic is needed.
19
+ */
20
+ function __OAppMsgInspectionRBAC_init() internal onlyInitializing {}
21
+
22
+ /**
23
+ * @notice Unchained initialization function for the contract.
24
+ * @dev This function is empty on purpose, as no custom logic is needed.
25
+ */
26
+ function __OAppMsgInspectionRBAC_init_unchained() internal onlyInitializing {}
27
+
28
+ /**
29
+ * @inheritdoc IOAppMsgInspection
30
+ */
31
+ function setMsgInspector(address _msgInspector) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
32
+ _setMsgInspector(_msgInspector);
33
+ }
34
+ }
@@ -0,0 +1,126 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppOptionsType3 } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppOptionsType3.sol";
5
+ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
6
+
7
+ /**
8
+ * @title OAppOptionsType3BaseUpgradeable
9
+ * @author LayerZero Labs
10
+ * @custom:version 1.0.0
11
+ * @notice Abstract upgradeable contract implementing type 3 OApp options.
12
+ * @dev No public management functions are exposed by this contract, wrappers should be used with access control.
13
+ * Alternatively, refer to `OAppOptionsType3RBACUpgradeable` for a permissioned implementation.
14
+ */
15
+ abstract contract OAppOptionsType3BaseUpgradeable is IOAppOptionsType3, Initializable {
16
+ /// @dev Option type 3 prefix (`0x0003`).
17
+ uint16 internal constant OPTION_TYPE_3 = 3;
18
+
19
+ /// @custom:storage-location erc7201:layerzerov2.storage.oappoptionstype3
20
+ struct OAppOptionsType3Storage {
21
+ /// @dev `msgType` must be defined in the child contract. E.g., `SEND` or `SEND_AND_CALL`.
22
+ mapping(uint32 eid => mapping(uint16 msgType => bytes options)) enforcedOptions;
23
+ }
24
+
25
+ // keccak256(abi.encode(uint256(keccak256("layerzerov2.storage.oappoptionstype3")) - 1)) & ~bytes32(uint256(0xff))
26
+ bytes32 private constant OAPP_OPTIONS_TYPE_3_STORAGE_LOCATION =
27
+ 0x8d2bda5d9f6ffb5796910376005392955773acee5548d0fcdb10e7c264ea0000;
28
+
29
+ /**
30
+ * @notice Internal function to get the OAppOptionsType3 storage.
31
+ * @return $ Storage pointer
32
+ */
33
+ function _getOAppOptionsType3Storage() internal pure returns (OAppOptionsType3Storage storage $) {
34
+ assembly {
35
+ $.slot := OAPP_OPTIONS_TYPE_3_STORAGE_LOCATION
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @notice Initializes the contract.
41
+ * @dev This function is empty on purpose, as no custom logic is needed.
42
+ */
43
+ function __OAppOptionsType3Base_init() internal onlyInitializing {}
44
+
45
+ /**
46
+ * @notice Unchained initialization function for the contract.
47
+ * @dev This function is empty on purpose, as no custom logic is needed.
48
+ */
49
+ function __OAppOptionsType3Base_init_unchained() internal onlyInitializing {}
50
+
51
+ /**
52
+ * @inheritdoc IOAppOptionsType3
53
+ */
54
+ function enforcedOptions(uint32 _eid, uint16 _msgType) public view returns (bytes memory options) {
55
+ OAppOptionsType3Storage storage $ = _getOAppOptionsType3Storage();
56
+ return $.enforcedOptions[_eid][_msgType];
57
+ }
58
+
59
+ /**
60
+ * @dev If there is an enforced `lzReceive` option `{ gasLimit: 200k, msg.value: 1 ether }` AND a caller supplies a
61
+ * `lzReceive` option `{ gasLimit: 100k, msg.value: 0.5 ether }`, the resulting options will be
62
+ * `{ gasLimit: 300k, msg.value: 1.5 ether }` when the message is executed on the remote `lzReceive` function.
63
+ * @dev The presence of duplicated options is handled off-chain in the verifier/executor.
64
+ * @inheritdoc IOAppOptionsType3
65
+ */
66
+ function combineOptions(
67
+ uint32 _eid,
68
+ uint16 _msgType,
69
+ bytes calldata _extraOptions
70
+ ) public view virtual returns (bytes memory options) {
71
+ OAppOptionsType3Storage storage $ = _getOAppOptionsType3Storage();
72
+ bytes memory enforced = $.enforcedOptions[_eid][_msgType];
73
+
74
+ // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.
75
+ if (enforced.length == 0) return _extraOptions;
76
+
77
+ // No caller options, return enforced
78
+ if (_extraOptions.length == 0) return enforced;
79
+
80
+ /// @dev If caller provided `_extraOptions`, they must be type 3 as it's the ONLY type that can be combined.
81
+ if (_extraOptions.length >= 2) {
82
+ _assertOptionsType3(_extraOptions);
83
+ /// @dev Remove the first 2 bytes containing the type from the `_extraOptions` and combine with enforced.
84
+ return bytes.concat(enforced, _extraOptions[2:]);
85
+ }
86
+
87
+ // No valid set of options was found.
88
+ revert InvalidOptions(_extraOptions);
89
+ }
90
+
91
+ /**
92
+ * @dev Internal function to assert that options are of type 3.
93
+ * @param _options Options to be checked
94
+ */
95
+ function _assertOptionsType3(bytes memory _options) internal pure virtual {
96
+ uint16 optionsType;
97
+ assembly {
98
+ optionsType := mload(add(_options, 2))
99
+ }
100
+ if (optionsType != OPTION_TYPE_3) revert InvalidOptions(_options);
101
+ }
102
+
103
+ // ============ Internal Functions to Wrap with Access Control ============
104
+
105
+ /**
106
+ * @notice Internal function to set the enforced options for specific endpoint and message type combinations.
107
+ * @dev To be wrapped with access control.
108
+ * @dev Provides a way for the OApp to enforce things like paying for minimum destination `lzReceive` gas amounts.
109
+ * @dev These enforced options can vary as the potential options/execution on the remote may differ as per the
110
+ * `msgType`. E.g., the amount of `lzReceive` gas necessary to deliver a `lzCompose` message adds overhead you
111
+ * don't want to pay if you are only sending a standard message such as `lzReceive` WITHOUT `sendCompose`.
112
+ * @param _enforcedOptions Array of `EnforcedOptionParam` structures specifying enforced options
113
+ */
114
+ function _setEnforcedOptions(IOAppOptionsType3.EnforcedOptionParam[] memory _enforcedOptions) internal virtual {
115
+ OAppOptionsType3Storage storage $ = _getOAppOptionsType3Storage();
116
+ for (uint256 i = 0; i < _enforcedOptions.length; i++) {
117
+ /// @dev Enforced options are only available for option type 3, as types 1 and 2 don't support combining.
118
+ if (_enforcedOptions[i].options.length != 0) {
119
+ _assertOptionsType3(_enforcedOptions[i].options);
120
+ }
121
+ $.enforcedOptions[_enforcedOptions[i].eid][_enforcedOptions[i].msgType] = _enforcedOptions[i].options;
122
+ }
123
+
124
+ emit EnforcedOptionSet(_enforcedOptions);
125
+ }
126
+ }
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.22;
3
+
4
+ import { IOAppOptionsType3 } from "@layerzerolabs/oapp-evm-contracts/contracts/interfaces/IOAppOptionsType3.sol";
5
+ import { AccessControl2StepUpgradeable } from "@layerzerolabs/utils-upgradeable-evm-contracts/contracts/access/AccessControl2StepUpgradeable.sol";
6
+ import { OAppOptionsType3BaseUpgradeable } from "./OAppOptionsType3BaseUpgradeable.sol";
7
+
8
+ /**
9
+ * @title OAppOptionsType3RBACUpgradeable
10
+ * @author LayerZero Labs
11
+ * @custom:version 1.0.0
12
+ * @notice Abstract upgradeable contract implementing the IOAppOptionsType3 interface with RBAC access control.
13
+ * @dev Exposes public management functions through `AccessControl2StepUpgradeable`.
14
+ */
15
+ abstract contract OAppOptionsType3RBACUpgradeable is OAppOptionsType3BaseUpgradeable, AccessControl2StepUpgradeable {
16
+ /**
17
+ * @notice Initializes the contract.
18
+ * @dev This function is empty on purpose, as no custom logic is needed.
19
+ */
20
+ function __OAppOptionsType3RBAC_init() internal onlyInitializing {}
21
+
22
+ /**
23
+ * @notice Unchained initialization function for the contract.
24
+ * @dev This function is empty on purpose, as no custom logic is needed.
25
+ */
26
+ function __OAppOptionsType3RBAC_init_unchained() internal onlyInitializing {}
27
+
28
+ /**
29
+ * @inheritdoc IOAppOptionsType3
30
+ */
31
+ function setEnforcedOptions(
32
+ IOAppOptionsType3.EnforcedOptionParam[] calldata _enforcedOptions
33
+ ) public virtual onlyRole(DEFAULT_ADMIN_ROLE) {
34
+ _setEnforcedOptions(_enforcedOptions);
35
+ }
36
+ }
package/foundry.toml ADDED
@@ -0,0 +1,20 @@
1
+ [profile.default]
2
+ solc = '0.8.22'
3
+ verbosity = 3
4
+ src = "contracts"
5
+ test = "test"
6
+ out = "artifacts"
7
+ cache_path = "cache"
8
+ libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib']
9
+ optimizer = true
10
+ optimizer_runs = 200
11
+
12
+ remappings = [
13
+ 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test/',
14
+ 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std/',
15
+ '@openzeppelin/=node_modules/@openzeppelin/',
16
+ '@layerzerolabs/=node_modules/@layerzerolabs/',
17
+ ]
18
+
19
+ [fuzz]
20
+ runs = 1000