Reserve Manipulation Remediation
Overview
Related Detector: Reserve Manipulation
Using token.balanceOf(address(this)) in a price or share calculation lets anyone inflate the input by transferring tokens directly to the contract — no deposit call, no accounting update. The pool then computes swaps or shares off a balance the attacker controls. The remediation is to price off internally tracked reserves that only change through the contract’s own logic.
Recommended Fix
Before (Vulnerable)
function getPrice() public view returns (uint256) {
// A direct token transfer inflates this balance and the derived price.
return (token1.balanceOf(address(this)) * 1e18) / token0.balanceOf(address(this));
}
After (Fixed — tracked reserves)
uint256 public reserve0;
uint256 public reserve1;
// Reserves change only through deposit/withdraw/swap, never by a raw transfer in.
function getPrice() public view returns (uint256) {
return (reserve1 * 1e18) / reserve0;
}
function _sync(uint256 newR0, uint256 newR1) internal {
reserve0 = newR0;
reserve1 = newR1;
}
Any surplus balance beyond tracked reserves (a “donation”) is then simply ignored by pricing, removing the attack. If you must skim donations, do so explicitly through a controlled function.
Common Mistakes
- Tracking reserves for pricing but still using raw
balanceOfin a share or reward path elsewhere. - Syncing reserves from
balanceOfon every call, which re-imports the manipulable value. - Assuming fee-on-transfer or rebasing tokens keep
balanceOfand tracked reserves in lockstep — they do not.