Skip to content

fix(staking-vault): early-unlock penalty enforcement and storage TTL bump (#2035 #2039) - #2061

Merged
Olowodarey merged 2 commits into
Arena1X:mainfrom
Peace-001:fix/staking-vault-penalty-and-ttl
Oct 20, 2026
Merged

Olowodarey merged 2 commits into
Arena1X:mainfrom
Peace-001:fix/staking-vault-penalty-and-ttl

Conversation

@Peace-001

Copy link
Copy Markdown

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.rs and fees.rs contained correct helper functions (calculate_penalty, route_penalty_to_pool) but lib.rs had no request_unlock or withdraw entrypoint. 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.rs

  • Added net_after_penalty(amount, penalty) -> Result<i128, StakingError> — computes amount - penalty with checked_sub so an incorrect penalty configuration (penalty > amount) returns Overflow rather than underflowing.
  • Added #[cfg(test)] mod penalty_tests with 5 unit tests.

contracts/staking-vault/src/lib.rs

  • Added request_unlock(env, staker) — the single authorised exit path. Calls lock::calculate_penalty (checked arithmetic) when now < unlock_at, routes penalty to the reward pool via fees::route_penalty_to_pool, removes shares from the pool, and queues net_amount in position.pending_unlock_amount. There is no code path through which a staker can receive their tokens without this function running.
  • Added withdraw(env, staker) — enforces cooldown_period before transferring the queued pending_unlock_amount; removes the position key from persistent storage (allowing it to expire naturally — see [Contract] — Staking Vault Storage TTL Bump #2039).
  • Added claim_rewards(env, staker) — lets stakers collect accrued reward tokens without unlocking.

Penalty tests

Test Assertion
early_unlock_applies_correct_penalty 500 bps on 1_000_000_000 == 50_000_000 penalty; net == 950_000_000
normal_unlock_after_lock_applies_no_penalty penalty_bps=0 → penalty=0, net=full amount
penalty_cannot_be_bypassed 10_001 bps → InvalidPenaltyConfig; 10_000 bps → net=0
calculate_penalty_uses_checked_arithmetic near-i128::MAX does not panic
net_after_penalty_rejects_over_deduction penalty > amount → Overflow

Issue #2039 — Storage TTL Bump

Problem

lib.rs called extend_ttl(&key, LEDGER_BUMP_POSITION, LEDGER_BUMP_POSITION) — using the same value for both threshold and amount. Soroban's extend_ttl is 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.rs

  • Added LEDGER_THRESHOLD_POSITION = 259_200 (~15 days) — strictly less than LEDGER_BUMP_POSITION (~30 days) so extend_ttl always produces a net TTL extension.
  • Added bump_position(env, staker) — the single call site for position TTL management. Checks has(&key) before calling extend_ttl (no-op for absent keys, e.g. post-withdrawal) and uses the split threshold/amount.
  • Added #[cfg(test)] mod ttl_tests with 3 unit tests.

contracts/staking-vault/src/lib.rs

  • get_position_raw and set_position both delegate to bump_position instead of inlining extend_ttl with equal threshold/amount.
  • withdraw removes the position key with env.storage().persistent().remove (not set_position) so the entry is allowed to expire after full withdrawal.

TTL tests

Test Assertion
active_lock_storage_ttl_is_extended_on_access Entry still present after bump_position
fully_withdrawn_entry_is_allowed_to_expire Absent key stays absent; no panic
ledger_threshold_is_strictly_less_than_bump LEDGER_THRESHOLD_POSITION < LEDGER_BUMP_POSITION

Files Changed

File Change summary
contracts/staking-vault/src/lock.rs net_after_penalty helper; 5 penalty unit tests
contracts/staking-vault/src/storage_types.rs LEDGER_THRESHOLD_POSITION constant; bump_position helper; 3 TTL unit tests
contracts/staking-vault/src/lib.rs request_unlock, withdraw, claim_rewards entrypoints; get_position_raw/set_position use bump_position; 6 integration tests

Running the Tests

cd contracts/staking-vault
cargo test -- --nocapture

@drips-wave

drips-wave Bot commented Oct 18, 2026

Copy link
Copy Markdown

@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! 🚀

Learn more about application limits

@vercel

vercel Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
insight-arena-4rll Error Error Oct 20, 2026 8:09pm UTC

@Olowodarey
Olowodarey merged commit 18c4975 into Arena1X:main Oct 20, 2026
1 of 5 checks passed

This branch had an error being deployed

1 failed deployment
Preview — 7d8f3203 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Contract] — Staking Vault Storage TTL Bump [Contract] — Staking Vault Early Unlock Penalty

3 participants