From d218c4fdf7846bf41139ba9220c35e88a9dcc7b1 Mon Sep 17 00:00:00 2001 From: Alien Deployer Date: Mon, 28 Oct 2024 02:49:28 +0300 Subject: [PATCH] TPF: draft implementation --- chains/RealLib.sol | 22 ++- src/integrations/pearl/ILiquidBox.sol | 13 +- src/integrations/pearl/ILiquidBoxFactory.sol | 2 + src/integrations/pearl/ILiquidBoxManager.sol | 19 ++ src/strategies/TridentPearlFarmStrategy.sol | 183 ++++++++++++++++--- test/base/chains/RealSetup.sol | 13 +- test/strategies/TPF.Real.t.sol | 24 +++ 7 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 src/integrations/pearl/ILiquidBoxManager.sol create mode 100644 test/strategies/TPF.Real.t.sol diff --git a/chains/RealLib.sol b/chains/RealLib.sol index 3ea0f668..0855ba79 100644 --- a/chains/RealLib.sol +++ b/chains/RealLib.sol @@ -15,6 +15,7 @@ import {DiaAdapter} from "../src/adapters/DiaAdapter.sol"; import {ILiquidBoxFactory} from "../src/integrations/pearl/ILiquidBoxFactory.sol"; import {ILiquidBox} from "../src/integrations/pearl/ILiquidBox.sol"; import {IGaugeV2CL} from "../src/integrations/pearl/IGaugeV2CL.sol"; +import {TridentPearlFarmStrategy} from "../src/strategies/TridentPearlFarmStrategy.sol"; /// @dev Re.al network [chainId: 111188] data library /// ______ _ @@ -135,21 +136,30 @@ library RealLib { ISwapper swapper = ISwapper(IPlatform(platform).swapper()); swapper.addBlueChipsPools(bcPools, false); swapper.addPools(pools, false); - address[] memory tokenIn = new address[](1); + address[] memory tokenIn = new address[](3); tokenIn[0] = TOKEN_USDC; + tokenIn[1] = TOKEN_PEARL; + tokenIn[2] = TOKEN_MORE; // todo thresholds - uint[] memory thresholdAmount = new uint[](1); - thresholdAmount[0] = 1e3; + uint[] memory thresholdAmount = new uint[](3); + thresholdAmount[0] = 1e4; + thresholdAmount[1] = 1e15; + thresholdAmount[2] = 1e15; swapper.setThresholds(tokenIn, thresholdAmount); LogDeployLib.logSetupSwapper(platform, showLog); } - //endregion -- Setup Swapper ----- + //endregion ----- Setup Swapper ----- //region ----- Add farms ----- factory.addFarms(farms()); LogDeployLib.logAddedFarms(address(factory), showLog); //endregion -- Add farms ----- + //region ----- Deploy strategy logics ----- + _addStrategyLogic(factory, StrategyIdLib.TRIDENT_PEARL_FARM, address(new TridentPearlFarmStrategy()), true); + LogDeployLib.logDeployStrategies(platform, showLog); + //endregion -- Deploy strategy logics ----- + // ... } @@ -204,6 +214,7 @@ library RealLib { function _makeTridentPearlFarm(address pool) internal view returns (IFactory.Farm memory) { address alm = ILiquidBoxFactory(TRIDENT_LIQUID_BOX_FACTORY).getBoxByPool(pool); address gauge = ILiquidBox(alm).gauge(); + address boxManager = ILiquidBoxFactory(TRIDENT_LIQUID_BOX_FACTORY).boxManager(); IFactory.Farm memory farm; farm.status = 0; @@ -211,9 +222,10 @@ library RealLib { farm.strategyLogicId = StrategyIdLib.TRIDENT_PEARL_FARM; farm.rewardAssets = new address[](1); farm.rewardAssets[0] = IGaugeV2CL(gauge).rewardToken(); - farm.addresses = new address[](2); + farm.addresses = new address[](3); farm.addresses[0] = alm; farm.addresses[1] = gauge; + farm.addresses[2] = boxManager; farm.nums = new uint[](0); farm.ticks = new int24[](0); return farm; diff --git a/src/integrations/pearl/ILiquidBox.sol b/src/integrations/pearl/ILiquidBox.sol index ba0de5e1..dcbe5df1 100644 --- a/src/integrations/pearl/ILiquidBox.sol +++ b/src/integrations/pearl/ILiquidBox.sol @@ -45,7 +45,13 @@ interface ILiquidBox { * other words, how much of each token the vault would hold if it withdrew * all its liquidity from Uniswap. */ - function getTotalAmounts() external view returns (uint total0, uint total1, uint128 liquidity); + function getTotalAmounts() external view returns ( + uint total0, + uint total1, + uint pool0, + uint pool1, + uint128 liquidity + ); /** * @dev Returns the amount of tokens in existence. @@ -57,4 +63,9 @@ interface ILiquidBox { function token1() external view returns (address); function gauge() external view returns (address); + + function getRequiredAmountsForInput( + uint amount0, + uint amount1 + ) external view returns (uint, uint); } diff --git a/src/integrations/pearl/ILiquidBoxFactory.sol b/src/integrations/pearl/ILiquidBoxFactory.sol index fb9ff3ca..f65f771b 100644 --- a/src/integrations/pearl/ILiquidBoxFactory.sol +++ b/src/integrations/pearl/ILiquidBoxFactory.sol @@ -3,4 +3,6 @@ pragma solidity ^0.8.23; interface ILiquidBoxFactory { function getBoxByPool(address pool) external view returns (address); + + function boxManager() external view returns (address); } diff --git a/src/integrations/pearl/ILiquidBoxManager.sol b/src/integrations/pearl/ILiquidBoxManager.sol new file mode 100644 index 00000000..7ab60a19 --- /dev/null +++ b/src/integrations/pearl/ILiquidBoxManager.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +interface ILiquidBoxManager { + function deposit( + address box, + uint deposit0, + uint deposit1, + uint amount0Min, + uint amount1Min + ) external payable returns (uint shares); + + function withdraw( + address box, + uint shares, + uint amount0Min, + uint amount1Min + ) external returns (uint amount0, uint amount1); +} diff --git a/src/strategies/TridentPearlFarmStrategy.sol b/src/strategies/TridentPearlFarmStrategy.sol index 958bd4da..9109e635 100644 --- a/src/strategies/TridentPearlFarmStrategy.sol +++ b/src/strategies/TridentPearlFarmStrategy.sol @@ -6,7 +6,14 @@ import "./base/FarmingStrategyBase.sol"; import "./libs/StrategyIdLib.sol"; import "../adapters/libs/AmmAdapterIdLib.sol"; import {FarmMechanicsLib} from "./libs/FarmMechanicsLib.sol"; - +import {ILiquidBox} from "../integrations/pearl/ILiquidBox.sol"; +import {IUniswapV3Pool} from "../integrations/uniswapv3/IUniswapV3Pool.sol"; +import {UniswapV3MathLib} from "./libs/UniswapV3MathLib.sol"; +import {ILiquidBoxManager} from "../integrations/pearl/ILiquidBoxManager.sol"; +import {IGaugeV2CL} from "../integrations/pearl/IGaugeV2CL.sol"; + +/// @title Earn Pearl emission by staking Trident ALM tokens to gauge +/// @author Alien Deployer (https://github.com/a17) contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { using SafeERC20 for IERC20; @@ -17,6 +24,8 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { /// @inheritdoc IControllable string public constant VERSION = "1.0.0"; + uint internal constant _PRECISION = 10 ** 36; + /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INITIALIZATION */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ @@ -28,7 +37,7 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { } IFactory.Farm memory farm = _getFarm(addresses[0], nums[0]); - if (farm.addresses.length != 1 || farm.nums.length != 1 || farm.ticks.length != 0) { + if (farm.addresses.length != 3 || farm.nums.length != 0 || farm.ticks.length != 0) { revert IFarmingStrategy.BadFarm(); } @@ -45,8 +54,9 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { __FarmingStrategyBase_init(addresses[0], nums[0]); address[] memory _assets = assets(); - IERC20(_assets[0]).forceApprove(farm.addresses[0], type(uint).max); - IERC20(_assets[1]).forceApprove(farm.addresses[0], type(uint).max); + IERC20(_assets[0]).forceApprove(farm.addresses[2], type(uint).max); + IERC20(_assets[1]).forceApprove(farm.addresses[2], type(uint).max); + IERC20(farm.addresses[0]).forceApprove(farm.addresses[1], type(uint).max); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ @@ -76,7 +86,11 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { /// @inheritdoc IStrategy function getRevenue() external view returns (address[] memory __assets, uint[] memory amounts) { - // todo + IFarmingStrategy.FarmingStrategyBaseStorage storage $f = _getFarmingStrategyBaseStorage(); + IFactory.Farm memory farm = IFactory(IPlatform(platform()).factory()).farm($f.farmId); + __assets = $f._rewardAssets; + amounts = new uint[](1); + amounts[0] = IGaugeV2CL(farm.addresses[1]).earnedReward(address(this)); } /// @inheritdoc IStrategy @@ -85,7 +99,39 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { view returns (string[] memory variants, address[] memory addresses, uint[] memory nums, int24[] memory ticks) { - // todo + IAmmAdapter _ammAdapter = IAmmAdapter(IPlatform(platform_).ammAdapter(keccak256(bytes(ammAdapterId()))).proxy); + addresses = new address[](0); + ticks = new int24[](0); + + IFactory.Farm[] memory farms = IFactory(IPlatform(platform_).factory()).farms(); + uint len = farms.length; + //slither-disable-next-line uninitialized-local + uint localTtotal; + //nosemgrep + for (uint i; i < len; ++i) { + //nosemgrep + IFactory.Farm memory farm = farms[i]; + //nosemgrep + if (farm.status == 0 && CommonLib.eq(farm.strategyLogicId, strategyLogicId())) { + ++localTtotal; + } + } + + variants = new string[](localTtotal); + nums = new uint[](localTtotal); + localTtotal = 0; + //nosemgrep + for (uint i; i < len; ++i) { + //nosemgrep + IFactory.Farm memory farm = farms[i]; + //nosemgrep + if (farm.status == 0 && CommonLib.eq(farm.strategyLogicId, strategyLogicId())) { + nums[localTtotal] = i; + //slither-disable-next-line calls-loop + variants[localTtotal] = _generateDescription(farm, _ammAdapter); + ++localTtotal; + } + } } /// @inheritdoc IStrategy @@ -94,9 +140,14 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { } /// @inheritdoc IStrategy - function getAssetsProportions() external view returns (uint[] memory proportions) { + function getAssetsProportions() public view returns (uint[] memory proportions) { + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + LPStrategyBaseStorage storage $lp = _getLPStrategyBaseStorage(); proportions = new uint[](2); - proportions[0] = _getProportion0(pool()); + (uint total0, uint total1,,,) = ILiquidBox(__$__._underlying).getTotalAmounts(); + uint price = _getPoolPrice($lp.pool); + uint pool0PricedInToken1 = UniswapV3MathLib.mulDiv(total0, price, _PRECISION); + proportions[0] = pool0PricedInToken1 * 1e18 / (pool0PricedInToken1 + total1); proportions[1] = 1e18 - proportions[0]; } @@ -107,7 +158,10 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { /// @inheritdoc IStrategy function description() external view returns (string memory) { - // todo + IFarmingStrategy.FarmingStrategyBaseStorage storage $f = _getFarmingStrategyBaseStorage(); + ILPStrategy.LPStrategyBaseStorage storage $lp = _getLPStrategyBaseStorage(); + IFactory.Farm memory farm = IFactory(IPlatform(platform()).factory()).farm($f.farmId); + return _generateDescription(farm, $lp.ammAdapter); } /// @inheritdoc IStrategy @@ -146,29 +200,50 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @inheritdoc StrategyBase - function _depositAssets(uint[] memory amounts, bool claimRevenue) internal override returns (uint value) { - // todo + function _depositAssets(uint[] memory amounts, bool /*claimRevenue*/ ) internal override returns (uint value) { + IFactory.Farm memory farm = _getFarm(); + value = ILiquidBoxManager(farm.addresses[2]).deposit(farm.addresses[0], amounts[0], amounts[1], 0, 0); + if (value != 0) { + IGaugeV2CL(farm.addresses[1]).deposit(value); + } + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + __$__.total += value; } /// @inheritdoc StrategyBase function _depositUnderlying(uint amount) internal override returns (uint[] memory amountsConsumed) { - // todo + IFactory.Farm memory farm = _getFarm(); + IGaugeV2CL(farm.addresses[1]).deposit(amount); + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + amountsConsumed = _previewDepositUnderlying(amount); + __$__.total += amount; } /// @inheritdoc StrategyBase function _withdrawAssets(uint value, address receiver) internal override returns (uint[] memory amountsOut) { - // todo + amountsOut = new uint[](2); + IFactory.Farm memory farm = _getFarm(); + IGaugeV2CL(farm.addresses[1]).withdraw(value); + (amountsOut[0], amountsOut[1]) = ILiquidBoxManager(farm.addresses[2]).withdraw(farm.addresses[0], value, 0, 0); + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + address[] memory _assets = __$__._assets; + IERC20(_assets[0]).safeTransfer(receiver, amountsOut[0]); + IERC20(_assets[1]).safeTransfer(receiver, amountsOut[1]); + __$__.total -= value; } /// @inheritdoc StrategyBase function _withdrawUnderlying(uint amount, address receiver) internal override { - // todo + IFactory.Farm memory farm = _getFarm(); + IGaugeV2CL(farm.addresses[1]).withdraw(amount); + IERC20(farm.addresses[0]).safeTransfer(receiver, amount); + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + __$__.total -= amount; } /// @inheritdoc StrategyBase function _claimRevenue() internal - view override returns ( address[] memory __assets, @@ -177,12 +252,25 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { uint[] memory __rewardAmounts ) { - // todo + IFactory.Farm memory farm = _getFarm(); + StrategyBaseStorage storage __$__ = _getStrategyBaseStorage(); + FarmingStrategyBaseStorage storage _$_ = _getFarmingStrategyBaseStorage(); + __assets = __$__._assets; + __rewardAssets = _$_._rewardAssets; + __amounts = new uint[](2); + __rewardAmounts = new uint[](1); + uint balBefore = IERC20(__rewardAssets[0]).balanceOf(address(this)); + IGaugeV2CL(farm.addresses[1]).collectReward(); + uint balAfter = IERC20(__rewardAssets[0]).balanceOf(address(this)); + __rewardAmounts[0] = balAfter - balBefore; } /// @inheritdoc StrategyBase function _compound() internal override { - // todo + (uint[] memory amountsToDeposit) = _swapForDepositProportion(getAssetsProportions()[0]); + if (amountsToDeposit[0] != 0 || amountsToDeposit[1] != 0) { + _depositAssets(amountsToDeposit, true); + } } /// @inheritdoc StrategyBase @@ -192,32 +280,79 @@ contract TridentPearlFarmStrategy is LPStrategyBase, FarmingStrategyBase { override(StrategyBase, LPStrategyBase) returns (uint[] memory amountsConsumed, uint value) { - // todo + amountsConsumed = new uint[](2); + IFactory.Farm memory farm = _getFarm(); + address pool = farm.pool; + address alm = farm.addresses[0]; + (amountsConsumed[0], amountsConsumed[1]) = + ILiquidBox(alm).getRequiredAmountsForInput(amountsMax[0], amountsMax[1]); + value = _calcShares(pool, alm, amountsConsumed[0], amountsConsumed[1]); } /// @inheritdoc StrategyBase function _previewDepositUnderlying(uint amount) internal view override returns (uint[] memory amountsConsumed) { - // todo + StrategyBaseStorage storage $ = _getStrategyBaseStorage(); + ILiquidBox alm = ILiquidBox($._underlying); + + (uint total0, uint total1,,,) = alm.getTotalAmounts(); + uint totalInAlm = alm.totalSupply(); + amountsConsumed = new uint[](2); + amountsConsumed[0] = total0 * amount / totalInAlm; + amountsConsumed[1] = total1 * amount / totalInAlm; } /// @inheritdoc StrategyBase function _assetsAmounts() internal view override returns (address[] memory assets_, uint[] memory amounts_) { - // todo + StrategyBaseStorage storage $ = _getStrategyBaseStorage(); + assets_ = $._assets; + amounts_ = new uint[](2); + uint _total = $.total; + if (_total > 0) { + ILiquidBox alm = ILiquidBox($._underlying); + (amounts_[0], amounts_[1],,,) = alm.getTotalAmounts(); + uint totalInAlm = alm.totalSupply(); + (amounts_[0], amounts_[1]) = (amounts_[0] * _total / totalInAlm, amounts_[1] * _total / totalInAlm); + } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL LOGIC */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ - /// @dev proportion of 1e18 - function _getProportion0(address pool_) internal view returns (uint) { - // todo + function _calcShares(address pool, address alm, uint deposit0, uint deposit1) internal view returns (uint shares) { + uint totalSupply = IERC20(alm).totalSupply(); + (uint total0, uint total1,,,) = ILiquidBox(alm).getTotalAmounts(); + uint price = _getPoolPrice(pool); + + shares = deposit1 + UniswapV3MathLib.mulDiv(deposit0, price, _PRECISION); + + if (totalSupply != 0) { + uint pool0PricedInToken1 = UniswapV3MathLib.mulDiv(total0, price, _PRECISION); + shares = UniswapV3MathLib.mulDiv(shares, totalSupply, pool0PricedInToken1 + total1); + } + } + + function _getPoolPrice(address pool) internal view returns (uint price) { + (, int24 tick,,,,,) = IUniswapV3Pool(pool).slot0(); + uint160 sqrtPrice = UniswapV3MathLib.getSqrtRatioAtTick(tick); + // sqrtPrice < type(uint128).max for maxTick value int24(16777215) + uint ratioX192 = uint(sqrtPrice) * sqrtPrice; + price = UniswapV3MathLib.mulDiv(ratioX192, _PRECISION, 1 << 192); } function _generateDescription( IFactory.Farm memory farm, IAmmAdapter _ammAdapter ) internal view returns (string memory) { - // todo + //slither-disable-next-line calls-loop + return string.concat( + "Earn ", + //slither-disable-next-line calls-loop + CommonLib.implode(CommonLib.getSymbols(farm.rewardAssets), ", "), + " on Pearl pool ", + //slither-disable-next-line calls-loop + CommonLib.implode(CommonLib.getSymbols(_ammAdapter.poolTokens(farm.pool)), "-"), + " by Trident ALM" + ); } } diff --git a/test/base/chains/RealSetup.sol b/test/base/chains/RealSetup.sol index 84eadfd3..257a51ed 100644 --- a/test/base/chains/RealSetup.sol +++ b/test/base/chains/RealSetup.sol @@ -26,6 +26,17 @@ abstract contract RealSetup is ChainSetup, DeployCore { } function _deal(address token, address to, uint amount) internal override { - deal(token, to, amount); + if (token == RealLib.TOKEN_arcUSD) { + vm.prank(0xA3949263535D40d470132Ab6CA76b16D6183FD31); // stack vault + IERC20(token).transfer(to, amount + 1); // need for this token + } else if (token == RealLib.TOKEN_UKRE) { + vm.prank(0x05bB93F3c1905Fc77ee1256E0998ABA75B5C9603); // top2 holder + IERC20(token).transfer(to, amount + amount / 10); + } else if (token == RealLib.TOKEN_DAI) { + vm.prank(0x05bB93F3c1905Fc77ee1256E0998ABA75B5C9603); // top2 holder + IERC20(token).transfer(to, amount + amount / 10); + } else { + deal(token, to, amount); + } } } diff --git a/test/strategies/TPF.Real.t.sol b/test/strategies/TPF.Real.t.sol new file mode 100644 index 00000000..58487ca9 --- /dev/null +++ b/test/strategies/TPF.Real.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import "../base/chains/RealSetup.sol"; +import "../base/UniversalTest.sol"; + +contract TridentPearlFarmStrategyTest is RealSetup, UniversalTest { + receive() external payable {} + + function testTPF() public universalTest { + _addStrategy(0); + _addStrategy(1); + // _addStrategy(2); + // _addStrategy(3); + // _addStrategy(4); + // _addStrategy(6); + } + + function _addStrategy(uint farmId) internal { + strategies.push( + Strategy({id: StrategyIdLib.TRIDENT_PEARL_FARM, pool: address(0), farmId: farmId, underlying: address(0)}) + ); + } +}