Full Report
Belt Finance has a strategy token. This represents shares within the pool of assets. Each token is given out proportionally (pro rata) for assets put into the strategy contract. The strategy token is an interest baring asset as well. To understand the bug, we need to understand the withdrawal flow of the contract. There are two main variables for keeping track of funds: balanceSnapshot and wantLockedInHere. wantLockedInHere is the balance of the contract not being put to work to generate yield on the assets. balanceSnapshot holds the balance of the contract. When calling withdrawal, two paths can be hit. First, if the contract has enough funds in wantLockedInHere it will send this. Otherwise, it will liquidate the yield-generating asset and decrease the value of balanceSnapshot. When making a withdrawal from the contract directly, instead of through a different contract, there is a double counting bug that occurs. In particular, both balanceSnapshot and wantLockedInHere will be subtracted from. Why does this happen when calling directly? There's an if statement that fails to actually NOT withdraw the money but update the state variables. By making these variables very small, the contract has much MORE assets than it believes. Because the perceived value is so low, the attackers amount of shares appears to be much higher than it actually is. Now, when the attacks deposits money again, the contract will mint too many shares because of how low the balance appears to be. A attacker could call earn to get the real value of the contract. Practically, this means that this attack can be performed multiple times. Finally, an attacker calls withdraw to claim all of the shares they have earned from the contract. They now have more money than what they started with. Calling contracts in weird ways causes problems! A great bug find for a 1 million dollar payout.
Analysis Summary
# Vulnerability: Belt Finance Strategy Double-Counting Logic Error
## CVE Details
- **CVE ID:** N/A (Smart contract vulnerabilities often do not receive standard CVE identifiers; identified via Immunefi bug bounty program).
- **CVSS Score:** 9.1 (Critical - estimated based on $60M funds at risk)
- **CWE:** CWE-682: Incorrect Calculation; CWE-841: Improper Enforcement of Behavioral Workflow.
## Affected Systems
- **Products:** Belt Finance
- **Versions:** Alpaca Strategy Implementation (specifically `StrategyAlpacaImpl.sol`)
- **Configurations:** Contracts utilizing `balanceSnapshot` and `wantLockedInHere` state variables for share calculation on Binance Smart Chain (BSC).
## Vulnerability Description
The vulnerability is a logic error in the withdrawal flow of the strategy contract. The contract tracks funds using two variables: `wantLockedInHere` (idle assets) and `balanceSnapshot` (yield-bearing assets).
The flow intended to only subtract from `balanceSnapshot` if assets were liquidated from the yield-generating pool. However, the code was written to subtract the withdrawal amount from `balanceSnapshot` regardless of whether the funds came from the idle balance or the yield pool. This resulted in a "double counting" bug where both internal accounting variables were decreased. By repeatedly calling the withdraw function directly, an attacker could artificially deflate the `balanceSnapshot` to a near-zero value. Because share minting is calculated based on total perceived assets, a subsequent deposit would grant the attacker a disproportionately high number of strategy shares, allowing them to drain the pool.
## Exploitation
- **Status:** PoC available (Submitted via Whitehat; not exploited in the wild).
- **Complexity:** Medium
- **Attack Vector:** Network (Smart Contract Interaction)
## Impact
- **Confidentiality:** None
- **Integrity:** High (Manipulation of internal state variables and share distributions).
- **Availability:** High (Potential drain of $60M in BNB).
## Remediation
### Patches
- The bug was patched by moving the `balanceSnapshot` update inside the conditional `if` block. This ensures the snapshot is only updated when a withdrawal from the yield-generating source actually occurs.
- **Fixed Code Logic:**
solidity
if(_wantAmt > wantLockedInHere()) {
balanceSnapshot = balanceSnapshot.sub(_wantAmt.sub(wantLockedInHere()));
_withdraw(_wantAmt.sub(wantLockedInHere()));
}
### Workarounds
- No manual workarounds were provided for users; the protocol team applied the fix directly to the smart contract logic.
## Detection
- **Indicators of Compromise:** Unusual volume of small, direct withdrawals followed by large deposits and immediate full withdrawals.
- **Detection Methods:** Monitor for discrepancies between `balanceSnapshot` totals and the actual underlying tokens held in the yield-generating sub-protocols.
## References
- [Official Immunefi Bugfix Review](https://medium.com/immunefi/belt-finance-logic-error-bugfix-review-largest-ever-bounty-payout-39308a158291)
- [Vulnerable Code Snippet - GitHub](https://github.com/BeltFi/belt-contract/blob/b45069e946dfe19418ce49637b3590b0f3e5a217/contracts/bsc/earnV2/strategies/alpaca/StrategyAlpacaImpl.sol#L166)