Skip to main content
Sigvex

Reserve Manipulation Remediation

How to stop direct-transfer balance inflation by pricing off tracked reserves, not live balanceOf.

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.

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 balanceOf in a share or reward path elsewhere.
  • Syncing reserves from balanceOf on every call, which re-imports the manipulable value.
  • Assuming fee-on-transfer or rebasing tokens keep balanceOf and tracked reserves in lockstep — they do not.

References