Resupply Zero-Rate Borrow
Exploit Transactions
0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3Victim Addresses
0x6e90c85a495d54c6d7e1f3400fef1f6e59f86bd6Ethereum0xcb7e25fbbd8afe4ce73d7dac647dbc3d847f3c82Ethereum0x01144442fba7adccb5c9dc9cf33dd009d50a9e1dEthereumLoss Breakdown
Similar Incidents
Bao Donation Borrow Exploit
38%Abracadabra xSUSHI stale-rate exploit
35%Balancer bb-a-USD stale-rate exploit
34%Conic crvUSD Oracle Exploit
33%EFVault Withdraw Under-Burn
32%MahaLend Liquidity Index Inflation
31%Root Cause Analysis
Resupply Zero-Rate Borrow
1. Incident Overview TL;DR
An unprivileged attacker exploited Resupply Finance in Ethereum block 22785461 by manipulating the collateral price of a fresh Curve ERC4626 vault that Resupply accepted as collateral. Resupply inverted that vault price as 1e36 / oraclePrice; once the oracle price exceeded 1e36, integer division floored the pair exchange rate to zero. With exchangeRate == 0, the solvency path stopped charging any debt against collateral, so the attacker posted 1 vault share wei and borrowed 10,000,000 reUSD. The borrowed reUSD was then sold through public Curve liquidity, realizing a multi-million-dollar profit.
2. Key Background
The exploited Resupply pair at 0x6e90c85a495d54c6d7e1f3400fef1f6e59f86bd6 uses the collateral vault 0x01144442fba7adccb5c9dc9cf33dd009d50a9e1d and the oracle 0xcb7e25fbbd8afe4ce73d7dac647dbc3d847f3c82. The verified oracle is a minimal ERC4626 oracle: it prices the vault as convertToAssets(1e18) with no sanity check on supply, donation sensitivity, or maximum price bound. The verified Curve vault is donation-inflatable because its asset accounting includes the controller's borrowed-token balance plus controller debt, so a direct donation to the controller increases totalAssets() without minting shares.
Verified oracle logic:
function getPrices(address _vault) external view returns (uint256 _price) {
_price = IERC4626(_vault).convertToAssets(1e18);
}
Verified vault accounting:
def _total_assets() -> uint256:
self.controller.check_lock()
return self.borrowed_token.balanceOf(self.controller.address) + self.controller.total_debt()
The vault also uses DEAD_SHARES = 1000, which still leaves first-mint pricing highly sensitive when supply is effectively zero.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a compositional pricing failure. Resupply assumed the vault oracle output would always be safely invertible, but the oracle trusted an ERC4626 share price that can be inflated by donations to the vault controller. After a 2,000 crvUSD donation and a 1-share mint, the vault's convertToAssets(1e18) rose above 1e36. ResupplyPairCore then inverted that value with integer division and stored exchangeRate = 0 instead of rejecting the condition. The downstream solvency computation multiplied borrower debt by this zero exchange rate, so any nonzero collateral looked solvent. That allowed the attacker to mint the full reUSD borrow limit against effectively worthless collateral.
The critical invariant was: borrowers with nonzero debt must always face a strictly positive collateral-per-debt exchange rate in the LTV calculation. The code-level breakpoint was Resupply's exchange-rate update path, which accepted an oversized oracle price and silently floored it to zero.
Relevant verified pair logic:
_exchangeRate = 1e36 / IOracle(exchangeRateInfo.oracle).getPrices(address(collateral));
...
ltv = (((_borrowerAmount * _exchangeRate) / EXCHANGE_PRECISION) * LTV_PRECISION) / _collateralAmount;
4. Detailed Root Cause Analysis
The attack started from a clean pre-state immediately before block 22785461: the pair had zero collateral and zero debt, and the vault had zero total supply. The attacker flash-borrowed 4,000 USDC from Morpho and swapped it into 3,999.250024417328922292 crvUSD through the public crvUSD/USDC Curve pool.
The next step created the pathological vault price. The attacker transferred 2,000 crvUSD directly to the vault controller 0x89707721927d7aaeeee513797A8d6cBbD0e08f41, then minted exactly one vault share for 2.000000000000000001 crvUSD. The trace and validator reproduction both show the resulting deposit event:
emit Deposit(sender: Exploit, owner: Exploit, assets: 2000000000000000001, shares: 1)
At that point the controller held 2002000000000000000001 crvUSD while total share supply was only 1, so the vault reported an enormous assets-per-share value. The validator run observed:
CurveLend Vault::convertToAssets(1000000000000000000) -> 2000000000000000000001998001998001998
That value is greater than 1e36, which is the inversion bound implicit in Resupply's 1e36 / oraclePrice formula. When the attacker deposited the single share as collateral, Resupply updated the pair exchange rate and emitted:
emit UpdateExchangeRate(exchangeRate: 0)
Once exchangeRate became zero, the borrow path accepted the position as solvent. The same trace then emitted:
emit Borrow(_borrower: Exploit, _receiver: Exploit, _borrowAmount: 10000000000000000000000000, _sharesAdded: 10000000000000000000000000, _mintFees: 0)
The attacker received 10,000,000 reUSD, swapped it through the public reUSD/scrvUSD pool, redeemed scrvUSD into crvUSD, swapped the proceeds back to USDC, repaid the 4,000 USDC flash loan, and retained profit.
5. Adversary Flow Analysis
The adversary-controlled EOA was 0x6d9f6e900ac2ce6770fd9f04f98b7b0fc355e2ea, and it deployed the helper contract 0x151aa63dbb7c605e7b0a173ab7375e1450e79238 in the same transaction. The entire exploit was permissionless and completed in one transaction: 0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3.
Execution sequence:
1. Flash-loan 4,000 USDC from Morpho.
2. Swap USDC -> crvUSD in the public Curve pool.
3. Donate 2,000 crvUSD to the Curve vault controller.
4. Mint 1 vault share for 2.000000000000000001 crvUSD.
5. Deposit that 1 share into Resupply as collateral.
6. Trigger UpdateExchangeRate(0) and borrow 10,000,000 reUSD.
7. Dump reUSD into public Curve liquidity, unwind to USDC, repay flash liquidity, keep profit.
The receipt metadata, trace, and validator reproduction agree on the pivotal on-chain events: Deposit(... shares: 1), AddCollateral(..., 1), UpdateExchangeRate(0), and Borrow(..., 10000000000000000000000000, ...).
6. Impact & Losses
The direct measurable extraction from the public crvUSD/USDC pool was 9,806,396.552565 USDC, encoded as "9806396552565" in smallest units. The attack also routed value through WETH after the primary stablecoin drain, but the submitted loss figure already captures the main realized stablecoin extraction and is sufficient to establish the protocol impact. Resupply was left with bad debt because 10,000,000 reUSD had been minted against collateral whose solvency value had been reduced to zero by the vulnerable pricing logic.
The validator reproduction independently confirms the economic outcome: after repaying the flash loan, the PoC contract still held 9806396543810 USDC, comfortably above the semantic profit threshold.
7. References
- Exploit transaction:
0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3 - Resupply Pair:
0x6e90c85a495d54c6d7e1f3400fef1f6e59f86bd6 - BasicVaultOracle:
0xcb7e25fbbd8afe4ce73d7dac647dbc3d847f3c82 - Curve vault:
0x01144442fba7adccb5c9dc9cf33dd009d50a9e1d - Collector receipt metadata:
/workspace/session/artifacts/collector/seed/1/0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3/metadata.json - Collector trace:
/workspace/session/artifacts/collector/seed/1/0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3/trace.cast.log - Collector balance diff:
/workspace/session/artifacts/collector/seed/1/0xffbbd492e0605a8bb6d490c3cd879e87ff60862b0684160d08fd5711e7a872d3/balance_diff.json - Verified source URLs referenced in the analysis:
https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode&address=0x6e90c85a495d54c6d7e1f3400fef1f6e59f86bd6https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode&address=0xcb7e25fbbd8afe4ce73d7dac647dbc3d847f3c82https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode&address=0x01144442fba7adccb5c9dc9cf33dd009d50a9e1d