From 4130d256b314587041b1e251a2a1569579758ac8 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 12 May 2022 09:15:33 -0400 Subject: [PATCH] add cooldown() external (#25) * add cooldown() external * simplifying claimRewards call * adding tests on the strategies * fixing lint * adding sweep tests * renaming some elements * fixing some strategies * fixing failing tests Co-authored-by: Pablo Veyrat --- .../AaveFlashloanStrategy.sol | 50 +- .../strategies/BaseStrategyUpgradeable.sol | 13 +- .../genericLender/GenericAaveFraxStaker.sol | 2 +- test/optimizerApr/lenderAave.test.ts | 2 +- .../aaveFlashloanStrategy1.test.ts | 1181 ++++++++++------- .../aaveFlashloanStrategy2.test.ts | 2 +- .../aaveFlashloanStrategyCoverage.test.ts | 114 +- .../aaveFlashloanStrategyHarvestHint.test.ts | 2 +- .../aaveFlashloanStrategy_random.test.ts | 2 +- .../aaveFlashloanStrategy_random_DAI.test.ts | 2 +- test/strategyStETH/strategyStETHlocal.ts | 12 +- test/utils-interaction.ts | 3 +- 12 files changed, 794 insertions(+), 591 deletions(-) diff --git a/contracts/strategies/AaveFlashloanStrategy/AaveFlashloanStrategy.sol b/contracts/strategies/AaveFlashloanStrategy/AaveFlashloanStrategy.sol index 4e2f28c..3f9f791 100644 --- a/contracts/strategies/AaveFlashloanStrategy/AaveFlashloanStrategy.sol +++ b/contracts/strategies/AaveFlashloanStrategy/AaveFlashloanStrategy.sol @@ -108,6 +108,15 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower IAToken private _aToken; IVariableDebtToken private _debtToken; + // ================================== Errors =================================== + + error ErrorSwap(); + error InvalidSender(); + error InvalidSetOfParameters(); + error InvalidWithdrawCheck(); + error TooSmallAmountOut(); + error TooHighParameterValue(); + // ============================ Initializer ==================================== /// @notice Constructor of the `Strategy` @@ -363,7 +372,7 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower } if (boolParams.withdrawCheck) { - require(_amountNeeded == _liquidatedAmount + _loss, "54"); // dev: withdraw safety check + if (_amountNeeded != _liquidatedAmount + _loss) revert InvalidWithdrawCheck(); // dev: withdraw safety check } } @@ -386,14 +395,13 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower ) external onlyRole(GUARDIAN_ROLE) { (uint256 ltv, uint256 liquidationThreshold) = _getProtocolCollatRatios(address(want)); (uint256 daiLtv, ) = _getProtocolCollatRatios(_dai); - require( - _targetCollatRatio < liquidationThreshold && - _maxCollatRatio < liquidationThreshold && - _targetCollatRatio < _maxCollatRatio && - _maxBorrowCollatRatio < ltv && - _daiBorrowCollatRatio < daiLtv, - "8" - ); + if ( + _targetCollatRatio >= liquidationThreshold || + _maxCollatRatio >= liquidationThreshold || + _targetCollatRatio >= _maxCollatRatio || + _maxBorrowCollatRatio >= ltv || + _daiBorrowCollatRatio >= daiLtv + ) revert InvalidSetOfParameters(); targetCollatRatio = _targetCollatRatio; maxCollatRatio = _maxCollatRatio; @@ -407,7 +415,8 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower uint256 _minRatio, uint8 _maxIterations ) external onlyRole(GUARDIAN_ROLE) { - require(_minRatio < maxBorrowCollatRatio && _maxIterations > 0 && _maxIterations < 16, "8"); + if (_minRatio >= maxBorrowCollatRatio || _maxIterations == 0 || _maxIterations >= 16) + revert InvalidSetOfParameters(); minWant = _minWant; minRatio = _minRatio; maxIterations = _maxIterations; @@ -420,7 +429,7 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower /// @notice Sets the discount factor for the StkAAVE price function setDiscountFactor(uint256 _discountFactor) external onlyRole(GUARDIAN_ROLE) { - require(_discountFactor < 10000, "4"); + if (_discountFactor > 10000) revert TooHighParameterValue(); discountFactor = _discountFactor; } @@ -471,7 +480,7 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower if (!success) _revertBytes(result); uint256 amountOut = abi.decode(result, (uint256)); - require(amountOut >= minAmountOut, "15"); + if (amountOut < minAmountOut) revert TooSmallAmountOut(); } /// @notice Flashload callback, as defined by EIP-3156 @@ -484,7 +493,7 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower uint256, bytes calldata data ) external override returns (bytes32) { - require(msg.sender == FlashMintLib.LENDER && initiator == address(this), "1"); + if (msg.sender != FlashMintLib.LENDER || initiator != address(this)) revert InvalidSender(); (bool deficit, uint256 amountWant) = abi.decode(data, (bool, uint256)); return FlashMintLib.loanLogic(deficit, amountWant, amount, address(want)); @@ -496,13 +505,8 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower /// @dev stkAAVE require a "cooldown" period of 10 days before being claimed function _claimRewards() internal returns (uint256 stkAaveBalance) { stkAaveBalance = _balanceOfStkAave(); - uint256 cooldownStatus; - if (stkAaveBalance > 0) { - cooldownStatus = _checkCooldown(); // don't check status if we have no stkAave - } - // If it's the claim period claim - if (stkAaveBalance > 0 && cooldownStatus == 1) { + if (stkAaveBalance > 0 && _checkCooldown() == 1) { // redeem AAVE from stkAave _stkAave.claimRewards(address(this), type(uint256).max); _stkAave.redeem(address(this), stkAaveBalance); @@ -514,7 +518,7 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower stkAaveBalance = _balanceOfStkAave(); // request start of cooldown period, if there's no cooldown in progress - if (boolParams.cooldownStkAave && stkAaveBalance > 0 && cooldownStatus == 0) { + if (boolParams.cooldownStkAave && stkAaveBalance > 0 && _checkCooldown() == 0) { _stkAave.cooldown(); } } @@ -523,6 +527,10 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower _claimRewards(); } + function cooldown() external onlyRole(KEEPER_ROLE) { + _stkAave.cooldown(); + } + /// @notice Reduce exposure by withdrawing funds and repaying debt /// @param amountToFree Amount of `want` to withdraw/repay /// @return balance Current balance of `want` @@ -994,6 +1002,6 @@ contract AaveFlashloanStrategy is BaseStrategyUpgradeable, IERC3156FlashBorrower revert(add(32, errMsg), mload(errMsg)) } } - revert("117"); + revert ErrorSwap(); } } diff --git a/contracts/strategies/BaseStrategyUpgradeable.sol b/contracts/strategies/BaseStrategyUpgradeable.sol index 25d12b8..3d5432b 100644 --- a/contracts/strategies/BaseStrategyUpgradeable.sol +++ b/contracts/strategies/BaseStrategyUpgradeable.sol @@ -44,6 +44,11 @@ abstract contract BaseStrategyUpgradeable is BaseStrategyEvents, AccessControlUp /// @notice See note on `setEmergencyExit()` bool public emergencyExit; + // ============================ Errors ========================================= + + error InvalidToken(); + error ZeroAddress(); + // ============================ Constructor ==================================== /// @custom:oz-upgrades-unsafe-allow constructor @@ -62,7 +67,7 @@ abstract contract BaseStrategyUpgradeable is BaseStrategyEvents, AccessControlUp poolManager = IPoolManager(_poolManager); want = IERC20(poolManager.token()); wantBase = 10**(IERC20Metadata(address(want)).decimals()); - require(guardian != address(0) && governor != address(0) && governor != guardian, "0"); + if (guardian == address(0) || governor == address(0) || governor == guardian) revert ZeroAddress(); // AccessControl // Governor is guardian so no need for a governor role _setupRole(GUARDIAN_ROLE, guardian); @@ -73,7 +78,7 @@ abstract contract BaseStrategyUpgradeable is BaseStrategyEvents, AccessControlUp // Initializing roles first for (uint256 i = 0; i < keepers.length; i++) { - require(keepers[i] != address(0), "0"); + if (keepers[i] == address(0)) revert ZeroAddress(); _setupRole(KEEPER_ROLE, keepers[i]); } _setRoleAdmin(KEEPER_ROLE, GUARDIAN_ROLE); @@ -304,13 +309,13 @@ abstract contract BaseStrategyUpgradeable is BaseStrategyEvents, AccessControlUp /// Implement `_protectedTokens()` to specify any additional tokens that /// should be protected from sweeping in addition to `want`. function sweep(address _token, address to) external onlyRole(GUARDIAN_ROLE) { - require(_token != address(want), "93"); + if (_token == address(want)) revert InvalidToken(); address[] memory __protectedTokens = _protectedTokens(); for (uint256 i = 0; i < __protectedTokens.length; i++) // In the strategy we use so far, the only protectedToken is the want token // and this has been checked above - require(_token != __protectedTokens[i], "93"); + if (_token == __protectedTokens[i]) revert InvalidToken(); IERC20(_token).safeTransfer(to, IERC20(_token).balanceOf(address(this))); } diff --git a/contracts/strategies/OptimizerAPR/genericLender/GenericAaveFraxStaker.sol b/contracts/strategies/OptimizerAPR/genericLender/GenericAaveFraxStaker.sol index fe9aba5..65cf7cf 100644 --- a/contracts/strategies/OptimizerAPR/genericLender/GenericAaveFraxStaker.sol +++ b/contracts/strategies/OptimizerAPR/genericLender/GenericAaveFraxStaker.sol @@ -36,7 +36,7 @@ contract GenericAaveFraxStaker is GenericAaveUpgradeable { // ================================ Parameters ================================= /// @notice Minimum amount of aFRAX to stake - uint256 private constant minStakingAmount = 1000 * 1e18; // 100 aFrax + uint256 private constant minStakingAmount = 1000 * 1e18; // 1000 aFrax /// @notice Staking duration uint256 public stakingPeriod; diff --git a/test/optimizerApr/lenderAave.test.ts b/test/optimizerApr/lenderAave.test.ts index 67b058c..a0b5329 100644 --- a/test/optimizerApr/lenderAave.test.ts +++ b/test/optimizerApr/lenderAave.test.ts @@ -15,7 +15,7 @@ import { } from '../../typechain'; import { gwei } from '../../utils/bignumber'; import { deploy, deployUpgradeable, latestTime, impersonate } from '../test-utils'; -import hre, { ethers, network } from 'hardhat'; +import { ethers, network } from 'hardhat'; import { expect } from '../test-utils/chai-setup'; import { BASE_TOKENS } from '../utils'; import { parseUnits, parseEther } from 'ethers/lib/utils'; diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategy1.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategy1.test.ts index ab6d881..fd7db30 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategy1.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategy1.test.ts @@ -1,11 +1,9 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; -import { ethers, network } from 'hardhat'; -import { utils, constants, BigNumber, Contract } from 'ethers'; +import hre, { ethers, network } from 'hardhat'; +import { utils, constants, BigNumber, Contract, Signer } from 'ethers'; import { expect } from '../test-utils/chai-setup'; import { deploy, impersonate } from '../test-utils'; import { expectApprox } from '../../utils/bignumber'; -import axios from 'axios'; -import qs from 'qs'; import { AaveFlashloanStrategy, FlashMintLib, @@ -22,9 +20,10 @@ import { IProtocolDataProvider__factory, ILendingPool__factory, } from '../../typechain'; -import { parseUnits } from 'ethers/lib/utils'; +import { parseUnits, parseEther } from 'ethers/lib/utils'; +import { latestTime, increaseTime } from '../test-utils/helpers'; -describe('AaveFlashloan Strategy1', () => { +describe('AaveFlashloanStrategy - Main test file', () => { // ATokens let aToken: ERC20, debtToken: ERC20; @@ -44,8 +43,10 @@ describe('AaveFlashloan Strategy1', () => { // let incentivesController: IAaveIncentivesController; let lendingPool: ILendingPool; let flashMintLib: FlashMintLib; + let stkAaveHolder: string; let strategy: AaveFlashloanStrategy; + const impersonatedSigners: { [key: string]: Signer } = {}; // ReserveInterestRateStrategy for USDC const reserveInterestRateStrategyUSDC = '0x8Cae0596bC1eD42dc3F04c4506cfe442b3E74e27'; @@ -68,6 +69,7 @@ describe('AaveFlashloan Strategy1', () => { wantToken = (await ethers.getContractAt(ERC20__factory.abi, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48')) as ERC20; dai = (await ethers.getContractAt(ERC20__factory.abi, '0x6B175474E89094C44Da98b954EedeAC495271d0F')) as ERC20; aave = (await ethers.getContractAt(ERC20__factory.abi, '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9')) as ERC20; + stkAaveHolder = '0x32B61Bb22Cbe4834bc3e73DcE85280037D944a4D'; stkAave = (await ethers.getContractAt( IStakedAave__factory.abi, '0x4da27a545c0c5B758a6BA100e3a049001de870f5', @@ -120,7 +122,7 @@ describe('AaveFlashloan Strategy1', () => { }); describe('Constructor', () => { - it('initialize', async () => { + it('success - contract well initialized', async () => { expect( strategy.initialize(poolManager.address, reserveInterestRateStrategyUSDC, governor.address, guardian.address, [ keeper.address, @@ -130,61 +132,24 @@ describe('AaveFlashloan Strategy1', () => { expect(strategy.connect(proxyAdmin).boolParams()).to.revertedWith( 'TransparentUpgradeableProxy: admin cannot fallback to proxy target', ); + // Parameters const isActive1 = (await strategy.connect(deployer).boolParams()).isFlashMintActive; const isActive2 = (await strategy.connect(user).boolParams()).isFlashMintActive; - await expect(isActive1).to.be.true; + expect(isActive1).to.be.equal(true); expect(isActive1).to.equal(isActive2); - }); - - it('approvals1', async () => { - const token = await poolManager.token(); - const want = await strategy.want(); - expect(want).to.equal(token); - const wantContract = (await ethers.getContractAt(ERC20__factory.abi, want)) as ERC20; - const allowance1 = await wantContract.allowance(strategy.address, lendingPool.address); - expect(allowance1).to.equal(constants.MaxUint256); - - const allowance2 = await aToken.allowance(strategy.address, lendingPool.address); - expect(allowance2).to.equal(constants.MaxUint256); - - // PoolManager - expect(await wantContract.allowance(strategy.address, poolManager.address)).to.equal(constants.MaxUint256); - }); - it('approvals2', async () => { - const allowanceDai1 = await dai.allowance(strategy.address, lendingPool.address); - const allowanceDai2 = await dai.allowance(strategy.address, await flashMintLib.LENDER()); - expect(allowanceDai1).to.equal(constants.MaxUint256); - expect(allowanceDai2).to.equal(constants.MaxUint256); - const allowanceAave = await aave.allowance(strategy.address, '0x1111111254fb6c44bAC0beD2854e76F90643097d'); - expect(allowanceAave).to.equal(constants.MaxUint256); - - const allowanceStkAave = await stkAave.allowance(strategy.address, '0x1111111254fb6c44bAC0beD2854e76F90643097d'); - expect(allowanceStkAave).to.equal(constants.MaxUint256); - }); - - it('roles', async () => { - const GUARDIAN_ROLE = await strategy.GUARDIAN_ROLE(); - const POOLMANAGER_ROLE = await strategy.POOLMANAGER_ROLE(); - await expect(await strategy.hasRole(GUARDIAN_ROLE, guardian.address)).to.be.true; - await expect(await strategy.hasRole(GUARDIAN_ROLE, governor.address)).to.be.true; - await expect(await strategy.hasRole(GUARDIAN_ROLE, strategy.address)).to.be.false; - await expect(await strategy.hasRole(GUARDIAN_ROLE, poolManager.address)).to.be.false; - await expect(await strategy.hasRole(POOLMANAGER_ROLE, poolManager.address)).to.be.true; - }); - - it('params', async () => { expect(await strategy.maxIterations()).to.equal(6); - await expect((await strategy.boolParams()).isFlashMintActive).to.be.true; + expect((await strategy.boolParams()).isFlashMintActive).to.be.equal(true); expect(await strategy.discountFactor()).to.equal(9000); expect(await strategy.minWant()).to.equal(100); expect(await strategy.minRatio()).to.equal(utils.parseEther('0.005')); - await expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.true; - await expect((await strategy.boolParams()).withdrawCheck).to.be.false; - await expect((await strategy.boolParams()).cooldownStkAave).to.be.true; - }); + expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.equal(true); + expect((await strategy.boolParams()).withdrawCheck).to.be.equal(false); + expect((await strategy.boolParams()).cooldownStkAave).to.be.equal(true); + expect(await strategy.cooldownSeconds()).to.be.equal(await stkAave.COOLDOWN_SECONDS()); + expect(await strategy.unstakeWindow()).to.be.equal(await stkAave.UNSTAKE_WINDOW()); - it('collat ratios', async () => { + // Collateral Ratios const { ltv, liquidationThreshold } = await protocolDataProvider.getReserveConfigurationData(wantToken.address); const _DEFAULT_COLLAT_TARGET_MARGIN = utils.parseUnits('0.02', 4); const _DEFAULT_COLLAT_MAX_MARGIN = utils.parseUnits('0.005', 4); @@ -194,12 +159,62 @@ describe('AaveFlashloan Strategy1', () => { liquidationThreshold.sub(_DEFAULT_COLLAT_TARGET_MARGIN).mul(1e14), ); expect(await strategy.maxCollatRatio()).to.equal(liquidationThreshold.sub(_DEFAULT_COLLAT_MAX_MARGIN).mul(1e14)); + + // Base strategy parameters + expect(await strategy.want()).to.be.equal(wantToken.address); + expect(await strategy.poolManager()).to.be.equal(poolManager.address); + expect(await strategy.wantBase()).to.be.equal(parseUnits('1', 6)); + expect(await strategy.debtThreshold()).to.be.equal(parseUnits('100', 18)); + expect(await strategy.emergencyExit()).to.be.equal(false); + expect(await strategy.isActive()).to.be.equal(false); + expect(await strategy.estimatedTotalAssets()).to.be.equal(0); + }); + + it('success - approvals correctly granted', async () => { + const token = await poolManager.token(); + const want = await strategy.want(); + expect(want).to.equal(token); + const wantContract = (await ethers.getContractAt(ERC20__factory.abi, want)) as ERC20; + expect(await wantContract.allowance(strategy.address, lendingPool.address)).to.equal(constants.MaxUint256); + expect(await aToken.allowance(strategy.address, lendingPool.address)).to.equal(constants.MaxUint256); + + // PoolManager + expect(await wantContract.allowance(strategy.address, poolManager.address)).to.equal(constants.MaxUint256); + expect(await dai.allowance(strategy.address, lendingPool.address)).to.equal(constants.MaxUint256); + expect(await dai.allowance(strategy.address, await flashMintLib.LENDER())).to.equal(constants.MaxUint256); + + expect(await aave.allowance(strategy.address, '0x1111111254fb6c44bAC0beD2854e76F90643097d')).to.equal( + constants.MaxUint256, + ); + expect(await stkAave.allowance(strategy.address, '0x1111111254fb6c44bAC0beD2854e76F90643097d')).to.equal( + constants.MaxUint256, + ); }); }); - describe('setters', () => { - it('setCollateralTargets', async () => { - expect( + describe('Access Control', () => { + it('success - roles well initialized', async () => { + // Roles + const GUARDIAN_ROLE = await strategy.GUARDIAN_ROLE(); + const POOLMANAGER_ROLE = await strategy.POOLMANAGER_ROLE(); + const KEEPER_ROLE = await strategy.KEEPER_ROLE(); + expect(await strategy.hasRole(GUARDIAN_ROLE, guardian.address)).to.be.equal(true); + expect(await strategy.hasRole(GUARDIAN_ROLE, governor.address)).to.be.equal(true); + expect(await strategy.hasRole(GUARDIAN_ROLE, strategy.address)).to.be.equal(false); + expect(await strategy.hasRole(GUARDIAN_ROLE, poolManager.address)).to.be.equal(false); + expect(await strategy.hasRole(POOLMANAGER_ROLE, poolManager.address)).to.be.equal(true); + expect(await strategy.hasRole(KEEPER_ROLE, keeper.address)).to.be.equal(true); + expect(await strategy.getRoleAdmin(KEEPER_ROLE)).to.be.equal(GUARDIAN_ROLE); + expect(await strategy.getRoleAdmin(GUARDIAN_ROLE)).to.be.equal(POOLMANAGER_ROLE); + expect(await strategy.getRoleAdmin(POOLMANAGER_ROLE)).to.be.equal(POOLMANAGER_ROLE); + }); + it('success - restricted functions revert with correct error messages', async () => { + const revertMessageGuardian = `AccessControl: account ${user.address.toLowerCase()} is missing role ${await strategy.GUARDIAN_ROLE()}`; + const revertMessageKeeper = `AccessControl: account ${user.address.toLowerCase()} is missing role ${await strategy.KEEPER_ROLE()}`; + const revertMessagePoolManager = `AccessControl: account ${user.address.toLowerCase()} is missing role ${await strategy.POOLMANAGER_ROLE()}`; + + // Guardian + await expect( strategy .connect(user) .setCollateralTargets( @@ -208,130 +223,215 @@ describe('AaveFlashloan Strategy1', () => { utils.parseUnits('0.6', 18), utils.parseUnits('0.8', 18), ), - ).to.be.revertedWith(`AccessControl: account ${user.address.toLowerCase()} is missing role`); - + ).to.be.revertedWith(revertMessageGuardian); await expect( - strategy + strategy.connect(user).setBoolParams({ + isFlashMintActive: false, + automaticallyComputeCollatRatio: false, + withdrawCheck: false, + cooldownStkAave: false, + }), + ).to.be.revertedWith(revertMessageGuardian); + await expect(strategy.connect(user).setMinsAndMaxs(1000, utils.parseUnits('0.7', 18), 20)).to.be.revertedWith( + revertMessageGuardian, + ); + await expect(strategy.connect(user).setDiscountFactor(12000)).to.revertedWith(revertMessageGuardian); + await expect(strategy.connect(user).setDebtThreshold(0)).to.be.revertedWith(revertMessageGuardian); + await expect(strategy.connect(user).sweep(user.address, user.address)).to.be.revertedWith(revertMessageGuardian); + + // PoolManager + await expect(strategy.connect(user).addGuardian(user.address)).to.be.revertedWith(revertMessagePoolManager); + await expect(strategy.connect(user).revokeGuardian(user.address)).to.be.revertedWith(revertMessagePoolManager); + await expect(strategy.connect(user).withdraw(0)).to.be.revertedWith(revertMessagePoolManager); + await expect(strategy.connect(user).setEmergencyExit()).to.be.revertedWith(revertMessagePoolManager); + + // Keeper + await expect(strategy.connect(user)['harvest(uint256)'](0, { gasLimit: 3e6 })).to.be.revertedWith( + revertMessageKeeper, + ); + await expect(strategy.connect(user).claimRewards()).to.be.revertedWith(revertMessageKeeper); + await expect(strategy.connect(user).cooldown()).to.be.revertedWith(revertMessageKeeper); + await expect(strategy.connect(user).sellRewards(0, '0x')).to.be.revertedWith(revertMessageKeeper); + }); + }); + describe('Setters', () => { + describe('setCollateralTargets', () => { + it('reverts - invalid parameters', async () => { + await expect( + strategy + .connect(guardian) + .setCollateralTargets( + utils.parseUnits('0.75', 18), + utils.parseUnits('0.8', 18), + utils.parseUnits('0.6', 18), + utils.parseUnits('0.8', 18), + ), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect( + strategy + .connect(guardian) + .setCollateralTargets( + utils.parseUnits('1', 18), + utils.parseUnits('0.8', 18), + utils.parseUnits('0.6', 18), + utils.parseUnits('0.7', 18), + ), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect( + strategy + .connect(guardian) + .setCollateralTargets( + utils.parseUnits('0.81', 18), + utils.parseUnits('0.79', 18), + utils.parseUnits('0.6', 18), + utils.parseUnits('0.7', 18), + ), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect( + strategy + .connect(guardian) + .setCollateralTargets( + utils.parseUnits('0.75', 18), + utils.parseUnits('0.8', 18), + utils.parseUnits('0.9', 18), + utils.parseUnits('0.7', 18), + ), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect( + strategy + .connect(guardian) + .setCollateralTargets( + utils.parseUnits('0.75', 18), + utils.parseUnits('0.8', 18), + utils.parseUnits('0.6', 18), + utils.parseUnits('0.9', 18), + ), + ).to.be.revertedWith('InvalidSetOfParameters'); + }); + it('success - parameters correctly set', async () => { + await strategy .connect(guardian) .setCollateralTargets( utils.parseUnits('0.75', 18), utils.parseUnits('0.8', 18), utils.parseUnits('0.6', 18), - utils.parseUnits('0.8', 18), - ), - ).to.be.reverted; - - await strategy - .connect(guardian) - .setCollateralTargets( - utils.parseUnits('0.75', 18), - utils.parseUnits('0.8', 18), - utils.parseUnits('0.6', 18), - utils.parseUnits('0.7', 18), - ); + utils.parseUnits('0.7', 18), + ); - expect(await strategy.targetCollatRatio()).to.equal(utils.parseUnits('0.75', 18)); - expect(await strategy.maxCollatRatio()).to.equal(utils.parseUnits('0.8', 18)); - expect(await strategy.maxBorrowCollatRatio()).to.equal(utils.parseUnits('0.6', 18)); - expect(await strategy.daiBorrowCollatRatio()).to.equal(utils.parseUnits('0.7', 18)); + expect(await strategy.targetCollatRatio()).to.equal(utils.parseUnits('0.75', 18)); + expect(await strategy.maxCollatRatio()).to.equal(utils.parseUnits('0.8', 18)); + expect(await strategy.maxBorrowCollatRatio()).to.equal(utils.parseUnits('0.6', 18)); + expect(await strategy.daiBorrowCollatRatio()).to.equal(utils.parseUnits('0.7', 18)); + }); }); - - it('setBoolParams', async () => { - await expect((await strategy.boolParams()).isFlashMintActive).to.be.true; - await expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.true; - await expect((await strategy.boolParams()).withdrawCheck).to.be.false; - await expect((await strategy.boolParams()).cooldownStkAave).to.be.true; - - expect( - strategy.connect(user).setBoolParams({ + describe('setBoolParams', () => { + it('success', async () => { + expect((await strategy.boolParams()).isFlashMintActive).to.be.equal(true); + expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.equal(true); + expect((await strategy.boolParams()).withdrawCheck).to.be.equal(false); + expect((await strategy.boolParams()).cooldownStkAave).to.be.equal(true); + + await strategy.connect(guardian).setBoolParams({ isFlashMintActive: false, automaticallyComputeCollatRatio: false, withdrawCheck: false, cooldownStkAave: false, - }), - ).to.be.revertedWith(`AccessControl: account ${user.address.toLowerCase()} is missing role`); - - await strategy.connect(guardian).setBoolParams({ - isFlashMintActive: false, - automaticallyComputeCollatRatio: false, - withdrawCheck: false, - cooldownStkAave: false, - }); + }); - await expect((await strategy.boolParams()).isFlashMintActive).to.be.false; - await expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.false; - await expect((await strategy.boolParams()).withdrawCheck).to.be.false; - await expect((await strategy.boolParams()).cooldownStkAave).to.be.false; - }); + expect((await strategy.boolParams()).isFlashMintActive).to.be.equal(false); + expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.equal(false); + expect((await strategy.boolParams()).withdrawCheck).to.be.equal(false); + expect((await strategy.boolParams()).cooldownStkAave).to.be.equal(false); + await strategy.connect(guardian).setBoolParams({ + isFlashMintActive: false, + automaticallyComputeCollatRatio: true, + withdrawCheck: false, + cooldownStkAave: true, + }); - it('setMinsAndMaxs - revert', async () => { - await expect(strategy.connect(user).setMinsAndMaxs(1000, utils.parseUnits('0.7', 18), 20)).to.be.revertedWith( - '8', - ); + expect((await strategy.boolParams()).isFlashMintActive).to.be.equal(false); + expect((await strategy.boolParams()).automaticallyComputeCollatRatio).to.be.equal(true); + expect((await strategy.boolParams()).withdrawCheck).to.be.equal(false); + expect((await strategy.boolParams()).cooldownStkAave).to.be.equal(true); + }); }); + describe('setMinsAndMaxs', () => { + it('reverts - invalid parameters', async () => { + await expect( + strategy.connect(guardian).setMinsAndMaxs(1000, utils.parseUnits('0.7', 18), 20), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect( + strategy.connect(guardian).setMinsAndMaxs(1000, utils.parseUnits('0.7', 18), 0), + ).to.be.revertedWith('InvalidSetOfParameters'); + await expect(strategy.connect(guardian).setMinsAndMaxs(1000, utils.parseUnits('10', 18), 5)).to.be.revertedWith( + 'InvalidSetOfParameters', + ); + }); + it('success - parameters updated', async () => { + expect(await strategy.minWant()).to.equal(100); + expect(await strategy.minRatio()).to.equal(utils.parseUnits('0.005', 18)); + expect(await strategy.maxIterations()).to.equal(6); - it('setMinsAndMaxs', async () => { - expect(strategy.connect(user).setMinsAndMaxs(1000, utils.parseUnits('0.7', 18), 20)).to.be.revertedWith( - `AccessControl: account ${user.address.toLowerCase()} is missing role`, - ); - - expect(await strategy.minWant()).to.equal(100); - expect(await strategy.minRatio()).to.equal(utils.parseUnits('0.005', 18)); - expect(await strategy.maxIterations()).to.equal(6); - - await strategy.connect(guardian).setMinsAndMaxs(1000, utils.parseUnits('0.6', 18), 15); + await strategy.connect(guardian).setMinsAndMaxs(1000, utils.parseUnits('0.6', 18), 15); - expect(await strategy.minWant()).to.equal(1000); - expect(await strategy.minRatio()).to.equal(utils.parseUnits('0.6', 18)); - expect(await strategy.maxIterations()).to.equal(15); + expect(await strategy.minWant()).to.equal(1000); + expect(await strategy.minRatio()).to.equal(utils.parseUnits('0.6', 18)); + expect(await strategy.maxIterations()).to.equal(15); + }); }); - - it('setAavePoolVariables', async () => { - await strategy.setAavePoolVariables(); + describe('setAavePoolVariables', () => { + it('success - variables correctly set', async () => { + await strategy.setAavePoolVariables(); + expect(await strategy.cooldownSeconds()).to.be.equal(await stkAave.COOLDOWN_SECONDS()); + expect(await strategy.unstakeWindow()).to.be.equal(await stkAave.UNSTAKE_WINDOW()); + }); }); + describe('setDiscountFactor', () => { + it('reverts - too high parameter value', async () => { + await expect(strategy.connect(guardian).setDiscountFactor(12000)).to.revertedWith('TooHighParameterValue'); + }); + it('success - parameter updated', async () => { + expect(await strategy.discountFactor()).to.equal(9000); - it('setDiscountFactor', async () => { - expect(await strategy.discountFactor()).to.equal(9000); - expect(strategy.setDiscountFactor(12000)).to.revertedWith( - `AccessControl: account ${deployer.address.toLowerCase()} is missing role ${await strategy.GUARDIAN_ROLE()}`, - ); - expect(strategy.connect(guardian).setDiscountFactor(12000)).to.revertedWith('4'); - await strategy.connect(guardian).setDiscountFactor(2000); - expect(await strategy.discountFactor()).to.equal(2000); + await strategy.connect(guardian).setDiscountFactor(2000); + expect(await strategy.discountFactor()).to.equal(2000); + await strategy.connect(guardian).setDiscountFactor(10000); + expect(await strategy.discountFactor()).to.equal(10000); + }); }); - - it('addGuardian', async () => { - expect(strategy.addGuardian(user.address)).to.be.revertedWith( - `AccessControl: account ${deployer.address.toLowerCase()} is missing role ${await strategy.POOLMANAGER_ROLE()}`, - ); - await expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.false; - await impersonate(poolManager.address, async acc => { - await network.provider.send('hardhat_setBalance', [ - poolManager.address, - utils.parseEther('1').toHexString().replace('0x0', '0x'), - ]); - await strategy.connect(acc).addGuardian(user.address); + describe('addGuardian', () => { + it('success - guardian added', async () => { + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.equal(false); + await impersonate(poolManager.address, async acc => { + await network.provider.send('hardhat_setBalance', [ + poolManager.address, + utils.parseEther('1').toHexString().replace('0x0', '0x'), + ]); + await strategy.connect(acc).addGuardian(user.address); + }); + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.equal(true); }); - await expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.true; }); - - it('revokeGuardian', async () => { - expect(strategy.revokeGuardian(user.address)).to.be.revertedWith( - `AccessControl: account ${deployer.address.toLowerCase()} is missing role ${await strategy.POOLMANAGER_ROLE()}`, - ); - await expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), guardian.address)).to.be.true; - await impersonate(poolManager.address, async acc => { - await network.provider.send('hardhat_setBalance', [ - poolManager.address, - utils.parseEther('1').toHexString().replace('0x0', '0x'), - ]); - await strategy.connect(acc).revokeGuardian(guardian.address); + describe('revokeGuardian', () => { + it('success - guardian revoke', async () => { + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), guardian.address)).to.be.equal(true); + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.equal(false); + await impersonate(poolManager.address, async acc => { + await network.provider.send('hardhat_setBalance', [ + poolManager.address, + utils.parseEther('1').toHexString().replace('0x0', '0x'), + ]); + await strategy.connect(acc).addGuardian(user.address); + await strategy.connect(acc).revokeGuardian(guardian.address); + await strategy.connect(acc).revokeGuardian(user.address); + }); + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), guardian.address)).to.be.equal(false); + expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), user.address)).to.be.equal(false); }); - await expect(await strategy.hasRole(await strategy.GUARDIAN_ROLE(), guardian.address)).to.be.false; }); }); - describe('Strategy', () => { + describe('Strategy Actions', () => { const _startAmountUSDC = utils.parseUnits((2_000_000).toString(), 6); let _guessedBorrowed = utils.parseUnits((0).toString(), 6); @@ -341,386 +441,533 @@ describe('AaveFlashloan Strategy1', () => { await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { await wantToken.connect(acc).transfer(user.address, _startAmountUSDC); }); - // console.log('balance', utils.formatUnits(await wantToken.balanceOf(user.address), 6)); await wantToken.connect(user).transfer(poolManager.address, _startAmountUSDC); - // await wantToken.connect(user).transfer(strategy.address, _startAmountUSDC); }); - it('estimatedTotalAssets', async () => { - expect(await strategy.estimatedTotalAssets()).to.equal(0); - await strategy['harvest()']({ gasLimit: 3e6 }); + describe('estimatedTotalAssets', () => { + it('success - assets correctly estimated', async () => { + expect(await strategy.estimatedTotalAssets()).to.equal(0); + await strategy['harvest()']({ gasLimit: 3e6 }); - const { deposits, borrows } = await strategy.getCurrentPosition(); - _guessedBorrowed = borrows; - const totalAssets = (await wantToken.balanceOf(strategy.address)).add(deposits).sub(borrows); - const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; + const { deposits, borrows } = await strategy.getCurrentPosition(); + _guessedBorrowed = borrows; + const totalAssets = (await wantToken.balanceOf(strategy.address)).add(deposits).sub(borrows); + const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; - expect(debtRatio).to.equal(utils.parseUnits('0.75', 9)); - expect(totalAssets).to.be.closeTo(_startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), 10); - expect(await strategy.estimatedTotalAssets()).to.equal(totalAssets); - }); + expect(debtRatio).to.equal(utils.parseUnits('0.75', 9)); + expect(totalAssets).to.be.closeTo(_startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), 10); + expect(await strategy.estimatedTotalAssets()).to.equal(totalAssets); + }); - it('estimatedTotalAssets - check harvest with guessedBorrows', async () => { - expect(await strategy.estimatedTotalAssets()).to.equal(0); - await strategy.connect(keeper)['harvest(uint256)'](_guessedBorrowed, { gasLimit: 3e6 }); + it('success - check harvest with guessedBorrows', async () => { + expect(await strategy.estimatedTotalAssets()).to.equal(0); + await strategy.connect(keeper)['harvest(uint256)'](_guessedBorrowed, { gasLimit: 3e6 }); - const { deposits, borrows } = await strategy.getCurrentPosition(); - expectApprox(borrows, _guessedBorrowed, 0.1); - const totalAssets = (await wantToken.balanceOf(strategy.address)).add(deposits).sub(borrows); - const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; + const { deposits, borrows } = await strategy.getCurrentPosition(); + expectApprox(borrows, _guessedBorrowed, 0.1); + const totalAssets = (await wantToken.balanceOf(strategy.address)).add(deposits).sub(borrows); + const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; - expect(debtRatio).to.equal(utils.parseUnits('0.75', 9)); - expect(totalAssets).to.be.closeTo(_startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), 10); - expect(await strategy.estimatedTotalAssets()).to.equal(totalAssets); - }); + expect(debtRatio).to.equal(utils.parseUnits('0.75', 9)); + expect(totalAssets).to.be.closeTo(_startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), 10); + expect(await strategy.estimatedTotalAssets()).to.equal(totalAssets); + }); - it('estimatedTotalAssets - balanceExcludingRewards < minWant', async () => { - await impersonate(strategy.address, async acc => { - await network.provider.send('hardhat_setBalance', [ - strategy.address, - utils.parseEther('1').toHexString().replace('0x0', '0x'), - ]); + it('success - balanceExcludingRewards < minWant', async () => { + await impersonate(strategy.address, async acc => { + await network.provider.send('hardhat_setBalance', [ + strategy.address, + utils.parseEther('1').toHexString().replace('0x0', '0x'), + ]); - const balance = await wantToken.balanceOf(acc.address); - await wantToken.connect(acc).transfer(user.address, balance); + const balance = await wantToken.balanceOf(acc.address); + await wantToken.connect(acc).transfer(user.address, balance); + }); + + expect(await strategy.estimatedTotalAssets()).to.equal(0); }); + }); - expect(await strategy.estimatedTotalAssets()).to.equal(0); + describe('cooldown', () => { + it('reverts - when no stkAave balance', async () => { + await expect(strategy.connect(keeper).cooldown()).to.be.revertedWith('INVALID_BALANCE_ON_COOLDOWN'); + }); + it('success - cooldown activated', async () => { + const amountStorage = utils.parseEther('1').toHexString().replace('0x0', '0x'); + await impersonate(stkAaveHolder, async acc => { + await network.provider.send('hardhat_setBalance', [stkAaveHolder, amountStorage]); + await (await stkAave.connect(acc).transfer(strategy.address, parseEther('1'))).wait(); + }); + await strategy.connect(keeper).cooldown(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + }); + }); + describe('claimRewards', () => { + it('success - when nothing to claim', async () => { + const stkAaveBalance = await stkAave.balanceOf(strategy.address); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(stkAaveBalance); + }); + it('success - when stkAave balance is not null and check cooldown has not been created', async () => { + const impersonatedAddresses = [stkAaveHolder]; + + for (const address of impersonatedAddresses) { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [address], + }); + await hre.network.provider.send('hardhat_setBalance', [address, '0x10000000000000000000000000000']); + impersonatedSigners[address] = await ethers.getSigner(address); + } + await stkAave.connect(impersonatedSigners[stkAaveHolder]).transfer(strategy.address, parseEther('1')); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + // stkAave balance remains unchanged but cooldown must be triggered + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(parseEther('1')); + }); + it('success - when stkAave balance is not null check cooldown has been created but we are in the meantime', async () => { + const impersonatedAddresses = [stkAaveHolder]; + + for (const address of impersonatedAddresses) { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [address], + }); + await hre.network.provider.send('hardhat_setBalance', [address, '0x10000000000000000000000000000']); + impersonatedSigners[address] = await ethers.getSigner(address); + } + await stkAave.connect(impersonatedSigners[stkAaveHolder]).transfer(strategy.address, parseEther('1')); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + await strategy.connect(keeper).claimRewards(); + // stkAave balance remains unchanged but cooldown must be triggered + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(parseEther('1')); + }); + it('success - cooldown status is 1', async () => { + const impersonatedAddresses = [stkAaveHolder]; + + for (const address of impersonatedAddresses) { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [address], + }); + await hre.network.provider.send('hardhat_setBalance', [address, '0x10000000000000000000000000000']); + impersonatedSigners[address] = await ethers.getSigner(address); + } + await stkAave.connect(impersonatedSigners[stkAaveHolder]).transfer(strategy.address, parseEther('1')); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + await increaseTime(24 * 10 * 3600 + 10); + await strategy.connect(keeper).claimRewards(); + // Rewards have been claimed and redeemed + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(parseEther('0')); + // Rewards have been gained: it's 0.001 in 10 days so: we get + expectApprox(await aave.balanceOf(strategy.address), parseEther('1.00191'), 0.1); + }); + it('success - cooldown status should be 1 but unstake window was overriden', async () => { + const impersonatedAddresses = [stkAaveHolder]; + + for (const address of impersonatedAddresses) { + await hre.network.provider.request({ + method: 'hardhat_impersonateAccount', + params: [address], + }); + await hre.network.provider.send('hardhat_setBalance', [address, '0x10000000000000000000000000000']); + impersonatedSigners[address] = await ethers.getSigner(address); + } + await stkAave.connect(impersonatedSigners[stkAaveHolder]).transfer(strategy.address, parseEther('1')); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + await strategy.connect(keeper).claimRewards(); + await increaseTime(24 * 30 * 3600 + 10); + await strategy.connect(keeper).claimRewards(); + // Rewards have not been claimed because we went over the unstake window + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(parseEther('1')); + // Cooldown reset + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + // Rewards have been gained: it's 0.001 in 10 days so: we get + }); + it('success - when rewards to claim because real money invested', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); + await increaseTime(24 * 365 * 3600); + // This operation should just claim and trigger the cooldown + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + // Gained Approx 86 stkAave in the meantime + expectApprox(await stkAave.balanceOf(strategy.address), parseEther('86.682886399'), 0.1); + }); + it('success - when rewards to claim because real money invested and then changed to Aave', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); + await increaseTime(24 * 365 * 3600); + // This operation should just claim and trigger the cooldown + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.stakersCooldowns(strategy.address)).to.be.equal(await latestTime()); + // Nothing much should happen here + await strategy.connect(keeper).claimRewards(); + // 1 stkAave is 100 USDC approx and yield on stkAave is 0.1% + expectApprox(await stkAave.balanceOf(strategy.address), parseEther('86.682886399'), 0.1); + await increaseTime(24 * 10 * 3600 + 10); + await strategy.connect(keeper).claimRewards(); + expect(await stkAave.balanceOf(strategy.address)).to.be.equal(0); + // Made some gains in the meantime + expectApprox(await aave.balanceOf(strategy.address), parseEther('86.847909'), 0.1); + }); }); - it('sellRewards', async () => { - expect(await stkAave.balanceOf(strategy.address)).to.equal(0); - expect(await aave.balanceOf(strategy.address)).to.equal(0); + describe('sellRewards', () => { + it('success - rewards correctly sold', async () => { + expect(await stkAave.balanceOf(strategy.address)).to.equal(0); + expect(await aave.balanceOf(strategy.address)).to.equal(0); - await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day - await network.provider.send('evm_mine'); + await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day + await network.provider.send('evm_mine'); - expect(await stkAave.stakersCooldowns(strategy.address)).to.equal(0); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + expect(await stkAave.stakersCooldowns(strategy.address)).to.equal(0); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - await strategy.connect(keeper).claimRewards(); - expect(strategy.connect(guardian).sellRewards(0, '0x')).to.be.revertedWith( - `AccessControl: account ${guardian.address.toLowerCase()} is missing role ${await strategy.KEEPER_ROLE()}`, - ); + await strategy.connect(keeper).claimRewards(); + await strategy['harvest()']({ gasLimit: 3e6 }); + await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day + await network.provider.send('evm_mine'); + await strategy['harvest()']({ gasLimit: 3e6 }); - await strategy['harvest()']({ gasLimit: 3e6 }); - await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day - await network.provider.send('evm_mine'); - await strategy['harvest()']({ gasLimit: 3e6 }); - - // expect(parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address)))).to.be.closeTo(1.8, 0.1); - - // const payloadRevert = ( - // await axios.get( - // `https://api.1inch.exchange/v4.0/1/swap?${qs.stringify({ - // fromTokenAddress: stkAave.address, - // toTokenAddress: wantToken.address, - // fromAddress: strategy.address, - // amount: (await stkAave.balanceOf(strategy.address)).mul(10).toString(), - // slippage: 50, - // disableEstimate: true, - // })}`, - // ) - // ).data.tx.data; - // await strategy.connect(keeper).sellRewards(0, payloadRevert, true); - - await strategy.connect(keeper).claimRewards(); - await expect(strategy.connect(keeper).sellRewards(0, '0x')).to.be.reverted; - - const chainId = 1; - const oneInchParams = qs.stringify({ - fromTokenAddress: stkAave.address, - toTokenAddress: wantToken.address, - fromAddress: strategy.address, - amount: parseUnits('1.79', 18).toString(), - slippage: 50, - disableEstimate: true, - }); - const url = `https://api.1inch.exchange/v4.0/${chainId}/swap?${oneInchParams}`; - - const res = await axios.get(url); - const payload = res.data.tx.data; - - const usdcBefore = await wantToken.balanceOf(strategy.address); - - await strategy.connect(keeper).claimRewards(); - await strategy.connect(keeper).sellRewards(0, payload); - - const usdcAfter = parseFloat(utils.formatUnits(await wantToken.balanceOf(strategy.address), 6)); - const stkAaveAfter = parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address))); - - expect(usdcBefore).to.equal(0); - expect(stkAaveAfter).to.be.closeTo(0, 0.1); - expect(usdcAfter).to.be.closeTo(210, 5); - }); + await strategy.connect(keeper).claimRewards(); + await expect(strategy.connect(keeper).sellRewards(0, '0x')).to.be.reverted; - it('_prepareReturn', async () => { - const balance = (await wantToken.balanceOf(strategy.address)) - .add(await wantToken.balanceOf(poolManager.address)) - .mul((await poolManager.strategies(strategy.address)).debtRatio) - .div(BigNumber.from(1e9)); + // Obtained and works for this block: to swap 0.01 stkAave + const payload = + '0xe449022e000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000' + + '0000000000000001165faa24c0e7600000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000010000000000000000000000001a76f6b9b3d9c532e0b56990944a31a705933fbdcfee7c08'; - await strategy['harvest()']({ gasLimit: 3e6 }); + const aaveBefore = await aave.balanceOf(strategy.address); + const stkAaveBefore = await stkAave.balanceOf(strategy.address); - const targetCollatRatio = await strategy.targetCollatRatio(); - const expectedBorrows = balance.mul(targetCollatRatio).div(utils.parseEther('1').sub(targetCollatRatio)); - const expectedDeposits = expectedBorrows.mul(utils.parseEther('1')).div(targetCollatRatio); + await strategy.connect(keeper).sellRewards(0, payload); - const deposits = await aToken.balanceOf(strategy.address); - const borrows = await debtToken.balanceOf(strategy.address); + const aaveAfter = await aave.balanceOf(strategy.address); + const stkAaveAfter = await stkAave.balanceOf(strategy.address); - expect(deposits).to.be.closeTo(expectedDeposits, 5); - expect(borrows).to.be.closeTo(expectedBorrows, 5); + expect(aaveBefore).to.equal(0); + expect(stkAaveAfter).to.be.equal(stkAaveBefore.sub(parseUnits('1', 16))); + expect(aaveAfter).to.be.gt(0); + // Checking if we can sweep + expect(await aave.balanceOf(guardian.address)).to.be.equal(0); + await strategy.connect(guardian).sweep(aave.address, guardian.address); + expect(await aave.balanceOf(guardian.address)).to.be.gt(0); + expect(await aave.balanceOf(strategy.address)).to.be.equal(0); + }); + it('reverts - because of slippage protection', async () => { + await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day + await network.provider.send('evm_mine'); + await strategy.connect(keeper).claimRewards(); + await strategy['harvest()']({ gasLimit: 3e6 }); + await network.provider.send('evm_increaseTime', [3600 * 24 * 1]); // forward 1 day + await network.provider.send('evm_mine'); + await strategy['harvest()']({ gasLimit: 3e6 }); + await strategy.connect(keeper).claimRewards(); + + // Obtained and works for this block: to swap 0.01 stkAave + const payload = + '0xe449022e000000000000000000000000000000000000000000000000002386f26fc1000000000000000000000000000000000000000' + + '0000000000000001165faa24c0e7600000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000010000000000000000000000001a76f6b9b3d9c532e0b56990944a31a705933fbdcfee7c08'; + + await expect(strategy.connect(keeper).sellRewards(parseEther('10'), payload)).to.be.revertedWith( + 'TooSmallAmountOut', + ); + }); + it('reverts - on a valid token but for which no allowance has been given', async () => { + // To swap USDC to agEUR + await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { + await wantToken.connect(acc).transfer(strategy.address, _startAmountUSDC); + }); + // Swaps USDC to agEUR + const payload = + '0xe449022e00000000000000000000000000000000000000000000000000000000000f424000000000000000000000' + + '00000000000000000000000000000bf8f77c58644fb300000000000000000000000000000000000000000000000000' + + '00000000000060000000000000000000000000000000000000000000000000000000000000000180000000000000000' + + '00000007ed3f364668cd2b9449a8660974a26a092c64849cfee7c08'; + + await expect(strategy.connect(keeper).sellRewards(0, payload)).to.be.reverted; + }); }); - it('_prepareReturn 2', async () => { - await strategy['harvest()']({ gasLimit: 3e6 }); - const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; - expect(await strategy.estimatedTotalAssets()).to.be.closeTo( - _startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), - 10, - ); + describe('_prepareReturn', () => { + it('success - results expected', async () => { + const balance = (await wantToken.balanceOf(strategy.address)) + .add(await wantToken.balanceOf(poolManager.address)) + .mul((await poolManager.strategies(strategy.address)).debtRatio) + .div(BigNumber.from(1e9)); - const newDebtRatio = utils.parseUnits('0.5', 9); - await poolManager.updateStrategyDebtRatio(strategy.address, newDebtRatio); - await strategy['harvest()']({ gasLimit: 3e6 }); - expect(await strategy.estimatedTotalAssets()).to.be.closeTo( - _startAmountUSDC.mul(newDebtRatio).div(utils.parseUnits('1', 9)), - 50000, - ); - }); + await strategy['harvest()']({ gasLimit: 3e6 }); - it('_prepareReturn 3', async () => { - await strategy['harvest()']({ gasLimit: 3e6 }); + const targetCollatRatio = await strategy.targetCollatRatio(); + const expectedBorrows = balance.mul(targetCollatRatio).div(utils.parseEther('1').sub(targetCollatRatio)); + const expectedDeposits = expectedBorrows.mul(utils.parseEther('1')).div(targetCollatRatio); - // fake profit for strategy - await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { - await wantToken.connect(acc).transfer(strategy.address, _startAmountUSDC); - }); + const deposits = await aToken.balanceOf(strategy.address); + const borrows = await debtToken.balanceOf(strategy.address); - await strategy['harvest()']({ gasLimit: 3e6 }); + expect(deposits).to.be.closeTo(expectedDeposits, 5); + expect(borrows).to.be.closeTo(expectedBorrows, 5); + expect(await strategy.isActive()).to.be.equal(true); + }); - const balance = (await poolManager.strategies(strategy.address)).totalStrategyDebt; + it('success - other scenario', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); + const debtRatio = (await poolManager.strategies(strategy.address)).debtRatio; + expect(await strategy.estimatedTotalAssets()).to.be.closeTo( + _startAmountUSDC.mul(debtRatio).div(utils.parseUnits('1', 9)), + 10, + ); - const targetCollatRatio = await strategy.targetCollatRatio(); - const expectedBorrows = balance.mul(targetCollatRatio).div(utils.parseEther('1').sub(targetCollatRatio)); - const expectedDeposits = expectedBorrows.mul(utils.parseEther('1')).div(targetCollatRatio); + const newDebtRatio = utils.parseUnits('0.5', 9); + await poolManager.updateStrategyDebtRatio(strategy.address, newDebtRatio); + await strategy['harvest()']({ gasLimit: 3e6 }); + expect(await strategy.estimatedTotalAssets()).to.be.closeTo( + _startAmountUSDC.mul(newDebtRatio).div(utils.parseUnits('1', 9)), + 50000, + ); + }); - const deposits = await aToken.balanceOf(strategy.address); - const borrows = await debtToken.balanceOf(strategy.address); + it('success - last scenario', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); - expect(deposits).to.be.closeTo(expectedDeposits, 10); - expect(borrows).to.be.closeTo(expectedBorrows, 10); - }); + // fake profit for strategy + await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { + await wantToken.connect(acc).transfer(strategy.address, _startAmountUSDC); + }); - it('manualDeleverage', async () => { - const _amount = 10_000; - const amount = utils.parseUnits(_amount.toString(), 6); - await strategy.connect(guardian).manualDeleverage(0); + await strategy['harvest()']({ gasLimit: 3e6 }); - await strategy['harvest()']({ gasLimit: 3e6 }); + const balance = (await poolManager.strategies(strategy.address)).totalStrategyDebt; - const aBefore = await aToken.balanceOf(strategy.address); - const debtBefore = await debtToken.balanceOf(strategy.address); + const targetCollatRatio = await strategy.targetCollatRatio(); + const expectedBorrows = balance.mul(targetCollatRatio).div(utils.parseEther('1').sub(targetCollatRatio)); + const expectedDeposits = expectedBorrows.mul(utils.parseEther('1')).div(targetCollatRatio); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - await strategy.connect(guardian).manualDeleverage(amount); + const deposits = await aToken.balanceOf(strategy.address); + const borrows = await debtToken.balanceOf(strategy.address); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - expect(_amount).to.be.closeTo(aBefore.sub(await aToken.balanceOf(strategy.address)).div(1e6), 2); - expect(_amount).to.be.closeTo(debtBefore.sub(await debtToken.balanceOf(strategy.address)).div(1e6), 2); + expect(deposits).to.be.closeTo(expectedDeposits, 10); + expect(borrows).to.be.closeTo(expectedBorrows, 10); + }); }); + describe('manualDeleverage', () => { + it('success - changing leverage', async () => { + const _amount = 10_000; + const amount = utils.parseUnits(_amount.toString(), 6); + await strategy.connect(guardian).manualDeleverage(0); - it('manualReleaseWant', async () => { - await strategy['harvest()']({ gasLimit: 3e6 }); - await strategy.connect(guardian).manualReleaseWant(0); - - const _amount = 10_000; - const amount = utils.parseUnits(_amount.toString(), 6); + await strategy['harvest()']({ gasLimit: 3e6 }); - const aBefore = await aToken.balanceOf(strategy.address); - const debtBefore = await debtToken.balanceOf(strategy.address); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + const aBefore = await aToken.balanceOf(strategy.address); + const debtBefore = await debtToken.balanceOf(strategy.address); - await strategy.connect(guardian).manualReleaseWant(amount); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + await strategy.connect(guardian).manualDeleverage(amount); - expect(await wantToken.balanceOf(strategy.address)).to.equal(amount); - expect(_amount).to.be.closeTo(aBefore.sub(await aToken.balanceOf(strategy.address)).div(1e6), 2); - expect((await debtToken.balanceOf(strategy.address)).div(1e6)).to.equal(debtBefore.div(1e6)); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + expect(_amount).to.be.closeTo(aBefore.sub(await aToken.balanceOf(strategy.address)).div(1e6), 2); + expect(_amount).to.be.closeTo(debtBefore.sub(await debtToken.balanceOf(strategy.address)).div(1e6), 2); + }); }); + describe('manualReleaseWant', () => { + it('success - want sold', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); + await strategy.connect(guardian).manualReleaseWant(0); - it('_adjustPosition - _leverDownTo', async () => { - await strategy['harvest()']({ gasLimit: 3e6 }); - - await strategy.connect(guardian).setBoolParams({ - isFlashMintActive: (await strategy.boolParams()).isFlashMintActive, - automaticallyComputeCollatRatio: false, - withdrawCheck: (await strategy.boolParams()).withdrawCheck, - cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, - }); - const newCollatRatio = utils.parseUnits('0.7', 18); - await strategy - .connect(guardian) - .setCollateralTargets( - newCollatRatio, - await strategy.maxCollatRatio(), - await strategy.maxBorrowCollatRatio(), - await strategy.daiBorrowCollatRatio(), - ); - - expect(await strategy.targetCollatRatio()).to.equal(newCollatRatio); + const _amount = 10_000; + const amount = utils.parseUnits(_amount.toString(), 6); - await strategy['harvest()']({ gasLimit: 3e6 }); + const aBefore = await aToken.balanceOf(strategy.address); + const debtBefore = await debtToken.balanceOf(strategy.address); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - const borrow = (await poolManager.strategies(strategy.address)).totalStrategyDebt - .mul(newCollatRatio) - .div(utils.parseEther('1').sub(newCollatRatio)); + await strategy.connect(guardian).manualReleaseWant(amount); - expect(borrow).to.be.closeTo(await debtToken.balanceOf(strategy.address), 5); - expect(await aToken.balanceOf(strategy.address)).to.be.closeTo( - borrow.mul(utils.parseEther('1')).div(newCollatRatio), - 5, - ); - expect(0).to.be.closeTo(await wantToken.balanceOf(strategy.address), 5); + expect(await wantToken.balanceOf(strategy.address)).to.equal(amount); + expect(_amount).to.be.closeTo(aBefore.sub(await aToken.balanceOf(strategy.address)).div(1e6), 2); + expect((await debtToken.balanceOf(strategy.address)).div(1e6)).to.equal(debtBefore.div(1e6)); + }); }); + describe('_adjustPosition - _leverDownTo', () => { + it('success - position adjusted', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); - it('_leverMax - isFlashMintActive', async () => { - await strategy.connect(guardian).setBoolParams({ - isFlashMintActive: false, - automaticallyComputeCollatRatio: (await strategy.boolParams()).automaticallyComputeCollatRatio, - withdrawCheck: (await strategy.boolParams()).withdrawCheck, - cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, - }); - await strategy['harvest()']({ gasLimit: 3e6 }); + await strategy.connect(guardian).setBoolParams({ + isFlashMintActive: (await strategy.boolParams()).isFlashMintActive, + automaticallyComputeCollatRatio: false, + withdrawCheck: (await strategy.boolParams()).withdrawCheck, + cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, + }); + const newCollatRatio = utils.parseUnits('0.7', 18); + await strategy + .connect(guardian) + .setCollateralTargets( + newCollatRatio, + await strategy.maxCollatRatio(), + await strategy.maxBorrowCollatRatio(), + await strategy.daiBorrowCollatRatio(), + ); - const targetCollatRatioBefore = await strategy.targetCollatRatio(); - const aTokenBefore = await aToken.balanceOf(strategy.address); - const debtTokenBefore = await debtToken.balanceOf(strategy.address); + expect(await strategy.targetCollatRatio()).to.equal(newCollatRatio); - await strategy.connect(guardian).setBoolParams({ - isFlashMintActive: true, - automaticallyComputeCollatRatio: (await strategy.boolParams()).automaticallyComputeCollatRatio, - withdrawCheck: (await strategy.boolParams()).withdrawCheck, - cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, - }); - await strategy['harvest()']({ gasLimit: 3e6 }); + await strategy['harvest()']({ gasLimit: 3e6 }); - expect(targetCollatRatioBefore).to.equal(await strategy.targetCollatRatio()); - expect(aTokenBefore).to.be.lte(await aToken.balanceOf(strategy.address)); - expect(debtTokenBefore).to.be.lte(await debtToken.balanceOf(strategy.address)); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - }); + const borrow = (await poolManager.strategies(strategy.address)).totalStrategyDebt + .mul(newCollatRatio) + .div(utils.parseEther('1').sub(newCollatRatio)); - it('_leverDownTo - isFlashMintActive false', async () => { - await strategy.connect(guardian).setBoolParams({ - isFlashMintActive: false, - automaticallyComputeCollatRatio: false, - withdrawCheck: (await strategy.boolParams()).withdrawCheck, - cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, - }); - - await strategy['harvest()']({ gasLimit: 3e6 }); - const newCollatRatio = utils.parseUnits('0.7', 18); - await strategy - .connect(guardian) - .setCollateralTargets( - newCollatRatio, - await strategy.maxCollatRatio(), - await strategy.maxBorrowCollatRatio(), - await strategy.daiBorrowCollatRatio(), + expect(borrow).to.be.closeTo(await debtToken.balanceOf(strategy.address), 5); + expect(await aToken.balanceOf(strategy.address)).to.be.closeTo( + borrow.mul(utils.parseEther('1')).div(newCollatRatio), + 5, ); - await strategy['harvest()']({ gasLimit: 3e6 }); + expect(0).to.be.closeTo(await wantToken.balanceOf(strategy.address), 5); + }); + }); + describe('_leverMax', () => { + it('success - when flash mint is active', async () => { + await strategy.connect(guardian).setBoolParams({ + isFlashMintActive: false, + automaticallyComputeCollatRatio: (await strategy.boolParams()).automaticallyComputeCollatRatio, + withdrawCheck: (await strategy.boolParams()).withdrawCheck, + cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, + }); + await strategy['harvest()']({ gasLimit: 3e6 }); + + const targetCollatRatioBefore = await strategy.targetCollatRatio(); + const aTokenBefore = await aToken.balanceOf(strategy.address); + const debtTokenBefore = await debtToken.balanceOf(strategy.address); + + await strategy.connect(guardian).setBoolParams({ + isFlashMintActive: true, + automaticallyComputeCollatRatio: (await strategy.boolParams()).automaticallyComputeCollatRatio, + withdrawCheck: (await strategy.boolParams()).withdrawCheck, + cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, + }); + await strategy['harvest()']({ gasLimit: 3e6 }); + + expect(targetCollatRatioBefore).to.equal(await strategy.targetCollatRatio()); + expect(aTokenBefore).to.be.lte(await aToken.balanceOf(strategy.address)); + expect(debtTokenBefore).to.be.lte(await debtToken.balanceOf(strategy.address)); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + }); + it('success - flashloan more than maxLiquidity', async () => { + const balanceStorage = utils + .solidityKeccak256(['uint256', 'uint256'], [strategy.address, 9]) + .replace('0x0', '0x'); + const amountTx = utils.hexZeroPad(utils.parseUnits('900000000', 6).toHexString(), 32); + + await network.provider.send('hardhat_setStorageAt', [ + '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + balanceStorage, + amountTx, + ]); - expect(await strategy.targetCollatRatio()).to.equal(newCollatRatio); + await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { + await wantToken.connect(acc).approve(lendingPool.address, constants.MaxUint256); + await aToken.connect(acc).approve(lendingPool.address, constants.MaxUint256); + // await lendingPool.connect(acc).deposit(wantToken.address, utils.parseUnits('120000000', 6), acc.address, 0); + }); + await poolManager.updateStrategyDebtRatio(strategy.address, utils.parseUnits('1', 9)); - expect((await aToken.balanceOf(strategy.address)).mul(newCollatRatio).div(utils.parseEther('1'))).to.be.closeTo( - await debtToken.balanceOf(strategy.address), - 5, - ); - expect(96).to.be.closeTo(await wantToken.balanceOf(strategy.address), 5); - }); + await strategy['harvest()']({ gasLimit: 3e6 }); - it('emergencyExit', async () => { - await impersonate(poolManager.address, async acc => { - await network.provider.send('hardhat_setBalance', [ - poolManager.address, - utils.parseEther('1').toHexString().replace('0x0', '0x'), - ]); - await strategy.connect(acc).setEmergencyExit(); - }); + // // expect(parseFloat(utils.formatUnits(await strategy.estimatedTotalAssets(), 6))).to.be.closeTo(301_500_000, 100); + + await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { + await lendingPool.connect(acc).deposit(wantToken.address, utils.parseUnits('200000000', 6), acc.address, 0); + }); - expect(await strategy.estimatedTotalAssets()).to.equal(0); - await strategy['harvest()']({ gasLimit: 3e6 }); - expect(await strategy.estimatedTotalAssets()).to.be.closeTo(utils.parseUnits('1500000', 6), 10); + await strategy['harvest()']({ gasLimit: 3e6 }); + console.log( + utils.formatUnits(await aToken.balanceOf(strategy.address), 6), + utils.formatUnits(await debtToken.balanceOf(strategy.address), 6), + utils.formatUnits( + (await aToken.balanceOf(strategy.address)).sub(await debtToken.balanceOf(strategy.address)), + 6, + ), + ); + }); }); + describe('_leverDownTo', () => { + it('success - when isFlashMintActive false', async () => { + await strategy.connect(guardian).setBoolParams({ + isFlashMintActive: false, + automaticallyComputeCollatRatio: false, + withdrawCheck: (await strategy.boolParams()).withdrawCheck, + cooldownStkAave: (await strategy.boolParams()).cooldownStkAave, + }); - // it.only('flashloan more than maxLiquidity', async () => { - // const balanceStorage = utils.solidityKeccak256(['uint256', 'uint256'], [strategy.address, 9]); - - // await network.provider.send('hardhat_setStorageAt', [ - // '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - // balanceStorage.replace('0x0', '0x'), - // utils.hexZeroPad(utils.parseUnits('900000000', 6).toHexString(), 32), - // ]); - - // await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { - // await wantToken.connect(acc).approve(lendingPool.address, constants.MaxUint256); - // await aToken.connect(acc).approve(lendingPool.address, constants.MaxUint256); - // // await lendingPool.connect(acc).deposit(wantToken.address, utils.parseUnits('120000000', 6), acc.address, 0); - // }); - // await poolManager.updateStrategyDebtRatio(strategy.address, utils.parseUnits('1', 9)); - - // await strategy['harvest()']({ gasLimit: 3e6 }); - - // // // expect(parseFloat(utils.formatUnits(await strategy.estimatedTotalAssets(), 6))).to.be.closeTo(301_500_000, 100); - - // await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { - // await lendingPool.connect(acc).deposit(wantToken.address, utils.parseUnits('200000000', 6), acc.address, 0); - // }); - - // await strategy['harvest()']({ gasLimit: 3e6 }); - // console.log('cr', await strategy.targetCollatRatio()); - // console.log( - // utils.formatUnits(await aToken.balanceOf(strategy.address), 6), - // utils.formatUnits(await debtToken.balanceOf(strategy.address), 6), - // utils.formatUnits( - // (await aToken.balanceOf(strategy.address)).sub(await debtToken.balanceOf(strategy.address)), - // 6, - // ), - // ); - // }); - - it('cooldownStkAave', async () => { - await strategy['harvest()']({ gasLimit: 3e6 }); - await expect((await strategy.boolParams()).cooldownStkAave).to.be.true; - - await network.provider.send('evm_increaseTime', [3600 * 24]); - await network.provider.send('evm_mine'); - await strategy['harvest()']({ gasLimit: 3e6 }); - - await network.provider.send('evm_increaseTime', [3600 * 24 * 10.5]); // forward 11 days - await network.provider.send('evm_mine'); - - const stkAaveBalanceBefore = parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address), 18)); - await strategy['harvest()']({ gasLimit: 3e6 }); - const aaveBalanceAfterRedeem = parseFloat(utils.formatUnits(await aave.balanceOf(strategy.address), 18)); - - expect(stkAaveBalanceBefore).to.be.closeTo(aaveBalanceAfterRedeem, 0.1); + await strategy['harvest()']({ gasLimit: 3e6 }); + const newCollatRatio = utils.parseUnits('0.7', 18); + await strategy + .connect(guardian) + .setCollateralTargets( + newCollatRatio, + await strategy.maxCollatRatio(), + await strategy.maxBorrowCollatRatio(), + await strategy.daiBorrowCollatRatio(), + ); + await strategy['harvest()']({ gasLimit: 3e6 }); + + expect(await strategy.targetCollatRatio()).to.equal(newCollatRatio); + + expect((await aToken.balanceOf(strategy.address)).mul(newCollatRatio).div(utils.parseEther('1'))).to.be.closeTo( + await debtToken.balanceOf(strategy.address), + 5, + ); + expect(96).to.be.closeTo(await wantToken.balanceOf(strategy.address), 5); + }); + }); + describe('emergencyExit', () => { + it('success - funds exited', async () => { + await impersonate(poolManager.address, async acc => { + await network.provider.send('hardhat_setBalance', [ + poolManager.address, + utils.parseEther('1').toHexString().replace('0x0', '0x'), + ]); + await strategy.connect(acc).setEmergencyExit(); + }); + + expect(await strategy.estimatedTotalAssets()).to.equal(0); + await strategy['harvest()']({ gasLimit: 3e6 }); + expect(await strategy.estimatedTotalAssets()).to.be.closeTo(utils.parseUnits('1500000', 6), 10); + }); }); + describe('cooldownStkAave', () => { + it('success - cooldown triggered', async () => { + await strategy['harvest()']({ gasLimit: 3e6 }); + await expect((await strategy.boolParams()).cooldownStkAave).to.be.equal(true); + + await network.provider.send('evm_increaseTime', [3600 * 24]); + await network.provider.send('evm_mine'); + await strategy['harvest()']({ gasLimit: 3e6 }); + + await network.provider.send('evm_increaseTime', [3600 * 24 * 10.5]); // forward 11 days + await network.provider.send('evm_mine'); - it('estimatedApr', async () => { - expect(await strategy.estimatedAPR()).to.equal(0); + const stkAaveBalanceBefore = parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address), 18)); + await strategy['harvest()']({ gasLimit: 3e6 }); + const aaveBalanceAfterRedeem = parseFloat(utils.formatUnits(await aave.balanceOf(strategy.address), 18)); - await strategy['harvest()']({ gasLimit: 3e6 }); - expect(parseFloat(utils.formatUnits(await aToken.balanceOf(strategy.address), 6))).to.be.closeTo(9677419, 1000); + expect(stkAaveBalanceBefore).to.be.closeTo(aaveBalanceAfterRedeem, 0.1); + }); + }); + describe('estimatedApr', () => { + it('success - apr correctly estimated', async () => { + expect(await strategy.estimatedAPR()).to.equal(0); + + await strategy['harvest()']({ gasLimit: 3e6 }); + expect(parseFloat(utils.formatUnits(await aToken.balanceOf(strategy.address), 6))).to.be.closeTo(9677419, 1000); - expect(await wantToken.balanceOf(strategy.address)).to.equal(0); - expect(parseFloat(utils.formatUnits(await strategy.estimatedAPR(), 18))).to.be.closeTo(0.067, 0.001); + expect(await wantToken.balanceOf(strategy.address)).to.equal(0); + expect(parseFloat(utils.formatUnits(await strategy.estimatedAPR(), 18))).to.be.closeTo(0.067, 0.001); + }); }); }); - - // describe("", () => { - // it.only('', async () => {}); - // }) }); diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategy2.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategy2.test.ts index 39ddc29..5e0655a 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategy2.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategy2.test.ts @@ -6,7 +6,7 @@ import { expect } from '../test-utils/chai-setup'; import { setup } from './setup_tests'; import { parseUnits } from 'ethers/lib/utils'; -describe('AaveFlashloan Strategy2', () => { +describe('AaveFlashloanStrategy - Scenario', () => { it('scenario static', async () => { const { strategy, diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategyCoverage.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategyCoverage.test.ts index 1a4eb65..7283f97 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategyCoverage.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategyCoverage.test.ts @@ -3,8 +3,6 @@ import { ethers, network } from 'hardhat'; import { utils, Contract } from 'ethers'; import { expect } from '../test-utils/chai-setup'; import { deploy, impersonate } from '../test-utils'; -import axios from 'axios'; -import qs from 'qs'; import { AaveFlashloanStrategy, FlashMintLib, @@ -16,17 +14,19 @@ import { AaveFlashloanStrategy__factory, PoolManager, IAaveIncentivesController, + IProtocolDataProvider__factory, + IProtocolDataProvider, } from '../../typechain'; -import { logBN } from '../utils-interaction'; -import { setDaiBalanceFor } from './aaveFlashloanStrategy_random_DAI.test'; +import { findBalancesSlot, setTokenBalanceFor } from '../utils-interaction'; import { parseUnits } from 'ethers/lib/utils'; -describe('AaveFlashloan Strat - Coverage', () => { +describe('AaveFlashloanStrategy - Coverage', () => { // ATokens let aToken: ERC20, debtToken: ERC20; // Tokens let wantToken: ERC20, aave: ERC20, stkAave: IStakedAave; + let decimalsToken: number; // Guardians let deployer: SignerWithAddress, @@ -38,6 +38,7 @@ describe('AaveFlashloan Strat - Coverage', () => { let poolManager: PoolManager; let incentivesController: IAaveIncentivesController; + let protocolDataProvider: IProtocolDataProvider; let flashMintLib: FlashMintLib; let strategy: AaveFlashloanStrategy; @@ -63,6 +64,7 @@ describe('AaveFlashloan Strat - Coverage', () => { const tokenAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; wantToken = (await ethers.getContractAt(ERC20__factory.abi, tokenAddress)) as ERC20; + decimalsToken = await wantToken.decimals(); aave = (await ethers.getContractAt(ERC20__factory.abi, '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9')) as ERC20; stkAave = (await ethers.getContractAt( IStakedAave__factory.abi, @@ -78,6 +80,11 @@ describe('AaveFlashloan Strat - Coverage', () => { '0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5', )) as IAaveIncentivesController; + protocolDataProvider = (await ethers.getContractAt( + IProtocolDataProvider__factory.abi, + '0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d', + )) as IProtocolDataProvider; + flashMintLib = (await deploy('FlashMintLib')) as FlashMintLib; const strategyImplementation = (await deploy('AaveFlashloanStrategy', [], { @@ -97,21 +104,23 @@ describe('AaveFlashloan Strat - Coverage', () => { keeper.address, ]); - aToken = (await ethers.getContractAt(ERC20__factory.abi, '0xBcca60bB61934080951369a648Fb03DF4F96263C')) as ERC20; - debtToken = (await ethers.getContractAt(ERC20__factory.abi, '0x619beb58998eD2278e08620f97007e1116D5D25b')) as ERC20; + // (address aToken_, , address debtToken_) = _protocolDataProvider.getReserveTokensAddresses(address(want)); + const getReserveTokensAddresses = await protocolDataProvider.getReserveTokensAddresses(wantToken.address); + aToken = (await ethers.getContractAt(ERC20__factory.abi, getReserveTokensAddresses.aTokenAddress)) as ERC20; + debtToken = (await ethers.getContractAt( + ERC20__factory.abi, + getReserveTokensAddresses.variableDebtTokenAddress, + )) as ERC20; }); - describe('Strategy', () => { - const _startAmount = 1_000_000_000; + describe('Strategy Scenario', () => { + const _startAmount = 100_000_000; beforeEach(async () => { await (await poolManager.addStrategy(strategy.address, utils.parseUnits('0.75', 9))).wait(); - await impersonate('0x6262998Ced04146fA42253a5C0AF90CA02dfd2A3', async acc => { - await wantToken.connect(acc).transfer(user.address, _startAmount); - }); - - await setDaiBalanceFor(user.address, _startAmount); + const balanceSlot = await findBalancesSlot(wantToken.address); + await setTokenBalanceFor(wantToken, user.address, _startAmount, balanceSlot); // sending funds to emission controller await network.provider.send('hardhat_setBalance', [ @@ -125,8 +134,7 @@ describe('AaveFlashloan Strat - Coverage', () => { utils.parseEther('100').toHexString().replace('0x0', '0x'), ]); - await wantToken.connect(user).transfer(poolManager.address, _startAmount); - + await wantToken.connect(user).transfer(poolManager.address, parseUnits(_startAmount.toString(), decimalsToken)); await strategy.connect(keeper)['harvest()']({ gasLimit: 3e6 }); }); @@ -139,7 +147,7 @@ describe('AaveFlashloan Strat - Coverage', () => { await strategy.connect(keeper)['harvest(uint256)'](ethers.constants.Zero, { gasLimit: 3e6 }); const { borrows } = await strategy.getCurrentPosition(); - expect(borrows).to.equal(ethers.constants.Zero); + expect(borrows).to.be.equal(ethers.constants.Zero); }); it('_liquidatePosition - withdrawCheck - success', async () => { @@ -158,73 +166,7 @@ describe('AaveFlashloan Strat - Coverage', () => { await strategy.connect(keeper)['harvest(uint256)'](ethers.constants.Zero, { gasLimit: 3e6 }); const { borrows } = await strategy.getCurrentPosition(); - expect(borrows).to.equal(ethers.constants.Zero); - }); - - it('sellRewards - cooldown triggered', async () => { - expect(await stkAave.balanceOf(strategy.address)).to.equal(0); - expect(await aave.balanceOf(strategy.address)).to.equal(0); - - await network.provider.send('evm_increaseTime', [3600 * 24 * 2]); // forward 2 day - await network.provider.send('evm_mine'); - await strategy.connect(keeper)['harvest()']({ gasLimit: 3e6 }); - - const chainId = 1; - const oneInchParams = qs.stringify({ - fromTokenAddress: stkAave.address, - toTokenAddress: wantToken.address, - fromAddress: strategy.address, - amount: parseUnits('3.59', 18).toString(), - slippage: 50, - disableEstimate: true, - }); - const url = `https://api.1inch.exchange/v4.0/${chainId}/swap?${oneInchParams}`; - - const res = await axios.get(url); - const payload = res.data.tx.data; - - await strategy.connect(keeper).claimRewards(); - await strategy.connect(keeper).sellRewards(0, payload); - - const stkAaveAfter = parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address))); - - expect(stkAaveAfter).to.be.closeTo(0, 0.01); - - await network.provider.send('evm_increaseTime', [3600 * 24 * 5]); // forward 5 days - await network.provider.send('evm_mine'); - - // cooldown triggered - await strategy.connect(keeper)['harvest()']({ gasLimit: 3e6 }); - }); - - it('sellRewards - not claiming', async () => { - expect(await stkAave.balanceOf(strategy.address)).to.equal(0); - expect(await aave.balanceOf(strategy.address)).to.equal(0); - - await network.provider.send('evm_increaseTime', [3600 * 24 * 2]); // forward 1 day - await network.provider.send('evm_mine'); - await strategy.connect(keeper)['harvest()']({ gasLimit: 3e6 }); - - const chainId = 1; - const oneInchParams = qs.stringify({ - fromTokenAddress: stkAave.address, - toTokenAddress: wantToken.address, - fromAddress: strategy.address, - amount: parseUnits('3.59', 18).toString(), - slippage: 50, - disableEstimate: true, - }); - const url = `https://api.1inch.exchange/v4.0/${chainId}/swap?${oneInchParams}`; - - const res = await axios.get(url); - const payload = res.data.tx.data; - - await strategy.connect(keeper).claimRewards(); - await strategy.connect(keeper).sellRewards(0, payload); - - const stkAaveAfter = parseFloat(utils.formatUnits(await stkAave.balanceOf(strategy.address))); - - expect(stkAaveAfter).to.be.closeTo(0, 0.01); + expect(borrows).to.be.equal(ethers.constants.Zero); }); it('onFlashLoan - revert', async () => { @@ -232,7 +174,7 @@ describe('AaveFlashloan Strat - Coverage', () => { strategy .connect(keeper) .onFlashLoan(keeper.address, keeper.address, ethers.constants.Zero, ethers.constants.Zero, '0x'), - ).to.be.revertedWith('1'); + ).to.be.revertedWith('InvalidSender'); }); it('cooldownStkAave - too soon', async () => { @@ -254,7 +196,7 @@ describe('AaveFlashloan Strat - Coverage', () => { }); it('estimatedAPR', async () => { const estimatedAPR = await strategy.estimatedAPR(); - expect(estimatedAPR).to.be.closeTo(parseUnits('0.0406', 18), parseUnits('0.005', 18)); + expect(estimatedAPR).to.be.closeTo(parseUnits('0.026836', 18), parseUnits('0.005', 18)); }); }); }); diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategyHarvestHint.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategyHarvestHint.test.ts index 0076384..b63746f 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategyHarvestHint.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategyHarvestHint.test.ts @@ -25,7 +25,7 @@ import { getParamsOptim } from '../utils'; const PRECISION = 3; const toOriginalBase = (n: BigNumber, base = 6) => n.mul(utils.parseUnits('1', base)).div(utils.parseUnits('1', 27)); -describe('AaveFlashloanHarvestHint', () => { +describe('AaveFlashloanStrategy - Harvest Hint', () => { // ATokens let aToken: ERC20, debtToken: ERC20; let aDAIToken: ERC20, debtDAIToken: ERC20; diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random.test.ts index 681ca15..e34a177 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random.test.ts @@ -3,7 +3,7 @@ import { network } from 'hardhat'; import { utils } from 'ethers'; import { setup } from './setup_tests'; -describe('AaveFlashloan strategy - Random USDC', () => { +describe('AaveFlashloanStrategy - Random USDC', () => { it('scenario random', async () => { const { _wantToken, strategy, lendingPool, poolManager, oldStrategy, realGuardian, richUSDCUser, aToken, harvest } = await setup(14456160); diff --git a/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random_DAI.test.ts b/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random_DAI.test.ts index edb2bbc..c9e2612 100644 --- a/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random_DAI.test.ts +++ b/test/strategyAaveFlashLoan/aaveFlashloanStrategy_random_DAI.test.ts @@ -13,7 +13,7 @@ export async function setDaiBalanceFor(account: string, amount: number) { ]); } -describe('AaveFlashloan strategy - Random DAI', () => { +describe('AaveFlashloanStrategy - Random DAI', () => { it('scenario random', async () => { const { _wantToken, strategy, lendingPool, poolManager, oldStrategy, realGuardian, richUSDCUser, aToken, harvest } = await setup(14456160, 'DAI'); diff --git a/test/strategyStETH/strategyStETHlocal.ts b/test/strategyStETH/strategyStETHlocal.ts index dc4c341..93c755c 100644 --- a/test/strategyStETH/strategyStETHlocal.ts +++ b/test/strategyStETH/strategyStETHlocal.ts @@ -132,7 +132,7 @@ describe('StrategyStETH', () => { stETH.address, parseUnits('3', 9), ), - ).to.be.revertedWith('0'); + ).to.be.revertedWith('ZeroAddress'); }); it('reverts - zero governor address', async () => { const strategy = (await deployUpgradeable(new StETHStrategy__factory(guardian))) as StETHStrategy; @@ -147,7 +147,7 @@ describe('StrategyStETH', () => { stETH.address, parseUnits('3', 9), ), - ).to.be.revertedWith('0'); + ).to.be.revertedWith('ZeroAddress'); }); it('reverts - zero keeper address', async () => { const strategy = (await deployUpgradeable(new StETHStrategy__factory(guardian))) as StETHStrategy; @@ -162,7 +162,7 @@ describe('StrategyStETH', () => { stETH.address, parseUnits('3', 9), ), - ).to.be.revertedWith('0'); + ).to.be.revertedWith('ZeroAddress'); }); it('reverts - want != weth', async () => { const strategy = (await deployUpgradeable(new StETHStrategy__factory(guardian))) as StETHStrategy; @@ -518,10 +518,12 @@ describe('StrategyStETH', () => { }); describe('sweep', () => { it('reverts - wETH', async () => { - await expect(strategy.connect(guardian).sweep(wETH.address, governor.address)).to.be.revertedWith('93'); + await expect(strategy.connect(guardian).sweep(wETH.address, governor.address)).to.be.revertedWith('InvalidToken'); }); it('reverts - stETH', async () => { - await expect(strategy.connect(guardian).sweep(stETH.address, governor.address)).to.be.revertedWith('93'); + await expect(strategy.connect(guardian).sweep(stETH.address, governor.address)).to.be.revertedWith( + 'InvalidToken', + ); }); }); describe('harvest - other cases', () => { diff --git a/test/utils-interaction.ts b/test/utils-interaction.ts index 771e2b0..fdd424c 100644 --- a/test/utils-interaction.ts +++ b/test/utils-interaction.ts @@ -368,11 +368,10 @@ export async function findBalancesSlot(tokenAddress: string): Promise { throw Error('Balances slot not found!'); } -export async function setTokenBalanceFor(token: ERC20, account: string, amount: BigNumberish) { +export async function setTokenBalanceFor(token: ERC20, account: string, amount: BigNumberish, balanceSlot = 0) { // for FRAX we know it's 0 // const balanceSlot = await findBalancesSlot(token.address); // console.log('the balance slot is ', balanceSlot); - const balanceSlot = 0; const balanceStorage = utils.solidityKeccak256(['uint256', 'uint256'], [account, balanceSlot]).replace('0x0', '0x'); const amountStorage = utils.hexZeroPad(utils.parseUnits(amount.toString(), await token.decimals()).toHexString(), 32);