BACK TO BLOG

Fuzzing a Solana Vault: Fee Shares Priced Against the Wrong Supply

Arsen ButenkoArsen Butenko·October 21, 2026·
Share X LinkedIn
Fuzzing a Solana Vault: Fee Shares Priced Against the Wrong Supply

A swap path in a Solana yield protocol priced fee shares against the wrong supply. Fee-paying swaps underpaid the protocol's fee recipients, and one fuzz property breaks on the pre-fix code.

This post walks through the flow, the property and where it breaks, the fix, and what our everyday fuzzing at Adevar Labs looks like.

The flow

We recently audited a Solana yield protocol built around vaults. Each vault holds a basket of yield-bearing assets. Users (LPs) deposit into a vault and receive shares that represent their part of it. They can swap between the basket's assets inside the vault, and to exit they redeem shares at the redemption price, the amount the vault pays out per share.

The protocol takes a cut of the yield and of every swap fee, and pays it to its fee recipient in newly minted shares. The yield cut is not minted right away. The vault records it as owed shares, which build up as yield accrues and stay unminted until the vault settles them. Owed shares are still a claim on the vault, and that matters for everything below.

The swap flow works like this:

  1. The user swaps one basket asset for another.
  2. The vault charges a fee and splits it. One part stays in the vault and raises the share price for LPs. The other part is the fee cut, which belongs to the fee recipient.
  3. The vault converts the fee cut into shares at the current share price and mints them to the fee recipient.

The property

For the fee recipient, the swap should guarantee one thing: the shares it receives are worth its fee cut in USD.

To turn that into a property, we take the shares minted to the fee recipient, price them at the redemption price, and compare the result with the fee cut:

rust
let value = shares_minted * redemption_price;

fuzz_assert!(
    value.abs_diff(fee_cut) <= redemption_price,
    "fee recipient underpaid"
);

In plain terms:

| value of minted shares - fee cut | <= value of one share

The two sides rarely match exactly. The vault mints only whole shares and rounds down. A fee cut worth 32.5 shares becomes 32 shares. So even correct code can lose up to just under one share on a swap.

The property allows a gap of up to one share. A bigger gap means the vault priced the shares wrong.

The violation

The fuzzer broke the property on the pre-fix code. Here is one of the crashing inputs:

text
[FUZZ_FINDING] FEE-SHARES-FAIR: fee recipient underpaid
  fee cut                 = 43,084,886
  value of minted shares  = 28,280,000
  gap                     = 14,804,886   (~11 shares)

The fee recipient was owed 43,084,886, in USD base units.

The shares it minted were worth 28,280,000. That is about 66% of the fee cut.

With one share worth 1,346,667, the vault minted 21 shares where roughly 32 were due.

Rounding explains one share. This gap was eleven.

What went wrong

The vault prices a share in two places, and they disagree. Redemption divides the vault value by minted shares plus owed shares. The swap divides by minted shares only and leaves owed shares out.

Take a vault worth 1,500, with 1,000 minted shares and 500 owed shares:

text
redemption: 1,500 / (1,000 + 500) = 1.00 per share
swap:       1,500 / 1,000         = 1.50 per share

The swap values each share at 1.50, so it mints fewer of them. For a fee cut of 32.5 it mints 21 shares instead of 32. At the redemption price those shares are worth 21, which leaves the fee recipient 11.5 short. We chose these numbers to mirror the crashing input: 21 shares minted where about 32 were due.

In general, the fee recipient receives minted / (minted + owed) of its fee cut, two thirds in this example. The missing third stays in the vault and accrues to LPs. The cost falls on the fee recipient on every fee-paying swap while owed shares exist, and it grows with owed shares. The more the vault owes its fee recipient, the less each new swap pays it.

We rated it Medium. Fee recipients lose part of their cut on every fee-paying swap while owed shares exist, and LPs collect the difference. It happens in normal use.

The fix

The team changed the swap to price fee shares the same way redemption does: against minted shares plus owed shares. With the same numbers, the swap now mints 32 shares for a fee cut of 32.5. The remaining gap is 0.5. That's just rounding, and the property holds.

How we fuzz at Adevar Labs

Every Solana audit we run starts with a manual review by two senior security researchers. Fuzzing builds on that review.

A fuzzer reports one thing: whether a property broke. That makes choosing the properties the most important step, and we always do it by hand. As our researchers review the code, they write down what must always hold. Each property becomes a tracked item with the rule in plain English and the rule as a formula. On this engagement we wrote 26 properties, and the fee-share property in this post is one of them.

Everything after that is semi-automated. Our AI workflow turns each property into a Crucible harness and sets up the fuzzing environment: program state, accounts, and the instruction sequences to explore. That work is mechanical, so we automate it and spend the saved time on the hardest parts of the codebase.

Scaling to 2,000 cores with Asymmetric Research

Once the properties exist, results depend on how many inputs the fuzzer can try. Locally we run four fuzzing workers, which is enough to break a property in a simple state. Deeper bugs need more, like a long call sequence or an unusual mix of amounts, prices and timings.

We worked with Asymmetric Research (AR) to scale this up. With AR, the same harness runs on up to 2,000 cores without changing a line. More cores try more inputs and call orders in the same time, so the campaign reaches states a laptop run misses.

The campaign runs for the whole engagement:

  • While we review the code, every property runs in the background.
  • When the team ships a fix, the same properties run against it.
  • If a fix brings a bug back, the property breaks again and we catch it in the fix review.

Lessons

Both share-price formulas look correct on their own. The bug only shows when you read them side by side and see that the denominators differ. If a vault prices shares in more than one place, every one of those places has to count the same supply. Leave out one term, as the swap did with owed shares, and someone gets paid in too few shares.

Two habits catch this:

  • Fuzzing - write one property per path that mints or burns shares, and price the result at the redemption price. A gap bigger than one share means that path prices shares wrong.
  • Reading every path that mints shares, side by side - deposit, swap, fees, yield. Different denominators? Ask why.

Building on Solana? Reach out at audits@adevarlabs.com before you ship. We will go over where you are and point you in the right direction, whether that is us or someone else.

SHIP SAFELY

Ready to secure your protocol?

BOOK A FREE CONSULTATION