Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Wrong withdraw in LendingPool#_withdrawFromVault()

Summary

Wrong withdraw funds in LendingPool#_withdrawFromVault() cause transaction reverts and prevent funds from being accessible for withdrawals, disrupting protocol functionality.

Vulnerability Details

The _withdrawFromVault() is designed to withdraw funds from the Curve vault when the reserve lacks enough funds.

function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
...
// Ensure sufficient liquidity is available
@> _ensureLiquidity(amount);
...
}
function _ensureLiquidity(uint256 amount) internal {
...
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
// Withdraw required amount from the Curve vault
@> _withdrawFromVault(requiredAmount);
}
}

However, this function sends the withdrawn funds to this (LendingPool) instead of reserve.reserveRTokenAddress, which actually holds user liquidity. Additionally, the function incorrectly specifies msg.sender as the vault share owner instead of this, even though the Curve vault shares are owned by the LendingPool contract (since _depositIntoVault() deposits funds from this contract).

curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));

Proof Of Code

Make MockCurveCrvUSDVault

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "../interfaces/curve/ICurveCrvUSDVault.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockCurveCrvUSDVault is ERC20 {
address asset;
constructor(address _asset) ERC20("Mock curveCrvUSD", "curveCrvUSD") {
asset = _asset;
}
function deposit(uint256 assets, address receiver) external returns (uint256 shares) {
IERC20(asset).transferFrom(msg.sender, address(this), assets);
_mint(receiver, assets);
shares = assets;
}
function withdraw(
uint256 assets,
address receiver,
address owner,
uint256 maxLoss,
address[] calldata strategies
) external returns (uint256 shares) {
IERC20(asset).transfer(receiver, assets);
_burn(owner, assets);
shares = assets;
}
}

Update LendingPool.test.js

describe("LendingPool", function () {
...
beforeEach(async function () {
...
const LendingPool = await ethers.getContractFactory("LendingPool");
lendingPool = await LendingPool.deploy(
crvusd.target,
rToken.target,
debtToken.target,
raacNFT.target,
raacHousePrices.target,
initialPrimeRate
);
+ const scrvCurve = await ethers.getContractFactory("MockCurveCrvUSDVault");
+ const curveVault = await scrvCurve.deploy(crvusd.target);
+ await lendingPool.connect(owner).setCurveVault(curveVault);
...
+ const depositAmount = ethers.parseEther("1000");
+ await crvusd.connect(user2).approve(lendingPool.target, depositAmount);
+ await lendingPool.connect(user2).deposit(depositAmount);+
+ const withdrawAmount = ethers.parseEther("800");
+ await lendingPool.connect(user2).withdraw(withdrawAmount);
});
});

As seen above, withdraw transaction reverts in _withdrawFromVault().

Impact

Core function - withdraw and borrow reverts

Tools Used

manual

Recommendations

- curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
+ curveVault.withdraw(amount, reserve.reserveRTokenAddress, address(this), 0, new address[](0));
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_withdrawFromVault incorrectly uses msg.sender instead of address(this) as the owner parameter, causing vault withdrawals to fail

Support

FAQs

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