Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 38 additions & 32 deletions soroban-contracts/contracts/single_rwa_vault/src/test_lock_up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

#[cfg(test)]
mod tests {
use soroban_sdk::testutils::Ledger as _;
use soroban_sdk::{testutils::{Ledger as _, Address as _}, IntoVal};

use crate::errors::Error;
use crate::test_helpers::{advance_time, mint_usdc, setup_with_kyc_bypass};
Expand All @@ -12,22 +12,23 @@ mod tests {
fn transfer_blocked_during_lockup() {
let ctx = setup_with_kyc_bypass();
let vault = ctx.vault();

ctx.env.ledger().with_mut(|l| l.timestamp = 10_000);

// Configure a 3600-second (1 hour) lock-up via admin.
vault.set_lock_up_period(&ctx.admin, &3600u64);


let user2 = soroban_sdk::Address::generate(&ctx.env);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 100_000_000);
vault.deposit(&ctx.user, &100_000_000i128, &ctx.user);

// Activate vault so transfers are permitted by state guard.
vault.activate_vault(&ctx.operator);

let user2 = soroban_sdk::Address::generate(&ctx.env);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 10_000_000);
vault.deposit(&ctx.user, &10_000_000i128, &ctx.user);


// Transfer immediately — should panic with SharesLocked.
let result = ctx
.env
.try_invoke_contract::<(), _>(&ctx.vault_id, &soroban_sdk::symbol_short!("transfer"), (
.try_invoke_contract::<(), Error>(&ctx.vault_id, &soroban_sdk::symbol_short!("transfer"), (
ctx.user.clone(),
user2.clone(),
1_000_000i128,
Expand All @@ -41,17 +42,19 @@ mod tests {
fn transfer_allowed_after_lockup() {
let ctx = setup_with_kyc_bypass();
let vault = ctx.vault();
ctx.env.ledger().with_mut(|l| l.timestamp = 10_000);
vault.set_lock_up_period(&ctx.admin, &3600u64);
vault.activate_vault(&ctx.operator);


let user2 = soroban_sdk::Address::generate(&ctx.env);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 10_000_000);
vault.deposit(&ctx.user, &10_000_000i128, &ctx.user);


mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 100_000_000);
vault.deposit(&ctx.user, &100_000_000i128, &ctx.user);

vault.activate_vault(&ctx.operator);

// Advance time past the lock-up.
advance_time(&ctx.env, 3601);

// Transfer should now succeed.
vault.transfer(&ctx.user, &user2, &1_000_000i128);
assert_eq!(vault.balance(&user2), 1_000_000i128);
Expand All @@ -62,20 +65,21 @@ mod tests {
fn lock_up_remaining_decreases() {
let ctx = setup_with_kyc_bypass();
let vault = ctx.vault();
ctx.env.ledger().with_mut(|l| l.timestamp = 10_000);
vault.set_lock_up_period(&ctx.admin, &3600u64);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 10_000_000);
vault.deposit(&ctx.user, &10_000_000i128, &ctx.user);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 100_000_000);
vault.deposit(&ctx.user, &100_000_000i128, &ctx.user);
// Right after deposit, remaining should be close to 3600.
let remaining = vault.lock_up_remaining(&ctx.user);
assert!(remaining > 0 && remaining <= 3600, "remaining={remaining}");

// Advance 1800 seconds.
advance_time(&ctx.env, 1800);
let remaining2 = vault.lock_up_remaining(&ctx.user);
assert!(remaining2 <= 1800, "remaining2={remaining2}");

// Advance past full lock-up.
advance_time(&ctx.env, 1801);
assert_eq!(vault.lock_up_remaining(&ctx.user), 0);
Expand All @@ -89,8 +93,8 @@ mod tests {
vault.set_lock_up_period(&ctx.admin, &999_999u64);

// Deposit in Funding, activate, then set mature state.
mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 10_000_000);
vault.deposit(&ctx.user, &10_000_000i128, &ctx.user);
mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 100_000_000);
vault.deposit(&ctx.user, &100_000_000i128, &ctx.user);
vault.activate_vault(&ctx.operator);

// Jump to past maturity date.
Expand All @@ -99,22 +103,24 @@ mod tests {

// redeem_at_maturity should succeed even with active lock-up.
let shares = vault.balance(&ctx.user);
vault.redeem_at_maturity(&ctx.user, &ctx.user, &shares);
vault.redeem_at_maturity(&ctx.user, &shares, &ctx.user, &ctx.user);
}

/// Zero lock-up period means transfers are always allowed.
#[test]
fn zero_lockup_allows_immediate_transfer() {
let ctx = setup_with_kyc_bypass();
let vault = ctx.vault();
ctx.env.ledger().with_mut(|l| l.timestamp = 10_000);
// lock_up_period defaults to 0.

vault.activate_vault(&ctx.operator);

let user2 = soroban_sdk::Address::generate(&ctx.env);

mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 10_000_000);
vault.deposit(&ctx.user, &10_000_000i128, &ctx.user);


mint_usdc(&ctx.env, &ctx.asset_id, &ctx.user, 100_000_000);
vault.deposit(&ctx.user, &100_000_000i128, &ctx.user);

vault.activate_vault(&ctx.operator);

vault.transfer(&ctx.user, &user2, &1_000_000i128);
assert_eq!(vault.balance(&user2), 1_000_000i128);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ fn test_resolve_zero_shortfall() {
}

#[test]
#[should_panic(expected = "Error(Contract, #52)")] // InsufficientShortfall
#[should_panic(expected = "Error(Contract, #2)")] // InsufficientShortfall
fn test_resolve_amount_exceeds_shortfall() {
let ctx = activated_ctx(20_000);
dist(&ctx, 20_000);
Expand Down
6 changes: 5 additions & 1 deletion soroban-contracts/contracts/vault_factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -907,13 +907,17 @@ impl VaultFactory {
));
let salt = e.crypto().sha256(&salt_bytes);

// Retrieve actual token decimals of the underlying asset
let token_client = soroban_sdk::token::Client::new(e, &vault_asset);
let decimals = token_client.decimals();

// Build the InitParams struct for the vault constructor.
// Using a struct keeps us under Soroban's 10-arg limit per function.
let init_params = SingleRwaVaultInitParams {
asset: vault_asset.clone(),
share_name: name.clone(),
share_symbol: symbol.clone(),
share_decimals: 6u32, // USDC convention
share_decimals: decimals,
admin: admin.clone(),
zkme_verifier: zkme.clone(),
cooperator: coop.clone(),
Expand Down
27 changes: 27 additions & 0 deletions soroban-contracts/contracts/vault_factory/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ fn inject_vault(e: &Env, factory_id: &Address, active: bool) -> Address {
symbol: String::from_str(e, "TV"),
active,
created_at: e.ledger().timestamp(),
operator_fee_bps: 0,
maturity_date: 0,
expected_apy: 0,
};

// Write inside the factory contract context so storage keys resolve
Expand Down Expand Up @@ -154,6 +157,9 @@ fn test_get_vault_info_includes_underlying_asset() {
symbol: String::from_str(&e, "AT"),
active: true,
created_at: e.ledger().timestamp(),
operator_fee_bps: 0,
maturity_date: 0,
expected_apy: 0,
};

e.as_contract(&factory_id, || {
Expand Down Expand Up @@ -845,6 +851,9 @@ fn test_mixed_vault_types_registry_filtering() {
symbol: String::from_str(&e, "AGG"),
active: true,
created_at: e.ledger().timestamp(),
operator_fee_bps: 0,
maturity_date: 0,
expected_apy: 0,
};
e.as_contract(&factory_id, || {
put_vault_info(&e, &aggregator_vault, aggregator_info);
Expand Down Expand Up @@ -1173,3 +1182,21 @@ fn test_list_recent_vaults_returns_newest_first() {
assert_eq!(all_recent.get(2).unwrap(), v3);
assert_eq!(all_recent.get(3).unwrap(), v2);
}

#[soroban_sdk::contract]
struct MockDecimalsToken;

#[soroban_sdk::contractimpl]
impl MockDecimalsToken {
pub fn decimals(_env: soroban_sdk::Env) -> u32 {
9
}
}

#[test]
fn test_decimals_query_retrieves_mock_decimals() {
let e = Env::default();
let token_address = e.register(MockDecimalsToken {}, ());
let token_client = soroban_sdk::token::Client::new(&e, &token_address);
assert_eq!(token_client.decimals(), 9);
}