Lending pool reserve liquidity can be incorrectly accounted due to transferring accrued dust.
Lending pool borrower's debt accruing is compounding, whereas the liquidity rate is linear.
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));
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_LiquidityIsIncorrectlyAccounted() 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 nftTokenId = 1;
uint256 nftPrice = 2000e18;
vm.prank(owner);
raacHousePrices.setHousePrice(nftTokenId, nftPrice);
address bob = makeAddr("Bob");
crvUSD.mint(bob, nftPrice);
vm.startPrank(bob);
crvUSD.approve(address(raacNft), nftPrice);
raacNft.mint(nftTokenId, nftPrice);
raacNft.approve(address(lendingPool), nftTokenId);
lendingPool.depositNFT(nftTokenId);
vm.stopPrank();
uint256 borrowAmount = 1000e18;
vm.prank(bob);
lendingPool.borrow(borrowAmount);
vm.warp(block.timestamp + 365 days);
lendingPool.updateState();
uint256 bobDebt = lendingPool.getUserDebt(bob);
crvUSD.mint(bob, bobDebt - borrowAmount);
vm.startPrank(bob);
crvUSD.approve(address(lendingPool), bobDebt);
lendingPool.repay(bobDebt);
vm.stopPrank();
vm.prank(alice);
lendingPool.withdraw(type(uint256).max);
(, , , uint256 totalLiquidityBefore, , , , ) = lendingPool.reserve();
uint256 underlyinCrvUsdBalanceBefore = crvUSD.balanceOf(address(rToken));
vm.prank(owner);
lendingPool.transferAccruedDust(owner, type(uint256).max);
(, , , uint256 totalLiquidityAfter, , , , ) = lendingPool.reserve();
uint256 underlyinCrvUsdBalanceAfter = crvUSD.balanceOf(address(rToken));
assertEq(totalLiquidityBefore, underlyinCrvUsdBalanceBefore);
assertGt(underlyinCrvUsdBalanceBefore, underlyinCrvUsdBalanceAfter);
assertEq(totalLiquidityBefore, totalLiquidityAfter);
}
}