$1,000,000 in security audit grants are live now, Apply here →

← Resource Hub

6 Critical DeFi Vulnerabilities From Real Audits

Updated 13 min read

Quick answer. Six DeFi vulnerabilities belong in every auditor's toolbox. They are sandwiched reward compounds, unbounded dividend loops, unchecked collateral swaps, gas manipulation via swap paths, blacklisted addresses blocking liquidations, and price impact that times order execution.

Your toolbox of findings decides how many new vulnerabilities you can uncover, in any kind of codebase. Knowing what should raise alarm bells makes a huge difference.

Below are six DeFi vulnerabilities from past Guardian audits, from a live session with security researchers. Each comes with its root cause, attack and fix.

Build a Findings Toolbox

A checklist copied from Twitter is a start. Real findings you understand explicitly are the next level, because you remember them.

When a new codebase puts you in a familiar scenario, you recognize it. That is scenario-based auditing, and each finding below comes from a scenario that recurs across codebases.

1. Reward Compounds That Can Be Sandwiched

The first finding came from a Key Finance review. Its root cause applies to many codebases.

There was no fee or lockup period on staking for a share of compounded rewards. A malicious actor could buy and stake right before the compound function ran.

They immediately accrue a portion of rewards meant for other stakers. Then they claim, unstake and sell right after the compound, stealing rewards from everyone else.

Why stepwise rewards are dangerous

The pattern is common. Value belongs to people who staked or deposited, and they share in it pro rata, much like an ERC-4626 vault.

A harvest or compound function adds rewards to the shared pot. Plotted over time, the value per share rises in steps: flat, then a jump each time the function is called.

Someone who staked for the whole period and someone who staked one block before the jump get the same reward per token.

So an attacker watches the mempool for the compound call. They front-run it with a stake and back-run it with an unstake, with no real exposure to the staked asset.

Every reward the attacker takes comes from long-term stakers.

If an honest user stakes 1 token and the attacker stakes 100 for one block, the attacker gets 100 times the rewards.

This is a vampire attack on yield. MEV bots would take nearly all the rewards risk-free, and normal stakers would earn almost nothing.

The fix: account for value smoothly

Remove the stepwise increase. Perpetuals protocols face the same issue: when a trader realizes a loss, LPs get a boost.

The solution there is to factor the traders' current PnL into the value of the LP's market token the entire time. Value then moves smoothly, and stepping in for one block gains nothing.

For the mechanics of front-running and back-running, see sandwich attacks.

2. Unbounded Loops in Dividend Payouts

This is a classic denial of service. A distributeDividend function loops over the length of a users array to pay each user.

Whenever you see a loop over users, suspect a DoS finding. You can add as many users to a DeFi protocol as you like, since they are just addresses.

Here, minting pushes the recipient onto users whenever their balance was zero. So an attacker mints one wei to thousands of fresh addresses and cheaply makes the list huge.

Distributing dividends then loops thousands of times and exceeds the block gas limit, so it never succeeds.

The fix: a points-per-share model

The solution matters more than the finding. Everything needed is already tracked in the total supply.

The goal of a dividend is to credit each token in the supply with a set amount. So track one global dividend per token instead of crediting users one by one.

Raising that value pays every holder at once. Each holder records the dividend per token at the moment they received their tokens.

A holder's dividends equal their balance times the global dividend per token, minus the value they recorded.

A worked example: distributing 5,000 tokens over a supply of 100 adds 50 to the dividend per token.

A holder of 10 tokens who recorded 100 now sees 150, so they are owed 10 × (150 − 100) = 500. One storage write replaced a loop over every user.

The rule: if anything is distributed to holders of a token and there is a for loop, there is an issue. Use a dividend-per-token or per-share model instead.

3. Collateral Swaps That Skip Solvency Checks

This finding came from IVX, an options protocol, though it needs no options knowledge.

The finding: users could call swapMargin to siphon funds from their portfolio well past the point of insolvency.

A user sells options contracts to the AMM and collects the premium in their portfolio. Then they repeatedly call swapMargin and sandwich themselves to extract nearly all the portfolio's value.

They keep their initial margin and the premium, with no margin left to exercise the contracts at expiry.

Repeated, this drains nearly all funds from the IVX LP contract, leaving the user to be liquidated while insolvent.

How the swap worked

Each user's portfolio contract holds their margin, which acts as collateral.

swapMargin swaps that collateral into another supported collateral token. Say USDC starts to depeg: a user can swap into DAI without closing and reopening every position.

The function checked that both the asset in and the asset out were supported collateral. It then swapped on Uniswap.

The minimum amount out was supplied by the caller, the portfolio owner. Nothing checked that the result kept the position from being liquidatable.

So the owner sets the minimum amount out to zero and front-runs their own swap on the same pool.

Looping this removes about 99% of the collateral from the portfolio.

The same flaw in a perpetuals protocol

Say a trader has 1,000 USDC of collateral and a position at −$100 PnL. Closing properly should return $900.

With this flaw, they pull all 1,000 USDC out and leave the protocol to cover the loss. Losing trades cost them nothing, and winning trades they close properly for profit.

The fix is a simple check after the swap: if the position is liquidatable, revert.

The heuristic: can users make themselves liquidatable?

This is similar in spirit to the Euler Finance exploit. Euler had a donate function that let users donate funds to the protocol.

In the same transaction, they could liquidate their own position and collect the liquidator fee. With a flash loan, they could build an artificially leveraged position and make it liquidatable at once.

The takeaway from both: never let a user force their own position into a liquidatable state. Combined with other features, that is usually a severe issue. For more, see liquidation vulnerabilities.

4. Unbounded Swap Paths and Gas Manipulation

This GMX V2 finding is titled "Unbounded swap path length."

Nothing capped the length of an order's swap path, which let malicious users make risk-free trades.

GMX now has a validateSwapPath check that enforces a maximum length.

Before it, a path could run ETH to USDC to BTC to DAI to DOGE to ETH and on for hundreds of hops. No single transaction can process that.

How GMX V2 executes orders

Users do not open or change positions directly. They create an order, and a keeper executes it later, in a separate transaction and block.

The two-step design addresses front-running. GMX V2 rewards trades that balance longs and shorts with positive price impact, and punishes trades that unbalance them with negative price impact.

Say longs total $900 and shorts $1,000. John's $100 long would balance them, so he should get positive price impact.

If Alice front-runs John with her own $100 long, she balances the market and takes the positive impact. John then moves it to $1,100 long against $1,000 short and gets negative price impact.

Done carefully, front-running could even push positions closer to liquidation.

The two-step design brings its own attack surface. An order gets the prices from the block in which it was created, not the block in which the keeper executes it.

If ETH is $1,000 at creation and $1,100 at execution, the order still executes at $1,000. The trader can close at once for a profit.

Normally this is harmless. Oracle latency is small, a few seconds on Arbitrum, so prices barely move. Orders also cannot be canceled for a period, which blocks small risk-free trades.

The attack

The unbounded swap path turns that small window into a huge one. The attacker needs two abilities:

  1. Make execution revert for as long as they like, so the keeper cannot execute the order.
  2. Flip a switch later, once prices favor them, so execution succeeds.

A huge swap path gives the first: it exceeds the block gas limit, so execution always reverts. The trick is to engineer the path to sit just over the limit, not far over it.

Say the block gas limit on Avalanche was about 8 million at the time, and the order needs about 8,050,000 gas to execute.

The callback gives the second ability, because the attacker controls the callback contract. Make it spend 100,000 less gas, and the order needs about 7,950,000 gas and fits in a block.

The full sequence:

  1. Create a market increase order while ETH is $1,000. Every execution attempt reverts.
  2. Wait, say until block 100, while ETH rises to $2,000. No position is open, but the order is banked at a $1,000 cost basis.
  3. Flip the callback to use less gas. The keeper executes the order at the creation-block price of $1,000.
  4. Create a decrease order. It executes at about $2,000, realizing a 100% gain with no risk.

Does gas variance between blocks make this unreliable? No: the max callback gas limit was 2 million, so the attacker can swing execution cost by close to that.

The proof of concept

The PoC built a swap path of a very particular length, 63 swaps, with a callback that used 1.9 million gas. It created the order, and execution reverted out of gas.

After waiting about 50 blocks, it flipped the switch in the attack contract so the callback used roughly 2 million less gas. The order executed, and a market decrease realized the profit.

The PoC made $100,000 in profit without risking any capital. At any point, the attacker could also have canceled the order and gotten their money back.

Why it is Critical

At the time of recording (2023), Guardian did not use a formal likelihood and impact matrix for rankings like this. It came down to intuition about the outcome.

This one is Critical because the attacker can manipulate the exchange to guarantee a profit. Done at scale, that drains the LPs, and risk-free money is a strong economic incentive.

Back every Critical and High finding with a PoC, which also heads off disputes over rankings. This class of bug is best called gas manipulation: using gas to manipulate the system.

5. Blacklisted Addresses Block Liquidations

This finding began as the missing piece of a more complicated attack, then proved a simpler issue alone. The same root cause has since turned up in other protocols.

Everyone expects ERC-20 transfers to work. Sending ETH can fail on a missing receive function or a deliberate revert, but an ERC-20 transfer goes to a token contract you probably trust.

But if the recipient is blacklisted for that token, the transfer reverts. That is a serious problem for protocols that must liquidate positions in a timely manner.

USDC is the most popular token with a blacklist. At the time of recording (2023), about 200 addresses were blacklisted for it across chains.

A blacklisted address is rare, but you may not need access to one. Often it only needs to exist, so you can point the system at it.

Liquidations are everywhere: by our estimate, about 75% of DeFi systems have a liquidate function. Asking whether a blacklisted address can stop a liquidation could be a free High finding in many contests.

The GMX finding

Addresses blacklisted for tokens such as USDC could not be liquidated whenever liquidation sent them leftover collateral in that token.

Blacklisted addresses could also make risk-free trades with market increase orders:

  1. Force the order's collateral swap to fail through low liquidity in a niche market.
  2. The order cannot be canceled, because cancellation tries to send the token the user is blacklisted for. It stays in the data store until liquidity is added.
  3. When prices are favorable, deposit liquidity into that market so the order goes through.

How the liquidation is negated

Take a position with $500 of USDC collateral and a $10,000 size. With 20x max leverage, it sits right at the limit.

Lose a single dollar and the position is liquidatable. Between that point and a $500 loss, it is liquidatable but solvent: the collateral still covers the losses.

Say the loss reaches $250. Liquidation sends $250 to the LPs and the remaining $250 back to the trader.

If the trader is blacklisted for USDC, that $250 transfer reverts, and the whole liquidation reverts with it.

Only once losses reach $500 is there nothing left to send back. Then liquidation succeeds, but the position is already insolvent.

The whole safety range before insolvency is negated, and positions can go insolvent at the LPs' expense.

An insolvent position also pays no liquidation fee, so liquidators have no incentive. The protocol or the LPs, who keep losing as the position worsens, have to step in.

GMX V2 has no liquidation fee and uses a liquidation keeper, so the protocol handles liquidations.

If a system's liquidation incentive is missing or not enforced, that is usually a High, possibly Critical, depending on the outcome.

Inside the USDC blacklist

The USDC implementation, FiatTokenV2_1, inherits Blacklistable, where a blacklister role can blacklist and unblacklist accounts.

Minting, transfer and transferFrom all revert if a blacklisted account sends, receives or calls them. Any interaction with a blacklisted account reverts.

USDC also blacklists its own contract address at initialization. So on any chain, at least one blacklisted address already exists.

Other centralized tokens may have blacklists too; see our weird ERC-20 tokens checklist.

6. Price Impact That Controls Order Execution

The last finding, also from GMX V2, is the one we are proudest of.

Start with a limit increase order for a long, with a trigger price of $5,000 for ETH. Then set the order's sizeDeltaUsd to exactly one wei less than the trigger price.

For a limit increase long, the trigger price is used as the max price. Execution uses the max price, since the order increases a long, and applies price impact from there.

Negative price impact moves the execution price up, and positive price impact moves it down. The order's acceptable price is set loosely enough not to interfere.

The increase logic computes the size in tokens by dividing sizeDeltaUsd by the execution price, and that division truncates.

With a size one wei below the price, the result rounds to zero. The position's size in tokens is zero, and position validation reverts with an empty position error.

// Simplified: sizeDeltaUsd = triggerPrice - 1
uint256 sizeDeltaInTokens = sizeDeltaUsd / executionPrice; // 0 while executionPrice > sizeDeltaUsd
if (sizeDeltaInTokens == 0) revert EmptyPosition();

So the order reverts until positive price impact pulls the execution price down by at least one wei. Price impact now decides whether the order executes.

Turning it into a risk-free trade

  1. Open the limit increase in a niche market with low open interest and little trading volume.
  2. Control the market's open interest balance. While waiting, keep it so the order gets negative price impact and keeps reverting.
  3. When prices are favorable, shift the open interest so the order gets positive price impact. The size no longer rounds to zero, and the order executes.

The swap path multiplies the gain. The attacker supplies a huge initial collateral amount, magnitudes more than the capital needed to rig open interest, and execute the swap path at outdated prices.

Finding it took deep context: about three months with the codebase in earlier reviews, plus a couple of weeks in that specific review.

DeFi Vulnerability Checklist

  • Can anyone stake just before a reward compound and exit just after it?
  • Does any distribution loop over a list of users that anyone can grow?
  • Can a user move or swap collateral without a solvency check afterward?
  • Can a user force their own position into a liquidatable state?
  • Are swap paths and other array inputs bounded, or can a user make execution revert now and succeed later?
  • Does any liquidation, cancellation or refund send a token that may have a blacklist?
  • Is the liquidation incentive present and enforced?
  • Can rounding plus price impact let a user decide when an order executes?

How Guardian Helps

A Guardian smart contract audit pairs an invariant suite around solvency and accounting with senior researchers who attack protocol logic with proof-of-concept depth.

With Vanguard, two independent teams review the same codebase and cross-check each other's findings.

Frequently Asked Questions

What is a reward compounding sandwich?

When a compound function raises share value in one step, an attacker stakes just before it and unstakes just after. They take rewards from long-term stakers with no real exposure. Smooth accounting removes the opportunity.

How can a blacklisted address stop a liquidation?

If liquidation sends leftover collateral back to the position owner, and the owner is blacklisted for that token, the transfer reverts. The position cannot be liquidated until it is insolvent, so LPs absorb the losses.

What is a gas manipulation attack?

The attacker makes keeper execution of their order revert, for example with a swap path sized just over the block gas limit. When prices favor them, they cut their callback's gas use and execute at stale prices.

How do you fix an unbounded loop in dividend payouts?

Use a dividend-per-token model. Distributing raises one global value, each holder stores the value at which they last settled, and dividends equal their balance times the difference.

Sources

Put Guardian's findings toolbox on your protocol

Guardian researchers bring years of critical DeFi findings, and the proofs of concept behind them, to every review.