Core Contracts

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

Denial of Service (DoS) Risk in redeemFromMarket Function

Summary

The redeemFromMarket function lacks a balance check before transferring rewards, which can lead to a Denial of Service (DoS) attack. If the contract's balance of raacToken is insufficient, redemption transactions will fail, preventing users from reclaiming their deposits.

Vulnerability Details

  • The function calculates reward using calculateReward(marketId, amount).

  • It then attempts to transfer raacToken to the user without checking if the contract holds enough balance.

  • If the contract’s raacToken balance is insufficient, the safeTransfer call will revert, making redemption impossible.

  • This can occur if:

    • The contract does not receive enough reward tokens initially.

    • Rewards are miscalculated, leading to an overcommitment.

    • A malicious actor drains the contract’s raacToken balance before redemptions occur.

Test:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "contracts/core/collectors/FeeCollector.sol";
import "contracts/core/tokens/RAACToken.sol";
import "contracts/core/tokens/veRAACToken.sol";
import "forge-std/Test.sol";
import "contracts/mocks/core/oracles/TestRAACHousePriceOracle.sol";
import "contracts/mocks/core/tokens/crvUSDToken.sol";
import "contracts/mocks/core/tokens/MockUSDC.sol";
import "contracts/core/tokens/RToken.sol";
import "contracts/core/tokens/DebtToken.sol";
import "contracts/core/tokens/RAACNFT.sol";
import "contracts/core/primitives/RAACHousePrices.sol";
import "contracts/core/pools/LendingPool/LendingPool.sol";
import "forge-std/Console2.sol";
import "contracts/interfaces/core/pools/LendingPool/ILendingPool.sol";
import "contracts/core/pools/StabilityPool/StabilityPool.sol";
import "contracts/core/pools/StabilityPool/NFTLiquidator.sol";
import "contracts/core/pools/StabilityPool/MarketCreator.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract Pool2 is Test{
RAACToken public raccToken;
veRAACToken public veraacToken;
crvUSDToken public crv;
RToken public rToken;
DebtToken public debtToken;
RAACNFT public raccNFT;
TestRAACHousePriceOracle public oracle;
RAACHousePrices public housePrice;
MockUSDC public usdc;
LendingPool public pool;
NFTLiquidator nftLq;
StabilityPool sbPool;
TransparentUpgradeableProxy proxy;
MarketCreator public market;
uint256 NFTTokenId = 1;
address alice = address(0x1001);
address bob = address(0x1002);
address candy = address(0x1003);
function setUp() public {
crv = new crvUSDToken(address(this));
rToken = new RToken("rt","rt",address(this),address(crv));
debtToken = new DebtToken("db","db",address(this));
address router;
usdc = new MockUSDC(1_000_000e6);
housePrice = new RAACHousePrices(address(this));
oracle = new TestRAACHousePriceOracle(router,bytes32('1'),address(housePrice));
raccNFT = new RAACNFT(address(usdc),address(housePrice),address(this));
pool = new LendingPool(address(crv),address(rToken),address(debtToken),address(raccNFT),address(housePrice),1e26);
rToken.setReservePool(address(pool));
housePrice.setOracle(address(this));
debtToken.setReservePool(address(pool));
nftLq = new NFTLiquidator(address(crv),address(raccNFT),address(this),50);
sbPool = new StabilityPool(address(this));
//add proxy.
proxy = new TransparentUpgradeableProxy(address(sbPool),address(this),"");
raccToken = new RAACToken(address(this),1_000,1_000);
veraacToken = new veRAACToken(address(raccToken));
market = new MarketCreator(address(this),address(raccToken),address(crv));
}
function testRewardRunOut() public {
// Setup minter
raccToken.setMinter(address(this));
raccToken.whitelistAddress(address(market));
raccToken.mint(address(market),100e18);
//owner create market.
market.createMarket(address(crv), 1 days, 100e18);
//alice deposit.
crv.mint(alice,10e18);
vm.startPrank(alice);
crv.approve(address(market), 10e18);
market.participateInMarket(1,10e18);
skip(2 days);
//alice redeem
market.redeemFromMarket(1);
//bob deposit.
crv.mint(bob,10e18);
vm.startPrank(bob);
crv.approve(address(market), 10e18);
market.participateInMarket(1,10e18);
vm.stopPrank();
skip(2 days);
//bob redeem
vm.prank(bob);
market.redeemFromMarket(1);
}

Out:

│ ├─ [23380] crvUSDToken::transfer(0x0000000000000000000000000000000000001002, 10000000000000000000 [1e19])
│ │ ├─ emit Transfer(from: MarketCreator: [0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7], to: 0x0000000000000000000000000000000000001002, value: 10000000000000000000 [1e19])
│ │ └─ ← [Return] true
│ ├─ [4922] RAACToken::transfer(0x0000000000000000000000000000000000001002, 100000000000000000000 [1e20])
│ │ └─ ← [Revert] ERC20InsufficientBalance(0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7, 0, 10000000000000000000 [1e19])
│ └─ ← [Revert] ERC20InsufficientBalance(0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7, 0, 10000000000000000000 [1e19])
└─ ← [Revert] ERC20InsufficientBalance(0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7, 0, 10000000000000000000 [1e19])
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 4.33ms (428.13µs CPU time)
Ran 1 test suite in 1.31s (4.33ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in tests/Pool2.t.sol:Pool2
[FAIL: ERC20InsufficientBalance(0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7, 0, 10000000000000000000 [1e19])] testRewardRunOut() (gas: 703209)
Encountered a total of 1 failing tests, 0 tests succeeded

Impact

Dos

Tools Used

Foundry

Recommendations

check reward assets balance before transfer to user

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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