@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,870 @@
|
|
|
1
|
+
//! Shared test utilities for OFT unit tests.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides common test contracts and helpers used across multiple test files.
|
|
4
|
+
|
|
5
|
+
use crate::{
|
|
6
|
+
codec::oft_msg_codec::OFTMessage,
|
|
7
|
+
oft_core::OFTClient,
|
|
8
|
+
types::{OFTReceipt, SendParam},
|
|
9
|
+
};
|
|
10
|
+
use endpoint_v2::{LayerZeroReceiverClient, MessagingFee, MessagingParams, MessagingReceipt, Origin};
|
|
11
|
+
use oapp::oapp_core::OAppCoreClient;
|
|
12
|
+
use soroban_sdk::{
|
|
13
|
+
address_payload::AddressPayload,
|
|
14
|
+
bytes, contract, contractimpl, log, symbol_short,
|
|
15
|
+
testutils::{Address as _, MockAuth, MockAuthInvoke},
|
|
16
|
+
token::{StellarAssetClient, TokenClient},
|
|
17
|
+
Address, Bytes, BytesN, Env, IntoVal, String, Symbol,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// ==================== Constants ====================
|
|
21
|
+
|
|
22
|
+
/// Default shared decimals used for cross-chain normalization in tests
|
|
23
|
+
pub const DEFAULT_SHARED_DECIMALS: u32 = 6;
|
|
24
|
+
|
|
25
|
+
// ==================== Helper Functions ====================
|
|
26
|
+
|
|
27
|
+
/// Create a SendParam for testing with default options.
|
|
28
|
+
pub fn create_send_param(env: &Env, dst_eid: u32, amount_ld: i128, min_amount_ld: i128) -> SendParam {
|
|
29
|
+
SendParam {
|
|
30
|
+
dst_eid,
|
|
31
|
+
to: BytesN::from_array(env, &[1u8; 32]),
|
|
32
|
+
amount_ld,
|
|
33
|
+
min_amount_ld,
|
|
34
|
+
extra_options: bytes!(env),
|
|
35
|
+
compose_msg: bytes!(env),
|
|
36
|
+
oft_cmd: bytes!(env),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Creates a valid recipient address by deploying a dummy contract.
|
|
41
|
+
/// Use this in tests when the address needs to pass the `.exists()` check.
|
|
42
|
+
pub fn create_recipient_address(env: &Env) -> Address {
|
|
43
|
+
env.register(DummyRecipient, ())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Creates a G-address (account address) from a 32-byte Ed25519 public key.
|
|
47
|
+
/// This is useful for testing with account addresses instead of contract addresses.
|
|
48
|
+
pub fn create_g_address(env: &Env, public_key: &BytesN<32>) -> Address {
|
|
49
|
+
Address::from_payload(env, AddressPayload::AccountIdPublicKeyEd25519(public_key.clone()))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Generates a unique G-address (account address) for testing.
|
|
53
|
+
/// Each call generates a different address by using a counter-based approach.
|
|
54
|
+
pub fn generate_g_address(env: &Env) -> Address {
|
|
55
|
+
// Use Address::generate which creates a unique address each time
|
|
56
|
+
// Then convert it to ensure it's a G-address (account address)
|
|
57
|
+
let addr = Address::generate(env);
|
|
58
|
+
// Extract the payload - if it's already a G-address, use it; otherwise convert
|
|
59
|
+
match addr.to_payload() {
|
|
60
|
+
Some(AddressPayload::AccountIdPublicKeyEd25519(_pk)) => {
|
|
61
|
+
// Already a G-address, return as-is
|
|
62
|
+
addr
|
|
63
|
+
}
|
|
64
|
+
Some(AddressPayload::ContractIdHash(hash)) => {
|
|
65
|
+
// It's a contract address, convert hash to G-address
|
|
66
|
+
// Use the hash bytes as the Ed25519 public key
|
|
67
|
+
create_g_address(env, &hash)
|
|
68
|
+
}
|
|
69
|
+
None => {
|
|
70
|
+
// Fallback: create from hash bytes
|
|
71
|
+
let hash = BytesN::from_array(env, &[0u8; 32]);
|
|
72
|
+
create_g_address(env, &hash)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
pub fn encode_oft_message(env: &Env, send_to: &BytesN<32>, amount_sd: u64) -> Bytes {
|
|
78
|
+
let msg = OFTMessage { send_to: send_to.clone(), amount_sd, compose: None };
|
|
79
|
+
msg.encode(env)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn encode_oft_message_with_compose(
|
|
83
|
+
env: &Env,
|
|
84
|
+
send_to: &BytesN<32>,
|
|
85
|
+
amount_sd: u64,
|
|
86
|
+
compose_from: &BytesN<32>,
|
|
87
|
+
compose_msg: &Bytes,
|
|
88
|
+
) -> Bytes {
|
|
89
|
+
use crate::codec::oft_msg_codec::ComposeData;
|
|
90
|
+
let msg = OFTMessage {
|
|
91
|
+
send_to: send_to.clone(),
|
|
92
|
+
amount_sd,
|
|
93
|
+
compose: Some(ComposeData { from: compose_from.clone(), msg: compose_msg.clone() }),
|
|
94
|
+
};
|
|
95
|
+
msg.encode(env)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
pub fn create_origin(src_eid: u32, sender: &BytesN<32>, nonce: u64) -> Origin {
|
|
99
|
+
Origin { src_eid, sender: sender.clone(), nonce }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ==================== Test OFT Contracts ====================
|
|
103
|
+
|
|
104
|
+
mod test_mint_burn_oft {
|
|
105
|
+
use crate::{
|
|
106
|
+
self as oft_core,
|
|
107
|
+
oft_core::{OFTCore, OFTInternal},
|
|
108
|
+
};
|
|
109
|
+
use endpoint_v2::Origin;
|
|
110
|
+
use oapp::oapp_receiver::LzReceiveInternal;
|
|
111
|
+
use soroban_sdk::{contractclient, contractimpl, Address, Bytes, BytesN, Env};
|
|
112
|
+
|
|
113
|
+
#[contractclient(name = "MintBurnTokenClient")]
|
|
114
|
+
#[allow(dead_code)]
|
|
115
|
+
trait MintBurnToken {
|
|
116
|
+
fn mint(env: Env, to: Address, amount: i128);
|
|
117
|
+
fn burn(env: Env, from: Address, amount: i128);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[oapp_macros::oapp]
|
|
121
|
+
#[common_macros::lz_contract]
|
|
122
|
+
pub struct TestMintBurnOFT;
|
|
123
|
+
|
|
124
|
+
#[contractimpl]
|
|
125
|
+
impl TestMintBurnOFT {
|
|
126
|
+
pub fn __constructor(
|
|
127
|
+
env: &Env,
|
|
128
|
+
token: &Address,
|
|
129
|
+
owner: &Address,
|
|
130
|
+
endpoint: &Address,
|
|
131
|
+
delegate: &Address,
|
|
132
|
+
shared_decimals: u32,
|
|
133
|
+
) {
|
|
134
|
+
Self::__initialize_oft(env, token, shared_decimals, owner, endpoint, delegate)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#[contractimpl(contracttrait)]
|
|
139
|
+
impl OFTCore for TestMintBurnOFT {}
|
|
140
|
+
|
|
141
|
+
impl LzReceiveInternal for TestMintBurnOFT {
|
|
142
|
+
fn __lz_receive(
|
|
143
|
+
env: &Env,
|
|
144
|
+
origin: &Origin,
|
|
145
|
+
guid: &BytesN<32>,
|
|
146
|
+
message: &Bytes,
|
|
147
|
+
extra_data: &Bytes,
|
|
148
|
+
executor: &Address,
|
|
149
|
+
value: i128,
|
|
150
|
+
) {
|
|
151
|
+
<Self as OFTInternal>::__receive(env, origin, guid, message, extra_data, executor, value)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
impl OFTInternal for TestMintBurnOFT {
|
|
156
|
+
fn __debit(env: &Env, sender: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
|
|
157
|
+
// Inline mint_burn::debit implementation
|
|
158
|
+
let (amount_sent_ld, amount_received_ld) = Self::__debit_view(env, amount_ld, min_amount_ld, dst_eid);
|
|
159
|
+
MintBurnTokenClient::new(env, &Self::token(env)).burn(sender, &amount_received_ld);
|
|
160
|
+
(amount_sent_ld, amount_received_ld)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn __credit(env: &Env, to: &Address, amount_ld: i128, _src_eid: u32) -> i128 {
|
|
164
|
+
// Inline mint_burn::credit implementation
|
|
165
|
+
MintBurnTokenClient::new(env, &Self::token(env)).mint(to, &amount_ld);
|
|
166
|
+
amount_ld
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
pub use test_mint_burn_oft::TestMintBurnOFT;
|
|
171
|
+
|
|
172
|
+
mod test_lock_unlock_oft {
|
|
173
|
+
use crate::{
|
|
174
|
+
self as oft_core,
|
|
175
|
+
oft_core::{OFTCore, OFTInternal},
|
|
176
|
+
};
|
|
177
|
+
use endpoint_v2::Origin;
|
|
178
|
+
use oapp::oapp_receiver::{LzReceiveInternal, OAppReceiver};
|
|
179
|
+
use soroban_sdk::{contractimpl, token::TokenClient, Address, Bytes, BytesN, Env};
|
|
180
|
+
|
|
181
|
+
#[oapp_macros::oapp(custom = [receiver])]
|
|
182
|
+
#[common_macros::lz_contract]
|
|
183
|
+
pub struct TestLockUnlockOFT;
|
|
184
|
+
|
|
185
|
+
#[contractimpl]
|
|
186
|
+
impl TestLockUnlockOFT {
|
|
187
|
+
pub fn __constructor(
|
|
188
|
+
env: &Env,
|
|
189
|
+
token: &Address,
|
|
190
|
+
owner: &Address,
|
|
191
|
+
endpoint: &Address,
|
|
192
|
+
delegate: &Address,
|
|
193
|
+
shared_decimals: u32,
|
|
194
|
+
) {
|
|
195
|
+
Self::__initialize_oft(env, token, shared_decimals, owner, endpoint, delegate)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[contractimpl(contracttrait)]
|
|
200
|
+
impl OFTCore for TestLockUnlockOFT {}
|
|
201
|
+
|
|
202
|
+
impl LzReceiveInternal for TestLockUnlockOFT {
|
|
203
|
+
fn __lz_receive(
|
|
204
|
+
env: &Env,
|
|
205
|
+
origin: &Origin,
|
|
206
|
+
guid: &BytesN<32>,
|
|
207
|
+
message: &Bytes,
|
|
208
|
+
extra_data: &Bytes,
|
|
209
|
+
executor: &Address,
|
|
210
|
+
value: i128,
|
|
211
|
+
) {
|
|
212
|
+
<Self as OFTInternal>::__receive(env, origin, guid, message, extra_data, executor, value)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Custom receiver to demonstrate overriding next_nonce or other methods
|
|
217
|
+
#[contractimpl(contracttrait)]
|
|
218
|
+
impl OAppReceiver for TestLockUnlockOFT {}
|
|
219
|
+
|
|
220
|
+
impl OFTInternal for TestLockUnlockOFT {
|
|
221
|
+
fn __debit(env: &Env, sender: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
|
|
222
|
+
// Inline lock_unlock::debit implementation
|
|
223
|
+
let (amount_sent_ld, amount_received_ld) = Self::__debit_view(env, amount_ld, min_amount_ld, dst_eid);
|
|
224
|
+
TokenClient::new(env, &Self::token(env)).transfer(
|
|
225
|
+
sender,
|
|
226
|
+
env.current_contract_address(),
|
|
227
|
+
&amount_received_ld,
|
|
228
|
+
);
|
|
229
|
+
(amount_sent_ld, amount_received_ld)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn __credit(env: &Env, to: &Address, amount_ld: i128, _src_eid: u32) -> i128 {
|
|
233
|
+
// Inline lock_unlock::credit implementation
|
|
234
|
+
TokenClient::new(env, &Self::token(env)).transfer(&env.current_contract_address(), to, &amount_ld);
|
|
235
|
+
amount_ld
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
pub use test_lock_unlock_oft::TestLockUnlockOFT;
|
|
240
|
+
|
|
241
|
+
// ==================== Dummy Contracts ====================
|
|
242
|
+
|
|
243
|
+
/// Dummy recipient contract for testing - used to create valid contract addresses
|
|
244
|
+
#[contract]
|
|
245
|
+
pub struct DummyRecipient;
|
|
246
|
+
|
|
247
|
+
#[contractimpl]
|
|
248
|
+
impl DummyRecipient {
|
|
249
|
+
pub fn __constructor(_env: &Env) {}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Simple token contract for testing (replaces OpenZeppelin dependency)
|
|
253
|
+
#[contract]
|
|
254
|
+
pub struct DummyToken;
|
|
255
|
+
|
|
256
|
+
#[contractimpl]
|
|
257
|
+
impl DummyToken {
|
|
258
|
+
fn admin(env: &Env) -> Address {
|
|
259
|
+
env.storage().instance().get(&symbol_short!("admin")).unwrap()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
fn get_balance(env: &Env, addr: &Address) -> i128 {
|
|
263
|
+
env.storage().persistent().get(&addr).unwrap_or(0)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fn set_balance(env: &Env, addr: &Address, amount: i128) {
|
|
267
|
+
env.storage().persistent().set(&addr, &amount);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
pub fn __constructor(env: &Env, owner: Address, decimals: u32) {
|
|
271
|
+
env.storage().instance().set(&symbol_short!("admin"), &owner);
|
|
272
|
+
env.storage().instance().set(&symbol_short!("decimal"), &decimals);
|
|
273
|
+
env.storage().instance().set(&symbol_short!("name"), &String::from_str(env, "DummyToken"));
|
|
274
|
+
env.storage().instance().set(&symbol_short!("symbol"), &String::from_str(env, "DUMMY"));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// keep the same behavior as SAC that requires admin's authorization
|
|
278
|
+
pub fn set_admin(env: &Env, admin: &Address) {
|
|
279
|
+
Self::admin(env).require_auth();
|
|
280
|
+
env.storage().instance().set(&symbol_short!("admin"), &admin);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
pub fn mint(env: &Env, to: &Address, amount: i128) {
|
|
284
|
+
Self::admin(env).require_auth();
|
|
285
|
+
if amount < 0 {
|
|
286
|
+
panic!("negative amount");
|
|
287
|
+
}
|
|
288
|
+
let balance = Self::get_balance(env, to);
|
|
289
|
+
Self::set_balance(env, to, balance + amount);
|
|
290
|
+
log!(&env, "minted {} to {}", amount, to);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// keep the same behavior as SAC that requires from's authorization
|
|
294
|
+
pub fn burn(env: &Env, from: &Address, amount: i128) {
|
|
295
|
+
from.require_auth();
|
|
296
|
+
if amount < 0 {
|
|
297
|
+
panic!("negative amount");
|
|
298
|
+
}
|
|
299
|
+
let balance = Self::get_balance(env, from);
|
|
300
|
+
if balance < amount {
|
|
301
|
+
panic!("insufficient balance");
|
|
302
|
+
}
|
|
303
|
+
Self::set_balance(env, from, balance - amount);
|
|
304
|
+
log!(&env, "burned {} from {}", amount, from);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
pub fn balance(env: &Env, id: Address) -> i128 {
|
|
308
|
+
Self::get_balance(env, &id)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
pub fn transfer(env: &Env, from: Address, to: Address, amount: i128) {
|
|
312
|
+
from.require_auth();
|
|
313
|
+
if amount < 0 {
|
|
314
|
+
panic!("negative amount");
|
|
315
|
+
}
|
|
316
|
+
let from_balance = Self::get_balance(env, &from);
|
|
317
|
+
if from_balance < amount {
|
|
318
|
+
panic!("insufficient balance");
|
|
319
|
+
}
|
|
320
|
+
Self::set_balance(env, &from, from_balance - amount);
|
|
321
|
+
let to_balance = Self::get_balance(env, &to);
|
|
322
|
+
Self::set_balance(env, &to, to_balance + amount);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
pub fn decimals(env: &Env) -> u32 {
|
|
326
|
+
env.storage().instance().get(&symbol_short!("decimal")).unwrap_or(7)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
pub fn name(env: &Env) -> String {
|
|
330
|
+
env.storage().instance().get(&symbol_short!("name")).unwrap()
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
pub fn symbol(env: &Env) -> String {
|
|
334
|
+
env.storage().instance().get(&symbol_short!("symbol")).unwrap()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ==================== Mock Endpoint ====================
|
|
339
|
+
|
|
340
|
+
/// A comprehensive mock endpoint contract for testing OFT functionality.
|
|
341
|
+
/// Supports: quote, set_delegate, clear, send_compose, and compose verification.
|
|
342
|
+
#[contract]
|
|
343
|
+
pub struct MockEndpointWithCompose;
|
|
344
|
+
|
|
345
|
+
#[contractimpl]
|
|
346
|
+
impl MockEndpointWithCompose {
|
|
347
|
+
pub fn __constructor(env: Env, native_fee: i128, zro_fee: i128, native_token: Address, zro_token: Address) {
|
|
348
|
+
env.storage().instance().set(&symbol_short!("ntv_fee"), &native_fee);
|
|
349
|
+
env.storage().instance().set(&symbol_short!("zro_fee"), &zro_fee);
|
|
350
|
+
env.storage().instance().set(&symbol_short!("ntk"), &native_token);
|
|
351
|
+
env.storage().instance().set(&symbol_short!("zro"), &zro_token);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// Returns the native token address (required by OAppSenderInternal)
|
|
355
|
+
pub fn native_token(env: Env) -> Address {
|
|
356
|
+
env.storage().instance().get(&symbol_short!("ntk")).unwrap()
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// Returns the ZRO token address (required by OAppSenderInternal)
|
|
360
|
+
pub fn zro(env: Env) -> Option<Address> {
|
|
361
|
+
env.storage().instance().get(&symbol_short!("zro"))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Required by OApp initialization to set delegate
|
|
365
|
+
pub fn set_delegate(_env: Env, _oapp: Address, _delegate: Option<Address>) {
|
|
366
|
+
// No-op for testing
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/// Required by OAppReceiver.lz_receive to clear the payload
|
|
370
|
+
pub fn clear(_env: Env, _oapp: Address, _origin: Origin, _receiver: Address, _guid: BytesN<32>, _message: Bytes) {
|
|
371
|
+
// No-op for testing
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/// Required by quote_send to get messaging fees
|
|
375
|
+
pub fn quote(env: Env, _sender: Address, params: MessagingParams) -> MessagingFee {
|
|
376
|
+
let native_fee: i128 = env.storage().instance().get(&symbol_short!("ntv_fee")).unwrap_or(1000);
|
|
377
|
+
let zro_fee: i128 =
|
|
378
|
+
if params.pay_in_zro { env.storage().instance().get(&symbol_short!("zro_fee")).unwrap_or(500) } else { 0 };
|
|
379
|
+
MessagingFee { native_fee, zro_fee }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/// Required by send to send cross-chain messages
|
|
383
|
+
pub fn send(env: Env, _sender: Address, params: MessagingParams, _refund_address: Address) -> MessagingReceipt {
|
|
384
|
+
// Increment nonce for each send
|
|
385
|
+
let nonce: u64 = env.storage().instance().get(&symbol_short!("nonce")).unwrap_or(0) + 1;
|
|
386
|
+
env.storage().instance().set(&symbol_short!("nonce"), &nonce);
|
|
387
|
+
|
|
388
|
+
// Store send details for verification
|
|
389
|
+
env.storage().instance().set(&symbol_short!("sent"), &true);
|
|
390
|
+
env.storage().instance().set(&Symbol::new(&env, "last_dst_eid"), ¶ms.dst_eid);
|
|
391
|
+
env.storage().instance().set(&Symbol::new(&env, "last_msg"), ¶ms.message);
|
|
392
|
+
|
|
393
|
+
let native_fee: i128 = env.storage().instance().get(&symbol_short!("ntv_fee")).unwrap_or(1000);
|
|
394
|
+
let zro_fee: i128 =
|
|
395
|
+
if params.pay_in_zro { env.storage().instance().get(&symbol_short!("zro_fee")).unwrap_or(500) } else { 0 };
|
|
396
|
+
|
|
397
|
+
MessagingReceipt {
|
|
398
|
+
guid: BytesN::from_array(&env, &[nonce as u8; 32]),
|
|
399
|
+
nonce,
|
|
400
|
+
fee: MessagingFee { native_fee, zro_fee },
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/// Helper to check if send was called
|
|
405
|
+
pub fn was_sent(env: Env) -> bool {
|
|
406
|
+
env.storage().instance().get(&symbol_short!("sent")).unwrap_or(false)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/// Get the last destination EID that was sent to
|
|
410
|
+
pub fn get_last_dst_eid(env: Env) -> Option<u32> {
|
|
411
|
+
env.storage().instance().get(&Symbol::new(&env, "last_dst_eid"))
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// Get the current nonce
|
|
415
|
+
pub fn get_nonce(env: Env) -> u64 {
|
|
416
|
+
env.storage().instance().get(&symbol_short!("nonce")).unwrap_or(0)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// Implements the send_compose method from MessagingComposer
|
|
420
|
+
pub fn send_compose(env: Env, from: Address, to: Address, guid: BytesN<32>, index: u32, message: Bytes) {
|
|
421
|
+
env.storage().instance().set(&symbol_short!("composed"), &true);
|
|
422
|
+
env.storage().instance().set(&Symbol::new(&env, "compose_from"), &from);
|
|
423
|
+
env.storage().instance().set(&Symbol::new(&env, "compose_to"), &to);
|
|
424
|
+
env.storage().instance().set(&Symbol::new(&env, "compose_guid"), &guid);
|
|
425
|
+
env.storage().instance().set(&Symbol::new(&env, "compose_idx"), &index);
|
|
426
|
+
env.storage().instance().set(&Symbol::new(&env, "compose_msg"), &message);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Helper to check if compose was called
|
|
430
|
+
pub fn was_composed(env: Env) -> bool {
|
|
431
|
+
env.storage().instance().get(&symbol_short!("composed")).unwrap_or(false)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
pub fn get_compose_to(env: Env) -> Option<Address> {
|
|
435
|
+
env.storage().instance().get(&Symbol::new(&env, "compose_to"))
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
#[allow(dead_code)]
|
|
439
|
+
pub fn get_compose_msg(env: Env) -> Option<Bytes> {
|
|
440
|
+
env.storage().instance().get(&Symbol::new(&env, "compose_msg"))
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// ==================== Test Setup ====================
|
|
445
|
+
|
|
446
|
+
/// Default fees for mock endpoint
|
|
447
|
+
pub const DEFAULT_NATIVE_FEE: i128 = 1000;
|
|
448
|
+
pub const DEFAULT_ZRO_FEE: i128 = 500;
|
|
449
|
+
/// Large amount for pre-minting tokens during setup
|
|
450
|
+
pub const INITIAL_MINT_AMOUNT: i128 = 1_000_000_000_000_000_000;
|
|
451
|
+
|
|
452
|
+
/// OFT strategy type for test setup
|
|
453
|
+
#[derive(Clone, Copy, PartialEq, Eq, Default)]
|
|
454
|
+
pub enum OFTType {
|
|
455
|
+
#[default]
|
|
456
|
+
MintBurn,
|
|
457
|
+
LockUnlock,
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/// Token type for test setup
|
|
461
|
+
#[derive(Clone, Copy, PartialEq, Eq, Default)]
|
|
462
|
+
pub enum TokenType {
|
|
463
|
+
SAC, // Stellar Asset Contract (native, 7 decimals)
|
|
464
|
+
#[default]
|
|
465
|
+
ContractToken, // Custom contract token (configurable decimals)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
pub struct OFTTestSetup<'a> {
|
|
469
|
+
pub env: &'a Env,
|
|
470
|
+
pub oft: OFTClient<'a>,
|
|
471
|
+
pub endpoint_client: MockEndpointWithComposeClient<'a>,
|
|
472
|
+
pub token: Address,
|
|
473
|
+
pub token_client: TokenClient<'a>,
|
|
474
|
+
pub native_token: Address,
|
|
475
|
+
pub zro_token: Address,
|
|
476
|
+
pub owner: Address,
|
|
477
|
+
pub native_fee: i128,
|
|
478
|
+
pub zro_fee: i128,
|
|
479
|
+
pub oft_type: OFTType,
|
|
480
|
+
pub token_decimals: u32,
|
|
481
|
+
pub shared_decimals: u32,
|
|
482
|
+
pub issuer: Address,
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/// Builder for OFTTestSetup
|
|
486
|
+
pub struct OFTTestSetupBuilder<'a> {
|
|
487
|
+
env: &'a Env,
|
|
488
|
+
native_fee: i128,
|
|
489
|
+
zro_fee: i128,
|
|
490
|
+
oft_type: OFTType,
|
|
491
|
+
token_type: TokenType,
|
|
492
|
+
token_decimals: u32,
|
|
493
|
+
shared_decimals: u32,
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
impl<'a> OFTTestSetupBuilder<'a> {
|
|
497
|
+
pub fn new(env: &'a Env) -> Self {
|
|
498
|
+
Self {
|
|
499
|
+
env,
|
|
500
|
+
native_fee: DEFAULT_NATIVE_FEE,
|
|
501
|
+
zro_fee: DEFAULT_ZRO_FEE,
|
|
502
|
+
oft_type: OFTType::default(),
|
|
503
|
+
token_type: TokenType::default(),
|
|
504
|
+
token_decimals: 7,
|
|
505
|
+
shared_decimals: DEFAULT_SHARED_DECIMALS,
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
pub fn with_token_decimals(mut self, decimals: u32) -> Self {
|
|
510
|
+
self.token_decimals = decimals;
|
|
511
|
+
// Automatically use ContractToken if custom decimals are requested (SAC is fixed at 7)
|
|
512
|
+
if decimals != 7 {
|
|
513
|
+
self.token_type = TokenType::ContractToken;
|
|
514
|
+
}
|
|
515
|
+
self
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
pub fn with_shared_decimals(mut self, decimals: u32) -> Self {
|
|
519
|
+
self.shared_decimals = decimals;
|
|
520
|
+
self
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
pub fn with_fees(mut self, native_fee: i128, zro_fee: i128) -> Self {
|
|
524
|
+
self.native_fee = native_fee;
|
|
525
|
+
self.zro_fee = zro_fee;
|
|
526
|
+
self
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
pub fn with_native_fee(mut self, native_fee: i128) -> Self {
|
|
530
|
+
self.native_fee = native_fee;
|
|
531
|
+
self
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
pub fn with_zro_fee(mut self, zro_fee: i128) -> Self {
|
|
535
|
+
self.zro_fee = zro_fee;
|
|
536
|
+
self
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
pub fn lock_unlock(mut self) -> Self {
|
|
540
|
+
self.oft_type = OFTType::LockUnlock;
|
|
541
|
+
self
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
pub fn with_sac(mut self) -> Self {
|
|
545
|
+
self.token_type = TokenType::SAC;
|
|
546
|
+
self.token_decimals = 7; // SAC has fixed 7 decimals
|
|
547
|
+
self
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
pub fn build(self) -> OFTTestSetup<'a> {
|
|
551
|
+
let env = self.env;
|
|
552
|
+
let native_fee = self.native_fee;
|
|
553
|
+
let zro_fee = self.zro_fee;
|
|
554
|
+
let oft_type = self.oft_type;
|
|
555
|
+
|
|
556
|
+
let owner = create_recipient_address(env);
|
|
557
|
+
|
|
558
|
+
// Create native token for fees
|
|
559
|
+
let native_sac = env.register_stellar_asset_contract_v2(owner.clone());
|
|
560
|
+
let native_token = native_sac.address();
|
|
561
|
+
|
|
562
|
+
// Create ZRO token
|
|
563
|
+
let zro_sac = env.register_stellar_asset_contract_v2(owner.clone());
|
|
564
|
+
let zro_token = zro_sac.address();
|
|
565
|
+
|
|
566
|
+
// Create OFT token based on token_type
|
|
567
|
+
let (token, actual_token_decimals, issuer) = match self.token_type {
|
|
568
|
+
TokenType::SAC => {
|
|
569
|
+
let sac = env.register_stellar_asset_contract_v2(owner.clone());
|
|
570
|
+
(sac.address(), 7u32, sac.issuer().address()) // SAC has fixed 7 decimals
|
|
571
|
+
}
|
|
572
|
+
TokenType::ContractToken => {
|
|
573
|
+
let token = env.register(DummyToken, (&owner, self.token_decimals));
|
|
574
|
+
(token, self.token_decimals, owner.clone())
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
let token_client = TokenClient::new(env, &token);
|
|
578
|
+
|
|
579
|
+
// Register mock endpoint
|
|
580
|
+
let endpoint_address =
|
|
581
|
+
env.register(MockEndpointWithCompose, (&native_fee, &zro_fee, &native_token, &zro_token));
|
|
582
|
+
let endpoint_client = MockEndpointWithComposeClient::new(env, &endpoint_address);
|
|
583
|
+
|
|
584
|
+
// Register OFT based on type
|
|
585
|
+
let delegate = owner.clone();
|
|
586
|
+
let oft_address = match oft_type {
|
|
587
|
+
OFTType::MintBurn => {
|
|
588
|
+
env.register(TestMintBurnOFT, (&token, &owner, &endpoint_address, &delegate, &self.shared_decimals))
|
|
589
|
+
}
|
|
590
|
+
OFTType::LockUnlock => {
|
|
591
|
+
env.register(TestLockUnlockOFT, (&token, &owner, &endpoint_address, &delegate, &self.shared_decimals))
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
let oft = OFTClient::new(env, &oft_address);
|
|
595
|
+
|
|
596
|
+
// Pre-mint large amounts to owner
|
|
597
|
+
OFTTestSetup::mint_to(env, &owner, &token, &owner, INITIAL_MINT_AMOUNT);
|
|
598
|
+
OFTTestSetup::mint_to(env, &owner, &native_token, &owner, INITIAL_MINT_AMOUNT);
|
|
599
|
+
OFTTestSetup::mint_to(env, &owner, &zro_token, &owner, INITIAL_MINT_AMOUNT);
|
|
600
|
+
|
|
601
|
+
// Setup based on OFT type
|
|
602
|
+
match oft_type {
|
|
603
|
+
OFTType::MintBurn => {
|
|
604
|
+
// Transfer token ownership to OFT so it can burn tokens
|
|
605
|
+
OFTTestSetup::transfer_token_ownership(env, &owner, &token, &oft_address);
|
|
606
|
+
}
|
|
607
|
+
OFTType::LockUnlock => {
|
|
608
|
+
// Fund the OFT with tokens so it can unlock/release them on receive
|
|
609
|
+
OFTTestSetup::mint_to(env, &owner, &token, &oft_address, INITIAL_MINT_AMOUNT);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
log!(&env, "token decimals: {}", self.token_decimals);
|
|
614
|
+
log!(&env, "token address: {}", token);
|
|
615
|
+
log!(&env, "token client: {}", token_client.address);
|
|
616
|
+
log!(&env, "native token address: {}", native_token);
|
|
617
|
+
log!(&env, "zro token address: {}", zro_token);
|
|
618
|
+
log!(&env, "owner: {}", owner);
|
|
619
|
+
log!(&env, "native fee: {}", native_fee);
|
|
620
|
+
log!(&env, "zro fee: {}", zro_fee);
|
|
621
|
+
log!(&env, "oft address: {}", oft_address);
|
|
622
|
+
|
|
623
|
+
OFTTestSetup {
|
|
624
|
+
env,
|
|
625
|
+
oft,
|
|
626
|
+
endpoint_client,
|
|
627
|
+
token,
|
|
628
|
+
token_client,
|
|
629
|
+
native_token,
|
|
630
|
+
zro_token,
|
|
631
|
+
owner,
|
|
632
|
+
native_fee,
|
|
633
|
+
zro_fee,
|
|
634
|
+
oft_type,
|
|
635
|
+
token_decimals: actual_token_decimals,
|
|
636
|
+
shared_decimals: self.shared_decimals,
|
|
637
|
+
issuer,
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
impl<'a> OFTTestSetup<'a> {
|
|
643
|
+
/// Create a new test setup with default configuration (MintBurn OFT)
|
|
644
|
+
pub fn new(env: &'a Env) -> Self {
|
|
645
|
+
OFTTestSetupBuilder::new(env).build()
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/// Returns true if this setup uses a LockUnlock OFT
|
|
649
|
+
pub fn is_lock_unlock(&self) -> bool {
|
|
650
|
+
self.oft_type == OFTType::LockUnlock
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
pub fn set_peer(&self, eid: u32, peer: &BytesN<32>) {
|
|
654
|
+
let peer_option = Some(peer.clone());
|
|
655
|
+
self.env.mock_auths(&[MockAuth {
|
|
656
|
+
address: &self.owner,
|
|
657
|
+
invoke: &MockAuthInvoke {
|
|
658
|
+
contract: &self.oft.address,
|
|
659
|
+
fn_name: "set_peer",
|
|
660
|
+
args: (&eid, &peer_option, &self.owner).into_val(self.env),
|
|
661
|
+
sub_invokes: &[],
|
|
662
|
+
},
|
|
663
|
+
}]);
|
|
664
|
+
OAppCoreClient::new(self.env, &self.oft.address).set_peer(&eid, &peer_option, &self.owner);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
pub fn mint_to(env: &Env, owner: &Address, token: &Address, to: &Address, amount: i128) {
|
|
668
|
+
env.mock_auths(&[MockAuth {
|
|
669
|
+
address: owner,
|
|
670
|
+
invoke: &MockAuthInvoke {
|
|
671
|
+
contract: token,
|
|
672
|
+
fn_name: "mint",
|
|
673
|
+
args: (to, amount).into_val(env),
|
|
674
|
+
sub_invokes: &[],
|
|
675
|
+
},
|
|
676
|
+
}]);
|
|
677
|
+
StellarAssetClient::new(env, token).mint(to, &amount);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
pub fn transfer_token_ownership(env: &Env, owner: &Address, token: &Address, new_admin: &Address) {
|
|
681
|
+
env.mock_auths(&[MockAuth {
|
|
682
|
+
address: owner,
|
|
683
|
+
invoke: &MockAuthInvoke {
|
|
684
|
+
contract: token,
|
|
685
|
+
fn_name: "set_admin",
|
|
686
|
+
args: (new_admin,).into_val(env),
|
|
687
|
+
sub_invokes: &[],
|
|
688
|
+
},
|
|
689
|
+
}]);
|
|
690
|
+
StellarAssetClient::new(env, token).set_admin(new_admin);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/// Fund an account with native fees only (transfers from owner)
|
|
694
|
+
pub fn fund_native_fees(&self, to: &Address, amount: i128) {
|
|
695
|
+
self.env.mock_auths(&[MockAuth {
|
|
696
|
+
address: &self.owner,
|
|
697
|
+
invoke: &MockAuthInvoke {
|
|
698
|
+
contract: &self.native_token,
|
|
699
|
+
fn_name: "transfer",
|
|
700
|
+
args: (&self.owner, to, amount).into_val(self.env),
|
|
701
|
+
sub_invokes: &[],
|
|
702
|
+
},
|
|
703
|
+
}]);
|
|
704
|
+
TokenClient::new(self.env, &self.native_token).transfer(&self.owner, to, &amount);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/// Fund an account with ZRO fees (transfers from owner)
|
|
708
|
+
pub fn fund_zro_fees(&self, to: &Address, amount: i128) {
|
|
709
|
+
self.env.mock_auths(&[MockAuth {
|
|
710
|
+
address: &self.owner,
|
|
711
|
+
invoke: &MockAuthInvoke {
|
|
712
|
+
contract: &self.zro_token,
|
|
713
|
+
fn_name: "transfer",
|
|
714
|
+
args: (&self.owner, to, amount).into_val(self.env),
|
|
715
|
+
sub_invokes: &[],
|
|
716
|
+
},
|
|
717
|
+
}]);
|
|
718
|
+
TokenClient::new(self.env, &self.zro_token).transfer(&self.owner, to, &amount);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/// Fund an account with OFT tokens only (transfers from owner)
|
|
722
|
+
pub fn fund_tokens(&self, to: &Address, amount: i128) {
|
|
723
|
+
self.env.mock_auths(&[MockAuth {
|
|
724
|
+
address: &self.owner,
|
|
725
|
+
invoke: &MockAuthInvoke {
|
|
726
|
+
contract: &self.token,
|
|
727
|
+
fn_name: "transfer",
|
|
728
|
+
args: (&self.owner, to, amount).into_val(self.env),
|
|
729
|
+
sub_invokes: &[],
|
|
730
|
+
},
|
|
731
|
+
}]);
|
|
732
|
+
self.token_client.transfer(&self.owner, to, &amount);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/// Quote OFT to get the receipt for authorization
|
|
736
|
+
pub fn quote_oft(&self, from: &Address, send_param: &SendParam) -> OFTReceipt {
|
|
737
|
+
let (_, _, receipt) = self.oft.quote_oft(from, send_param);
|
|
738
|
+
receipt
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/// Send tokens cross-chain with proper sender authentication
|
|
742
|
+
pub fn send(
|
|
743
|
+
&self,
|
|
744
|
+
sender: &Address,
|
|
745
|
+
send_param: &SendParam,
|
|
746
|
+
fee: &MessagingFee,
|
|
747
|
+
refund_address: &Address,
|
|
748
|
+
oft_receipt: &OFTReceipt,
|
|
749
|
+
) -> (MessagingReceipt, OFTReceipt) {
|
|
750
|
+
// Token operation sub-invoke differs based on OFT type
|
|
751
|
+
// Both MintBurn and LockUnlock use amount_received_ld (after fee/dust removal)
|
|
752
|
+
let token_sub_invoke = match self.oft_type {
|
|
753
|
+
OFTType::MintBurn => MockAuthInvoke {
|
|
754
|
+
contract: &self.token,
|
|
755
|
+
fn_name: "burn",
|
|
756
|
+
args: (sender, &oft_receipt.amount_received_ld).into_val(self.env),
|
|
757
|
+
sub_invokes: &[],
|
|
758
|
+
},
|
|
759
|
+
OFTType::LockUnlock => MockAuthInvoke {
|
|
760
|
+
contract: &self.token,
|
|
761
|
+
fn_name: "transfer",
|
|
762
|
+
args: (sender, &self.oft.address, &oft_receipt.amount_received_ld).into_val(self.env),
|
|
763
|
+
sub_invokes: &[],
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
self.env.mock_auths(&[MockAuth {
|
|
768
|
+
address: sender,
|
|
769
|
+
invoke: &MockAuthInvoke {
|
|
770
|
+
contract: &self.oft.address,
|
|
771
|
+
fn_name: "send",
|
|
772
|
+
args: (sender, send_param, fee, refund_address).into_val(self.env),
|
|
773
|
+
sub_invokes: &[
|
|
774
|
+
MockAuthInvoke {
|
|
775
|
+
contract: &self.native_token,
|
|
776
|
+
fn_name: "transfer",
|
|
777
|
+
args: (sender, &self.endpoint_client.address, &fee.native_fee).into_val(self.env),
|
|
778
|
+
sub_invokes: &[],
|
|
779
|
+
},
|
|
780
|
+
MockAuthInvoke {
|
|
781
|
+
contract: &self.zro_token,
|
|
782
|
+
fn_name: "transfer",
|
|
783
|
+
args: (sender, &self.endpoint_client.address, &fee.zro_fee).into_val(self.env),
|
|
784
|
+
sub_invokes: &[],
|
|
785
|
+
},
|
|
786
|
+
token_sub_invoke,
|
|
787
|
+
],
|
|
788
|
+
},
|
|
789
|
+
}]);
|
|
790
|
+
self.oft.send(sender, send_param, fee, refund_address)
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/// Try send tokens cross-chain with proper sender authentication (returns Result)
|
|
794
|
+
pub fn try_send(
|
|
795
|
+
&self,
|
|
796
|
+
sender: &Address,
|
|
797
|
+
send_param: &SendParam,
|
|
798
|
+
fee: &MessagingFee,
|
|
799
|
+
refund_address: &Address,
|
|
800
|
+
oft_receipt: &OFTReceipt,
|
|
801
|
+
) -> Result<
|
|
802
|
+
Result<(MessagingReceipt, OFTReceipt), soroban_sdk::Error>,
|
|
803
|
+
Result<soroban_sdk::Error, soroban_sdk::InvokeError>,
|
|
804
|
+
> {
|
|
805
|
+
// Token operation sub-invoke differs based on OFT type
|
|
806
|
+
// Both MintBurn and LockUnlock use amount_received_ld (after fee/dust removal)
|
|
807
|
+
let token_sub_invoke = match self.oft_type {
|
|
808
|
+
OFTType::MintBurn => MockAuthInvoke {
|
|
809
|
+
contract: &self.token,
|
|
810
|
+
fn_name: "burn",
|
|
811
|
+
args: (sender, &oft_receipt.amount_received_ld).into_val(self.env),
|
|
812
|
+
sub_invokes: &[],
|
|
813
|
+
},
|
|
814
|
+
OFTType::LockUnlock => MockAuthInvoke {
|
|
815
|
+
contract: &self.token,
|
|
816
|
+
fn_name: "transfer",
|
|
817
|
+
args: (sender, &self.oft.address, &oft_receipt.amount_received_ld).into_val(self.env),
|
|
818
|
+
sub_invokes: &[],
|
|
819
|
+
},
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
self.env.mock_auths(&[MockAuth {
|
|
823
|
+
address: sender,
|
|
824
|
+
invoke: &MockAuthInvoke {
|
|
825
|
+
contract: &self.oft.address,
|
|
826
|
+
fn_name: "send",
|
|
827
|
+
args: (sender, send_param, fee, refund_address).into_val(self.env),
|
|
828
|
+
sub_invokes: &[
|
|
829
|
+
MockAuthInvoke {
|
|
830
|
+
contract: &self.native_token,
|
|
831
|
+
fn_name: "transfer",
|
|
832
|
+
args: (sender, &self.endpoint_client.address, &fee.native_fee).into_val(self.env),
|
|
833
|
+
sub_invokes: &[],
|
|
834
|
+
},
|
|
835
|
+
MockAuthInvoke {
|
|
836
|
+
contract: &self.zro_token,
|
|
837
|
+
fn_name: "transfer",
|
|
838
|
+
args: (sender, &self.endpoint_client.address, &fee.zro_fee).into_val(self.env),
|
|
839
|
+
sub_invokes: &[],
|
|
840
|
+
},
|
|
841
|
+
token_sub_invoke,
|
|
842
|
+
],
|
|
843
|
+
},
|
|
844
|
+
}]);
|
|
845
|
+
self.oft.try_send(sender, send_param, fee, refund_address)
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/// Execute lz_receive with proper executor authentication
|
|
849
|
+
pub fn lz_receive(
|
|
850
|
+
&self,
|
|
851
|
+
executor: &Address,
|
|
852
|
+
origin: &Origin,
|
|
853
|
+
guid: &BytesN<32>,
|
|
854
|
+
message: &Bytes,
|
|
855
|
+
extra_data: &Bytes,
|
|
856
|
+
value: i128,
|
|
857
|
+
) {
|
|
858
|
+
self.env.mock_auths(&[MockAuth {
|
|
859
|
+
address: executor,
|
|
860
|
+
invoke: &MockAuthInvoke {
|
|
861
|
+
contract: &self.oft.address,
|
|
862
|
+
fn_name: "lz_receive",
|
|
863
|
+
args: (executor, origin, guid, message, extra_data, value).into_val(self.env),
|
|
864
|
+
sub_invokes: &[],
|
|
865
|
+
},
|
|
866
|
+
}]);
|
|
867
|
+
LayerZeroReceiverClient::new(self.env, &self.oft.address)
|
|
868
|
+
.lz_receive(executor, origin, guid, message, extra_data, &value);
|
|
869
|
+
}
|
|
870
|
+
}
|