diff --git a/contracts/helpers/MultiBlockHarvester.sol b/contracts/helpers/MultiBlockHarvester.sol index fe663364..eb7f9f00 100644 --- a/contracts/helpers/MultiBlockHarvester.sol +++ b/contracts/helpers/MultiBlockHarvester.sol @@ -10,6 +10,7 @@ import { BaseHarvester, YieldBearingParams } from "./BaseHarvester.sol"; import { ITransmuter } from "../interfaces/ITransmuter.sol"; import { IAgToken } from "../interfaces/IAgToken.sol"; import { IPool } from "../interfaces/IPool.sol"; +import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import "../utils/Errors.sol"; import "../utils/Constants.sol"; @@ -19,6 +20,7 @@ import "../utils/Constants.sol"; /// @dev Contract to harvest yield from multiple yield bearing assets in multiple blocks transactions contract MultiBlockHarvester is BaseHarvester { using SafeERC20 for IERC20; + using SafeMath for uint256; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// VARIABLES @@ -116,7 +118,6 @@ contract MultiBlockHarvester is BaseHarvester { address(this), block.timestamp ); - _checkSlippage(amount, amountOut, yieldBearingInfo.asset, depositAddress, false); if (yieldBearingAsset == XEVT) { _adjustAllowance(yieldBearingInfo.asset, address(depositAddress), amountOut); (uint256 shares, ) = IPool(depositAddress).deposit(amountOut, address(this)); @@ -129,8 +130,10 @@ contract MultiBlockHarvester is BaseHarvester { address(this), block.timestamp ); + _checkSlippage(amount, amountOut, address(agToken), depositAddress, false); } else if (yieldBearingAsset == USDM) { IERC20(yieldBearingInfo.asset).safeTransfer(depositAddress, amountOut); + _checkSlippage(amount, amountOut, yieldBearingInfo.asset, depositAddress, false); } } else { uint256 amountOut = transmuter.swapExactInput( @@ -161,23 +164,23 @@ contract MultiBlockHarvester is BaseHarvester { address depositAddress, bool assetIn ) internal view { - uint256 decimalsAsset = IERC20Metadata(asset).decimals(); - // Divide or multiply the amountIn to match the decimals of the asset - amountIn = _scaleAmountBasedOnDecimals(decimalsAsset, 18, amountIn, assetIn); + amountIn = _scaleAmountBasedOnDecimals(IERC20Metadata(asset).decimals(), 18, amountIn, assetIn); - if (asset == USDC || asset == USDM || asset == EURC) { + uint256 result; + if (asset == USDC || asset == USDM || asset == EURC || asset == address(agToken)) { // Assume 1:1 ratio between stablecoins - unchecked { - uint256 slippage = ((amountIn - amountOut) * 1e9) / amountIn; - if (slippage > maxSlippage) revert SlippageTooHigh(); - } + (, result) = amountIn.trySub(amountOut); } else if (asset == XEVT) { // Assume 1:1 ratio between the underlying asset of the vault - unchecked { - uint256 slippage = ((IPool(depositAddress).convertToAssets(amountIn) - amountOut) * 1e9) / amountIn; - if (slippage > maxSlippage) revert SlippageTooHigh(); + if (assetIn) { + (, result) = IPool(depositAddress).convertToAssets(amountIn).trySub(amountOut); + } else { + (, result) = amountIn.trySub(IPool(depositAddress).convertToAssets(amountOut)); } } else revert InvalidParam(); + + uint256 slippage = (result * 1e9) / amountIn; + if (slippage > maxSlippage) revert SlippageTooHigh(); } } diff --git a/package.json b/package.json index 70f93eef..97ecf11c 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "", "scripts": { - "ci:coverage": "forge coverage --report lcov && yarn lcov:clean", + "ci:coverage": "forge coverage --report lcov --no-match-path 'test/scripts/*' && yarn lcov:clean", "coverage": "FOUNDRY_PROFILE=dev forge coverage --report lcov && yarn lcov:clean && yarn lcov:generate-html", "compile": "forge build", "compile:dev": "FOUNDRY_PROFILE=dev forge build", diff --git a/test/fuzz/MultiBlockHarvester.t.sol b/test/fuzz/MultiBlockHarvester.t.sol index 113441af..72b87a74 100644 --- a/test/fuzz/MultiBlockHarvester.t.sol +++ b/test/fuzz/MultiBlockHarvester.t.sol @@ -108,20 +108,22 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { ) ); - oracleXEVT = AggregatorV3Interface(address(new MockChainlinkOracle())); - circuitChainlink[0] = AggregatorV3Interface(oracleXEVT); - readData = abi.encode(circuitChainlink, stalePeriods, circuitChainIsMultiplied, chainlinkDecimals, quoteType); - MockChainlinkOracle(address(oracleXEVT)).setLatestAnswer(int256(BASE_8)); - transmuter.setOracle( - XEVT, - abi.encode( - OracleReadType.CHAINLINK_FEEDS, - OracleReadType.STABLE, - readData, - targetData, - abi.encode(uint128(0), uint128(0)) - ) - ); + { + address oracle = 0x6B102047A4bB943DE39233E44487F2d57bDCb33e; + uint256 normalizationFactor = 1e18; // price == 36 decimals + readData = bytes(""); + targetData = abi.encode(oracle, normalizationFactor); + transmuter.setOracle( + XEVT, + abi.encode( + OracleReadType.NO_ORACLE, + OracleReadType.MORPHO_ORACLE, + readData, + targetData, + abi.encode(uint128(0), uint128(0)) + ) + ); + } oracleUSDM = AggregatorV3Interface(address(new MockChainlinkOracle())); circuitChainlink[0] = AggregatorV3Interface(oracleUSDM); @@ -179,6 +181,8 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { harvester.setYieldBearingToDepositAddress(XEVT, XEVT); harvester.setYieldBearingToDepositAddress(USDM, receiver); + transmuter.toggleTrusted(address(harvester), TrustedType.Seller); + agToken.mint(address(harvester), 1_000_000e18); vm.stopPrank(); @@ -358,7 +362,7 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { } function test_harvest_IncreaseExposureXEVT(uint256 amount) external { - amount = 7022; + amount = bound(amount, 1e3, 1e11); _loadReserve(EURC, amount); _setYieldBearingData(XEVT, EURC); @@ -399,7 +403,6 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { assertEq(expectedIncrease, 0); assertEq(issuedFromStablecoinBefore, 0); - assertEq(issuedFromYieldBearingAssetBefore, amount * 1e12); assertEq(totalIssuedBefore, issuedFromYieldBearingAssetBefore); assertEq(expectedAmount, issuedFromYieldBearingAssetBefore - ((targetExposure * totalIssuedBefore) / 1e9)); @@ -520,13 +523,13 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { } function test_ComputeRebalanceAmount_HigherThanMaxWithHarvest() external { - _loadReserve(XEVT, 1e11); - _loadReserve(EURC, 1e11); + _loadReserve(USDC, 1e11); + _loadReserve(USDM, 1e23); uint64 minExposure = uint64((15 * 1e9) / 100); uint64 maxExposure = uint64((60 * 1e9) / 100); - _setYieldBearingData(XEVT, EURC, minExposure, maxExposure); + _setYieldBearingData(USDM, USDC, minExposure, maxExposure); - (uint8 increase, uint256 amount) = harvester.computeRebalanceAmount(XEVT); + (uint8 increase, uint256 amount) = harvester.computeRebalanceAmount(USDM); assertEq(amount, (2e23 * uint256(maxExposure)) / 1e9 - 1e23); assertEq(increase, 0); } @@ -544,13 +547,13 @@ contract MultiBlockHarvestertTest is Fixture, FunctionUtils { } function test_ComputeRebalanceAmount_LowerThanMinAfterHarvest() external { - _loadReserve(EURC, 9e10); - _loadReserve(XEVT, 1e10); + _loadReserve(USDC, 9e10); + _loadReserve(USDM, 1e22); uint64 minExposure = uint64((89 * 1e9) / 100); uint64 maxExposure = uint64((999 * 1e9) / 1000); - _setYieldBearingData(XEVT, EURC, minExposure, maxExposure); + _setYieldBearingData(USDM, USDC, minExposure, maxExposure); - (uint8 increase, uint256 amount) = harvester.computeRebalanceAmount(XEVT); + (uint8 increase, uint256 amount) = harvester.computeRebalanceAmount(USDM); assertEq(amount, 9e22 - (1e23 * uint256(minExposure)) / 1e9); assertEq(increase, 1); }