@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.
@@ -0,0 +1,195 @@
1
+ use crate::{
2
+ errors::OAppError,
3
+ oapp_core::{endpoint_client, get_peer_or_panic, OAppCore},
4
+ };
5
+ use common_macros::contract_trait;
6
+ use endpoint_v2::Origin;
7
+ use soroban_sdk::{assert_with_error, token::TokenClient, Address, Bytes, BytesN, Env};
8
+
9
+ /// The version of the OAppReceiver implementation.
10
+ /// Version is bumped when changes are made to this contract.
11
+ pub const RECEIVER_VERSION: u64 = 1;
12
+
13
+ // =====================================================
14
+ // LzReceiveInternal Trait
15
+ // =====================================================
16
+
17
+ /// Application-specific handler for incoming LayerZero messages.
18
+ ///
19
+ /// Implement this trait to define how your OApp processes cross-chain messages.
20
+ /// The default `OAppReceiver::lz_receive` calls `clear_payload_and_transfer` first,
21
+ /// then delegates to your `__lz_receive` implementation.
22
+ ///
23
+ /// **Important:** Do NOT call `clear_payload_and_transfer` in your implementation -
24
+ /// it is handled automatically by the default `lz_receive`.
25
+ pub trait LzReceiveInternal {
26
+ /// Processes a verified cross-chain message.
27
+ ///
28
+ /// Called after payload verification. Implement your message handling logic here.
29
+ fn __lz_receive(
30
+ env: &Env,
31
+ origin: &Origin,
32
+ guid: &BytesN<32>,
33
+ message: &Bytes,
34
+ extra_data: &Bytes,
35
+ executor: &Address,
36
+ value: i128,
37
+ );
38
+ }
39
+
40
+ // =====================================================
41
+ // OAppReceiver Trait
42
+ // =====================================================
43
+
44
+ /// Receiver trait for OApps that accept cross-chain messages from LayerZero.
45
+ ///
46
+ /// Mirrors `ILayerZeroReceiver` function signatures, allowing the executor to call
47
+ /// these methods via `LayerZeroReceiverClient`.
48
+ ///
49
+ /// # Default Implementations
50
+ /// | Method | Behavior |
51
+ /// |--------------------------|----------------------------------------------------------------|
52
+ /// | `allow_initialize_path` | Returns true if origin sender matches configured peer |
53
+ /// | `next_nonce` | Returns 0 (unordered delivery) |
54
+ /// | `lz_receive` | Verifies payload, then calls `LzReceiveInternal::__lz_receive` |
55
+ /// | `is_compose_msg_sender` | Returns true if sender is current contract |
56
+ ///
57
+ /// # Usage
58
+ ///
59
+ /// ```ignore
60
+ /// use oapp::oapp_receiver::LzReceiveInternal;
61
+ ///
62
+ /// #[common_macros::lz_contract]
63
+ /// #[oapp_macros::oapp]
64
+ /// pub struct MyOApp;
65
+ ///
66
+ /// impl LzReceiveInternal for MyOApp {
67
+ /// fn __lz_receive(env: &Env, origin: &Origin, guid: &BytesN<32>,
68
+ /// message: &Bytes, extra_data: &Bytes, executor: &Address, value: i128) {
69
+ /// // Your message handling logic here
70
+ /// }
71
+ /// }
72
+ /// ```
73
+ ///
74
+ /// # Customization
75
+ /// For custom behavior (e.g., ordered nonce enforcement), use `#[oapp(custom = [receiver])]`
76
+ /// and implement both `LzReceiveInternal` and `OAppReceiver`.
77
+ #[contract_trait]
78
+ pub trait OAppReceiver: OAppCore + LzReceiveInternal {
79
+ /// Checks if a messaging path can be initialized for the given origin.
80
+ ///
81
+ /// # Arguments
82
+ /// * `origin` - The origin of the message
83
+ ///
84
+ /// # Returns
85
+ /// True if the path can be initialized, false otherwise
86
+ fn allow_initialize_path(env: &soroban_sdk::Env, origin: &endpoint_v2::Origin) -> bool {
87
+ let peer = Self::peer(env, origin.src_eid);
88
+ peer.is_some_and(|peer| peer == origin.sender)
89
+ }
90
+
91
+ /// Retrieves the next nonce for a given source endpoint and sender address.
92
+ ///
93
+ /// The path nonce starts from 1. If 0 is returned it means that there is NO nonce ordered enforcement.
94
+ /// This is required by the off-chain executor to determine if the OApp expects message execution to be ordered.
95
+ /// This is also enforced by the OApp.
96
+ /// By default this is NOT enabled, i.e. next_nonce is hardcoded to return 0.
97
+ ///
98
+ /// # Arguments
99
+ /// * `src_eid` - The source endpoint ID
100
+ /// * `sender` - The sender OApp address
101
+ ///
102
+ /// # Returns
103
+ /// The next nonce
104
+ fn next_nonce(_env: &soroban_sdk::Env, _src_eid: u32, _sender: &soroban_sdk::BytesN<32>) -> u64 {
105
+ 0
106
+ }
107
+
108
+ /// Entry point for receiving messages or packets from the LayerZero endpoint.
109
+ ///
110
+ /// The default implementation calls `clear_payload_and_transfer` to validate the message
111
+ /// and clear it from the endpoint, then delegates to `__lz_receive` for application logic.
112
+ ///
113
+ /// # Arguments
114
+ /// * `executor` - The address of the executor for the received message
115
+ /// * `origin` - The origin information containing the source endpoint and sender address:
116
+ /// - `src_eid`: The source endpoint ID
117
+ /// - `sender`: The sender address on the source chain
118
+ /// - `nonce`: The nonce of the message
119
+ /// * `guid` - The unique identifier for the received LayerZero message
120
+ /// * `message` - The payload of the received message
121
+ /// * `extra_data` - Additional arbitrary data provided by the corresponding executor
122
+ /// * `value` - The native token value sent with the message
123
+ fn lz_receive(
124
+ env: &soroban_sdk::Env,
125
+ executor: &soroban_sdk::Address,
126
+ origin: &endpoint_v2::Origin,
127
+ guid: &soroban_sdk::BytesN<32>,
128
+ message: &soroban_sdk::Bytes,
129
+ extra_data: &soroban_sdk::Bytes,
130
+ value: i128,
131
+ ) {
132
+ clear_payload_and_transfer::<Self>(env, executor, origin, guid, message, value);
133
+ Self::__lz_receive(env, origin, guid, message, extra_data, executor, value)
134
+ }
135
+
136
+ /// Indicates whether an address is an approved composeMsg sender to the Endpoint.
137
+ ///
138
+ /// Applications can optionally choose to implement separate composeMsg senders that are NOT the bridging layer.
139
+ /// The default sender IS the OAppReceiver implementer.
140
+ ///
141
+ /// # Arguments
142
+ /// * `origin` - The origin information containing the source endpoint and sender address
143
+ /// * `message` - The lzReceive payload
144
+ /// * `sender` - The sender address to check
145
+ ///
146
+ /// # Returns
147
+ /// True if the sender is a valid composeMsg sender, false otherwise
148
+ fn is_compose_msg_sender(
149
+ env: &soroban_sdk::Env,
150
+ _origin: &endpoint_v2::Origin,
151
+ _message: &soroban_sdk::Bytes,
152
+ sender: &soroban_sdk::Address,
153
+ ) -> bool {
154
+ env.current_contract_address() == *sender
155
+ }
156
+ }
157
+
158
+ // =====================================================
159
+ // Helper functions
160
+ // =====================================================
161
+
162
+ /// Clears the message payload from the endpoint and transfers native tokens from the executor to the oapp if has value.
163
+ ///
164
+ /// # Arguments
165
+ /// * `env` - The environment
166
+ /// * `executor` - The address of the executor delivering the message
167
+ /// * `origin` - The origin information (source EID, sender, nonce)
168
+ /// * `guid` - The unique identifier for the LayerZero message to clear the payload from
169
+ /// * `message` - The message payload to clear
170
+ /// * `value` - The native token value to transfer from the executor to the oapp if has value
171
+ pub fn clear_payload_and_transfer<T: OAppCore>(
172
+ env: &Env,
173
+ executor: &Address,
174
+ origin: &Origin,
175
+ guid: &BytesN<32>,
176
+ message: &Bytes,
177
+ value: i128,
178
+ ) {
179
+ // Require authorization from the executor and transfer the value from the executor to the oapp if has value
180
+ executor.require_auth();
181
+ // Assert that the message is from the expected peer
182
+ assert_with_error!(env, get_peer_or_panic::<T>(env, origin.src_eid) == origin.sender, OAppError::OnlyPeer);
183
+
184
+ let this_address = env.current_contract_address();
185
+ let endpoint_client = endpoint_client::<T>(env);
186
+
187
+ // Transfer the value from the executor to the oapp if has value
188
+ if value != 0 {
189
+ let token_client = TokenClient::new(env, &endpoint_client.native_token());
190
+ token_client.transfer(executor, &this_address, &value);
191
+ }
192
+
193
+ // Clear the message payload from the endpoint
194
+ endpoint_client.clear(&this_address, origin, &this_address, guid, message);
195
+ }
@@ -0,0 +1,152 @@
1
+ use crate::{
2
+ errors::OAppError,
3
+ oapp_core::{endpoint_client, get_peer_or_panic, OAppCore},
4
+ };
5
+ use endpoint_v2::{MessagingFee, MessagingParams, MessagingReceipt};
6
+ use soroban_sdk::{contracttype, token::TokenClient, Address, Bytes, Env};
7
+ use utils::option_ext::OptionExt;
8
+
9
+ /// The version of the OAppSender implementation.
10
+ /// Version is bumped when changes are made to this contract.
11
+ pub const SENDER_VERSION: u64 = 1;
12
+
13
+ /// Represents a fee payer address with explicit authorization state.
14
+ ///
15
+ /// This enum forces callers of `__lz_send` to explicitly declare whether
16
+ /// `require_auth()` has already been called for the fee payer address.
17
+ /// This prevents the common mistake of forgetting to authorize the fee payer.
18
+ ///
19
+ /// # Variants
20
+ /// - `Unverified` — Safe default. `__lz_send` will call `require_auth()` on the address.
21
+ /// Use this when the caller has **not** already authorized the fee payer.
22
+ /// - `Verified` — Caller asserts that `require_auth()` has already been called.
23
+ /// Use this to avoid a duplicate `require_auth()` node in the Soroban auth tree
24
+ /// (e.g., when the same address was already authorized as the message sender).
25
+ #[contracttype]
26
+ #[derive(Clone, Debug, Eq, PartialEq)]
27
+ pub enum FeePayer {
28
+ /// The fee payer has **not** been authorized yet.
29
+ /// `__lz_send` will call `fee_payer.require_auth()` before transferring fees.
30
+ /// This is the safe default — use this if unsure.
31
+ Unverified(Address),
32
+
33
+ /// The fee payer has **already** been authorized by the caller via `require_auth()`.
34
+ /// `__lz_send` will skip the auth check to avoid creating a duplicate auth node
35
+ /// in the Soroban authorization tree.
36
+ ///
37
+ /// # Safety
38
+ /// Only use this variant if you have already called `require_auth()` on this address
39
+ /// in the current contract invocation. Misuse may allow unauthorized fee deductions.
40
+ Verified(Address),
41
+ }
42
+
43
+ impl FeePayer {
44
+ /// Returns a reference to the underlying address.
45
+ pub fn address(&self) -> &Address {
46
+ match self {
47
+ FeePayer::Unverified(addr) | FeePayer::Verified(addr) => addr,
48
+ }
49
+ }
50
+ }
51
+
52
+ /// A helper trait for sending cross-chain messages via LayerZero.
53
+ ///
54
+ /// Contracts should implement this trait to gain access to the `__quote` and `__lz_send` helper
55
+ /// methods for cross-chain messaging. This trait provides default implementations that handle
56
+ /// fee payment and message dispatch through the LayerZero endpoint.
57
+ ///
58
+ /// # Important
59
+ /// This trait is intended to be used as an **internal helper** only. Do **NOT** expose these
60
+ /// methods as part of your contract's public interface (i.e., do not use `#[contract_impl]` on
61
+ /// the implementation of this trait). Instead, call these methods internally from your
62
+ /// contract's own public functions.
63
+ pub trait OAppSenderInternal: OAppCore {
64
+ /// Quote the messaging fee for sending a message to the other chain
65
+ ///
66
+ /// # Arguments
67
+ /// * `dst_eid`: The destination endpoint ID
68
+ /// * `message`: The message to send
69
+ /// * `options`: The options for the message
70
+ /// * `pay_in_zro`: Whether to pay the fee in ZRO
71
+ ///
72
+ /// # Returns
73
+ /// * `MessagingFee`: The messaging fee for the message
74
+ fn __quote(env: &Env, dst_eid: u32, message: &Bytes, options: &Bytes, pay_in_zro: bool) -> MessagingFee {
75
+ let receiver = get_peer_or_panic::<Self>(env, dst_eid);
76
+ endpoint_client::<Self>(env).quote(
77
+ &env.current_contract_address(),
78
+ &MessagingParams { dst_eid, receiver, message: message.clone(), options: options.clone(), pay_in_zro },
79
+ )
80
+ }
81
+
82
+ /// Send a message to the other chain
83
+ ///
84
+ /// # Arguments
85
+ /// * `dst_eid`: The destination endpoint ID
86
+ /// * `message`: The message to send
87
+ /// * `options`: The options for the message
88
+ /// * `fee_payer`: The fee payer, wrapped in [`FeePayer`] to indicate authorization state.
89
+ /// Use `FeePayer::Unverified(addr)` if auth has not been checked (safe default),
90
+ /// or `FeePayer::Verified(addr)` if `addr.require_auth()` was already called by the caller.
91
+ /// * `fee`: The messaging fee
92
+ /// * `refund_address`: The address to receive any excess fees
93
+ ///
94
+ /// # Returns
95
+ /// * `MessagingReceipt`: The receipt for the sent message
96
+ fn __lz_send(
97
+ env: &Env,
98
+ dst_eid: u32,
99
+ message: &Bytes,
100
+ options: &Bytes,
101
+ fee_payer: &FeePayer,
102
+ fee: &MessagingFee,
103
+ refund_address: &Address,
104
+ ) -> MessagingReceipt {
105
+ // Enforce fee payer authorization if not already verified by the caller
106
+ let payer = match fee_payer {
107
+ FeePayer::Unverified(addr) => {
108
+ addr.require_auth();
109
+ addr
110
+ }
111
+ FeePayer::Verified(addr) => addr,
112
+ };
113
+
114
+ // Pay the messaging fees
115
+ Self::__pay_native(env, payer, fee.native_fee);
116
+ // Skip the ZRO payment call only when the fee is exactly zero. Using `!= 0` instead of
117
+ // `> 0` so that an invalid negative value still reaches `__pay_zro` and fails loudly
118
+ // rather than being silently ignored.
119
+ let pay_in_zro = fee.zro_fee != 0;
120
+ if pay_in_zro {
121
+ Self::__pay_zro(env, payer, fee.zro_fee);
122
+ }
123
+
124
+ // Send the message to the other chain
125
+ let receiver = get_peer_or_panic::<Self>(env, dst_eid);
126
+ endpoint_client::<Self>(env).send(
127
+ &env.current_contract_address(),
128
+ &MessagingParams { dst_eid, receiver, message: message.clone(), options: options.clone(), pay_in_zro },
129
+ refund_address,
130
+ )
131
+ }
132
+
133
+ /// Pay the native fee to the endpoint for sending a message to the other chain
134
+ ///
135
+ /// # Arguments
136
+ /// * `fee_payer`: The address of the fee payer
137
+ /// * `native_fee`: The native fee to pay
138
+ fn __pay_native(env: &Env, fee_payer: &Address, native_fee: i128) {
139
+ let token_client = TokenClient::new(env, &endpoint_client::<Self>(env).native_token());
140
+ token_client.transfer(fee_payer, Self::endpoint(env), &native_fee);
141
+ }
142
+
143
+ /// Pay the ZRO fee to the endpoint for sending a message to the other chain
144
+ ///
145
+ /// # Arguments
146
+ /// * `fee_payer`: The address of the fee payer
147
+ /// * `zro_fee`: The ZRO fee to pay
148
+ fn __pay_zro(env: &Env, fee_payer: &Address, zro_fee: i128) {
149
+ let zro_token = endpoint_client::<Self>(env).zro().unwrap_or_panic(env, OAppError::ZroTokenUnavailable);
150
+ TokenClient::new(env, &zro_token).transfer(fee_payer, Self::endpoint(env), &zro_fee);
151
+ }
152
+ }
@@ -0,0 +1,5 @@
1
+ mod oapp_core;
2
+ mod oapp_options_type3;
3
+ mod oapp_receiver;
4
+ mod oapp_sender;
5
+ mod test_macros;
@@ -0,0 +1,225 @@
1
+ use crate::{self as oapp, oapp_core::PeerSet, oapp_receiver::LzReceiveInternal};
2
+ use endpoint_v2::Origin;
3
+ use soroban_sdk::{
4
+ contract, contractimpl, contracttype,
5
+ testutils::{Address as _, MockAuth, MockAuthInvoke},
6
+ Address, Bytes, BytesN, Env, IntoVal,
7
+ };
8
+ use utils::testing_utils::assert_eq_event;
9
+
10
+ #[contract]
11
+ pub struct DummyEndpoint;
12
+
13
+ #[derive(Clone)]
14
+ #[contracttype]
15
+ enum DummyEndpointDataKey {
16
+ Delegate(Address),
17
+ }
18
+
19
+ #[contractimpl]
20
+ impl DummyEndpoint {
21
+ pub fn set_delegate(env: Env, oapp: &Address, delegate: &Option<Address>) {
22
+ let key = DummyEndpointDataKey::Delegate(oapp.clone());
23
+ match delegate {
24
+ Some(d) => env.storage().persistent().set(&key, d),
25
+ None => env.storage().persistent().remove(&key),
26
+ }
27
+ }
28
+
29
+ pub fn get_delegate(env: Env, oapp: Address) -> Option<Address> {
30
+ env.storage().persistent().get(&DummyEndpointDataKey::Delegate(oapp))
31
+ }
32
+ }
33
+
34
+ #[oapp_macros::oapp]
35
+ #[common_macros::lz_contract]
36
+ pub struct DummyOApp;
37
+
38
+ impl LzReceiveInternal for DummyOApp {
39
+ fn __lz_receive(
40
+ _env: &Env,
41
+ _origin: &Origin,
42
+ _guid: &BytesN<32>,
43
+ _message: &Bytes,
44
+ _extra_data: &Bytes,
45
+ _executor: &Address,
46
+ _value: i128,
47
+ ) {
48
+ // Not used in core tests
49
+ }
50
+ }
51
+
52
+ #[contractimpl]
53
+ impl DummyOApp {
54
+ pub fn __constructor(env: &Env, owner: &Address, endpoint: &Address) {
55
+ oapp::oapp_core::init_ownable_oapp::<Self>(env, owner, endpoint, owner);
56
+ }
57
+ }
58
+
59
+ const REMOTE_EID: u32 = 100;
60
+ const UNSET_EID: u32 = 999;
61
+
62
+ struct TestSetup<'a> {
63
+ env: Env,
64
+ owner: Address,
65
+ endpoint: Address,
66
+ oapp_client: DummyOAppClient<'a>,
67
+ }
68
+
69
+ fn setup<'a>() -> TestSetup<'a> {
70
+ let env = Env::default();
71
+
72
+ let owner = Address::generate(&env);
73
+ soroban_sdk::log!(&env, "owner: {}", owner);
74
+ let endpoint = env.register(DummyEndpoint, ());
75
+ soroban_sdk::log!(&env, "endpoint: {}", endpoint);
76
+ let oapp = env.register(DummyOApp, (&owner, &endpoint));
77
+ soroban_sdk::log!(&env, "oapp: {}", oapp);
78
+ let oapp_client = DummyOAppClient::new(&env, &oapp);
79
+
80
+ TestSetup { env, owner, endpoint, oapp_client }
81
+ }
82
+
83
+ fn set_peer_with_auth(
84
+ env: &Env,
85
+ signer: &Address,
86
+ oapp_client: &DummyOAppClient<'_>,
87
+ eid: u32,
88
+ peer: &Option<BytesN<32>>,
89
+ ) {
90
+ env.mock_auths(&[MockAuth {
91
+ address: signer,
92
+ invoke: &MockAuthInvoke {
93
+ contract: &oapp_client.address,
94
+ fn_name: "set_peer",
95
+ args: (&eid, peer, signer).into_val(env),
96
+ sub_invokes: &[],
97
+ },
98
+ }]);
99
+ oapp_client.set_peer(&eid, peer, signer);
100
+ }
101
+
102
+ fn set_delegate_with_auth(env: &Env, signer: &Address, oapp_client: &DummyOAppClient<'_>, delegate: &Option<Address>) {
103
+ env.mock_auths(&[MockAuth {
104
+ address: signer,
105
+ invoke: &MockAuthInvoke {
106
+ contract: &oapp_client.address,
107
+ fn_name: "set_delegate",
108
+ args: (delegate, signer).into_val(env),
109
+ sub_invokes: &[],
110
+ },
111
+ }]);
112
+ oapp_client.set_delegate(delegate, signer);
113
+ }
114
+
115
+ #[test]
116
+ fn test_constructor_initializes_owner_and_endpoint_and_delegate() {
117
+ let TestSetup { env, owner, endpoint, oapp_client } = setup();
118
+
119
+ // owner initialized via oapp_initialize -> init_owner
120
+ assert_eq!(Some(owner.clone()), oapp_client.owner());
121
+
122
+ // endpoint stored via OAppCoreStorage::set_endpoint
123
+ assert_eq!(endpoint, oapp_client.endpoint());
124
+
125
+ // delegate set via oapp_initialize(..., owner) -> endpoint.set_delegate(..., Some(owner))
126
+ let endpoint_client = DummyEndpointClient::new(&env, &endpoint);
127
+ assert_eq!(Some(owner), endpoint_client.get_delegate(&oapp_client.address));
128
+ }
129
+
130
+ #[test]
131
+ fn test_oapp_version_defaults_to_zero() {
132
+ let TestSetup { oapp_client, .. } = setup();
133
+ assert_eq!((1, 1), oapp_client.oapp_version());
134
+ }
135
+
136
+ #[test]
137
+ fn test_peer_lifecycle_set_get_update_remove_and_events() {
138
+ let TestSetup { env, owner, oapp_client, .. } = setup();
139
+
140
+ // Unset cases
141
+ assert_eq!(None, oapp_client.peer(&UNSET_EID));
142
+ assert_eq!(None, oapp_client.peer(&REMOTE_EID));
143
+
144
+ // Set peer v1
145
+ let peer_v1: BytesN<32> = BytesN::from_array(&env, &[33; 32]);
146
+ let peer_v1_option = Some(peer_v1.clone());
147
+ set_peer_with_auth(&env, &owner, &oapp_client, REMOTE_EID, &peer_v1_option);
148
+
149
+ assert_eq_event(&env, &oapp_client.address, PeerSet { eid: REMOTE_EID, peer: Some(peer_v1.clone()) });
150
+ assert_eq!(Some(peer_v1), oapp_client.peer(&REMOTE_EID));
151
+ assert_eq!(None, oapp_client.peer(&UNSET_EID));
152
+
153
+ // Update to peer v2
154
+ let peer_v2: BytesN<32> = BytesN::from_array(&env, &[2; 32]);
155
+ let peer_v2_option = Some(peer_v2.clone());
156
+ set_peer_with_auth(&env, &owner, &oapp_client, REMOTE_EID, &peer_v2_option);
157
+
158
+ assert_eq_event(&env, &oapp_client.address, PeerSet { eid: REMOTE_EID, peer: Some(peer_v2.clone()) });
159
+ assert_eq!(Some(peer_v2), oapp_client.peer(&REMOTE_EID));
160
+
161
+ // Remove peer
162
+ let none_peer: Option<BytesN<32>> = None;
163
+ set_peer_with_auth(&env, &owner, &oapp_client, REMOTE_EID, &none_peer);
164
+ assert_eq_event(&env, &oapp_client.address, PeerSet { eid: REMOTE_EID, peer: None });
165
+ assert_eq!(None, oapp_client.peer(&REMOTE_EID));
166
+ }
167
+
168
+ #[test]
169
+ #[should_panic(expected = "HostError: Error(Auth, InvalidAction)")]
170
+ fn test_set_peer_unauthorized() {
171
+ let TestSetup { env, owner, oapp_client, .. } = setup();
172
+
173
+ let test_peer: BytesN<32> = BytesN::from_array(&env, &[33; 32]);
174
+ oapp_client.set_peer(&REMOTE_EID, &Some(test_peer), &owner);
175
+ }
176
+
177
+ #[test]
178
+ #[should_panic(expected = "Error(Contract, #1086)")] // RbacError::Unauthorized
179
+ fn test_set_peer_non_owner_authorized() {
180
+ let TestSetup { env, owner, oapp_client, .. } = setup();
181
+ let non_owner = Address::generate(&env);
182
+ assert!(non_owner != owner);
183
+
184
+ let peer: BytesN<32> = BytesN::from_array(&env, &[33; 32]);
185
+ let peer_option = Some(peer);
186
+ set_peer_with_auth(&env, &non_owner, &oapp_client, REMOTE_EID, &peer_option);
187
+ }
188
+
189
+ #[test]
190
+ fn test_set_delegate_updates_and_clears_endpoint_delegate() {
191
+ let TestSetup { env, owner, endpoint, oapp_client } = setup();
192
+
193
+ let delegate = Address::generate(&env);
194
+ let delegate_option = Some(delegate.clone());
195
+ set_delegate_with_auth(&env, &owner, &oapp_client, &delegate_option);
196
+
197
+ let endpoint_client = DummyEndpointClient::new(&env, &endpoint);
198
+ assert_eq!(Some(delegate), endpoint_client.get_delegate(&oapp_client.address));
199
+
200
+ // Clear delegate
201
+ let none_delegate: Option<Address> = None;
202
+ set_delegate_with_auth(&env, &owner, &oapp_client, &none_delegate);
203
+ assert_eq!(None, endpoint_client.get_delegate(&oapp_client.address));
204
+ }
205
+
206
+ #[test]
207
+ #[should_panic(expected = "HostError: Error(Auth, InvalidAction)")]
208
+ fn test_set_delegate_unauthorized() {
209
+ let TestSetup { env, owner, oapp_client, .. } = setup();
210
+
211
+ let delegate = Address::generate(&env);
212
+ oapp_client.set_delegate(&Some(delegate), &owner);
213
+ }
214
+
215
+ #[test]
216
+ #[should_panic(expected = "Error(Contract, #1086)")] // RbacError::Unauthorized
217
+ fn test_set_delegate_non_owner_authorized() {
218
+ let TestSetup { env, owner, oapp_client, .. } = setup();
219
+ let non_owner = Address::generate(&env);
220
+ assert!(non_owner != owner);
221
+
222
+ let delegate = Address::generate(&env);
223
+ let delegate_option = Some(delegate);
224
+ set_delegate_with_auth(&env, &non_owner, &oapp_client, &delegate_option);
225
+ }