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

Fee-on-Transfer Token Handling Causes Underlying Imbalance

Summary

The _handleTokenOperations function does not correctly handle fee-on-transfer tokens, which results in a mismatch between the wToken supply and the actual collateral deposited into Aave. This issue leads to long-term liquidity imbalances, where more wTokens exist than the underlying collateral, making redemptions unreliable.


Technical Analysis

Vulnerable Code

function _handleTokenOperations(...) private {
IERC20Metadata(_collateralToken).safeTransferFrom(msg.sender, address(this), _collateralAmount);
IAave(_aaveV3Pool).supply(_collateralToken, _collateralAmount, ...);
IWToken(_wToken).mint(address(this), _collateralAmount);
}
​

Root Cause

  1. Incorrect Assumption of Full Transfer

    • The function assumes that the entire _collateralAmount is received when calling safeTransferFrom(), but fee-on-transfer tokens (e.g., USDT, STA, certain DeFi tokens) deduct a fee, meaning less than _collateralAmount is actually received.

  2. wTokens Are Minted Based on Requested Amount, Not Received Amount

    • _collateralAmount is blindly used to mint wTokens, leading to more wTokens being minted than actual collateral deposited into Aave.

Impact

πŸ”΄ Severity: High
βœ… Likelihood: High
βœ… Affected Parties: Protocol users and liquidity providers


Attack Scenario: Exploiting the Imbalance

Scenario 1: Accidental Imbalance Growth Over Time

  1. A user deposits 100 fee-on-transfer tokens (e.g., a token with a 5% transfer fee).

  2. The contract only receives 95 tokens but mints 100 wTokens.

  3. Over time, as more fee-on-transfer tokens are deposited, wToken supply exceeds aToken balances.

  4. Users can no longer fully redeem their wTokens because not enough collateral exists.

Outcome:

  • The protocol faces long-term liquidity issues, as wToken holders are unable to redeem their assets properly.

  • The protocol’s balance sheet becomes inaccurate, potentially leading to unexpected insolvencies.


Scenario 2: Immediate Exploitation by a Malicious User

Steps to Exploit

  1. An attacker deposits a fee-on-transfer token with a high transfer fee (e.g., 50%).

  2. The contract mints wTokens equal to the full requested deposit, but only half the collateral reaches Aave.

  3. The attacker redeems their wTokens immediately, withdrawing more collateral than they actually deposited.

  4. Result: The attacker drains the liquidity pool, leaving other users with unredeemable wTokens.

Outcome:

🚨 Liquidity Drain – The attacker effectively extracts more value than deposited, leading to user losses.


Proof of Concept (PoC)

Exploit Demonstration with a Fee-on-Transfer Token

PoC simulates a user exploiting this issue using a fee-on-transfer token.

Malicious Token Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
​
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
​
contract FeeOnTransferToken is ERC20 {
uint256 public feePercentage; // Set transfer fee
​
constructor(uint256 _feePercentage) ERC20("FeeToken", "FOT") {
feePercentage = _feePercentage;
_mint(msg.sender, 1000000 * 10**18);
}
​
function transfer(address recipient, uint256 amount) public override returns (bool) {
uint256 fee = (amount * feePercentage) / 100;
uint256 amountAfterFee = amount - fee;
​
super.transfer(recipient, amountAfterFee);
super.transfer(address(0xdead), fee); // Burn the fee
return true;
}
}
​

Attack Execution

  1. Deploy FeeOnTransferToken with a 50% fee.

  2. Deposit 100 tokens into AaveDIVAWrapper.

  3. The contract mints 100 wTokens but only receives 50 tokens.

  4. Withdraw 100 wTokens, draining collateral reserves.


Mitigation Strategies

βœ… Fix 1: Mint wTokens Based on Received Collateral, Not Requested Amount

Modify _handleTokenOperations to check how many tokens were actually received.

Code

function _handleTokenOperations(address _collateralToken, uint256 _collateralAmount, address _wToken) private {
uint256 balanceBefore = IERC20Metadata(_collateralToken).balanceOf(address(this));
​
IERC20Metadata(_collateralToken).safeTransferFrom(msg.sender, address(this), _collateralAmount);
​
uint256 balanceAfter = IERC20Metadata(_collateralToken).balanceOf(address(this));
uint256 actualReceived = balanceAfter - balanceBefore;
​
IAave(_aaveV3Pool).supply(_collateralToken, actualReceived, address(this), 0);
​
// Mint wTokens based on actual received amount
IWToken(_wToken).mint(address(this), actualReceived);
}
​

βœ… Ensures the minted wTokens match the actual deposited collateral.
βœ… Prevents liquidity imbalances.


βœ… Fix 2: Enforce Token Whitelisting to Exclude Fee-on-Transfer Tokens

To prevent any fee-on-transfer tokens from being used, enforce a whitelist of approved ERC20 tokens.

Implementation

mapping(address => bool) private approvedTokens;
​
function addApprovedToken(address _token) external onlyOwner {
approvedTokens[_token] = true;
}
​
function _handleTokenOperations(address _collateralToken, uint256 _collateralAmount, address _wToken) private {
require(approvedTokens[_collateralToken], "Token not supported");
uint256 balanceBefore = IERC20Metadata(_collateralToken).balanceOf(address(this));
IERC20Metadata(_collateralToken).safeTransferFrom(msg.sender, address(this), _collateralAmount);
uint256 balanceAfter = IERC20Metadata(_collateralToken).balanceOf(address(this));
uint256 actualReceived = balanceAfter - balanceBefore;
​
IAave(_aaveV3Pool).supply(_collateralToken, actualReceived, address(this), 0);
IWToken(_wToken).mint(address(this), actualReceived);
}
​

βœ… Prevents fee-on-transfer tokens from being registered.
βœ… Protects against future unexpected token behaviors.

Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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