From 8f484025aac819fd4b694821fe18e028c0780056 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CEnchanterme=E2=80=9D?= <“encountermehere@gmail.com”> Date: Sun, 27 Sep 2026 19:48:40 +0100 Subject: [PATCH] fix: emit credits_earned in unlock_assets event The unlocked event previously carried (user, amount, total_credits) but omitted the credits earned in the transaction, preventing indexers from tracking credit distributions without storage scans. Add credits_earned to the event payload so off-chain systems can track per-transaction credit distributions and build an audit trail for credit earnings. Event payload is now (user, amount, credits_earned, total_credits). --- docs/events.md | 1 + soroban/contracts/farming-pool/src/lib.rs | 4 ++- soroban/contracts/farming-pool/src/test.rs | 41 ++++++++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/events.md b/docs/events.md index b2ec0a1..203af55 100644 --- a/docs/events.md +++ b/docs/events.md @@ -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` diff --git a/soroban/contracts/farming-pool/src/lib.rs b/soroban/contracts/farming-pool/src/lib.rs index dc82372..04f8614 100644 --- a/soroban/contracts/farming-pool/src/lib.rs +++ b/soroban/contracts/farming-pool/src/lib.rs @@ -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; @@ -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); diff --git a/soroban/contracts/farming-pool/src/test.rs b/soroban/contracts/farming-pool/src/test.rs index 32c674e..e406ce2 100644 --- a/soroban/contracts/farming-pool/src/test.rs +++ b/soroban/contracts/farming-pool/src/test.rs @@ -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 @@ -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:?}", @@ -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:?}",