nabilech.com

ResupplyFi Rekt: How a $4K Flash Loan Led to a $9.8M ERC-4626 Donation Attack

1. Introduction

In June 2025, ResupplyFi — a lending protocol integrated with the Convex and Yearn ecosystems — suffered a devastating exploit that drained approximately $9.8 million in under 90 minutes.

The attacker leveraged a classic ERC-4626 “donation attack” combined with a flawed vault implementation to manipulate the exchange rate of vault shares, turning a $4,000 flash loan into a multi-million-dollar heist.

This wasn’t a typical rug pull or price oracle hack — it was an accounting manipulation. By exploiting an uninitialized vault with low liquidity, the attacker inflated the value of their vault shares to nearly infinite levels and borrowed nearly the entire treasury.


Key Facts at a Glance
  • Date of exploit: June 2025
  • Protocol affected: ResupplyFi
  • Funds stolen: ~$9.8M
  • Attack type: ERC-4626 donation attack
  • Root cause: Mismanaged exchange rate calculation
  • Trigger: A tiny donation + 1 wei deposit
  • Attacker’s capital: ~$4K flash loan
  • Result: Vault drained, tokens swapped, funds laundered via Tornado Cash

Why This Exploit Matters

This attack highlights critical risks around vault-based lending protocols and ERC-4626 adoption:

  • Shows how low-liquidity vaults can be weaponized against themselves.
  • Demonstrates why donation attacks remain one of the biggest threats to yield vaults.
  • Reveals design oversights that even audited protocols can miss.
  • Emphasizes the importance of sanity checks and minimum liquidity safeguards.

In the following sections, we’ll break down how ResupplyFi works, what went wrong, and provide a step-by-step exploit walkthrough with math, code snippets, and transaction analysis.

2. Protocol Overview

ResupplyFi is a lending protocol built on top of Convex and Yearn strategies. It allows users to deposit stablecoins, receive vault shares in return, and use those shares as collateral to borrow other assets.

The exploit specifically targeted the wstUSR vault, an ERC-4626-compliant vault holding crvUSD tokens.


2.1. ERC-4626 Vault Basics

ERC-4626 is a standardized interface for tokenized vaults.
It manages two important components:

  • Assets → the tokens deposited into the vault (e.g., crvUSD).
  • Shares → ERC-20 tokens representing proportional ownership of the vault.

The exchange rate between assets and shares is calculated as:

exchangeRate = totalAssets / totalSupply;
  • totalAssets: Total tokens held by the vault.
  • totalSupply: Total minted shares representing ownership.

Example — Normal Behavior

Assume a vault starts empty, and a user deposits 1,000 crvUSD:

  • totalAssets = 1,000
  • totalSupply = 1,000
  • exchangeRate = 1,000 / 1,000 = 1

Each share is worth 1 crvUSD.
If another user deposits 500 crvUSD, they receive 500 shares — everything stays balanced.


2.2. ResupplyFi wstUSR Vault

The exploited vault, wstUSR, was designed to accept crvUSD deposits and mint vault shares accordingly.
However, a critical misconfiguration existed:

  • When the vault was deployed, there was no initial liquidity.
  • This allowed the first deposits to manipulate the exchange rate dramatically.

This is where the donation attack comes into play.


2.3. How ERC-4626 Donation Attacks Work

A donation attack happens when:

  1. An attacker donates tokens directly to the vault without minting shares.
  2. The vault’s totalAssets increases, but totalSupply stays constant.
  3. The exchange rate skyrockets, making future shares appear extremely valuable.

Then, with even a tiny deposit (e.g., 1 wei), the attacker receives overpriced shares that unlock huge borrowing power.


Illustrative Example

Initial vault state:

  • totalAssets = 0
  • totalSupply = 0

Step 1 — Donation:

  • Attacker transfers 2,000 crvUSD directly to the vault.
  • Now, totalAssets = 2,000, but totalSupply = 0 → the exchange rate calculation breaks.

Step 2 — Tiny Deposit:

  • Attacker deposits 1 wei (~0.000000000000000001 crvUSD).
  • Because of the broken math, the vault calculates that 1 wei = almost the entire vault value.
  • Attacker receives nearly 2,000 shares for free.

This vulnerability sat at the core of ResupplyFi’s implementation — and the attacker weaponized it perfectly.

3. Vulnerability Analysis — Deep Dive

The ResupplyFi exploit was a multi-million-dollar heist caused by a subtle logic flaw in the ERC-4626 vault implementation.
Unlike many DeFi hacks that rely on price oracle manipulation or reentrancy, this attack was a pure accounting exploit — exploiting the donation vulnerability in ERC-4626 vaults combined with misconfigured initial liquidity.

The attacker managed to mint vault shares worth millions using only a tiny initial deposit, then used those inflated shares as collateral to drain the protocol.


3.1. ERC-4626 Vault Mechanics

Before diving into the exploit, let’s understand how an ERC-4626 vault works.

An ERC-4626 vault manages two things:

  • Assets → the underlying ERC-20 tokens deposited (e.g., crvUSD).
  • Shares → ERC-20 tokens representing proportional ownership of the vault.

The relationship between assets and shares is defined by this formula:

exchangeRate = totalAssets / totalSupply;
  • totalAssets → how many tokens the vault actually holds.
  • totalSupply → total minted shares representing vault ownership.

How Deposits Work Normally
function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
    shares = (assets * totalSupply) / totalAssets;
    _mint(receiver, shares);
    asset.safeTransferFrom(msg.sender, address(this), assets);
}
  • A user deposits tokens (assets).
  • The vault mints shares proportional to the deposit size.
  • The deposit and shares maintain a 1:1 value mapping in healthy vaults.

Key Assumption

This formula assumes the vault has:

  • Sufficient liquidity (totalAssets > 0).
  • A nonzero share supply (totalSupply > 0).

If totalSupply == 0 or totalAssets == 0, the first depositor sets the initial exchange rate.


3.2. The Donation Attack Vector

The donation attack abuses a flaw in ERC-4626 vault math:
sending assets directly to the vault without minting shares.

How It Works
  1. Attacker donates tokenstotalAssets increases.
  2. totalSupply remains unchanged.
  3. The exchange rate spikes artificially.
  4. The next depositor gets massively overpriced shares.

Example of the Problem

Initial Vault State:

totalAssets  = 0
totalSupply  = 0
exchangeRate = undefined

Step 1 — Donation:

Attacker sends 2,000 crvUSD directly to the vault
totalAssets  = 2,000
totalSupply  = 0

Step 2 — Tiny Deposit:

shares = assets * totalSupply / totalAssets;
  • Since totalSupply == 0, most implementations special-case the first deposit: if (totalSupply == 0) { shares = assets; // 1:1 assumption }
  • But now, because totalAssets already has 2,000 tokens,
    depositing 1 wei results in 1 share
    but that 1 share represents almost the entire vault.

This is the core exploit vector.


3.3. Vulnerable Function in ResupplyFi

Here’s a simplified version of the vulnerable code from the wstUSR vault:

function deposit(uint256 assets, address receiver)
    public
    returns (uint256 shares)
{
    require(assets > 0, "Zero deposit");

    uint256 supply = totalSupply();
    uint256 balance = totalAssets();

    // Vulnerable share calculation
    shares = assets * supply / balance;

    _mint(receiver, shares);
    asset.safeTransferFrom(msg.sender, address(this), assets);
}

Why It’s Vulnerable
1. No Initial Liquidity Check
  • There was no requirement for the vault to start with a minimum locked liquidity.
  • The first depositor could effectively set any exchange rate they wanted.
2. Donations Skew totalAssets
  • ERC-20 tokens could be sent directly to the vault without minting shares.
  • This inflates totalAssets artificially.
3. Broken Exchange Rate
  • The formula assumes proportionality, but after a donation,
    shares minted no longer represent real value.

3.4. Mathematical Walkthrough of the Exploit

Let’s go step by step with real numbers to understand how the attacker manipulated the vault.


Step 1 — Initial Vault State
totalAssets  = 0
totalSupply  = 0
exchangeRate = undefined

Step 2 — Donation of 2,000 crvUSD

The attacker sends 2,000 crvUSD directly to the vault:

totalAssets  = 2,000
totalSupply  = 0

At this stage:

  • The vault now holds funds.
  • But no shares exist, so ownership is undefined.

Step 3 — Deposit 1 Wei

The attacker deposits 1 wei (≈ 1e-18 crvUSD):

shares = (1 wei * totalSupply) / totalAssets;
  • totalSupply == 0
  • So, the vault defaults to a 1:1 minting assumption.
  • The attacker receives 1 share.

Step 4 — Value Per Share Goes Sky-High

Now we have:

totalAssets = 2,000
totalSupply = 1
exchangeRate = 2,000 / 1 = 2,000 crvUSD per share

This 1 share is now worth 2,000 crvUSD in vault accounting terms.


Step 5 — Infinite Borrowing

ResupplyFi allowed borrowing against vault shares.
Because the exchange rate was inflated, the attacker’s tiny deposit unlocked near-infinite collateral power.

  • With just 1 wei deposited, they effectively controlled 2,000 crvUSD worth of collateral.
  • This collateral was used to borrow massive amounts from other lending markets.

3.5. Key Takeaways from the Vulnerability
  • ERC-4626 vaults are extremely vulnerable when:
    • There’s no minimum initial liquidity.
    • Direct donations aren’t handled properly.
  • Small math oversights can turn into catastrophic exploits.
  • Auditors must test donation edge cases explicitly.

4. Exploit Walkthrough

The attacker executed the exploit in four major phases:

  1. Acquiring temporary capital via a flash loan.
  2. Manipulating the exchange rate using a donation + 1 wei deposit trick.
  3. Borrowing against the inflated shares to drain protocol funds.
  4. Swapping and laundering stolen assets via decentralized exchanges and mixers.

We’ll now walk through each stage step by step.


4.1. Step 1 — Flash Loan Setup

The attacker started by borrowing ~$4,000 worth of crvUSD using a flash loan.
This capital was needed to seed the vault and make the initial donation.

Transaction Example (simplified pseudo-code):

function exploit() external {
    // Borrow $4k crvUSD from a lending pool
    flashLoan(4000e18, address(this), abi.encode("attack"));
}

The flash loan was critical because:

  • No upfront capital required.
  • Ensured atomic execution → if anything failed, the loan would revert.
  • Gave enough liquidity to perform multiple donation cycles.

4.2. Step 2 — Donation Attack + 1 Wei Deposit

After securing the flash loan, the attacker donated a large chunk of tokens directly to the wstUSR vault without minting shares:

IERC20(crvUSD).transfer(address(vault), 2_000e18);

This increased totalAssets but did not mint any shares.

Vault State After Donation
VariableBeforeAfter Donation
totalAssets02,000
totalSupply00
exchangeRateundefinedundefined

Then, the attacker performed a tiny deposit of just 1 wei:

vault.deposit(1, attacker);
Why 1 Wei Was Enough

The vault’s formula for shares was:

shares = assets * totalSupply / totalAssets;

However, since totalSupply == 0, the vault defaulted to:

shares = assets;

Thus:

  • The attacker deposited 1 wei.
  • Received 1 share.
  • But that 1 share was now worth 2,000 crvUSD according to vault accounting.

4.3. Step 3 — Inflating the Exchange Rate

Now the vault looked like this:

VariableValue
totalAssets2,000 crvUSD
totalSupply1 share
exchangeRate2,000 / 1 = 2,000

Effectively, 1 share ≈ $2,000.

But the attacker wasn’t done yet — they repeated this donation trick several times:

  1. Donate more tokenstotalAssets grows even higher.
  2. totalSupply stays low.
  3. The exchange rate skyrockets.

After a few cycles, the attacker pumped the exchange rate so high that a handful of shares represented nearly the entire vault.


4.4. Step 4 — Borrowing Against Inflated Shares

ResupplyFi allowed users to use wstUSR shares as collateral to borrow other stablecoins.
With the exchange rate artificially inflated, the attacker’s tiny deposit unlocked massive borrowing power.

lendingPool.borrow(
    collateral = address(wstUSR),
    amount = 9_800_000e18,
    onBehalfOf = attacker
);

Since the vault thought the attacker owned millions in collateral, the lending system let them drain the reserves.


4.5. Step 5 — Converting & Laundering Funds

After draining ~$9.8M worth of assets:

  • The attacker swapped crvUSD → ETH, USDT, DAI using Curve & Uniswap.
  • Split funds across multiple wallets.
  • Laundered most of it via Tornado Cash.

4.6. Attack Timeline
StepActionValue
1Flash loan taken~$4,000
2First donation2,000 crvUSD
3Tiny deposit1 wei
4Exchange rate spike1 share ≈ 2,000 crvUSD
5Borrow funds~$9.8M drained
6Launder fundsSwapped & sent via Tornado

4.7. Why This Worked
  • ERC-4626 donation vulnerability allowed manipulation of totalAssets.
  • No minimum initial liquidity meant the first depositor set the price.
  • Collateral borrowing logic trusted vault accounting blindly.
  • Atomic flash loan gave the attacker free capital and risk-free execution.

5. Root Cause & Post-Mortem

The ResupplyFi exploit wasn’t caused by a reentrancy bug, price oracle manipulation, or lack of access control.
It was fundamentally a mathematical design flaw in the ERC-4626 vault implementation, amplified by misconfigured initial liquidity and unsafe borrowing assumptions.


5.1. The Core Bug — ERC-4626 Donation Exploit

At the heart of the issue lies this seemingly simple formula used in the vault:

shares = assets * totalSupply / totalAssets;

This works only if:

  • totalSupply > 0
  • totalAssets > 0
  • No external asset transfers bypass the vault’s share-minting process.

But ResupplyFi didn’t enforce these constraints.


What Went Wrong ?
1. Missing Minimum Initial Liquidity
  • The vault started with zero assets and zero shares.
  • The first depositor effectively sets the price per share.
  • The attacker exploited this by donating tokens and making a tiny deposit to mint shares at a massively inflated rate.

2. Unhandled Donations

The vault didn’t account for direct token transfers.
An attacker could send assets directly to the vault without going through deposit():

IERC20(asset).transfer(address(vault), donatedAmount);
  • totalAssets increases.
  • totalSupply remains constant.
  • Exchange rate calculation breaks.

3. Blind Trust in ERC-4626 Accounting

The lending module relied entirely on the vault’s reported exchange rate.
It assumed:

collateralValue = shares * exchangeRate;

But if the exchange rate was manipulated, the lending system overestimated the collateral value,
allowing the attacker to borrow way beyond their real deposits.


4. No Sanity Checks

There were no checks on:

  • Minimum deposits.
  • Maximum exchange rate bounds.
  • Initial supply ratios.

A few lines of additional validation would have stopped this exploit entirely.


5.2. Why Audits Missed It

ResupplyFi’s contracts were audited, yet this bug slipped through.
Common reasons why:

1. ERC-4626 Is Complex
  • Many auditors assume the standard implementation is safe.
  • The donation edge case isn’t obvious unless explicitly tested.
2. Lack of Simulation-Based Testing
  • No chaos testing or fuzzing around early vault deposits.
  • Low-liquidity edge cases often get ignored in normal test scenarios.
3. Collateral Mispricing Was Overlooked
  • The audit focused on lending logic and access control.
  • The assumption: if ERC-4626 vault math is correct, collateral valuation must also be correct.
  • This assumption proved catastrophic.

5.3. How a Few Lines Could Have Prevented $9.8M Loss

Adding minimum liquidity locks in the vault would have blocked the exploit.

Fix 1 — Enforce Initial Liquidity
require(totalSupply > 0 || assets >= MIN_LIQUIDITY, "Insufficient initial deposit");
  • Forces the first depositor to deposit a minimum amount.
  • Ensures the initial exchange rate can’t be skewed.

Fix 2 — Block Donations
uint256 before = asset.balanceOf(address(this));
asset.safeTransferFrom(msg.sender, address(this), assets);
uint256 after = asset.balanceOf(address(this));
require(after - before == assets, "Unexpected asset transfer");
  • Ensures all vault assets come through the deposit function.
  • Prevents silent balance inflation.

Fix 3 — Sanity Check Exchange Rates
uint256 newExchangeRate = totalAssets * 1e18 / (totalSupply + shares);
require(newExchangeRate <= MAX_EXCHANGE_RATE, "Abnormal exchange rate");
  • Prevents extreme price manipulation.
  • Protects lending protocols from false collateral valuations.

5.4. Root Cause Summary
Root CauseImpact
No initial liquidity lockAttacker set fake price per share
Unhandled donationsInflated totalAssets silently
Blind trust in vault accountingBorrowing limits bypassed
Missing sanity checksNo defense against manipulation

5.5. Lessons Learned
  • ERC-4626 vaults are fragile — donation attacks are well-known and should always be mitigated.
  • Lending systems must independently verify collateral pricing instead of blindly trusting vault math.
  • Auditors should fuzz low-liquidity edge cases, especially during initialization.
  • Minimum liquidity locks + sanity checks should be standard best practices.

6. Security Lessons & Mitigations

The ResupplyFi hack highlights a key lesson in DeFi: even well-audited protocols are vulnerable to subtle mathematical and accounting flaws.
ERC-4626 vaults are convenient, but their safety depends on careful implementation, especially during initialization and low-liquidity states.


6.1. Minimum Initial Liquidity

Problem:
Without a minimum initial deposit, the first depositor can set the exchange rate, allowing manipulation.

Mitigation:
Enforce a minimum liquidity requirement on the first deposit:

uint256 constant MIN_LIQUIDITY = 1_000e18; // example: 1,000 tokens

function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
    if (totalSupply() == 0) {
        require(assets >= MIN_LIQUIDITY, "Insufficient initial deposit");
    }
    // rest of deposit logic
}
  • Prevents attackers from skewing share value with tiny deposits.
  • Ensures early vault deposits are meaningful and secure.

6.2. Prevent Direct Donations

Problem:
ERC-20 tokens sent directly to the vault inflate totalAssets without minting shares.

Mitigation:
Check that all vault assets arrive through controlled deposit functions:

uint256 balanceBefore = asset.balanceOf(address(this));
asset.safeTransferFrom(msg.sender, address(this), assets);
uint256 balanceAfter = asset.balanceOf(address(this));
require(balanceAfter - balanceBefore == assets, "Unexpected asset transfer");
  • Ensures totalAssets accurately reflects minted shares.
  • Protects exchange rate calculations from external tampering.

6.3. Sanity Checks on Exchange Rates

Problem:
Inflated exchange rates allow borrowing against overvalued shares.

Mitigation:
Implement bounds on exchange rates:

uint256 newExchangeRate = totalAssets * 1e18 / (totalSupply + shares);
require(newExchangeRate <= MAX_EXCHANGE_RATE, "Abnormal exchange rate");
  • Prevents massive overvaluation of vault shares.
  • Adds a failsafe against unexpected token donations or price spikes.

6.4. Independent Collateral Verification

Problem:
Lending protocols trusted the vault’s reported exchange rate blindly.

Mitigation:

  • Compute collateral independently in the lending module.
  • Cross-check vault price against on-chain oracles or TWAP (time-weighted average price).
  • Don’t assume vault shares always represent assets 1:1.
collateralValue = min(exchangeRate, oraclePrice) * shares;
  • Reduces risk of overborrow due to manipulated vault math.

6.5. Audit Recommendations

Auditors should focus on low-liquidity edge cases:

  • Test vault initialization with small deposits.
  • Simulate direct ERC-20 donations.
  • Evaluate the effect on exchange rates and borrowing power.
  • Include flash loan simulations to detect atomic exploitability.

6.6. Key Takeaways
LessonDescription
Minimum initial liquidityPrevents first depositor from setting fake prices
Prevent direct donationsKeeps totalAssets and totalSupply in sync
Exchange rate sanity checksStops extreme share valuation spikes
Independent collateral checksLending protocols shouldn’t blindly trust vault math
Edge-case auditingSimulate low-liquidity deposits, donations, and flash loans

The ResupplyFi donation attack proves that subtle mathematical bugs in standard interfaces like ERC-4626 can lead to multi-million-dollar losses.
Even minor oversights in vault initialization, asset handling, or exchange rate validation are exploitable in DeFi’s high-leverage environment.

By combining minimum liquidity enforcement, donation prevention, sanity checks, and independent collateral verification, developers can significantly reduce the risk of similar exploits.

7. Conclusion

The ResupplyFi hack is a textbook example of how subtle logic flaws in DeFi protocols can lead to catastrophic losses.
A small flash loan combined with a donation attack on a low-liquidity ERC-4626 vault enabled the attacker to drain nearly $9.8 million in under 90 minutes.


Key Takeaways
  1. ERC-4626 vaults are powerful but fragile
    • Standardized interfaces make vaults reusable, but assumptions about deposits and exchange rates can be weaponized.
  2. Initial liquidity matters
    • Low-liquidity vaults allow attackers to set arbitrary share prices, enabling abuse of lending protocols.
  3. Direct donations must be handled carefully
    • Any external asset transfer can break accounting and inflate collateral values.
  4. Sanity checks and minimum deposits are essential
    • Implement minimum initial deposits and upper/lower bounds on exchange rates.
  5. Collateral verification should be independent
    • Lending protocols must not blindly trust vault math; always cross-check with oracles or internal calculations.
  6. Audits must simulate edge cases
    • Test low-liquidity deposits, donation attacks, and flash loan scenarios to uncover subtle vulnerabilities.

Final Thoughts

The ResupplyFi exploit reinforces a critical lesson for DeFi developers and auditors:
Even small oversights in vault logic or initialization can be catastrophic.

By following best practices—minimum liquidity, controlled deposits, sanity checks, and independent collateral verification—protocol designers can mitigate these risks and protect users’ funds.

This case serves as a wake-up call for all ERC-4626 vaults: convenience and composability are powerful, but security must never be an afterthought.

The blog now provides a full, step-by-step, technical walkthrough:

  • Protocol overview
  • Vulnerability analysis with code snippets and math
  • Exploit reconstruction
  • Root cause post-mortem
  • Security lessons and mitigations
  • Conclusion with actionable takeaways

Key Resources on the ResupplyFi Exploit

1. Etherscan Transaction Details

While specific Etherscan links are not provided in the available resources, you can explore the transaction details related to the ResupplyFi exploit by searching for the contract addresses and transaction hashes associated with the incident on Etherscan.

2. Detailed Analyses and Reports
3. Community Discussions

Leave a Comment

Your email address will not be published. Required fields are marked *