# _recalfs should use earningof instead of balanceof to calculate fs

_reCalFs used balanceOf instead of earningOf, letting unwithdrawn sponsor earnings inflate fs and boost poolOwner score via dctStake.

- Payout: Contest reward
- Severity: medium
- Program: Goat
- Credited: cccz
- Disclosed: 2024-06-12
- Write-up quality: 6/10
- Original: https://cdn.cantina.xyz/reports/cantina_competition_goat_mar2024.pdf#3.2.2
- Library page: https://bugbountylibrary.com/r/cantina%3Agoat_mar2024%3A3.2.2
- Tags: domain:web3 impact:integrity kind:contest platform:cantina sev:medium tech:solidity technique:source-review vuln:business-logic vuln:reward-distribution

**Target:** Solidity staking/earning accounting contract (_reCalFs)
**Root cause:** _reCalFs computes fs from _eEarning.balanceOf, which can exceed earningOf when earnings are not yet withdrawn/updated; _maxEarningOf is based on earningOf, so the numerator is inconsistent with the cap.
**Steps:**
1. Observe _reCalFs uses `_eEarning.balanceOf(account_) + _voting.defenderEarningFreezedOf(account_)` as numerator with `_eEarning.maxEarningOf(account_)` as denominator.
2. Note normal paths call eEarning.update/withdraw first so balanceOf==earningOf.
3. Call `dctStake`, which invokes _reCalFs without update/withdraw, leaving balanceOf > earningOf.
4. Resulting calFs is inflated, raising poolOwner's fs score.
**How found:** Code reading: noticed numerator uses balanceOf while denominator/max uses earningOf, and that dctStake skips the update/withdraw that normally reconciles the two.
**Exploitation:** Stake via dctStake while unwithdrawn earnings remain in balanceOf, so _reCalFs computes an inflated fs for the poolOwner, increasing their score/rewards without corresponding earningOf.
**Insight:** When a ratio mixes two accounting views (balanceOf vs earningOf), check every caller for paths that skip the reconciliation step; stake/deposit entrypoints often bypass update/withdraw.
