@layerzerolabs/oapp-stellar-contracts 0.2.122

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.
package/Cargo.toml ADDED
@@ -0,0 +1,44 @@
1
+ [package]
2
+ name = "oapp"
3
+ version = "0.0.1"
4
+ edition = "2021"
5
+ publish = false
6
+
7
+ [lib]
8
+ crate-type = ["rlib"]
9
+ doctest = false
10
+
11
+ [dependencies]
12
+ soroban-sdk = { version = "25.1.1", features = ["hazmat-address", "hazmat-crypto"] }
13
+ utils = { path = "dependencies/common-utils-stellar-contracts" }
14
+ common-macros = { path = "dependencies/common-utils-macros-stellar-contracts" }
15
+ endpoint-v2 = { path = "dependencies/protocol-stellar-v2/endpoint-v2", default-features = false, features = [
16
+ "library",
17
+ ] }
18
+ oapp-macros = { path = "dependencies/oapp-macros-stellar-contracts" }
19
+
20
+ [dev-dependencies]
21
+ soroban-sdk = { version = "25.1.1", features = ["hazmat-address", "hazmat-crypto", "testutils"] }
22
+ utils = { path = "dependencies/common-utils-stellar-contracts", features = ["testutils"] }
23
+ endpoint-v2 = { path = "dependencies/protocol-stellar-v2/endpoint-v2", default-features = false, features = [
24
+ "testutils",
25
+ ] }
26
+ common-macros = { path = "dependencies/common-utils-macros-stellar-contracts" }
27
+ oapp-macros = { path = "dependencies/oapp-macros-stellar-contracts" }
28
+ trybuild = "1.0.114"
29
+ insta = "1.44.3"
30
+ prettyplease = "0.2.37"
31
+
32
+ [profile.release]
33
+ opt-level = "z"
34
+ overflow-checks = true
35
+ debug = 0
36
+ strip = "symbols"
37
+ debug-assertions = false
38
+ panic = "abort"
39
+ codegen-units = 1
40
+ lto = true
41
+
42
+ [profile.release-with-logs]
43
+ inherits = "release"
44
+ debug-assertions = true
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2026 - LayerZero Labs Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any
4
+ person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the
6
+ Software without restriction, including without
7
+ limitation the rights to use, copy, modify, merge,
8
+ publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software
10
+ is furnished to do so, subject to the following
11
+ conditions:
12
+ The above copyright notice and this permission notice
13
+ shall be included in all copies or substantial portions
14
+ of the Software.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19
+ SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23
+ DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # `@layerzerolabs/oapp-stellar-contracts`
2
+
3
+ Stellar/Soroban OApp framework (traits + `oapp-macros`).
4
+
5
+ Developer guide: [docs/oapp-guide.md](../docs/oapp-guide.md).
package/clippy.toml ADDED
@@ -0,0 +1,6 @@
1
+ # Clippy configuration for Stellar contracts
2
+
3
+ # Set the too_many_arguments threshold to 11.
4
+ # This aligns with Stellar: contract functions are limited to 10 parameters, plus 1 for 'env'.
5
+ # (Default is 7; raised here to prevent unnecessary clippy warnings on valid contract interfaces.)
6
+ too-many-arguments-threshold = 11
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@layerzerolabs/oapp-stellar-contracts",
3
+ "version": "0.2.122",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "files": [
7
+ "src",
8
+ "Cargo.toml",
9
+ "rust-toolchain.toml",
10
+ "rustfmt.toml",
11
+ "clippy.toml"
12
+ ],
13
+ "dependencies": {
14
+ "@layerzerolabs/common-utils-macros-stellar-contracts": "0.2.122",
15
+ "@layerzerolabs/protocol-stellar-v2": "0.2.122",
16
+ "@layerzerolabs/common-utils-stellar-contracts": "0.2.122",
17
+ "@layerzerolabs/oapp-macros-stellar-contracts": "0.2.122"
18
+ },
19
+ "devDependencies": {
20
+ "@layerzerolabs/build-utils-rust": "0.2.122",
21
+ "@layerzerolabs/vm-tooling-stellar": "0.2.122"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "registry": "https://registry.npmjs.org/"
26
+ },
27
+ "externalRepoConfig": {
28
+ "targets": [
29
+ "audit-external",
30
+ "console-pen-test",
31
+ "monorepo-external",
32
+ "onesig-client"
33
+ ]
34
+ },
35
+ "implicitDependencies": {
36
+ "@layerzerolabs/protocol-stellar-v2": "workspace:*",
37
+ "@layerzerolabs/common-utils-stellar-contracts": "workspace:*",
38
+ "@layerzerolabs/common-utils-macros-stellar-contracts": "workspace:*",
39
+ "@layerzerolabs/oapp-macros-stellar-contracts": "workspace:*"
40
+ },
41
+ "scripts": {
42
+ "build": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo build --release\" stellar",
43
+ "clean": "rm -rf ./dependencies ./target ./node_modules",
44
+ "format": "pnpm exec lz-tool --script \"cargo fmt\" stellar",
45
+ "lint": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo clippy -- -D warnings\" stellar",
46
+ "lint:fix": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo clippy --fix --allow-dirty --allow-staged && cargo fmt\" stellar",
47
+ "resolve-dependencies": "pnpm exec build-utils-rust resolve",
48
+ "test": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo nextest run -E 'not test(/^ui_/)'\" stellar",
49
+ "test:snapshot:check": "pnpm exec turbo-snapshot check",
50
+ "test:snapshot:update": "pnpm exec turbo-snapshot update",
51
+ "test:ui": "pnpm resolve-dependencies && pnpm exec lz-tool --script \"cargo nextest run -E 'test(/^ui_/)'\" stellar"
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ [toolchain]
2
+ channel = "1.90.0"
3
+ targets = ["wasm32v1-none"]
4
+ components = ["rustfmt", "clippy"]
package/rustfmt.toml ADDED
@@ -0,0 +1,15 @@
1
+ format_macro_bodies = true
2
+ max_width = 120
3
+ format_macro_matchers = true
4
+ format_strings = true
5
+ imports_granularity = "Crate"
6
+ match_arm_blocks = false
7
+ reorder_impl_items = true
8
+ group_imports = "StdExternalCrate"
9
+ use_field_init_shorthand = true
10
+ use_small_heuristics = "Max"
11
+ wrap_comments = true
12
+ format_code_in_doc_comments = true
13
+
14
+ # most of these are unstable, so we enable them
15
+ unstable_features = true
package/src/errors.rs ADDED
@@ -0,0 +1,13 @@
1
+ use common_macros::contract_error;
2
+
3
+ // OApp library error codes: 2000-2099
4
+ // See docs/error-spec.md for allocation rules
5
+
6
+ /// OAppError: 2000-2099
7
+ #[contract_error]
8
+ pub enum OAppError {
9
+ InvalidOptions = 2000,
10
+ NoPeer,
11
+ OnlyPeer,
12
+ ZroTokenUnavailable,
13
+ }
@@ -0,0 +1,3 @@
1
+ mod oapp_msg_inspector;
2
+
3
+ pub use oapp_msg_inspector::*;
@@ -0,0 +1,70 @@
1
+ //! OApp Message Inspector interface.
2
+ //!
3
+ //! This module defines the `IOAppMsgInspector` trait that external inspector contracts
4
+ //! must implement to validate outgoing LayerZero messages and options.
5
+ //!
6
+ //! Implementations can signal failure in two ways:
7
+ //! - Return `false` to indicate the inspection failed.
8
+ //! - Panic directly to abort the transaction immediately.
9
+ //!
10
+ //! ## Usage
11
+ //!
12
+ //! ### Returning a boolean
13
+ //!
14
+ //! ```ignore
15
+ //! use oapp::interfaces::IOAppMsgInspector;
16
+ //!
17
+ //! pub struct MyInspector;
18
+ //!
19
+ //! #[contractimpl]
20
+ //! impl IOAppMsgInspector for MyInspector {
21
+ //! fn inspect(env: &Env, oapp: &Address, message: &Bytes, options: &Bytes) -> bool {
22
+ //! is_valid(message, options)
23
+ //! }
24
+ //! }
25
+ //! ```
26
+ //!
27
+ //! ### Panicking on failure
28
+ //!
29
+ //! ```ignore
30
+ //! use oapp::interfaces::IOAppMsgInspector;
31
+ //!
32
+ //! pub struct MyInspector;
33
+ //!
34
+ //! #[contractimpl]
35
+ //! impl IOAppMsgInspector for MyInspector {
36
+ //! fn inspect(env: &Env, oapp: &Address, message: &Bytes, options: &Bytes) -> bool {
37
+ //! if !is_valid(message, options) {
38
+ //! panic_with_error!(env, MyError::InspectionFailed);
39
+ //! }
40
+ //! true
41
+ //! }
42
+ //! }
43
+ //! ```
44
+
45
+ use soroban_sdk::{contractclient, Address, Bytes, Env};
46
+
47
+ /// Interface for OApp message inspectors.
48
+ ///
49
+ /// Contracts implementing this trait can be set as message inspectors on OFT contracts
50
+ /// to validate outgoing messages and options before they are sent cross-chain.
51
+ ///
52
+ /// Implementations may either return `false` to indicate failure, or panic directly
53
+ /// to abort the transaction.
54
+ #[contractclient(name = "OAppMsgInspectorClient")]
55
+ pub trait IOAppMsgInspector {
56
+ /// Allows the inspector to examine LayerZero message contents and determine their validity.
57
+ ///
58
+ /// # Arguments
59
+ /// * `oapp` - The address of the OApp contract sending the message
60
+ /// * `message` - The message payload to be inspected.
61
+ /// * `options` - Additional LayerZero options for inspection.
62
+ ///
63
+ /// # Returns
64
+ /// * `bool` - `true` if the inspection passed, `false` if the message or options are invalid.
65
+ ///
66
+ /// # Panics
67
+ /// Implementations may choose to panic instead of returning `false` to abort the
68
+ /// transaction immediately with a specific error.
69
+ fn inspect(env: &Env, oapp: &Address, message: &Bytes, options: &Bytes) -> bool;
70
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,15 @@
1
+ #![no_std]
2
+
3
+ pub mod oapp_core;
4
+ pub mod oapp_options_type3;
5
+ pub mod oapp_receiver;
6
+ pub mod oapp_sender;
7
+
8
+ mod errors;
9
+ mod interfaces;
10
+
11
+ pub use errors::*;
12
+ pub use interfaces::*;
13
+
14
+ #[cfg(test)]
15
+ mod tests;
@@ -0,0 +1,143 @@
1
+ use crate::{errors::OAppError, oapp_receiver::RECEIVER_VERSION, oapp_sender::SENDER_VERSION};
2
+ use common_macros::{contract_trait, only_role, storage};
3
+ use endpoint_v2::LayerZeroEndpointV2Client;
4
+ use soroban_sdk::{contractevent, Address, BytesN, Env};
5
+ use utils::{
6
+ option_ext::OptionExt,
7
+ ownable::{Ownable, OwnableInitializer},
8
+ rbac::{RoleBasedAccessControl, AUTHORIZER},
9
+ };
10
+
11
+ // =====================================================
12
+ // OAppCore Storage and Events
13
+ // =====================================================
14
+
15
+ #[storage]
16
+ pub enum OAppCoreStorage {
17
+ // Endpoint address - set once during initialization
18
+ #[instance(Address)]
19
+ Endpoint,
20
+
21
+ // Mapping from endpoint ID to remote peer address
22
+ #[persistent(BytesN<32>)]
23
+ Peer { eid: u32 },
24
+ }
25
+
26
+ #[contractevent]
27
+ #[derive(Clone, Debug, Eq, PartialEq)]
28
+ pub struct PeerSet {
29
+ pub eid: u32,
30
+ pub peer: Option<BytesN<32>>,
31
+ }
32
+
33
+ // =====================================================
34
+ // OAppCore Interface and Default Implementation
35
+ // =====================================================
36
+
37
+ #[contract_trait]
38
+ pub trait OAppCore: Ownable + RoleBasedAccessControl {
39
+ /// Retrieves the OApp version information.
40
+ ///
41
+ /// # Returns
42
+ /// A tuple containing:
43
+ /// - `sender_version`: The version of the OAppSender
44
+ /// - `receiver_version`: The version of the OAppReceiver
45
+ fn oapp_version(_env: &soroban_sdk::Env) -> (u64, u64) {
46
+ (SENDER_VERSION, RECEIVER_VERSION)
47
+ }
48
+
49
+ /// Retrieves the LayerZero endpoint address associated with the OApp.
50
+ ///
51
+ /// # Returns
52
+ /// The LayerZero endpoint address
53
+ fn endpoint(env: &soroban_sdk::Env) -> soroban_sdk::Address {
54
+ OAppCoreStorage::endpoint(env).unwrap()
55
+ }
56
+
57
+ /// Retrieves the peer (OApp) associated with a corresponding endpoint.
58
+ ///
59
+ /// # Arguments
60
+ /// * `eid` - The endpoint ID
61
+ ///
62
+ /// # Returns
63
+ /// The peer address (OApp instance) associated with the corresponding endpoint
64
+ fn peer(env: &soroban_sdk::Env, eid: u32) -> Option<soroban_sdk::BytesN<32>> {
65
+ OAppCoreStorage::peer(env, eid)
66
+ }
67
+
68
+ /// Sets or removes the peer address (OApp instance) for a corresponding endpoint.
69
+ ///
70
+ /// # Arguments
71
+ /// * `eid` - The endpoint ID
72
+ /// * `peer` - The address of the peer to be associated with the corresponding endpoint, or None to remove the peer
73
+ /// * `operator` - The authorizer address
74
+ #[only_role(operator, AUTHORIZER)]
75
+ fn set_peer(
76
+ env: &soroban_sdk::Env,
77
+ eid: u32,
78
+ peer: &Option<soroban_sdk::BytesN<32>>,
79
+ operator: &soroban_sdk::Address,
80
+ ) {
81
+ OAppCoreStorage::set_or_remove_peer(env, eid, peer);
82
+ PeerSet { eid, peer: peer.clone() }.publish(env);
83
+ }
84
+
85
+ /// Sets the delegate address for the OApp Core.
86
+ ///
87
+ /// # Arguments
88
+ /// * `delegate` - The address of the delegate to be set, or None to remove the delegate
89
+ /// * `operator` - The authorizer address
90
+ #[only_role(operator, AUTHORIZER)]
91
+ fn set_delegate(env: &soroban_sdk::Env, delegate: &Option<soroban_sdk::Address>, operator: &soroban_sdk::Address) {
92
+ endpoint_client::<Self>(env).set_delegate(&env.current_contract_address(), delegate);
93
+ }
94
+ }
95
+
96
+ // =====================================================
97
+ // Helper Functions
98
+ // =====================================================
99
+
100
+ /// Initializes the OApp with the specified configuration.
101
+ ///
102
+ /// This function sets up the OApp by initializing ownership, storing the LayerZero endpoint,
103
+ /// and setting a delegate address for the endpoint.
104
+ ///
105
+ /// # Arguments
106
+ /// * `env` - The Soroban environment
107
+ /// * `owner` - The address that will own this OApp
108
+ /// * `endpoint` - The LayerZero endpoint address to associate with this OApp
109
+ /// * `delegate` - The delegate address to set on the endpoint for this OApp
110
+ pub fn init_ownable_oapp<T: OAppCore + OwnableInitializer>(
111
+ env: &Env,
112
+ owner: &Address,
113
+ endpoint: &Address,
114
+ delegate: &Address,
115
+ ) {
116
+ T::init_owner(env, owner);
117
+ OAppCoreStorage::set_endpoint(env, endpoint);
118
+ LayerZeroEndpointV2Client::new(env, endpoint)
119
+ .set_delegate(&env.current_contract_address(), &Some(delegate.clone()));
120
+ }
121
+
122
+ /// Retrieves the peer address associated with a specific endpoint ID; panics if NOT set.
123
+ ///
124
+ /// This is a safe getter that ensures a peer has been configured for the given endpoint.
125
+ /// If no peer is set (i.e., the peer is `None`), it will panic with `OAppError::NoPeer`.
126
+ ///
127
+ /// # Arguments
128
+ /// * `env` - The Soroban environment
129
+ /// * `eid` - The endpoint ID
130
+ ///
131
+ /// # Returns
132
+ /// The peer address (`BytesN<32>`) associated with the specified endpoint
133
+ ///
134
+ /// # Panics
135
+ /// Panics with `OAppError::NoPeer` if no peer is set for the given endpoint ID
136
+ pub fn get_peer_or_panic<T: OAppCore>(env: &Env, eid: u32) -> BytesN<32> {
137
+ T::peer(env, eid).unwrap_or_panic(env, OAppError::NoPeer)
138
+ }
139
+
140
+ /// Returns a client for the LayerZero endpoint.
141
+ pub fn endpoint_client<'a, T: OAppCore>(env: &'a Env) -> LayerZeroEndpointV2Client<'a> {
142
+ LayerZeroEndpointV2Client::new(env, &T::endpoint(env))
143
+ }
@@ -0,0 +1,133 @@
1
+ use crate::{self as oapp, errors::OAppError};
2
+ use common_macros::{contract_trait, only_role, storage};
3
+ use soroban_sdk::{assert_with_error, contractevent, contracttype, panic_with_error, Bytes, Env, Vec};
4
+ use utils::{buffer_reader::BufferReader, rbac::{RoleBasedAccessControl, AUTHORIZER}};
5
+
6
+ pub const OPTION_TYPE3: u16 = 3;
7
+
8
+ #[contracttype]
9
+ #[derive(Clone, Debug, Eq, PartialEq)]
10
+ pub struct EnforcedOptionParam {
11
+ pub eid: u32,
12
+ pub msg_type: u32,
13
+ pub options: Option<Bytes>,
14
+ }
15
+
16
+ #[storage]
17
+ pub enum OAppOptionsType3Storage {
18
+ #[persistent(Bytes)]
19
+ EnforcedOptions { eid: u32, msg_type: u32 },
20
+ }
21
+
22
+ #[contractevent]
23
+ #[derive(Clone, Debug, Eq, PartialEq)]
24
+ pub struct EnforcedOptionSet {
25
+ pub enforced_options: Vec<EnforcedOptionParam>,
26
+ }
27
+
28
+ // =========================================================================
29
+ // OAppOptionsType3 Trait and Default Implementation
30
+ // =========================================================================
31
+
32
+ #[contract_trait]
33
+ pub trait OAppOptionsType3: RoleBasedAccessControl {
34
+ /// Retrieves the enforced options for a given endpoint and message type.
35
+ ///
36
+ /// # Arguments
37
+ /// * `eid` - The endpoint ID
38
+ /// * `msg_type` - The OApp message type
39
+ ///
40
+ /// # Returns
41
+ /// The enforced options for the given endpoint and message type
42
+ fn enforced_options(env: &soroban_sdk::Env, eid: u32, msg_type: u32) -> Option<soroban_sdk::Bytes> {
43
+ OAppOptionsType3Storage::enforced_options(env, eid, msg_type)
44
+ }
45
+
46
+ /// Sets or removes the enforced options for specific endpoint and message type combinations.
47
+ ///
48
+ /// Only the `authorizer` of the OApp can call this function.
49
+ /// Provides a way for the OApp to enforce things like paying for PreCrime, AND/OR minimum dst lzReceive gas amounts etc.
50
+ /// These enforced options can vary as the potential options/execution on the remote may differ as per the msg_type.
51
+ /// e.g. Amount of lzReceive() gas necessary to deliver a lzCompose() message adds overhead you don't want to pay
52
+ /// if you are only making a standard LayerZero message ie. lzReceive() WITHOUT sendCompose().
53
+ ///
54
+ /// # Arguments
55
+ /// * `options` - A vector of EnforcedOptionParam structures specifying enforced options
56
+ /// * `operator` - The authorizer address
57
+ #[only_role(operator, AUTHORIZER)]
58
+ fn set_enforced_options(
59
+ env: &soroban_sdk::Env,
60
+ options: &soroban_sdk::Vec<oapp::oapp_options_type3::EnforcedOptionParam>,
61
+ operator: &soroban_sdk::Address,
62
+ ) {
63
+ for param in options {
64
+ if let Some(ref opts) = param.options {
65
+ assert_option_type3(env, opts);
66
+ }
67
+ OAppOptionsType3Storage::set_or_remove_enforced_options(env, param.eid, param.msg_type, &param.options);
68
+ }
69
+ EnforcedOptionSet { enforced_options: options.clone() }.publish(env);
70
+ }
71
+
72
+ /// Combines options for a given endpoint and message type.
73
+ ///
74
+ /// If there is an enforced lzReceive option:
75
+ /// - {gas_limit: 200k, value: 1 XLM} AND a caller supplies a lzReceive option: {gas_limit: 100k, value: 0.5 XLM}
76
+ /// - The resulting options will be {gas_limit: 300k, value: 1.5 XLM} when the message is executed on the remote lz_receive() function.
77
+ /// The presence of duplicated options is handled off-chain in the verifier/executor.
78
+ ///
79
+ /// # Arguments
80
+ /// * `eid` - The endpoint ID
81
+ /// * `msg_type` - The OApp message type
82
+ /// * `extra_options` - Additional options passed by the caller
83
+ ///
84
+ /// # Returns
85
+ /// The combination of caller specified options AND enforced options
86
+ fn combine_options(
87
+ env: &soroban_sdk::Env,
88
+ eid: u32,
89
+ msg_type: u32,
90
+ extra_options: &soroban_sdk::Bytes,
91
+ ) -> soroban_sdk::Bytes {
92
+ let enforced_options_opt = Self::enforced_options(env, eid, msg_type);
93
+
94
+ // No enforced options, pass whatever the caller supplied, even if it's empty or legacy type 1/2 options.
95
+ if enforced_options_opt.is_none() {
96
+ return extra_options.clone();
97
+ }
98
+
99
+ // No caller options, return enforced
100
+ let mut enforced_options = enforced_options_opt.unwrap(); // unwrap is safe because we checked if it is none above
101
+ if extra_options.is_empty() {
102
+ return enforced_options;
103
+ }
104
+
105
+ // If caller provided extra_options, must be type 3 as its the ONLY type that can be combined.
106
+ if extra_options.len() >= 2 {
107
+ assert_option_type3(env, extra_options);
108
+
109
+ // Remove the first 2 bytes containing the type from the extra_options and combine with enforced.
110
+ enforced_options.append(&extra_options.slice(2..));
111
+ return enforced_options;
112
+ }
113
+
114
+ // No valid set of options was found.
115
+ panic_with_error!(env, OAppError::InvalidOptions);
116
+ }
117
+ }
118
+
119
+ // =========================================================================
120
+ // Helpers Functions
121
+ // =========================================================================
122
+
123
+ /// Asserts that the provided options are of type 3.
124
+ ///
125
+ /// # Arguments
126
+ /// * `options` - The options to be checked
127
+ ///
128
+ /// # Panics
129
+ /// If the options are not of type 3
130
+ pub fn assert_option_type3(env: &Env, options: &Bytes) {
131
+ let options_type = BufferReader::new(options).read_u16();
132
+ assert_with_error!(env, options_type == OPTION_TYPE3, OAppError::InvalidOptions);
133
+ }