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

Incorrect Accrued Yield Calculation Due to Decimal Mismatch and Improper aToken Accounting

Summary

The _getAccruedYieldPrivate function contains a critical vulnerability in its yield calculation logic due to unhandled token decimal mismatches and improper accounting of aToken balances. This allows attackers to manipulate yield calculations and steal funds.

Vulnerability Details

Core Issue

The function calculates "accrued yield" as aTokenBalance - wTokenSupply without accounting for differences in token decimals or isolating protocol-earned aTokens from external sources.

Technical Analysis

  1. Decimal Mismatch:

    • aToken (Aave) and wToken (protocol) may use different decimals (e.g., aToken=18 vs. wToken=6).

    • Example: 1 aToken (1e18 units) vs. 1 wToken (1e6 units) are treated as equal in raw values, causing 1000x+ miscalculations.

  2. Incorrect aToken Inclusion:

    • Any aTokens sent to the contract (e.g., via direct transfer) are counted as yield. Attackers can artificially inflate aTokenBalance to withdraw unauthorized funds.

  3. Assumption Violation:

    • The code assumes aTokenBalance >= wTokenSupply except for rounding errors. With decimal mismatches, aTokenBalance could be smaller than wTokenSupply even when yield exists, returning 0 erroneously.

Impact

  • High Severity: Attackers can:

    1. Drain funds by donating aTokens to manipulate yield calculations.

    2. Exploit decimal mismatches to claim excess yield.

    3. Cause denial-of-service by forcing 0 yield returns.

  • Financial Loss: Users lose deposited collateral due to incorrect yield distribution.

  • Reputational Risk: Protocol becomes untrustworthy due to fund mismanagement.

Tools Used

  • Manual Code Review

Recommendations

  1. Decimal Normalization:

    // During contract initialization:
    uint256 aTokenDecimals = IERC20Metadata(aToken).decimals();
    uint256 wTokenDecimals = IERC20Metadata(wToken).decimals();
    // In yield calculation:
    uint256 normalizedAToken = aTokenBalance * (10 ** (18 - aTokenDecimals));
    uint256 normalizedWToken = wTokenSupply * (10 ** (18 - wTokenDecimals));
    return normalizedAToken > normalizedWToken
    ? (normalizedAToken - normalizedWToken) / (10 ** (18 - aTokenDecimals))
    : 0;
  2. Isolate Protocol-Earned aTokens:

    • Track aTokens minted through user deposits in a state variable:

      mapping(address => uint256) private _protocolATokenBalances;
    • Use _protocolATokenBalances[_collateralToken] instead of direct balanceOf checks.

  3. Add Safety Checks:

    function initializeCollateral(address _collateralToken, address _wToken) external {
    require(
    IERC20Metadata(IAave(_aaveV3Pool).getReserveData(_collateralToken).aTokenAddress).decimals()
    == IERC20Metadata(_wToken).decimals(),
    "Decimal mismatch"
    );
    // ... rest of initialization
    }
  4. Reject Direct aToken Transfers:
    Implement a blackhole address for unexpected aToken transfers or use a dedicated vault to isolate yield-bearing assets.

Updates

Lead Judging Commences

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

Appeal created

maze Submitter
6 months ago
bube Lead Judge
6 months ago
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.