Fee-on-Transfer / Deflationary-Token Accounting Mismatch
- Name: Fee-on-transfer / deflationary-token accounting mismatch
- Category: exploit pattern / token-integration failure / internal-accounting mismatch / reserve-desynchronization vulnerability
- Summary: These attacks happen when a protocol treats the nominal
tokenAmountInof a transfer as if it were the same thing as the assets actually received, then lets that bad receipt story flow onward into reserve publication, pricing, or claim issuance. Fee-on-transfer, burn-on-transfer, or otherwise non-standard tokens break the first assumption. The attacker uses the resulting gap between internal bookkeeping and real balances to manufacture fake reserves, distorted prices, or redeemable claims, then cashes that mismatch out against other assets. - What it is:
- A protocol integrates a token whose transfer semantics differ from vanilla ERC-20 assumptions.
- The protocol updates internal balances, reserve records, or accounting shares from the requested transfer amount rather than from the actual balance delta it received.
- Each transfer therefore pushes protocol bookkeeping away from reality.
- The attacker repeats the mismatch until the protocol’s price, reserve, or claim math becomes absurd.
- A sync / skim / gulp / oracle / withdraw path then converts the bookkeeping lie into real asset outflow.
- Typical preconditions:
- The protocol accepts arbitrary or weakly screened tokens.
- Internal accounting assumes
transfer/transferFromdelivers the full nominal amount. - Price or redemption logic reads cached reserves or internal balances rather than actual post-transfer balance deltas.
- The attacker can iterate the mismatch cheaply, often with flash-loaned capital.
- A public sync-style function, or some other accounting refresh path, lets the attacker choose when the mismatch is crystallized into solvency-critical math.
- Common vulnerable surfaces:
- AMMs that cache token reserves internally and trust nominal transfer amounts
- vaults or wrappers that mint shares against expected receipts rather than actual received balances
- lending or collateral systems that admit fee-on-transfer receipt assets without haircut or wrapper isolation
- bridge or custody adapters that credit deposits from requested amounts rather than observed receipts
- helper functions such as
gulp,skim,sync, or reserve-refresh paths that can be called at attacker-chosen times
- Exploit sequence:
- The attacker targets a pool or accounting module that accepts a fee-on-transfer or deflationary token.
- The attacker performs repeated transfers or swaps so the protocol’s internal balance for that token diverges from the amount actually sitting in the contract.
- The real token balance is pushed toward a dust state, or the accounting gap is otherwise made economically extreme.
- The attacker triggers a sync-style path that snaps pricing or accounting to the manipulated real balance at the most favorable moment.
- The attacker trades, redeems, or withdraws other assets against the distorted reserve or claim math.
- The protocol is left with depleted reserves because it honored accounting that the token’s transfer semantics had already invalidated.
- Variants:
- AMM reserve desync — internal reserves assume full token receipts, then spot pricing or swap math is weaponized against the mismatch.
- Dust-state payout loop — the attacker pushes a fee-charging token to near-zero balance so tiny nominal inputs burn away entirely while the protocol still pays out as if it received them.
- Share-mint overcredit — a vault mints claims from nominal deposits even though the underlying token delivered less.
- Bridge / custody overcredit — deposit accounting credits more than the contract actually received, leaving a latent hole to be cashed out later.
- Detection heuristics:
- Compare every token integration path against actual
balanceOfdeltas before and after transfers. - Ask whether any accounting variable can increase by
tokenAmountIneven if the token contract delivered less. - Stress-test fee-on-transfer, burn-on-transfer, rebasing, and other non-standard token behavior in pools that claim to be permissionless.
- Review public sync functions with adversarial timing in mind: what happens if the attacker decides exactly when reserves are refreshed?
- Probe dust states, because fee rounding often becomes pathological when balances and trade sizes approach the smallest unit.
- Compare every token integration path against actual
- Defenses:
- Base accounting on observed balance deltas, not on nominal transfer arguments.
- Maintain explicit token allowlists or hard-deny classes of non-standard tokens when downstream math assumes vanilla ERC-20 semantics.
- Isolate fee-on-transfer assets behind wrappers whose behavior is well understood instead of admitting them directly into shared solvency or pricing paths.
- Treat sync / skim / gulp style functions as security-sensitive accounting surfaces, not harmless housekeeping.
- Add invariant tests asserting that pricing, reserve, and share math remain valid even when tokens deliver less than requested.
What sound design looks like
- Every asset-admission path measures reality from observed balance deltas, not from user-supplied nominal amounts. The system credits deposits, mints shares, updates reserves, and advances accounting only after it can prove how many units actually arrived.
- Token-integration policy is explicit about semantic support. If downstream math assumes vanilla ERC-20 behavior, fee-on-transfer, rebasing, callback-bearing, or burn-on-transfer assets are either wrapped, haircutted, or rejected rather than admitted optimistically.
- Sync-style functions such as
gulp(),skim(), reserve refreshes, or lazy accounting snaps cannot be used by an attacker to decide when hidden accounting drift becomes price truth. - Shared pricing, solvency, and redemption paths either consume live balances directly or maintain cached balances with invariants strong enough to prove the cache cannot drift from custody reality.
Three questions reviewers should not collapse
Did the token call succeed?is only the outermost question. False-return, soft-fail, hook-bearing, or wrapper-mediated assets can already make that answer weaker than reviewers expect.How much actually arrived?is the decisive accounting question for this pattern. A fee-on-transfer path can report transfer success while still delivering less than the nominal amount.When may the protocol publish that receipt into shared economic truth?is the downstream question Balancer-style incidents expose. Even a correctly observed live balance can become dangerous if a latersync,gulp,skim, vault checkpoint, or reserve refresh lets an attacker choose when distorted custody reality becomes AMM price, redeemable share value, or bridge credit truth.- Strong review keeps those three questions separate: transfer semantics, receipt measurement, and reserve publication authority. Weak review tends to flatten them into one vague belief that
the transfer worked, which is exactly how nominal-input bugs survive into solvency logic. - The healthy routing habit is to ask which boundary broke first:
- receipt admission — did the protocol ever prove how many tokens actually arrived?
- reserve publication — who was allowed to publish that measured or mismeasured balance into shared pricing, share, or collateral truth?
- reserve spend — which downstream path was still willing to pay out, lend against, or redeem from that published state?
- Balancer STA is the canonical case because all three boundaries failed in sequence: nominal receipt was overcredited,
gulp()let the attacker publish dust-state reality at an attacker-chosen time, and swap paths immediately spent against the poisoned reserve.
Best concrete case studies
- gulp pool drain (2020-06-28) — the canonical AMM receipt-mismatch exemplar: a fee-on-transfer token pushed internal reserves away from real custody,
gulp()let the attacker decide when that drift became canonical price truth, and swap paths spent valuable reserves against effectively zero newly received STA. - Spartan Protocol current-balance liquidity-share inflation drain (2021-05-01) is the useful boundary contrast. Spartan also let observable balances outrank healthy accounting truth, but the sharper root cause there is not taxed-transfer receipt drift; it is balance-delta-provenance-failure — real balances were later misattributed as fresh LP backing.
Failure-prevention checklist
- Measure
balanceBefore/balanceAfterfor every supported token transfer path and credit only the observed delta. - Make token-admission assumptions explicit per module. If reserve math, vault shares, or bridge credits assume standard ERC-20 semantics, reject unsupported assets at the boundary instead of hoping downstream code tolerates them.
- Treat fee-on-transfer compatibility as a balance-sheet property, not a UX nicety. Review swaps, deposits, withdrawals, flash-loan callbacks, and rescue / sync helpers for nominal-versus-actual drift.
- Add invariant tests that total issued claims, cached reserves, and redeemable balances never exceed assets actually held after arbitrary transfer-fee behavior.
- Review receipt measurement and reserve publication as two separate ownership problems. The function that measures
balanceAfter - balanceBeforeshould not be silently overruled by a later helper that republishes stale or attacker-shaped balances as canonical price or reserve truth. - Review dust states and repeated small transfers. Rounding plus deflation often becomes most dangerous when balances approach zero.
- Gate or redesign public reserve-refresh helpers so attackers cannot choose the exact block where distorted live balances become authoritative pricing inputs.
- Where support is unavoidable, isolate non-standard tokens behind wrappers with clearly modeled receipt semantics and explicit monitoring for drift.
Audit prompts
- Which variables increase from
amountIn,amount, orsharesExpectedrather than from an observed post-transfer balance delta? - Which function first decides
this transfer succeeded, which function decidesthis much actually arrived, and which function decidesthis amount is now safe to publish into reserves, pricing, or redeemable claims? Are those decisions consistent, or can one helper launder another helper’s weaker assumption into economic truth? - If a token burns, taxes, or redirects part of every transfer, where does the missing amount show up in internal accounting?
- Can a public
sync,skim,gulp, vault harvest, or reserve-refresh path convert latent balance drift into solvency-critical pricing at attacker-chosen timing? - Do any mint, share-issuance, or bridge-credit paths assume the contract received the full nominal amount before checking custody reality?
- What happens when the managed token balance is pushed near dust and transfer-fee rounding dominates the economics?
- Are fee-on-transfer or otherwise non-standard tokens admitted into modules whose downstream math was only reviewed against vanilla ERC-20 behavior?
- Can aggregate withdrawable value, LP claims, or oracle-visible reserves exceed the assets actually custodied after a sequence of taxed transfers?
Lessons that should already be standard
transferFrom(amount)is not receipt evidence. Protocol accounting should treat nominal transfer arguments as requests, not facts.The transfer workedis still not a full accounting verdict. Call success, actual receipt amount, and reserve publication authority are different questions and should fail independently.- Permissionless asset support without semantics-aware accounting is usually just permissionless balance-sheet corruption.
- Housekeeping functions like
syncorgulpare treasury surfaces when they decide when accounting lies become pricing truth. - Non-standard token behavior rarely stays local. Once one module overcredits receipt, downstream pricing, collateral, redemption, and share math inherit the lie.
- Dust states deserve adversarial review. Many integrations look fine at normal sizes and break only when rounding and transfer fees dominate.
Where this pattern stops
-
Stay here when the decisive lie is
the protocol credited or priced against more tokens than it actually received, because transfer-time burns, taxes, or deflation made nominal input diverge from custody reality. -
Pivot to balance-delta-provenance-failure when the measured balance delta itself is real but the protocol later attributes that balance to the wrong payer, market, position, or claim. That family is about
receipt ownership drift, notreceipt amount drift. -
Pivot to interface-conformance semantic-compatibility failure when the deeper exploit surface is broader unsupported token behavior such as soft-failing
transferFrom, callbacks, rebases, or mutable token semantics rather than the specificnominal amount != actual receiptgap. -
Pivot to state-publication-authority-failure when the accounting mismatch already exists but the sharper preserved lesson is about who was allowed to publish stale, dust-state, or attacker-shaped balances into canonical reserve, oracle, or share-price truth. Balancer STA spans both notes, but the dominant root cause still begins at bad receipt measurement.
-
Pair with internal-mark-publication-quarantine-and-reserve-spend-separation when the design question is not only
how should the protocol measure actual receipt?but also how a latersync,gulp, or reserve-refresh helper should be prevented from turning already-bad custody reality into immediate reserve-spend authority. -
Pivot to asset-identity-aliasing-multi-entry-token-surface when the protocol received real value but confused two addresses or entry surfaces for the same economic asset. That is an identity problem, not a transfer-semantics problem.
-
High-signal incidents:
- gulp pool drain (2020-06-28) — the canonical AMM exemplar: a fee-on-transfer token, cached internal reserves, public
gulp()syncing, and repeated dust-state swaps that turned nominal STA input into real WETH, WBTC, LINK, and SNX outflows.
- gulp pool drain (2020-06-28) — the canonical AMM exemplar: a fee-on-transfer token, cached internal reserves, public
Internal linkages
- Positive-design companion: Actual-receipt accounting and token-semantics admission discipline — use that note when the sharper design question is how a token-admission boundary should prove actual receipt, contain weird token behavior, and keep later sync helpers from canonizing semantic drift into reserve truth.
- Publication-authority companion: Sync, harvest, and cache-write authority discipline — use that note when the transfer-semantics bug only became spendable because a public
sync,skim,gulp, or reserve-refresh helper was allowed to promote latent drift into canonical pricing or reserve truth. - Reserve-spend companion: Internal-mark publication quarantine and reserve-spend separation — use that note when the healthier follow-up is how a protocol should stage dust-state or attacker-shaped live balances through publication and activation so reserve-paying consumers do not instantly spend against them.
- Broader asset-integration family: interface-conformance semantic-compatibility failure — use that note when the preserved lesson is that the protocol admitted an asset whose semantics it never modeled safely; use this note when the sharp failure is specifically
nominal transfer amount ≠ actual received amount. - Best concrete case study: balancer-sta-deflationary-token-gulp-pool-drain-2020-06-28.
- Best boundary contrast on
real balance, wrong claim: spartan-protocol-current-balance-liquidity-share-inflation-drain-2021-05-01. - Adjacent pattern: balance-delta-provenance-failure — both families turn accounting drift into spendable value, but this note is for
bad receipt measurementand that note is forgood balances, bad attribution. - Adjacent pattern: state-publication-authority-failure — this note is for
nominal transfer amount already drifted from actual receipt; that note is for the later question of who was allowed to canonize stale, dust-state, or attacker-shaped balances into reserve truth once the drift already existed. - Adjacent pattern: solvency-check bypass — both families weaponize accounting drift, but fee-on-transfer exploits usually start at the token-integration boundary rather than inside a lending protocol’s helper path.
- Adjacent pattern: oracle manipulation — once reserve accounting is false, spot pricing becomes a manipulable oracle problem inside the protocol itself.
Control surface
-
The leverage sits in token admission policy, actual-versus-nominal balance accounting, sync-function exposure, and every place protocol math quietly assumes a token transfer delivered exactly what it asked for.
-
The bug is rarely just
supports weird tokens. It is that the protocol prices, credits, or pays out against assets it never truly received. -
Sources:
- https://medium.com/balancer-protocol/incident-with-non-standard-erc20-deflationary-tokens-95a0f6d46dea
- https://medium.com/1inch-network/balancer-hack-2020-a8f7131c980e
- https://diligence.security/blog/2020/11/token-interaction-checklist/
- https://www.certik.com/blog/little-pains-great-gains-balancer-defi-contract-was-drained
- https://github.com/balancer-labs/balancer-core/blob/140df49361a58e6c79b395964be98387702a7c0d/contracts/BPool.sol#L334-L340
- https://github.com/balancer-labs/balancer-core/blob/140df49361a58e6c79b395964be98387702a7c0d/contracts/BPool.sol#L423-L470
-
Last reviewed: 2026-08-03 UTC