Security Research

5 Smart Contract Bug Patterns We Found During Security Audits

Learn five recurring smart contract bug patterns behind Web3 exploits using real findings from Kann Audits protocol security reviews.

Five recurring Web3 exploit patterns mapped across accounting, authorization, oracle, external-call, and state-transition layers

Attackers rarely think about a function in isolation. They search for a state they can manipulate, an assumption the code does not enforce, and a transition that converts that mismatch into control or value.

Why the Same Vulnerability Patterns Keep Appearing

Smart contract code is deterministic, but protocol behavior is compositional. A locally correct function can become unsafe when balances come from another unit, a price can change inside the transaction, a callback arrives in an unexpected order, or an authorization is replayed in another domain.

Developers naturally focus on the happy path: the expected caller, the expected asset, the current oracle response, and the state sequence shown in product documentation. An attacker begins elsewhere. They ask which inputs are controllable, which state can be prepared beforehand, which external component can call back, and which invariant is assumed rather than enforced.

The five categories below group real findings from published Kann Audits security reviews. Not every referenced finding has Critical severity. The word critical in this article describes bug patterns capable of supporting major exploit paths when they appear in value-bearing systems.

01Controllable input
02Prepared state
03Broken assumption
04Reachable transition
05Security impact

Bug Pattern #1 — Broken Accounting Across Assets, Shares, or Time

Protocol accounting becomes dangerous when two values look comparable but represent different assets, decimal scales, time periods, or ownership claims. The compiler sees integers. The security model must preserve the economic meaning attached to each integer.

Why This Bug Happens

Teams often reuse one balance inside a loop, cache a cumulative value as if it were a period delta, or calculate shares from an asset balance that outsiders can modify. These errors pass ordinary unit tests because the happy path uses one asset, one reward cycle, or an initialized vault.

Real Audit Findings

Fluton's Critical C-04 used the scaled balance of one asset to overwrite borrowing limits for every supported asset. Depending on finalization order, capacity could collapse or become inflated enough to support under-collateralized borrowing against a pooled Aave position.

Mystic Finance's Critical reward-accounting finding subtracted the last reward amount from cumulative yield rather than comparing cumulative snapshots. After later cycles, the computed reward grew beyond the actual period yield, allowing early withdrawers to take value that should remain for others.

Manifest Finance's Critical first-deposit finding showed a third accounting boundary: direct token transfers could increase total assets without increasing share supply, making a later deposit calculate zero shares.

How an Attacker Thinks About It

The attacker's target is usually a desynchronization: assets versus shares, collateral versus debt, cumulative yield versus incremental reward, or native units versus scaled units.

“Which value can I change without updating the accounting value that is supposed to move with it?”

Potential Exploit

Potential attack path
01Manipulate one accounting input
02Paired value remains stale
03Protocol computes false capacity or ownership
04Attacker withdraws or borrows
05Other users absorb deficit

How to Prevent It

Give important values explicit semantic names and conversion functions. Store cumulative checkpoints separately from period deltas. Recompute cross-asset capacity from each asset's own balance and risk parameters. For vaults, enforce nonzero share output and defend initialization against donations and rounding.

Before deploying similar logic, verify

  • Are compared values denominated in the same asset, decimals, and time basis?
  • Can any token balance change outside protocol entry points?
  • Does operation order alter solvency or user ownership?
  • Do invariant tests conserve assets, debt, shares, and rewards across sequences?

What Auditors Look For

Security researchers annotate every unit, trace value through conversions, and build multi-step tests in which deposits, borrows, rewards, and withdrawals occur in different orders. They compare user-level accounting with aggregate protocol liabilities and look for rounding that consistently favors one side.

Bug Pattern #2 — Access Control That Exists but Does Not Cover the Real Entry Point

Authorization failures are not limited to a missing owner check. They also occur when a proxy bypasses the checked implementation, a callback trusts its arguments instead of its caller, or a migration function is assumed to be called only by an operator but remains public.

Why This Bug Happens

Complex systems distribute behavior across routers, facets, libraries, hooks, automation, and bridge callbacks. A developer may secure one layer while the deployed selector resolves through another. Comments and naming can also create a false sense that a function is internal to an operational flow even when its visibility permits any caller.

Real Audit Findings

Fluton's Critical AdminFacet finding showed privileged configuration functions without the existing onlyOwner modifier. A separate High finding found four gateway callback functions callable by any external address with attacker-selected plaintext amounts, despite those callbacks being intended for the TFHE Gateway.

The castr.fun report documented a Critical migrateLiquidity function with no access restriction. Any address could trigger migration before fees were collected, delete the position, and orphan accumulated user earnings.

How an Attacker Thinks About It

“Which sensitive function can I call directly, without following the workflow the developers had in mind?”

Potential Exploit

Potential attack path
01Enumerate deployed selectors
02Call privileged or callback function directly
03Supply attacker-controlled configuration or amount
04Protocol trusts caller
05Assets, fees, or availability are affected

How to Prevent It

Define a privilege matrix that maps every deployed selector to authorized callers. Enforce the restriction at the callable boundary, not only in a parent contract or intended upstream caller. For callbacks, validate both msg.sender and request identity before processing data.

Before deploying similar logic, verify

  • Can every privileged selector be mapped to an explicit role?
  • Do facets, proxies, hooks, and callbacks enforce authorization after dispatch?
  • Can operational functions be called out of sequence by a normal address?
  • Do negative tests cover every entry point on the deployed proxy address?

What Auditors Look For

Auditors compare the intended trust model with the actual ABI and deployment routing. They enumerate selectors, trace delegatecalls, inspect callback authentication, and attempt direct calls that bypass the documented sequence.

Bug Pattern #3 — Oracle and Pricing Assumptions That Attackers Can Control

A price is safe only if the source, pair, amount, freshness, and manipulation cost match the operation being protected. Slippage code can exist and still provide no protection when any one of those inputs is wrong.

Why This Bug Happens

Price integrations compress a large security model into a few returned numbers. Teams may quote the wrong token pair, calculate minimum output for only part of a swap, trust an instantaneous AMM price, or accept a response without proving it belongs to the current request.

Real Audit Findings

The castr.fun review found three distinct High-severity oracle and pricing failures. H-02 calculated swap protection using WETH against WETH and only part of the amount being exchanged. H-03 read the pool's current slot0 price, which an attacker could move in the same transaction with temporary liquidity. H-06 accepted an oracle fulfillment without checking its request ID, allowing a delayed response from a prior cycle to overwrite current winner state.

How an Attacker Thinks About It

“Can I make the protocol observe a price or response that is technically valid but wrong for this operation?”

Potential Exploit

Potential attack path
01Acquire temporary liquidity or stale response
02Move spot price or submit old fulfillment
03Protocol accepts unbound data
04Swap or state transition executes
05Attacker reverses manipulation and keeps value

How to Prevent It

Bind quotes to the exact input token, output token, and full amount. Use a manipulation-resistant reference such as an appropriately configured TWAP when the threat model requires it. Validate freshness, decimals, sign, sequencer status where applicable, and request identity. Fail safely when the oracle cannot provide enough history or confidence.

Before deploying similar logic, verify

  • Does the quote use the correct token direction and complete amount?
  • Can the observed price be moved within the same block or transaction?
  • Are response ID, timestamp, round, and freshness validated?
  • What happens when liquidity or observation history is insufficient?

What Auditors Look For

Researchers reconstruct the economic transaction around the oracle read, not just the interface call. They calculate who can influence the source, for how long, at what cost, and whether the protocol's slippage or state update uses the returned value consistently.

Bug Pattern #4 — Unsafe External Calls Before Internal State Is Final

A smart contract loses control of execution whenever it calls an untrusted recipient, token, hook, or integration. If the contract has not made the current action unrepeatable, the callee can exploit the unfinished state.

Why This Bug Happens

Code often reads naturally as calculate, transfer, then clean up. On-chain, transfer is not necessarily a terminal operation. ETH recipients can execute fallback logic, token standards can invoke hooks, and external integrations can call back through another public entry point.

Real Audit Finding

The castr.fun Critical claimRewards finding transferred ETH before burning the NFT and clearing reward accounting. During the callback, the attacker still owned the position and its claim data remained valid, enabling a second claim against assets pooled for all lockers.

How an Attacker Thinks About It

“When the contract calls me, which assumptions from the current operation are still true and reusable?”

Potential Exploit

Potential attack path
01Open one valid position
02Start claim
03Receive external callback
04Reenter before state is consumed
05Repeat withdrawal against shared pool

How to Prevent It

Apply checks-effects-interactions, use pull payments where practical, and make each entitlement idempotent before external control is transferred. Reentrancy guards help, but cross-function and cross-contract reentry still require correct invariants.

Before deploying similar logic, verify

  • Which calls can transfer execution to untrusted code?
  • Is the current claim consumed before every such call?
  • Can another entry point observe or mutate the same storage?
  • Do tests include malicious recipients and callback-enabled tokens?

What Auditors Look For

Auditors mark interaction boundaries, snapshot storage immediately before them, and enumerate every function callable during reentry. They test whether the same entitlement, position, or aggregate balance can be used twice.

Bug Pattern #5 — Incorrect State Transitions and Replayable Rights

Protocols frequently split one user action across multiple transactions, contracts, or chains. Security depends on each transition consuming the old right and creating the next one exactly once.

Why This Bug Happens

Developers reason about the intended sequence while code must defend every reachable sequence. A signature may be consumed on one chain but remain fresh on another. A position may be burned before a withdrawal claim is recorded. A report may be marked non-blocking even while the researcher retains a live escalation right.

Real Audit Findings

The RWA High cross-chain replay finding allowed the same borrow authorization to be accepted on multiple chains because the signed hash did not bind block.chainid. Mystic Finance's High finding burned the user's position without recording the withdrawal request required for later settlement.

N4A's Medium lifecycle finding removed a rejected report from blocking accounting before its dispute window expired. That created a race in which refund progression could begin while the researcher still had a valid escalation right.

How an Attacker Thinks About It

“Where does the system believe this right has ended, and where can I still exercise it?”

Potential Exploit

Potential attack path
01Obtain valid right or position
02Move one part of system forward
03Other domain remains stale
04Reuse right or bypass pending obligation
05Funds or recourse become inconsistent

How to Prevent It

Write explicit state machines with allowed transitions, terminal states, and invariants for every pending right. Bind signatures to their complete domain. Make irreversible effects atomic with creation of the next entitlement, and preserve blocking status until every dispute or cancellation window is truly closed.

Before deploying similar logic, verify

  • Can the same right be exercised in another chain, contract, or phase?
  • Does every irreversible effect create the next durable entitlement?
  • Are dispute, timelock, refund, and cancellation states mutually consistent?
  • Have reordered, repeated, delayed, and interrupted transitions been tested?

What Auditors Look For

Security researchers draw the state graph, identify who can trigger each edge, and compare storage updates across every component that represents the same right. They test time boundaries, retries, duplicate messages, cross-chain domains, and failure paths that interrupt the intended sequence.

From Vulnerable Code to Verifiable Remediation

Major Web3 exploits often begin with a small logic mistake that becomes financially meaningful only after an attacker composes it with liquidity, transaction ordering, a callback, or another contract's behavior. That is why a protocol audit has to reason about the complete system rather than scan functions in isolation.

A strong smart contract audit turns a suspected pattern into a reproducible path, assesses impact against the actual architecture, and documents a remediation that restores the broken invariant. Fix verification then checks the submitted change against the original vulnerability and adjacent logic.

The published findings referenced here provide evidence of what was reviewed and what the reports recorded. They do not prove that later code or deployments remain unchanged, and they do not justify claims about hypothetical dollar losses. Credible blockchain security work keeps those boundaries explicit.

01Vulnerable code
02Security audit
03Validated finding
04Remediation
05Fix verification
06Documented outcome

Primary references

Sources and further reading

A note on scope: Security reviews reduce uncertainty within a defined code and architecture scope. They do not guarantee that every vulnerability has been found or cover changes made after review.

Back to all research

Building a protocol?

Talk to a Security Researcher

Kann Audits performs expert security audits across smart contracts, protocol architecture, economic logic, integrations, and surrounding infrastructure.