@layerzerolabs/protocol-stellar-v2 0.2.33 → 0.2.35
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/.turbo/turbo-build.log +351 -367
- package/.turbo/turbo-lint.log +220 -223
- package/.turbo/turbo-test.log +1993 -1796
- package/Cargo.lock +10 -10
- package/Cargo.toml +1 -1
- package/contracts/common-macros/src/storage.rs +7 -5
- package/contracts/common-macros/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +3 -3
- package/contracts/endpoint-v2/src/endpoint_v2.rs +5 -4
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +7 -8
- package/contracts/endpoint-v2/src/messaging_channel.rs +78 -45
- package/contracts/endpoint-v2/src/storage.rs +8 -3
- package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +2 -2
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -15
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +46 -9
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +7 -23
- package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +23 -20
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +94 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_nonce.rs +17 -15
- package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +48 -13
- package/contracts/endpoint-v2/src/tests/messaging_channel/pending_inbound_nonces.rs +111 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +15 -25
- package/contracts/layerzero-views/src/layerzero_view.rs +2 -2
- package/contracts/layerzero-views/src/tests/layerzero_view_tests.rs +3 -4
- package/contracts/layerzero-views/src/tests/setup.rs +0 -21
- package/contracts/message-libs/blocked-message-lib/src/lib.rs +4 -4
- package/contracts/message-libs/uln-302/src/send_uln.rs +5 -5
- package/contracts/oapps/counter/src/counter.rs +6 -0
- package/contracts/oapps/oapp/src/oapp_sender.rs +3 -2
- package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +5 -11
- package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +7 -14
- package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +11 -22
- package/contracts/oapps/oft/integration-tests/setup.rs +59 -7
- package/contracts/oapps/oft/integration-tests/utils.rs +28 -2
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +5 -0
- package/contracts/oapps/oft/src/interfaces/mintable.rs +14 -0
- package/contracts/oapps/oft/src/interfaces/mod.rs +2 -2
- package/contracts/oapps/oft/src/oft.rs +8 -7
- package/contracts/oapps/oft/src/oft_types/mint_burn.rs +8 -8
- package/contracts/oapps/oft/src/oft_types/mod.rs +3 -4
- package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +7 -5
- package/contracts/oapps/sac-manager/src/errors.rs +14 -0
- package/contracts/{sac-manager → oapps/sac-manager}/src/lib.rs +0 -4
- package/contracts/oapps/sac-manager/src/sac_manager.rs +115 -0
- package/contracts/oapps/sac-manager/src/storage.rs +20 -0
- package/contracts/{sac-manager → oapps/sac-manager}/src/tests/mod.rs +0 -4
- package/contracts/oapps/sac-manager/src/tests/sac_manager/clawback.rs +86 -0
- package/contracts/oapps/sac-manager/src/tests/sac_manager/mint.rs +58 -0
- package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/mod.rs +1 -3
- package/contracts/oapps/sac-manager/src/tests/sac_manager/set_minter.rs +69 -0
- package/contracts/oapps/sac-manager/src/tests/sac_manager/test_helper.rs +18 -0
- package/contracts/oapps/sac-manager/src/tests/sac_manager/view_functions.rs +28 -0
- package/contracts/{sac-manager → oapps/sac-manager}/src/tests/test_helper.rs +16 -59
- package/docs/layerzero-v2-on-stellar.md +46 -2
- package/package.json +8 -3
- package/sdk/.turbo/turbo-test.log +424 -429
- package/sdk/dist/generated/bml.d.ts +3 -3
- package/sdk/dist/generated/bml.js +3 -3
- package/sdk/dist/generated/counter.d.ts +32 -3
- package/sdk/dist/generated/counter.js +6 -3
- package/sdk/dist/generated/dvn.d.ts +3 -3
- package/sdk/dist/generated/dvn.js +3 -3
- package/sdk/dist/generated/dvn_fee_lib.d.ts +2 -2
- package/sdk/dist/generated/dvn_fee_lib.js +2 -2
- package/sdk/dist/generated/endpoint.d.ts +12 -13
- package/sdk/dist/generated/endpoint.js +7 -7
- package/sdk/dist/generated/executor.d.ts +3 -3
- package/sdk/dist/generated/executor.js +3 -3
- package/sdk/dist/generated/executor_fee_lib.d.ts +2 -2
- package/sdk/dist/generated/executor_fee_lib.js +2 -2
- package/sdk/dist/generated/executor_helper.d.ts +2 -2
- package/sdk/dist/generated/executor_helper.js +2 -2
- package/sdk/dist/generated/layerzero_view.d.ts +3 -3
- package/sdk/dist/generated/layerzero_view.js +3 -3
- package/sdk/dist/generated/oft.d.ts +32 -3
- package/sdk/dist/generated/oft.js +7 -4
- package/sdk/dist/generated/price_feed.d.ts +3 -3
- package/sdk/dist/generated/price_feed.js +3 -3
- package/sdk/dist/generated/sac_manager.d.ts +47 -318
- package/sdk/dist/generated/sac_manager.js +24 -129
- package/sdk/dist/generated/sml.d.ts +2 -2
- package/sdk/dist/generated/sml.js +2 -2
- package/sdk/dist/generated/treasury.d.ts +2 -2
- package/sdk/dist/generated/treasury.js +2 -2
- package/sdk/dist/generated/uln302.d.ts +3 -3
- package/sdk/dist/generated/uln302.js +3 -3
- package/sdk/dist/generated/upgrader.d.ts +2 -2
- package/sdk/dist/generated/upgrader.js +2 -2
- package/sdk/package.json +6 -1
- package/sdk/test/oft-sml.test.ts +72 -36
- package/sdk/test/sac-manager-redistribution.test.ts +38 -182
- package/contracts/endpoint-v2/src/tests/messaging_channel/lazy_inbound_nonce.rs +0 -39
- package/contracts/oapps/oft/src/interfaces/mint_burnable.rs +0 -18
- package/contracts/sac-manager/src/errors.rs +0 -18
- package/contracts/sac-manager/src/extensions/mod.rs +0 -6
- package/contracts/sac-manager/src/extensions/redistribution.rs +0 -109
- package/contracts/sac-manager/src/extensions/supply_control/mod.rs +0 -488
- package/contracts/sac-manager/src/extensions/supply_control/rate_limit.rs +0 -126
- package/contracts/sac-manager/src/interfaces/mod.rs +0 -3
- package/contracts/sac-manager/src/interfaces/sac_manager.rs +0 -52
- package/contracts/sac-manager/src/sac_manager.rs +0 -193
- package/contracts/sac-manager/src/storage.rs +0 -20
- package/contracts/sac-manager/src/tests/redistribution/mod.rs +0 -1
- package/contracts/sac-manager/src/tests/redistribution/redistribute_funds.rs +0 -82
- package/contracts/sac-manager/src/tests/sac_manager/admin_mint.rs +0 -206
- package/contracts/sac-manager/src/tests/sac_manager/burn.rs +0 -215
- package/contracts/sac-manager/src/tests/sac_manager/clawback.rs +0 -209
- package/contracts/sac-manager/src/tests/sac_manager/mint.rs +0 -252
- package/contracts/sac-manager/src/tests/sac_manager/set_oft_address.rs +0 -47
- package/contracts/sac-manager/src/tests/sac_manager/test_helper.rs +0 -75
- package/contracts/sac-manager/src/tests/sac_manager/view_functions.rs +0 -60
- package/contracts/sac-manager/src/tests/supply_control/enumerable_set.rs +0 -256
- package/contracts/sac-manager/src/tests/supply_control/mod.rs +0 -8
- package/contracts/sac-manager/src/tests/supply_control/refill.rs +0 -90
- package/contracts/sac-manager/src/tests/supply_control/set_mint_whitelist.rs +0 -245
- package/contracts/sac-manager/src/tests/supply_control/set_supply_controller.rs +0 -267
- package/contracts/sac-manager/src/tests/supply_control/set_supply_controller_manager.rs +0 -122
- package/contracts/sac-manager/src/tests/supply_control/test_helper.rs +0 -38
- package/contracts/sac-manager/src/tests/supply_control/update_allow_any_mint_burn.rs +0 -114
- package/contracts/sac-manager/src/tests/supply_control/update_limit_config.rs +0 -257
- /package/contracts/{sac-manager → oapps/sac-manager}/Cargo.toml +0 -0
- /package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/set_admin.rs +0 -0
- /package/contracts/{sac-manager → oapps/sac-manager}/src/tests/sac_manager/set_authorized.rs +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
use crate::extensions::rate_limiter::{Direction, Mode, RateLimitConfig};
|
|
4
4
|
use crate::integration_tests::setup::{decode_packet, ChainSetup};
|
|
5
|
+
use crate::MintableClient;
|
|
5
6
|
use endpoint_v2::{MessagingFee, Origin, OutboundPacket};
|
|
6
7
|
use message_lib_common::packet_codec_v1;
|
|
7
8
|
use oft_core::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam};
|
|
@@ -38,7 +39,11 @@ pub fn create_recipient_address(env: &Env) -> Address {
|
|
|
38
39
|
// OFT Core Operations
|
|
39
40
|
// ============================================================================
|
|
40
41
|
|
|
41
|
-
pub fn quote_oft(
|
|
42
|
+
pub fn quote_oft(
|
|
43
|
+
chain: &ChainSetup<'_>,
|
|
44
|
+
from: &Address,
|
|
45
|
+
send_param: &SendParam,
|
|
46
|
+
) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
|
|
42
47
|
chain.oft.quote_oft(from, send_param)
|
|
43
48
|
}
|
|
44
49
|
|
|
@@ -61,7 +66,8 @@ pub fn quote_send(
|
|
|
61
66
|
chain.oft.quote_send(sender, send_param, &pay_in_zro)
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
/// Send without fee (standard OFT send)
|
|
69
|
+
/// Send without fee (standard OFT send).
|
|
70
|
+
/// Sender authorizes OFT send (OFT debits by calling token burn directly) and SAC burn.
|
|
65
71
|
pub fn send(
|
|
66
72
|
env: &Env,
|
|
67
73
|
chain: &ChainSetup<'_>,
|
|
@@ -308,6 +314,26 @@ pub fn mint_to(env: &Env, owner: &Address, token: &Address, to: &Address, amount
|
|
|
308
314
|
sac.mint(to, &amount);
|
|
309
315
|
}
|
|
310
316
|
|
|
317
|
+
/// Mints the OFT token (via the Mintable wrapper) to the given address.
|
|
318
|
+
/// Use when OFT is MintBurn; the wrapper calls the underlying SAC mint.
|
|
319
|
+
pub fn mint_oft_token_to(env: &Env, chain: &ChainSetup<'_>, to: &Address, amount: i128) {
|
|
320
|
+
env.mock_auths(&[MockAuth {
|
|
321
|
+
address: &chain.owner,
|
|
322
|
+
invoke: &MockAuthInvoke {
|
|
323
|
+
contract: &chain.sac_wrapper,
|
|
324
|
+
fn_name: "mint",
|
|
325
|
+
args: (to, &amount, &chain.owner).into_val(env),
|
|
326
|
+
sub_invokes: &[MockAuthInvoke {
|
|
327
|
+
contract: &chain.oft_token,
|
|
328
|
+
fn_name: "mint",
|
|
329
|
+
args: (to, &amount).into_val(env),
|
|
330
|
+
sub_invokes: &[],
|
|
331
|
+
}],
|
|
332
|
+
},
|
|
333
|
+
}]);
|
|
334
|
+
MintableClient::new(env, &chain.sac_wrapper).mint(to, &amount, &chain.owner);
|
|
335
|
+
}
|
|
336
|
+
|
|
311
337
|
pub fn transfer_sac_admin(env: &Env, owner: &Address, token: &Address, new_admin: &Address) {
|
|
312
338
|
env.mock_auths(&[MockAuth {
|
|
313
339
|
address: owner,
|
|
@@ -122,6 +122,11 @@ pub trait OFTFee: OFTFeeInternal + Auth {
|
|
|
122
122
|
Self::__effective_fee_bps(env, dst_eid)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/// Returns true if the OFT has a fee rate greater than 0 for the specified destination
|
|
126
|
+
fn has_oft_fee(env: &soroban_sdk::Env, dst_eid: u32) -> bool {
|
|
127
|
+
Self::__effective_fee_bps(env, dst_eid) > 0
|
|
128
|
+
}
|
|
129
|
+
|
|
125
130
|
/// Returns the fee deposit address.
|
|
126
131
|
fn fee_deposit_address(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
|
|
127
132
|
Self::__fee_deposit_address(env)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//! Mintable trait - the interface the OFT uses to mint tokens on credit (receive).
|
|
2
|
+
|
|
3
|
+
use soroban_sdk::{contractclient, Address, Env};
|
|
4
|
+
|
|
5
|
+
/// The mint interface for OFT MintBurn operations.
|
|
6
|
+
///
|
|
7
|
+
/// A contract that implements `mint` (e.g. SAC Manager or a token wrapper) is used
|
|
8
|
+
/// for crediting; the OFT calls the token (SAC) directly for burn on debit.
|
|
9
|
+
#[contractclient(name = "MintableClient")]
|
|
10
|
+
pub trait Mintable {
|
|
11
|
+
/// Mints `amount` tokens to `to`. The `operation` address is the caller (e.g. OFT)
|
|
12
|
+
/// requesting the mint, for use by SAC wrappers that enforce authorization.
|
|
13
|
+
fn mint(env: &Env, to: &Address, amount: i128, operation: &Address);
|
|
14
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
mod
|
|
1
|
+
mod mintable;
|
|
2
2
|
|
|
3
|
-
pub use
|
|
3
|
+
pub use mintable::*;
|
|
@@ -80,21 +80,22 @@ impl OFTInternal for OFT {
|
|
|
80
80
|
/// Overrides default to add pausable check and fee calculation.
|
|
81
81
|
///
|
|
82
82
|
/// Dust handling (consistent with EVM):
|
|
83
|
-
/// - fee
|
|
84
|
-
/// - fee
|
|
83
|
+
/// - no fee: dust stays with sender (amount_sent_ld has dust removed)
|
|
84
|
+
/// - has fee: dust is absorbed into the charged fee (amount_sent_ld is the full amount)
|
|
85
85
|
fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
|
|
86
86
|
Self::__assert_not_paused(env);
|
|
87
87
|
|
|
88
88
|
let conversion_rate = Self::__decimal_conversion_rate(env);
|
|
89
|
-
let
|
|
89
|
+
let has_fee = Self::has_oft_fee(env, dst_eid);
|
|
90
90
|
|
|
91
|
-
let (amount_sent_ld, amount_received_ld) = if
|
|
91
|
+
let (amount_sent_ld, amount_received_ld) = if !has_fee {
|
|
92
92
|
// No fee: dust stays with sender (default OFT behavior)
|
|
93
93
|
let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
|
|
94
94
|
(amount_sent_ld, amount_sent_ld)
|
|
95
95
|
} else {
|
|
96
96
|
// With fee: match EVM OFTFee behavior
|
|
97
97
|
// - sender pays full amount_ld (no dust removed), dust is absorbed into the charged fee
|
|
98
|
+
let fee = Self::__fee_view(env, dst_eid, amount_ld);
|
|
98
99
|
let amount_received_ld = oft_utils::remove_dust(amount_ld - fee, conversion_rate);
|
|
99
100
|
(amount_ld, amount_received_ld)
|
|
100
101
|
};
|
|
@@ -110,8 +111,8 @@ impl OFTInternal for OFT {
|
|
|
110
111
|
OftType::LockUnlock => {
|
|
111
112
|
lock_unlock::debit::<Self>(env, &Self::token(env), sender, amount_ld, min_amount_ld, dst_eid)
|
|
112
113
|
}
|
|
113
|
-
OftType::MintBurn(
|
|
114
|
-
mint_burn::debit::<Self>(env, &
|
|
114
|
+
OftType::MintBurn(_mintable) => {
|
|
115
|
+
mint_burn::debit::<Self>(env, &Self::token(env), sender, amount_ld, min_amount_ld, dst_eid)
|
|
115
116
|
}
|
|
116
117
|
};
|
|
117
118
|
|
|
@@ -133,7 +134,7 @@ impl OFTInternal for OFT {
|
|
|
133
134
|
// Core credit logic (based on mode)
|
|
134
135
|
let amount_credited = match Self::oft_type(env) {
|
|
135
136
|
OftType::LockUnlock => lock_unlock::credit::<Self>(env, &Self::token(env), to, amount_ld, src_eid),
|
|
136
|
-
OftType::MintBurn(
|
|
137
|
+
OftType::MintBurn(mintable) => mint_burn::credit::<Self>(env, &mintable, to, amount_ld, src_eid),
|
|
137
138
|
};
|
|
138
139
|
|
|
139
140
|
// Rate limit checks
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
//! MintBurn type implementation for OFT.
|
|
2
2
|
//!
|
|
3
3
|
//! This OFT type burns tokens on debit (send) and mints tokens on credit (receive).
|
|
4
|
-
//! Used when the OFT contract has mint
|
|
4
|
+
//! Used when the OFT contract has mint authority over the token.
|
|
5
5
|
|
|
6
|
-
use crate::interfaces::
|
|
6
|
+
use crate::interfaces::MintableClient;
|
|
7
7
|
use oft_core::OFTCore;
|
|
8
|
-
use soroban_sdk::{Address, Env};
|
|
8
|
+
use soroban_sdk::{token::TokenClient, Address, Env};
|
|
9
9
|
|
|
10
10
|
/// Debit tokens using MintBurn OFT type (burns tokens from sender).
|
|
11
11
|
///
|
|
12
12
|
/// # Parameters
|
|
13
13
|
/// * `env` - The Soroban environment
|
|
14
|
-
/// * `
|
|
14
|
+
/// * `token` - Address of the token (SAC) to burn from
|
|
15
15
|
/// * `sender` - Address of the token sender
|
|
16
16
|
/// * `amount_ld` - Amount to debit in local decimals
|
|
17
17
|
/// * `min_amount_ld` - Minimum amount that must be received (for slippage protection)
|
|
@@ -22,14 +22,14 @@ use soroban_sdk::{Address, Env};
|
|
|
22
22
|
/// * `amount_received_ld` - The amount received in local decimals on the remote
|
|
23
23
|
pub fn debit<T: OFTCore>(
|
|
24
24
|
env: &Env,
|
|
25
|
-
|
|
25
|
+
token: &Address,
|
|
26
26
|
sender: &Address,
|
|
27
27
|
amount_ld: i128,
|
|
28
28
|
min_amount_ld: i128,
|
|
29
29
|
dst_eid: u32,
|
|
30
30
|
) -> (i128, i128) {
|
|
31
31
|
let (amount_sent_ld, amount_received_ld) = T::__debit_view(env, amount_ld, min_amount_ld, dst_eid);
|
|
32
|
-
|
|
32
|
+
TokenClient::new(env, token).burn(sender, &amount_received_ld);
|
|
33
33
|
(amount_sent_ld, amount_received_ld)
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ pub fn debit<T: OFTCore>(
|
|
|
37
37
|
///
|
|
38
38
|
/// # Parameters
|
|
39
39
|
/// * `env` - The Soroban environment
|
|
40
|
-
/// * `mintable` - Address of the contract responsible for minting tokens
|
|
40
|
+
/// * `mintable` - Address of the contract responsible for minting tokens (e.g. SAC wrapper)
|
|
41
41
|
/// * `to` - Address of the token recipient
|
|
42
42
|
/// * `amount_ld` - Amount to credit in local decimals
|
|
43
43
|
/// * `_src_eid` - Source endpoint ID (unused)
|
|
@@ -45,6 +45,6 @@ pub fn debit<T: OFTCore>(
|
|
|
45
45
|
/// # Returns
|
|
46
46
|
/// The amount credited
|
|
47
47
|
pub fn credit<T: OFTCore>(env: &Env, mintable: &Address, to: &Address, amount_ld: i128, _src_eid: u32) -> i128 {
|
|
48
|
-
|
|
48
|
+
MintableClient::new(env, mintable).mint(to, &amount_ld, &env.current_contract_address());
|
|
49
49
|
amount_ld
|
|
50
50
|
}
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
//!
|
|
5
5
|
//! - **LockUnlock**: Locks tokens on send, unlocks on receive. Operates directly on the
|
|
6
6
|
//! token via standard SEP-41 `transfer`.
|
|
7
|
-
//! - **MintBurn**: Burns tokens on send
|
|
8
|
-
//! that implements [`
|
|
9
|
-
//! whether it's a raw SAC or a token wrapper (e.g. SAC Manager).
|
|
7
|
+
//! - **MintBurn**: Burns tokens on send (via TokenClient on the token), mints on receive
|
|
8
|
+
//! via a contract that implements [`Mintable`](crate::interfaces::Mintable).
|
|
10
9
|
|
|
11
10
|
use soroban_sdk::{contracttype, Address};
|
|
12
11
|
|
|
@@ -20,6 +19,6 @@ pub enum OftType {
|
|
|
20
19
|
/// Lock tokens on send, unlock on receive.
|
|
21
20
|
LockUnlock,
|
|
22
21
|
/// Burn tokens on send, mint on receive.
|
|
23
|
-
/// The address is the contract
|
|
22
|
+
/// The address is the Mintable contract used for minting on credit
|
|
24
23
|
MintBurn(Address),
|
|
25
24
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
extern crate std;
|
|
2
2
|
|
|
3
3
|
use crate as oft;
|
|
4
|
-
use crate::extensions::rate_limiter::{
|
|
4
|
+
use crate::extensions::rate_limiter::{
|
|
5
|
+
Direction, Mode, RateLimitConfig, RateLimitError, RateLimiter, RateLimiterInternal,
|
|
6
|
+
};
|
|
5
7
|
use soroban_sdk::{contract, contractimpl, testutils::Ledger as _, Address, Env};
|
|
6
8
|
use utils::auth::Auth;
|
|
7
9
|
|
|
@@ -451,10 +453,10 @@ fn test_switching_from_net_to_gross_mode() {
|
|
|
451
453
|
|
|
452
454
|
// Switch to Gross mode
|
|
453
455
|
client.set_rate_limit(&direction, &eid, &Some(config_with_mode(100, 100, Mode::Gross)));
|
|
454
|
-
|
|
456
|
+
|
|
455
457
|
// in-flight should be checkpointed
|
|
456
458
|
assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40);
|
|
457
|
-
|
|
459
|
+
|
|
458
460
|
// Now releases should be no-op
|
|
459
461
|
client.release(&direction, &eid, &20i128);
|
|
460
462
|
assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40, "Gross mode should not release");
|
|
@@ -474,10 +476,10 @@ fn test_switching_from_gross_to_net_mode() {
|
|
|
474
476
|
|
|
475
477
|
// Switch to Net mode
|
|
476
478
|
client.set_rate_limit(&direction, &eid, &Some(config_with_mode(100, 100, Mode::Net)));
|
|
477
|
-
|
|
479
|
+
|
|
478
480
|
// in-flight should be checkpointed
|
|
479
481
|
assert_eq!(client.rate_limit_in_flight(&direction, &eid), 60);
|
|
480
|
-
|
|
482
|
+
|
|
481
483
|
// Now releases should work
|
|
482
484
|
client.release(&direction, &eid, &20i128);
|
|
483
485
|
assert_eq!(client.rate_limit_in_flight(&direction, &eid), 40, "Net mode should release");
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//! General errors for the SAC Manager contract.
|
|
2
|
+
|
|
3
|
+
use common_macros::contract_error;
|
|
4
|
+
|
|
5
|
+
/// General SacManagerError: 3220-3249
|
|
6
|
+
#[contract_error]
|
|
7
|
+
pub enum SacManagerError {
|
|
8
|
+
/// The address is already a minter
|
|
9
|
+
AlreadyMinter,
|
|
10
|
+
/// Minter address not in the minters list (e.g. when deactivating)
|
|
11
|
+
MinterNotFound,
|
|
12
|
+
/// Caller is not authorized for this operation
|
|
13
|
+
Unauthorized,
|
|
14
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//! SAC Manager Contract - Manages a Stellar Asset Contract (SAC) as its admin.
|
|
2
|
+
//!
|
|
3
|
+
//! This contract becomes the admin of a SAC and provides:
|
|
4
|
+
//! - Mintable interface for OFT (mint on credit only; OFT burns on the token directly)
|
|
5
|
+
//! - Admin operations (clawback, set_admin, set_authorized)
|
|
6
|
+
//! - Upgradeable (owner-authorized)
|
|
7
|
+
//!
|
|
8
|
+
//! ## Authorization Model
|
|
9
|
+
//!
|
|
10
|
+
//! - **Owner** (via Ownable): The admin address. Can do everything (upgrades, TTL, admin ops, set_minter).
|
|
11
|
+
//! - **Minters**: Stored list of addresses. Only an address in Minters can call mint via Mintable with `operation: minter_address`.
|
|
12
|
+
|
|
13
|
+
use crate::{errors::SacManagerError, storage::SacManagerStorage};
|
|
14
|
+
use common_macros::{contract_impl, lz_contract, only_auth};
|
|
15
|
+
use oft::Mintable;
|
|
16
|
+
use soroban_sdk::{assert_with_error, token::StellarAssetClient, Address, Env, Vec};
|
|
17
|
+
use utils::ownable::OwnableInitializer;
|
|
18
|
+
|
|
19
|
+
// =========================================================================
|
|
20
|
+
// SAC Manager Contract
|
|
21
|
+
// =========================================================================
|
|
22
|
+
|
|
23
|
+
/// SAC Manager Contract
|
|
24
|
+
///
|
|
25
|
+
/// Manages a SAC as its admin, forwarding token actions to
|
|
26
|
+
/// the underlying SAC while enforcing access control.
|
|
27
|
+
#[lz_contract(upgradeable(no_migration))]
|
|
28
|
+
pub struct SacManager;
|
|
29
|
+
|
|
30
|
+
#[contract_impl]
|
|
31
|
+
impl SacManager {
|
|
32
|
+
/// Constructs the SAC manager contract.
|
|
33
|
+
///
|
|
34
|
+
/// # Arguments
|
|
35
|
+
/// * `sac_token` - The underlying Stellar Asset Contract address
|
|
36
|
+
/// * `owner` - The initial owner address (for upgrades, owner ops)
|
|
37
|
+
///
|
|
38
|
+
/// Minters can be set separately via `set_minter` after construction.
|
|
39
|
+
pub fn __constructor(env: &Env, sac_token: &Address, owner: &Address) {
|
|
40
|
+
Self::init_owner(env, owner);
|
|
41
|
+
SacManagerStorage::set_sac_token(env, sac_token);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// =========================================================================
|
|
45
|
+
// SAC Admin Operations
|
|
46
|
+
// =========================================================================
|
|
47
|
+
|
|
48
|
+
/// Delegates to the underlying SAC to clawback `amount` from `from` account.
|
|
49
|
+
#[only_auth]
|
|
50
|
+
pub fn clawback(env: &Env, from: &Address, amount: i128) {
|
|
51
|
+
sac_client(env).clawback(from, &amount);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Delegates to the underlying SAC `set_admin` to release the admin role from the current contract to `to`.
|
|
55
|
+
#[only_auth]
|
|
56
|
+
pub fn release_sac_admin(env: &Env, to: &Address) {
|
|
57
|
+
sac_client(env).set_admin(to);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Delegates to the underlying SAC `set_authorized` to set whether `id` is authorized to use its balance.
|
|
61
|
+
#[only_auth]
|
|
62
|
+
pub fn set_authorized(env: &Env, id: &Address, authorize: bool) {
|
|
63
|
+
sac_client(env).set_authorized(id, &authorize);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Adds (`active = true`) or removes (`active = false`) a minter.
|
|
67
|
+
#[only_auth]
|
|
68
|
+
pub fn set_minter(env: &Env, minter: &Address, active: bool) {
|
|
69
|
+
let mut minters = SacManagerStorage::minters(env);
|
|
70
|
+
let minter_index = minters.first_index_of(minter);
|
|
71
|
+
if active {
|
|
72
|
+
assert_with_error!(env, minter_index.is_none(), SacManagerError::AlreadyMinter);
|
|
73
|
+
minters.push_back(minter.clone());
|
|
74
|
+
} else {
|
|
75
|
+
minters.remove(minter_index.unwrap_or_panic(env, SacManagerError::MinterNotFound));
|
|
76
|
+
}
|
|
77
|
+
SacManagerStorage::set_minters(env, &minters);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// =========================================================================
|
|
81
|
+
// View Functions
|
|
82
|
+
// =========================================================================
|
|
83
|
+
|
|
84
|
+
/// Returns the underlying SAC (Stellar Asset Contract) address.
|
|
85
|
+
pub fn underlying_sac(env: &Env) -> Address {
|
|
86
|
+
SacManagerStorage::sac_token(env).unwrap()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Returns the list of addresses authorized to call mint.
|
|
90
|
+
pub fn minters(env: &Env) -> Vec<Address> {
|
|
91
|
+
SacManagerStorage::minters(env)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =========================================================================
|
|
96
|
+
// Mintable Implementation (OFT mint on credit)
|
|
97
|
+
// =========================================================================
|
|
98
|
+
|
|
99
|
+
#[contract_impl]
|
|
100
|
+
impl Mintable for SacManager {
|
|
101
|
+
fn mint(env: &Env, to: &Address, amount: i128, operation: &Address) {
|
|
102
|
+
assert_with_error!(env, Self::minters(env).contains(operation), SacManagerError::Unauthorized);
|
|
103
|
+
operation.require_auth();
|
|
104
|
+
|
|
105
|
+
sac_client(env).mint(to, &amount);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// =========================================================================
|
|
110
|
+
// Helper Functions
|
|
111
|
+
// =========================================================================
|
|
112
|
+
|
|
113
|
+
fn sac_client(env: &Env) -> StellarAssetClient<'_> {
|
|
114
|
+
StellarAssetClient::new(env, &SacManager::underlying_sac(env))
|
|
115
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//! Storage definitions for the SAC manager contract.
|
|
2
|
+
//!
|
|
3
|
+
//! This file contains the core storage.
|
|
4
|
+
|
|
5
|
+
use common_macros::storage;
|
|
6
|
+
use soroban_sdk::{Address, Vec};
|
|
7
|
+
|
|
8
|
+
/// Core storage for the SacManager contract.
|
|
9
|
+
/// Contains essential state.
|
|
10
|
+
#[storage]
|
|
11
|
+
pub enum SacManagerStorage {
|
|
12
|
+
/// The underlying SAC (Stellar Asset Contract) address
|
|
13
|
+
#[instance(Address)]
|
|
14
|
+
SacToken,
|
|
15
|
+
|
|
16
|
+
/// Addresses authorized to call mint (e.g. OFT or other minters).
|
|
17
|
+
#[persistent(Vec<Address>)]
|
|
18
|
+
#[default(Vec::new(env))]
|
|
19
|
+
Minters,
|
|
20
|
+
}
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
//! Token Wrapper Tests
|
|
2
2
|
//!
|
|
3
3
|
//! Test modules organized by source module:
|
|
4
|
-
//! - `supply_control`: Tests for supply control extension (controllers, mint, burn, rate limits)
|
|
5
|
-
//! - `redistribution`: Tests for redistribution extension (feature flag settings)
|
|
6
4
|
//! - `sac_manager`: Tests for the main SAC manager contract (admin, authorized, clawback, mint/burn, views)
|
|
7
5
|
|
|
8
6
|
#![cfg(test)]
|
|
9
7
|
|
|
10
8
|
mod test_helper;
|
|
11
9
|
|
|
12
|
-
mod redistribution;
|
|
13
10
|
mod sac_manager;
|
|
14
|
-
mod supply_control;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//! Clawback Integration Tests
|
|
2
|
+
//!
|
|
3
|
+
//! Tests for admin-initiated clawback functionality via `clawback`.
|
|
4
|
+
//! The clawback operation uses SAC clawback, which requires AUTH_CLAWBACK_ENABLED
|
|
5
|
+
//! flag on the SAC issuer.
|
|
6
|
+
|
|
7
|
+
use super::test_helper::mock_clawback_auth;
|
|
8
|
+
use crate::tests::test_helper::{mock_oft_mint_auth, TestSetup};
|
|
9
|
+
use soroban_sdk::testutils::IssuerFlags;
|
|
10
|
+
|
|
11
|
+
// =========================================================================
|
|
12
|
+
// clawback Tests (owner only)
|
|
13
|
+
// =========================================================================
|
|
14
|
+
|
|
15
|
+
#[test]
|
|
16
|
+
fn test_clawback_by_owner() {
|
|
17
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
18
|
+
let user = setup.generate_address();
|
|
19
|
+
|
|
20
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::RevocableFlag);
|
|
21
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::ClawbackEnabledFlag);
|
|
22
|
+
|
|
23
|
+
mock_oft_mint_auth(&setup, &user, 1000_i128);
|
|
24
|
+
setup.sac_manager_client.mint(&user, &1000, &setup.minter);
|
|
25
|
+
assert_eq!(setup.sac_client.balance(&user), 1000);
|
|
26
|
+
|
|
27
|
+
mock_clawback_auth(&setup, &setup.owner, &user, 500_i128);
|
|
28
|
+
setup.sac_manager_client.clawback(&user, &500);
|
|
29
|
+
assert_eq!(setup.sac_client.balance(&user), 500);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[test]
|
|
33
|
+
fn test_clawback_full_balance() {
|
|
34
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
35
|
+
let user = setup.generate_address();
|
|
36
|
+
|
|
37
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::RevocableFlag);
|
|
38
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::ClawbackEnabledFlag);
|
|
39
|
+
|
|
40
|
+
mock_oft_mint_auth(&setup, &user, 1000_i128);
|
|
41
|
+
setup.sac_manager_client.mint(&user, &1000, &setup.minter);
|
|
42
|
+
|
|
43
|
+
mock_clawback_auth(&setup, &setup.owner, &user, 1000_i128);
|
|
44
|
+
setup.sac_manager_client.clawback(&user, &1000);
|
|
45
|
+
assert_eq!(setup.sac_client.balance(&user), 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[test]
|
|
49
|
+
fn test_clawback_fails_when_amount_exceeds_balance() {
|
|
50
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
51
|
+
let user = setup.generate_address();
|
|
52
|
+
|
|
53
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::RevocableFlag);
|
|
54
|
+
setup.sac_contract.issuer().set_flag(IssuerFlags::ClawbackEnabledFlag);
|
|
55
|
+
|
|
56
|
+
mock_oft_mint_auth(&setup, &user, 100_i128);
|
|
57
|
+
setup.sac_manager_client.mint(&user, &100, &setup.minter);
|
|
58
|
+
|
|
59
|
+
mock_clawback_auth(&setup, &setup.owner, &user, 200_i128);
|
|
60
|
+
let result = setup.sac_manager_client.try_clawback(&user, &200);
|
|
61
|
+
assert!(result.is_err());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn test_clawback_by_non_owner_fails() {
|
|
66
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
67
|
+
let user = setup.generate_address();
|
|
68
|
+
let random = setup.generate_address();
|
|
69
|
+
|
|
70
|
+
mock_clawback_auth(&setup, &random, &user, 500_i128);
|
|
71
|
+
let result = setup.sac_manager_client.try_clawback(&user, &500);
|
|
72
|
+
assert!(result.is_err());
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// =========================================================================
|
|
76
|
+
// clawback Auth Tests
|
|
77
|
+
// =========================================================================
|
|
78
|
+
|
|
79
|
+
#[test]
|
|
80
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
81
|
+
fn test_clawback_fails_without_auth() {
|
|
82
|
+
let setup = TestSetup::new().build();
|
|
83
|
+
let user = setup.generate_address();
|
|
84
|
+
|
|
85
|
+
setup.sac_manager_client.clawback(&user, &300);
|
|
86
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//! Mint (Mintable) Integration Tests
|
|
2
|
+
//!
|
|
3
|
+
//! Tests minter-originated mint via Mintable::mint(..., operation: minter),
|
|
4
|
+
//! authorization (operation must be in minters, operation must auth), and error cases.
|
|
5
|
+
|
|
6
|
+
use crate::errors::SacManagerError;
|
|
7
|
+
use crate::tests::test_helper::{mock_auth, mock_oft_mint_auth, TestSetup};
|
|
8
|
+
|
|
9
|
+
// =========================================================================
|
|
10
|
+
// Mint success — minter calls mint
|
|
11
|
+
// =========================================================================
|
|
12
|
+
|
|
13
|
+
#[test]
|
|
14
|
+
fn test_mint_by_minter() {
|
|
15
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
16
|
+
let recipient = setup.generate_address();
|
|
17
|
+
|
|
18
|
+
mock_oft_mint_auth(&setup, &recipient, 1000_i128);
|
|
19
|
+
setup.sac_manager_client.mint(&recipient, &1000, &setup.minter);
|
|
20
|
+
assert_eq!(setup.sac_client.balance(&recipient), 1000);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// =========================================================================
|
|
24
|
+
// Mint error cases — operation not in minters / non-minter
|
|
25
|
+
// =========================================================================
|
|
26
|
+
|
|
27
|
+
#[test]
|
|
28
|
+
fn test_mint_fails_when_operation_not_in_minters() {
|
|
29
|
+
let setup = TestSetup::new().with_empty_minters().build();
|
|
30
|
+
let recipient = setup.generate_address();
|
|
31
|
+
|
|
32
|
+
let result = setup.sac_manager_client.try_mint(&recipient, &1000, &setup.minter);
|
|
33
|
+
assert_eq!(result.err().unwrap().unwrap(), SacManagerError::Unauthorized.into());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[test]
|
|
37
|
+
fn test_mint_by_non_minter_fails() {
|
|
38
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
39
|
+
let recipient = setup.generate_address();
|
|
40
|
+
let random = setup.generate_address();
|
|
41
|
+
|
|
42
|
+
mock_auth(&setup.env, &setup.sac_manager, &random, "mint", (&recipient, 1000_i128, &random));
|
|
43
|
+
let result = setup.sac_manager_client.try_mint(&recipient, &1000, &random);
|
|
44
|
+
assert_eq!(result.err().unwrap().unwrap(), SacManagerError::Unauthorized.into());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// =========================================================================
|
|
48
|
+
// Mint auth tests — operation must authorize
|
|
49
|
+
// =========================================================================
|
|
50
|
+
|
|
51
|
+
#[test]
|
|
52
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
53
|
+
fn test_mint_fails_without_minter_auth() {
|
|
54
|
+
let setup = TestSetup::new().with_manager_as_sac_admin().build();
|
|
55
|
+
let recipient = setup.generate_address();
|
|
56
|
+
|
|
57
|
+
setup.sac_manager_client.mint(&recipient, &1000, &setup.minter);
|
|
58
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
//! set_minter Integration Tests
|
|
2
|
+
|
|
3
|
+
use super::test_helper::mock_set_minter_auth;
|
|
4
|
+
use crate::errors::SacManagerError;
|
|
5
|
+
use crate::tests::test_helper::TestSetup;
|
|
6
|
+
|
|
7
|
+
// =========================================================================
|
|
8
|
+
// set_minter Tests
|
|
9
|
+
// =========================================================================
|
|
10
|
+
|
|
11
|
+
#[test]
|
|
12
|
+
fn test_set_minter_add_when_empty() {
|
|
13
|
+
let setup = TestSetup::new().with_empty_minters().build();
|
|
14
|
+
let new_minter = setup.generate_address();
|
|
15
|
+
|
|
16
|
+
mock_set_minter_auth(&setup, &new_minter, true);
|
|
17
|
+
setup.sac_manager_client.set_minter(&new_minter, &true);
|
|
18
|
+
let stored = setup.sac_manager_client.minters();
|
|
19
|
+
assert_eq!(stored.len(), 1);
|
|
20
|
+
assert_eq!(stored.get(0), Some(new_minter));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn test_set_minter_add_then_remove() {
|
|
25
|
+
let setup = TestSetup::new().build();
|
|
26
|
+
let new_minter = setup.generate_address();
|
|
27
|
+
|
|
28
|
+
mock_set_minter_auth(&setup, &new_minter, true);
|
|
29
|
+
setup.sac_manager_client.set_minter(&new_minter, &true);
|
|
30
|
+
assert_eq!(setup.sac_manager_client.minters().len(), 2);
|
|
31
|
+
|
|
32
|
+
mock_set_minter_auth(&setup, &new_minter, false);
|
|
33
|
+
setup.sac_manager_client.set_minter(&new_minter, &false);
|
|
34
|
+
let stored = setup.sac_manager_client.minters();
|
|
35
|
+
assert_eq!(stored.len(), 1);
|
|
36
|
+
assert_eq!(stored.get(0), Some(setup.minter.clone()));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
#[test]
|
|
40
|
+
fn test_set_minter_add_fails_if_already_active() {
|
|
41
|
+
let setup = TestSetup::new().build();
|
|
42
|
+
|
|
43
|
+
mock_set_minter_auth(&setup, &setup.minter, true);
|
|
44
|
+
let result = setup.sac_manager_client.try_set_minter(&setup.minter, &true);
|
|
45
|
+
assert_eq!(result.err().unwrap().unwrap(), SacManagerError::AlreadyMinter.into());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[test]
|
|
49
|
+
fn test_set_minter_remove_fails_if_not_minter() {
|
|
50
|
+
let setup = TestSetup::new().build();
|
|
51
|
+
let random = setup.generate_address();
|
|
52
|
+
|
|
53
|
+
mock_set_minter_auth(&setup, &random, false);
|
|
54
|
+
let result = setup.sac_manager_client.try_set_minter(&random, &false);
|
|
55
|
+
assert_eq!(result.err().unwrap().unwrap(), SacManagerError::MinterNotFound.into());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// =========================================================================
|
|
59
|
+
// set_minter Auth Tests
|
|
60
|
+
// =========================================================================
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
#[should_panic(expected = "Error(Auth, InvalidAction)")]
|
|
64
|
+
fn test_set_minter_without_auth() {
|
|
65
|
+
let setup = TestSetup::new().with_empty_minters().build();
|
|
66
|
+
let new_minter = setup.generate_address();
|
|
67
|
+
|
|
68
|
+
setup.sac_manager_client.set_minter(&new_minter, &true);
|
|
69
|
+
}
|