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

Liquidity Provider's Inability to Access Collateral Due to Non-Cooperative Recipients

Summary

In the AaveDIVAWrapperCore::_removeLiquidity function , where liquidity providers (LPs) are unable to withdraw their collateral if the short or long recipients do not cooperate by transferring their position tokens back to the LP. This issue effectively locks the LP's collateral within the protocol, undermining their ability to manage their funds.

Vulnerability Details

In the DIVA Protocol, when an LP adds liquidity, they specify recipients for the short and long position tokens. These tokens represent claims on the pool's collateral. The removal of liquidity requires the LP to return both types of position tokens to the AaveDIVAWrapperCore contract . The process involves the following steps:

  1. The LP must gather both short and long position tokens from the respective recipients.

  2. The function call for transferring these tokens is as follows:

// LP must hold the both Short/Long tokes to trasfer it back to this contract
_shortTokenContract.transferFrom(msg.sender, address(this), _positionTokenAmountToRemove);
_longTokenContract.transferFrom(msg.sender, address(this), _positionTokenAmountToRemove);
  1. If either the short or long recipient refuses to transfer their tokens back to the LP, the LP cannot complete the liquidity removal process.

  2. The LP's collateral remains locked in the protocol, as the removal process cannot proceed without both position tokens.

Impact

  • LPs do not have full control over their collateral.

In `test/AaveDIVAWrapper.t.sol` :

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import "forge-std/Test.sol";
import {IAaveDIVAWrapper} from "../src/interfaces/IAaveDIVAWrapper.sol";
import {IAave} from "../src/interfaces/IAave.sol";
import {IDIVA} from "../src/interfaces/IDIVA.sol";
import "../src/AaveDIVAWrapper.sol";
import "../src/interfaces/IDIVA.sol";
import "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
interface IUSDC {
function approve(address spender, uint256 amount) external;
function transfer(address recipient, uint256 amount) external;
function mint(address account, uint256 amount) external;
function balanceOf(address) external view returns (uint256);
function transferFrom(address sender, address recipient, uint256 amount) external;
function decimals() external view returns (uint8);
}
contract AaveDIVAWrapperTest is Test {
AaveDIVAWrapperCore wrapper;
// trader is LP
address trader = address(0x4D8336bDa6C11BD2a805C291Ec719BaeDD10AcB9); // in polygon
address blacklistedUser = address(0x0E6b8E34dC115a2848F585851AF23D99D09b8463); // in polygon USDC token
address shortRecipient;
address longRecipient;
bytes32 poolId;
IAave _aavePool = IAave(0x794a61358D6845594F94dc1DB02A252b5b4814aD); // in polygon
IDIVA _diva = IDIVA(0x2C9c47E7d254e493f02acfB410864b9a86c28e1D); // in polygon
IUSDC USDC = IUSDC(0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359); // in polygon
function setUp() public {
// Deploy or connect to the necessary contracts
wrapper = new AaveDIVAWrapper(address(_diva),address(_aavePool),address(this));
shortRecipient = address(0x1); // short recipent address
longRecipient = address(0x2); // log recipent address
vm.startPrank(trader);
IUSDC(USDC).approve(address(this), type(uint256).max);
vm.stopPrank();
IUSDC(USDC).transferFrom(trader, address(this), 100* 10**USDC.decimals());
wrapper.registerCollateralToken(address(USDC));
}
modifier createPool {
// Create a pool
USDC.approve(address(wrapper), type(uint256).max);
poolId = wrapper.createContingentPool(IAaveDIVAWrapper.PoolParams({
referenceAsset: "BTC/USD",
expiryTime: uint96(block.timestamp + 86400),
floor: 100,
inflection: 150,
cap: 200,
gradient: uint256(5 * 10 ** (USDC.decimals()-1)),
collateralAmount: 100 * 10**USDC.decimals(),
collateralToken: address(USDC),
dataProvider: address(0x11),
capacity: type(uint256).max,
longRecipient: longRecipient,
shortRecipient: shortRecipient,
permissionedERC721Token: address(0)
}));
_;
}
modifier addliquidity {
vm.startPrank(trader);
USDC.approve(address(wrapper), type(uint256).max);
wrapper.addLiquidity(poolId,100 *10**USDC.decimals() , longRecipient, shortRecipient);
vm.stopPrank();
_;
}
function testRemoveLiquidity() external createPool addliquidity{
IDIVA.Pool memory _pool = IDIVA(_diva).getPoolParameters(poolId);
IERC20 shorttoken = IERC20(_pool.shortToken);
IERC20 longtoken = IERC20(_pool.longToken);
vm.startPrank(trader);
shorttoken.approve(address(wrapper), type(uint256).max);
longtoken.approve(address(wrapper), type(uint256).max);
// Now LP not access to his collateral until get all the shot/long tokens from recipents
// Expect transfer amount exceeds balance
wrapper.removeLiquidity(poolId, 10*10**USDC.decimals() , address(this));
vm.stopPrank();
}
}

Run this command :

forge test --mt testRemoveLiquidity --fork-url https://polygon-mainnet.g.alchemy.com/v2/Yn7tIZfhjHHmJQbJ4SeXkpuRju0udqkR -vvv

Output :

│ ├─ [1540] 0xD081cdDB830DCF90D005D101dCE0BDe6Ea97eADd::transferFrom(0x4D8336bDa6C11BD2a805C291Ec719BaeDD10AcB9, AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 10000000 [1e7])
│ │ ├─ [1342] 0xAbf6dC88125B6977780437cfA3EB64a2Ac0665e5::transferFrom(0x4D8336bDa6C11BD2a805C291Ec719BaeDD10AcB9, AaveDIVAWrapper: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 10000000 [1e7]) [delegatecall]
│ │ │ └─ ← [Revert] revert: ERC20: transfer amount exceeds balance

Tools Used

  • Manuial Review

  • Foundry

Recommendations

  • Unified Ownership: Encourage or require LPs to designate themselves as both short and long recipients to maintain control over the position tokens.

  • Implement a mechanism that allows LPs to reclaim position tokens from recipients, possibly through a buyback or transfer agreement.

Updates

Lead Judging Commences

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

Support

FAQs

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