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
24 changes: 24 additions & 0 deletions soroban/contracts/factory/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,14 @@ impl Factory {
.instance()
.get(&DataKey::PoolCount)
.unwrap_or(0);
if start_id >= count {
return Ok(ListPoolsResponse {
records: vec![&env],
next_start_id: count,
total: count,
has_more: false,
});
}
let capped_limit = if limit == 0 { 20 } else { limit.min(20) };
let end = start_id.saturating_add(capped_limit).min(count);
let mut records: Vec<(u32, PoolRecord)> = vec![&env];
Expand Down Expand Up @@ -352,6 +360,14 @@ impl Factory {
.instance()
.get(&DataKey::PoolCount)
.unwrap_or(0);
if start_id >= count {
return Ok(ListPoolsResponse {
records: vec![&env],
next_start_id: count,
total: count,
has_more: false,
});
}
let capped_limit = if limit == 0 { 20 } else { limit.min(20) };
let end = start_id.saturating_add(capped_limit).min(count);
let mut records: Vec<(u32, PoolRecord)> = vec![&env];
Expand Down Expand Up @@ -410,6 +426,14 @@ impl Factory {
.instance()
.get(&DataKey::PoolCount)
.unwrap_or(0);
if start_id >= count {
return Ok(ListPoolsResponse {
records: vec![&env],
next_start_id: count,
total: count,
has_more: false,
});
}
let capped_limit = if limit == 0 { 20 } else { limit.min(20) };
let effective_scan = if scan_limit == 0 {
MAX_POOL_SCAN_PER_CALL
Expand Down
11 changes: 11 additions & 0 deletions soroban/contracts/factory/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,17 @@ fn test_list_pools_returns_empty_when_start_is_beyond_count() {
assert!(!page.has_more);
}

#[test]
fn test_list_pools_returns_empty_when_start_equals_count() {
let t = setup_with_pool_records(3);
let page = t.client.list_pools(&3u32, &5u32);

assert_eq!(page.records.len(), 0);
assert_eq!(page.next_start_id, 3);
assert_eq!(page.total, 3);
assert!(!page.has_more);
}

#[test]
fn test_list_pools_caps_limit_at_twenty() {
let t = setup_with_pool_records(25);
Expand Down
67 changes: 67 additions & 0 deletions soroban/contracts/farming-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,44 @@ fn add_total_credits(env: &Env, amount: i128) {
);
}

fn read_total_banked_credits(env: &Env) -> i128 {
env.storage()
.instance()
.get(&DataKey::TotalBankedCredits)
.unwrap_or(0)
}

fn add_total_banked_credits(env: &Env, amount: i128) {
let total = read_total_banked_credits(env);
env.storage().instance().set(
&DataKey::TotalBankedCredits,
&total.checked_add(amount).expect("total banked credits overflow"),
);
}

fn subtract_total_banked_credits(env: &Env, amount: i128) {
let total = read_total_banked_credits(env);
env.storage().instance().set(
&DataKey::TotalBankedCredits,
&total.checked_sub(amount).expect("total banked credits underflow"),
);
}

fn read_total_credits_earned(env: &Env, user: &Address) -> i128 {
let key = DataKey::TotalCreditsEarned(user.clone());
env.storage().persistent().get(&key).unwrap_or(0)
}

fn add_total_credits_earned(env: &Env, user: &Address, amount: i128) {
let key = DataKey::TotalCreditsEarned(user.clone());
let total = env.storage().persistent().get::<DataKey, i128>(&key).unwrap_or(0);
env.storage().persistent().set(
&key,
&total.checked_add(amount).expect("user lifetime credits overflow"),
);
bump_user(env, &key);
}

fn read_total_deposits(env: &Env) -> i128 {
env.storage()
.instance()
Expand Down Expand Up @@ -633,6 +671,10 @@ fn checkpoint(env: &Env, user: &Address, stake: &mut UserStake) {
stake.credits_banked += accrued;
add_total_credits(env, accrued);
add_total_distributed_credits(env, accrued);
if accrued > 0 {
add_total_banked_credits(env, accrued);
add_total_credits_earned(env, user, accrued);
}
stake.start_ledger = current;
stake.credit_rate = read_credit_rate(env);
stake.multiplier = read_global_multiplier(env);
Expand All @@ -653,6 +695,10 @@ fn checkpoint_position(env: &Env, user: &Address, position: &mut Position) {
position.total_credits += delta;
add_total_credits(env, delta);
add_total_distributed_credits(env, delta);
if delta > 0 {
add_total_banked_credits(env, delta);
add_total_credits_earned(env, user, delta);
}
position.checkpoint_ledger = current;
position.credit_rate = read_credit_rate(env);

Expand Down Expand Up @@ -721,6 +767,9 @@ impl FarmingPool {
env.storage().instance().set(&DataKey::TotalStaked, &0i128);
env.storage().instance().set(&DataKey::TotalLocked, &0i128);
env.storage().instance().set(&DataKey::TotalCredits, &0i128);
env.storage()
.instance()
.set(&DataKey::TotalBankedCredits, &0i128);
env.storage()
.instance()
.set(&DataKey::TotalDeposits, &0i128);
Expand Down Expand Up @@ -1514,6 +1563,9 @@ impl FarmingPool {
let mut stake = get_user_stake(&env, &from).expect("no active stake");
checkpoint(&env, &from, &mut stake);
let total_credits = stake.credits_banked;
if total_credits > 0 {
subtract_total_banked_credits(&env, total_credits);
}

// Return staked tokens to caller.
let stake_token = get_stake_token(&env)?;
Expand Down Expand Up @@ -1853,6 +1905,21 @@ impl FarmingPool {
.unwrap_or(0))
}

/// Return the total credits currently banked across all users.
pub fn total_banked_credits(env: Env) -> Result<i128, PoolError> {
require_initialized(&env)?;
bump_instance(&env);
Ok(read_total_banked_credits(&env))
}

/// Return the cumulative credits earned by `user` across their lifetime,
/// including amounts already withdrawn.
pub fn total_credits_earned(env: Env, user: Address) -> Result<i128, PoolError> {
require_initialized(&env)?;
bump_instance(&env);
Ok(read_total_credits_earned(&env, &user))
}

/// Return the running total of all tokens deposited into the pool.
///
/// Incremented by `stake` and `lock_assets` with the amount transferred in.
Expand Down
38 changes: 38 additions & 0 deletions soroban/contracts/farming-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,44 @@ fn test_total_distributed_credits_accumulates_across_users_and_systems() {
assert_eq!(t.client.total_distributed_credits(), 10_500);
}

#[test]
fn test_total_credits_earned_tracks_lifetime_credits_across_withdrawals() {
let t = setup(2, 1);
t.client.stake(&t.user, &1_000);

advance_ledgers(&t.env, 10);
assert_eq!(t.client.get_credits(&t.user), 10_000);
assert_eq!(t.client.total_credits_earned(&t.user), 0);

t.client.unstake(&t.user);
assert_eq!(t.client.total_credits_earned(&t.user), 10_000);

advance_ledgers(&t.env, 5);
t.client.stake(&t.user, &500);
advance_ledgers(&t.env, 5);
t.client.unstake(&t.user);
assert_eq!(t.client.total_credits_earned(&t.user), 12_500);
}

#[test]
fn test_total_banked_credits_tracks_current_bank_across_users() {
let t = setup(2, 1);
let other = Address::generate(&t.env);
t.token_sac.mint(&other, &1_000_000_000i128);

t.client.stake(&t.user, &1_000);
advance_ledgers(&t.env, 10);
assert_eq!(t.client.total_banked_credits(), 0);

t.client.unstake(&t.user);
assert_eq!(t.client.total_banked_credits(), 0);

t.client.stake(&other, &2_000);
advance_ledgers(&t.env, 5);
t.client.unstake(&other);
assert_eq!(t.client.total_banked_credits(), 0);
}

#[test]
fn test_pause_uninitialized_returns_not_initialized() {
let (_env, client, _user) = setup_uninitialized();
Expand Down
4 changes: 4 additions & 0 deletions soroban/contracts/farming-pool/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ pub enum DataKey {
TotalBoostAlloc,
/// Count of users with a non-zero boost allocation currently set.
BoostUserCount,
/// Aggregate of credits currently banked across all users.
TotalBankedCredits,
/// Cumulative credits earned by a user across their entire lifetime.
TotalCreditsEarned(Address),
}

/// Paginated response for `get_whitelisted_users`.
Expand Down
7 changes: 7 additions & 0 deletions soroban/contracts/vesting-wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,13 @@ impl VestingWallet {
Ok(())
}

/// Return the token address for the vesting schedule.
pub fn token(env: Env) -> Result<Address, VestingError> {
require_initialized(&env)?;
bump_instance(&env);
Ok(get_token(&env))
}

/// Return the current admin address.
pub fn admin(env: Env) -> Result<Address, VestingError> {
require_initialized(&env)?;
Expand Down
6 changes: 6 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,12 @@ fn test_double_initialize_returns_error() {
assert!(matches!(result, Err(Ok(VestingError::AlreadyInitialized))));
}

#[test]
fn test_token_getter_returns_initialized_token_address() {
let t = setup(0, 100, 1_000);
assert_eq!(t.client.token(), t.token_address);
}

#[test]
#[should_panic(expected = "start must be in the future")]
fn test_initialize_rejects_start_ledger_in_the_past() {
Expand Down