diff --git a/contracts/ionic/levered/LeveredPositionsLens.sol b/contracts/ionic/levered/LeveredPositionsLens.sol index c78b8dad..78f12180 100644 --- a/contracts/ionic/levered/LeveredPositionsLens.sol +++ b/contracts/ionic/levered/LeveredPositionsLens.sol @@ -16,6 +16,10 @@ contract LeveredPositionsLens is Initializable { factory = _factory; } + function reinitialize(ILeveredPositionFactory _factory) external reinitializer(2) { + factory = _factory; + } + /// @notice this is a lens fn, it is not intended to be used on-chain /// @dev returns lists of the market addresses, names and symbols of the underlying assets of those collateral markets that are whitelisted function getCollateralMarkets() diff --git a/contracts/ionic/strategies/flywheel/IonicFlywheelLensRouter.sol b/contracts/ionic/strategies/flywheel/IonicFlywheelLensRouter.sol index 9f362244..601722c4 100644 --- a/contracts/ionic/strategies/flywheel/IonicFlywheelLensRouter.sol +++ b/contracts/ionic/strategies/flywheel/IonicFlywheelLensRouter.sol @@ -143,6 +143,103 @@ contract IonicFlywheelLensRouter { return apr; } + function getRewardsAprForMarket(ICErc20 market) internal returns (int256 totalMarketRewardsApr) { + IonicComptroller comptroller = market.comptroller(); + BasePriceOracle oracle = comptroller.oracle(); + uint256 underlyingPrice = oracle.getUnderlyingPrice(market); + + address[] memory flywheels = comptroller.getAccruingFlywheels(); + for (uint256 j = 0; j < flywheels.length; j++) { + IonicFlywheelCore flywheel = IonicFlywheelCore(flywheels[j]); + ERC20 rewardToken = flywheel.rewardToken(); + + uint256 rewardSpeedPerSecondPerToken = getRewardSpeedPerSecondPerToken( + flywheel, + market, + uint256(rewardToken.decimals()) + ); + + uint256 marketApr = getApr( + rewardSpeedPerSecondPerToken, + oracle.price(address(rewardToken)), + underlyingPrice, + market.exchangeRateCurrent() + ); + + totalMarketRewardsApr += int256(marketApr); + } + } + + function getUserNetValueDeltaForMarket( + address user, + ICErc20 market, + int256 offchainApr, + int256 blocksPerYear + ) internal returns (int256) { + IonicComptroller comptroller = market.comptroller(); + BasePriceOracle oracle = comptroller.oracle(); + int256 netApr = getRewardsAprForMarket(market) + + getUserInterestAprForMarket(user, market, blocksPerYear) + + offchainApr; + return (netApr * int256(market.balanceOfUnderlying(user)) * int256(oracle.getUnderlyingPrice(market))) / 1e36; + } + + function getUserInterestAprForMarket( + address user, + ICErc20 market, + int256 blocksPerYear + ) internal returns (int256) { + return (int256(market.supplyRatePerBlock()) - int256(market.borrowRatePerBlock())) * blocksPerYear; + } + + struct AdjustedUserNetAprVars { + int256 userNetAssetsValue; + int256 userNetValueDelta; + BasePriceOracle oracle; + ICErc20[] markets; + IonicComptroller pool; + } + + function getAdjustedUserNetApr( + address user, + int256 blocksPerYear, + address[] memory offchainRewardsAprMarkets, + int256[] memory offchainRewardsAprs + ) public returns (int256) { + AdjustedUserNetAprVars memory vars; + + (, PoolDirectory.Pool[] memory pools) = fpd.getActivePools(); + for (uint256 i = 0; i < pools.length; i++) { + IonicComptroller pool = IonicComptroller(pools[i].comptroller); + vars.oracle = pool.oracle(); + vars.markets = pool.getAllMarkets(); + for (uint256 j = 0; j < vars.markets.length; j++) { + int256 offchainRewardsApr = 0; + for (uint256 k = 0; k < offchainRewardsAprMarkets.length; k++) { + if (offchainRewardsAprMarkets[k] == address(vars.markets[j])) offchainRewardsApr = offchainRewardsAprs[k]; + } + vars.userNetAssetsValue += + int256(vars.markets[j].balanceOfUnderlying(user) * vars.oracle.getUnderlyingPrice(vars.markets[j])) / + 1e18; + vars.userNetValueDelta += getUserNetValueDeltaForMarket( + user, + vars.markets[j], + offchainRewardsApr, + blocksPerYear + ); + } + } + + if (vars.userNetAssetsValue == 0) return 0; + else return (vars.userNetValueDelta * 1e18) / vars.userNetAssetsValue; + } + + function getUserNetApr(address user, int256 blocksPerYear) external returns (int256) { + address[] memory emptyAddrArray = new address[](0); + int256[] memory emptyIntArray = new int256[](0); + return getAdjustedUserNetApr(user, blocksPerYear, emptyAddrArray, emptyIntArray); + } + function getAllRewardTokens() public view returns (address[] memory uniqueRewardTokens) { (, PoolDirectory.Pool[] memory pools) = fpd.getActivePools(); diff --git a/contracts/test/FLRTest.t.sol b/contracts/test/FLRTest.t.sol index b85fa9af..e2ff0815 100644 --- a/contracts/test/FLRTest.t.sol +++ b/contracts/test/FLRTest.t.sol @@ -136,4 +136,18 @@ contract FLRTest is BaseTest { IonicFlywheelLensRouter router = IonicFlywheelLensRouter(0x3391ed1C5203168337Fa827cB5Ac8BB8B60D93B7); router.getPoolMarketRewardsInfo(IonicComptroller(0x044c436b2f3EF29D30f89c121f9240cf0a08Ca4b)); } + + function testNetAprPolygon() public fork(POLYGON_MAINNET) { + address user = 0x8982aa50bb919E42e9204f12e5b59D053Eb2A602; + int256 blocks = 26 * 24 * 365 * 60; + int256 apr = lensRouter.getUserNetApr(user, blocks); + emit log_named_int("apr", apr); + } + + function testNetAprChapel() public fork(BSC_CHAPEL) { + address user = 0x8982aa50bb919E42e9204f12e5b59D053Eb2A602; + int256 blocks = 26 * 24 * 365 * 60; + int256 apr = lensRouter.getUserNetApr(user, blocks); + emit log_named_int("apr", apr); + } } diff --git a/contracts/test/LeveredPositionTest.t.sol b/contracts/test/LeveredPositionTest.t.sol index a0817671..b5938a6f 100644 --- a/contracts/test/LeveredPositionTest.t.sol +++ b/contracts/test/LeveredPositionTest.t.sol @@ -84,6 +84,17 @@ contract LeveredPositionLensTest is BaseTest { emit log(""); } } + + function testPrintLeveredPositions() public fork(POLYGON_MAINNET) { + address[] memory markets = factory.getWhitelistedCollateralMarkets(); + + emit log_named_array("markets", markets); + + for (uint256 j = 0; j < markets.length; j++) { + address[] memory borrowable = factory.getBorrowableMarketsByCollateral(ICErc20(markets[j])); + emit log_named_array("borrowable", borrowable); + } + } } contract LeveredPositionFactoryTest is BaseTest { @@ -105,24 +116,6 @@ contract LeveredPositionFactoryTest is BaseTest { abi.encode(borrowRate / factory.blocksPerYear()) ); - { - // upgrade the factory - LeveredPositionFactoryFirstExtension newExt1 = new LeveredPositionFactoryFirstExtension(); - LeveredPositionFactorySecondExtension newExt2 = new LeveredPositionFactorySecondExtension(); - - vm.startPrank(factory.owner()); - DiamondBase asBase = DiamondBase(address(factory)); - address[] memory oldExts = asBase._listExtensions(); - if (oldExts.length == 1) { - asBase._registerExtension(newExt1, DiamondExtension(oldExts[0])); - asBase._registerExtension(newExt2, DiamondExtension(address(0))); - } else if (oldExts.length == 2) { - asBase._registerExtension(newExt1, DiamondExtension(oldExts[0])); - asBase._registerExtension(newExt2, DiamondExtension(oldExts[1])); - } - vm.stopPrank(); - } - uint256 _borrowRate = _stableMarket.borrowRatePerBlock() * factory.blocksPerYear(); emit log_named_uint("_borrowRate", _borrowRate); @@ -525,65 +518,6 @@ contract Jbrl2BrlLeveredPositionTest is LeveredPositionTest { } } -contract Par2EurLeveredPositionTest is LeveredPositionTest { - function setUp() public fork(POLYGON_MAINNET) {} - - function afterForkSetUp() internal override { - super.afterForkSetUp(); - - uint256 depositAmount = 2000e18; - - address twoEurMarket = 0x1944FA4a490f85Ed99e2c6fF9234F94DE16fdbde; - address parMarket = 0xCA1A940B02E15FF71C128f877b29bdb739785299; - address twoEurWhale = address(888); - address balancer = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; - address parWhale = 0xFa22D298E3b0bc1752E5ef2849cEc1149d596674; // uniswap pool - - IERC20Upgradeable twoEur = IERC20Upgradeable(ICErc20(twoEurMarket).underlying()); - vm.prank(balancer); - twoEur.transfer(twoEurWhale, 80 * depositAmount); - - _configurePair(twoEurMarket, parMarket); - _fundMarketAndSelf(ICErc20(twoEurMarket), twoEurWhale); - _fundMarketAndSelf(ICErc20(parMarket), parWhale); - - (position, maxLevRatio, minLevRatio) = _openLeveredPosition(address(this), depositAmount); - } -} - -contract MaticXMaticXBbaWMaticLeveredPositionTest is LeveredPositionTest { - function setUp() public fork(POLYGON_MAINNET) {} - - function afterForkSetUp() internal override { - super.afterForkSetUp(); - - uint256 depositAmount = 1000e18; - - address maticXBbaWMaticMarket = 0x13e763D25D78c3Fd6FEA534231BdaEBE7Fa52945; - address maticXMarket = 0x0db51E5255E44751b376738d8979D969AD70bff6; - address maticXBbaWMaticWhale = 0xB0B28d7A74e62DF5F6F9E0d9Ae0f4e7982De9585; - address maticXWhale = 0x72f0275444F2aF8dBf13F78D54A8D3aD7b6E68db; - - IonicComptroller pool = IonicComptroller(ICErc20(maticXBbaWMaticMarket).comptroller()); - _configurePairAndLiquidator(maticXBbaWMaticMarket, maticXMarket, new BalancerSwapLiquidator()); - - { - vm.prank(pool.admin()); - pool._supplyCapWhitelist(address(maticXBbaWMaticMarket), maticXBbaWMaticWhale, true); - } - - _fundMarketAndSelf(ICErc20(maticXBbaWMaticMarket), maticXBbaWMaticWhale); - _fundMarketAndSelf(ICErc20(maticXMarket), maticXWhale); - - (position, maxLevRatio, minLevRatio) = _openLeveredPosition(address(this), depositAmount); - - { - vm.prank(pool.admin()); - pool._supplyCapWhitelist(address(maticXBbaWMaticMarket), address(position), true); - } - } -} - contract BombTDaiLeveredPositionTest is LeveredPositionTest { uint256 depositAmount = 100e18; address whale = 0xe7B7dF67C1fe053f1C6B965826d3bFF19603c482; @@ -964,7 +898,7 @@ contract RetroCashAUsdcWethLeveredPositionTest is LeveredPositionTest { function afterForkSetUp() internal override { super.afterForkSetUp(); - uint256 depositAmount = 8e18; + uint256 depositAmount = 2e18; // LP token underlying xUSDC-WETH05 address lpTokenMarket = 0xC7cA03A0bE1dBAc350E5BfE5050fC5af6406490E;