Skip to content

fix: implement granular balance tracking for locked fallback tokens#309

Merged
hman38705 merged 1 commit into
solutions-plug:mainfrom
Fidelis900:fix/issue-37-token-fallback-accounting
Mar 27, 2026
Merged

fix: implement granular balance tracking for locked fallback tokens#309
hman38705 merged 1 commit into
solutions-plug:mainfrom
Fidelis900:fix/issue-37-token-fallback-accounting

Conversation

@Fidelis900

Copy link
Copy Markdown
Contributor

Title: fix: implement granular balance tracking for locked fallback tokens

Body:

Summary

Fixes #37 — Track Locked Balances during Token Fallback

When a governance token doesn't support balance_at snapshots, the fallback path locks tokens in the contract. The
LockedBalance key (per-user accumulator) was already being written correctly in cast_vote, but unlock_tokens was
reading the transfer amount from LockedTokens.amount instead — which gets overwritten on vote revision rather than
accumulated. This meant a user who revised their vote could have a stale LockedTokens.amount, and more critically,
nothing prevented the first caller from withdrawing the full contract balance.

Root Cause

rust
// BEFORE — uses LockedTokens.amount (overwritten on revision, not accumulated)
token_client.transfer(&e.current_contract_address(), &voter, &locked.amount);

LockedTokens.amount is set to weight on each vote call. If a user revises their vote, the struct is overwritten with
only the latest weight. LockedBalance is the correct source because it accumulates across revisions and is strictly
per-user.

Changes

  • voting.rs: unlock_tokens now reads the withdrawal amount from DataKey::LockedBalance(market_id, voter) instead
    of LockedTokens.amount. Returns BetNotFound if the balance is zero or missing, preventing any withdrawal without a
    corresponding lock.
  • test_snapshot_voting.rs: Added test_locked_balance_prevents_pool_drain — two users lock 3000 and 7000 tokens
    respectively via the fallback path, then each unlocks independently. Asserts each receives exactly their own amount
    and the contract ends at zero balance.

Security Impact

Without this fix, the first user to call unlock_tokens after a market resolves could drain the entire contract token
balance (sum of all locked tokens), leaving other voters unable to recover their funds.
closes #145

- unlock_tokens now reads the transfer amount from LockedBalance
  (the per-user accumulator) instead of LockedTokens.amount
- LockedTokens.amount was overwritten on vote revision, making it
  unreliable as the withdrawal source; LockedBalance correctly
  accumulates across revisions
- Each user can only withdraw exactly what they locked, preventing
  early claimers from draining tokens belonging to other voters
- Add test_locked_balance_prevents_pool_drain: two users lock 3000
  and 7000 respectively; asserts each receives only their own amount
  and contract balance reaches zero

Closes solutions-plug#37
@drips-wave

drips-wave Bot commented Mar 27, 2026

Copy link
Copy Markdown

@Fidelis900 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

@hman38705 hman38705 merged commit c064b73 into solutions-plug:main Mar 27, 2026
2 of 13 checks passed
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.

Track Locked Balances during Token Fallback

2 participants