fluid-contracts-public/test/foundry/lending/erc4626/handler.sol
2024-07-11 13:05:09 +00:00

174 lines
6.8 KiB
Solidity

// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import "forge-std/Test.sol";
import { FixedPointMathLib } from "solmate/src/utils/FixedPointMathLib.sol";
import { MockERC20 } from "solmate/src/test/utils/mocks/MockERC20.sol";
import { LiquidityCalcs } from "../../../../contracts/libraries/liquidityCalcs.sol";
import { IFluidLendingRewardsRateModel } from "../../../../contracts/protocols/lending/interfaces/iLendingRewardsRateModel.sol";
import { IFToken } from "../../../../contracts/protocols/lending/interfaces/iFToken.sol";
import { Events } from "../../../../contracts/protocols/lending/fToken/events.sol";
contract Handler is Test, Events {
using FixedPointMathLib for uint256;
IFToken token;
MockERC20 underlying;
address[] public actors;
address public admin;
address public rateModel;
address internal currentActor;
modifier useActor(uint256 actorIndexSeed) {
currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)];
vm.startPrank(currentActor);
_;
vm.stopPrank();
}
modifier useAdminActor() {
vm.startPrank(admin);
_;
vm.stopPrank();
}
constructor(IFToken token_, address underlying_, address[] memory actors_, address admin_) {
token = token_;
underlying = MockERC20(underlying_);
actors = actors_;
admin = admin_;
}
function deposit(
uint256 assets,
address receiver,
uint256 actorIndexSeed
) external useActor(actorIndexSeed) returns (uint256 shares) {
assets = bound(assets, 1e4, 1e18);
underlying.mint(currentActor, assets);
underlying.approve(address(token), type(uint256).max);
(, , , , , , , , uint256 currentTokenExchangePrice) = IFToken(address(token)).getData();
// solidity rounds down by default
uint256 expectedShares = (assets * LiquidityCalcs.EXCHANGE_PRICES_PRECISION) / currentTokenExchangePrice;
uint256 assetsBefore = underlying.balanceOf(currentActor);
uint256 sharesBefore = token.balanceOf(currentActor);
token.deposit(assets, currentActor);
uint256 assetsAfter = underlying.balanceOf(currentActor);
uint256 sharesAfter = token.balanceOf(currentActor);
// equal because expectedAssetsReceived is already rounded down as default in Solidity
assertEq(sharesAfter - sharesBefore, expectedShares);
assertEq(assetsBefore - assetsAfter, assets);
}
function mint(uint256 shares, uint256 actorIndexSeed) external useActor(actorIndexSeed) {
shares = bound(shares, 1e4, 1e18);
uint256 consumeAssets = token.previewMint(shares);
underlying.mint(currentActor, consumeAssets);
underlying.approve(address(token), type(uint256).max);
(, , , , , , , , uint256 currentTokenExchangePrice) = IFToken(address(token)).getData();
uint256 expectedAssets = (shares * currentTokenExchangePrice) / LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
uint256 assetsBefore = underlying.balanceOf(currentActor);
uint256 sharesBefore = token.balanceOf(currentActor);
token.mint(shares, currentActor);
uint256 assetsAfter = underlying.balanceOf(currentActor);
uint256 sharesAfter = token.balanceOf(currentActor);
assertEq(sharesAfter - sharesBefore, shares);
assertTrue(assetsBefore - assetsAfter >= expectedAssets);
}
function withdraw(
uint256 assets,
address receiver,
address owner,
uint256 actorIndexSeed
) external useActor(actorIndexSeed) {
assets = bound(assets, 1e4, 1e18);
underlying.mint(currentActor, assets);
underlying.approve(address(token), type(uint256).max);
(, , , , , , , , uint256 currentTokenExchangePrice) = IFToken(address(token)).getData();
uint256 sharesToBurn_ = assets.mulDivUp(LiquidityCalcs.EXCHANGE_PRICES_PRECISION, currentTokenExchangePrice);
if (token.balanceOf(currentActor) < sharesToBurn_) return;
uint256 expectedBurnedShares = (assets * LiquidityCalcs.EXCHANGE_PRICES_PRECISION) / currentTokenExchangePrice;
uint256 assetsBefore = underlying.balanceOf(currentActor);
uint256 sharesBefore = token.balanceOf(currentActor);
token.withdraw(assets, currentActor, currentActor);
uint256 assetsAfter = underlying.balanceOf(currentActor);
uint256 sharesAfter = token.balanceOf(currentActor);
// assert that the actual burned shares are greater than or equal to the expected amount, confirming rounding up
assertTrue(sharesBefore - sharesAfter >= expectedBurnedShares);
assertEq(assetsAfter - assetsBefore, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner,
uint256 actorIndexSeed
) external useActor(actorIndexSeed) {
shares = bound(shares, 1e4, 1e18);
uint256 sharesBalance = token.balanceOf(currentActor);
if (shares > sharesBalance) {
shares = sharesBalance;
}
if (token.previewRedeem(sharesBalance) == 0) return;
(, , , , , , , , uint256 currentTokenExchangePrice) = IFToken(address(token)).getData();
uint256 assetsBefore = underlying.balanceOf(currentActor);
uint256 sharesBefore = token.balanceOf(currentActor);
token.redeem(shares, currentActor, currentActor);
uint256 assetsAfter = underlying.balanceOf(currentActor);
uint256 sharesAfter = token.balanceOf(currentActor);
uint256 expectedAssetsReceived = (shares * currentTokenExchangePrice) /
LiquidityCalcs.EXCHANGE_PRICES_PRECISION;
// equal because expectedAssetsReceived is already rounded down as default in Solidity
assertEq(assetsAfter - assetsBefore, expectedAssetsReceived);
// the combination of rounding down in previewRedeem and rounding up in executeWithdraw does not lead to a situation where the amount of burned shares is higher than the input shares.
assertEq(sharesBefore - sharesAfter, shares);
}
function updateRewards() external useAdminActor {
//NOTE: function changes rateModel address to zero address in order to get the lowest rewards in this case 0 .
vm.expectEmit(true, true, true, true);
emit LogUpdateRewards(IFluidLendingRewardsRateModel(address(0)));
token.updateRewards(IFluidLendingRewardsRateModel(address(0)));
}
function initialDeposit(uint256 assets_) external useAdminActor {
assets_ = bound(assets_, 1e4, 2e18);
underlying.mint(admin, assets_);
underlying.approve(address(token), type(uint256).max);
token.deposit(assets_, admin);
}
function updateRates() public {
vm.expectEmit(true, false, false, false);
emit LogUpdateRates(0, 0);
token.updateRates();
}
}