Core Contracts

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

Incorrect Asset Transfer Flow Leading to DOS when interacting with Curve Vault via Deposits

Summary

The _depositIntoVault function in the LendingPool contract attempts to deposit assets into Curve vault without first transferring them from the reserveRTokenAddress. The assets need to be transferred to the lending pool contract using RToken.transferAsset() before depositing into the Curve vault.

Vulnerability Details

function _depositIntoVault(uint256 amount) internal {
// Current implementation - Will fail as contract doesn't own assets
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}

The issue occurs because:

  1. Assets are held by reserveRTokenAddress (RToken contract)

function deposit(ReserveData storage reserve,ReserveRateData storage rateData,uint256 amount,address depositor) internal returns (uint256 amountMinted) {
if (amount < 1) revert InvalidAmount();
// Update reserve interests
updateReserveInterests(reserve, rateData);
// Transfer asset from caller to the RToken contract
IERC20(reserve.reserveAssetAddress).safeTransferFrom(
msg.sender, // from
++ reserve.reserveRTokenAddress, // to
amount // amount
);
// ... some codes here
return amountMinted;
}
  1. LendingPool tries to deposit directly to Curve vault without first obtaining the assets

  2. The approve() and deposit() calls will fail due to insufficient balance

Impact

  1. Rebalancing operations will fail causing DOS

  2. Asset locked in the vault when curve vault is not immediately added after deployment

Tools Used

Manual/Hardhat

##POC

Mock Curve vault

contract MockCurveVault is Ownable {
IERC20 public reserveAsset;
mapping(address => uint256) public balances;
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount);
constructor(address _reserveAsset) Ownable(msg.sender){
reserveAsset = IERC20(_reserveAsset);
}
function deposit(uint256 amount, address recipient) external {
require(amount > 0, "Amount must be greater than 0");
reserveAsset.transferFrom(msg.sender, address(this), amount);
balances[recipient] += amount;
emit Deposit(recipient, amount);
}
function withdraw(uint256 amount, address to, address from, uint256, address[] calldata) external {
require(amount > 0, "Amount must be greater than 0");
require(balances[from] >= amount, "Insufficient balance");
balances[from] -= amount;
reserveAsset.transfer(to, amount);
emit Withdraw(from, amount);
}
function getBalance(address user) external view returns (uint256) {
return balances[user];
}
}

In LendingPool.test.js

beforeEach(async function () {
[owner, user1, user2, user3, attacker, innocentUser] = await ethers.getSigners();
// other codes ..
const CurveVault = await ethers.getContractFactory("MockCurveVault");
curveVault = await CurveVault.deploy(crvusd.target);
// other codes ...
const LendingPool = await ethers.getContractFactory("LendingPool");
lendingPool = await LendingPool.deploy(
crvusd.target,
rToken.target,
debtToken.target,
raacNFT.target,
raacHousePrices.target,
initialPrimeRate
);
// other codes
});
describe("Deposit fails", function () {
it("should revert when depositing in curve without owning asset", async function () {
const depositAmount = ethers.parseEther("100");
await lendingPool.setCurveVault(curveVault.target);
await expect(lendingPool.connect(user2).deposit(depositAmount)).to.be.reverted;
});
});

Recommendations

Implement proper asset transfer sequence:

function _depositIntoVault(uint256 amount) internal {
// First transfer assets from RToken to LendingPool
IRToken(reserve.reserveRTokenAddress).transferAsset(address(this), amount);
// Then approve and deposit into Curve vault
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

LendingPool::_depositIntoVault and _withdrawFromVault don't transfer tokens between RToken and LendingPool, breaking Curve vault interactions

Support

FAQs

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

Give us feedback!