SuperTokenV2 share inflation
Exploit Transactions
0xc6fb8217e45870a93c25e2098f54f6e3b24674a3083c30664867de474bf0212dVictim Addresses
0xff152e21c5a511c478ed23d1b89bb9391be6de96AvalancheLoss Breakdown
Similar Incidents
Flashstake LP Share Inflation
38%Nereus Oracle Overborrow
36%LibertiVault Reentrant Share Inflation
35%Platypus LP Cross-Asset Mispricing
35%Raft cbETH Share Inflation
32%MuBank Bond Manipulation
32%Root Cause Analysis
SuperTokenV2 share inflation
1. Incident Overview TL;DR
An unprivileged attacker exploited SuperTokenV2 on Avalanche at 0xc6fb8217e45870a93c25e2098f54f6e3b24674a3083c30664867de474bf0212d. The attacker chained a Trader Joe same-token flash swap with the victim vault's own ERC3156 flash loan, deposited during the victim callback while the vault's Aave-backed assets were temporarily understated, minted inflated shares, redeemed those shares after assets were restored, and kept 173637629135 raw USDC.e.
The root cause is accounting that prices shares from getTotalAssets() during an in-flight flash loan. In flashLoan, the vault withdraws assets before the borrower callback; in _deposit, it snapshots the reduced asset base and mints shares against that understated denominator.
2. Key Background
SuperTokenV2 lendingSwitchErc20 is an ERC20-like vault wrapper whose share accounting lives in baseSuperToken.sol. On Avalanche, the relevant deployment at 0xff152e21c5a511c478ed23d1b89bb9391be6de96 uses Aave aUSDC.e at 0x625e7708f30ca75bfd92586e17077590c60eb4cd as the backing position, and both getAvailableBalance() and getTotalAssets() resolve to that aToken balance.
The vault also exposes ERC3156 flash loans. Its implementation withdraws underlying from Aave to the borrower before the callback and only re-deposits amount + fee after the callback returns. That means callback-time pricing can observe a temporarily reduced asset denominator while share supply is unchanged.
Trader Joe pair 0xf4003f4efbe8691b60249e6afbd307abe7758adb provides the external funding leg. The attacker used the pair's public swap(..., data) callback path to source the same USDC.e needed to satisfy the victim flash-loan repayment condition inside one transaction.
3. Vulnerability Analysis & Root Cause Summary
The bug is a share-inflation flaw caused by combining callback-capable flash loans with share pricing that directly trusts transient vault assets. convertToShares computes shares as assets * totalSupply / getTotalAssets, and _deposit snapshots getTotalAssets() before pulling assets in. flashLoan meanwhile calls onWithdraw before invoking the borrower callback, so the vault's Aave-backed assets are lower during the callback even though outstanding shares are unchanged. Because superAaveTokenImpl.getTotalAssets() simply returns aavaToken.balanceOf(address(this)), the callback observes the depleted balance directly. A borrower can therefore call deposit from onFlashLoan, mint too many shares, wait for flashLoan to restore assets, and then redeem at the normal post-restoration exchange rate. The trace confirms that a callback-time deposit of 194263946117 USDC.e minted 1844317410414 shares and that redeeming those shares returned 368503793484 USDC.e.
4. Detailed Root Cause Analysis
The vulnerable code path is direct. In baseSuperToken.flashLoan, the vault computes the fee and then withdraws assets to the borrower before entering the callback:
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external virtual returns (bool) {
require(token == address(asset), "flash borrow token Error!");
uint256 fee = flashFee(token, amount);
onWithdraw(address(receiver), amount);
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,
"invalid return value"
);
onDeposit(address(receiver), amount + fee, 0);
emit FlashLoan(msg.sender, address(receiver), token, amount);
return true;
}
The pricing side is equally direct. _deposit snapshots getTotalAssets() before moving funds, then mints _amount * totalShares / totaStake shares:
function _deposit(address from, uint256 _amount, address receiver) internal returns (uint256) {
uint256 totaStake = getTotalAssets();
uint256 totalShares = totalSupply();
_amount = onDeposit(from, _amount, feeRate[enterFeeID]);
if (totalShares == 0 || totaStake == 0) {
_mint(receiver, _amount);
return _amount;
} else {
uint256 what = _amount.mul(totalShares) / totaStake;
require(what > 0, "super token mint 0!");
_mint(receiver, what);
return what;
}
}
On Avalanche Aave mode, getTotalAssets() is just the victim's aUSDC.e balance:
function getAvailableBalance() internal virtual view override returns (uint256){
return aavaToken.balanceOf(address(this));
}
function getTotalAssets() internal virtual view override returns (uint256){
return aavaToken.balanceOf(address(this));
}
The seed trace shows the exact exploit sequence. Trader Joe transfers 194263946117 USDC.e to the attacker helper, which then calls victim flashLoan for the same amount. Inside the callback, the helper calls deposit(194263946117, helper) and the victim emits Deposit(..., 194263946117, 1844317410414), proving that the callback-time denominator was understated. After flashLoan re-deposits amount + fee, the helper redeems 1844317410414 shares and receives 368503793484 USDC.e, far above the callback deposit amount. The balance-diff artifact then shows the final economic effect: the helper gains 173637629135 USDC.e, the Aave backing address loses 174220420973 USDC.e, Trader Joe pair gains 602218232 USDC.e in swap fee, and the attacker EOA contributes 19426394 USDC.e to cover the victim flash-loan fee.
5. Adversary Flow Analysis
The attacker flow has four observed transactions. First, EOA 0x7373dca267bdc623dfba228696c9d4e8234469f6 swapped AVAX for exactly 21000000 raw USDC.e through Trader Joe router 0xe3ffc583dc176575eea7fd9df2a7c65f7e23f4c3, creating the fee seed. Second, the same EOA deployed helper contract 0x792e8f3727cad6e00c58d478798f0907c4cec340, whose constructor parameters embed the victim vault, Aave, token, pair, and owner addresses. Third, the EOA approved 30000000 raw USDC.e to the helper.
In the exploit transaction, the helper invoked a Trader Joe flash swap to borrow 194263946117 USDC.e from pair 0xf4003f4efbe8691b60249e6afbd307abe7758adb. Inside joeCall, the helper invoked victim flashLoan for the same amount. The victim withdrew underlying from Aave, transferred USDC.e to the helper, and entered onFlashLoan. During that callback, the helper deposited the borrowed USDC.e back into the victim, which minted 1844317410414 shares. Once flashLoan finished and restored the victim's assets plus fee, the helper redeemed all minted shares for 368503793484 USDC.e, repaid the Trader Joe pair 194866164349 USDC.e, and retained the remaining USDC.e balance as profit.
6. Impact & Losses
The direct protocol loss is depletion of the victim's Aave-backed USDC.e position by 174220420973 raw units, or 174220.420973 USDC.e at 6 decimals. The attacker helper contract retained 173637629135 raw USDC.e after repaying the Trader Joe funding leg. The residual difference is explained by the Trader Joe swap fee (602218232 raw units) and the attacker EOA's contribution of 19426394 raw units to pay the victim flash-loan fee.
Affected public protocol components are the victim vault 0xff152e21c5a511c478ed23d1b89bb9391be6de96, its Aave aUSDC.e backing token 0x625e7708f30ca75bfd92586e17077590c60eb4cd, and the USDC.e token 0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e.
7. References
- Seed exploit transaction:
0xc6fb8217e45870a93c25e2098f54f6e3b24674a3083c30664867de474bf0212d - Supporting setup transactions:
0xbc1d4408e7c9fddb594485cf45835741bf2d8c9f7bf64debadb05b52d3f5c8f1,0x0a4019422649afcd4c79ae43589725d184199b873326106a4bc4f534eeab55c4,0x3151218dcf7cc872b083f5c739833dec758d1b43a743423941c9e89243297140 - Victim source files:
baseSuperToken.solandsuperAaveTokenImpl.solfor the deployed vault at0xff152e21c5a511c478ed23d1b89bb9391be6de96 - Seed trace artifact:
trace.cast.logshowingjoeCall,flashLoan, callback-timedeposit,redeem, and pair repayment - Balance diff artifact:
balance_diff.jsonshowing attacker profit and Aave-backed loss in raw token units