Skip to content
Merged
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
16 changes: 16 additions & 0 deletions soroban/contracts/factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ impl Factory {
///
/// Returns `NotInitialized` if the factory has not been initialized, or
/// `PoolNotFound` if `pool_id` has not been created yet.
///
/// # TTL keep-alive
/// On success, extends the persistent TTL of the requested pool record (and
/// the factory instance) when remaining TTL falls below `TTL_THRESHOLD`.
/// Pools that are never individually queried should be kept alive via
/// paginated `list_pools` reads, asset-range queries, or the permissionless
/// `refresh_pool_ttls` function.
pub fn get_pool(env: Env, pool_id: u32) -> Result<PoolRecord, FactoryError> {
require_initialized(&env)?;
bump_instance(&env);
Expand All @@ -225,6 +232,15 @@ impl Factory {
/// Guarded like `pool_count`: an empty page from an uninitialized factory
/// would be indistinguishable from an initialized but empty registry.
///
/// # TTL keep-alive
/// Extends the persistent TTL of every pool record returned in this page
/// (plus the factory instance). Unlike `get_pool`, which bumps one record
/// per call, each paginated read refreshes all pools in the window. Indexers
/// that page through the registry therefore keep listed pools alive more
/// aggressively than pools accessed only via `get_pool(id)`. For deliberate
/// full-registry maintenance independent of read patterns, use
/// `refresh_pool_ttls`.
///
/// Returns `NotInitialized` if the factory has not been initialized.
pub fn list_pools(
env: Env,
Expand Down
20 changes: 20 additions & 0 deletions soroban/contracts/factory/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,26 @@ fn test_get_pool_bumps_pool_record_ttl() {
assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO);
}

#[test]
fn test_list_pools_bumps_pool_record_ttl() {
let t = setup();
let id = t.client.create_pool(
&Address::generate(&t.env),
&4_320_000u128,
&2u32,
&50u64,
&0i128,
);

assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO);

advance_ledgers(&t.env, TTL_EXTEND_TO - TTL_THRESHOLD + 1);
assert!(pool_record_ttl(&t.env, &t.factory_addr, id) < TTL_THRESHOLD);

assert!(t.client.try_list_pools(&id, &1u32).is_ok());
assert_eq!(pool_record_ttl(&t.env, &t.factory_addr, id), TTL_EXTEND_TO);
}

#[test]
fn test_refresh_pool_ttls_restores_ttl_for_unqueried_pool() {
let t = setup();
Expand Down
2 changes: 1 addition & 1 deletion soroban/contracts/farming-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,7 @@ impl FarmingPool {
new_stake.credit_rate = read_credit_rate(&env);

// Checks-effects-interactions: persist state *before* the external
// token transfer below, consistent with `lock_assets`. See #69.
// token transfer below, consistent with `lock_assets`. See #69, #217.
set_user_stake(&env, &from, &new_stake);
add_total_staked(&env, amount);

Expand Down
147 changes: 141 additions & 6 deletions soroban/contracts/farming-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1576,9 +1576,20 @@ fn test_unpause_restores_operations() {
fn test_pause_emits_event() {
let t = setup(1, 1);
t.client.pause();
assert!(
!t.env.events().all().events().is_empty(),
"pause event not emitted"
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("paused").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

Expand All @@ -1587,9 +1598,133 @@ fn test_unpause_emits_event() {
let t = setup(1, 1);
t.client.pause();
t.client.unpause();
assert!(
!t.env.events().all().events().is_empty(),
"unpause event not emitted"
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("paused").into_val(&t.env)
],
().into_val(&t.env),
),
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("unpaused").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

#[test]
fn test_pause_staking_emits_event() {
let t = setup(1, 1);
t.client.pause_staking();
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("stg_pause").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

#[test]
fn test_pause_withdrawals_emits_event() {
let t = setup(1, 1);
t.client.pause_withdrawals();
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("wd_pause").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

#[test]
fn test_unpause_staking_emits_event() {
let t = setup(1, 1);
t.client.pause_staking();
t.client.unpause_staking();
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("stg_pause").into_val(&t.env)
],
().into_val(&t.env),
),
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("stg_unps").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

#[test]
fn test_unpause_withdrawals_emits_event() {
let t = setup(1, 1);
t.client.pause_withdrawals();
t.client.unpause_withdrawals();
assert_eq!(
t.env.events().all().filter_by_contract(&t.contract_id),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("wd_pause").into_val(&t.env)
],
().into_val(&t.env),
),
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("wd_unps").into_val(&t.env)
],
().into_val(&t.env),
)
]
);
}

Expand Down
4 changes: 4 additions & 0 deletions soroban/contracts/vesting-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ impl VestingWallet {
return Err(VestingError::AlreadyInitialized);
}
assert!(total_amount > 0, "total_amount must be positive");
assert!(
start_ledger >= env.ledger().sequence(),
"start must be in the future"
);
assert!(cliff_ledger >= start_ledger, "cliff must be >= start");
assert!(end_ledger > cliff_ledger, "end must be > cliff");
let duration = i128::from(end_ledger - start_ledger);
Expand Down
31 changes: 31 additions & 0 deletions soroban/contracts/vesting-wallet/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,37 @@ fn test_double_initialize_returns_error() {
assert!(matches!(result, Err(Ok(VestingError::AlreadyInitialized))));
}

#[test]
#[should_panic(expected = "start must be in the future")]
fn test_initialize_rejects_start_ledger_in_the_past() {
let env = Env::default();
env.mock_all_auths();

let admin = Address::generate(&env);
let beneficiary = Address::generate(&env);
let token_admin = Address::generate(&env);
let asset = env.register_stellar_asset_contract_v2(token_admin.clone());
let token_sac = StellarAssetClient::new(&env, &asset.address());
token_sac.mint(&admin, &100i128);

advance_ledgers(&env, 10);
let current = env.ledger().sequence();
let past_start = current - 1;

let contract_id = env.register(VestingWallet, ());
let client = VestingWalletClient::new(&env, &contract_id);
client.initialize(
&beneficiary,
&asset.address(),
&100i128,
&past_start,
&current,
&(current + 100),
&false,
&admin,
);
}

#[test]
fn test_initialize_rejects_total_amount_above_compute_vested_ceiling() {
let env = Env::default();
Expand Down