Skip to content
Open
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
179 changes: 179 additions & 0 deletions soroban/contracts/farming-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,20 @@ fn test_effective_stake_full_allocation_2x() {
assert_eq!(stake, 2_000);
}

#[test]
fn test_effective_stake_full_allocation_3x() {
// 100% allocation at 3× multiplier: all principal is boosted.
let stake = compute_total_stake(1_000, 100, 3);
assert_eq!(stake, 3_000);
}

#[test]
fn test_effective_stake_full_allocation_1x_has_no_gain() {
// 100% allocation at 1× multiplier should behave like no boost.
let stake = compute_total_stake(1_000, 100, 1);
assert_eq!(stake, 1_000);
}

#[test]
fn test_effective_stake_half_allocation_2x() {
// 50% allocation at 2×: principal = 500, virtual = 500*2 = 1000. total = 1500.
Expand Down Expand Up @@ -233,6 +247,25 @@ fn test_boost_update_preserves_previously_earned_credits() {
assert_eq!(t.client.get_credits(&t.user), 12_500);
}

#[test]
fn test_boost_change_checkpoints_old_rate_across_long_window() {
// This mirrors the bounty invariant: 500 ledgers unboosted, then 500 ledgers
// at 50% allocation with a 2× multiplier. Credits from the first window must
// not be retroactively recalculated after the boost is applied.
let t = setup(2, 1);
t.client.stake(&t.user, &1_000);
advance_ledgers(&t.env, 500);
t.client.set_boost(&t.user, &50u32);
advance_ledgers(&t.env, 500);

let unboosted_window = 1_000 * 500;
let boosted_window = 1_500 * 500;
assert_eq!(
t.client.get_credits(&t.user),
unboosted_window + boosted_window
);
}

#[test]
fn test_boost_can_be_updated_repeatedly_without_losing_credits() {
// 10 ledgers at 50% boost (effective 1500), then 10 at 100% (effective 2000).
Expand Down Expand Up @@ -260,6 +293,28 @@ fn test_set_boost_rejects_over_100_allocation() {
assert!(t.client.try_set_boost(&t.user, &101u32).is_err());
}

#[test]
fn test_set_boost_emits_boost_applied_event() {
let t = setup(2, 1);
t.client.set_boost(&t.user, &50u32);

assert_eq!(
t.env.events().all(),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("boost").into_val(&t.env),
soroban_sdk::symbol_short!("applied").into_val(&t.env)
],
(t.user.clone(), 50u32, 2u32).into_val(&t.env),
)
]
);
}

#[test]
fn test_admin_sets_global_multiplier() {
let t = setup(2, 1);
Expand All @@ -271,6 +326,50 @@ fn test_admin_sets_global_multiplier() {
assert_eq!(cfg.multiplier, 3);
}

#[test]
fn test_set_global_multiplier_emits_mult_set_event() {
let t = setup(2, 1);
t.client.set_global_multiplier(&3u32);

assert_eq!(
t.env.events().all(),
soroban_sdk::vec![
&t.env,
(
t.contract_id.clone(),
soroban_sdk::vec![
&t.env,
soroban_sdk::symbol_short!("boost").into_val(&t.env),
soroban_sdk::symbol_short!("mult_set").into_val(&t.env)
],
3u32.into_val(&t.env),
)
]
);
}

#[test]
fn test_set_global_multiplier_requires_admin_auth() {
let (env, contract_id, client, _admin, user) = setup_without_mocked_auth();

let result = client
.mock_auths(&[MockAuth {
address: &user,
invoke: &MockAuthInvoke {
contract: &contract_id,
fn_name: "set_global_multiplier",
args: (&3u32,).into_val(&env),
sub_invokes: &[],
},
}])
.try_set_global_multiplier(&3u32);

assert!(
result.is_err(),
"non-admin set_global_multiplier must be rejected"
);
}

#[test]
fn test_set_credit_rate_updates_public_getters() {
let t = setup_with_lock_period(2, 1, 12);
Expand Down Expand Up @@ -413,6 +512,21 @@ fn test_admin_multiplier_change_applies_from_next_checkpoint() {
assert_eq!(t.client.get_credits(&t.user), 35_000);
}

#[test]
fn test_global_multiplier_change_does_not_retroactively_alter_banked_credits() {
let t = setup(2, 1);
t.client.stake(&t.user, &1_000);
t.client.set_boost(&t.user, &50u32);
advance_ledgers(&t.env, 20);

t.client.set_boost(&t.user, &50u32); // checkpoints 30_000 at 2×
t.client.set_global_multiplier(&3u32);
assert_eq!(t.client.get_credits(&t.user), 30_000);

advance_ledgers(&t.env, 1);
assert_eq!(t.client.get_credits(&t.user), 32_000);
}

#[test]
#[should_panic(expected = "multiplier must be >= 1")]
fn test_admin_multiplier_rejects_zero() {
Expand Down Expand Up @@ -454,6 +568,71 @@ fn test_get_credits_zero_without_stake() {
assert_eq!(t.client.get_credits(&t.user), 0);
}

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

advance_ledgers(&t.env, 3);
let first = t.client.get_credits(&t.user);
advance_ledgers(&t.env, 7);
let second = t.client.get_credits(&t.user);

assert_eq!(first, 3_000);
assert_eq!(second, 10_000);
assert!(second > first);
}

#[test]
fn test_boosted_stake_earns_more_than_unboosted_same_amount() {
let t = setup(2, 1);
let boosted = t.user.clone();
let unboosted = Address::generate(&t.env);
t.token_sac.mint(&unboosted, &1_000_000i128);

t.client.stake(&boosted, &1_000);
t.client.set_boost(&boosted, &50u32);
t.client.stake(&unboosted, &1_000);
advance_ledgers(&t.env, 10);

assert_eq!(t.client.get_credits(&unboosted), 10_000);
assert_eq!(t.client.get_credits(&boosted), 15_000);
assert!(t.client.get_credits(&boosted) > t.client.get_credits(&unboosted));
}

#[test]
fn test_get_credits_and_calculate_credits_track_separate_position_types() {
// `get_credits` reports stake/unstake credit state. `calculate_credits`
// reports lock/unlock position credit state. A user can have both, but each
// view must only include its own position type.
let t = setup(2, 1);

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

t.client.stake(&t.user, &500);
advance_ledgers(&t.env, 10);
assert_eq!(t.client.calculate_credits(&t.user), 20_000);
assert_eq!(t.client.get_credits(&t.user), 5_000);
}

#[test]
fn test_stake_unstake_round_trip_restores_balance_and_clears_stake() {
let t = setup(2, 1);
let initial_balance = t.token.balance(&t.user);

t.client.stake(&t.user, &2_500);
assert_eq!(t.token.balance(&t.user), initial_balance - 2_500);
advance_ledgers(&t.env, 4);

let credits = t.client.unstake(&t.user);
assert_eq!(credits, 10_000);
assert_eq!(t.token.balance(&t.user), initial_balance);
assert!(t.client.get_stake(&t.user).is_none());
}

// ── lock_assets tests ─────────────────────────────────────────────────────────

#[test]
Expand Down