From 3e0a32f504eebdc6c4a743cc38bca23d11cdd4f3 Mon Sep 17 00:00:00 2001 From: Alien Deployer Date: Mon, 12 Feb 2024 01:16:15 +0300 Subject: [PATCH] fix strategy, update unreal addresses, IFO event, scripts --- README.md | 13 ++ chains/UnrealLib.sol | 10 +- script/DeployCustomIfoVaultAndStrategy.s.sol | 46 +++++++ script/DeployVaultAndStrategy.s.sol | 42 ++++++ script/PrepareFactoryUpgrade.s.sol | 16 +++ src/Factory.sol | 2 + src/IFO.sol | 10 +- src/PearlStrategy.sol | 8 +- src/PearlStrategyCustomIFO.sol | 131 +++++++++++++++++++ src/interfaces/IIFO.sol | 2 + 10 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 script/DeployCustomIfoVaultAndStrategy.s.sol create mode 100644 script/DeployVaultAndStrategy.s.sol create mode 100644 script/PrepareFactoryUpgrade.s.sol create mode 100644 src/PearlStrategyCustomIFO.sol diff --git a/README.md b/README.md index 6495496..8061b5f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ cast call --rpc-url https://rpc.unreal.gelato.digital 0x35bf701C24357FD0C7F60376 ### Unreal testnet + +#### Deployed contracts + * Controller 0x4F69329E8dE13aA7EAc664368C5858AF6371FA4c [Blockscout](https://unreal.blockscout.com/address/0x4F69329E8dE13aA7EAc664368C5858AF6371FA4c?tab=contract) * IFO 0x3222eb4824cEb0E9CcfE11018C83429105dFE00F [Blockscout](https://unreal.blockscout.com/address/0x3222eb4824cEb0E9CcfE11018C83429105dFE00F?tab=contract) * STGN 0x609e0d74fAB81085283df92B563750624054F8bE [Blockscout](https://unreal.blockscout.com/address/0x609e0d74fAB81085283df92B563750624054F8bE?tab=contract) @@ -41,6 +44,16 @@ cast call --rpc-url https://rpc.unreal.gelato.digital 0x35bf701C24357FD0C7F60376 * DepositHelper 0x7c8d0C7B63249A314df84707F8690F62CF625820 [Blockscout](https://unreal.blockscout.com/address/0x7c8d0C7B63249A314df84707F8690F62CF625820?tab=contract) * Compounder proxy 0x89c06219C24ab4aBd762A49cdE97ce69B05f3EAF [Blockscout](https://unreal.blockscout.com/address/0x89c06219C24ab4aBd762A49cdE97ce69B05f3EAF?tab=contract) +#### Addresses + +* PEARL 0xCE1581d7b4bA40176f0e219b2CaC30088Ad50C7A +* CVR 0xC0Fd0e8d5c3Bdc2C06b2Ee9FfE81ceCbE1B59364 +* pool PEARL-CVR 0x725565FF3d821D655A475819031e168ffb63901C +* LiquidBoxManager 0xCc2b204Fb8BdE255B5820049108c53137F7DdeD4 +* DAI-USDC LiquidBox ALM 0xAB5a4189e947E0e3EbEc25637f73deb55f6CEEA9 +* DAI-USDC GaugeV2ALM 0x54cbd289b263cd14e6707ecbda01161c4385dfe3 + + ### Goerli testnet * Controller 0x8216C9afFC982428aF33D1D9F165bAf9D75AebBa diff --git a/chains/UnrealLib.sol b/chains/UnrealLib.sol index 1b38e3b..b0b6cde 100644 --- a/chains/UnrealLib.sol +++ b/chains/UnrealLib.sol @@ -8,19 +8,19 @@ library UnrealLib { // Unreal tokens address public constant TOKEN_USDC = 0xabAa4C39cf3dF55480292BBDd471E88de8Cc3C97; address public constant TOKEN_DAI = 0x665D4921fe931C0eA1390Ca4e0C422ba34d26169; - address public constant TOKEN_PEARL = 0x1ef116600bBb2e99Ce6CE96B7E66A0df71AF5980; - address public constant TOKEN_CVR = 0xC716C749106B21aa4e8231b7ec891bdEab9bFB30; + address public constant TOKEN_PEARL = 0xCE1581d7b4bA40176f0e219b2CaC30088Ad50C7A; + address public constant TOKEN_CVR = 0xC0Fd0e8d5c3Bdc2C06b2Ee9FfE81ceCbE1B59364; // Pearl DeX address public constant POOL_PEARL_CVR_3000 = 0x6592E84E1903B990C5015F1Ff1A6cc27405EABfB; address public constant POOL_DAI_USDC_1000 = 0x1933cB66cB5A2b47A93753773C556ab6CA825831; address public constant POOL_DAI_PEARL_3000 = 0xeC491B6bC5554f76348FB40eEfbf0Ed60cd22Bd2; - address public constant POOL_PEARL_USDC_3000 = 0x76994b9683e17B0A9Ed344fCD432A8E3BF7E22C1; + address public constant POOL_PEARL_USDC_3000 = 0xd7e172f7e2F60B6438ffC2e52434150878644469; // Trident TDT-DAI-USDC - address public constant LIQUID_BOX_DAI_USDC = 0x8B9184243B8a787eaff8C304b17ED23fFD6F8c23; + address public constant LIQUID_BOX_DAI_USDC = 0xAB5a4189e947E0e3EbEc25637f73deb55f6CEEA9; // IGaugeV2ALM - address public constant ALM_GAUGE_DAI_USDC = 0x659f401aE8194e00673fe58367Ed77137542faA3; + address public constant ALM_GAUGE_DAI_USDC = 0x54CbD289B263CD14E6707EcbDa01161c4385DFe3; // Sturgeon infrastructure address public constant LIQUIDATOR = 0xE0D142466d1BF88FE23D5D265d76068077E4D6F0; diff --git a/script/DeployCustomIfoVaultAndStrategy.s.sol b/script/DeployCustomIfoVaultAndStrategy.s.sol new file mode 100644 index 0000000..620c309 --- /dev/null +++ b/script/DeployCustomIfoVaultAndStrategy.s.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; +import "../src/interfaces/IVault.sol"; +import "../src/HarvesterVault.sol"; +import "../src/interfaces/IMultiPool.sol"; +import "../src/IFO.sol"; +import "../src/PearlStrategyCustomIFO.sol"; + + +contract DeployCustomIfoVaultAndStrategy is Script { + address public constant CONTROLLER = 0x4F69329E8dE13aA7EAc664368C5858AF6371FA4c; + address public constant STGN = 0x609e0d74fAB81085283df92B563750624054F8bE; + address public constant PEARL_TOKEN = 0xCE1581d7b4bA40176f0e219b2CaC30088Ad50C7A; + + function run() external { + address underlying = 0xAB5a4189e947E0e3EbEc25637f73deb55f6CEEA9; + address pearlGauge = 0x54CbD289B263CD14E6707EcbDa01161c4385DFe3; + + uint deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + IFO ifo = new IFO(12e17); + ifo.setup(CONTROLLER, STGN, PEARL_TOKEN); + + address vault = address( + new HarvesterVault( + CONTROLLER, + IERC20(underlying), + "IFO Harvester DAI-USDC custom", + "ifocTDT-DAI-USDC", + 4_000 + ) + ); + address strategy = address(new PearlStrategyCustomIFO(address(ifo), vault, pearlGauge, true, address(0))); + IVault(vault).setStrategy(strategy); + address multigauge = IController(CONTROLLER).multigauge(); + IGauge(multigauge).addStakingToken(vault); + IController(CONTROLLER).registerVault(vault, true); + + vm.stopBroadcast(); + } + + function testDeploy_() external {} +} diff --git a/script/DeployVaultAndStrategy.s.sol b/script/DeployVaultAndStrategy.s.sol new file mode 100644 index 0000000..a100e08 --- /dev/null +++ b/script/DeployVaultAndStrategy.s.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; +import "../src/interfaces/IVault.sol"; +import "../src/HarvesterVault.sol"; +import "../src/PearlStrategy.sol"; +import "../src/interfaces/IMultiPool.sol"; + + +contract DeployVaultAndStrategy is Script { + address public constant CONTROLLER = 0x4F69329E8dE13aA7EAc664368C5858AF6371FA4c; + + function run() external { + address underlying = 0xAB5a4189e947E0e3EbEc25637f73deb55f6CEEA9; + address pearlGauge = 0x54CbD289B263CD14E6707EcbDa01161c4385DFe3; + address compounderVault = 0xE983c2da7Ef6bFFF9b0b10ADdBF2f0E1ed9b5043; + + uint deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + address vault = address( + new HarvesterVault( + CONTROLLER, + IERC20(underlying), + "Harvester DAI-USDC (New)", + "xTDT-DAI-USDC", + 4_000 + ) + ); + address strategy = address(new PearlStrategy(vault, pearlGauge, false, compounderVault)); + IVault(vault).setStrategy(strategy); + address multigauge = IController(CONTROLLER).multigauge(); + IGauge(multigauge).addStakingToken(vault); + IMultiPool(multigauge).registerRewardToken(vault, compounderVault); + IController(CONTROLLER).registerVault(vault, true); + + vm.stopBroadcast(); + } + + function testDeploy_() external {} +} diff --git a/script/PrepareFactoryUpgrade.s.sol b/script/PrepareFactoryUpgrade.s.sol new file mode 100644 index 0000000..36c6291 --- /dev/null +++ b/script/PrepareFactoryUpgrade.s.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.21; + +import "forge-std/Script.sol"; +import "../src/Factory.sol"; + +contract PrepareFactoryUpgrade is Script { + function run() external { + uint deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + new Factory(); + vm.stopBroadcast(); + } + + function testDeploy_() external {} +} diff --git a/src/Factory.sol b/src/Factory.sol index d7c5ec9..6cead01 100644 --- a/src/Factory.sol +++ b/src/Factory.sol @@ -8,6 +8,8 @@ import "./interfaces/IMultiPool.sol"; import "./lib/DeployerLib.sol"; contract Factory is Controllable { + string public constant VERSION = '1.0.0'; + function init(address controller_) external initializer { __Controllable_init(controller_); } diff --git a/src/IFO.sol b/src/IFO.sol index f74df9b..f607f10 100644 --- a/src/IFO.sol +++ b/src/IFO.sol @@ -6,13 +6,13 @@ import "./interfaces/IController.sol"; import "./interfaces/IIFO.sol"; import "./interfaces/IStrategyStrict.sol"; +/// @title Initial Farm Offering +/// @notice Contract contain all preminted STGN and allowing to change them to LP rewards until tokens exists on the balance. +/// The exchange processed by a fixed immutable rate. +/// Rewards goes directly to governance. contract IFO is IIFO { using SafeERC20 for IERC20; - // This contract should contain all preminted STGN and allowing to change them to LP rewards until tokens exists on the balance. - // The exchange will be done by a fixed rate that setup on deploy. Will be not changed later. - // Rewards will be sent directly to governance. - address public stgn; address public rewardToken; address public controller; @@ -28,7 +28,7 @@ contract IFO is IIFO { stgn = stgn_; rewardToken = rewardToken_; controller = controller_; - // add event + emit Setup(controller_, stgn_, rewardToken_, rate); } function exchange(uint amount) external returns (bool, uint) { diff --git a/src/PearlStrategy.sol b/src/PearlStrategy.sol index 4599519..3dc2279 100644 --- a/src/PearlStrategy.sol +++ b/src/PearlStrategy.sol @@ -25,6 +25,8 @@ import "./interfaces/IVeDistributor.sol"; contract PearlStrategy is StrategyStrictBase { using SafeERC20 for IERC20; + uint internal constant LIQUIDATOR_PRICE_IMPACT_TOLERANCE = 20_000; + uint public lastHardWork; address public gauge; @@ -80,15 +82,13 @@ contract PearlStrategy is StrategyStrictBase { (bool exchanged, uint got) = _ifo.exchange(rtReward); if (exchanged && got > 0) { multigauge.notifyRewardAmount(vault, controller.stgn(), got); - } /* else { - multigauge.notifyRewardAmount(vault, IGaugeV2ALM(gauge).rewardToken(), rtReward); - }*/ + } } else { address _compounder = compounder; ITetuLiquidator l = ITetuLiquidator(controller.liquidator()); address asset = IERC4626(_compounder).asset(); uint b = IERC20(asset).balanceOf(address(this)); - l.liquidate(gaugeRewardToken, asset, rtReward, 0); + l.liquidate(gaugeRewardToken, asset, rtReward, LIQUIDATOR_PRICE_IMPACT_TOLERANCE); uint got = IERC20(asset).balanceOf(address(this)) - b; if (got > 0) { uint shares = IERC4626(_compounder).deposit(got, address(this)); diff --git a/src/PearlStrategyCustomIFO.sol b/src/PearlStrategyCustomIFO.sol new file mode 100644 index 0000000..584eb4e --- /dev/null +++ b/src/PearlStrategyCustomIFO.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.21; + +import "./base/StrategyStrictBase.sol"; +import "./interfaces/IVault.sol"; +import "./interfaces/IGaugeV2ALM.sol"; +import "./interfaces/IController.sol"; +import "./interfaces/IIFO.sol"; +import "./interfaces/IGauge.sol"; +import "./interfaces/ITetuLiquidator.sol"; +import "./interfaces/IVeDistributor.sol"; + +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.°:°•.°+.*•´.*:*.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*/ +/* Pearl strategy */ +/* Contract for taking care of all necessary actions for the underlying token such us: */ +/* Rewards utilization via Liquidator */ +/* Compounding - creating more underlying */ +/* Sending profit to different destinations */ +/* Have rewards/compounding logic, depending on setup case */ +/* Strategy should send gas compensation on every compounding */ +/* Compensation will be taken from user part of profit but no more than 10% */ +/* Compounding should be called no more frequently than 1 per 12h */ +/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.°:°•.°+.*•´.*:*.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*/ + +contract PearlStrategyCustomIFO is StrategyStrictBase { + using SafeERC20 for IERC20; + + uint internal constant LIQUIDATOR_PRICE_IMPACT_TOLERANCE = 20_000; + + uint public lastHardWork; + + address public gauge; + + address public gaugeRewardToken; + + address public ifoAddress; + + bool public ifo; + + address public compounder; + + constructor(address ifoAddress_, address vault_, address gauge_, bool ifo_, address compounder_) StrategyStrictBase(vault_) { + gauge = gauge_; + ifo = ifo_; + ifoAddress = ifoAddress_; + compounder = compounder_; + IERC20(asset).approve(gauge_, type(uint).max); + IController controller = IController(IVault(vault).controller()); + address _gaugeRewardToken = IGaugeV2ALM(gauge_).rewardToken(); + gaugeRewardToken = _gaugeRewardToken; + if (ifo) { + IERC20(_gaugeRewardToken).approve(ifoAddress_, type(uint).max); + IERC20(controller.stgn()).approve(controller.multigauge(), type(uint).max); + } else { + IERC20(_gaugeRewardToken).approve(controller.liquidator(), type(uint).max); + address compounderAsset = IERC4626(compounder_).asset(); + IERC20(compounderAsset).approve(compounder_, type(uint).max); + IERC20(compounder_).approve(controller.multigauge(), type(uint).max); + } + } + + function isReadyToHardWork() external view returns (bool) { + return IGaugeV2ALM(gauge).earnedReward(address(this)) > 0; + } + + function doHardWork() external /* returns (uint earned, uint lost)*/ { + // claim fees if available + // liquidate fee if available + + uint rtReward = _claim(); + uint perfFee = rtReward / 10; + uint veDistFee = perfFee / 2; + rtReward -= perfFee; + IERC20 rt = IERC20(gaugeRewardToken); + + IController controller = IController(IVault(vault).controller()); + IVeDistributor veDist = IVeDistributor(controller.veDistributor()); + rt.safeTransfer(address(veDist), veDistFee); + veDist.checkpoint(); + rt.safeTransfer(controller.perfFeeTreasury(), perfFee - veDistFee); + + IGauge multigauge = IGauge(controller.multigauge()); + if (ifo) { + IIFO _ifo = IIFO(ifoAddress); + (bool exchanged, uint got) = _ifo.exchange(rtReward); + if (exchanged && got > 0) { + multigauge.notifyRewardAmount(vault, controller.stgn(), got); + } + } else { + address _compounder = compounder; + ITetuLiquidator l = ITetuLiquidator(controller.liquidator()); + address asset = IERC4626(_compounder).asset(); + uint b = IERC20(asset).balanceOf(address(this)); + l.liquidate(gaugeRewardToken, asset, rtReward, LIQUIDATOR_PRICE_IMPACT_TOLERANCE); + uint got = IERC20(asset).balanceOf(address(this)) - b; + if (got > 0) { + uint shares = IERC4626(_compounder).deposit(got, address(this)); + multigauge.notifyRewardAmount(vault, _compounder, shares); + } + } + } + + function investedAssets() public view override returns (uint) { + return IGaugeV2ALM(gauge).balanceOf(address(this)); + } + + function _claim() internal override returns (uint rtReward) { + IERC20 rt = IERC20(gaugeRewardToken); + uint oldBal = rt.balanceOf(address(this)); + IGaugeV2ALM(gauge).collectReward(); + rtReward = rt.balanceOf(address(this)) - oldBal; + } + + function _depositToPool(uint amount) internal override { + IGaugeV2ALM(gauge).deposit(amount); + } + + function _emergencyExitFromPool() internal override { + _withdrawAllFromPool(); + IERC20(asset).safeTransfer(vault, IERC20(asset).balanceOf(address(this))); + } + + function _withdrawFromPool(uint amount) internal override { + IGaugeV2ALM(gauge).withdraw(amount); + IERC20(asset).safeTransfer(vault, amount); + } + + function _withdrawAllFromPool() internal override { + _withdrawFromPool(IGaugeV2ALM(gauge).balanceOf(address(this))); + } +} diff --git a/src/interfaces/IIFO.sol b/src/interfaces/IIFO.sol index 8b1e7cb..1df0aea 100644 --- a/src/interfaces/IIFO.sol +++ b/src/interfaces/IIFO.sol @@ -2,5 +2,7 @@ pragma solidity 0.8.21; interface IIFO { + event Setup(address controller, address stgn, address rewardToken, uint rate); + function exchange(uint amount) external returns (bool, uint); }