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
1 change: 1 addition & 0 deletions docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Emitted by `unlock_assets` when a user withdraws assets from the pool.
| :--- | :--- | :--- |
| `user` | `Address` | The wallet address that unlocked assets. |
| `amount` | `i128` | The quantity of assets withdrawn in this call. |
| `credits_earned` | `i128` | The credits accrued and checkpointed during this unlock call (i.e. the delta since the previous checkpoint). |
| `total_credits` | `i128` | The user's checkpointed total credit balance at the time of withdrawal. |

### `paused`
Expand Down
4 changes: 3 additions & 1 deletion soroban/contracts/farming-pool/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1310,7 +1310,9 @@ impl FarmingPool {
return Err(PoolError::LockPeriodNotElapsed);
}

let credits_before = position.total_credits;
checkpoint_position(&env, &user, &mut position);
let credits_earned = position.total_credits - credits_before;
let total_credits = position.total_credits;
position.amount -= amount;

Expand Down Expand Up @@ -1344,7 +1346,7 @@ impl FarmingPool {

env.events().publish(
(symbol_short!("pool"), symbol_short!("unlocked")),
(user.clone(), amount, total_credits),
(user.clone(), amount, credits_earned, total_credits),
);

record_stake_event(&env, &user, symbol_short!("unlock"), amount);
Expand Down
41 changes: 39 additions & 2 deletions soroban/contracts/farming-pool/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,42 @@ fn test_unlock_assets_partial_keeps_remaining_position() {
assert_eq!(t.token.balance(&t.contract_id), 300);
}

#[test]
fn test_unlock_assets_emits_event_with_credits_earned() {
let t = setup(1, 1);
t.client.lock_assets(&t.user, &1_000);
advance_ledgers(&t.env, 10);
t.client.unlock_assets(&t.user, &1_000);

// The unlocked event must include credits_earned (the delta from this
// checkpoint) alongside total_credits so indexers can track credit
// distributions without storage scans.
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!("chkpt").into_val(&t.env)
],
(t.user.clone(), 10_000i128, 10_000i128).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!("unlocked").into_val(&t.env)
],
(t.user.clone(), 1_000i128, 10_000i128, 10_000i128).into_val(&t.env),
)
]
);
}

// ── unlock_assets split-invariance (#123) ─────────────────────────────────────
//
// #75 covers *when* checkpoints happen (time-invariance); this covers a
Expand Down Expand Up @@ -1589,7 +1625,8 @@ fn test_unlock_assets_final_outcome_is_invariant_to_how_the_withdrawal_is_split(
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("unlocked").into_val(&t.env)
],
(t.user.clone(), last_part, EXPECTED_TOTAL_CREDITS).into_val(&t.env),
(t.user.clone(), last_part, EXPECTED_TOTAL_CREDITS, EXPECTED_TOTAL_CREDITS)
.into_val(&t.env),
)
],
"final cumulative total_credits must be identical across partitions {partition:?}",
Expand All @@ -1606,7 +1643,7 @@ fn test_unlock_assets_final_outcome_is_invariant_to_how_the_withdrawal_is_split(
soroban_sdk::symbol_short!("pool").into_val(&t.env),
soroban_sdk::symbol_short!("unlocked").into_val(&t.env)
],
(t.user.clone(), last_part, EXPECTED_TOTAL_CREDITS).into_val(&t.env),
(t.user.clone(), last_part, 0i128, EXPECTED_TOTAL_CREDITS).into_val(&t.env),
)
],
"final cumulative total_credits must be identical across partitions {partition:?}",
Expand Down