@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 +44 -0
- package/LICENSE +23 -0
- package/README.md +5 -0
- package/clippy.toml +6 -0
- package/package.json +53 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/errors.rs +13 -0
- package/src/interfaces/mod.rs +3 -0
- package/src/interfaces/oapp_msg_inspector.rs +70 -0
- package/src/lib.rs +15 -0
- package/src/oapp_core.rs +143 -0
- package/src/oapp_options_type3.rs +133 -0
- package/src/oapp_receiver.rs +195 -0
- package/src/oapp_sender.rs +152 -0
- package/src/tests/mod.rs +5 -0
- package/src/tests/oapp_core.rs +225 -0
- package/src/tests/oapp_options_type3.rs +246 -0
- package/src/tests/oapp_receiver.rs +383 -0
- package/src/tests/oapp_sender.rs +570 -0
- package/src/tests/test_macros.rs +434 -0
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
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
|
+
}
|
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,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
package/src/oapp_core.rs
ADDED
|
@@ -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, ¶m.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
|
+
}
|