@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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
use crate::{
|
|
2
|
+
self as oapp,
|
|
3
|
+
errors::OAppError,
|
|
4
|
+
oapp_core::OAppCore,
|
|
5
|
+
oapp_options_type3::{EnforcedOptionParam, EnforcedOptionSet},
|
|
6
|
+
oapp_receiver::{LzReceiveInternal, OAppReceiver},
|
|
7
|
+
};
|
|
8
|
+
use common_macros::contract_impl;
|
|
9
|
+
use soroban_sdk::{
|
|
10
|
+
contract, contractimpl,
|
|
11
|
+
testutils::{Address as _, MockAuth, MockAuthInvoke},
|
|
12
|
+
vec, Address, Bytes, BytesN, Env, IntoVal,
|
|
13
|
+
};
|
|
14
|
+
use utils::testing_utils::assert_eq_event;
|
|
15
|
+
|
|
16
|
+
const OPTION_TYPE_3: u32 = 3;
|
|
17
|
+
const REMOTE_EID_1: u32 = 100;
|
|
18
|
+
const REMOTE_EID_2: u32 = 200;
|
|
19
|
+
const MSG_TYPE_SEND: u32 = 1;
|
|
20
|
+
const MSG_TYPE_RECEIVE: u32 = 2;
|
|
21
|
+
|
|
22
|
+
#[contract]
|
|
23
|
+
pub struct DummyEndpoint;
|
|
24
|
+
|
|
25
|
+
#[contractimpl]
|
|
26
|
+
impl DummyEndpoint {
|
|
27
|
+
pub fn set_delegate(_env: Env, _oapp: &Address, _delegate: &Option<Address>) {
|
|
28
|
+
// do nothing
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[oapp_macros::oapp(custom = [core, sender, receiver])]
|
|
33
|
+
#[common_macros::lz_contract]
|
|
34
|
+
pub struct DummyOAppOptionsType3;
|
|
35
|
+
|
|
36
|
+
#[contract_impl(contracttrait)]
|
|
37
|
+
impl utils::rbac::RoleBasedAccessControl for DummyOAppOptionsType3 {}
|
|
38
|
+
|
|
39
|
+
#[contract_impl(contracttrait)]
|
|
40
|
+
impl OAppCore for DummyOAppOptionsType3 {}
|
|
41
|
+
|
|
42
|
+
impl LzReceiveInternal for DummyOAppOptionsType3 {
|
|
43
|
+
fn __lz_receive(
|
|
44
|
+
_env: &Env,
|
|
45
|
+
_origin: &endpoint_v2::Origin,
|
|
46
|
+
_guid: &BytesN<32>,
|
|
47
|
+
_message: &Bytes,
|
|
48
|
+
_extra_data: &Bytes,
|
|
49
|
+
_executor: &Address,
|
|
50
|
+
_value: i128,
|
|
51
|
+
) {
|
|
52
|
+
// Dummy implementation for testing
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[contract_impl(contracttrait)]
|
|
57
|
+
impl OAppReceiver for DummyOAppOptionsType3 {}
|
|
58
|
+
|
|
59
|
+
#[contract_impl]
|
|
60
|
+
impl DummyOAppOptionsType3 {
|
|
61
|
+
pub fn __constructor(env: &Env, owner: &Address, endpoint: &Address, delegate: &Address) {
|
|
62
|
+
oapp::oapp_core::init_ownable_oapp::<Self>(env, owner, endpoint, delegate);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
struct TestSetup<'a> {
|
|
67
|
+
env: Env,
|
|
68
|
+
owner: Address,
|
|
69
|
+
oapp_client: DummyOAppOptionsType3Client<'a>,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
fn setup<'a>() -> TestSetup<'a> {
|
|
73
|
+
let env = Env::default();
|
|
74
|
+
|
|
75
|
+
let owner = Address::generate(&env);
|
|
76
|
+
let endpoint = env.register(DummyEndpoint, ());
|
|
77
|
+
let delegate = owner.clone();
|
|
78
|
+
let oapp = env.register(DummyOAppOptionsType3, (&owner, &endpoint, &delegate));
|
|
79
|
+
let oapp_client = DummyOAppOptionsType3Client::new(&env, &oapp);
|
|
80
|
+
|
|
81
|
+
TestSetup { env, owner, oapp_client }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn create_valid_options(env: &Env, data: &[u8]) -> Bytes {
|
|
85
|
+
let mut buffer = Bytes::from_array(env, &(OPTION_TYPE_3 as u16).to_be_bytes());
|
|
86
|
+
buffer.extend_from_slice(data);
|
|
87
|
+
buffer
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn set_enforced_options_with_auth(
|
|
91
|
+
env: &Env,
|
|
92
|
+
signer: &Address,
|
|
93
|
+
oapp_client: &DummyOAppOptionsType3Client<'_>,
|
|
94
|
+
enforced_params: &soroban_sdk::Vec<EnforcedOptionParam>,
|
|
95
|
+
) {
|
|
96
|
+
env.mock_auths(&[MockAuth {
|
|
97
|
+
address: signer,
|
|
98
|
+
invoke: &MockAuthInvoke {
|
|
99
|
+
contract: &oapp_client.address,
|
|
100
|
+
fn_name: "set_enforced_options",
|
|
101
|
+
args: (enforced_params, signer).into_val(env),
|
|
102
|
+
sub_invokes: &[],
|
|
103
|
+
},
|
|
104
|
+
}]);
|
|
105
|
+
oapp_client.set_enforced_options(enforced_params, signer);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[test]
|
|
109
|
+
fn test_enforced_options_lifecycle() {
|
|
110
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
111
|
+
|
|
112
|
+
// Unset returns None
|
|
113
|
+
assert_eq!(oapp_client.enforced_options(&999, &999), None);
|
|
114
|
+
|
|
115
|
+
// Set enforced options for different eid/msg_type combinations
|
|
116
|
+
let enforced1 = create_valid_options(&env, &[1, 2, 3, 4]);
|
|
117
|
+
let enforced2 = create_valid_options(&env, &[5, 6, 7, 8]);
|
|
118
|
+
let enforced_params = vec![
|
|
119
|
+
&env,
|
|
120
|
+
EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(enforced1.clone()) },
|
|
121
|
+
EnforcedOptionParam { eid: REMOTE_EID_2, msg_type: MSG_TYPE_RECEIVE, options: Some(enforced2.clone()) },
|
|
122
|
+
];
|
|
123
|
+
set_enforced_options_with_auth(&env, &owner, &oapp_client, &enforced_params);
|
|
124
|
+
|
|
125
|
+
// assert events
|
|
126
|
+
assert_eq_event(&env, &oapp_client.address, EnforcedOptionSet { enforced_options: enforced_params.clone() });
|
|
127
|
+
|
|
128
|
+
// Verify options were set correctly
|
|
129
|
+
assert_eq!(oapp_client.enforced_options(&REMOTE_EID_1, &MSG_TYPE_SEND), Some(enforced1.clone()));
|
|
130
|
+
assert_eq!(oapp_client.enforced_options(&REMOTE_EID_2, &MSG_TYPE_RECEIVE), Some(enforced2.clone()));
|
|
131
|
+
|
|
132
|
+
// Update enforced options for one combination
|
|
133
|
+
let updated = create_valid_options(&env, &[9, 8, 7, 6, 5]);
|
|
134
|
+
let update_params =
|
|
135
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(updated.clone()) }];
|
|
136
|
+
set_enforced_options_with_auth(&env, &owner, &oapp_client, &update_params);
|
|
137
|
+
assert_eq_event(&env, &oapp_client.address, EnforcedOptionSet { enforced_options: update_params.clone() });
|
|
138
|
+
assert_eq!(oapp_client.enforced_options(&REMOTE_EID_1, &MSG_TYPE_SEND), Some(updated));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn test_combine_options() {
|
|
143
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
144
|
+
|
|
145
|
+
// combine_options: no enforced -> returns extra (including empty)
|
|
146
|
+
let extra_only = create_valid_options(&env, &[9, 10, 11]);
|
|
147
|
+
assert_eq!(oapp_client.combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &extra_only), extra_only);
|
|
148
|
+
|
|
149
|
+
let empty_extra = Bytes::new(&env);
|
|
150
|
+
assert_eq!(oapp_client.combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &empty_extra), empty_extra);
|
|
151
|
+
|
|
152
|
+
// Set enforced options for the combine tests
|
|
153
|
+
let enforced = create_valid_options(&env, &[1, 2, 3, 4]);
|
|
154
|
+
let enforced_params =
|
|
155
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(enforced.clone()) }];
|
|
156
|
+
set_enforced_options_with_auth(&env, &owner, &oapp_client, &enforced_params);
|
|
157
|
+
|
|
158
|
+
// combine_options: enforced present -> empty extra returns enforced
|
|
159
|
+
assert_eq!(oapp_client.combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &Bytes::new(&env)), enforced.clone());
|
|
160
|
+
|
|
161
|
+
// combine_options: both present -> enforced + extra(without its 2-byte header)
|
|
162
|
+
let extra: Bytes = create_valid_options(&env, &[4, 5, 6]);
|
|
163
|
+
let combined = oapp_client.combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &extra);
|
|
164
|
+
let expected_combined = create_valid_options(&env, &[1, 2, 3, 4, 4, 5, 6]);
|
|
165
|
+
assert_eq!(combined, expected_combined);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#[test]
|
|
169
|
+
#[should_panic(expected = "HostError: Error(Auth, InvalidAction)")]
|
|
170
|
+
fn test_set_enforced_options_unauthorized() {
|
|
171
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
172
|
+
|
|
173
|
+
let options = create_valid_options(&env, &[1, 2, 3, 4]);
|
|
174
|
+
let enforced_params =
|
|
175
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(options.clone()) }];
|
|
176
|
+
oapp_client.set_enforced_options(&enforced_params, &owner);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[test]
|
|
180
|
+
#[should_panic(expected = "Error(Contract, #1086)")] // RbacError::Unauthorized
|
|
181
|
+
fn test_set_enforced_options_non_owner_authorized() {
|
|
182
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
183
|
+
let non_owner = Address::generate(&env);
|
|
184
|
+
assert!(non_owner != owner);
|
|
185
|
+
|
|
186
|
+
let options = create_valid_options(&env, &[1, 2, 3, 4]);
|
|
187
|
+
let enforced_params =
|
|
188
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(options.clone()) }];
|
|
189
|
+
|
|
190
|
+
set_enforced_options_with_auth(&env, &non_owner, &oapp_client, &enforced_params);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#[test]
|
|
194
|
+
fn test_set_enforced_options_invalid_options_returns_error() {
|
|
195
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
196
|
+
|
|
197
|
+
// wrong option type (not 3)
|
|
198
|
+
let mut invalid = Bytes::from_array(&env, &(4u16).to_be_bytes());
|
|
199
|
+
invalid.extend_from_slice(&[1, 2, 3]);
|
|
200
|
+
|
|
201
|
+
let enforced_params =
|
|
202
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(invalid) }];
|
|
203
|
+
|
|
204
|
+
env.mock_auths(&[MockAuth {
|
|
205
|
+
address: &owner,
|
|
206
|
+
invoke: &MockAuthInvoke {
|
|
207
|
+
contract: &oapp_client.address,
|
|
208
|
+
fn_name: "set_enforced_options",
|
|
209
|
+
args: (&enforced_params, &owner).into_val(&env),
|
|
210
|
+
sub_invokes: &[],
|
|
211
|
+
},
|
|
212
|
+
}]);
|
|
213
|
+
let result = oapp_client.try_set_enforced_options(&enforced_params, &owner);
|
|
214
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OAppError::InvalidOptions.into());
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
#[test]
|
|
218
|
+
fn test_combine_options_extra_invalid_type_returns_error_when_enforced_present() {
|
|
219
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
220
|
+
|
|
221
|
+
let enforced = create_valid_options(&env, &[1, 2, 3]);
|
|
222
|
+
let params =
|
|
223
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(enforced) }];
|
|
224
|
+
set_enforced_options_with_auth(&env, &owner, &oapp_client, ¶ms);
|
|
225
|
+
|
|
226
|
+
// extra has wrong option type (not 3) but len >= 2 -> validated and should panic
|
|
227
|
+
let mut extra_invalid = Bytes::from_array(&env, &(4u16).to_be_bytes());
|
|
228
|
+
extra_invalid.extend_from_slice(&[9, 9, 9]);
|
|
229
|
+
let result = oapp_client.try_combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &extra_invalid);
|
|
230
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OAppError::InvalidOptions.into());
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
fn test_combine_options_extra_too_short_returns_error_when_enforced_present() {
|
|
235
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
236
|
+
|
|
237
|
+
let enforced = create_valid_options(&env, &[1, 2, 3]);
|
|
238
|
+
let params =
|
|
239
|
+
vec![&env, EnforcedOptionParam { eid: REMOTE_EID_1, msg_type: MSG_TYPE_SEND, options: Some(enforced) }];
|
|
240
|
+
set_enforced_options_with_auth(&env, &owner, &oapp_client, ¶ms);
|
|
241
|
+
|
|
242
|
+
// extra is non-empty but len < 2 -> should panic
|
|
243
|
+
let extra_too_short = Bytes::from_array(&env, &[1u8]);
|
|
244
|
+
let result = oapp_client.try_combine_options(&REMOTE_EID_1, &MSG_TYPE_SEND, &extra_too_short);
|
|
245
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OAppError::InvalidOptions.into());
|
|
246
|
+
}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
use crate::{self as oapp, errors::OAppError, oapp_receiver::LzReceiveInternal};
|
|
2
|
+
use endpoint_v2::Origin;
|
|
3
|
+
use soroban_sdk::{
|
|
4
|
+
contract, contractimpl, symbol_short,
|
|
5
|
+
testutils::{Address as _, MockAuth, MockAuthInvoke},
|
|
6
|
+
token::StellarAssetClient,
|
|
7
|
+
Address, Bytes, BytesN, Env, IntoVal,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
#[oapp_macros::oapp]
|
|
11
|
+
#[common_macros::lz_contract]
|
|
12
|
+
pub struct DummyOAppReceiver;
|
|
13
|
+
|
|
14
|
+
impl LzReceiveInternal for DummyOAppReceiver {
|
|
15
|
+
fn __lz_receive(
|
|
16
|
+
_env: &Env,
|
|
17
|
+
_origin: &Origin,
|
|
18
|
+
_guid: &BytesN<32>,
|
|
19
|
+
_message: &Bytes,
|
|
20
|
+
_extra_data: &Bytes,
|
|
21
|
+
_executor: &Address,
|
|
22
|
+
_value: i128,
|
|
23
|
+
) {
|
|
24
|
+
// do nothing
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[contractimpl]
|
|
29
|
+
impl DummyOAppReceiver {
|
|
30
|
+
pub fn __constructor(env: &Env, owner: &Address, endpoint: &Address) {
|
|
31
|
+
oapp::oapp_core::init_ownable_oapp::<Self>(env, owner, endpoint, owner);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const REMOTE_EID: u32 = 100;
|
|
36
|
+
const UNSET_EID: u32 = 999;
|
|
37
|
+
|
|
38
|
+
// Mock Endpoint (minimal subset used by oapp_receiver::verify_and_clear_payload)
|
|
39
|
+
|
|
40
|
+
#[contract]
|
|
41
|
+
pub struct MockEndpoint;
|
|
42
|
+
|
|
43
|
+
#[contractimpl]
|
|
44
|
+
impl MockEndpoint {
|
|
45
|
+
pub fn __constructor(env: Env, native_token: Address) {
|
|
46
|
+
env.storage().instance().set(&symbol_short!("ntk"), &native_token);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn set_delegate(_env: Env, _oapp: &Address, _delegate: &Option<Address>) {
|
|
50
|
+
// do nothing
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
pub fn native_token(env: Env) -> Address {
|
|
54
|
+
env.storage().instance().get(&symbol_short!("ntk")).unwrap()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub fn clear(env: Env, caller: Address, origin: Origin, receiver: Address, guid: BytesN<32>, message: Bytes) {
|
|
58
|
+
// Record last clear for assertions
|
|
59
|
+
env.storage().instance().set(&symbol_short!("clr_c"), &caller);
|
|
60
|
+
env.storage().instance().set(&symbol_short!("clr_o"), &origin);
|
|
61
|
+
env.storage().instance().set(&symbol_short!("clr_r"), &receiver);
|
|
62
|
+
env.storage().instance().set(&symbol_short!("clr_g"), &guid);
|
|
63
|
+
env.storage().instance().set(&symbol_short!("clr_m"), &message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn last_clear(env: Env) -> (Address, Origin, Address, BytesN<32>, Bytes) {
|
|
67
|
+
(
|
|
68
|
+
env.storage().instance().get(&symbol_short!("clr_c")).unwrap(),
|
|
69
|
+
env.storage().instance().get(&symbol_short!("clr_o")).unwrap(),
|
|
70
|
+
env.storage().instance().get(&symbol_short!("clr_r")).unwrap(),
|
|
71
|
+
env.storage().instance().get(&symbol_short!("clr_g")).unwrap(),
|
|
72
|
+
env.storage().instance().get(&symbol_short!("clr_m")).unwrap(),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
struct TestSetup<'a> {
|
|
78
|
+
env: Env,
|
|
79
|
+
owner: Address,
|
|
80
|
+
endpoint: Address,
|
|
81
|
+
token_admin: Address,
|
|
82
|
+
native_token: Address,
|
|
83
|
+
native_token_admin_client: StellarAssetClient<'a>,
|
|
84
|
+
oapp_client: DummyOAppReceiverClient<'a>,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
fn setup<'a>() -> TestSetup<'a> {
|
|
88
|
+
let env = Env::default();
|
|
89
|
+
|
|
90
|
+
let owner = Address::generate(&env);
|
|
91
|
+
let token_admin = Address::generate(&env);
|
|
92
|
+
let native_token_sac = env.register_stellar_asset_contract_v2(token_admin.clone());
|
|
93
|
+
let native_token = native_token_sac.address();
|
|
94
|
+
let native_token_admin_client = StellarAssetClient::new(&env, &native_token);
|
|
95
|
+
|
|
96
|
+
let endpoint = env.register(MockEndpoint, (&native_token,));
|
|
97
|
+
let oapp = env.register(DummyOAppReceiver, (&owner, &endpoint));
|
|
98
|
+
let oapp_client = DummyOAppReceiverClient::new(&env, &oapp);
|
|
99
|
+
|
|
100
|
+
TestSetup { env, owner, endpoint, token_admin, native_token, native_token_admin_client, oapp_client }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn set_peer(env: &Env, owner: &Address, oapp_client: &DummyOAppReceiverClient<'_>, eid: u32, peer: &BytesN<32>) {
|
|
104
|
+
let peer_option = Some(peer.clone());
|
|
105
|
+
env.mock_auths(&[MockAuth {
|
|
106
|
+
address: owner,
|
|
107
|
+
invoke: &MockAuthInvoke {
|
|
108
|
+
contract: &oapp_client.address,
|
|
109
|
+
fn_name: "set_peer",
|
|
110
|
+
args: (&eid, &peer_option, owner).into_val(env),
|
|
111
|
+
sub_invokes: &[],
|
|
112
|
+
},
|
|
113
|
+
}]);
|
|
114
|
+
oapp_client.set_peer(&eid, &peer_option, owner);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn lz_receive(
|
|
118
|
+
env: &Env,
|
|
119
|
+
oapp_client: &DummyOAppReceiverClient<'_>,
|
|
120
|
+
executor: &Address,
|
|
121
|
+
origin: &Origin,
|
|
122
|
+
guid: &BytesN<32>,
|
|
123
|
+
message: &Bytes,
|
|
124
|
+
extra_data: &Bytes,
|
|
125
|
+
value: i128,
|
|
126
|
+
sub_invokes: &[MockAuthInvoke<'_>],
|
|
127
|
+
) {
|
|
128
|
+
env.mock_auths(&[MockAuth {
|
|
129
|
+
address: executor,
|
|
130
|
+
invoke: &MockAuthInvoke {
|
|
131
|
+
contract: &oapp_client.address,
|
|
132
|
+
fn_name: "lz_receive",
|
|
133
|
+
args: (executor, origin, guid, message, extra_data, value).into_val(env),
|
|
134
|
+
sub_invokes,
|
|
135
|
+
},
|
|
136
|
+
}]);
|
|
137
|
+
oapp_client.lz_receive(executor, origin, guid, message, extra_data, &value);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn test_is_compose_msg_sender() {
|
|
142
|
+
let TestSetup { env, oapp_client, .. } = setup();
|
|
143
|
+
|
|
144
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: BytesN::from_array(&env, &[1; 32]), nonce: 1 };
|
|
145
|
+
let message = Bytes::from_array(&env, &[1, 2, 3, 4]);
|
|
146
|
+
|
|
147
|
+
// Test with same contract address (should return true)
|
|
148
|
+
let result = oapp_client.is_compose_msg_sender(&origin, &message, &oapp_client.address);
|
|
149
|
+
assert_eq!(result, true);
|
|
150
|
+
let different_sender = Address::generate(&env);
|
|
151
|
+
|
|
152
|
+
// Test with different sender address (should return false)
|
|
153
|
+
assert_eq!(oapp_client.is_compose_msg_sender(&origin, &message, &different_sender), false);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#[test]
|
|
157
|
+
fn test_allow_initialize_path_cases() {
|
|
158
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
159
|
+
|
|
160
|
+
// no peer set -> false
|
|
161
|
+
let origin_no_peer = Origin { src_eid: UNSET_EID, sender: BytesN::from_array(&env, &[5; 32]), nonce: 1 };
|
|
162
|
+
assert_eq!(oapp_client.allow_initialize_path(&origin_no_peer), false);
|
|
163
|
+
|
|
164
|
+
// set peer for REMOTE_EID
|
|
165
|
+
let peer_bytes: BytesN<32> = BytesN::from_array(&env, &[5; 32]);
|
|
166
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &peer_bytes);
|
|
167
|
+
|
|
168
|
+
// matching -> true
|
|
169
|
+
let origin_match = Origin { src_eid: REMOTE_EID, sender: peer_bytes.clone(), nonce: 1 };
|
|
170
|
+
assert_eq!(oapp_client.allow_initialize_path(&origin_match), true);
|
|
171
|
+
|
|
172
|
+
// non-matching -> false
|
|
173
|
+
let origin_non_match = Origin { src_eid: REMOTE_EID, sender: BytesN::from_array(&env, &[6; 32]), nonce: 1 };
|
|
174
|
+
assert_eq!(oapp_client.allow_initialize_path(&origin_non_match), false);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#[test]
|
|
178
|
+
fn test_lz_receive_verifies_peer_and_calls_clear_value_zero() {
|
|
179
|
+
let TestSetup { env, owner, endpoint, oapp_client, .. } = setup();
|
|
180
|
+
|
|
181
|
+
// Configure peer
|
|
182
|
+
let peer: BytesN<32> = BytesN::from_array(&env, &[7; 32]);
|
|
183
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &peer);
|
|
184
|
+
|
|
185
|
+
let executor = Address::generate(&env);
|
|
186
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: peer, nonce: 1 };
|
|
187
|
+
let guid = BytesN::from_array(&env, &[2u8; 32]);
|
|
188
|
+
let message = Bytes::from_array(&env, &[1, 2, 3]);
|
|
189
|
+
let extra_data = Bytes::new(&env);
|
|
190
|
+
lz_receive(&env, &oapp_client, &executor, &origin, &guid, &message, &extra_data, 0, &[]);
|
|
191
|
+
|
|
192
|
+
let endpoint_client = MockEndpointClient::new(&env, &endpoint);
|
|
193
|
+
let (caller, cleared_origin, receiver, cleared_guid, cleared_message) = endpoint_client.last_clear();
|
|
194
|
+
assert_eq!(caller, oapp_client.address);
|
|
195
|
+
assert_eq!(cleared_origin, origin);
|
|
196
|
+
assert_eq!(receiver, oapp_client.address);
|
|
197
|
+
assert_eq!(cleared_guid, guid);
|
|
198
|
+
assert_eq!(cleared_message, message);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn test_lz_receive_transfers_native_token_when_value_positive() {
|
|
203
|
+
let TestSetup { env, owner, endpoint, native_token, native_token_admin_client, token_admin, oapp_client, .. } =
|
|
204
|
+
setup();
|
|
205
|
+
|
|
206
|
+
// Configure peer
|
|
207
|
+
let peer: BytesN<32> = BytesN::from_array(&env, &[9; 32]);
|
|
208
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &peer);
|
|
209
|
+
|
|
210
|
+
let executor = Address::generate(&env);
|
|
211
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: peer, nonce: 1 };
|
|
212
|
+
let guid = BytesN::from_array(&env, &[3u8; 32]);
|
|
213
|
+
let message = Bytes::from_array(&env, &[4, 5, 6]);
|
|
214
|
+
let extra_data = Bytes::new(&env);
|
|
215
|
+
let value: i128 = 123;
|
|
216
|
+
|
|
217
|
+
// Mint native token to executor (admin auth)
|
|
218
|
+
env.mock_auths(&[MockAuth {
|
|
219
|
+
address: &token_admin,
|
|
220
|
+
invoke: &MockAuthInvoke {
|
|
221
|
+
contract: &native_token,
|
|
222
|
+
fn_name: "mint",
|
|
223
|
+
args: (&executor, &value).into_val(&env),
|
|
224
|
+
sub_invokes: &[],
|
|
225
|
+
},
|
|
226
|
+
}]);
|
|
227
|
+
native_token_admin_client.mint(&executor, &value);
|
|
228
|
+
|
|
229
|
+
// lz_receive should transfer tokens from executor -> oapp
|
|
230
|
+
let transfer_invoke = MockAuthInvoke {
|
|
231
|
+
contract: &native_token,
|
|
232
|
+
fn_name: "transfer",
|
|
233
|
+
args: (&executor, &oapp_client.address, &value).into_val(&env),
|
|
234
|
+
sub_invokes: &[],
|
|
235
|
+
};
|
|
236
|
+
let sub_invokes = [transfer_invoke];
|
|
237
|
+
lz_receive(&env, &oapp_client, &executor, &origin, &guid, &message, &extra_data, value, &sub_invokes);
|
|
238
|
+
|
|
239
|
+
let endpoint_client = MockEndpointClient::new(&env, &endpoint);
|
|
240
|
+
// Asserts clear() was called (last_clear() will panic if not)
|
|
241
|
+
endpoint_client.last_clear();
|
|
242
|
+
|
|
243
|
+
let token_client = soroban_sdk::token::TokenClient::new(&env, &native_token);
|
|
244
|
+
assert_eq!(token_client.balance(&executor), 0);
|
|
245
|
+
assert_eq!(token_client.balance(&oapp_client.address), value);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[test]
|
|
249
|
+
fn test_lz_receive_rejects_negative_value_and_does_not_clear() {
|
|
250
|
+
let TestSetup { env, owner, endpoint, native_token, native_token_admin_client, token_admin, oapp_client, .. } =
|
|
251
|
+
setup();
|
|
252
|
+
|
|
253
|
+
// Configure peer
|
|
254
|
+
let peer: BytesN<32> = BytesN::from_array(&env, &[9; 32]);
|
|
255
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &peer);
|
|
256
|
+
|
|
257
|
+
let executor = Address::generate(&env);
|
|
258
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: peer, nonce: 1 };
|
|
259
|
+
let guid = BytesN::from_array(&env, &[3u8; 32]);
|
|
260
|
+
let message = Bytes::from_array(&env, &[4, 5, 6]);
|
|
261
|
+
let extra_data = Bytes::new(&env);
|
|
262
|
+
let value: i128 = -123;
|
|
263
|
+
|
|
264
|
+
// Fund executor with a positive balance so the only failure is the negative amount.
|
|
265
|
+
env.mock_auths(&[MockAuth {
|
|
266
|
+
address: &token_admin,
|
|
267
|
+
invoke: &MockAuthInvoke {
|
|
268
|
+
contract: &native_token,
|
|
269
|
+
fn_name: "mint",
|
|
270
|
+
args: (&executor, &(-value)).into_val(&env),
|
|
271
|
+
sub_invokes: &[],
|
|
272
|
+
},
|
|
273
|
+
}]);
|
|
274
|
+
native_token_admin_client.mint(&executor, &(-value));
|
|
275
|
+
|
|
276
|
+
// lz_receive should attempt transfer with a negative value and fail.
|
|
277
|
+
let transfer_invoke = MockAuthInvoke {
|
|
278
|
+
contract: &native_token,
|
|
279
|
+
fn_name: "transfer",
|
|
280
|
+
args: (&executor, &oapp_client.address, &value).into_val(&env),
|
|
281
|
+
sub_invokes: &[],
|
|
282
|
+
};
|
|
283
|
+
let sub_invokes = [transfer_invoke];
|
|
284
|
+
env.mock_auths(&[MockAuth {
|
|
285
|
+
address: &executor,
|
|
286
|
+
invoke: &MockAuthInvoke {
|
|
287
|
+
contract: &oapp_client.address,
|
|
288
|
+
fn_name: "lz_receive",
|
|
289
|
+
args: (&executor, &origin, &guid, &message, &extra_data, &value).into_val(&env),
|
|
290
|
+
sub_invokes: &sub_invokes,
|
|
291
|
+
},
|
|
292
|
+
}]);
|
|
293
|
+
|
|
294
|
+
let result = oapp_client.try_lz_receive(&executor, &origin, &guid, &message, &extra_data, &value);
|
|
295
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), soroban_sdk::Error::from_contract_error(8)); //negative amount is not allowed
|
|
296
|
+
|
|
297
|
+
// No balances changed.
|
|
298
|
+
let token_client = soroban_sdk::token::TokenClient::new(&env, &native_token);
|
|
299
|
+
assert_eq!(token_client.balance(&executor), -value);
|
|
300
|
+
assert_eq!(token_client.balance(&oapp_client.address), 0);
|
|
301
|
+
|
|
302
|
+
// Payload should not have been cleared.
|
|
303
|
+
let endpoint_client = MockEndpointClient::new(&env, &endpoint);
|
|
304
|
+
assert!(endpoint_client.try_last_clear().is_err());
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
#[test]
|
|
308
|
+
fn test_next_nonce_defaults_to_zero() {
|
|
309
|
+
let TestSetup { env, oapp_client, .. } = setup();
|
|
310
|
+
let sender = BytesN::from_array(&env, &[1u8; 32]);
|
|
311
|
+
assert_eq!(oapp_client.next_nonce(&REMOTE_EID, &sender), 0);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#[test]
|
|
315
|
+
#[should_panic(expected = "HostError: Error(Auth, InvalidAction)")]
|
|
316
|
+
fn test_lz_receive_requires_executor_auth() {
|
|
317
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
318
|
+
|
|
319
|
+
let peer: BytesN<32> = BytesN::from_array(&env, &[7; 32]);
|
|
320
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &peer);
|
|
321
|
+
|
|
322
|
+
// Call without executor auth mocked -> should fail at executor.require_auth()
|
|
323
|
+
let executor = Address::generate(&env);
|
|
324
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: peer, nonce: 1 };
|
|
325
|
+
let guid = BytesN::from_array(&env, &[2u8; 32]);
|
|
326
|
+
let message = Bytes::from_array(&env, &[1, 2, 3]);
|
|
327
|
+
let extra_data = Bytes::new(&env);
|
|
328
|
+
oapp_client.lz_receive(&executor, &origin, &guid, &message, &extra_data, &0);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
#[test]
|
|
332
|
+
fn test_lz_receive_wrong_peer_returns_only_peer_error() {
|
|
333
|
+
let TestSetup { env, owner, oapp_client, .. } = setup();
|
|
334
|
+
|
|
335
|
+
let configured_peer: BytesN<32> = BytesN::from_array(&env, &[7; 32]);
|
|
336
|
+
set_peer(&env, &owner, &oapp_client, REMOTE_EID, &configured_peer);
|
|
337
|
+
|
|
338
|
+
let executor = Address::generate(&env);
|
|
339
|
+
let wrong_sender: BytesN<32> = BytesN::from_array(&env, &[8; 32]);
|
|
340
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: wrong_sender, nonce: 1 };
|
|
341
|
+
let guid = BytesN::from_array(&env, &[2u8; 32]);
|
|
342
|
+
let message = Bytes::from_array(&env, &[1, 2, 3]);
|
|
343
|
+
let extra_data = Bytes::new(&env);
|
|
344
|
+
let value: i128 = 0;
|
|
345
|
+
|
|
346
|
+
env.mock_auths(&[MockAuth {
|
|
347
|
+
address: &executor,
|
|
348
|
+
invoke: &MockAuthInvoke {
|
|
349
|
+
contract: &oapp_client.address,
|
|
350
|
+
fn_name: "lz_receive",
|
|
351
|
+
args: (&executor, &origin, &guid, &message, &extra_data, &value).into_val(&env),
|
|
352
|
+
sub_invokes: &[],
|
|
353
|
+
},
|
|
354
|
+
}]);
|
|
355
|
+
let result = oapp_client.try_lz_receive(&executor, &origin, &guid, &message, &extra_data, &value);
|
|
356
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OAppError::OnlyPeer.into());
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[test]
|
|
360
|
+
fn test_lz_receive_no_peer_returns_no_peer_error() {
|
|
361
|
+
let TestSetup { env, oapp_client, .. } = setup();
|
|
362
|
+
|
|
363
|
+
let executor = Address::generate(&env);
|
|
364
|
+
let origin = Origin { src_eid: REMOTE_EID, sender: BytesN::from_array(&env, &[1; 32]), nonce: 1 };
|
|
365
|
+
let guid = BytesN::from_array(&env, &[2u8; 32]);
|
|
366
|
+
let message = Bytes::from_array(&env, &[1, 2, 3]);
|
|
367
|
+
let extra_data = Bytes::new(&env);
|
|
368
|
+
let value: i128 = 0;
|
|
369
|
+
|
|
370
|
+
env.mock_auths(&[MockAuth {
|
|
371
|
+
address: &executor,
|
|
372
|
+
invoke: &MockAuthInvoke {
|
|
373
|
+
contract: &oapp_client.address,
|
|
374
|
+
fn_name: "lz_receive",
|
|
375
|
+
args: (&executor, &origin, &guid, &message, &extra_data, &value).into_val(&env),
|
|
376
|
+
sub_invokes: &[],
|
|
377
|
+
},
|
|
378
|
+
}]);
|
|
379
|
+
|
|
380
|
+
// No peer configured -> should fail with NoPeer since no peer is set for the source eid
|
|
381
|
+
let result = oapp_client.try_lz_receive(&executor, &origin, &guid, &message, &extra_data, &value);
|
|
382
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OAppError::NoPeer.into());
|
|
383
|
+
}
|