@layerzerolabs/oft-core-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 +38 -0
- package/LICENSE +23 -0
- package/clippy.toml +6 -0
- package/package.json +54 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +15 -0
- package/src/codec/mod.rs +2 -0
- package/src/codec/oft_compose_msg_codec.rs +55 -0
- package/src/codec/oft_msg_codec.rs +58 -0
- package/src/errors.rs +15 -0
- package/src/events.rs +32 -0
- package/src/lib.rs +22 -0
- package/src/oft_core.rs +594 -0
- package/src/storage.rs +19 -0
- package/src/tests/mod.rs +14 -0
- package/src/tests/test_decimals.rs +124 -0
- package/src/tests/test_lz_receive.rs +282 -0
- package/src/tests/test_msg_inspector.rs +324 -0
- package/src/tests/test_oft_compose_msg_codec.rs +68 -0
- package/src/tests/test_oft_msg_codec.rs +64 -0
- package/src/tests/test_oft_version_and_approval.rs +22 -0
- package/src/tests/test_quote_oft.rs +159 -0
- package/src/tests/test_quote_send.rs +195 -0
- package/src/tests/test_resolve_address.rs +37 -0
- package/src/tests/test_send.rs +915 -0
- package/src/tests/test_token.rs +47 -0
- package/src/tests/test_utils.rs +870 -0
- package/src/types.rs +58 -0
- package/src/utils.rs +71 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
//! Tests for the OApp Message Inspector feature.
|
|
2
|
+
|
|
3
|
+
use crate::tests::test_utils::{create_send_param, OFTTestSetup};
|
|
4
|
+
use common_macros::contract_error;
|
|
5
|
+
use endpoint_v2::MessagingFee;
|
|
6
|
+
use oapp::IOAppMsgInspector;
|
|
7
|
+
use soroban_sdk::{
|
|
8
|
+
contract, contractimpl, panic_with_error,
|
|
9
|
+
testutils::{Address as _, MockAuth, MockAuthInvoke},
|
|
10
|
+
Address, Bytes, BytesN, Env, IntoVal,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// ==================== Mock Inspector Contracts ====================
|
|
14
|
+
|
|
15
|
+
/// A mock inspector that always passes (does nothing)
|
|
16
|
+
#[contract]
|
|
17
|
+
pub struct PassingInspector;
|
|
18
|
+
|
|
19
|
+
#[contractimpl]
|
|
20
|
+
impl IOAppMsgInspector for PassingInspector {
|
|
21
|
+
fn inspect(_env: &Env, _oapp: &Address, _message: &Bytes, _options: &Bytes) -> bool {
|
|
22
|
+
// Do nothing - inspection passes
|
|
23
|
+
true
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[contract_error]
|
|
28
|
+
pub enum InspectorError {
|
|
29
|
+
InspectionFailed = 1,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// A mock inspector that always fails (panics)
|
|
33
|
+
#[contract]
|
|
34
|
+
pub struct FailingInspector;
|
|
35
|
+
|
|
36
|
+
#[contractimpl]
|
|
37
|
+
impl IOAppMsgInspector for FailingInspector {
|
|
38
|
+
fn inspect(env: &Env, _oapp: &Address, _message: &Bytes, _options: &Bytes) -> bool {
|
|
39
|
+
panic_with_error!(env, InspectorError::InspectionFailed);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ==================== Storage Tests ====================
|
|
44
|
+
|
|
45
|
+
#[test]
|
|
46
|
+
fn test_msg_inspector_not_set_by_default() {
|
|
47
|
+
let env = Env::default();
|
|
48
|
+
let setup = OFTTestSetup::new(&env);
|
|
49
|
+
|
|
50
|
+
// msg_inspector should be None by default
|
|
51
|
+
let inspector = setup.oft.msg_inspector();
|
|
52
|
+
assert_eq!(inspector, None);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[test]
|
|
56
|
+
fn test_set_msg_inspector() {
|
|
57
|
+
let env = Env::default();
|
|
58
|
+
let setup = OFTTestSetup::new(&env);
|
|
59
|
+
|
|
60
|
+
// Deploy a passing inspector
|
|
61
|
+
let inspector_address = env.register(PassingInspector, ());
|
|
62
|
+
|
|
63
|
+
// Owner (authorizer) sets the inspector
|
|
64
|
+
env.mock_auths(&[MockAuth {
|
|
65
|
+
address: &setup.owner,
|
|
66
|
+
invoke: &MockAuthInvoke {
|
|
67
|
+
contract: &setup.oft.address,
|
|
68
|
+
fn_name: "set_msg_inspector",
|
|
69
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
70
|
+
sub_invokes: &[],
|
|
71
|
+
},
|
|
72
|
+
}]);
|
|
73
|
+
setup.oft.set_msg_inspector(&Some(inspector_address.clone()), &setup.owner);
|
|
74
|
+
|
|
75
|
+
// Verify inspector is set
|
|
76
|
+
let stored_inspector = setup.oft.msg_inspector();
|
|
77
|
+
assert_eq!(stored_inspector, Some(inspector_address));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[test]
|
|
81
|
+
fn test_remove_msg_inspector() {
|
|
82
|
+
let env = Env::default();
|
|
83
|
+
let setup = OFTTestSetup::new(&env);
|
|
84
|
+
|
|
85
|
+
// Deploy and set a passing inspector first
|
|
86
|
+
let inspector_address = env.register(PassingInspector, ());
|
|
87
|
+
|
|
88
|
+
env.mock_auths(&[MockAuth {
|
|
89
|
+
address: &setup.owner,
|
|
90
|
+
invoke: &MockAuthInvoke {
|
|
91
|
+
contract: &setup.oft.address,
|
|
92
|
+
fn_name: "set_msg_inspector",
|
|
93
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
94
|
+
sub_invokes: &[],
|
|
95
|
+
},
|
|
96
|
+
}]);
|
|
97
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
98
|
+
|
|
99
|
+
// Verify it's set
|
|
100
|
+
assert!(setup.oft.msg_inspector().is_some());
|
|
101
|
+
|
|
102
|
+
// Remove the inspector by setting to None
|
|
103
|
+
env.mock_auths(&[MockAuth {
|
|
104
|
+
address: &setup.owner,
|
|
105
|
+
invoke: &MockAuthInvoke {
|
|
106
|
+
contract: &setup.oft.address,
|
|
107
|
+
fn_name: "set_msg_inspector",
|
|
108
|
+
args: (&None::<Address>, &setup.owner).into_val(&env),
|
|
109
|
+
sub_invokes: &[],
|
|
110
|
+
},
|
|
111
|
+
}]);
|
|
112
|
+
setup.oft.set_msg_inspector(&None, &setup.owner);
|
|
113
|
+
|
|
114
|
+
// Verify inspector is removed
|
|
115
|
+
let stored_inspector = setup.oft.msg_inspector();
|
|
116
|
+
assert_eq!(stored_inspector, None);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ==================== Access Control Tests ====================
|
|
120
|
+
|
|
121
|
+
#[test]
|
|
122
|
+
#[should_panic(expected = "Error(Contract, #1086)")] // RbacError::Unauthorized
|
|
123
|
+
fn test_set_msg_inspector_requires_owner() {
|
|
124
|
+
let env = Env::default();
|
|
125
|
+
let setup = OFTTestSetup::new(&env);
|
|
126
|
+
|
|
127
|
+
// Deploy a passing inspector
|
|
128
|
+
let inspector_address = env.register(PassingInspector, ());
|
|
129
|
+
|
|
130
|
+
// Non-owner (not the authorizer) tries to set the inspector
|
|
131
|
+
let non_owner = Address::generate(&env);
|
|
132
|
+
env.mock_auths(&[MockAuth {
|
|
133
|
+
address: &non_owner,
|
|
134
|
+
invoke: &MockAuthInvoke {
|
|
135
|
+
contract: &setup.oft.address,
|
|
136
|
+
fn_name: "set_msg_inspector",
|
|
137
|
+
args: (&Some(inspector_address.clone()), &non_owner).into_val(&env),
|
|
138
|
+
sub_invokes: &[],
|
|
139
|
+
},
|
|
140
|
+
}]);
|
|
141
|
+
|
|
142
|
+
// This should panic because non_owner is not the authorizer
|
|
143
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &non_owner);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ==================== Integration Tests with Send ====================
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn test_send_without_inspector_succeeds() {
|
|
150
|
+
let env = Env::default();
|
|
151
|
+
let setup = OFTTestSetup::new(&env);
|
|
152
|
+
|
|
153
|
+
let sender = Address::generate(&env);
|
|
154
|
+
|
|
155
|
+
// Set peer
|
|
156
|
+
let peer = BytesN::from_array(&env, &[2u8; 32]);
|
|
157
|
+
let dst_eid = 100u32;
|
|
158
|
+
setup.set_peer(dst_eid, &peer);
|
|
159
|
+
|
|
160
|
+
let amount_ld = 12345670i128;
|
|
161
|
+
setup.fund_tokens(&sender, amount_ld);
|
|
162
|
+
setup.fund_native_fees(&sender, setup.native_fee);
|
|
163
|
+
|
|
164
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
165
|
+
let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
|
|
166
|
+
let oft_receipt = setup.quote_oft(&sender, &send_param);
|
|
167
|
+
|
|
168
|
+
// Send without inspector set - should succeed
|
|
169
|
+
let (msg_receipt, oft_receipt) = setup.send(&sender, &send_param, &fee, &sender, &oft_receipt);
|
|
170
|
+
|
|
171
|
+
assert!(msg_receipt.nonce > 0);
|
|
172
|
+
assert_eq!(oft_receipt.amount_sent_ld, amount_ld);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn test_send_with_passing_inspector() {
|
|
177
|
+
let env = Env::default();
|
|
178
|
+
let setup = OFTTestSetup::new(&env);
|
|
179
|
+
|
|
180
|
+
// Deploy and set a passing inspector
|
|
181
|
+
let inspector_address = env.register(PassingInspector, ());
|
|
182
|
+
env.mock_auths(&[MockAuth {
|
|
183
|
+
address: &setup.owner,
|
|
184
|
+
invoke: &MockAuthInvoke {
|
|
185
|
+
contract: &setup.oft.address,
|
|
186
|
+
fn_name: "set_msg_inspector",
|
|
187
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
188
|
+
sub_invokes: &[],
|
|
189
|
+
},
|
|
190
|
+
}]);
|
|
191
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
192
|
+
|
|
193
|
+
let sender = Address::generate(&env);
|
|
194
|
+
|
|
195
|
+
// Set peer
|
|
196
|
+
let peer = BytesN::from_array(&env, &[2u8; 32]);
|
|
197
|
+
let dst_eid = 100u32;
|
|
198
|
+
setup.set_peer(dst_eid, &peer);
|
|
199
|
+
|
|
200
|
+
let amount_ld = 12345670i128;
|
|
201
|
+
setup.fund_tokens(&sender, amount_ld);
|
|
202
|
+
setup.fund_native_fees(&sender, setup.native_fee);
|
|
203
|
+
|
|
204
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
205
|
+
let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
|
|
206
|
+
let oft_receipt = setup.quote_oft(&sender, &send_param);
|
|
207
|
+
|
|
208
|
+
// Send with passing inspector - should succeed
|
|
209
|
+
let (msg_receipt, oft_receipt) = setup.send(&sender, &send_param, &fee, &sender, &oft_receipt);
|
|
210
|
+
|
|
211
|
+
assert!(msg_receipt.nonce > 0);
|
|
212
|
+
assert_eq!(oft_receipt.amount_sent_ld, amount_ld);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
#[test]
|
|
216
|
+
fn test_send_with_failing_inspector() {
|
|
217
|
+
let env = Env::default();
|
|
218
|
+
let setup = OFTTestSetup::new(&env);
|
|
219
|
+
|
|
220
|
+
// Deploy and set a failing inspector
|
|
221
|
+
let inspector_address = env.register(FailingInspector, ());
|
|
222
|
+
env.mock_auths(&[MockAuth {
|
|
223
|
+
address: &setup.owner,
|
|
224
|
+
invoke: &MockAuthInvoke {
|
|
225
|
+
contract: &setup.oft.address,
|
|
226
|
+
fn_name: "set_msg_inspector",
|
|
227
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
228
|
+
sub_invokes: &[],
|
|
229
|
+
},
|
|
230
|
+
}]);
|
|
231
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
232
|
+
|
|
233
|
+
let sender = Address::generate(&env);
|
|
234
|
+
|
|
235
|
+
// Set peer
|
|
236
|
+
let peer = BytesN::from_array(&env, &[2u8; 32]);
|
|
237
|
+
let dst_eid = 100u32;
|
|
238
|
+
setup.set_peer(dst_eid, &peer);
|
|
239
|
+
|
|
240
|
+
let amount_ld = 12345670i128;
|
|
241
|
+
setup.fund_tokens(&sender, amount_ld);
|
|
242
|
+
setup.fund_native_fees(&sender, setup.native_fee);
|
|
243
|
+
|
|
244
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
245
|
+
let fee = MessagingFee { native_fee: setup.native_fee, zro_fee: 0 };
|
|
246
|
+
let oft_receipt = setup.quote_oft(&sender, &send_param);
|
|
247
|
+
|
|
248
|
+
// Send with failing inspector - should fail
|
|
249
|
+
let result = setup.try_send(&sender, &send_param, &fee, &sender, &oft_receipt);
|
|
250
|
+
|
|
251
|
+
// Verify the send failed due to inspector
|
|
252
|
+
assert!(result.is_err() || result.unwrap().is_err());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ==================== Integration Tests with Quote ====================
|
|
256
|
+
|
|
257
|
+
#[test]
|
|
258
|
+
fn test_quote_send_with_passing_inspector() {
|
|
259
|
+
let env = Env::default();
|
|
260
|
+
let setup = OFTTestSetup::new(&env);
|
|
261
|
+
|
|
262
|
+
// Deploy and set a passing inspector
|
|
263
|
+
let inspector_address = env.register(PassingInspector, ());
|
|
264
|
+
env.mock_auths(&[MockAuth {
|
|
265
|
+
address: &setup.owner,
|
|
266
|
+
invoke: &MockAuthInvoke {
|
|
267
|
+
contract: &setup.oft.address,
|
|
268
|
+
fn_name: "set_msg_inspector",
|
|
269
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
270
|
+
sub_invokes: &[],
|
|
271
|
+
},
|
|
272
|
+
}]);
|
|
273
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
274
|
+
|
|
275
|
+
let sender = Address::generate(&env);
|
|
276
|
+
|
|
277
|
+
// Set peer
|
|
278
|
+
let peer = BytesN::from_array(&env, &[2u8; 32]);
|
|
279
|
+
let dst_eid = 100u32;
|
|
280
|
+
setup.set_peer(dst_eid, &peer);
|
|
281
|
+
|
|
282
|
+
let amount_ld = 12345670i128;
|
|
283
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
284
|
+
|
|
285
|
+
// Quote with passing inspector - should succeed
|
|
286
|
+
let fee = setup.oft.quote_send(&sender, &send_param, &false);
|
|
287
|
+
|
|
288
|
+
assert!(fee.native_fee > 0);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#[test]
|
|
292
|
+
fn test_quote_send_with_failing_inspector() {
|
|
293
|
+
let env = Env::default();
|
|
294
|
+
let setup = OFTTestSetup::new(&env);
|
|
295
|
+
|
|
296
|
+
// Deploy and set a failing inspector
|
|
297
|
+
let inspector_address = env.register(FailingInspector, ());
|
|
298
|
+
env.mock_auths(&[MockAuth {
|
|
299
|
+
address: &setup.owner,
|
|
300
|
+
invoke: &MockAuthInvoke {
|
|
301
|
+
contract: &setup.oft.address,
|
|
302
|
+
fn_name: "set_msg_inspector",
|
|
303
|
+
args: (&Some(inspector_address.clone()), &setup.owner).into_val(&env),
|
|
304
|
+
sub_invokes: &[],
|
|
305
|
+
},
|
|
306
|
+
}]);
|
|
307
|
+
setup.oft.set_msg_inspector(&Some(inspector_address), &setup.owner);
|
|
308
|
+
|
|
309
|
+
let sender = Address::generate(&env);
|
|
310
|
+
|
|
311
|
+
// Set peer
|
|
312
|
+
let peer = BytesN::from_array(&env, &[2u8; 32]);
|
|
313
|
+
let dst_eid = 100u32;
|
|
314
|
+
setup.set_peer(dst_eid, &peer);
|
|
315
|
+
|
|
316
|
+
let amount_ld = 12345670i128;
|
|
317
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
318
|
+
|
|
319
|
+
// Quote with failing inspector - should fail
|
|
320
|
+
let result = setup.oft.try_quote_send(&sender, &send_param, &false);
|
|
321
|
+
|
|
322
|
+
// Verify the quote failed due to inspector
|
|
323
|
+
assert!(result.is_err());
|
|
324
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
use crate::codec::oft_compose_msg_codec::OFTComposeMsg;
|
|
2
|
+
use soroban_sdk::{Bytes, BytesN, Env};
|
|
3
|
+
|
|
4
|
+
#[test]
|
|
5
|
+
fn test_encode_and_decode_basic() {
|
|
6
|
+
let env = Env::default();
|
|
7
|
+
let nonce = 12345u64;
|
|
8
|
+
let src_eid = 101u32;
|
|
9
|
+
let amount_ld = 1_000_000_000i128; // 1 token with 9 decimals
|
|
10
|
+
let compose_from = BytesN::from_array(&env, &[0x42u8; 32]);
|
|
11
|
+
let compose_msg = Bytes::from_array(&env, b"test compose message");
|
|
12
|
+
|
|
13
|
+
let msg = OFTComposeMsg {
|
|
14
|
+
nonce,
|
|
15
|
+
src_eid,
|
|
16
|
+
amount_ld,
|
|
17
|
+
compose_from: compose_from.clone(),
|
|
18
|
+
compose_msg: compose_msg.clone(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
let encoded = msg.encode(&env);
|
|
22
|
+
let decoded = OFTComposeMsg::decode(&encoded);
|
|
23
|
+
|
|
24
|
+
assert_eq!(decoded.nonce, nonce);
|
|
25
|
+
assert_eq!(decoded.src_eid, src_eid);
|
|
26
|
+
assert_eq!(decoded.amount_ld, amount_ld);
|
|
27
|
+
assert_eq!(decoded.compose_from, compose_from);
|
|
28
|
+
assert_eq!(decoded.compose_msg, compose_msg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#[test]
|
|
32
|
+
fn test_encode_with_empty_compose_msg() {
|
|
33
|
+
let env = Env::default();
|
|
34
|
+
let nonce = 123u64;
|
|
35
|
+
let src_eid = 1u32;
|
|
36
|
+
let amount_ld = 1_000_000i128;
|
|
37
|
+
let compose_from = BytesN::from_array(&env, &[0x11u8; 32]);
|
|
38
|
+
let compose_msg = Bytes::new(&env); // Empty
|
|
39
|
+
|
|
40
|
+
let msg = OFTComposeMsg { nonce, src_eid, amount_ld, compose_from: compose_from.clone(), compose_msg };
|
|
41
|
+
|
|
42
|
+
let encoded = msg.encode(&env);
|
|
43
|
+
let decoded = OFTComposeMsg::decode(&encoded);
|
|
44
|
+
|
|
45
|
+
assert_eq!(decoded.nonce, nonce);
|
|
46
|
+
assert_eq!(decoded.src_eid, src_eid);
|
|
47
|
+
assert_eq!(decoded.amount_ld, amount_ld);
|
|
48
|
+
assert_eq!(decoded.compose_from, compose_from);
|
|
49
|
+
assert_eq!(decoded.compose_msg.len(), 0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[test]
|
|
53
|
+
#[should_panic]
|
|
54
|
+
fn test_decode_panic_on_empty_data() {
|
|
55
|
+
let env = Env::default();
|
|
56
|
+
let data = Bytes::new(&env);
|
|
57
|
+
OFTComposeMsg::decode(&data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[test]
|
|
61
|
+
#[should_panic]
|
|
62
|
+
fn test_decode_panic_on_insufficient_data() {
|
|
63
|
+
let env = Env::default();
|
|
64
|
+
let mut data = Bytes::new(&env);
|
|
65
|
+
// Only 30 bytes - not enough for nonce(8) + src_eid(4) + amount_ld(16) + compose_from(32) = 60 bytes minimum
|
|
66
|
+
data.extend_from_array(&[0u8; 30]);
|
|
67
|
+
OFTComposeMsg::decode(&data);
|
|
68
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
use crate::codec::oft_msg_codec::{ComposeData, OFTMessage};
|
|
2
|
+
use crate::utils::address_payload;
|
|
3
|
+
use soroban_sdk::{testutils::Address as _, Address, Bytes, BytesN, Env};
|
|
4
|
+
|
|
5
|
+
#[test]
|
|
6
|
+
fn test_encode_and_decode_without_compose() {
|
|
7
|
+
let env = Env::default();
|
|
8
|
+
let send_to_addr = BytesN::from_array(&env, &[1u8; 32]);
|
|
9
|
+
let amount_sd_val = 1000u64;
|
|
10
|
+
|
|
11
|
+
let msg = OFTMessage { send_to: send_to_addr.clone(), amount_sd: amount_sd_val, compose: None };
|
|
12
|
+
let encoded = msg.encode(&env);
|
|
13
|
+
|
|
14
|
+
// Test decode function
|
|
15
|
+
let decoded = OFTMessage::decode(&encoded);
|
|
16
|
+
assert_eq!(decoded.send_to, send_to_addr);
|
|
17
|
+
assert_eq!(decoded.amount_sd, amount_sd_val);
|
|
18
|
+
assert!(decoded.compose.is_none());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[test]
|
|
22
|
+
fn test_encode_and_decode_with_compose() {
|
|
23
|
+
let env = Env::default();
|
|
24
|
+
let sender = Address::generate(&env);
|
|
25
|
+
let send_to_addr = BytesN::from_array(&env, &[2u8; 32]);
|
|
26
|
+
let amount_sd_val = 5000u64;
|
|
27
|
+
let compose_msg_val = Bytes::from_array(&env, &[1u8, 2u8, 3u8, 4u8]);
|
|
28
|
+
|
|
29
|
+
let msg = OFTMessage {
|
|
30
|
+
send_to: send_to_addr.clone(),
|
|
31
|
+
amount_sd: amount_sd_val,
|
|
32
|
+
compose: Some(ComposeData { from: address_payload(&env, &sender), msg: compose_msg_val }),
|
|
33
|
+
};
|
|
34
|
+
let encoded = msg.encode(&env);
|
|
35
|
+
|
|
36
|
+
// Test decode function
|
|
37
|
+
let decoded = OFTMessage::decode(&encoded);
|
|
38
|
+
assert_eq!(decoded.send_to, send_to_addr);
|
|
39
|
+
assert_eq!(decoded.amount_sd, amount_sd_val);
|
|
40
|
+
let compose = decoded.compose.as_ref().unwrap();
|
|
41
|
+
assert_eq!(compose.msg.len(), 4);
|
|
42
|
+
|
|
43
|
+
// Verify compose message structure: sender's bytes32 (32 bytes) + compose_msg (4 bytes)
|
|
44
|
+
let compose_from_len = compose.from.to_array().len() as u32;
|
|
45
|
+
let compose_msg_len = compose.msg.len();
|
|
46
|
+
assert_eq!(compose_from_len + compose_msg_len, 36);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[test]
|
|
50
|
+
#[should_panic]
|
|
51
|
+
fn test_decode_panic_on_empty_data() {
|
|
52
|
+
let env = Env::default();
|
|
53
|
+
let data = Bytes::new(&env);
|
|
54
|
+
OFTMessage::decode(&data);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[test]
|
|
58
|
+
#[should_panic]
|
|
59
|
+
fn test_decode_panic_on_insufficient_data() {
|
|
60
|
+
let env = Env::default();
|
|
61
|
+
let mut data = Bytes::new(&env);
|
|
62
|
+
data.extend_from_array(&[0u8; 20]); // Not enough data for amount_sd
|
|
63
|
+
OFTMessage::decode(&data);
|
|
64
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
use soroban_sdk::Env;
|
|
2
|
+
|
|
3
|
+
use super::test_utils::OFTTestSetup;
|
|
4
|
+
|
|
5
|
+
#[test]
|
|
6
|
+
fn test_oft_version_returns_correct_values() {
|
|
7
|
+
let env = Env::default();
|
|
8
|
+
let setup = OFTTestSetup::new(&env);
|
|
9
|
+
|
|
10
|
+
let (major, minor) = setup.oft.oft_version();
|
|
11
|
+
assert_eq!(major, 1);
|
|
12
|
+
assert_eq!(minor, 1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn test_approval_required_returns_false_by_default() {
|
|
17
|
+
let env = Env::default();
|
|
18
|
+
let setup = OFTTestSetup::new(&env);
|
|
19
|
+
|
|
20
|
+
// Default OFTCore behavior: no separate token approval is required.
|
|
21
|
+
assert!(!setup.oft.approval_required());
|
|
22
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
use crate::errors::OFTError;
|
|
2
|
+
use soroban_sdk::Env;
|
|
3
|
+
|
|
4
|
+
use super::test_utils::{create_send_param, OFTTestSetup};
|
|
5
|
+
|
|
6
|
+
// ==================== Basic Quote OFT Tests ====================
|
|
7
|
+
|
|
8
|
+
#[test]
|
|
9
|
+
fn test_quote_oft_basic() {
|
|
10
|
+
let env = Env::default();
|
|
11
|
+
let setup = OFTTestSetup::new(&env);
|
|
12
|
+
|
|
13
|
+
let conversion_rate = setup.oft.decimal_conversion_rate();
|
|
14
|
+
let amount_ld = 12345670i128;
|
|
15
|
+
let dust_removed = (amount_ld / conversion_rate) * conversion_rate; // No dust with last digit 0
|
|
16
|
+
let send_param = create_send_param(&env, 1, amount_ld, dust_removed);
|
|
17
|
+
|
|
18
|
+
let (limit, fees, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
19
|
+
|
|
20
|
+
// Check limits
|
|
21
|
+
assert_eq!(limit.min_amount_ld, 0);
|
|
22
|
+
assert_eq!(limit.max_amount_ld, u64::MAX as i128);
|
|
23
|
+
|
|
24
|
+
// Check fees (should be empty for basic OFT)
|
|
25
|
+
assert_eq!(fees.len(), 0);
|
|
26
|
+
|
|
27
|
+
// Check receipt
|
|
28
|
+
assert_eq!(receipt.amount_sent_ld, dust_removed);
|
|
29
|
+
assert_eq!(receipt.amount_received_ld, dust_removed);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
fn test_quote_oft_with_dust_removal() {
|
|
34
|
+
let env = Env::default();
|
|
35
|
+
let setup = OFTTestSetup::new(&env);
|
|
36
|
+
|
|
37
|
+
let conversion_rate = setup.oft.decimal_conversion_rate();
|
|
38
|
+
// Amount with dust that will be removed
|
|
39
|
+
let amount_ld = 12345678i128;
|
|
40
|
+
let dust_removed = (amount_ld / conversion_rate) * conversion_rate;
|
|
41
|
+
let send_param = create_send_param(&env, 1, amount_ld, dust_removed);
|
|
42
|
+
|
|
43
|
+
let (limit, fees, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
44
|
+
|
|
45
|
+
// Check limits
|
|
46
|
+
assert_eq!(limit.min_amount_ld, 0);
|
|
47
|
+
assert_eq!(limit.max_amount_ld, u64::MAX as i128);
|
|
48
|
+
|
|
49
|
+
// Check fees (should be empty for basic OFT)
|
|
50
|
+
assert_eq!(fees.len(), 0);
|
|
51
|
+
|
|
52
|
+
// Check receipt - dust removed
|
|
53
|
+
assert_eq!(receipt.amount_sent_ld, dust_removed);
|
|
54
|
+
assert_eq!(receipt.amount_received_ld, dust_removed);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#[test]
|
|
58
|
+
fn test_quote_oft_zero_amount() {
|
|
59
|
+
let env = Env::default();
|
|
60
|
+
let setup = OFTTestSetup::new(&env);
|
|
61
|
+
|
|
62
|
+
let send_param = create_send_param(&env, 1, 0, 0);
|
|
63
|
+
|
|
64
|
+
let (limit, fees, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
65
|
+
|
|
66
|
+
assert_eq!(receipt.amount_sent_ld, 0);
|
|
67
|
+
assert_eq!(receipt.amount_received_ld, 0);
|
|
68
|
+
assert_eq!(limit.min_amount_ld, 0);
|
|
69
|
+
assert_eq!(limit.max_amount_ld, u64::MAX as i128);
|
|
70
|
+
assert_eq!(fees.len(), 0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[test]
|
|
74
|
+
fn test_quote_oft_dust_only_amount() {
|
|
75
|
+
let env = Env::default();
|
|
76
|
+
let setup = OFTTestSetup::new(&env);
|
|
77
|
+
|
|
78
|
+
let conversion_rate = setup.oft.decimal_conversion_rate();
|
|
79
|
+
// Amount that is entirely dust (less than conversion rate)
|
|
80
|
+
let amount_ld = conversion_rate - 1; // Less than conversion rate
|
|
81
|
+
let send_param = create_send_param(&env, 1, amount_ld, 0);
|
|
82
|
+
|
|
83
|
+
let (_, _, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
84
|
+
|
|
85
|
+
// All dust should be removed
|
|
86
|
+
assert_eq!(receipt.amount_sent_ld, 0);
|
|
87
|
+
assert_eq!(receipt.amount_received_ld, 0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[test]
|
|
91
|
+
fn test_quote_oft_different_dst_eids() {
|
|
92
|
+
let env = Env::default();
|
|
93
|
+
let setup = OFTTestSetup::new(&env);
|
|
94
|
+
|
|
95
|
+
let amount_ld = 12345670i128; // No dust
|
|
96
|
+
|
|
97
|
+
// Test with different destination EIDs
|
|
98
|
+
for dst_eid in [1u32, 100, 10000, u32::MAX] {
|
|
99
|
+
let send_param = create_send_param(&env, dst_eid, amount_ld, amount_ld);
|
|
100
|
+
let (limit, fees, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
101
|
+
|
|
102
|
+
// Results should be the same regardless of dst_eid
|
|
103
|
+
assert_eq!(limit.min_amount_ld, 0);
|
|
104
|
+
assert_eq!(limit.max_amount_ld, u64::MAX as i128);
|
|
105
|
+
assert_eq!(fees.len(), 0);
|
|
106
|
+
assert_eq!(receipt.amount_sent_ld, amount_ld);
|
|
107
|
+
assert_eq!(receipt.amount_received_ld, amount_ld);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn test_quote_oft_large_amounts() {
|
|
113
|
+
let env = Env::default();
|
|
114
|
+
let setup = OFTTestSetup::new(&env);
|
|
115
|
+
|
|
116
|
+
// Test with a large amount
|
|
117
|
+
let amount_ld = 1_000_000_000_000_000_000i128;
|
|
118
|
+
let send_param = create_send_param(&env, 1, amount_ld, amount_ld);
|
|
119
|
+
|
|
120
|
+
let (limit, fees, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
121
|
+
|
|
122
|
+
assert_eq!(limit.min_amount_ld, 0);
|
|
123
|
+
assert_eq!(limit.max_amount_ld, u64::MAX as i128);
|
|
124
|
+
assert_eq!(fees.len(), 0);
|
|
125
|
+
assert_eq!(receipt.amount_sent_ld, amount_ld);
|
|
126
|
+
assert_eq!(receipt.amount_received_ld, amount_ld);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ==================== Slippage Tests ====================
|
|
130
|
+
|
|
131
|
+
#[test]
|
|
132
|
+
fn test_quote_oft_slippage_less_than_received() {
|
|
133
|
+
let env = Env::default();
|
|
134
|
+
let setup = OFTTestSetup::new(&env);
|
|
135
|
+
|
|
136
|
+
let conversion_rate = setup.oft.decimal_conversion_rate();
|
|
137
|
+
let amount_ld = 12345678i128;
|
|
138
|
+
let dust_removed = (amount_ld / conversion_rate) * conversion_rate;
|
|
139
|
+
// min_amount_ld is less than what will be received
|
|
140
|
+
let send_param = create_send_param(&env, 1, amount_ld, dust_removed - conversion_rate);
|
|
141
|
+
|
|
142
|
+
let (_, _, receipt) = setup.oft.quote_oft(&setup.owner, &send_param);
|
|
143
|
+
|
|
144
|
+
assert_eq!(receipt.amount_sent_ld, dust_removed);
|
|
145
|
+
assert_eq!(receipt.amount_received_ld, dust_removed);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[test]
|
|
149
|
+
fn test_quote_oft_slippage_exceeded() {
|
|
150
|
+
let env = Env::default();
|
|
151
|
+
let setup = OFTTestSetup::new(&env);
|
|
152
|
+
|
|
153
|
+
let amount_ld = 12345678i128;
|
|
154
|
+
// min_amount_ld is higher than what can be received after dust removal
|
|
155
|
+
let send_param = create_send_param(&env, 1, amount_ld, amount_ld);
|
|
156
|
+
|
|
157
|
+
let result = setup.oft.try_quote_oft(&setup.owner, &send_param);
|
|
158
|
+
assert_eq!(result.err().unwrap().ok().unwrap(), OFTError::SlippageExceeded.into());
|
|
159
|
+
}
|