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_) {
if (diva_ == address(0) || aaveV3Pool_ == address(0)) {
revert ZeroAddress();
}
_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:
_aaveV3Pool (Aave V3 Pool address)
_diva (DIVA Protocol address)
_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.
-
Step 1: Make a foundry project and put all the contracts in the src directory.
-
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).
-
Step 3: Create all necessary mocks
-
step 4: Create a test file with any name in the test directory.
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 = new AaveDIVAWrapper(address(aaveMock), address(divaMock), AAVE_DIVA_WRAPPER_OWNER);
vm.stopPrank();
}
}
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();
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();
vm.startPrank(ALICE);
vm.expectRevert();
aaveDivaWrapper.redeemPositionToken(address(65), 10e6, ALICE);
vm.stopPrank();
WToken _wTokenContract = new WToken(
string(abi.encodePacked("w", usdtMock.symbol())),
usdtMock.decimals(),
AAVE_DIVA_WRAPPER_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();
}
Step 6: Now, to run the test, run the following commands in the terminal in sequence:
forge test --mt testAaveWrapperContractCompletelyDysfunction -vvvvv
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: WToken: caller is not owner
│ └─ ← 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.
Tools Used
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.
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) {}
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.