Repository navigation
fix(staking-vault): early-unlock penalty enforcement and storage TTL bump (#2035 #2039) - #2061
Merged
Olowodarey merged 2 commits intoOct 20, 2026
Merged
Conversation
|
@Peace-001 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch had an error being deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2035
Closes #2039
Fixes two independent staking-vault correctness issues in a single PR because they share the same three source files and a single test run validates both.
Issue #2035 — Early-Unlock Penalty Enforcement
Problem
lock.rsandfees.rscontained correct helper functions (calculate_penalty,route_penalty_to_pool) butlib.rshad norequest_unlockorwithdrawentrypoint. A staker could exit a locked position without any penalty being applied or even computed — the bypass was total because the enforcement path simply didn't exist.Fix
contracts/staking-vault/src/lock.rsnet_after_penalty(amount, penalty) -> Result<i128, StakingError>— computesamount - penaltywithchecked_subso an incorrect penalty configuration (penalty > amount) returnsOverflowrather than underflowing.#[cfg(test)] mod penalty_testswith 5 unit tests.contracts/staking-vault/src/lib.rsrequest_unlock(env, staker)— the single authorised exit path. Callslock::calculate_penalty(checked arithmetic) whennow < unlock_at, routes penalty to the reward pool viafees::route_penalty_to_pool, removes shares from the pool, and queuesnet_amountinposition.pending_unlock_amount. There is no code path through which a staker can receive their tokens without this function running.withdraw(env, staker)— enforcescooldown_periodbefore transferring the queuedpending_unlock_amount; removes the position key from persistent storage (allowing it to expire naturally — see [Contract] — Staking Vault Storage TTL Bump #2039).claim_rewards(env, staker)— lets stakers collect accrued reward tokens without unlocking.Penalty tests
early_unlock_applies_correct_penaltynormal_unlock_after_lock_applies_no_penaltypenalty_cannot_be_bypassedInvalidPenaltyConfig; 10_000 bps → net=0calculate_penalty_uses_checked_arithmetici128::MAXdoes not panicnet_after_penalty_rejects_over_deductionOverflowIssue #2039 — Storage TTL Bump
Problem
lib.rscalledextend_ttl(&key, LEDGER_BUMP_POSITION, LEDGER_BUMP_POSITION)— using the same value for boththresholdandamount. Soroban'sextend_ttlis a no-op when the current TTL already exceeds the threshold, so a position written shortly before the call could go un-bumped, eventually expiring while the lock was still active.Fix
contracts/staking-vault/src/storage_types.rsLEDGER_THRESHOLD_POSITION = 259_200(~15 days) — strictly less thanLEDGER_BUMP_POSITION(~30 days) soextend_ttlalways produces a net TTL extension.bump_position(env, staker)— the single call site for position TTL management. Checkshas(&key)before callingextend_ttl(no-op for absent keys, e.g. post-withdrawal) and uses the split threshold/amount.#[cfg(test)] mod ttl_testswith 3 unit tests.contracts/staking-vault/src/lib.rsget_position_rawandset_positionboth delegate tobump_positioninstead of inliningextend_ttlwith equal threshold/amount.withdrawremoves the position key withenv.storage().persistent().remove(notset_position) so the entry is allowed to expire after full withdrawal.TTL tests
active_lock_storage_ttl_is_extended_on_accessbump_positionfully_withdrawn_entry_is_allowed_to_expireledger_threshold_is_strictly_less_than_bumpLEDGER_THRESHOLD_POSITION < LEDGER_BUMP_POSITIONFiles Changed
contracts/staking-vault/src/lock.rsnet_after_penaltyhelper; 5 penalty unit testscontracts/staking-vault/src/storage_types.rsLEDGER_THRESHOLD_POSITIONconstant;bump_positionhelper; 3 TTL unit testscontracts/staking-vault/src/lib.rsrequest_unlock,withdraw,claim_rewardsentrypoints;get_position_raw/set_positionusebump_position; 6 integration testsRunning the Tests