mirror of
https://github.com/Instadapp/fluid-contracts-public.git
synced 2024-07-29 21:57:37 +00:00
272 lines
12 KiB
Solidity
272 lines
12 KiB
Solidity
//SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.21;
|
|
|
|
import { LiquidityUserModuleBaseTest } from "../liquidity/userModule/liquidityUserModuleBaseTest.t.sol";
|
|
import { Structs as ResolverStructs } from "../../../contracts/periphery/resolvers/liquidity/structs.sol";
|
|
import { FluidEthenaRateConfigHandler } from "../../../contracts/config/ethenaRateHandler/main.sol";
|
|
import { Events } from "../../../contracts/config/ethenaRateHandler/events.sol";
|
|
import { BigMathMinified } from "../../../contracts/libraries/bigMathMinified.sol";
|
|
import { Error } from "../../../contracts/config/error.sol";
|
|
import { ErrorTypes } from "../../../contracts/config/errorTypes.sol";
|
|
import { FluidReserveContract } from "../../../contracts/reserve/main.sol";
|
|
import { FluidReserveContractProxy } from "../../../contracts/reserve/proxy.sol";
|
|
import { IFluidReserveContract } from "../../../contracts/reserve/interfaces/iReserveContract.sol";
|
|
import { IFluidLiquidity } from "../../../contracts/liquidity/interfaces/iLiquidity.sol";
|
|
import { Structs as AdminModuleStructs } from "../../../contracts/liquidity/adminModule/structs.sol";
|
|
import { FluidLiquidityAdminModule } from "../../../contracts/liquidity/adminModule/main.sol";
|
|
import { IStakedUSDe } from "../../../contracts/config/ethenaRateHandler/interfaces/iStakedUSDe.sol";
|
|
import { IFluidVaultT1 } from "../../../contracts/protocols/vault/interfaces/iVaultT1.sol";
|
|
import { LiquiditySlotsLink } from "../../../contracts/libraries/liquiditySlotsLink.sol";
|
|
import { FluidVaultFactory } from "../../../contracts/protocols/vault/factory/main.sol";
|
|
|
|
import "forge-std/console2.sol";
|
|
|
|
contract FluidEthenaRateConfigHandlerTests is LiquidityUserModuleBaseTest, Events {
|
|
FluidReserveContract reserveContractImpl;
|
|
FluidReserveContract reserveContract; //proxy
|
|
FluidEthenaRateConfigHandler configHandler;
|
|
|
|
IStakedUSDe internal constant SUSDE_TOKEN = IStakedUSDe(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497);
|
|
|
|
uint256 internal constant RATE_PERCENT_MARGIN = 1000; // 10%
|
|
uint256 internal constant MAX_REWARDS_DELAY = 15 minutes;
|
|
uint256 internal constant UTILIZATION_PENALTY_START = 9000; // 90%
|
|
uint256 internal constant UTILIZATION100_PENALTY_PERCENT = 300; // 3%
|
|
|
|
// use existing vault on fork for simplicity, doesn't matter which collateral token the vault has to check
|
|
// for borrow rate magnifier being updated. e.g. ETH/USDC vault 0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C
|
|
address internal constant VAULT = 0xeAbBfca72F8a8bf14C4ac59e69ECB2eB69F0811C;
|
|
|
|
address internal constant GOVERNANCE = 0x2386DC45AdDed673317eF068992F19421B481F4c;
|
|
|
|
address internal constant VAULT_FACTORY = 0x324c5Dc1fC42c7a4D43d92df1eBA58a54d13Bf2d;
|
|
|
|
function setUp() public virtual override {
|
|
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
|
|
vm.rollFork(19491337);
|
|
|
|
super.setUp();
|
|
|
|
// deploy reserve contract
|
|
reserveContractImpl = new FluidReserveContract(IFluidLiquidity(address(liquidity)));
|
|
reserveContract = FluidReserveContract(
|
|
payable(new FluidReserveContractProxy(address(reserveContractImpl), new bytes(0)))
|
|
);
|
|
address[] memory authsRebalancers = new address[](1);
|
|
authsRebalancers[0] = alice;
|
|
reserveContract.initialize(authsRebalancers, authsRebalancers, admin);
|
|
|
|
// deploy configHandler. constructor params:
|
|
// constructor(
|
|
// IFluidReserveContract reserveContract_,
|
|
// IFluidLiquidity liquidity_,
|
|
// IFluidVaultT1 vault_,
|
|
// IStakedUSDe stakedUSDe_,
|
|
// address borrowToken_,
|
|
// uint256 ratePercentMargin_,
|
|
// uint256 maxRewardsDelay_,
|
|
// uint256 utilizationPenaltyStart_,
|
|
// uint256 utilization100PenaltyPercent_
|
|
// )
|
|
configHandler = new FluidEthenaRateConfigHandler(
|
|
IFluidReserveContract(address(reserveContract)),
|
|
IFluidLiquidity(address(liquidity)),
|
|
IFluidVaultT1(VAULT),
|
|
SUSDE_TOKEN,
|
|
address(USDC),
|
|
RATE_PERCENT_MARGIN,
|
|
MAX_REWARDS_DELAY,
|
|
UTILIZATION_PENALTY_START,
|
|
UTILIZATION100_PENALTY_PERCENT
|
|
);
|
|
|
|
// make configHandler an auth at Vault
|
|
vm.prank(GOVERNANCE);
|
|
FluidVaultFactory(VAULT_FACTORY).setVaultAuth(VAULT, address(configHandler), true);
|
|
}
|
|
|
|
function test_currentMagnifier() public {
|
|
assertEq(configHandler.currentMagnifier(), 1e4);
|
|
}
|
|
|
|
function test_getSUSDeYieldRate() public {
|
|
// rate at given block should be
|
|
// vestingAmount = 84310699350539958877622
|
|
// totalAssets = 399083115635905571965778772
|
|
// so 84310699350539958877622 / 399083115635905571965778772 = 0,00021126100315268389601938021 per 8 hours yield
|
|
// so 0,00021126100315268389601938021 * 365 * 3 = 0,23133079845218886614122133035 yearly yield (23.13%)
|
|
assertEq(SUSDE_TOKEN.vestingAmount(), 84310699350539958877622);
|
|
assertEq(SUSDE_TOKEN.totalAssets(), 399083115635905571965778772);
|
|
assertEq(configHandler.getSUSDeYieldRate(), 23133079845218885955);
|
|
}
|
|
|
|
function test_getSUSDeYieldRateWhenAboveMaxDelay() public {
|
|
vm.warp(block.timestamp + 9 hours);
|
|
|
|
assertEq(configHandler.getSUSDeYieldRate(), 0);
|
|
}
|
|
|
|
function test_rebalance() public {
|
|
_simulateLiquidityUtilizationAndBorrowRate(5000, 1200);
|
|
|
|
assertEq(configHandler.currentMagnifier(), 1e4);
|
|
assertEq(configHandler.calculateMagnifier(), 17349);
|
|
|
|
// check updates borrow rate magnifier as expected
|
|
// check emits event
|
|
vm.expectEmit(true, true, true, true);
|
|
emit LogUpdateBorrowRateMagnifier(1e4, 17349);
|
|
|
|
vm.prank(alice);
|
|
configHandler.rebalance();
|
|
|
|
assertEq(configHandler.currentMagnifier(), 17349);
|
|
}
|
|
|
|
function test_rebalance_RevertWhenNoUpdate() public {
|
|
_simulateLiquidityUtilizationAndBorrowRate(5000, 1200);
|
|
|
|
vm.prank(alice);
|
|
configHandler.rebalance();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(Error.FluidConfigError.selector, ErrorTypes.EthenaRateConfigHandler__NoUpdate)
|
|
);
|
|
vm.prank(alice);
|
|
configHandler.rebalance();
|
|
}
|
|
|
|
function test_rebalance_RevertWhenUnauthorized() public {
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(Error.FluidConfigError.selector, ErrorTypes.EthenaRateConfigHandler__Unauthorized)
|
|
);
|
|
vm.prank(bob);
|
|
configHandler.rebalance();
|
|
}
|
|
|
|
function test_calculateMagnifier() public {
|
|
// simulate sUSDe yield rate at 40%
|
|
// _simulateSUSDeYieldRate(sUSDeYieldRate);
|
|
|
|
// sUSDe yield rate is 23,133079845218885955%
|
|
// uint256 sUSDeYieldRate = 23133079845218885955;
|
|
|
|
uint256 borrowRate = 1200; // 12%
|
|
|
|
// check at utilization 50%
|
|
uint256 utilization = 5000;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
// expected borrow rate should be 23,133079845218885955% - 10% RATE_PERCENT_MARGIN
|
|
// so 20,819771860696997359%. given current borrow rate is 12%, magnifier would have to be 1.7349809883914164466
|
|
uint256 expectedMagnifier = 17349;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 90%
|
|
utilization = 9000;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 17349;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 0%
|
|
utilization = 0;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 17349;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 91%
|
|
utilization = 9100;
|
|
// penalty 10% of difference from margin to penalty, so 10% of 13% -> 1.3%.
|
|
// so expected borrow rate should be 23,133079845218885955% * 91.3% = 22.62415208862407046399
|
|
// given current borrow rate is 12%, magnifier would have to be 1.7600418248904035730762
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 17600;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 96%
|
|
utilization = 9600;
|
|
// penalty 10% of difference from margin to penalty, so 60% of 13% -> 7.8%.
|
|
// so expected borrow rate should be 23,133079845218885955% * 97.8% = 21.120501898684842876915
|
|
// given current borrow rate is 12%, magnifier would have to be 1.885346007385339205332
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 18853;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 99%
|
|
utilization = 9900;
|
|
// penalty 10% of difference from margin to penalty, so 90% of 13% -> 11.7%.
|
|
// so expected borrow rate should be 23,133079845218885955% * 101.7% = 23.526342202587607016235
|
|
// given current borrow rate is 12%, magnifier would have to be 1.9605285168823005846862
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 19605;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 100%
|
|
utilization = 10000;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
// expected borrow rate should be 23,133079845218885955% + penalty rate 3% (UTILIZATION100_PENALTY_PERCENT)
|
|
// so 23,133079845218885955 * 103% -> 23.82707224057545253365
|
|
// given current borrow rate is 12%, magnifier would have to be 1.985589353381287711137
|
|
expectedMagnifier = 19855;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check at utilization 110%
|
|
utilization = 11000;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
// should be capped at same rate as for 100%
|
|
expectedMagnifier = 19855;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check when borrow rate is 0%
|
|
utilization = 5000;
|
|
borrowRate = 0;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
expectedMagnifier = 1e4;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// check when liquidity borrow rate is above sUSDe yield target rate
|
|
utilization = 5000;
|
|
borrowRate = 4000;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
// magnifier should be 1, so even if sUSDe yield is below normal borrow rate the rate at Liquidity must always be paid
|
|
expectedMagnifier = 1e4;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
|
|
// when magnifier would go above max value
|
|
configHandler = new FluidEthenaRateConfigHandler(
|
|
IFluidReserveContract(address(reserveContract)),
|
|
IFluidLiquidity(address(liquidity)),
|
|
IFluidVaultT1(address(mockProtocol)),
|
|
SUSDE_TOKEN,
|
|
address(USDC),
|
|
RATE_PERCENT_MARGIN,
|
|
MAX_REWARDS_DELAY,
|
|
UTILIZATION_PENALTY_START,
|
|
80000 // set very high penalty of 800%
|
|
);
|
|
utilization = 9800;
|
|
borrowRate = 1200;
|
|
_simulateLiquidityUtilizationAndBorrowRate(utilization, borrowRate);
|
|
// max magnifier ever set should be max possible value which is 65535
|
|
expectedMagnifier = 65535;
|
|
assertEq(configHandler.calculateMagnifier(), expectedMagnifier);
|
|
}
|
|
|
|
function _simulateLiquidityUtilizationAndBorrowRate(uint256 utilization, uint256 borrowRate) internal {
|
|
uint256 exchangePricesAndConfig = resolver.getExchangePricesAndConfig(address(USDC));
|
|
|
|
exchangePricesAndConfig =
|
|
(exchangePricesAndConfig &
|
|
// mask to update bits: 0-15 (borrow rate), 30-43 (utilization)
|
|
0xfffffffffffffffffffffffffffffffffffffffffffffffffffff0003fff0000) |
|
|
borrowRate | // borrow rate
|
|
(utilization << LiquiditySlotsLink.BITS_EXCHANGE_PRICES_UTILIZATION);
|
|
|
|
bytes32 slot = LiquiditySlotsLink.calculateMappingStorageSlot(
|
|
LiquiditySlotsLink.LIQUIDITY_EXCHANGE_PRICES_MAPPING_SLOT,
|
|
address(USDC)
|
|
);
|
|
|
|
vm.store(address(liquidity), slot, bytes32(exchangePricesAndConfig));
|
|
}
|
|
}
|