//SPDX-License-Identifier: MIT pragma solidity 0.8.21; import { Structs as AdminModuleStructs } from "../../../../contracts/liquidity/adminModule/structs.sol"; import { AuthModule, FluidLiquidityAdminModule } from "../../../../contracts/liquidity/adminModule/main.sol"; import { Structs as ResolverStructs } from "../../../../contracts/periphery/resolvers/liquidity/structs.sol"; import { ErrorTypes } from "../../../../contracts/liquidity/errorTypes.sol"; import { Error } from "../../../../contracts/liquidity/error.sol"; import { LiquidityUserModuleBaseTest } from "./liquidityUserModuleBaseTest.t.sol"; import { BigMathMinified } from "../../../../contracts/libraries/bigMathMinified.sol"; import "forge-std/console2.sol"; contract LiquidityUserModuleYieldTests is LiquidityUserModuleBaseTest { function setUp() public virtual override { super.setUp(); } function test_operate_ExchangePriceSupplyWithInterestOnly() public { // alice supplies liquidity _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 4); // supply rate = 1% of borrow rate // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // at 1% utilization, for default values of 4% at 0% utilization and 10% at 80% utilization. // so over the range of 80%, the rate grows 6% linearly. // 80 = 6, 1 = x => x = 6 / 80 * 1 = 0,075 // so 4% + 0.075% = 4.075% // but borrow rate precision in Liquidity is only 0.01% so it becomes 4.07%. // with supplyExchangePrice increasing 1% of that because only 1% of supply is borrowed out uint256 expectedBorrowExchangePrice = 1040700000000; uint256 expectedSupplyExchangePrice = 1000407000000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceSupplyInterestFreeOnly() public { // alice supplies liquidity _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 0); // supply rate = 0 because no borrowers with interest // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. // with supplyExchangePrice staying the same as no suppliers that earn any interest uint256 expectedSupplyExchangePrice = 1e12; uint256 expectedBorrowExchangePrice = 1040700000000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceNumberUpOnlyWhenNoStorageUpdate() public { // alice supplies liquidity _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // set storage update threshold to 5% AdminModuleStructs.TokenConfig[] memory tokenConfigs = new AdminModuleStructs.TokenConfig[](1); tokenConfigs[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), fee: 0, // no fee for simplicity threshold: DEFAULT_STORAGE_UPDATE_THRESHOLD * 5, // 5% maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME / 1000); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. // just divided by 1000 to be below forced storage update if time diff > 1 day // 407000000 / 1000 = 407000 uint256 expectedSupplyExchangePrice = 1000000407000; // increased 1% of borrow exchange price (because 1% of supply is borrowed out) uint256 expectedBorrowExchangePrice = 1000040700000; uint256 exchangePricesAndConfigBefore = resolver.getExchangePricesAndConfig(address(USDC)); _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); // no storage update happening but that must not cause any issue with supplyExchangePrice // assert exchangePricesAndConfig had no storage update assertEq(exchangePricesAndConfigBefore, resolver.getExchangePricesAndConfig(address(USDC))); vm.prank(alice); (uint256 supplyExchangePrice2, uint256 borrowExchangePrice2) = mockProtocolWithInterest.operate( address(USDC), int256(DEFAULT_SUPPLY_AMOUNT), 0, address(0), address(0), abi.encode(alice) ); assertGe(supplyExchangePrice2, expectedSupplyExchangePrice); assertGe(borrowExchangePrice2, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenSupplyWithInterestBigger() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 80) / 100); // alice supplies liquidity interest free _supply(mockProtocolInterestFree, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 20) / 100); // total supply 1 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 18750); // 187.5% // simulate passing time 1 / 10 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME / 10); // at 100% utilization, borrow rate is 150%. // just here we only warp 1/10 of the year so 15% increase. uint256 expectedBorrowExchangePrice = 1150000000000; // total earnings for suppliers are 100% of borrow increase. But only 80% of suppliers earn that. // so exchange price must grow 25% more to account for that: 150000000000 * 1.25 = 187500000000 uint256 expectedSupplyExchangePrice = 1187500000000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenSupplyInterestFreeBigger() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 20); // alice supplies liquidity interest free _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 80); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 20); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase (0,0407%). But only 20% of suppliers earn that. // so exchange price must grow 5x more to account for that: 407000000 * 5 = 2035000000 uint256 expectedSupplyExchangePrice = 1002035000000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenSupplyWithInterestExactlySupplyInterestFree() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 50); // alice supplies liquidity interest free _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 50); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 8); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase (0,0407%). But only 50% of suppliers earn that. // so exchange price must grow 2x more to account for that: 407000000 * 2 = 814000000 uint256 expectedSupplyExchangePrice = 1000814000000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenSupplyWithInterestBiggerWithRevenueFee() public { // set revenue fee to 10% AdminModuleStructs.TokenConfig[] memory tokenConfigs = new AdminModuleStructs.TokenConfig[](1); tokenConfigs[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), fee: 10 * DEFAULT_PERCENT_PRECISION, threshold: 0, maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs); // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 80); // alice supplies liquidity interest free _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 20); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 4); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase MINUS the revenue fee. // so 40700000000 * 1% - 10% = 366300000. But only 80% of suppliers earn that. // so exchange price must grow 25% more to account for that: 366300000 * 1.25 = 457875000 uint256 expectedSupplyExchangePrice = 1000457875000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenSupplyInterestFreeBiggerWithRevenueFee() public { // set revenue fee to 10% AdminModuleStructs.TokenConfig[] memory tokenConfigs = new AdminModuleStructs.TokenConfig[](1); tokenConfigs[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), fee: 10 * DEFAULT_PERCENT_PRECISION, threshold: 0, maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs); // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 20); // alice supplies liquidity interest free _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 80); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 18); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase MINUS the revenue fee. // so 40700000000 * 1% - 10% = 366300000. But only 20% of suppliers earn that. // so exchange price must grow 5x more to account for that: 366300000 * 5 = 1831500000 uint256 expectedSupplyExchangePrice = 1001831500000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceSequences() public { // alice supplies liquidity _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 10); // total supply 10 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 47); // 10% of borrow rate // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for exchange price calculation. // 10% utilization borrow rate => x = 6 / 80 * 10 = 0,75. 4 + 0.75 => 4.75% // with 10% of supply earning yield uint256 expectedBorrowExchangePrice = 1047500000000; uint256 expectedSupplyExchangePrice = 1004750000000; // deposits DEFAULT_BORROW_AMOUNT _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); // utilization here increased to: // total borrow = DEFAULT_BORROW_AMOUNT * 1047500000000 / 1e12 = 0,52375 // total supply = 10 * DEFAULT_BORROW_AMOUNT * 1004750000000 / 1e12 + DEFAULT_BORROW_AMOUNT // = 5,02375 ether + 0,5 ether = 5,52375 // utilization = 0,52375 / 5,52375 = 9,4817%; cut off precision to 0.01%-> 9,48%. // so borrow rate: // at 9,48% utilization x = 6 / 80 * 9,48% = 0.711 // so 4% + 0.711% = 4.711% but cut off precision to 0.01%-> 4,71%. // cross-check resolver supply rate. see calc exchange price below overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 44); // 9,48% of borrow rate // simulate passing time 1 year for yield again vm.warp(block.timestamp + PASS_1YEAR_TIME); expectedBorrowExchangePrice = (1047500000000 * (1e4 + 471)) / 1e4; // same multiplicator here for supple exchange price as no revenue fee and only with interest suppliers. // only 9.48% of supply is borrowed out though so // increase in supplyExchangePrice = ((1004750000000 * 471 * 948) / 1e4 / 1e4) = 4486289130 expectedSupplyExchangePrice = 1004750000000 + 4486289130; // = 1009236289130 _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } // function test_operate_ExchangePriceBorrowWithInterestOnly() public { // already covered by tests for supply exchange prices as they use borrow with interest only // } function test_operate_ExchangePriceBorrowInterestFreeOnly() public { // alice supplies liquidity _supply(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity _borrow(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // both exchange prices should be initial value as there is no yield. uint256 expectedSupplyExchangePrice = 1e12; uint256 expectedBorrowExchangePrice = 1e12; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenBorrowWithInterestBigger() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity with interest _borrow(mockProtocolWithInterest, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 8) / 10); // alice borrows liquidity interest free _borrow(mockProtocolInterestFree, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 2) / 10); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 3); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase (1% is lent out). // But only 80% of the borrowers pay the yield. // so exchange price must grow 20% less to account for that: // supplyRate = 4,07% * 0,8 = 3,256%. so supplyIncrease = 325600000 uint256 expectedSupplyExchangePrice = 1000325600000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenBorrowInterestFreeBigger() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity with interest _borrow(mockProtocolWithInterest, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 2) / 10); // alice borrows liquidity interest free _borrow(mockProtocolInterestFree, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 8) / 10); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 0); // 0.008% // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase (1% is lent out). // But only 20% of the borrowers pay the yield. // so exchange price must grow 80% less to account for that: 407000000 * 0.2 = 81400000 uint256 expectedSupplyExchangePrice = 1000081400000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenBorrowWithInterestExacltyBorrowInterestFree() public { // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity with interest _borrow(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT / 2); // alice borrows liquidity interest free _borrow(mockProtocolInterestFree, address(USDC), alice, DEFAULT_BORROW_AMOUNT / 2); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 2); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for borrow exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // total earnings for suppliers are 1% of borrow increase. But only 50% of borrowers pay that. // so exchange price must grow half to account for that: 407000000 / 2 = 203500000 uint256 expectedSupplyExchangePrice = 1000203500000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenBorrowWithInterestBiggerWithRevenueFee() public { // set revenue fee to 10% AdminModuleStructs.TokenConfig[] memory tokenConfigs = new AdminModuleStructs.TokenConfig[](1); tokenConfigs[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), fee: 10 * DEFAULT_PERCENT_PRECISION, threshold: 0, maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs); // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity with interest _borrow(mockProtocolWithInterest, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 8) / 10); // alice borrows liquidity interest free _borrow(mockProtocolInterestFree, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 2) / 10); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 2); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // 10% of total earnings go to revenue so 4,07% * 0,9 = 3,663% // and only 1% total is lent out so 3,663% *0,01 = 0,03663% // But only 80% of the borrowers pay the yield. so rate must grow 20% less: 0,03663% *0,8 = 0,029304% // so supplyRate 0,029304%. so supplyIncrease = 293040000 uint256 expectedSupplyExchangePrice = 1000293040000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } function test_operate_ExchangePriceWhenBorrowInterestFreeBiggerWithRevenueFee() public { // set revenue fee to 10% AdminModuleStructs.TokenConfig[] memory tokenConfigs = new AdminModuleStructs.TokenConfig[](1); tokenConfigs[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), fee: 10 * DEFAULT_PERCENT_PRECISION, threshold: 0, maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs); // alice supplies liquidity with interest _supply(mockProtocolWithInterest, address(USDC), alice, DEFAULT_BORROW_AMOUNT * 100); // total supply 100 * DEFAULT_BORROW_AMOUNT. // alice borrows liquidity with interest _borrow(mockProtocolWithInterest, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 2) / 10); // alice borrows liquidity interest free _borrow(mockProtocolInterestFree, address(USDC), alice, (DEFAULT_BORROW_AMOUNT * 8) / 10); // cross-check resolver supply rate. see calc exchange price below ResolverStructs.OverallTokenData memory overallTokenData = resolver.getOverallTokenData(address(USDC)); assertEq(overallTokenData.supplyRate, 0); // simulate passing time 1 year for yield vm.warp(block.timestamp + PASS_1YEAR_TIME); // see test_operate_ExchangePriceSupplyWithInterestOnly for exchange price calculation. uint256 expectedBorrowExchangePrice = 1040700000000; // 10% of total earnings go to revenue so 40700000000 * 0.9 = 36630000000 = 3,663% // total earnings for suppliers are 1% of borrow increase (1% is lent out). so 366300000 = 0,03663% // But only 20% of the borrowers pay the yield. // so exchange price must grow 80% less to account for that: 366300000 * 0.2 = 73260000 = supplyRate: 0,07326% uint256 expectedSupplyExchangePrice = 1000073260000; _assertExchangePrices(expectedSupplyExchangePrice, expectedBorrowExchangePrice); } // todo: test supply with interest only but only borrow interest free -> no yield // todo: supply with interest free only, with borrow with interest -> all goes to revenue function _assertExchangePrices(uint256 expectedSupplyExchangePrice, uint256 expectedBorrowExchangePrice) internal { vm.prank(alice); (uint256 supplyExchangePrice, uint256 borrowExchangePrice) = mockProtocolWithInterest.operate( address(USDC), int256(DEFAULT_BORROW_AMOUNT), 0, address(0), address(0), abi.encode(alice) ); assertEq(supplyExchangePrice, expectedSupplyExchangePrice, "supply exchange price off"); assertEq(borrowExchangePrice, expectedBorrowExchangePrice, "borrow exchange price off"); } } abstract contract LiquidityUserModuleYieldCombinationBaseTest is LiquidityUserModuleBaseTest { uint256 constant baseLimit = 5 ether; uint256 immutable baseLimitAfterBigMath; constructor() { baseLimitAfterBigMath = BigMathMinified.fromBigNumber( BigMathMinified.toBigNumber( baseLimit, SMALL_COEFFICIENT_SIZE, DEFAULT_EXPONENT_SIZE, BigMathMinified.ROUND_DOWN ), DEFAULT_EXPONENT_SIZE, DEFAULT_EXPONENT_MASK ); } function setUp() public virtual override { super.setUp(); AdminModuleStructs.TokenConfig[] memory tokenConfigs_ = new AdminModuleStructs.TokenConfig[](1); tokenConfigs_[0] = AdminModuleStructs.TokenConfig({ token: address(USDC), // set threshold and fee to 0 so it doesn't affect tests that don't specifically target testing this fee: DEFAULT_TOKEN_FEE, // 5% threshold: 0, maxUtilization: 1e4 // 100% }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateTokenConfigs(tokenConfigs_); // Set withdraw config with actual limits AdminModuleStructs.UserSupplyConfig[] memory userSupplyConfigs_ = new AdminModuleStructs.UserSupplyConfig[](1); userSupplyConfigs_[0] = AdminModuleStructs.UserSupplyConfig({ user: address(mockProtocol), token: address(USDC), mode: 1, expandPercent: DEFAULT_EXPAND_WITHDRAWAL_LIMIT_PERCENT, // 20% expandDuration: DEFAULT_EXPAND_WITHDRAWAL_LIMIT_DURATION, // 2 days; baseWithdrawalLimit: baseLimit }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateUserSupplyConfigs(userSupplyConfigs_); // Set borrow config with actual limits AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_ = new AdminModuleStructs.UserBorrowConfig[](1); userBorrowConfigs_[0] = AdminModuleStructs.UserBorrowConfig({ user: address(mockProtocol), token: address(USDC), mode: 1, expandPercent: DEFAULT_EXPAND_DEBT_CEILING_PERCENT, // 20% expandDuration: DEFAULT_EXPAND_DEBT_CEILING_DURATION, // 2 days; baseDebtCeiling: baseLimit, maxDebtCeiling: 20 ether }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateUserBorrowConfigs(userBorrowConfigs_); } // test uses default values from liquidityTestHelpers.sol: // uint256 constant DEFAULT_TOKEN_FEE = 5 * DEFAULT_PERCENT_PRECISION; // 5% // uint256 constant DEFAULT_KINK = 80 * DEFAULT_PERCENT_PRECISION; // 80% // uint256 constant DEFAULT_RATE_AT_ZERO = 4 * DEFAULT_PERCENT_PRECISION; // 4% // uint256 constant DEFAULT_RATE_AT_KINK = 10 * DEFAULT_PERCENT_PRECISION; // 10% // uint256 constant DEFAULT_RATE_AT_MAX = 150 * DEFAULT_PERCENT_PRECISION; // 150% // // for rate data v2: // uint256 constant DEFAULT_KINK2 = 90 * DEFAULT_PERCENT_PRECISION; // 90% // uint256 constant DEFAULT_RATE_AT_KINK2 = 80 * DEFAULT_PERCENT_PRECISION; // 10% + half way to 150% = 80% for data compatibility with v1 function test_operate_YieldCombinationTest() public { // supply liquidity with interest _supply(mockProtocol, address(USDC), alice, 4 ether); // 1. test yield at ~zero: borrow very little amount _borrow(mockProtocol, address(USDC), alice, 1e15); _assertState( 400, // expected borrow rate 0, // expected supply rate. only very tiny amount of borrowers are paying yield 1e12, // expected supply exchange price. 1e12, // expected borrow exchange price. 0, // expected revenue 4 ether, // expected supply raw interest 0 ether, // expected supply interest free 1e15, // expected borrow raw interest 0 ether, // expected borrow interest free 0, // expected withdrawal limit baseLimitAfterBigMath // expected borrow limit ); // warp & assert everything // 1e15 paying 4% for half a year -> 2% yield. 5% of that is revenue. // supply rate = 4% - 5% revenue fee -> 3.8%. only 0.025% utilization -> 0.095%. // only earned for half a year so 0.0475%. // BUT: precision for utilization is actually cut off at 0.02% so we get 0.076%. / 2 = 0.038%. // Revenue should be 1e12 but precision cut off in utilization leads to supply exchange price difference. // the precision loss is counted towards revenue. total supply ends up being 4 ether * 1000003800000 / 1e12 instead of // 4 ether * 10000047500000 / 1e12, leading to a total diff of 3.8e12 vm.warp(block.timestamp + 365 days / 2); // earn half a year yield uint256 expectedSupplyExchangePrice = 1000003800000; uint256 expectedBorrowExchangePrice = 1020000000000; // increased by 2% uint256 expectedRevenue = 48e11; // 4.8e12 _assertState( 400, // expected borrow rate 0, // expected supply rate. supply rate precision is cut off to 0 expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 4 ether, // expected supply raw interest 0 ether, // expected supply interest free 1e15, // expected borrow raw interest 0 ether, // expected borrow interest free 0, // expected withdrawal limit (baseLimitAfterBigMath * expectedBorrowExchangePrice) / 1e12 // expected borrow limit ); // 2. test yield at below kink 1 _supply(mockProtocol, address(USDC), alice, 6 ether - 4000015200000000000); // bring total supply to 6 ether _borrow(mockProtocol, address(USDC), alice, 1.2 ether - 1020000000000000); // bring utilization to 20% // warp & assert everything // borrow rate = 4% + 1/4 of slope 6% (10% -4%) -> 5.5%. // 1.2 ether paying 5.5% for 10% of a year -> 0.55% yield. 5% of that is revenue. // supply rate = 5.5% - 5% revenue fee -> 5.225%. only 20% utilization -> 1.045%. // only earned for 10% of a year so 0.1045%. // 0.55% of 1.2 ether is 0.0066 ether in yield. 5% goes to revenue -> 0,00033 vm.warp(block.timestamp + 365 days / 10); // earn 10% of a year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 1001045) / 1000000; // increased by 0.1045%. assertEq(expectedSupplyExchangePrice, 1001048803971); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 1005500) / 1000000; // increased by 0.55% assertEq(expectedBorrowExchangePrice, 1025610000000); expectedRevenue += 0.00033 ether; // expected withdrawal limit: // 20% of total supply expanded (5999977200086639488 * 0.8 = 4799981760069311590) uint256 expectedWithdrawalLimit = (4799981760069311590 * expectedSupplyExchangePrice) / 1e12; _assertState( 550, // expected borrow rate 104, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 5999977200086639488, // expected supply raw interest. total supply 6006269999999999817 0 ether, // expected supply interest free 1176470588235294144, // expected borrow raw interest. total borrow 1206600000000000027 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. fully expanded 20% (baseLimitAfterBigMath * expectedBorrowExchangePrice) / 1e12 // expected borrow limit ); // 3. test yield at kink 1 _supply(mockProtocol, address(USDC), alice, 6.2 ether - 6006269999999999817); // bring total supply to 6.2 ether _borrow(mockProtocol, address(USDC), alice, 4.96 ether - 1206600000000000027); // bring utilization to 80% // warp & assert everything // borrow rate at kink1 = 10% // 4.96 ether paying 10% for a year -> 10% yield. 5% of that is revenue. // supply rate = 10% - 5% revenue fee -> 9.5%. only 80% utilization -> 7.6%. // 0.496 ether in yield. 5% goes to revenue -> 0,0248 vm.warp(block.timestamp + 365 days); // earn a year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 1076000) / 1000000; // increased by 7.6%. expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 1100000) / 1000000; // increased by 10% expectedRevenue += 0.0248 ether + 4930649; // tolerate some inaccuracy 4930649, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 5336959999996055776; // user total supply 20% expanded _assertState( 1000, // expected borrow rate 760, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 6193504228171088640, // expected supply raw interest. total supply 6671199999995069720 0 ether, // expected supply interest free 4836146293425376256, // expected borrow raw interest. total borrow 5456000000000000156 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. fully expanded 20% 6547200000000000187 // expected borrow limit. user total borrow fully expanded 20% ); // 4. test yield above kink 1 _supply(mockProtocol, address(USDC), alice, 7.5 ether - 6671199999995069720); // bring total supply to 7.5 ether _borrow(mockProtocol, address(USDC), alice, 6.375 ether - 5456000000000000156); // bring utilization to 85% // warp & assert everything // borrow rate = 10% + 1/4 of slope 140% (150% -10%) -> 45%. // 6.375 ether paying 45% for a 1/3 year -> 15% yield. 5% of that is revenue. // supply rate = 45% - 5% revenue fee -> 42.75%. only 85% utilization -> 36.3375%. // 0.95625 ether in yield. 5% goes to revenue -> 0,0478125 vm.warp(block.timestamp + 365 days / 3); // earn a 1/3 year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 1121125) / 1000000; // increased by 12,1125% (rate for 1/3 year) assertEq(expectedSupplyExchangePrice, 1207595704217); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 1150000) / 1000000; // increased by 15% (rate for 1/3 year) assertEq(expectedBorrowExchangePrice, 1297396650000); expectedRevenue += 0.0478125 ether + 5891211; // tolerate some inaccuracy 5891211, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 6726749999995287266; // user total supply 20% expanded _assertState( 4500, // expected borrow rate 3633, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 6962957445634592384, // expected supply raw interest. total supply 8408437499994109082 0 ether, // expected supply interest free 5650739116676461440, // expected borrow raw interest. total borrow 7331250000000000206 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. fully expanded 20% 8797500000000000247 // expected borrow limit. user total borrow fully expanded 20% ); // 5. test yield at kink 2 (same values without another kink for v1) _supply(mockProtocol, address(USDC), alice, 8.5 ether - 8408437499994109082); // bring total supply to 8.5 ether _borrow(mockProtocol, address(USDC), alice, 7.65 ether - 7331250000000000206); // bring utilization to 90% // warp & assert everything // borrow rate at kink2 = 80% // 7.65 ether paying 80% for 5% of a year -> 4% yield. 5% of that is revenue. // supply rate = 80% - 5% revenue fee -> 76%. only 90% utilization -> 68.4%. // 0.306 ether in yield. 5% goes to revenue -> 0,0153 vm.warp(block.timestamp + 365 days / 20); // earn 5% of a year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 1034200) / 1000000; // increased by 3.42%. (rate for 1/20 year) assertEq(expectedSupplyExchangePrice, 1248895477301); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 1040000) / 1000000; // increased by 4% (rate for 1/20 year) assertEq(expectedBorrowExchangePrice, 1349292516000); expectedRevenue += 0.0153 ether + 1559103; // tolerate some inaccuracy 1559103, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 7032559999998753095; // user total supply 20% expanded _assertState( 8000, // expected borrow rate 6840, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 7038779593466146176, // expected supply raw interest. total supply 8790699999998441369 0 ether, // expected supply interest free 5896423426097177216, // expected borrow raw interest. total borrow 7956000000000000306 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. fully expanded 20% 9547200000000000367 // expected borrow limit. user total borrow fully expanded 20% ); // 6. test yield above kink 2 (same values without another kink for v1) _supply(mockProtocol, address(USDC), alice, 8.8 ether - 8790699999998441369); // bring total supply to 8.8 ether _borrow(mockProtocol, address(USDC), alice, 8.096 ether - 7956000000000000306); // bring utilization to 92% // warp & assert everything // borrow rate = 80% + 1/5 of slope 70% (150% -80%) -> 94%. // 8.096 ether paying 94% for a 1/365 year -> 0.25753424657534246% yield. 5% of that is revenue. // supply rate = 94% - 5% revenue fee -> 89.3%. only 92% utilization -> 82.156%. // 0.020849972602739726 ether in yield. 5% goes to revenue -> 0,001042498630136986 vm.warp(block.timestamp + 1 days); // earn a 1 day yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 10022508493150684) / 10000000000000000; // increased by 0.22508493150684931% (rate for 1/365 year) assertEq(expectedSupplyExchangePrice, 1251706552830); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 10025753424657534) / 10000000000000000; // increased by 0.25753424657534246% (rate for 1/365 year) assertEq(expectedBorrowExchangePrice, 1352767406315); expectedRevenue += 0.001042498630136986 ether + 3689037; // tolerate some inaccuracy 3689037, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 7055845979174276467; // user total supply 20% fully expanded because start expand point was close _assertState( 9400, // expected borrow rate 8215, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 7046226173400646912, // expected supply raw interest. total supply 8819807473967845584 0 ether, // expected supply interest free 6000181505490541312, // expected borrow raw interest. total borrow 8116849972601671502 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. 9740219967122005802 // expected borrow limit. user total borrow fully expanded 20% because start expand point was close ); // 7. test yield at max _supply(mockProtocol, address(USDC), alice, 9.6 ether - 8819807473967845584); // bring total supply to 9.6 ether _borrow(mockProtocol, address(USDC), alice, 9.6 ether - 8116849972601671502); // bring utilization to 100% // warp & assert everything // borrow rate at max = 150%. // 9.6 ether paying 150% for a 10% of a year -> 15% yield. 5% of that is revenue. // supply rate = 150% - 5% revenue fee -> 142.5%. 100% utilization. // 1.44 ether in yield. 5% goes to revenue -> 0,072 vm.warp(block.timestamp + 365 days / 10); // earn a 10% year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 1142500) / 1000000; // increased by 14.25% (rate for 1/10 year) assertEq(expectedSupplyExchangePrice, 1430074736608); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 1150000) / 1000000; // increased by 15% (rate for 1/10 year) assertEq(expectedBorrowExchangePrice, 1555682517262); expectedRevenue += 0.072 ether + 335487; // tolerate some inaccuracy 1335487, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 8774399999998312507; // user total supply 20% fully expanded _assertState( 15000, // expected borrow rate 14250, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 7669529234544016768, // expected supply raw interest. total supply 10967999999997890634 0 ether, // expected supply interest free 7096563648107724032, // expected borrow raw interest. total borrow 11039999999998226085 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. 13247999999997871301 // expected borrow limit. user total borrow fully expanded ); // 8. test yield at utilization > 100% // utilization is at 11039999999998226085 / 10967999999997890634 = 100.656455142235132184% precision cut off 100.65% // warp & assert everything // borrow rate at max = 150%. + continuing the slope of rate -> 6.5% of slope 70% (150% -80%) // -> 150% + 4,55% = 154,55% // 11039999999998226085 ether paying 154,55% for 1 year yield. 5% of that is revenue. // supply rate = 154,55 - 5% revenue fee -> 146,8225%. 100.656455142235132184% utilization so 147,78624289% // Note: supply rate calculation in resolver uses utilization not from storage so result has higher precision) // supply exchange price calculation however uses 100.65% utilization so there it is 147,77684625. // 17.062319999997258414 ether in yield. 5% goes to revenue -> 0,853115999999862920 ether vm.warp(block.timestamp + 365 days); // earn a 1 year yield expectedSupplyExchangePrice = (expectedSupplyExchangePrice * 24777684625) / 10000000000; // increased by 147,77684625% assertEq(expectedSupplyExchangePrice, 3543394081385); expectedBorrowExchangePrice = (expectedBorrowExchangePrice * 25455) / 10000; // increased by 154,55% assertEq(expectedBorrowExchangePrice, 3959989847690); expectedRevenue += 0.853115999999862920 ether + 1039503299800972; // tolerate some inaccuracy 1039503299800972, from total amounts rounding // expected withdrawal limit: expectedWithdrawalLimit = 21740931597353998440; // user total supply 20% fully expanded _assertState( 15455, // expected borrow rate 14778, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 7669529234544016640, // expected supply raw interest. no changes only rounded down. total supply 27176164496692498051 0 ether, // expected supply interest free 7096563648107724160, // expected borrow raw interest. no changes only rounded up. total borrow 28102319999992497353 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. 33722783999990996823 // expected borrow limit. user total borrow fully expanded ); // 9. test max limit // Set borrow config with lower max limit AdminModuleStructs.UserBorrowConfig[] memory userBorrowConfigs_ = new AdminModuleStructs.UserBorrowConfig[](1); userBorrowConfigs_[0] = AdminModuleStructs.UserBorrowConfig({ user: address(mockProtocol), token: address(USDC), mode: 1, expandPercent: DEFAULT_EXPAND_DEBT_CEILING_PERCENT, // 20% expandDuration: DEFAULT_EXPAND_DEBT_CEILING_DURATION, // 2 days; baseDebtCeiling: 2 ether, // raw, so at exchange price ~4, this is ~8 ether maxDebtCeiling: 5 ether // raw, so at exchange price ~4, this is ~20 ether }); vm.prank(admin); FluidLiquidityAdminModule(address(liquidity)).updateUserBorrowConfigs(userBorrowConfigs_); // assert borrowLimit (ResolverStructs.UserBorrowData memory userBorrowData, ) = resolver.getUserBorrowData( address(mockProtocol), address(USDC) ); assertEq( userBorrowData.borrowLimit, (baseLimitAfterBigMath * expectedBorrowExchangePrice) / 1e12, "borrowLimit off" ); assertEq(userBorrowData.borrowableUntilLimit, 0, "borrowableUntilLimit off"); assertEq(userBorrowData.borrowable, 0, "borrowable off"); // assert reverts if borrowing more vm.expectRevert( abi.encodeWithSelector(Error.FluidLiquidityError.selector, ErrorTypes.UserModule__BorrowLimitReached) ); _borrow(mockProtocol, address(USDC), alice, userBorrowData.borrowable + 1); // 10. payback down to 50% utilization _payback(mockProtocol, address(USDC), alice, (userBorrowData.borrow - 13588082248346249025)); // to 27176164496692498051 / 2 // borrow rate = 4% + 5/8 of slope 6% (10% -4%) -> 7.75%. // supply rate = 7.75% - 5% revenue fee -> 7.3625%. only 50% utilization -> 3.68125%. expectedRevenue += 1538; // no changes but tolerate some inaccuracy 1538, from total amounts rounding _assertState( 775, // expected borrow rate 368, // expected supply rate. expectedSupplyExchangePrice, // expected supply exchange price. expectedBorrowExchangePrice, // expected borrow exchange price. expectedRevenue, // expected revenue. 7669529234544016512, // expected supply raw interest. no changes only rounded down. total supply 27176164496692497597 0 ether, // expected supply interest free 3431342698081069760, // expected borrow raw interest. total borrow 13588082248346249094 0 ether, // expected borrow interest free expectedWithdrawalLimit, // expected withdrawal limit. 16305698698015498659 // expected borrow limit. user total borrow fully expanded. allow minor precision diff of 253 ); // later: // todo: 11. test with supply Interest free being added // todo: 12. test with borrow interest free being added } function _assertState( uint256 borrowRate, uint256 supplyRate, uint256 supplyExchangePrice, uint256 borrowExchangePrice, uint256 revenue, uint256 supplyRawInterest, uint256 supplyInterestFree, uint256 borrowRawInterest, uint256 borrowInterestFree, uint256 withdrawalLimit, uint256 borrowLimit ) internal { ( ResolverStructs.UserSupplyData memory userSupplyData, ResolverStructs.OverallTokenData memory tokenData ) = resolver.getUserSupplyData(address(mockProtocol), address(USDC)); assertEq(tokenData.borrowRate, borrowRate, "borrowRate off"); assertEq(tokenData.supplyRate, supplyRate, "supplyRate off"); assertEq(tokenData.supplyExchangePrice, supplyExchangePrice, "supplyExchangePrice off"); assertEq(tokenData.borrowExchangePrice, borrowExchangePrice, "borrowExchangePrice off"); assertApproxEqAbs(tokenData.revenue, revenue, 1e3, "revenue off"); assertEq(tokenData.supplyRawInterest, supplyRawInterest, "supplyRawInterest off"); assertEq(tokenData.supplyInterestFree, supplyInterestFree, "supplyInterestFree off"); assertEq(tokenData.borrowRawInterest, borrowRawInterest, "borrowRawInterest off"); assertEq(tokenData.borrowInterestFree, borrowInterestFree, "borrowInterestFree off"); assertEq( tokenData.totalSupply, (supplyRawInterest * supplyExchangePrice) / 1e12 + supplyInterestFree, "totalSupply off" ); assertEq( tokenData.totalBorrow, (borrowRawInterest * borrowExchangePrice) / 1e12 + borrowInterestFree, "totalBorrow off" ); assertGe(tokenData.totalBorrow + USDC.balanceOf(address(liquidity)), tokenData.totalSupply + tokenData.revenue); // create liquidity to test withdrawable _supply(mockProtocolInterestFree, address(USDC), alice, 30 ether); (userSupplyData, ) = resolver.getUserSupplyData(address(mockProtocol), address(USDC)); // assert withdrawalLimit assertApproxEqAbs(userSupplyData.withdrawalLimit, withdrawalLimit, 1e3, "withdrawalLimit off"); assertApproxEqAbs( userSupplyData.withdrawableUntilLimit, userSupplyData.supply - withdrawalLimit, 1e3, "withdrawableUntilLimit off" ); assertApproxEqAbs( userSupplyData.withdrawable, userSupplyData.supply - withdrawalLimit, 1e3, "withdrawable off" ); if (userSupplyData.supply > 0 && userSupplyData.withdrawable < userSupplyData.supply) { // assert reverts if withdrawing more vm.expectRevert( abi.encodeWithSelector( Error.FluidLiquidityError.selector, ErrorTypes.UserModule__WithdrawalLimitReached ) ); _withdraw(mockProtocol, address(USDC), alice, userSupplyData.withdrawable + 1); } if (userSupplyData.withdrawable > 0) { // assert withdrawing exactly works _withdraw(mockProtocol, address(USDC), alice, userSupplyData.withdrawable - 1); // supply it back _supply(mockProtocol, address(USDC), alice, userSupplyData.withdrawable - 1); } // assert borrowLimit (ResolverStructs.UserBorrowData memory userBorrowData, ) = resolver.getUserBorrowData( address(mockProtocol), address(USDC) ); assertEq(userBorrowData.borrowLimit, borrowLimit, "borrowLimit off"); assertEq(userBorrowData.borrowableUntilLimit, borrowLimit - userBorrowData.borrow, "borrowableUntilLimit off"); assertEq(userBorrowData.borrowable, borrowLimit - userBorrowData.borrow, "borrowable off"); // assert reverts if borrowing more vm.expectRevert( abi.encodeWithSelector(Error.FluidLiquidityError.selector, ErrorTypes.UserModule__BorrowLimitReached) ); _borrow(mockProtocol, address(USDC), alice, userBorrowData.borrowable + 1); if (userBorrowData.borrowable > 1e3) { { uint256 borrowedBefore_ = userBorrowData.borrow; // assert borrowing exactly works _borrow(mockProtocol, address(USDC), alice, userBorrowData.borrowable - 1); // payback (userBorrowData, ) = resolver.getUserBorrowData(address(mockProtocol), address(USDC)); _payback(mockProtocol, address(USDC), alice, userBorrowData.borrow - borrowedBefore_); } } _withdraw(mockProtocolInterestFree, address(USDC), alice, 30 ether); uint256[] memory returnedSupplyExchangePrice; uint256[] memory returnedBorrowExchangePrice; { address[] memory tokens = new address[](1); tokens[0] = address(USDC); vm.prank(admin); (returnedSupplyExchangePrice, returnedBorrowExchangePrice) = FluidLiquidityAdminModule(address(liquidity)) .updateExchangePrices(tokens); } assertEq(returnedSupplyExchangePrice[0], supplyExchangePrice); assertEq(returnedBorrowExchangePrice[0], borrowExchangePrice); } } contract LiquidityUserModuleYieldCombinationRateV1Test is LiquidityUserModuleYieldCombinationBaseTest { function setUp() public virtual override { super.setUp(); // set rate data v1 _setDefaultRateDataV1(address(liquidity), admin, address(USDC)); } } contract LiquidityUserModuleYieldCombinationRateV2Test is LiquidityUserModuleYieldCombinationBaseTest { function setUp() public virtual override { super.setUp(); // set rate data v2 _setDefaultRateDataV2(address(liquidity), admin, address(USDC)); } }