@layerzerolabs/oft-adapter-starknet 0.2.9

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.
Files changed (82) hide show
  1. package/Scarb.lock +190 -0
  2. package/Scarb.toml +4 -0
  3. package/contracts/oft_adapter/Scarb.toml +30 -0
  4. package/contracts/oft_adapter/src/lib.cairo +1 -0
  5. package/contracts/oft_adapter/src/oft_adapter.cairo +150 -0
  6. package/contracts/oft_adapter/tests/constants.cairo +3 -0
  7. package/contracts/oft_adapter/tests/lib.cairo +11 -0
  8. package/contracts/oft_adapter/tests/mocks/erc20/erc20.cairo +49 -0
  9. package/contracts/oft_adapter/tests/mocks/erc20/interface.cairo +8 -0
  10. package/contracts/oft_adapter/tests/mocks/message_inspector/message_inspector.cairo +17 -0
  11. package/contracts/oft_adapter/tests/test_oft_adapter.cairo +579 -0
  12. package/dist/3UUTAAI4.js +9 -0
  13. package/dist/3UUTAAI4.js.map +1 -0
  14. package/dist/4VIL37TM.cjs +14 -0
  15. package/dist/4VIL37TM.cjs.map +1 -0
  16. package/dist/5AQVVHK2.cjs +17 -0
  17. package/dist/5AQVVHK2.cjs.map +1 -0
  18. package/dist/6JUHMRLN.js +14 -0
  19. package/dist/6JUHMRLN.js.map +1 -0
  20. package/dist/B3SWU5G3.cjs +990 -0
  21. package/dist/B3SWU5G3.cjs.map +1 -0
  22. package/dist/E63KEOR5.cjs +11 -0
  23. package/dist/E63KEOR5.cjs.map +1 -0
  24. package/dist/HRRTIIZM.cjs +1282 -0
  25. package/dist/HRRTIIZM.cjs.map +1 -0
  26. package/dist/HTWTY64L.js +14 -0
  27. package/dist/HTWTY64L.js.map +1 -0
  28. package/dist/IH3YG4QY.js +12 -0
  29. package/dist/IH3YG4QY.js.map +1 -0
  30. package/dist/JFLNUTE2.js +988 -0
  31. package/dist/JFLNUTE2.js.map +1 -0
  32. package/dist/JNHB3COG.cjs +14 -0
  33. package/dist/JNHB3COG.cjs.map +1 -0
  34. package/dist/KGI5KEFS.js +1280 -0
  35. package/dist/KGI5KEFS.js.map +1 -0
  36. package/dist/KRS32YQY.cjs +17 -0
  37. package/dist/KRS32YQY.cjs.map +1 -0
  38. package/dist/RU2XCZGW.js +12 -0
  39. package/dist/RU2XCZGW.js.map +1 -0
  40. package/dist/generated/abi/o-f-t-adapter.cjs +13 -0
  41. package/dist/generated/abi/o-f-t-adapter.cjs.map +1 -0
  42. package/dist/generated/abi/o-f-t-adapter.d.ts +722 -0
  43. package/dist/generated/abi/o-f-t-adapter.d.ts.map +1 -0
  44. package/dist/generated/abi/o-f-t-adapter.js +4 -0
  45. package/dist/generated/abi/o-f-t-adapter.js.map +1 -0
  46. package/dist/generated/abi.cjs +14 -0
  47. package/dist/generated/abi.cjs.map +1 -0
  48. package/dist/generated/abi.d.ts +2 -0
  49. package/dist/generated/abi.d.ts.map +1 -0
  50. package/dist/generated/abi.js +5 -0
  51. package/dist/generated/abi.js.map +1 -0
  52. package/dist/generated/casm.cjs +13 -0
  53. package/dist/generated/casm.cjs.map +1 -0
  54. package/dist/generated/casm.d.ts +2 -0
  55. package/dist/generated/casm.d.ts.map +1 -0
  56. package/dist/generated/casm.js +4 -0
  57. package/dist/generated/casm.js.map +1 -0
  58. package/dist/generated/sierra.cjs +13 -0
  59. package/dist/generated/sierra.cjs.map +1 -0
  60. package/dist/generated/sierra.d.ts +2 -0
  61. package/dist/generated/sierra.d.ts.map +1 -0
  62. package/dist/generated/sierra.js +4 -0
  63. package/dist/generated/sierra.js.map +1 -0
  64. package/dist/generated/verification/index.cjs +14 -0
  65. package/dist/generated/verification/index.cjs.map +1 -0
  66. package/dist/generated/verification/index.d.ts +2 -0
  67. package/dist/generated/verification/index.d.ts.map +1 -0
  68. package/dist/generated/verification/index.js +5 -0
  69. package/dist/generated/verification/index.js.map +1 -0
  70. package/dist/generated/verification/oft_adapter.cjs +10 -0
  71. package/dist/generated/verification/oft_adapter.cjs.map +1 -0
  72. package/dist/generated/verification/oft_adapter.d.ts +4 -0
  73. package/dist/generated/verification/oft_adapter.d.ts.map +1 -0
  74. package/dist/generated/verification/oft_adapter.js +4 -0
  75. package/dist/generated/verification/oft_adapter.js.map +1 -0
  76. package/dist/index.cjs +30 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.ts +5 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +9 -0
  81. package/dist/index.js.map +1 -0
  82. package/package.json +53 -0
@@ -0,0 +1,579 @@
1
+ //! OFT adapter tests
2
+
3
+ use core::num::traits::{Bounded, SaturatingAdd, SaturatingSub};
4
+ use layerzero::common::structs::packet::Origin;
5
+ use layerzero::endpoint::interfaces::layerzero_receiver::{
6
+ ILayerZeroReceiverDispatcher, ILayerZeroReceiverDispatcherTrait,
7
+ ILayerZeroReceiverSafeDispatcher, ILayerZeroReceiverSafeDispatcherTrait,
8
+ };
9
+ use layerzero::oapps::oapp::interface::{IOAppDispatcher, IOAppDispatcherTrait};
10
+ use layerzero::oapps::oft::errors::{err_oft_transfer_failed, err_slippage_exceeded};
11
+ use layerzero::oapps::oft::interface::{
12
+ IOFTDispatcher, IOFTDispatcherTrait, IOFTSafeDispatcher, IOFTSafeDispatcherTrait,
13
+ };
14
+ use layerzero::oapps::oft::oft_msg_codec::OFTMsgCodec;
15
+ use layerzero::oapps::oft::structs::SendParam;
16
+ use layerzero::{MessageReceipt, MessagingFee};
17
+ use lz_utils::bytes::{Bytes32, ContractAddressIntoBytes32};
18
+ use openzeppelin::token::erc20::ERC20Component::Errors as ERC20Errors;
19
+ use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
20
+ use snforge_std::{ContractClassTrait, DeclareResultTrait, declare, mock_call, start_mock_call};
21
+ use starknet::{ContractAddress, SyscallResultTrait};
22
+ use starkware_utils_testing::test_utils::{
23
+ assert_panic_with_error, assert_panic_with_felt_error, cheat_caller_address_once,
24
+ };
25
+ use crate::mocks::erc20::interface::{IMockERC20Dispatcher, IMockERC20DispatcherTrait};
26
+
27
+ // =============================== Test Constants =================================
28
+
29
+ pub const FAKE_ENDPOINT: ContractAddress = 'fake_endpoint'.try_into().unwrap();
30
+ pub const OWNER: ContractAddress = 'owner'.try_into().unwrap();
31
+ pub const STARK_TOKEN: ContractAddress = 'stark_token'.try_into().unwrap();
32
+ pub const USER: ContractAddress = 'user'.try_into().unwrap();
33
+ pub const RECIPIENT: ContractAddress = 'recipient'.try_into().unwrap();
34
+ pub const DST_EID: u32 = 101;
35
+ pub const SRC_EID: u32 = 201;
36
+ pub const SHARED_DECIMALS: u8 = 6;
37
+ pub const LOCAL_DECIMALS: u8 = 18;
38
+
39
+ // Test amounts
40
+ pub const TEST_AMOUNT_LD: u256 = 1000000000000000000_u256; // 1 token with 18 decimals
41
+ pub const MIN_AMOUNT_LD: u256 = 900000000000000000_u256; // 0.9 token with 18 decimals
42
+ pub const SMALL_AMOUNT: u256 = 500000000000000000_u256; // 0.5 token
43
+ pub const LARGE_AMOUNT: u256 = 10000000000000000000_u256; // 10 tokens
44
+ pub const INITIAL_SUPPLY: u256 = 1000000000000000000000_u256; // 1000 tokens
45
+
46
+ // =============================== Helper Functions =================================
47
+
48
+ fn MOCK_GUID() -> Bytes32 {
49
+ Bytes32 { value: 0xabcdef123456789 }
50
+ }
51
+
52
+ fn MOCK_ORIGIN() -> Origin {
53
+ Origin { src_eid: SRC_EID, sender: MOCK_PEER(), nonce: 1 }
54
+ }
55
+
56
+ fn MOCK_PEER() -> Bytes32 {
57
+ Bytes32 { value: 0x123456789abcdef }
58
+ }
59
+
60
+ fn deploy_mock_erc20() -> ContractAddress {
61
+ let contract = declare("MockERC20").unwrap_syscall().contract_class();
62
+ let (address, _) = contract
63
+ .deploy(@array![INITIAL_SUPPLY.low.into(), INITIAL_SUPPLY.high.into(), OWNER.into()])
64
+ .unwrap_syscall();
65
+ address
66
+ }
67
+
68
+ fn deploy_oft_adapter(
69
+ inner_token: ContractAddress, stark_token: ContractAddress,
70
+ ) -> ContractAddress {
71
+ let contract = declare("OFTAdapter").unwrap_syscall().contract_class();
72
+
73
+ mock_call(FAKE_ENDPOINT, selector!("set_delegate"), (), 1);
74
+ let (adapter_address, _) = contract
75
+ .deploy(@array![inner_token.into(), FAKE_ENDPOINT.into(), OWNER.into(), stark_token.into()])
76
+ .unwrap_syscall();
77
+
78
+ adapter_address
79
+ }
80
+
81
+ fn setup_oft_adapter() -> (ContractAddress, ContractAddress, ContractAddress) {
82
+ let stark_token = deploy_mock_erc20();
83
+ let inner_token = deploy_mock_erc20();
84
+ let adapter = deploy_oft_adapter(inner_token, stark_token);
85
+
86
+ (adapter, inner_token, stark_token)
87
+ }
88
+
89
+ fn setup_adapter_with_peers(adapter_address: ContractAddress) {
90
+ let oapp = IOAppDispatcher { contract_address: adapter_address };
91
+
92
+ cheat_caller_address_once(adapter_address, OWNER);
93
+ oapp.set_peer(DST_EID, MOCK_PEER());
94
+
95
+ cheat_caller_address_once(adapter_address, OWNER);
96
+ oapp.set_peer(SRC_EID, MOCK_PEER());
97
+ }
98
+
99
+ fn setup_user_tokens_and_approval(
100
+ inner_token: ContractAddress,
101
+ adapter_address: ContractAddress,
102
+ user: ContractAddress,
103
+ amount: u256,
104
+ ) {
105
+ let token = IERC20Dispatcher { contract_address: inner_token };
106
+
107
+ // Transfer tokens from owner to user
108
+ cheat_caller_address_once(inner_token, OWNER);
109
+ token.transfer(user, amount);
110
+
111
+ // Approve adapter to spend user's tokens
112
+ cheat_caller_address_once(inner_token, user);
113
+ token.approve(adapter_address, amount);
114
+ }
115
+
116
+ fn setup_adapter_tokens(
117
+ inner_token: ContractAddress, adapter_address: ContractAddress, amount: u256,
118
+ ) {
119
+ let token = IERC20Dispatcher { contract_address: inner_token };
120
+
121
+ // Transfer tokens to adapter for unlocking
122
+ cheat_caller_address_once(inner_token, OWNER);
123
+ token.transfer(adapter_address, amount);
124
+ }
125
+
126
+ fn create_test_send_param() -> SendParam {
127
+ SendParam {
128
+ dst_eid: DST_EID,
129
+ to: RECIPIENT.into(),
130
+ amount_ld: TEST_AMOUNT_LD,
131
+ min_amount_ld: MIN_AMOUNT_LD,
132
+ extra_options: Default::default(),
133
+ compose_msg: Default::default(),
134
+ oft_cmd: Default::default(),
135
+ }
136
+ }
137
+
138
+ // =============================== Constructor Tests =================================
139
+
140
+ #[test]
141
+ fn test_constructor_valid_params() {
142
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
143
+ let oft = IOFTDispatcher { contract_address: adapter_address };
144
+
145
+ // Check that token is correctly set
146
+ let token_address = oft.token();
147
+ assert_eq!(token_address, inner_token);
148
+
149
+ // Check shared decimals
150
+ let decimals = oft.shared_decimals();
151
+ assert_eq!(decimals, SHARED_DECIMALS);
152
+
153
+ // Check approval required
154
+ let approval_required = oft.approval_required();
155
+ assert(approval_required, 'Should require approval');
156
+ }
157
+
158
+ // =============================== Token Interface Tests =================================
159
+
160
+ #[test]
161
+ fn test_token_returns_inner_token() {
162
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
163
+ let oft = IOFTDispatcher { contract_address: adapter_address };
164
+
165
+ let token_address = oft.token();
166
+ assert_eq!(token_address, inner_token);
167
+ }
168
+
169
+ #[test]
170
+ fn test_approval_required_returns_true() {
171
+ let (adapter_address, _, _) = setup_oft_adapter();
172
+ let oft = IOFTDispatcher { contract_address: adapter_address };
173
+
174
+ let approval_required = oft.approval_required();
175
+ assert(approval_required, 'Should require approval');
176
+ }
177
+
178
+ #[test]
179
+ fn test_shared_decimals() {
180
+ let (adapter_address, _, _) = setup_oft_adapter();
181
+ let oft = IOFTDispatcher { contract_address: adapter_address };
182
+
183
+ let decimals = oft.shared_decimals();
184
+ assert_eq!(decimals, SHARED_DECIMALS);
185
+ }
186
+
187
+ // =============================== Debit View Function Tests =================================
188
+
189
+ #[test]
190
+ fn test_debit_normal_operation() {
191
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
192
+ let token = IERC20Dispatcher { contract_address: inner_token };
193
+
194
+ setup_adapter_with_peers(adapter_address);
195
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, TEST_AMOUNT_LD);
196
+
197
+ // Check initial balances
198
+ let initial_user_balance = token.balance_of(USER);
199
+ let initial_adapter_balance = token.balance_of(adapter_address);
200
+
201
+ // Test quote_oft which should show the amounts correctly
202
+ let send_param = create_test_send_param();
203
+ let oft = IOFTDispatcher { contract_address: adapter_address };
204
+
205
+ let quote = oft.quote_oft(send_param);
206
+
207
+ // Verify the quote shows correct amounts
208
+ assert_eq!(quote.receipt.amount_sent_ld, TEST_AMOUNT_LD);
209
+ assert_eq!(quote.receipt.amount_received_ld, TEST_AMOUNT_LD);
210
+
211
+ // Verify balances haven't changed (quote doesn't transfer)
212
+ assert_eq!(token.balance_of(USER), initial_user_balance);
213
+ assert_eq!(token.balance_of(adapter_address), initial_adapter_balance);
214
+ }
215
+
216
+ #[test]
217
+ fn test_debit_zero_amount() {
218
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
219
+
220
+ setup_adapter_with_peers(adapter_address);
221
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, TEST_AMOUNT_LD);
222
+
223
+ let mut send_param = create_test_send_param();
224
+ send_param.amount_ld = 0;
225
+ send_param.min_amount_ld = 0;
226
+
227
+ let oft = IOFTDispatcher { contract_address: adapter_address };
228
+ let quote = oft.quote_oft(send_param);
229
+
230
+ // Should handle zero amount gracefully
231
+ assert(quote.receipt.amount_sent_ld == 0, 'Should accept zero amount');
232
+ }
233
+
234
+ #[test]
235
+ fn test_debit_insufficient_balance() {
236
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
237
+ let token = IERC20Dispatcher { contract_address: inner_token };
238
+
239
+ setup_adapter_with_peers(adapter_address);
240
+ // Don't give user any tokens - verify they have zero balance
241
+ let user_balance = token.balance_of(USER);
242
+ assert(user_balance == 0, 'User should have no tokens');
243
+
244
+ let send_param = create_test_send_param();
245
+ let oft = IOFTDispatcher { contract_address: adapter_address };
246
+
247
+ let quote = oft.quote_oft(send_param);
248
+
249
+ // Quote should still work but actual send would fail
250
+ assert(quote.receipt.amount_sent_ld == TEST_AMOUNT_LD, 'Quote works');
251
+ }
252
+
253
+ #[test]
254
+ fn test_debit_insufficient_allowance() {
255
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
256
+ let token = IERC20Dispatcher { contract_address: inner_token };
257
+
258
+ setup_adapter_with_peers(adapter_address);
259
+
260
+ // Give user tokens but no approval
261
+ cheat_caller_address_once(inner_token, OWNER);
262
+ token.transfer(USER, TEST_AMOUNT_LD);
263
+
264
+ let send_param = create_test_send_param();
265
+ let oft = IOFTDispatcher { contract_address: adapter_address };
266
+
267
+ let quote = oft.quote_oft(send_param);
268
+
269
+ // Quote should still work but actual send would fail
270
+ assert(quote.receipt.amount_sent_ld == TEST_AMOUNT_LD, 'Quote works');
271
+ }
272
+
273
+ #[test]
274
+ #[feature("safe_dispatcher")]
275
+ fn test_debit_slippage_exceeded() {
276
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
277
+
278
+ setup_adapter_with_peers(adapter_address);
279
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, TEST_AMOUNT_LD);
280
+
281
+ let mut send_param = create_test_send_param();
282
+ send_param.min_amount_ld = TEST_AMOUNT_LD + 1; // Set min higher than amount
283
+
284
+ let oft_safe = IOFTSafeDispatcher { contract_address: adapter_address };
285
+ let result = oft_safe.quote_oft(send_param);
286
+
287
+ assert_panic_with_error(result, err_slippage_exceeded(TEST_AMOUNT_LD, TEST_AMOUNT_LD + 1));
288
+ }
289
+
290
+ // =============================== Debit Function Tests =================================
291
+
292
+ #[test]
293
+ #[fuzzer(runs: 10)]
294
+ fn test_debit_successful_send(
295
+ amount_seed: u256, native_fee: u128, user_native_remainder: u128, user_erc20_remainder: u128,
296
+ ) {
297
+ let (adapter_address, inner_token, stark_token) = setup_oft_adapter();
298
+ let stark_token_dispatcher = IERC20Dispatcher { contract_address: stark_token };
299
+ let inner_token_dispatcher = IERC20Dispatcher { contract_address: inner_token };
300
+
301
+ // =============================== Transfer parameters =================================
302
+
303
+ let diff_decimals = IOFTDispatcher { contract_address: adapter_address }
304
+ .decimal_conversion_rate();
305
+
306
+ let amount_ld = amount_seed % (Bounded::<u64>::MAX.into() * diff_decimals);
307
+ let amount_sd: u64 = (amount_ld / diff_decimals).try_into().unwrap();
308
+ let dust_ld = amount_ld % diff_decimals;
309
+ let clean_amount_ld = amount_ld - dust_ld;
310
+ // Expect a lossless token transfer.
311
+ let min_amount_ld = amount_sd.into() * diff_decimals;
312
+
313
+ // Preconditions
314
+ assert_eq!(clean_amount_ld, min_amount_ld);
315
+ assert(amount_sd > 0, 'Greater than 0'); // Hopefully, we never hit 0 of `u64`...
316
+
317
+ // =============================== Setup =================================
318
+
319
+ setup_adapter_with_peers(adapter_address);
320
+
321
+ let mocked_messaging_fee = MessagingFee { native_fee: native_fee.into(), lz_token_fee: 0 };
322
+
323
+ IMockERC20Dispatcher { contract_address: stark_token }
324
+ .mint(USER, native_fee.into() + user_native_remainder.into());
325
+ IMockERC20Dispatcher { contract_address: inner_token }
326
+ .mint(USER, amount_ld.into() + user_erc20_remainder.into());
327
+
328
+ // mock endpoint quote call to return the expected send fee
329
+ start_mock_call(FAKE_ENDPOINT, selector!("quote"), mocked_messaging_fee);
330
+
331
+ // =============================== Send tokens =================================
332
+
333
+ // User has to approve the adapter to spend the native fee
334
+ cheat_caller_address_once(stark_token, USER);
335
+ IERC20Dispatcher { contract_address: stark_token }.approve(adapter_address, native_fee.into());
336
+
337
+ // User has to approve the adapter to transfer the amount to send
338
+ cheat_caller_address_once(inner_token, USER);
339
+ IERC20Dispatcher { contract_address: inner_token }
340
+ .approve(adapter_address, clean_amount_ld.into());
341
+
342
+ let send_param = SendParam {
343
+ dst_eid: DST_EID,
344
+ to: RECIPIENT.into(),
345
+ amount_ld,
346
+ min_amount_ld: clean_amount_ld,
347
+ // not using extra options, compose msg, or oft cmd in this test
348
+ extra_options: Default::default(),
349
+ compose_msg: Default::default(),
350
+ oft_cmd: Default::default(),
351
+ };
352
+
353
+ let quote = IOFTDispatcher { contract_address: adapter_address }
354
+ .quote_send(send_param.clone(), false);
355
+
356
+ // Mock a message receipt returned from the endpoint.
357
+ // GUID, nonce, and payees are not important for this test.
358
+ start_mock_call(
359
+ FAKE_ENDPOINT,
360
+ selector!("send"),
361
+ MessageReceipt { guid: Bytes32 { value: 1 }, nonce: 1, payees: array![] },
362
+ );
363
+
364
+ cheat_caller_address_once(adapter_address, USER);
365
+ let result = IOFTDispatcher { contract_address: adapter_address }.send(send_param, quote, USER);
366
+
367
+ // =============================== Assertions =================================
368
+
369
+ // User should have sent the amount of tokens to the OFT adapter.
370
+ assert_eq!(result.oft_receipt.amount_sent_ld, clean_amount_ld);
371
+ assert_eq!(result.oft_receipt.amount_received_ld, clean_amount_ld);
372
+
373
+ // Native tokens should have been approved to the endpoint.
374
+ // The real endpoint would have used the allowance and refund the remainder,
375
+ // but here the OFT adapter will have approved the endpoint to spend the native fee.
376
+ assert_eq!(stark_token_dispatcher.allowance(adapter_address, FAKE_ENDPOINT), native_fee.into());
377
+
378
+ // The sender should have spent the native fee.
379
+ assert_eq!(stark_token_dispatcher.balance_of(USER), user_native_remainder.into());
380
+
381
+ // The sender's balance of the custom token should have been reduced by the amount of custom
382
+ // tokens sent.
383
+ assert_eq!(
384
+ inner_token_dispatcher.balance_of(USER), user_erc20_remainder.into() + dust_ld.into(),
385
+ );
386
+
387
+ // The adapter should have received the amount of tokens.
388
+ assert_eq!(inner_token_dispatcher.balance_of(adapter_address), clean_amount_ld.into());
389
+
390
+ // The adapter should have received the native fee.
391
+ assert_eq!(stark_token_dispatcher.balance_of(adapter_address), native_fee.into());
392
+
393
+ // The total supply of the custom token should be the total, nothing should be burnt
394
+ assert_eq!(
395
+ inner_token_dispatcher.total_supply(),
396
+ amount_ld.into() + user_erc20_remainder.into() + INITIAL_SUPPLY.into(),
397
+ );
398
+ }
399
+
400
+ #[test]
401
+ #[feature("safe_dispatcher")]
402
+ #[fuzzer(runs: 10)]
403
+ fn test_send_fee_transfer_failure(native_fee: u256) {
404
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
405
+
406
+ start_mock_call(inner_token, selector!("transfer_from"), false);
407
+ let result = IOFTSafeDispatcher { contract_address: adapter_address }
408
+ .send(create_test_send_param(), MessagingFee { native_fee, lz_token_fee: 0 }, USER);
409
+
410
+ assert_panic_with_error(result, err_oft_transfer_failed());
411
+ }
412
+
413
+ // =============================== Credit Function Tests =================================
414
+
415
+ #[test]
416
+ fn test_credit_normal_operation() {
417
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
418
+ let token = IERC20Dispatcher { contract_address: inner_token };
419
+ let lz_receiver = ILayerZeroReceiverDispatcher { contract_address: adapter_address };
420
+
421
+ setup_adapter_with_peers(adapter_address);
422
+ setup_adapter_tokens(inner_token, adapter_address, LARGE_AMOUNT);
423
+
424
+ let initial_user_balance = token.balance_of(USER);
425
+ let initial_adapter_balance = token.balance_of(adapter_address);
426
+
427
+ // Encode a message with 1 token in shared decimals.
428
+ let (message, _) = OFTMsgCodec::encode(USER.into(), 1_000_000, @"");
429
+
430
+ cheat_caller_address_once(adapter_address, FAKE_ENDPOINT);
431
+ lz_receiver.lz_receive(MOCK_ORIGIN(), MOCK_GUID(), message, USER, Default::default(), 0);
432
+
433
+ let final_user_balance = token.balance_of(USER);
434
+ let final_adapter_balance = token.balance_of(adapter_address);
435
+
436
+ // User should receive tokens, adapter should lose tokens
437
+ assert(final_user_balance > initial_user_balance, 'User should receive tokens');
438
+ assert(final_adapter_balance < initial_adapter_balance, 'Adapter should lose tokens');
439
+ assert_eq!(final_user_balance - initial_user_balance, TEST_AMOUNT_LD);
440
+ }
441
+
442
+ #[test]
443
+ fn test_credit_zero_amount() {
444
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
445
+ let token = IERC20Dispatcher { contract_address: inner_token };
446
+ let lz_receiver = ILayerZeroReceiverDispatcher { contract_address: adapter_address };
447
+
448
+ setup_adapter_with_peers(adapter_address);
449
+ setup_adapter_tokens(inner_token, adapter_address, LARGE_AMOUNT);
450
+
451
+ let initial_user_balance = token.balance_of(USER);
452
+ let (message, _) = OFTMsgCodec::encode(USER.into(), 0, @"");
453
+
454
+ cheat_caller_address_once(adapter_address, FAKE_ENDPOINT);
455
+ lz_receiver.lz_receive(MOCK_ORIGIN(), MOCK_GUID(), message, USER, Default::default(), 0);
456
+
457
+ let final_user_balance = token.balance_of(USER);
458
+ assert_eq!(final_user_balance, initial_user_balance);
459
+ }
460
+
461
+ #[test]
462
+ #[feature("safe_dispatcher")]
463
+ #[fuzzer(runs: 10)]
464
+ fn test_credit_insufficient_adapter_balance(
465
+ amount_to_receive: u64, tokens_missing_on_adapter: u64,
466
+ ) {
467
+ // prevent flakiness
468
+ let amount_to_receive = amount_to_receive.saturating_add(1);
469
+ let tokens_on_adapter = amount_to_receive
470
+ .saturating_sub(tokens_missing_on_adapter.saturating_add(1));
471
+
472
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
473
+ setup_adapter_with_peers(adapter_address);
474
+
475
+ IMockERC20Dispatcher { contract_address: inner_token }
476
+ .mint(adapter_address, tokens_on_adapter.into());
477
+
478
+ let (message, _) = OFTMsgCodec::encode(USER.into(), amount_to_receive, @"");
479
+
480
+ cheat_caller_address_once(adapter_address, FAKE_ENDPOINT);
481
+ // Simulate receiving tokens when adapter has insufficient balance
482
+ let result = ILayerZeroReceiverSafeDispatcher { contract_address: adapter_address }
483
+ .lz_receive(MOCK_ORIGIN(), MOCK_GUID(), message, USER, Default::default(), 0);
484
+
485
+ assert_panic_with_felt_error(result, ERC20Errors::INSUFFICIENT_BALANCE);
486
+ }
487
+
488
+ #[test]
489
+ #[feature("safe_dispatcher")]
490
+ fn test_credit_transfer_failure() {
491
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
492
+
493
+ setup_adapter_with_peers(adapter_address);
494
+ start_mock_call(inner_token, selector!("transfer"), false);
495
+
496
+ let (message, _) = OFTMsgCodec::encode(USER.into(), 0, @"");
497
+
498
+ cheat_caller_address_once(adapter_address, FAKE_ENDPOINT);
499
+ let result = ILayerZeroReceiverSafeDispatcher { contract_address: adapter_address }
500
+ .lz_receive(MOCK_ORIGIN(), MOCK_GUID(), message, USER, Default::default(), 0);
501
+
502
+ assert_panic_with_error(result, err_oft_transfer_failed());
503
+ }
504
+
505
+ // =============================== Integration Tests =================================
506
+
507
+ #[test]
508
+ fn test_full_send_receive_cycle() {
509
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
510
+ let token = IERC20Dispatcher { contract_address: inner_token };
511
+ let lz_receiver = ILayerZeroReceiverDispatcher { contract_address: adapter_address };
512
+
513
+ setup_adapter_with_peers(adapter_address);
514
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, LARGE_AMOUNT);
515
+ setup_adapter_tokens(inner_token, adapter_address, LARGE_AMOUNT);
516
+
517
+ // First, simulate sending tokens (debit)
518
+ let send_param = create_test_send_param();
519
+ let oft = IOFTDispatcher { contract_address: adapter_address };
520
+
521
+ let initial_balance = token.balance_of(USER);
522
+
523
+ // Quote the send first and verify it works
524
+ let quote = oft.quote_oft(send_param);
525
+ assert_eq!(quote.receipt.amount_sent_ld, TEST_AMOUNT_LD);
526
+
527
+ // Encode a message with 1 token in shared decimals.
528
+ let (message, _) = OFTMsgCodec::encode(USER.into(), 1_000_000, @"");
529
+
530
+ // Then simulate receiving tokens back (credit)
531
+ cheat_caller_address_once(adapter_address, FAKE_ENDPOINT);
532
+ lz_receiver.lz_receive(MOCK_ORIGIN(), MOCK_GUID(), message, USER, Default::default(), 0);
533
+
534
+ let final_balance = token.balance_of(USER);
535
+
536
+ // User should have received tokens
537
+ assert(final_balance > initial_balance, 'Should receive tokens');
538
+ }
539
+
540
+ // =============================== Edge Case Tests =================================
541
+
542
+ #[test]
543
+ fn test_large_amounts() {
544
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
545
+
546
+ setup_adapter_with_peers(adapter_address);
547
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, INITIAL_SUPPLY);
548
+
549
+ let mut send_param = create_test_send_param();
550
+ send_param.amount_ld = INITIAL_SUPPLY / 2; // Send half of total supply
551
+ send_param.min_amount_ld = INITIAL_SUPPLY / 3;
552
+
553
+ let oft = IOFTDispatcher { contract_address: adapter_address };
554
+
555
+ let quote = oft.quote_oft(send_param);
556
+
557
+ assert_eq!(quote.receipt.amount_sent_ld, INITIAL_SUPPLY / 2);
558
+ }
559
+
560
+ #[test]
561
+ fn test_multiple_users() {
562
+ let (adapter_address, inner_token, _) = setup_oft_adapter();
563
+ let user2: ContractAddress = 'user2'.try_into().unwrap();
564
+
565
+ setup_adapter_with_peers(adapter_address);
566
+ setup_user_tokens_and_approval(inner_token, adapter_address, USER, TEST_AMOUNT_LD);
567
+ setup_user_tokens_and_approval(inner_token, adapter_address, user2, TEST_AMOUNT_LD);
568
+
569
+ let send_param = create_test_send_param();
570
+ let oft = IOFTDispatcher { contract_address: adapter_address };
571
+
572
+ // Both users should be able to quote sends
573
+ let quote1 = oft.quote_oft(send_param);
574
+ let send_param2 = create_test_send_param();
575
+ let quote2 = oft.quote_oft(send_param2);
576
+
577
+ assert_eq!(quote1.receipt.amount_sent_ld, TEST_AMOUNT_LD);
578
+ assert_eq!(quote2.receipt.amount_sent_ld, TEST_AMOUNT_LD);
579
+ }
@@ -0,0 +1,9 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ export { __export };
8
+ //# sourceMappingURL=3UUTAAI4.js.map
9
+ //# sourceMappingURL=3UUTAAI4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"3UUTAAI4.js"}
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ var B3SWU5G3_cjs = require('./B3SWU5G3.cjs');
4
+ var E63KEOR5_cjs = require('./E63KEOR5.cjs');
5
+
6
+ // src/generated/abi.ts
7
+ var abi_exports = {};
8
+ E63KEOR5_cjs.__export(abi_exports, {
9
+ oFTAdapter: () => B3SWU5G3_cjs.oFTAdapter
10
+ });
11
+
12
+ exports.abi_exports = abi_exports;
13
+ //# sourceMappingURL=4VIL37TM.cjs.map
14
+ //# sourceMappingURL=4VIL37TM.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generated/abi.ts"],"names":[],"mappings":";;;;;;AAAA,IAAA,WAAA,GAAA","file":"4VIL37TM.cjs","sourcesContent":["export * from \"./abi/o-f-t-adapter.js\";"]}