The LendingPool contract allows users to deposit and withdraw reserve assets, updating reserve state and interest rates via calls to functions in the ReserveLibrary. However, the design currently results in multiple (redundant) calls to update reserve interests. For example, both the LendingPool deposit/withdraw functions and the ReserveLibrary functions they invoke call updateReserveInterests, leading to three updates per deposit operation. While the functionality remains correct, the redundant updates incur unnecessary gas costs and may complicate state tracking. This inefficiency might be exploited or simply lead to higher operational costs, which affects protocol efficiency.
The LendingPool functions call ReserveLibrary functions that update reserve interest and liquidity parameters:
pragma solidity ^0.8.19;
import {Test, console} from "forge-std/Test.sol";
import {StabilityPool} from "../src/core/pools/StabilityPool/StabilityPool.sol";
import {LendingPool} from "../src/core/pools/LendingPool/LendingPool.sol";
import {DEToken} from "../src/core/tokens/DEToken.sol";
import {RToken} from "../src/core/tokens/RToken.sol";
import {DebtToken} from "../src/core/tokens/DebtToken.sol";
import {RAACToken} from "../src/core/tokens/RAACToken.sol";
import {RAACMinter} from "../src/core/minters/RAACMinter/RAACMinter.sol";
import {RAACHousePrices} from "../src/core/primitives/RAACHousePrices.sol";
import {RAACNFT} from "../src/core/tokens/RAACNFT.sol";
import {CurveCrvUSDVaultMock} from "./mocks/CurveCrvUSDVaultMock.m.sol";
import {crvUSDToken} from "../src/mocks/core/tokens/crvUSDToken.sol";
import {ERC20Mock} from "../src/mocks/core/tokens/ERC20Mock.sol";
import {ILendingPool} from "../src/interfaces/core/pools/LendingPool/ILendingPool.sol";
import {PercentageMath} from "../src/libraries/math/PercentageMath.sol";
import {WadRayMath} from "../src/libraries/math/WadRayMath.sol";
contract PoolsTest is Test {
using PercentageMath for uint256;
using WadRayMath for uint256;
StabilityPool stabilityPool;
LendingPool lendingPool;
DEToken deToken;
RToken rToken;
DebtToken debtToken;
RAACToken raacToken;
RAACMinter raacMinter;
RAACHousePrices raacHousePrices;
RAACNFT raacNft;
CurveCrvUSDVaultMock curveCrvUsdVault;
crvUSDToken crvUsdToken;
ERC20Mock erc20Mock;
address STABILITY_POOL_OWNER = makeAddr("STABILITY_POOL_OWNER");
address LENDING_POOL_OWNER = makeAddr("LENDING_POOL_OWNER");
address DETOKEN_OWNER = makeAddr("DETOKEN_OWNER");
address RTOKEN_OWNER = makeAddr("RTOKEN_OWNER");
address DEBT_TOKEN_OWNER = makeAddr("DEBT_TOKEN_OWNER");
address RAAC_TOKEN_OWNER = makeAddr("RAAC_TOKEN_OWNER");
address RAAC_MINTER_DUMMY = makeAddr("RAAC_MINTER_DUMMY");
address RAAC_HOUSE_PRICES_OWNER = makeAddr("RAAC_HOUSE_PRICES_OWNER");
address RAAC_HOUSE_PRICES_ORACLE = makeAddr("RAAC_HOUSE_PRICES_ORACLE");
address CRV_USD_TOKEN_OWENR = makeAddr("CRV_USD_TOKEN_OWENR");
address CURVE_CRV_USD_VAULT_OWENR = makeAddr("CURVE_CRV_USD_VAULT_OWENR");
address ERC20_MOCK_TOKEN_OWNER = makeAddr("ERC20_MOCK_TOKEN_OWNER");
address RAAC_NFT_TOKEN_OWNER = makeAddr("RAAC_NFT_TOKEN_OWNER");
address RAAC_MINTER_OWNER = makeAddr("RAAC_MINTER_OWNER");
address RAAC_OWNER = makeAddr("RAAC_OWNER");
address ALICE = makeAddr("ALICE");
address BOB = makeAddr("BOB");
address CHARLIE = makeAddr("CHARLIE");
address DEVIL = makeAddr("DEVIL");
address MANAGER_1 = makeAddr("MANAGER_1");
address MANAGER_2 = makeAddr("MANAGER_2");
address MANAGER_3 = makeAddr("MANAGER_3");
address MANAGER_4 = makeAddr("MANAGER_4");
address MANAGER_5 = makeAddr("MANAGER_5");
address MANAGER_6 = makeAddr("MANAGER_6");
address MANAGER_7 = makeAddr("MANAGER_7");
address MANAGER_8 = makeAddr("MANAGER_8");
address MANAGER_9 = makeAddr("MANAGER_9");
address MANAGER_10 = makeAddr("MANAGER_10");
uint256 initialPrimeRate = 5e27;
uint256 initialRaacSwapTaxRateInBps = 200;
uint256 initialRaacBurnTaxRateInBps = 150;
function setUp() public {
vm.warp(block.timestamp + 1 days + 1);
vm.startPrank(CRV_USD_TOKEN_OWENR);
crvUsdToken = new crvUSDToken(CRV_USD_TOKEN_OWENR);
vm.stopPrank();
vm.startPrank(CURVE_CRV_USD_VAULT_OWENR);
curveCrvUsdVault = new CurveCrvUSDVaultMock(address(crvUsdToken), CURVE_CRV_USD_VAULT_OWENR);
vm.stopPrank();
vm.startPrank(RTOKEN_OWNER);
rToken = new RToken("R_Token_V1", "RTKNV1", RTOKEN_OWNER, address(crvUsdToken));
vm.stopPrank();
vm.startPrank(DETOKEN_OWNER);
deToken = new DEToken("DE_Token_V1", "DETKNV1", DETOKEN_OWNER, address(rToken));
vm.stopPrank();
vm.startPrank(DEBT_TOKEN_OWNER);
debtToken = new DebtToken("DEBT_TOKEN_V1", "DEBTKNV1", DEBT_TOKEN_OWNER);
vm.stopPrank();
vm.startPrank(ERC20_MOCK_TOKEN_OWNER);
erc20Mock = new ERC20Mock("ERC20_MOCK_TOKEN", "ERC20MTKN");
vm.stopPrank();
vm.startPrank(RAAC_HOUSE_PRICES_OWNER);
raacHousePrices = new RAACHousePrices(RAAC_HOUSE_PRICES_OWNER);
vm.stopPrank();
vm.startPrank(RAAC_NFT_TOKEN_OWNER);
raacNft = new RAACNFT(address(erc20Mock), address(raacHousePrices), RAAC_NFT_TOKEN_OWNER);
vm.stopPrank();
vm.startPrank(RAAC_OWNER);
raacToken = new RAACToken(RAAC_OWNER, initialRaacSwapTaxRateInBps, initialRaacBurnTaxRateInBps);
vm.stopPrank();
vm.startPrank(LENDING_POOL_OWNER);
lendingPool = new LendingPool(
address(crvUsdToken),
address(rToken),
address(debtToken),
address(raacNft),
address(raacHousePrices),
initialPrimeRate
);
vm.stopPrank();
vm.startPrank(STABILITY_POOL_OWNER);
stabilityPool = new StabilityPool(STABILITY_POOL_OWNER);
vm.stopPrank();
vm.startPrank(RAAC_MINTER_OWNER);
raacMinter = new RAACMinter(address(raacToken), address(stabilityPool), address(lendingPool), RAAC_MINTER_OWNER);
vm.stopPrank();
vm.startPrank(RAAC_OWNER);
raacToken.setMinter(address(raacMinter));
vm.stopPrank();
vm.startPrank(LENDING_POOL_OWNER);
lendingPool.setCurveVault(address(curveCrvUsdVault));
vm.stopPrank();
vm.startPrank(RTOKEN_OWNER);
rToken.setReservePool(address(lendingPool));
vm.stopPrank();
vm.startPrank(DEBT_TOKEN_OWNER);
debtToken.setReservePool(address(lendingPool));
vm.stopPrank();
vm.startPrank(STABILITY_POOL_OWNER);
stabilityPool.initialize(
address(rToken),
address(deToken),
address(raacToken),
address(raacMinter),
address(crvUsdToken),
address(lendingPool)
);
vm.stopPrank();
crvUsdToken.mint(address(lendingPool), 1000_000_000e18);
}
function testLendingPoolVerboseReserveStateUpdate() public {
uint256 depositAmount = 1e18;
vm.startPrank(ALICE);
crvUsdToken.mint(ALICE, depositAmount);
vm.stopPrank();
(,,,,,,,, uint256 prevReserveUpdateTimes) = lendingPool.reserve();
(,,,,,,,, uint256 prevRateUpdateTimes) = lendingPool.rateData();
console.log("modified reserve and rate data before update... ");
console.log("reserve update times: ", prevReserveUpdateTimes);
console.log("rate update times: ", prevRateUpdateTimes);
vm.startPrank(ALICE);
crvUsdToken.approve(address(lendingPool), depositAmount);
lendingPool.deposit(depositAmount);
vm.stopPrank();
(,,,,,,,, uint256 currReserveUpdateTimes) = lendingPool.reserve();
(,,,,,,,, uint256 currRateUpdateTimes) = lendingPool.rateData();
console.log("modified reserve and rate data after udpate... ");
console.log("reserve update times: ", currReserveUpdateTimes);
console.log("rate update times: ", currRateUpdateTimes);
assertEq(currReserveUpdateTimes, 3);
assertEq(currRateUpdateTimes, 3);
assertEq(prevReserveUpdateTimes, 0);
assertEq(prevRateUpdateTimes, 0);
assertNotEq(prevReserveUpdateTimes, currReserveUpdateTimes);
assertNotEq(prevRateUpdateTimes, currRateUpdateTimes);
assert(currReserveUpdateTimes > prevReserveUpdateTimes);
assert(currRateUpdateTimes > prevRateUpdateTimes);
}
}
The test confirms that after a deposit, the update counters have increased by 3, indicating that updateReserveInterests was called three times in the deposit process.
To eliminate redundant calls, streamline the update of reserve interests so that each deposit or withdrawal operation updates the reserve state only once.
function deposit(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
// Update the reserve state before the deposit
// @info: verbose function already called inside ReserveLibrary.deposit
- ReserveLibrary.updateReserveState(reserve, rateData);
// Perform the deposit through ReserveLibrary
uint256 mintedAmount = ReserveLibrary.deposit(reserve, rateData, amount, msg.sender);
// Rebalance liquidity after deposit
_rebalanceLiquidity();
emit Deposit(msg.sender, amount, mintedAmount);
}
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
// Update the reserve state before the withdrawal
// @info: verbose function already called inside ReserveLibrary.withdraw
- ReserveLibrary.updateReserveState(reserve, rateData);
// Ensure sufficient liquidity is available
_ensureLiquidity(amount);
// Perform the withdrawal through ReserveLibrary
(uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) = ReserveLibrary.withdraw(
reserve, // ReserveData storage
rateData, // ReserveRateData storage
amount, // Amount to withdraw
msg.sender // Recipient
);
// Rebalance liquidity after withdrawal
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}
Implementing these modifications will reduce unnecessary gas consumption and simplify state management in deposit and withdrawal operations, thereby enhancing the efficiency and maintainability of the lending protocol.