@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.
@@ -0,0 +1,124 @@
1
+ use super::test_utils::OFTTestSetup;
2
+ use crate::{
3
+ tests::test_utils::OFTTestSetupBuilder,
4
+ utils::{to_ld, to_sd},
5
+ };
6
+ use soroban_sdk::Env;
7
+
8
+ #[test]
9
+ fn test_decimal_conversion_rate() {
10
+ let env = Env::default();
11
+ let setup = OFTTestSetup::new(&env);
12
+
13
+ // SAC has 7 decimals, shared decimals is 6
14
+ // conversion_rate = 10^(7-6) = 10
15
+ let rate = setup.oft.decimal_conversion_rate();
16
+ assert_eq!(rate, 10_i128.pow(setup.token_decimals - setup.shared_decimals));
17
+ }
18
+
19
+ #[test]
20
+ #[should_panic(expected = "Error(Contract, #3002)")] // InvalidLocalDecimals
21
+ fn test_invalid_local_decimals() {
22
+ let env = Env::default();
23
+ OFTTestSetupBuilder::new(&env).with_token_decimals(5).with_shared_decimals(6).build();
24
+ }
25
+
26
+ #[test]
27
+ #[should_panic]
28
+ fn test_to_ld_overflow() {
29
+ // 10^38 * 10 > i128::MAX - should overflow
30
+ let conversion_rate = 10_i128.pow(38);
31
+ let amount_sd = 10u64;
32
+
33
+ to_ld(amount_sd, conversion_rate);
34
+ }
35
+
36
+ #[test]
37
+ #[should_panic(expected = "Error(Contract, #3004)")] // Overflow
38
+ fn test_to_sd_overflow() {
39
+ let env = Env::default();
40
+
41
+ // u64::MAX + 1 exceeds u64 range - should overflow
42
+ let conversion_rate = 1i128;
43
+ let amount_ld = u64::MAX as i128 + 1;
44
+
45
+ to_sd(&env, amount_ld, conversion_rate);
46
+ }
47
+
48
+ // ==================== Positive Conversion Tests ====================
49
+
50
+ #[test]
51
+ fn test_to_ld_basic() {
52
+ // Conversion rate = 10 (7 decimals - 6 shared = 1 decimal difference)
53
+ let conversion_rate = 10i128;
54
+
55
+ assert_eq!(to_ld(0, conversion_rate), 0);
56
+ assert_eq!(to_ld(1, conversion_rate), 10);
57
+ assert_eq!(to_ld(100, conversion_rate), 1_000);
58
+ assert_eq!(to_ld(1_000_000, conversion_rate), 10_000_000);
59
+ }
60
+
61
+ #[test]
62
+ fn test_to_sd_basic() {
63
+ let env = Env::default();
64
+
65
+ // Conversion rate = 10 (7 decimals - 6 shared = 1 decimal difference)
66
+ let conversion_rate = 10i128;
67
+
68
+ assert_eq!(to_sd(&env, 0, conversion_rate), 0);
69
+ assert_eq!(to_sd(&env, 10, conversion_rate), 1);
70
+ assert_eq!(to_sd(&env, 1_000, conversion_rate), 100);
71
+ assert_eq!(to_sd(&env, 10_000_000, conversion_rate), 1_000_000);
72
+ }
73
+
74
+ #[test]
75
+ fn test_to_sd_truncates_dust() {
76
+ let env = Env::default();
77
+
78
+ // Conversion rate = 10
79
+ let conversion_rate = 10i128;
80
+
81
+ // 15 LD → 1 SD (5 is dust, truncated via division)
82
+ assert_eq!(to_sd(&env, 15, conversion_rate), 1);
83
+
84
+ // 19 LD → 1 SD (9 is dust, truncated)
85
+ assert_eq!(to_sd(&env, 19, conversion_rate), 1);
86
+
87
+ // 20 LD → 2 SD (exact)
88
+ assert_eq!(to_sd(&env, 20, conversion_rate), 2);
89
+ }
90
+
91
+ // ==================== Shared Decimals Tests ====================
92
+
93
+ #[test]
94
+ fn test_shared_decimals_default() {
95
+ let env = Env::default();
96
+ let setup = OFTTestSetup::new(&env);
97
+
98
+ // Default setup: 7 local decimals, 6 shared decimals
99
+ let shared = setup.oft.shared_decimals();
100
+ assert_eq!(shared, setup.shared_decimals);
101
+ assert_eq!(shared, 6);
102
+ }
103
+
104
+ #[test]
105
+ fn test_shared_decimals_equal_to_local() {
106
+ let env = Env::default();
107
+ // When shared_decimals == local_decimals, conversion_rate = 1
108
+ let setup = OFTTestSetupBuilder::new(&env).with_token_decimals(8).with_shared_decimals(8).build();
109
+
110
+ let shared = setup.oft.shared_decimals();
111
+ assert_eq!(shared, 8);
112
+ assert_eq!(setup.oft.decimal_conversion_rate(), 1);
113
+ }
114
+
115
+ #[test]
116
+ fn test_shared_decimals_large_difference() {
117
+ let env = Env::default();
118
+ // 18 local decimals, 6 shared decimals → conversion_rate = 10^12
119
+ let setup = OFTTestSetupBuilder::new(&env).with_token_decimals(18).with_shared_decimals(6).build();
120
+
121
+ let shared = setup.oft.shared_decimals();
122
+ assert_eq!(shared, 6);
123
+ assert_eq!(setup.oft.decimal_conversion_rate(), 10_i128.pow(12));
124
+ }
@@ -0,0 +1,282 @@
1
+ use super::test_utils::OFTTestSetup;
2
+ use crate::{
3
+ events::OFTReceived,
4
+ tests::test_utils::{
5
+ create_origin, create_recipient_address, encode_oft_message, encode_oft_message_with_compose,
6
+ generate_g_address, OFTTestSetupBuilder,
7
+ },
8
+ utils::address_payload,
9
+ };
10
+ use endpoint_v2::LayerZeroReceiverClient;
11
+ use soroban_sdk::{testutils::Address as _, Address, Bytes, BytesN, Env};
12
+ use utils::testing_utils::assert_contains_event;
13
+
14
+ // Helper function to reduce code duplication for lz_receive tests
15
+ fn run_lz_receive_test(setup: &OFTTestSetup, recipient: &Address, amount_sd: u64) {
16
+ let env = setup.env;
17
+ let executor = Address::generate(env);
18
+
19
+ let src_eid = 100u32;
20
+ let peer = BytesN::from_array(env, &[2u8; 32]);
21
+ setup.set_peer(src_eid, &peer);
22
+
23
+ // Check initial balance
24
+ let initial_balance = setup.token_client.balance(recipient);
25
+ let initial_oft_balance =
26
+ if setup.is_lock_unlock() { Some(setup.token_client.balance(&setup.oft.address)) } else { None };
27
+
28
+ // Create OFT message
29
+ let recipient_bytes32 = address_payload(env, recipient);
30
+ let message = encode_oft_message(env, &recipient_bytes32, amount_sd);
31
+
32
+ let guid = BytesN::from_array(env, &[1u8; 32]);
33
+ let origin = create_origin(src_eid, &peer, 1);
34
+ let extra_data = Bytes::new(env);
35
+
36
+ // Verify recipient received tokens
37
+ let conversion_rate = setup.oft.decimal_conversion_rate();
38
+ let expected_amount_ld = (amount_sd as i128) * conversion_rate;
39
+ setup.lz_receive(&executor, &origin, &guid, &message, &extra_data, 0);
40
+ assert_contains_event(
41
+ env,
42
+ &setup.oft.address,
43
+ OFTReceived { guid: guid.clone(), src_eid, to: recipient.clone(), amount_received_ld: expected_amount_ld },
44
+ );
45
+ if setup.issuer == *recipient {
46
+ assert_eq!(setup.token_client.balance(recipient), i64::MAX as i128);
47
+ } else {
48
+ assert_eq!(setup.token_client.balance(recipient), initial_balance + expected_amount_ld);
49
+ }
50
+
51
+ // For LockUnlock strategy, verify OFT contract balance decreased
52
+ if let Some(initial_oft) = initial_oft_balance {
53
+ assert_eq!(setup.token_client.balance(&setup.oft.address), initial_oft - expected_amount_ld);
54
+ }
55
+ }
56
+
57
+ // ==================== MintBurn Strategy Tests ====================
58
+
59
+ #[test]
60
+ fn test_mint_burn_sac_lz_receive_to_c_address() {
61
+ let env = Env::default();
62
+ let recipient = create_recipient_address(&env);
63
+ let setup = OFTTestSetupBuilder::new(&env).with_sac().build();
64
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
65
+ }
66
+
67
+ #[test]
68
+ fn test_mint_burn_contract_token_lz_receive_to_c_address() {
69
+ let env = Env::default();
70
+ let recipient = create_recipient_address(&env);
71
+ let setup = OFTTestSetup::new(&env);
72
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
73
+ }
74
+
75
+ #[test]
76
+ #[should_panic(expected = "HostError: Error(Contract, #13)")] // trustline entry is missing for account
77
+ fn test_mint_burn_sac_lz_receive_to_g_address_with_no_trustline() {
78
+ let env = Env::default();
79
+ let recipient = generate_g_address(&env);
80
+ let setup = OFTTestSetupBuilder::new(&env).with_sac().build();
81
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
82
+ }
83
+
84
+ #[test]
85
+ #[should_panic(expected = "HostError: Error(Contract, #2)")] // operation invalid on issuer
86
+ fn test_mint_burn_sac_lz_receive_to_issuer() {
87
+ let env = Env::default();
88
+ let setup = OFTTestSetupBuilder::new(&env).with_sac().build();
89
+ run_lz_receive_test(&setup, &setup.issuer, 1_000_000u64);
90
+ }
91
+
92
+ #[test]
93
+ fn test_mint_burn_contract_token_lz_receive_to_g_address() {
94
+ let env = Env::default();
95
+ let recipient = generate_g_address(&env);
96
+ let setup = OFTTestSetup::new(&env);
97
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
98
+ }
99
+
100
+ #[test]
101
+ fn test_mint_burn_lz_receive_with_compose() {
102
+ let env = Env::default();
103
+ let setup = OFTTestSetup::new(&env);
104
+
105
+ let executor = Address::generate(&env);
106
+ let recipient = create_recipient_address(&env);
107
+ let sender_on_src = Address::generate(&env);
108
+
109
+ let src_eid = 100u32;
110
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
111
+ setup.set_peer(src_eid, &peer);
112
+
113
+ // Create OFT message with compose
114
+ let recipient_bytes32 = address_payload(&env, &recipient);
115
+ let compose_from = address_payload(&env, &sender_on_src);
116
+ let compose_msg = Bytes::from_array(&env, b"test compose payload");
117
+ let amount_sd = 1000000u64;
118
+ let message = encode_oft_message_with_compose(&env, &recipient_bytes32, amount_sd, &compose_from, &compose_msg);
119
+
120
+ let guid = BytesN::from_array(&env, &[1u8; 32]);
121
+ let origin = create_origin(src_eid, &peer, 1);
122
+ let extra_data = Bytes::new(&env);
123
+
124
+ // Verify compose not called yet
125
+ assert!(!setup.endpoint_client.was_composed());
126
+
127
+ // Execute lz_receive
128
+ setup.lz_receive(&executor, &origin, &guid, &message, &extra_data, 0);
129
+
130
+ // Verify tokens were credited
131
+ let conversion_rate = setup.oft.decimal_conversion_rate();
132
+ let expected_amount_ld = (amount_sd as i128) * conversion_rate;
133
+ assert_eq!(setup.token_client.balance(&recipient), expected_amount_ld);
134
+
135
+ // Verify compose was called
136
+ assert!(setup.endpoint_client.was_composed());
137
+
138
+ // Verify compose recipient
139
+ let compose_to = setup.endpoint_client.get_compose_to().unwrap();
140
+ assert_eq!(compose_to, recipient);
141
+ }
142
+
143
+ #[test]
144
+ fn test_mint_burn_lz_receive_zero_amount() {
145
+ let env = Env::default();
146
+ let recipient = create_recipient_address(&env);
147
+ let setup = OFTTestSetup::new(&env);
148
+ run_lz_receive_test(&setup, &recipient, 0u64);
149
+ }
150
+
151
+ // ==================== Lock/Unlock Strategy Tests ====================
152
+
153
+ #[test]
154
+ fn test_lock_unlock_sac_lz_receive_to_c_address() {
155
+ let env = Env::default();
156
+ let recipient = create_recipient_address(&env);
157
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().with_sac().build();
158
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
159
+ }
160
+
161
+ #[test]
162
+ fn test_lock_unlock_contract_token_lz_receive_to_c_address() {
163
+ let env = Env::default();
164
+ let recipient = create_recipient_address(&env);
165
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().build();
166
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
167
+ }
168
+
169
+ #[test]
170
+ #[should_panic(expected = "HostError: Error(Contract, #13)")] // trustline entry is missing for account
171
+ fn test_lock_unlock_sac_lz_receive_to_g_address_with_no_trustline() {
172
+ let env = Env::default();
173
+ let recipient = generate_g_address(&env);
174
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().with_sac().build();
175
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
176
+ }
177
+
178
+ #[test]
179
+ fn test_lock_unlock_sac_lz_receive_to_issuer() {
180
+ let env = Env::default();
181
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().with_sac().build();
182
+ run_lz_receive_test(&setup, &setup.issuer, 1_000_000u64);
183
+ }
184
+
185
+ #[test]
186
+ fn test_lock_unlock_contract_token_lz_receive_to_g_address() {
187
+ let env = Env::default();
188
+ let recipient = generate_g_address(&env);
189
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().build();
190
+ run_lz_receive_test(&setup, &recipient, 1_000_000u64);
191
+ }
192
+
193
+ #[test]
194
+ fn test_lock_unlock_lz_receive_with_compose() {
195
+ let env = Env::default();
196
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().build();
197
+
198
+ let executor = Address::generate(&env);
199
+ let recipient = create_recipient_address(&env);
200
+ let sender_on_src = Address::generate(&env);
201
+
202
+ let src_eid = 100u32;
203
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
204
+ setup.set_peer(src_eid, &peer);
205
+
206
+ // Create OFT message with compose
207
+ let recipient_bytes32 = address_payload(&env, &recipient);
208
+ let compose_from = address_payload(&env, &sender_on_src);
209
+ let compose_msg = Bytes::from_array(&env, b"test compose payload");
210
+ let amount_sd = 1000000u64;
211
+ let message = encode_oft_message_with_compose(&env, &recipient_bytes32, amount_sd, &compose_from, &compose_msg);
212
+
213
+ let guid = BytesN::from_array(&env, &[1u8; 32]);
214
+ let origin = create_origin(src_eid, &peer, 1);
215
+ let extra_data = Bytes::new(&env);
216
+
217
+ // Verify compose not called yet
218
+ assert!(!setup.endpoint_client.was_composed());
219
+
220
+ // Execute lz_receive
221
+ setup.lz_receive(&executor, &origin, &guid, &message, &extra_data, 0);
222
+
223
+ // Verify tokens were credited
224
+ let conversion_rate = setup.oft.decimal_conversion_rate();
225
+ let expected_amount_ld = (amount_sd as i128) * conversion_rate;
226
+ assert_eq!(setup.token_client.balance(&recipient), expected_amount_ld);
227
+
228
+ // Verify compose was called
229
+ assert!(setup.endpoint_client.was_composed());
230
+
231
+ // Verify compose recipient
232
+ let compose_to = setup.endpoint_client.get_compose_to().unwrap();
233
+ assert_eq!(compose_to, recipient);
234
+ }
235
+
236
+ #[test]
237
+ fn test_lock_unlock_lz_receive_zero_amount() {
238
+ let env = Env::default();
239
+ let recipient = create_recipient_address(&env);
240
+ let setup = OFTTestSetupBuilder::new(&env).lock_unlock().build();
241
+ run_lz_receive_test(&setup, &recipient, 0u64);
242
+ }
243
+
244
+ // ==================== Authorizations Tests ====================
245
+
246
+ #[test]
247
+ #[should_panic(expected = "HostError: Error(Auth, InvalidAction)")]
248
+ fn test_lz_receive_without_giving_authorization() {
249
+ let env = Env::default();
250
+ let setup = OFTTestSetup::new(&env);
251
+
252
+ let executor = Address::generate(&env);
253
+ let recipient = create_recipient_address(&env);
254
+
255
+ // Set peer
256
+ let src_eid = 100u32;
257
+ let peer = BytesN::from_array(&env, &[2u8; 32]);
258
+ setup.set_peer(src_eid, &peer);
259
+
260
+ // Check initial balance
261
+ assert_eq!(setup.token_client.balance(&recipient), 0);
262
+
263
+ // Create OFT message
264
+ let recipient_bytes32 = address_payload(&env, &recipient);
265
+ let amount_sd = 1000000u64; // 1 token in shared decimals
266
+ let message = encode_oft_message(&env, &recipient_bytes32, amount_sd);
267
+
268
+ // Create origin
269
+ let guid = BytesN::from_array(&env, &[1u8; 32]);
270
+ let origin = create_origin(src_eid, &peer, 1);
271
+ let extra_data = Bytes::new(&env);
272
+
273
+ // Execute lz_receive
274
+ LayerZeroReceiverClient::new(setup.env, &setup.oft.address).lz_receive(
275
+ &executor,
276
+ &origin,
277
+ &guid,
278
+ &message,
279
+ &extra_data,
280
+ &0i128,
281
+ );
282
+ }