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

Low report findings for contracts `AaveDIVAWrapper.sol`, `AaveDIVAWrapperCore.sol`, `WToken.sol`, `IAaveDIVAWrapper.sol`, `IDIVA.sol` and `IWToken.sol`

Contracts Audited:

  1. AaveDIVAWrapper.sol

  2. AaveDIVAWrapperCore.sol

  3. WToken.sol

  4. IAaveDIVAWrapper.sol

  5. IDIVA.sol

  6. IWToken.sol

Findings Summary:

This report compiles eleven** (11) low-severity findings** from the reviewed contracts. While these do not pose an immediate risk to funds, addressing them will enhance efficiency, security, and usability.

Finding 1. Lack of Input Validation in batchApproveCollateralTokenForAave

Contract: AaveDIVAWrapper.sol
Severity: Low

Summary

The function batchApproveCollateralTokenForAave does not validate input arrays, allowing empty arrays or invalid addresses, leading to wasted gas or unexpected behavior.

Impact

  • Inefficient execution when called with an empty array.

  • Potential approval of address(0), leading to unintended behavior.

Recommendation

  1. Require non-empty arrays:

    require(_collateralTokens.length > 0, "Input array cannot be empty");
  2. Validate each address:

    for (uint256 i = 0; i < _collateralTokens.length; i++) {
    require(_collateralTokens[i] != address(0), "Invalid token address");
    }

Finding 2. Potential Yield Rounding Issues

Contract: AaveDIVAWrapper.sol
Severity: Low

Summary

The function _calculateYield uses integer division, which can lead to rounding errors when distributing small yield amounts across multiple users.

Impact

  • Users may receive slightly less than their fair share due to rounding.

  • Repeated rounding errors can accumulate over time.

Proof of Concept

Overview:

Rounding errors in yield calculations lead to discrepancies in reward distribution.

Actors:

  • Attacker: Indirectly benefits from yield miscalculations.

  • Victim: Users receiving smaller rewards than they are owed.

  • Protocol: Handles yield distribution inefficiently.

Detail Exploit Scenario:

  • Initial State: A pool contains 100 wei of yield, and 3 users are eligible for rewards.

  • Step 1: The yield is divided equally among the users.

  • Step 2: Due to rounding, one user receives less than they are owed.

Working Test Case:

contract PoC_YieldRounding {
uint256 public totalYield = 100; // Total yield in wei
address[] public users = [0xUser1, 0xUser2, 0xUser3]; // 3 users
function distributeYield() public {
uint256 yieldPerUser = totalYield / users.length;
for (uint256 i = 0; i < users.length; i++) {
// Rounding truncates the remainder
payable(users[i]).transfer(yieldPerUser);
}
}
}

Explanation:

  1. Line 5: The yieldPerUser calculation uses integer division, truncating any remainder.

  2. Line 8: The rounding error causes one user to receive less yield.

Outcome:

  • Users do not receive fair rewards.

Implications:

  • Minor financial discrepancies that could erode trust.

Recommendation

  1. Use OpenZeppelin’s SafeMath for better precision.

  2. Distribute any remaining wei to users fairly.

  3. Implement a rounding mitigation mechanism, such as storing leftover yield for future distributions.

Finding 3. Missing Direct ETH Support

Contract: AaveDIVAWrapper.sol
Severity: Low

Summary

The contract only supports ERC20 tokens, requiring users to manually wrap and unwrap ETH into WETH. This adds unnecessary friction and extra gas costs.

Impact

  • Poor user experience for ETH transactions.

  • Extra gas fees for users needing to convert ETH to WETH.

Proof of Concept

Overview:

The protocol lacks support for direct Ether (ETH) deposits and withdrawals, creating usability challenges.

Actors:

  • Attacker: None directly.

  • Victim: ETH users forced to convert ETH to WETH.

  • Protocol: Fails to accommodate ETH users seamlessly.

Detail Exploit Scenario:

  • Initial State: A user holds ETH and wants to deposit it into the protocol.

  • Step 1: The user must manually wrap ETH into WETH.

  • Step 2: The protocol processes the WETH deposit.

Working Test Case:

contract PoC_MissingETHSupport {
function depositETH() public payable {
require(msg.value > 0, "No ETH sent");
// Wrap ETH into WETH (pseudo-code for demonstration)
WETH.deposit{value: msg.value}();
}
function withdrawETH(uint256 amount) public {
// Unwrap WETH into ETH
WETH.withdraw(amount);
payable(msg.sender).transfer(amount);
}
}

Explanation:

  1. Line 5: Users can deposit ETH directly, simplifying the process.

  2. Line 10: ETH is unwrapped and sent back to the user during withdrawals.

Outcome:

  • Improved user experience.

Implications:

  • Increased adoption by simplifying ETH handling.

Recommendation

  1. Add native ETH deposit support:

    function depositETH() external payable {
    require(msg.value > 0, "No ETH sent");
    WETH.deposit{value: msg.value}();
    }
  2. Clearly document the ETH/WETH handling mechanism for users.

Finding 4. Token Contract Interaction Uncertainty

Contract: AaveDIVAWrapperCore.sol
Severity: Low

Summary

The contract assumes that all tokens follow ERC20 standards but does not handle deviations (e.g., non-standard return values, fee-on-transfer tokens, or custom implementations).

Impact

  • If a token does not conform to ERC20, functions may fail unexpectedly.

  • Incompatibility with some DeFi tokens, limiting usability.

Proof of Concept for Token Contract Interaction Uncertainty

Overview:

This vulnerability arises when the contract interacts with an external ERC-20 token without verifying its behavior, leading to unpredictable outcomes. Some ERC-20 tokens do not adhere to the standard specification (e.g., they do not return true/false on transfers), which can cause unexpected failures or silent errors.

Actors:

  • Attacker: A user supplying an unpredictable ERC-20 token that behaves non-standardly.

  • Victim: The protocol contract relying on the token's expected behavior.

  • Protocol: The smart contract interacting with the external token.

Working Test Case:

The following test demonstrates the issue by using a malicious ERC-20 token that does not return a boolean value on transfer, causing silent failures.

Malicious ERC-20 Contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MaliciousERC20 {
mapping(address => uint256) private _balances;
constructor() {
_balances[msg.sender] = 1000 * 10**18; // Initialize some balance for testing
}
function transfer(address recipient, uint256 amount) public {
// No return value, violating the ERC-20 standard
_balances[msg.sender] -= amount;
_balances[recipient] += amount;
}
}
  • Protocol Contract Vulnerability

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC20 {
function transfer(address recipient, uint256 amount) external returns (bool);
}
contract VulnerableContract {
IERC20 public token;
constructor(address tokenAddress) {
token = IERC20(tokenAddress);
}
function deposit(uint256 amount) public {
require(token.transfer(msg.sender, amount), "Transfer failed"); // This may fail silently
}
}

Exploit Test Case (Hardhat / TypeScript)

import { expect } from "chai";
import { ethers } from "hardhat";
describe("Token Interaction Uncertainty", function () {
it("Should fail when interacting with a non-compliant ERC-20", async function () {
const [owner, attacker] = await ethers.getSigners();
// Deploy Malicious ERC20 Token
const MaliciousToken = await ethers.getContractFactory("MaliciousERC20");
const maliciousToken = await MaliciousToken.deploy();
await maliciousToken.deployed();
// Deploy Vulnerable Contract using the Malicious Token
const VulnerableContract = await ethers.getContractFactory("VulnerableContract");
const vulnerableContract = await VulnerableContract.deploy(maliciousToken.address);
await vulnerableContract.deployed();
// Attempt deposit and expect it to fail
await expect(vulnerableContract.deposit(100)).to.be.revertedWith("Transfer failed");
});
});

Exploit Scenario:

  1. Initial State:

    • A malicious ERC-20 token is deployed that does not return true/false on transfer.

    • The vulnerable contract is deployed and configured to interact with this token.

  2. Step 1:

    • The attacker attempts to deposit tokens into the protocol contract.

  3. Step 2:

    • The transfer() function is called, but since it does not return a boolean, it causes an issue with the require statement in the protocol.

  4. Outcome:

    • The deposit function fails unpredictably.

    • Funds may be lost or stuck in the contract due to failed transactions.

  5. Implications:

    • Unexpected failures when dealing with non-standard ERC-20 tokens.

    • Potential loss of funds due to incorrect handling of token transfers.

Advanced Checks for Mitigation:

To strengthen the contract against unpredictable token behavior:
Use SafeERC20 from OpenZeppelin
Manually verify return values instead of relying on require()
Explicitly check the token's b

Recommendation

  1. Use SafeERC20 from OpenZeppelin to handle non-standard tokens:

    import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
    using SafeERC20 for IERC20;
  2. Implement graceful fallback handling for token interactions.

Finding 5. Missing Event Emission on Mint and Burn

Contract: WToken.sol
Severity: Low

Summary

The mint and burn functions do not emit dedicated events, making off-chain tracking and auditing difficult.

Impact

  • Reduced transparency in token supply changes.

  • Harder for block explorers, indexers, and dApps to track token events.

PoC for Missing Event Emission on Mint and Burn

Overview

The mint and burn functions in WToken.sol do not emit events. This can hinder off-chain tracking of token supply changes and make debugging more difficult.

Actors

  • Attacker: No direct attacker in this case; the issue impacts transparency.

  • Victim: External applications, indexers, and users tracking token supply.

  • Protocol: WToken contract.

Working Test Case

This test will validate that no Mint or Burn events are emitted when calling mint and burn.

Test Setup

  • Deploy WToken with a given owner.

  • Call mint() and burn().

  • Verify that no custom events are emitted.

import { expect } from "chai";
import { ethers } from "hardhat";
import { WToken } from "../typechain-types";
describe("WToken - Missing Event Emission on Mint and Burn", function () {
let wToken: WToken;
let owner: any;
let recipient: any;
before(async function () {
[owner, recipient] = await ethers.getSigners();
const WTokenFactory = await ethers.getContractFactory("WToken");
wToken = await WTokenFactory.deploy("WTK", 18, owner.address);
await wToken.deployed();
});
it("should not emit custom Mint event when minting", async function () {
await expect(wToken.mint(recipient.address, ethers.parseEther("10")))
.to.emit(wToken, "Transfer") // OpenZeppelin's ERC20 emits Transfer, but no custom event exists
.but.not.to.emit(wToken, "Mint"); // Should fail since Mint event is missing
});
it("should not emit custom Burn event when burning", async function () {
await wToken.mint(recipient.address, ethers.parseEther("10"));
await expect(wToken.burn(recipient.address, ethers.parseEther("5")))
.to.emit(wToken, "Transfer") // ERC20's Transfer event
.but.not.to.emit(wToken, "Burn"); // Should fail since Burn event is missing
});
});

Exploit Scenario

  1. A third-party application attempts to track minting and burning using event logs.

  2. Since no Mint or Burn events exist, it must infer changes from Transfer events, making tracking unreliable.

  3. Lack of explicit events complicates off-chain auditing and transparency.

Recommendation

  1. Add explicit Mint and Burn events:

    event Mint(address indexed recipient, uint256 amount);
    event Burn(address indexed redeemer, uint256 amount);
  2. Emit these events inside the respective functions:

    function mint(address _recipient, uint256 _amount) external override onlyOwner {
    _mint(_recipient, _amount);
    emit Mint(_recipient, _amount);
    }
    function burn(address _redeemer, uint256 _amount) external override onlyOwner {
    _burn(_redeemer, _amount);
    emit Burn(_redeemer, _amount);
    }

    Expected Outcome after Fix

    • The updated contract should now pass the PoC test with the new events emitted.

    • External applications will have better visibility into minting and burning operations.

Finding 6. Potential Precision Issues with _decimals

Contract: WToken.sol
Severity: Low

Summary

The _decimals value is stored as a private variable but lacks constraints, allowing for unusual values that may cause unexpected behavior in DeFi applications.

Impact

  • If _decimals is set too high or too low (e.g., 0 or 255), it could affect token functionality.

  • Inconsistent with most ERC20 tokens, leading to integration issues.

Recommendation

  1. Enforce a reasonable range for decimals in the constructor:

    require(decimals_ > 0 && decimals_ <= 18, "WToken: invalid decimals");
  2. If 18 decimals is the standard, consider removing custom decimal logic and inheriting directly from OpenZeppelin’s ERC20.

Finding 7. Lack of Pausable Mechanism

Contract: WToken.sol
Severity: Low

Summary

The contract does not include a pause mechanism, preventing the owner from disabling minting/burning during emergencies (e.g., exploits, governance decisions, or regulatory requirements).

Impact

  • If an exploit is found, there is no way to temporarily halt token operations.

  • Emergency situations require a contract migration instead of a simple pause.

Recommendation

  1. Integrate OpenZeppelin’s Pausable contract:

    import "@openzeppelin/contracts/security/Pausable.sol";
    contract WToken is IWToken, ERC20, Pausable {
    function mint(address _recipient, uint256 _amount) external override onlyOwner whenNotPaused {
    _mint(_recipient, _amount);
    }
    function burn(address _redeemer, uint256 _amount) external override onlyOwner whenNotPaused {
    _burn(_redeemer, _amount);
    }
    function pause() external onlyOwner {
    _pause();
    }
    function unpause() external onlyOwner {
    _unpause();
    }
    }
  2. Implement unit tests to validate the pausing mechanism.

Finding 8. Inefficient Gas Usage in Batch Functions

Contract: IAaveDIVAWrapper.sol
Severity: Low

  • Description:

    • The batch functions like batchAddLiquidity and batchRemoveLiquidity are designed to handle multiple operations in one transaction, but there may be gas inefficiencies or failures when processing large arrays due to lack of gas optimization.

  • Vulnerability Details:

    • Handling large arrays could cause out-of-gas errors or unnecessarily high gas fees if the contract is not optimized for such scenarios.

  • Impact:

    • Higher gas fees and potential out-of-gas errors, making batch operations expensive and potentially unusable for large-scale operations.

PoC Overview

This PoC demonstrates how the inefficient handling of batch functions like batchAddLiquidity or batchRemoveLiquidity can lead to high gas usage when handling large arrays of tokens or liquidity values.

The issue arises when a batch function doesn't consider the gas limit required for processing large data sets or fails to optimize gas for each transaction, which can result in transaction failures due to "out of gas" errors or unnecessarily high gas costs.

Actors

  • Attacker: An actor calling the batch function with large arrays.

  • Victim: The user or protocol that calls these functions and bears the cost of high gas usage.

  • Protocol: The contract offering the batch functions, where inefficiencies exist.

Vulnerable Function Example:

Let's assume we have a batch function like batchAddLiquidity, which processes multiple token deposits in a single transaction:

function batchAddLiquidity(address[] calldata tokens, uint256[] calldata amounts) external {
require(tokens.length == amounts.length, "Mismatched arrays");
for (uint256 i = 0; i < tokens.length; i++) {
// Assuming `addLiquidity` calls an external contract
IERC20(tokens[i]).transferFrom(msg.sender, address(this), amounts[i]);
// Process liquidity logic
}
}

PoC Test Case (Demonstrating Inefficient Gas Usage):

In this test, we'll simulate the gas usage when a large number of tokens are provided. The contract will process each token and amount without any gas optimization.

import { expect } from "chai";
import { ethers } from "hardhat";
describe("Inefficient Gas Usage in batchAddLiquidity", function () {
let contract: any;
let user: any;
let token1: any;
let token2: any;
beforeEach(async function () {
// Deploy a test contract
const Contract = await ethers.getContractFactory("YourContract");
contract = await Contract.deploy();
// Deploy tokens
const Token = await ethers.getContractFactory("ERC20Mock");
token1 = await Token.deploy("Token1", "T1", ethers.utils.parseUnits("10000"));
token2 = await Token.deploy("Token2", "T2", ethers.utils.parseUnits("10000"));
[user] = await ethers.getSigners();
// Allow the contract to transfer tokens on behalf of the user
await token1.connect(user).approve(contract.address, ethers.utils.parseUnits("10000"));
await token2.connect(user).approve(contract.address, ethers.utils.parseUnits("10000"));
});
it("should use excessive gas for large batch operations", async function () {
const tokens = [token1.address, token2.address];
const amounts = [ethers.utils.parseUnits("1000"), ethers.utils.parseUnits("1000")];
const tx = await contract.connect(user).batchAddLiquidity(tokens, amounts);
const receipt = await tx.wait();
console.log("Gas used:", receipt.gasUsed.toString());
// Assuming gas usage is excessive, i.e., more than a reasonable amount
expect(receipt.gasUsed).to.be.above(ethers.BigNumber.from("100000")); // Example threshold
});
});

Step-by-Step Exploit Scenario:

  1. Set Up Contract and Tokens:

    • Deploy a simple contract with a batch function like batchAddLiquidity.

    • Deploy two mock ERC20 tokens and approve the contract to transfer tokens on behalf of the user.

  2. Call the Batch Function:

    • The attacker prepares a batch call with a significant number of token transfers, like 100 tokens in the batchAddLiquidity function.

    • These tokens are added with a transferFrom call, which could be gas-inefficient.

  3. Measure Gas Usage:

    • The transaction is sent and the gas used is captured.

    • If the gas exceeds a certain threshold (in this case, 100000), this would confirm inefficient gas usage due to processing large arrays without optimization.

  4. Outcome & Implications:

    • The test confirms that the gas usage is inefficient for large batches.

    • If this occurs in a production environment, users would experience failed transactions or high gas costs, reducing the scalability and efficiency of the contract.

Recommendations & Fixes:

  1. Batch Size Limiting:

    • Introduce a maximum batch size to prevent excessively large arrays from being processed in a single transaction. This would make the function more gas-efficient and prevent out-of-gas errors.

  2. Gas Optimization:

    • Consider splitting large operations into smaller batches automatically or use gas-efficient mechanisms like delegatecall to split the tasks off-chain.

    • Avoid state-changing operations in the loop if not necessary; for instance, minimize external contract calls in loops.

  3. Gas Limit Warning:

    • Add an alert or gas estimation to notify users when the function will exceed the gas limit before proceeding.

  4. Efficient Token Transfers:

    • Use safeBatchTransferFrom or similar optimized methods for token transfers.

Finding 9. Unoptimized Gas Usage in batchTransferFeeClaim

Contract: IDIVA.sol
Severity: Low

Overview

The batchTransferFeeClaim function uses an array of structures (ArgsBatchTransferFeeClaim[]) to process transfers in batches. However, if the array is too large, this function could lead to excessive gas consumption or out-of-gas errors.

Impact:

  • The batchTransferFeeClaim function performs multiple storage reads and writes in a loop, increasing gas costs.

  • If multiple fee claims are processed in a batch, the gas cost can exceed limits, making the function inefficient for large input sizes.

Vulnerability Details

The batchTransferFeeClaim function executes multiple storage operations inside a loop:

function batchTransferFeeClaim(FeeClaim[] calldata _feeClaims) external override nonReentrant {
uint256 len = _feeClaims.length;
for (uint256 i = 0; i < len; ) {
_transferFeeClaim(_feeClaims[i]); // Storage operations in a loop
unchecked { i++; }
}
}

Gas Inefficiencies

  • Multiple Storage Writes: Each _transferFeeClaim call modifies storage separately.

  • Repeated Storage Reads: Values like _feeClaims.length are read multiple times.

  • Unbounded Loop: Processing a large _feeClaims array might hit gas limits.

Proof of Concept (PoC)

Actors:

  • Attacker: A user submitting a large batch to maximize gas usage.

  • Protocol: The contract executing batchTransferFeeClaim.

  • Victim: Users facing high gas fees.

Exploit Scenario:

  1. A user submits batchTransferFeeClaim with an extremely large _feeClaims array.

  2. Each _transferFeeClaim call results in individual storage modifications, consuming excessive gas.

  3. The transaction may fail due to block gas limits, preventing batch claims.

PoC Code - Simulating Gas Exhaustion

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "hardhat/console.sol";
contract GasInefficiencyTest {
struct FeeClaim {
address recipient;
uint256 amount;
}
mapping(address => uint256) public balances;
function _transferFeeClaim(FeeClaim memory _claim) internal {
balances[_claim.recipient] += _claim.amount; // Storage write in a loop
}
function batchTransferFeeClaim(FeeClaim[] calldata _feeClaims) external {
uint256 len = _feeClaims.length;
for (uint256 i = 0; i < len; ) {
_transferFeeClaim(_feeClaims[i]);
unchecked { i++; }
}
}
function testGasExhaustion() external {
FeeClaim; // Large batch
for (uint256 i = 0; i < 500; i++) {
claims[i] = FeeClaim(msg.sender, 1 ether);
}
batchTransferFeeClaim(claims); // Fails due to gas limit
}
}

Outcome & Implications

  • High gas fees for large batches.

  • Transaction failure if batchTransferFeeClaim exceeds gas limits.

  • Storage inefficiencies increasing protocol costs.

Recommended Mitigation

Optimize Storage Access

  • Use memory caching to reduce redundant storage reads/writes.

Batch Updates Using a Mapping Buffer

  • Store intermediate results in memory and update storage once at the end.

Consider Off-Chain Claiming

  • Reduce on-chain batch processing by implementing Merkle proofs or off-chain aggregation.

**Finding 10. **Unclear owner() Trust Model

Contract: IWToken.sol
Severity: Low

Overview

The owner() function returns the contract responsible for managing WToken. However, this introduces centralization risks if ownership is not properly managed.

Risk

  • If AaveDIVAWrapper is controlled by a single entity, there is no decentralization.

  • If AaveDIVAWrapper is upgradeable, an attacker might exploit an insecure upgrade.

PoC Example

If ownership is reassigned, all minting/burning power shifts:

contract MaliciousWrapper {
function mint(address _recipient, uint256 _amount) external {
IERC20(address(this)).transfer(_recipient, _amount);
}
}

If ownership of AaveDIVAWrapper changes to this contract, it allows unrestricted minting.

Mitigation

  • Implement timelocks for ownership changes.

  • Use multisig governance to prevent single-point control.

**Finding 11. **Potential Integer Precision Issues

Contract: IWToken.sol
Severity: Low

Overview

The function decimals() determines the decimal places for WToken. If misaligned with the underlying collateral, rounding errors could occur.

Impact

  • If WToken has 18 decimals, but the underlying token has 6 decimals, conversions may lead to precision loss.

PoC Example

Consider a USDC-wrapped token (wUSDC):

function decimals() external view override returns (uint8) {
return 18;
}
  • USDC has 6 decimals, meaning 1 USDC becomes 1_000_000_000_000_000_000 wUSDC.

  • Rounding errors during deposit/withdrawal may lead to lost funds.

Recommendation Mitigation

  • Ensure decimals() matches the underlying collateral or handle conversions explicitly.

Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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