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";
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;
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);
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);
}
function test_RTokenScalingIssue() public {
uint256 initialDeposit = 4000e18;
uint256 housePrice = 3000e18;
raacHousePrices.setHousePrice(TOKEN_ID, housePrice);
address lender = makeAddr("lender");
address borrower = makeAddr("borrower");
crvusd.mint(lender, initialDeposit);
crvusd.mint(borrower, housePrice);
assertEq(crvusd.balanceOf(lender), initialDeposit);
assertEq(crvusd.balanceOf(borrower), housePrice);
vm.startPrank(lender);
crvusd.approve(address(lendingPool), initialDeposit);
lendingPool.deposit(initialDeposit);
vm.stopPrank();
uint256 rTokenBalanceLender = rToken.balanceOf(lender);
uint256 deTokenBalanceLender = deToken.balanceOf(lender);
console2.log("rTokenBalanceLender after deposit to LendingPool", rTokenBalanceLender);
console2.log("deTokenBalanceLender after deposit to LendingPool", deTokenBalanceLender);
console2.log("-----------------------------------------------------");
assertEq(rTokenBalanceLender, initialDeposit);
vm.startPrank(borrower);
crvusd.approve(address(raacNFT), housePrice);
raacNFT.mint(TOKEN_ID, housePrice);
assertEq(raacNFT.balanceOf(borrower), 1);
raacNFT.approve(address(lendingPool), TOKEN_ID);
lendingPool.depositNFT(TOKEN_ID);
assertEq(raacNFT.balanceOf(address(lendingPool)), 1);
lendingPool.borrow(housePrice);
vm.stopPrank();
vm.warp(block.timestamp + 100 days);
lendingPool.updateState();
rTokenBalanceLender = rToken.balanceOf(lender);
uint256 rTokenBalanceLenderSnapshot = rTokenBalanceLender;
deTokenBalanceLender = deToken.balanceOf(lender);
console2.log("rTokenBalanceLender after borrow:", rTokenBalanceLender);
console2.log("deTokenBalanceLender after borrow:", deTokenBalanceLender);
console2.log("-----------------------------------------------------");
assertGt(rTokenBalanceLender, initialDeposit, "Lender should have more RTokens when interest is accrued");
uint256 transferAmount = 1000e18;
vm.startPrank(lender);
rToken.approve(address(stabilityPool), transferAmount);
stabilityPool.deposit(transferAmount);
rTokenBalanceLender = rToken.balanceOf(lender);
uint256 stabilityPoolRTokenBalance = rToken.balanceOf(address(stabilityPool));
deTokenBalanceLender = deToken.balanceOf(lender);
assertApproxEqRel(stabilityPoolRTokenBalance, transferAmount, 1);
console2.log("rTokenBalanceLender after deposit to StabilityPool:", rTokenBalanceLender);
console2.log("stabilityPoolRTokenBalance after deposit to StabilityPool:", stabilityPoolRTokenBalance);
console2.log("deTokenBalanceLender after deposit to StabilityPool:", deTokenBalanceLender);
console2.log("-----------------------------------------------------");
deToken.approve(address(stabilityPool), deTokenBalanceLender);
stabilityPool.withdraw(deTokenBalanceLender);
vm.stopPrank();
rTokenBalanceLender = rToken.balanceOf(lender);
stabilityPoolRTokenBalance = rToken.balanceOf(address(stabilityPool));
deTokenBalanceLender = deToken.balanceOf(lender);
console2.log("rTokenBalanceLender after withdraw from StabilityPool:", rTokenBalanceLender);
console2.log("stabilityPoolRTokenBalance after withdraw from StabilityPool:", stabilityPoolRTokenBalance);
console2.log("deTokenBalanceLender after withdraw from StabilityPool:", deTokenBalanceLender);
console2.log("-----------------------------------------------------");
assertLt(rTokenBalanceLender, rTokenBalanceLenderSnapshot, "Should have less RTokens after withdraw");
assertEq(deTokenBalanceLender, 0);
assertGt(stabilityPoolRTokenBalance, 0);
uint256 debt = debtToken.balanceOf(borrower);
vm.startPrank(borrower);
crvusd.mint(borrower, debt);
crvusd.approve(address(lendingPool), debt);
lendingPool.repay(debt);
vm.stopPrank();
assertEq(debtToken.balanceOf(borrower), 0);
vm.startPrank(lender);
rToken.approve(address(lendingPool), type(uint256).max);
lendingPool.withdraw(type(uint256).max);
vm.stopPrank();
rTokenBalanceLender = rToken.balanceOf(lender);
deTokenBalanceLender = deToken.balanceOf(lender);
stabilityPoolRTokenBalance = rToken.balanceOf(address(stabilityPool));
uint256 crvusdBalanceLender = crvusd.balanceOf(lender);
uint256 crvusdStabilityPoolBalance = crvusd.balanceOf(address(stabilityPool));
uint256 missingCrvusd = rTokenBalanceLenderSnapshot - crvusdBalanceLender;
console2.log("rTokenBalanceLender after withdraw from LendingPool:", rTokenBalanceLender);
console2.log("deTokenBalanceLender after withdraw from LendingPool:", deTokenBalanceLender);
console2.log("crvusdBalanceLender after withdraw from LendingPool:", crvusdBalanceLender);
console2.log("crvusdStabilityPoolBalance after withdraw from LendingPool:", crvusdStabilityPoolBalance);
console2.log("stabilityPoolRTokenBalance after withdraw from LendingPool:", stabilityPoolRTokenBalance);
console2.log("-----------------------------------------------------");
console2.log("missingCrvusd:", missingCrvusd);
assertLt(crvusdBalanceLender, rTokenBalanceLenderSnapshot);
assertEq(missingCrvusd, stabilityPoolRTokenBalance);
assertEq(crvusdStabilityPoolBalance, 0);
assertEq(rTokenBalanceLender, 0);
assertEq(deTokenBalanceLender, 0);
}
}
Instead of trying to fix individual scaling issues, the RToken should be completely refactored to follow Aave's battle-tested token design. I can highly recommend reading this Documentation as it gives you a more detailed step by step guide (docs)[]. There is no need to reinvent the wheel here so just use the existing structure of the AToken.