# Unbond_instant removes incorrect amount of shares

Acala's unbond_instant passes fee-deducted final_amount to OnUnbonded, leaving residual shares that keep earning rewards and become stuck.

- Payout: Contest reward
- Severity: medium
- Program: Acala Network
- Credited: TheSchnilch
- Disclosed: 2024-05-02
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-03-acala-findings/issues/24
- Library page: https://bugbountylibrary.com/r/c4%3A2024-03-acala%3AM-03
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:rust technique:source-review vuln:accounting-error vuln:business-logic vuln:precision-loss

**Target:** Earning pallet unbond_instant (Substrate/Rust)
**Root cause:** unbond_instant calls T::OnUnbonded::happened with final_amount (amount minus fee) instead of change.change, so fewer shares are removed than were bonded.
**Steps:**
1. Bond 100 tokens via Earning::bond(ALICE, 100); OnBonded receives change.change=100.
2. Call Earning::unbond_instant(ALICE, 100); fee_ratio applied, final_amount=90.
3. OnUnbonded::happened receives 90, removing only 90 shares.
4. 10 shares remain in reward system, still accruing rewards and unrecoverable.
5. Reproduce with cargo test poc -- --nocapture after adding println! in bond and unbond_instant.
**How found:** Code reading of unbond_instant noticed OnUnbonded::happened was passed final_amount rather than change.change, then verified via println! instrumentation and a unit test.
**Exploitation:** Any user repeatedly calling unbond_instant leaves residual shares (up to 20% fee) that continue earning rewards and cannot be removed, diluting rewards for honest bonders.
**Insight:** In share-based staking, check that the share-accounting hook receives the gross bonded amount, not the fee-netted payout; fee deduction and share removal must use different values.
