# If redeem() is called twice between an account's _sync(), debt and earmarked of the account will be incorrectly updated

AlchemistV3.redeem() overwrites _redemptions[block.number] and lastRedemptionBlock, so multiple redemptions between an account's _sync() miscompute account.debt and earmarked.

- Payout: Contest reward
- Severity: medium
- Program: Alchemix
- Credited: KupiaSec
- Disclosed: 2025-07-15
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_alchemix_may2025.pdf#3.2.16
- Library page: https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.2.16
- Tags: domain:web3 impact:funds-loss kind:contest platform:cantina sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** AlchemistV3.sol redeem()/_sync() accounting
**Root cause:** _sync() only reads the last stored RedemptionInfo (lastRedemptionBlock) and _earmarkWeight, so intermediate redemptions between syncs are collapsed into one snapshot, losing per-redemption weight deltas.
**Steps:**
1. State: totalDebt=10000, cumulativeEarmarked=2000, account.debt=100, account.earmarked=20
2. Earmark 500 debt tokens (cumulativeEarmarked=2500)
3. Call redeem(600) (totalDebt=9400, cumulativeEarmarked=1900)
4. Earmark 400 (cumulativeEarmarked=2300)
5. Call redeem(500) (totalDebt=8900, cumulativeEarmarked=1800)
6. Call _sync(tokenId); expected debt=89, earmarked=18 but actual 88.25/17.25
**How found:** Code reading of redeem() setting lastRedemptionBlock and _redemptions[block.number], then tracing _sync()'s use of previousRedemption.earmarkWeight to notice only the last snapshot is consumed.
**Exploitation:** Repeated redeem() calls between an account's _sync() cause the account's debt and earmarked to be understated, letting the account's collateral/debt accounting drift from protocol totals.
**Insight:** When a sync function reconstructs state from a single 'last checkpoint' snapshot, check whether multiple state-changing events can occur between checkpoints; per-block overwrites of snapshot mappings silently drop intermediate deltas.
