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

Misplaced Constructor Parameters Render AaveDIVAWrapperCore Dysfunctional, Causing Denial of Service (DoS) and Necessitating Redeployment.

Summary

The AaveDIVAWrapperCore contract serves as the foundational component of the protocol, providing the essential structure upon which the AaveDIVAWrapper contract builds. The latter extends AaveDIVAWrapperCore to create a fully functional implementation of the protocol.

As an abstract contract, AaveDIVAWrapperCore includes a constructor that is invoked in a manner similar to the AaveDIVAWrapper contract. However, the AaveDIVAWrapperCore contract does not define its constructor parameters directly; instead, it receives them from its descendant, AaveDIVAWrapper. Specifically, the constructor of AaveDIVAWrapperCore expects the following arguments:

  • address diva_: The address of the DIVA Protocol contract.

  • address aaveV3Pool_: The address of the Aave V3 lending pool contract.

  • address owner_: The owner’s address, which is further passed to the OpenZeppelin Ownable contract to establish ownership control.

To prevent unintended initialization, the constructor enforces a strict validation mechanism, ensuring that none of the provided addresses (except owner_, which is validated within OpenZeppelin's Ownable contract) are zero addresses. If an invalid address is provided, the contract execution is halted by triggering a ZeroAddress() revert.

AaveDIVAWrapperCore Constructor Implementation - Reference Link

constructor(address diva_, address aaveV3Pool_, address owner_) Ownable(owner_) {
// Validate that none of the input addresses is zero to prevent unintended initialization with default addresses.
// Zero address check on `owner_` is performed in the OpenZeppelin's `Ownable` contract.
if (diva_ == address(0) || aaveV3Pool_ == address(0)) {
revert ZeroAddress();
}
// Store the addresses of DIVA Protocol and Aave V3 in storage.
_diva = diva_;
_aaveV3Pool = aaveV3Pool_;
}

By maintaining a well-structured and modular design, the AaveDIVAWrapperCore contract ensures that the AaveDIVAWrapper contract can efficiently interact with both DIVA Protocol and Aave V3 while adhering to security best practices.

Vulnerability Details

Despite the well-structured inheritance model, a critical flaw exists in the way the AaveDIVAWrapper contract passes constructor arguments to AaveDIVAWrapperCore. Instead of supplying the parameters in the correct order (diva_, aaveV3Pool_, owner_), the AaveDIVAWrapper constructor mistakenly provides them in the following incorrect sequence:

  1. _aaveV3Pool (Aave V3 Pool address)

  2. _diva (DIVA Protocol address)

  3. _owner (Owner's address)

This incorrect argument order fundamentally breaks the intended functionality of the protocol. When AaveDIVAWrapperCore attempts to assign the received values, it mistakenly interprets _aaveV3Pool as diva_, _diva as aaveV3Pool_, and _owner as owner_. As a result, the contract operates with incorrectly assigned addresses, leading to a complete breakdown of the protocol.

AaveDIVAWrapper Constructor with Incorrect Argument Order - Reference Link

constructor(address _aaveV3Pool, address _diva, address _owner) AaveDIVAWrapperCore(_aaveV3Pool, _diva, _owner) {}

This oversight results in a Denial of Service (DoS) scenario that renders the entire Aave Diva Wrapper Protocol completely dysfunctional. Since the contract relies on valid addresses to execute key functions, the incorrect assignments cause every interaction to fail. The repercussions of this issue impact all participants in the ecosystem:

  • Users: Unable to create liquidity pools, add or remove liquidity, redeem position tokens, or convert wrapped collateral tokens back to the original token.

  • Owner: Incapable of registering new collateral tokens or claiming yield generated from Aave deposits.

  • Data Providers / Oracles: Unable to resolve pools by reporting outcomes to the DIVA Protocol.

Proof of Concept

To demonstrate this vulnerability, a Proof of Concept (PoC) is provided below.

Note: The PoC is written using the Foundry tool.

  1. Step 1: Make a foundry project and put all the contracts in the src directory.

  2. Step 2: Create a test folder and mocks folder in the test and src directories respectively (or we already have a mocks folder in src).

  3. Step 3: Create all necessary mocks

  4. step 4: Create a test file with any name in the test directory.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {AaveMock} from "../src/mocks/AaveMock.m.sol";
import {ATokenMock} from "../src/mocks/ATokenMock.m.sol";
import {DivaMock} from "../src/mocks/DivaMock.m.sol";
import {MockERC20} from "../src/mocks/MockERC20.sol";
import {MockLongToken} from "../src/mocks/MockLongToken.m.sol";
import {MockShortToken} from "../src/mocks/MockShortToken.m.sol";
import {MockUSDT} from "../src/mocks/MockUSDTEthereum.sol";
import {AaveDIVAWrapper} from "../src/AaveDIVAWrapper.sol";
import {WToken} from "../src/WToken.sol";
import {IAaveDIVAWrapper} from "../src/interfaces/IAaveDIVAWrapper.sol";
contract AaveDivaWrapperTest is Test {
AaveDIVAWrapper aaveDivaWrapper;
DivaMock divaMock;
AaveMock aaveMock;
MockUSDT usdtMock;
address AAVE_DIVA_WRAPPER_OWNER = makeAddr("AAVE_DIVA_WRAPPER_OWNER");
address ALICE = makeAddr("ALICE");
address BOB = makeAddr("BOB");
address CHARLIE = makeAddr("CHARLIE");
address INTRUDER = makeAddr("INTRUDER");
address CHAINLINK_DATA_PROVIDER = makeAddr("CHAINLINK_DATA_PROVIDER");
address ERC721_PERMISSIONED_TOKEN = makeAddr("ERC721_PERMISSIONED_TOKEN");
function setUp() public {
aaveMock = new AaveMock();
divaMock = new DivaMock();
usdtMock = new MockUSDT();
aaveMock.setAaveAllowedTokens(address(usdtMock));
vm.startPrank(AAVE_DIVA_WRAPPER_OWNER);
// AaveDIVAWrapper constructor
// constructor(address _aaveV3Pool, address _diva, address _owner)
aaveDivaWrapper = new AaveDIVAWrapper(address(aaveMock), address(divaMock), AAVE_DIVA_WRAPPER_OWNER);
vm.stopPrank();
}
}
  1. Step 5: Now, Put the following Test PoC in the test file adjacent to the setUp function.

function testAaveWrapperContractCompletelyDysfunction() public {
vm.startPrank(AAVE_DIVA_WRAPPER_OWNER);
vm.expectRevert();
aaveDivaWrapper.registerCollateralToken(address(usdtMock));
vm.stopPrank();
// all values are hyptotheical and used for simulation only
IAaveDIVAWrapper.PoolParams memory poolParams = IAaveDIVAWrapper.PoolParams({
referenceAsset: "USDT/USD",
expiryTime: uint96(block.timestamp + 2 hours),
floor: 100,
inflection: 150,
cap: 200,
gradient: (5 * (10 ** (usdtMock.decimals() - 1))),
collateralAmount: (100 * (10 ** usdtMock.decimals())),
collateralToken: address(usdtMock),
dataProvider: CHAINLINK_DATA_PROVIDER,
capacity: type(uint256).max,
longRecipient: ALICE,
shortRecipient: ALICE,
permissionedERC721Token: ERC721_PERMISSIONED_TOKEN
});
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.createContingentPool(poolParams);
vm.stopPrank();
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.addLiquidity(bytes32(abi.encodePacked("any_random_poolId")), 10e6, ALICE, ALICE);
vm.stopPrank();
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.removeLiquidity(bytes32(abi.encodePacked("any_random_poolId")), 10e6, ALICE);
vm.stopPrank();
// here, we don't need an actual position token for the simulation
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.redeemPositionToken(address(65), 10e6, ALICE);
vm.stopPrank();
// here, we don't need an actual wtoken (e.g., wrapped usdt) for the simulation
WToken _wTokenContract = new WToken(
string(abi.encodePacked("w", usdtMock.symbol())),
usdtMock.decimals(),
AAVE_DIVA_WRAPPER_OWNER // wToken owner
);
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.redeemWToken(address(_wTokenContract), 10e6, ALICE);
vm.stopPrank();
vm.startPrank(AAVE_DIVA_WRAPPER_OWNER);
vm.expectRevert();
aaveDivaWrapper.claimYield(address(usdtMock), ALICE);
vm.stopPrank();
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.approveCollateralTokenForAave(address(usdtMock));
vm.stopPrank();
// and all batch functions will also get reverted because they call above called function in a iterative way
}
  1. Step 6: Now, to run the test, run the following commands in the terminal in sequence:

forge test --mt testAaveWrapperContractCompletelyDysfunction -vvvvv
  1. Step 7: See the output of the test in the terminal, the output should be similar to the below one:

[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/AaveDivaWrapperTest.t.sol:AaveDivaWrapperTest
[PASS] testAaveWrapperContractCompletelyDysfunction() (gas: 674344)
Traces:
[9884199] AaveDivaWrapperTest::setUp()
├─ [1849348] → new AaveMock@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 9237 bytes of code
├─ [3404866] → new DivaMock@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 17005 bytes of code
├─ [481807] → new MockUSDT@0xF62849F9A0B5Bf2913b396098F7c7019b51A820a
│ └─ ← [Return] 2181 bytes of code
├─ [618611] AaveMock::setAaveAllowedTokens(MockUSDT: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a])
│ ├─ [1216] MockUSDT::symbol() [staticcall]
│ │ └─ ← [Return] "USDT"
│ ├─ [266] MockUSDT::decimals() [staticcall]
│ │ └─ ← [Return] 6
│ ├─ [519050] → new ATokenMock@0x104fBc016F4bb334D775a19E8A6510109AC63E00
│ │ └─ ← [Return] 2253 bytes of code
│ └─ ← [Stop]
├─ [0] VM::startPrank(AAVE_DIVA_WRAPPER_OWNER: [0xE3956835e40e4731A03596C87534501ac1656f1E])
│ └─ ← [Return]
├─ [3316589] → new AaveDIVAWrapper@0x93Cf436DE8BF36C3d42Ab9dDD57631eCe44F9986
│ ├─ emit OwnershipTransferred(previousOwner: 0x0000000000000000000000000000000000000000, newOwner: AAVE_DIVA_WRAPPER_OWNER: [0xE3956835e40e4731A03596C87534501ac1656f1E])
│ └─ ← [Return] 16320 bytes of code
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]
[674344] AaveDivaWrapperTest::testAaveWrapperContractCompletelyDysfunction()
├─ [0] VM::startPrank(AAVE_DIVA_WRAPPER_OWNER: [0xE3956835e40e4731A03596C87534501ac1656f1E])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [12871] AaveDIVAWrapper::registerCollateralToken(MockUSDT: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a])
│ ├─ [214] DivaMock::getReserveData(MockUSDT: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall]
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [266] MockUSDT::decimals() [staticcall]
│ └─ ← [Return] 6
├─ [266] MockUSDT::decimals() [staticcall]
│ └─ ← [Return] 6
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [7888] AaveDIVAWrapper::createContingentPool(PoolParams({ referenceAsset: "USDT/USD", expiryTime: 7201, floor: 100, inflection: 150, cap: 200, gradient: 500000 [5e5], collateralAmount: 100000000 [1e8], collateralToken: 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a, dataProvider: 0x1e6974FFaa047925EfcADEc7382f1652E3e06d95, capacity: 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77], longRecipient: 0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916, shortRecipient: 0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916, permissionedERC721Token: 0xdC834545376a3E551F6aAC8360C19fdCac9bC620 }))
│ └─ ← [Revert] CollateralTokenNotRegistered()
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [8501] AaveDIVAWrapper::addLiquidity(0x616e795f72616e646f6d5f706f6f6c4964000000000000000000000000000000, 10000000 [1e7], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ ├─ [192] AaveMock::getPoolParameters(0x616e795f72616e646f6d5f706f6f6c4964000000000000000000000000000000) [staticcall]
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [8437] AaveDIVAWrapper::removeLiquidity(0x616e795f72616e646f6d5f706f6f6c4964000000000000000000000000000000, 10000000 [1e7], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ ├─ [192] AaveMock::getPoolParameters(0x616e795f72616e646f6d5f706f6f6c4964000000000000000000000000000000) [staticcall]
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [8553] AaveDIVAWrapper::redeemPositionToken(0x0000000000000000000000000000000000000041, 10000000 [1e7], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ ├─ [192] AaveMock::getPoolParametersByAddress(0x0000000000000000000000000000000000000041) [staticcall]
│ │ └─ ← [Revert] EvmError: Revert
│ └─ ← [Revert] EvmError: Revert
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [3216] MockUSDT::symbol() [staticcall]
│ └─ ← [Return] "USDT"
├─ [266] MockUSDT::decimals() [staticcall]
│ └─ ← [Return] 6
├─ [519050] → new WToken@0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9
│ └─ ← [Return] 2253 bytes of code
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [9595] AaveDIVAWrapper::redeemWToken(WToken: [0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9], 10000000 [1e7], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ ├─ [2540] WToken::balanceOf(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916]) [staticcall]
│ │ └─ ← [Return] 0
│ ├─ [636] WToken::burn(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916], 10000000 [1e7])
│ │ └─ ← [Revert] revert: WToken: caller is not owner
│ └─ ← [Revert] revert: WToken: caller is not owner
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(AAVE_DIVA_WRAPPER_OWNER: [0xE3956835e40e4731A03596C87534501ac1656f1E])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [10006] AaveDIVAWrapper::claimYield(MockUSDT: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Revert] CollateralTokenNotRegistered()
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(ALICE: [0xef211076B8d8b46797E09c9a374Fb4Cdc1dF0916])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error f4844814:)
│ └─ ← [Return]
├─ [2588] AaveDIVAWrapper::approveCollateralTokenForAave(MockUSDT: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a])
│ └─ ← [Revert] CollateralTokenNotRegistered()
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.39ms (320.70µs CPU time)
Ran 1 test suite in 1.10s (2.39ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

As demonstrated in the output, test passes and there're so many reverts, these reverts proved that wrong order of parameters cause a severe DoS.

Impact

The incorrect ordering of constructor parameters in the AaveDIVAWrapper contract has severe consequences that ripple across the entire Aave Diva Wrapper Protocol, leading to complete inoperability.

1. Complete Dysfunctionality of the Protocol

The misordered parameters cause AaveDIVAWrapperCore to store incorrect addresses for DIVA Protocol, Aave V3 Pool, and Owner. Since these addresses are fundamental to the contract’s ability to interact with external protocols and manage ownership, their misconfiguration renders the protocol non-functional.

  • Calls to DIVA Protocol functions fail because the contract is referencing an incorrect address instead of the actual DIVA contract.

  • Attempts to interact with the Aave V3 Pool fail due to the contract pointing to an invalid address.

As a result, the entire protocol is frozen, with no actor being able to perform any intended operations.

2. Denial of Service (DoS) for All Participants

The misconfiguration results in a Denial of Service (DoS) across all major functionalities of the protocol. This affects every participant, as outlined below:

  • Users (Liquidity Providers and Traders)

    • Unable to create pools or add liquidity, preventing them from earning yield.

    • Cannot redeem position tokens (both long and short) for their underlying collateral.

    • Cannot unwrap their collateral tokens back into the original asset, causing potential financial losses.

    • All interactions with the protocol revert due to incorrect contract references.

  • Protocol Owner / Administrators

    • Cannot register collateral tokens, preventing the expansion of supported assets.

    • Unable to claim yield generated from Aave deposits, potentially leading to locked funds in the protocol.

  • Data Providers and Oracles

    • Price settlement operations fail, as the protocol cannot correctly interact with the DIVA Protocol.

    • No pool resolutions can be processed, meaning no position tokens can be settled based on real-world events.

As a result, no participant can successfully use the protocol in any capacity.

3. The Necessity of Redeployment and Migration

Since the constructor is executed only once during contract deployment, this issue cannot be patched via an upgrade. The only way to resolve the problem is by redeploying a corrected version of the AaveDIVAWrapper contract with the correct parameter order.

This introduces additional challenges:

  • All existing user funds and liquidity pools must be migrated to the new contract, requiring users to take action.

  • Any previously issued position tokens will likely become invalid, requiring new tokens to be minted.

  • Time and gas costs for developers, users, and liquidity providers increase, leading to inefficiencies.

If the protocol is not swiftly redeployed with a clear migration plan, the ecosystem may suffer long-term damage due to user frustration and loss of confidence.

  • Why This Issue Should Be Considered Critical

    • The incorrect constructor parameter ordering in AaveDIVAWrapper is not a minor oversight. It is a critical vulnerability that completely disrupts the protocol’s functionality and can have severe financial, operational, and reputational consequences.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To address the critical flaw caused by the incorrect ordering of constructor parameters in the AaveDIVAWrapper contract it is suggested that, Please correct the Constructor Parameter Order and Redeploy the Aave Diva Wrapper Protocol if this version is already deployed and already in production.

  1. One Possible solution:

AaveDIVAWrapper::constructor:

- constructor(address _aaveV3Pool, address _diva, address _owner) AaveDIVAWrapperCore(_aaveV3Pool, _diva, _owner) {}
+ constructor(address _aaveV3Pool, address _diva, address _owner) AaveDIVAWrapperCore(_diva, _aaveV3Pool, _owner) {}
  1. As we know there could have so many solutions of a problem, so another one is also given below:

AaveDIVAWrapperCore::constructor:

- constructor(address diva_, address aaveV3Pool_, address owner_) Ownable(owner_) {
+ constructor(address aaveV3Pool_, address diva_, address owner_) Ownable(owner_) {
// Validate that none of the input addresses is zero to prevent unintended initialization with default addresses.
// Zero address check on `owner_` is performed in the OpenZeppelin's `Ownable` contract.
if (diva_ == address(0) || aaveV3Pool_ == address(0)) {
revert ZeroAddress();
}
// Store the addresses of DIVA Protocol and Aave V3 in storage.
_diva = diva_;
_aaveV3Pool = aaveV3Pool_;
}

Two solutions are provided, Please use either of one.

Updates

Lead Judging Commences

bube Lead Judge 9 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Constructor arguments mismatch

Support

FAQs

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