@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.
- package/Scarb.lock +190 -0
- package/Scarb.toml +4 -0
- package/contracts/oft_adapter/Scarb.toml +30 -0
- package/contracts/oft_adapter/src/lib.cairo +1 -0
- package/contracts/oft_adapter/src/oft_adapter.cairo +150 -0
- package/contracts/oft_adapter/tests/constants.cairo +3 -0
- package/contracts/oft_adapter/tests/lib.cairo +11 -0
- package/contracts/oft_adapter/tests/mocks/erc20/erc20.cairo +49 -0
- package/contracts/oft_adapter/tests/mocks/erc20/interface.cairo +8 -0
- package/contracts/oft_adapter/tests/mocks/message_inspector/message_inspector.cairo +17 -0
- package/contracts/oft_adapter/tests/test_oft_adapter.cairo +579 -0
- package/dist/3UUTAAI4.js +9 -0
- package/dist/3UUTAAI4.js.map +1 -0
- package/dist/4VIL37TM.cjs +14 -0
- package/dist/4VIL37TM.cjs.map +1 -0
- package/dist/5AQVVHK2.cjs +17 -0
- package/dist/5AQVVHK2.cjs.map +1 -0
- package/dist/6JUHMRLN.js +14 -0
- package/dist/6JUHMRLN.js.map +1 -0
- package/dist/B3SWU5G3.cjs +990 -0
- package/dist/B3SWU5G3.cjs.map +1 -0
- package/dist/E63KEOR5.cjs +11 -0
- package/dist/E63KEOR5.cjs.map +1 -0
- package/dist/HRRTIIZM.cjs +1282 -0
- package/dist/HRRTIIZM.cjs.map +1 -0
- package/dist/HTWTY64L.js +14 -0
- package/dist/HTWTY64L.js.map +1 -0
- package/dist/IH3YG4QY.js +12 -0
- package/dist/IH3YG4QY.js.map +1 -0
- package/dist/JFLNUTE2.js +988 -0
- package/dist/JFLNUTE2.js.map +1 -0
- package/dist/JNHB3COG.cjs +14 -0
- package/dist/JNHB3COG.cjs.map +1 -0
- package/dist/KGI5KEFS.js +1280 -0
- package/dist/KGI5KEFS.js.map +1 -0
- package/dist/KRS32YQY.cjs +17 -0
- package/dist/KRS32YQY.cjs.map +1 -0
- package/dist/RU2XCZGW.js +12 -0
- package/dist/RU2XCZGW.js.map +1 -0
- package/dist/generated/abi/o-f-t-adapter.cjs +13 -0
- package/dist/generated/abi/o-f-t-adapter.cjs.map +1 -0
- package/dist/generated/abi/o-f-t-adapter.d.ts +722 -0
- package/dist/generated/abi/o-f-t-adapter.d.ts.map +1 -0
- package/dist/generated/abi/o-f-t-adapter.js +4 -0
- package/dist/generated/abi/o-f-t-adapter.js.map +1 -0
- package/dist/generated/abi.cjs +14 -0
- package/dist/generated/abi.cjs.map +1 -0
- package/dist/generated/abi.d.ts +2 -0
- package/dist/generated/abi.d.ts.map +1 -0
- package/dist/generated/abi.js +5 -0
- package/dist/generated/abi.js.map +1 -0
- package/dist/generated/casm.cjs +13 -0
- package/dist/generated/casm.cjs.map +1 -0
- package/dist/generated/casm.d.ts +2 -0
- package/dist/generated/casm.d.ts.map +1 -0
- package/dist/generated/casm.js +4 -0
- package/dist/generated/casm.js.map +1 -0
- package/dist/generated/sierra.cjs +13 -0
- package/dist/generated/sierra.cjs.map +1 -0
- package/dist/generated/sierra.d.ts +2 -0
- package/dist/generated/sierra.d.ts.map +1 -0
- package/dist/generated/sierra.js +4 -0
- package/dist/generated/sierra.js.map +1 -0
- package/dist/generated/verification/index.cjs +14 -0
- package/dist/generated/verification/index.cjs.map +1 -0
- package/dist/generated/verification/index.d.ts +2 -0
- package/dist/generated/verification/index.d.ts.map +1 -0
- package/dist/generated/verification/index.js +5 -0
- package/dist/generated/verification/index.js.map +1 -0
- package/dist/generated/verification/oft_adapter.cjs +10 -0
- package/dist/generated/verification/oft_adapter.cjs.map +1 -0
- package/dist/generated/verification/oft_adapter.d.ts +4 -0
- package/dist/generated/verification/oft_adapter.d.ts.map +1 -0
- package/dist/generated/verification/oft_adapter.js +4 -0
- package/dist/generated/verification/oft_adapter.js.map +1 -0
- package/dist/index.cjs +30 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- 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
|
+
}
|
package/dist/3UUTAAI4.js
ADDED
|
@@ -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\";"]}
|