From a57c47f339ea5101ec76ac07c8d4dff0ea9d0f57 Mon Sep 17 00:00:00 2001 From: Shubhangi Date: Tue, 26 Nov 2024 17:46:06 +0530 Subject: [PATCH 1/3] Initial base implementation for MetaPool and 3CRV pool --- src/helpers/AddressBook.sol | 5 + src/interfaces/ICurve.sol | 5 + .../base/BaseBeefyCurveMetaPoolStrategy.sol | 206 ++++++ .../beefy/BeefythUSDDAIUSDCUSDTStrategy.sol | 19 + .../BeefythUSDDAIUSDCUSDTStrategyWrapper.sol | 62 ++ .../BeefythUSDDAIUSDCUSDTStrategy.t.sol | 638 ++++++++++++++++++ 6 files changed, 935 insertions(+) create mode 100644 src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol create mode 100644 src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol create mode 100644 test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol create mode 100644 test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol diff --git a/src/helpers/AddressBook.sol b/src/helpers/AddressBook.sol index 5f731cd..27c91cd 100644 --- a/src/helpers/AddressBook.sol +++ b/src/helpers/AddressBook.sol @@ -19,6 +19,7 @@ address constant RETH_MAINNET = 0xae78736Cd615f374D3085123A210448E74Fc6393; address constant ETHX_MAINNET = 0xA35b1B31Ce002FBF2058D22F30f95D405200A15b; address constant GHO_MAINNET = 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f; address constant COMP_MAINNET = 0xc00e94Cb662C3520282E6f5717214004A7f26888; +address constant _3CRV_MAINNET = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490; // Main protocols contracts address constant CONVEX_BOOSTER_MAINNET = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31; @@ -63,6 +64,10 @@ uint256 constant CONVEX_CRVUSD_WETH_COLLATERAL_POOL_ID_MAINNET = 326; uint256 constant CONVEX_DETH_FRXETH_CONVEX_POOL_ID_MAINNET = 195; address constant COMPOUND_USDT_V3_COMMET_MAINNET = 0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840; address constant COMPOUND_USDT_V3_REWARDS_MAINNET = 0x1B0e765F6224C21223AeA2af16c1C46E38885a40; +address constant CURVE_3_POOL_MAINNET = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; +address constant CURVE_THUSD_DAI_USDC_USDT_MAINNET = 0x91553BAD9Fbc8bD69Ff5d5678Cbf7D514d00De0b; +address constant BEEFY_THUSD_DAI_USDC_USDT_MAINNET = 0x3f5e39bf80798cB94846B48f1c635001a2E43066; + //////////////////////////////// POLYGON //////////////////////////////// // Tokens diff --git a/src/interfaces/ICurve.sol b/src/interfaces/ICurve.sol index 6113f75..eceed77 100644 --- a/src/interfaces/ICurve.sol +++ b/src/interfaces/ICurve.sol @@ -116,3 +116,8 @@ interface ICurveAtriCryptoZapper { function exchange_underlying(uint256 i, uint256 j, uint256 _dx, uint256 _min_dy, address _receiver) external; function get_dy_underlying(uint256 i, uint256 j, uint256 _dx) external view returns (uint256); } + +interface ICurve3pool { + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; + function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external; +} \ No newline at end of file diff --git a/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol new file mode 100644 index 0000000..873e1df --- /dev/null +++ b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.19; + +import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol"; +import { IBeefyVault } from "src/interfaces/IBeefyVault.sol"; +import { ICurveLpPool, ICurve3pool } from "src/interfaces/ICurve.sol"; +import { BaseBeefyStrategy, IMaxApyVault, SafeTransferLib } from "src/strategies/base/BaseBeefyStrategy.sol"; +import { _3CRV_MAINNET } from "src/helpers/AddressBook.sol"; + +/// @title BaseBeefyCurveMetaPoolStrategy +/// @author Adapted from https://github.com/Grandthrax/yearn-steth-acc/blob/master/contracts/strategies.sol +/// @notice `BaseBeefyCurveMetaPoolStrategy` supplies an underlying token into a generic Beefy Vault, +/// earning the Beefy Vault's yield +contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { + using SafeTransferLib for address; + + //////////////////////////////////////////////////////////////// + /// STRATEGY GLOBAL STATE VARIABLES /// + //////////////////////////////////////////////////////////////// + + /*==================CURVE-RELATED STORAGE VARIABLES==================*/ + /// @notice Curve Meta pool for this Strategy + ICurveLpPool public curveLpPool; + /// @notice Curve 3pool for this Strategy - DAI/USDC/USDT Pool + ICurve3pool public curve3pool; + + address _3crvToken = _3CRV_MAINNET; + + //////////////////////////////////////////////////////////////// + /// INITIALIZATION /// + //////////////////////////////////////////////////////////////// + constructor() initializer { } + + /// @notice Initialize the Strategy + /// @param _vault The address of the MaxApy Vault associated to the strategy + /// @param _keepers The addresses of the keepers to be added as valid keepers to the strategy + /// @param _strategyName the name of the strategy + /// @param _curveLpPool The address of the strategy's main Curve pool + /// @param _beefyVault The address of the strategy's Beefy vault + /// @param _curve3pool The address of the strategy's Curve 3pool + function initialize( + IMaxApyVault _vault, + address[] calldata _keepers, + bytes32 _strategyName, + address _strategist, + ICurveLpPool _curveLpPool, + IBeefyVault _beefyVault, + ICurve3pool _curve3pool + ) + public + virtual + initializer + { + super.initialize(_vault, _keepers, _strategyName, _strategist, _beefyVault); + + // Curve init + curveLpPool = _curveLpPool; + curve3pool = _curve3pool; + + underlyingAsset.safeApprove(address(curve3pool), type(uint256).max); + address(curve3pool).safeApprove(address(curveLpPool), type(uint256).max); + address(curveLpPool).safeApprove(address(beefyVault), type(uint256).max); + + /// min single trade by default + minSingleTrade = 10e6; + /// Unlimited max single trade by default + maxSingleTrade = 100_000e6; + } + + //////////////////////////////////////////////////////////////// + /// INTERNAL CORE FUNCTIONS /// + //////////////////////////////////////////////////////////////// + /// @notice Invests `amount` of underlying into the Beefy vault + /// @dev + /// @param amount The amount of underlying to be deposited in the pool + /// @param minOutputAfterInvestment minimum expected output after `_invest()` (designated in Curve LP tokens) + /// @return The amount of tokens received, in terms of underlying + function _invest(uint256 amount, uint256 minOutputAfterInvestment) internal virtual override returns (uint256) { + // Don't do anything if amount to invest is 0 + if (amount == 0) return 0; + + uint256 underlyingBalance = _underlyingBalance(); + + assembly ("memory-safe") { + if gt(amount, underlyingBalance) { + // throw the `NotEnoughFundsToInvest` error + mstore(0x00, 0xb2ff68ae) + revert(0x1c, 0x04) + } + } + + amount = Math.min(maxSingleTrade, amount); + + uint256[3] memory amountsUsdc; + amountsUsdc[1] = amount; + + uint256 _before = _3crvToken.balanceOf(address(this)); + // Add liquidity to the curve3pool in underlying token [coin1 -> usdc] + curve3pool.add_liquidity(amountsUsdc, 0); + uint256 _after = _3crvToken.balanceOf(address(this)); + + uint256 _3crvTokenReceived; + assembly ("memory-safe") { + _3crvTokenReceived := sub(_after, _before) + } + + uint256[2] memory amounts; + amounts[1] = _3crvTokenReceived; + // Add liquidity to the curve Metapool in 3crv token [coin1 -> 3crv] + uint256 lpReceived = curveLpPool.add_liquidity(amounts, 0, address(this)); + + _before = beefyVault.balanceOf(address(this)); + + // Deposit Curve LP tokens to Beefy vault + beefyVault.deposit(lpReceived); + + _after = beefyVault.balanceOf(address(this)); + uint256 shares; + + assembly ("memory-safe") { + shares := sub(_after, _before) + if lt(shares, minOutputAfterInvestment) { + // throw the `MinOutputAmountNotReached` error + mstore(0x00, 0xf7c67a48) + revert(0x1c, 0x04) + } + } + + emit Invested(address(this), amount); + + return shares; + } + + /// @dev care should be taken, as the `amount` parameter is not in terms of underlying, + /// but in terms of Beefy's moo tokens + /// Note that if minimum withdrawal amount is not reached, funds will not be divested, and this + /// will be accounted as a loss later. + /// @return amountDivested the total amount divested, in terms of underlying asset + function _divest(uint256 amount) internal virtual override returns (uint256 amountDivested) { + if (amount == 0) return 0; + + uint256 _before = beefyVault.want().balanceOf(address(this)); + + // Withdraw from Beefy and unwrap directly to Curve LP tokens + beefyVault.withdraw(amount); + + uint256 _after = beefyVault.want().balanceOf(address(this)); + + uint256 lptokens = _after - _before; + + // Remove liquidity and obtain usdce + uint256 _3crvTokenReceived = curveLpPool.remove_liquidity_one_coin( + lptokens, + 1, + //usdce + 0, + address(this) + ); + + _before = underlyingAsset.balanceOf(address(this)); + + curve3pool.remove_liquidity_one_coin( + lptokens, + 1, + //usdce + 0 + ); + + amountDivested = underlyingAsset.balanceOf(address(this)) - _before; + } + + //////////////////////////////////////////////////////////////// + /// INTERNAL VIEW FUNCTIONS /// + //////////////////////////////////////////////////////////////// + + /// @notice Determines the current value of `shares`. + /// @return _assets the estimated amount of underlying computed from shares `shares` + function _shareValue(uint256 shares) internal view virtual override returns (uint256 _assets) { + uint256 lpTokenAmount = super._shareValue(shares); + uint256 lpPrice = _lpPrice(); + + // lp price add get function _lpPrice() + assembly { + let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal + _assets := div(mul(lpTokenAmount, lpPrice), scale) + } + } + + /// @notice Determines how many shares depositor of `amount` of underlying would receive. + /// @return shares the estimated amount of shares computed in exchange for underlying `amount` + function _sharesForAmount(uint256 amount) internal view virtual override returns (uint256 shares) { + uint256 lpTokenAmount; + uint256 lpPrice = _lpPrice(); + assembly { + let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal + lpTokenAmount := div(mul(amount, scale), lpPrice) + } + shares = super._sharesForAmount(lpTokenAmount); + } + + /// @notice Returns the estimated price for the strategy's curve's LP token + /// @return returns the estimated lp token price + function _lpPrice() internal view returns (uint256) { + return ((curveLpPool.get_virtual_price() * curveLpPool.get_dy(0, 1, 1 ether)) / 1 ether); + } +} diff --git a/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol b/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol new file mode 100644 index 0000000..2b6e823 --- /dev/null +++ b/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.19; + +import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol"; + +// import { FRXETH_MAINNET } from "src/helpers/AddressBook.sol"; +// import { IBeefyVault } from "src/interfaces/IBeefyVault.sol"; +// import { ICurveLpPool } from "src/interfaces/ICurve.sol"; +// import { IWETH } from "src/interfaces/IWETH.sol"; +import { BaseBeefyCurveMetaPoolStrategy } from "src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol"; +import { BaseBeefyStrategy, IMaxApyVault, SafeTransferLib } from "src/strategies/base/BaseBeefyStrategy.sol"; + +// import { console2 } from "forge-std/console2.sol"; + +/// @title BeefythUSDDAIUSDCUSDTStrategy +/// @author Adapted from https://github.com/Grandthrax/yearn-steth-acc/blob/master/contracts/strategies.sol +/// @notice `BeefythUSDDAIUSDCUSDTStrategy` supplies an underlying token into a generic Beefy Vault, +/// earning the Beefy Vault's yield +contract BeefythUSDDAIUSDCUSDTStrategy is BaseBeefyCurveMetaPoolStrategy {} diff --git a/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol b/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol new file mode 100644 index 0000000..393bd07 --- /dev/null +++ b/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.19; + +import { BeefythUSDDAIUSDCUSDTStrategy, SafeTransferLib } from "src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol"; + +contract BeefythUSDDAIUSDCUSDTStrategyWrapper is BeefythUSDDAIUSDCUSDTStrategy { + using SafeTransferLib for address; + + function triggerLoss(uint256 amount) external { + underlyingAsset.safeTransfer(address(underlyingAsset), amount); + } + + function mockReport(uint128 gain, uint128 loss, uint128 debtPayment, address treasury) external { + vault.report(gain, loss, debtPayment, treasury); + } + + function prepareReturn( + uint256 debtOutstanding, + uint256 minExpectedBalance + ) + external + returns (uint256 unrealizedProfit, uint256 loss, uint256 debtPayment) + { + (unrealizedProfit, loss, debtPayment) = _prepareReturn(debtOutstanding, minExpectedBalance); + } + + function adjustPosition() external { + _adjustPosition(0, 0); + } + + function invest(uint256 amount, uint256 minOutputAfterInvestment) external returns (uint256) { + return _invest(amount, minOutputAfterInvestment); + } + + function divest(uint256 shares) external returns (uint256) { + return _divest(shares); + } + + function liquidatePosition(uint256 amountNeeded) external returns (uint256, uint256) { + return _liquidatePosition(amountNeeded); + } + + function liquidateAllPositions() external returns (uint256) { + return _liquidateAllPositions(); + } + + function shareValue(uint256 shares) external view returns (uint256) { + return _shareValue(shares); + } + + function sharesForAmount(uint256 amount) external view returns (uint256) { + return _sharesForAmount(amount); + } + + function shareBalance() external view returns (uint256) { + return _shareBalance(); + } + + function lpPrice() external view returns (uint256) { + return _lpPrice(); + } +} diff --git a/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol new file mode 100644 index 0000000..a91f1fa --- /dev/null +++ b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol @@ -0,0 +1,638 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.19; + +import { ProxyAdmin } from "openzeppelin/proxy/transparent/ProxyAdmin.sol"; +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import { BaseTest, IERC20, Vm, console2 } from "../../base/BaseTest.t.sol"; + +import { IStrategyWrapper } from "../../interfaces/IStrategyWrapper.sol"; +import { ICurveLpPool } from "src/interfaces/ICurve.sol"; +import { IMaxApyVault } from "src/interfaces/IMaxApyVault.sol"; + +import { ConvexdETHFrxETHStrategyEvents } from "../../helpers/ConvexdETHFrxETHStrategyEvents.sol"; + +import { BeefythUSDDAIUSDCUSDTStrategyWrapper } from "../../mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol"; + +import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol"; +import { MaxApyVault } from "src/MaxApyVault.sol"; +import "src/helpers/AddressBook.sol"; +import { StrategyData } from "src/helpers/VaultTypes.sol"; +import { _1_USDC } from "test/helpers/Tokens.sol"; + +contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategyEvents { + using SafeTransferLib for address; + + address public TREASURY; + IStrategyWrapper public strategy; + BeefythUSDDAIUSDCUSDTStrategyWrapper public implementation; + MaxApyVault public vaultDeployment; + IMaxApyVault public vault; + ITransparentUpgradeableProxy public proxy; + ProxyAdmin public proxyAdmin; + + function setUp() public { + super._setUp("MAINNET"); + vm.rollFork(21270037); + + TREASURY = makeAddr("treasury"); + + vaultDeployment = new MaxApyVault(users.alice, USDC_MAINNET, "MaxApyUSDCVault", "maxUSDC", TREASURY); + + vault = IMaxApyVault(address(vaultDeployment)); + + proxyAdmin = new ProxyAdmin(users.alice); + implementation = new BeefythUSDDAIUSDCUSDTStrategyWrapper(); + + address[] memory keepers = new address[](1); + keepers[0] = users.keeper; + TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( + address(implementation), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address[],bytes32,address,address,address)", + address(vault), + keepers, + bytes32("MaxApy thUSDDAIUSDCUSDT Strategy"), + users.alice, + CURVE_THUSD_DAI_USDC_USDT_MAINNET, + BEEFY_THUSD_DAI_USDC_USDT_MAINNET + ) + ); + proxy = ITransparentUpgradeableProxy(address(_proxy)); + + strategy = IStrategyWrapper(address(_proxy)); + USDC_MAINNET.safeApprove(address(vault), type(uint256).max); + + vm.label(USDC_MAINNET, "USDC_MAINNET"); + } + + /*==================INITIALIZATION TESTS==================*/ + + function testBeefythUSDDAIUSDCUSDT__Initialization() public { + MaxApyVault _vault = new MaxApyVault(users.alice, USDC_MAINNET, "MaxApyUSDCVault", "maxUSDC", TREASURY); + + ProxyAdmin _proxyAdmin = new ProxyAdmin(users.alice); + BeefythUSDDAIUSDCUSDTStrategyWrapper _implementation = new BeefythUSDDAIUSDCUSDTStrategyWrapper(); + + address[] memory keepers = new address[](1); + keepers[0] = users.keeper; + + TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( + address(_implementation), + address(_proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address[],bytes32,address,address,address)", + address(_vault), + keepers, + bytes32("MaxApy thUSDDAIUSDCUSDT Strategy"), + users.alice, + CURVE_THUSD_DAI_USDC_USDT_MAINNET, + BEEFY_THUSD_DAI_USDC_USDT_MAINNET + ) + ); + + IStrategyWrapper _strategy = IStrategyWrapper(address(_proxy)); + assertEq(_strategy.vault(), address(_vault)); + + assertEq(_strategy.hasAnyRole(address(_vault), _strategy.VAULT_ROLE()), true); + assertEq(_strategy.underlyingAsset(), USDC_MAINNET); + assertEq(IERC20(USDC_MAINNET).allowance(address(_strategy), address(_vault)), type(uint256).max); + assertEq(_strategy.hasAnyRole(users.keeper, _strategy.KEEPER_ROLE()), true); + assertEq(_strategy.hasAnyRole(users.alice, _strategy.ADMIN_ROLE()), true); + + assertEq(_strategy.owner(), users.alice); + assertEq(_strategy.strategyName(), bytes32("MaxApy thUSDDAIUSDCUSDT Strategy")); + + assertEq(_strategy.curveLpPool(), CURVE_THUSD_DAI_USDC_USDT_MAINNET, "hereee"); + assertEq(IERC20(USDC_MAINNET).allowance(address(_strategy), CURVE_THUSD_DAI_USDC_USDT_MAINNET), type(uint256).max); + + assertEq(_proxyAdmin.owner(), users.alice); + vm.startPrank(address(_proxyAdmin)); + vm.stopPrank(); + + vm.startPrank(users.alice); + } + + /*==================STRATEGY CONFIGURATION TESTS==================*/ + + function testBeefythUSDDAIUSDCUSDT__SetEmergencyExit() public { + vm.stopPrank(); + vm.startPrank(users.bob); + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.setEmergencyExit(2); + vm.stopPrank(); + vm.startPrank(address(vault)); + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.setEmergencyExit(2); + + vm.stopPrank(); + vm.startPrank(users.alice); + vm.expectEmit(); + emit StrategyEmergencyExitUpdated(address(strategy), 2); + strategy.setEmergencyExit(2); + } + + function testBeefythUSDDAIUSDCUSDT__SetMinSingleTrade() public { + vm.stopPrank(); + vm.startPrank(users.bob); + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.setMinSingleTrade(1 * _1_USDC); + + vm.stopPrank(); + vm.startPrank(address(vault)); + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.setMinSingleTrade(1 * _1_USDC); + + vm.stopPrank(); + vm.startPrank(users.alice); + vm.expectEmit(); + emit MinSingleTradeUpdated(1 * _1_USDC); + strategy.setMinSingleTrade(1 * _1_USDC); + assertEq(strategy.minSingleTrade(), 1 * _1_USDC); + } + + function testBeefythUSDDAIUSDCUSDT__IsActive() public { + vault.addStrategy(address(strategy), 10_000, 0, 0, 0); + assertEq(strategy.isActive(), false); + + deal(USDC_MAINNET, address(strategy), 1 * _1_USDC); + assertEq(strategy.isActive(), false); + + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + assertEq(strategy.isActive(), true); + vm.stopPrank(); + + strategy.divest(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy))); + vm.startPrank(address(strategy)); + IERC20(USDC_MAINNET).transfer(makeAddr("random"), IERC20(USDC_MAINNET).balanceOf(address(strategy))); + assertEq(strategy.isActive(), false); + + deal(USDC_MAINNET, address(strategy), 1 * _1_USDC); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + assertEq(strategy.isActive(), true); + } + + function testBeefythUSDDAIUSDCUSDT__SetStrategist() public { + // Negatives + vm.startPrank(users.bob); + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.setStrategist(address(0)); + + vm.startPrank(users.alice); + vm.expectRevert(abi.encodeWithSignature("InvalidZeroAddress()")); + strategy.setStrategist(address(0)); + + // Positives + address random = makeAddr("random"); + vm.expectEmit(); + emit StrategistUpdated(address(strategy), random); + strategy.setStrategist(random); + assertEq(strategy.strategist(), random); + } + + /*==================STRATEGY CORE LOGIC TESTS==================*/ + // function testBeefythUSDDAIUSDCUSDT__InvestmentSlippage() public { + // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + // vault.deposit(100 * _1_USDC, users.alice); + + // vm.startPrank(users.keeper); + + // // Expect revert if output amount is gt amount obtained + // vm.expectRevert(abi.encodeWithSignature("MinOutputAmountNotReached()")); + // strategy.harvest(0, type(uint256).max, address(0), block.timestamp); + // } + + function testBeefythUSDDAIUSDCUSDT__PrepareReturn() public { + uint256 snapshotId = vm.snapshot(); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + strategy.mockReport(0, 0, 0, TREASURY); + + (uint256 unrealizedProfit, uint256 loss, uint256 debtPayment) = strategy.prepareReturn(1 * _1_USDC, 0); + + assertEq(loss, 0); + assertEq(debtPayment, 1 * _1_USDC); + + vm.revertTo(snapshotId); + + snapshotId = vm.snapshot(); + deal({ token: USDC_MAINNET, to: address(strategy), give: 60 * _1_USDC }); + + strategy.adjustPosition(); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + (unrealizedProfit, loss, debtPayment) = strategy.prepareReturn(0, 0); + + assertApproxEq(unrealizedProfit, 60 * _1_USDC, 1 * _1_USDC); + assertEq(loss, 0); + assertEq(debtPayment, 0); + + vm.revertTo(snapshotId); + + snapshotId = vm.snapshot(); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + strategy.mockReport(0, 0, 0, TREASURY); + + strategy.triggerLoss(10 * _1_USDC); + + (unrealizedProfit, loss, debtPayment) = strategy.prepareReturn(0, 0); + + assertEq(unrealizedProfit, 0); + assertEq(loss, 10 * _1_USDC); + assertEq(debtPayment, 0); + + vm.revertTo(snapshotId); + + snapshotId = vm.snapshot(); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 80 * _1_USDC }); + + strategy.adjustPosition(); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + strategy.mockReport(0, 0, 0, TREASURY); + + (unrealizedProfit, loss, debtPayment) = strategy.prepareReturn(0, 0); + + assertEq(loss, 0); + assertEq(debtPayment, 0); + } + + function testBeefythUSDDAIUSDCUSDT__Invest() public { + // uint256 returned = strategy.invest(0, 0); + // assertEq(returned, 0); + // assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); + + // vm.expectRevert(abi.encodeWithSignature("NotEnoughFundsToInvest()")); + // returned = strategy.invest(1, 0); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + // uint256 expectedShares = strategy.sharesForAmount(10 * _1_USDC); + + + // vm.expectEmit(); + // emit Invested(address(strategy), 10 * _1_USDC); + strategy.invest(10 * _1_USDC, 0); + + // assertApproxEq( + // expectedShares, IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), expectedShares / 10 + // ); + } + + function testBeefythUSDDAIUSDCUSDT__Divest() public { + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + uint256 expectedShares = strategy.sharesForAmount(10 * _1_USDC); + + vm.expectEmit(); + emit Invested(address(strategy), 10 * _1_USDC); + strategy.invest(10 * _1_USDC, 0); + + assertApproxEq( + expectedShares, IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), expectedShares / 10 + ); + + uint256 strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); + uint256 amountDivested = strategy.divest(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy))); + + assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountDivested); + } + + function testBeefythUSDDAIUSDCUSDT__LiquidatePosition() public { + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + (uint256 liquidatedAmount, uint256 loss) = strategy.liquidatePosition(1 * _1_USDC); + assertEq(liquidatedAmount, 1 * _1_USDC); + assertEq(loss, 0); + + (liquidatedAmount, loss) = strategy.liquidatePosition(10 * _1_USDC); + assertEq(liquidatedAmount, 10 * _1_USDC); + assertEq(loss, 0); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 5 * _1_USDC }); + uint256 invested = strategy.invest(5 * _1_USDC, 0); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + + (liquidatedAmount, loss) = strategy.liquidatePosition(149 * _1_USDC / 10); + + assertEq(liquidatedAmount, 149 * _1_USDC / 10); + assertLt(loss, 1 * _1_USDC / 5); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 50 * _1_USDC }); + invested = strategy.invest(50 * _1_USDC, 0); + + (liquidatedAmount, loss) = strategy.liquidatePosition(495 * _1_USDC / 10); + + assertEq(liquidatedAmount, 495 * _1_USDC / 10); + assertLt(loss, 1 * _1_USDC / 5); + } + + function testBeefythUSDDAIUSDCUSDT__LiquidateAllPositions() public { + uint256 snapshotId = vm.snapshot(); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + uint256 shares = strategy.sharesForAmount(10 * _1_USDC); + vm.expectEmit(); + emit Invested(address(strategy), 10 * _1_USDC); + strategy.invest(10 * _1_USDC, 0); + + assertApproxEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), shares, shares / 10); + + uint256 strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); + uint256 amountFreed = strategy.liquidateAllPositions(); + + assertApproxEq(amountFreed, 10 * _1_USDC, 3 * _1_USDC / 100); + + assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountFreed); + assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); + + vm.revertTo(snapshotId); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 100 * _1_USDC }); + shares = strategy.sharesForAmount(100 * _1_USDC); + + vm.expectEmit(); + emit Invested(address(strategy), 100 * _1_USDC); + strategy.invest(100 * _1_USDC, 0); + + assertApproxEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), shares, 1 * _1_USDC); + + strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); + amountFreed = strategy.liquidateAllPositions(); + + assertApproxEq(amountFreed, 100 * _1_USDC, 2 * _1_USDC); + + assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountFreed); + assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); + } + + function testBeefythUSDDAIUSDCUSDT__Harvest() public { + vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); + strategy.harvest(0, 0, address(0), block.timestamp); + + uint256 snapshotId = vm.snapshot(); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + vm.startPrank(users.keeper); + + vm.expectEmit(); + emit StrategyReported(address(strategy), 0, 0, 0, 0, 0, uint128(40 * _1_USDC), uint128(40 * _1_USDC), 4000); + + vm.expectEmit(); + emit Harvested(0, 0, 0, 0); + + strategy.harvest(0, 0, address(0), block.timestamp); + + uint256 expectedStrategyShareBalance = strategy.sharesForAmount(40 * _1_USDC); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 60 * _1_USDC); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), 0); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + vm.warp(block.timestamp + 1 days); + + strategy.harvest(0, 0, address(0), block.timestamp); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 60 * _1_USDC); + + vm.revertTo(snapshotId); + snapshotId = vm.snapshot(); + + vm.startPrank(users.alice); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + vault.deposit(100 * _1_USDC, users.alice); + + vm.startPrank(users.keeper); + + vm.expectEmit(); + emit StrategyReported(address(strategy), 0, 0, 0, 0, 0, uint128(40 * _1_USDC), uint128(40 * _1_USDC), 4000); + + vm.expectEmit(); + emit Harvested(0, 0, 0, 0); + + strategy.harvest(0, 0, address(0), block.timestamp); + + expectedStrategyShareBalance = strategy.sharesForAmount(40 * _1_USDC); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 60 * _1_USDC); + + vm.startPrank(users.alice); + strategy.setEmergencyExit(2); + + vm.startPrank(users.keeper); + + deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); + vm.warp(block.timestamp + 1 days); + + strategy.harvest(0, 0, address(0), block.timestamp); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 109956498446582789159); + assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); + vm.revertTo(snapshotId); + + vm.startPrank(users.alice); + + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + + expectedStrategyShareBalance = strategy.sharesForAmount(40 * _1_USDC); + vault.deposit(100 * _1_USDC, users.alice); + + vm.startPrank(users.keeper); + + vm.expectEmit(); + emit StrategyReported(address(strategy), 0, 0, 0, 0, 0, uint128(40 * _1_USDC), uint128(40 * _1_USDC), 4000); + + vm.expectEmit(); + emit Harvested(0, 0, 0, 0); + strategy.harvest(0, 0, address(0), block.timestamp); + + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 60 * _1_USDC); + + expectedStrategyShareBalance = strategy.sharesForAmount(10 * _1_USDC); + + vm.startPrank(address(strategy)); + uint256 withdrawn = strategy.divest(expectedStrategyShareBalance); + + IERC20(USDC_MAINNET).transfer(makeAddr("random"), withdrawn); + vm.startPrank(users.keeper); + + strategy.harvest(0, 0, address(0), block.timestamp); + + StrategyData memory data = vault.strategies(address(strategy)); + + assertEq(vault.debtRatio(), 2994); + assertEq(data.strategyDebtRatio, 2994); + } + + function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate() public { + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + vault.deposit(100 * _1_USDC, users.alice); + vm.startPrank(users.keeper); + + strategy.harvest(0, 0, address(0), block.timestamp); + + vm.stopPrank(); + uint256 expected = strategy.previewLiquidate(30 * _1_USDC); + vm.startPrank(address(vault)); + + uint256 loss = strategy.liquidate(30 * _1_USDC); + + assertLe(expected, 30 * _1_USDC - loss); + } + + function testBeefythUSDDAIUSDCUSDT__PreviewLiquidateExact() public { + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + vault.deposit(100 * _1_USDC, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 requestedAmount = strategy.previewLiquidateExact(30 * _1_USDC); + + vm.startPrank(address(vault)); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + + strategy.liquidateExact(30 * _1_USDC); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + + // withdraw exactly what requested + assertEq(withdrawn, 30 * _1_USDC); + // losses are equal or fewer than expected + assertLe(withdrawn - 30 * _1_USDC, requestedAmount - 30 * _1_USDC); + } + + function testBeefythUSDDAIUSDCUSDT__maxLiquidateExact() public { + vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + vault.deposit(100 * _1_USDC, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 maxLiquidateExact = strategy.maxLiquidateExact(); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + uint256 requestedAmount = strategy.previewLiquidateExact(maxLiquidateExact); + vm.startPrank(address(vault)); + uint256 losses = strategy.liquidateExact(maxLiquidateExact); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + // withdraw exactly what requested + assertEq(withdrawn, maxLiquidateExact); + // losses are equal or fewer than expected + assertLe(losses, requestedAmount - maxLiquidateExact); + } + + function testBeefythUSDDAIUSDCUSDT__MaxLiquidate() public { + vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + vault.deposit(100 * _1_USDC, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 maxWithdraw = strategy.maxLiquidate(); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + vm.startPrank(address(vault)); + strategy.liquidate(maxWithdraw); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + assertLe(withdrawn, maxWithdraw); + } + + function testBeefythUSDDAIUSDCUSDT___SimulateHarvest() public { + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + vault.deposit(100 * _1_USDC, users.alice); + vm.startPrank(users.keeper); + (uint256 expectedBalance, uint256 outputAfterInvestment,,,,) = strategy.simulateHarvest(); + strategy.harvest(expectedBalance, outputAfterInvestment, address(0), block.timestamp); + } + + // function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate__FUZZY(uint256 amount) public { + // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + // deal(USDC_MAINNET, users.alice, amount); + // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + // vault.deposit(amount, users.alice); + // vm.startPrank(users.keeper); + + // strategy.harvest(0, 0, address(0), block.timestamp); + + // vm.stopPrank(); + // uint256 expected = strategy.previewLiquidate(amount / 3); + // vm.startPrank(address(vault)); + + // uint256 loss = strategy.liquidate(amount / 3); + + // assertLe(expected, amount / 3 - loss); + // } + + // function testBeefythUSDDAIUSDCUSDT__PreviewLiquidateExact__FUZZY(uint256 amount) public { + // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + // deal(USDC_MAINNET, users.alice, amount); + // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + // vault.deposit(amount, users.alice); + // vm.startPrank(users.keeper); + // strategy.harvest(0, 0, address(0), block.timestamp); + // vm.stopPrank(); + // uint256 requestedAmount = strategy.previewLiquidateExact(amount / 3); + + // vm.startPrank(address(vault)); + // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + + // strategy.liquidateExact(amount / 3); + // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + + // // withdraw exactly what requested + // assertGe(withdrawn, amount / 3); + // // losses are equal or fewer than expected + // assertLe(withdrawn - (amount / 3), requestedAmount - (amount / 3)); + // } + + // function testBeefythUSDDAIUSDCUSDT__maxLiquidateExact_FUZZY(uint256 amount) public { + // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + // deal(USDC_MAINNET, users.alice, amount); + // vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + // vault.deposit(amount, users.alice); + // vm.startPrank(users.keeper); + // strategy.harvest(0, 0, address(0), block.timestamp); + // vm.stopPrank(); + // uint256 maxLiquidateExact = strategy.maxLiquidateExact(); + // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + // uint256 requestedAmount = strategy.previewLiquidateExact(maxLiquidateExact); + // vm.startPrank(address(vault)); + // uint256 losses = strategy.liquidateExact(maxLiquidateExact); + // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + // // withdraw exactly what requested + // assertGe(withdrawn, maxLiquidateExact); + // // losses are equal or fewer than expected + // assertLe(losses, requestedAmount - maxLiquidateExact); + // } + + // function testBeefythUSDDAIUSDCUSDT__MaxLiquidate_FUZZY(uint256 amount) public { + // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + // deal(USDC_MAINNET, users.alice, amount); + // vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + // vault.deposit(amount, users.alice); + // vm.startPrank(users.keeper); + // strategy.harvest(0, 0, address(0), block.timestamp); + // vm.stopPrank(); + // uint256 maxWithdraw = strategy.maxLiquidate(); + // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + // vm.startPrank(address(vault)); + // strategy.liquidate(maxWithdraw); + // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + // assertLe(withdrawn, maxWithdraw); + // } +} From f42b5907de6ef58c7c956745f0b972a2f1439c81 Mon Sep 17 00:00:00 2001 From: Shubhangi Date: Tue, 10 Dec 2024 12:44:04 +0530 Subject: [PATCH 2/3] Beefu curve metapool strategy implementation --- src/helpers/AddressBook.sol | 3 +- src/interfaces/ICurve.sol | 16 +- .../base/BaseBeefyCurveMetaPoolStrategy.sol | 137 ++++++++--- ...xApyTestMainnetUSDC.integration.fuzz.t.sol | 41 +++- test/interfaces/IStrategyWrapper.sol | 2 + .../BeefythUSDDAIUSDCUSDTStrategy.t.sol | 228 +++++++++--------- 6 files changed, 277 insertions(+), 150 deletions(-) diff --git a/src/helpers/AddressBook.sol b/src/helpers/AddressBook.sol index 27c91cd..cbc9ddf 100644 --- a/src/helpers/AddressBook.sol +++ b/src/helpers/AddressBook.sol @@ -19,7 +19,7 @@ address constant RETH_MAINNET = 0xae78736Cd615f374D3085123A210448E74Fc6393; address constant ETHX_MAINNET = 0xA35b1B31Ce002FBF2058D22F30f95D405200A15b; address constant GHO_MAINNET = 0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f; address constant COMP_MAINNET = 0xc00e94Cb662C3520282E6f5717214004A7f26888; -address constant _3CRV_MAINNET = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490; +address constant CRV3POOL_MAINNET = 0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490; // Main protocols contracts address constant CONVEX_BOOSTER_MAINNET = 0xF403C135812408BFbE8713b5A23a04b3D48AAE31; @@ -64,7 +64,6 @@ uint256 constant CONVEX_CRVUSD_WETH_COLLATERAL_POOL_ID_MAINNET = 326; uint256 constant CONVEX_DETH_FRXETH_CONVEX_POOL_ID_MAINNET = 195; address constant COMPOUND_USDT_V3_COMMET_MAINNET = 0x3Afdc9BCA9213A35503b077a6072F3D0d5AB0840; address constant COMPOUND_USDT_V3_REWARDS_MAINNET = 0x1B0e765F6224C21223AeA2af16c1C46E38885a40; -address constant CURVE_3_POOL_MAINNET = 0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7; address constant CURVE_THUSD_DAI_USDC_USDT_MAINNET = 0x91553BAD9Fbc8bD69Ff5d5678Cbf7D514d00De0b; address constant BEEFY_THUSD_DAI_USDC_USDT_MAINNET = 0x3f5e39bf80798cB94846B48f1c635001a2E43066; diff --git a/src/interfaces/ICurve.sol b/src/interfaces/ICurve.sol index eceed77..e8f2537 100644 --- a/src/interfaces/ICurve.sol +++ b/src/interfaces/ICurve.sol @@ -99,6 +99,17 @@ interface ICurveTriPool { function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256); function exchange(int128 i, int128 j, uint256 _dx, uint256 _min_dy) external; + + function get_virtual_price() external view returns (uint256); + + function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; + + function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external; + + function calc_token_amount(uint256[3] memory amounts, bool deposit) external view returns (uint256); + + function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); + } interface ICurveAtriCryptoZapper { @@ -115,9 +126,4 @@ interface ICurveAtriCryptoZapper { function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256); function exchange_underlying(uint256 i, uint256 j, uint256 _dx, uint256 _min_dy, address _receiver) external; function get_dy_underlying(uint256 i, uint256 j, uint256 _dx) external view returns (uint256); -} - -interface ICurve3pool { - function add_liquidity(uint256[3] calldata amounts, uint256 min_mint_amount) external; - function remove_liquidity_one_coin(uint256 token_amount, int128 i, uint256 min_amount) external; } \ No newline at end of file diff --git a/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol index 873e1df..d742b1e 100644 --- a/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol +++ b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol @@ -3,9 +3,12 @@ pragma solidity ^0.8.19; import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol"; import { IBeefyVault } from "src/interfaces/IBeefyVault.sol"; -import { ICurveLpPool, ICurve3pool } from "src/interfaces/ICurve.sol"; +import { ICurveLpPool, ICurveTriPool } from "src/interfaces/ICurve.sol"; import { BaseBeefyStrategy, IMaxApyVault, SafeTransferLib } from "src/strategies/base/BaseBeefyStrategy.sol"; -import { _3CRV_MAINNET } from "src/helpers/AddressBook.sol"; +import { CRV3POOL_MAINNET } from "src/helpers/AddressBook.sol"; +import {ERC20} from "solady/tokens/ERC20.sol"; + +import {console2} from "forge-std/console2.sol"; /// @title BaseBeefyCurveMetaPoolStrategy /// @author Adapted from https://github.com/Grandthrax/yearn-steth-acc/blob/master/contracts/strategies.sol @@ -22,9 +25,9 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { /// @notice Curve Meta pool for this Strategy ICurveLpPool public curveLpPool; /// @notice Curve 3pool for this Strategy - DAI/USDC/USDT Pool - ICurve3pool public curve3pool; + ICurveTriPool public curveTriPool; - address _3crvToken = _3CRV_MAINNET; + address public crvTriPoolToken; //////////////////////////////////////////////////////////////// /// INITIALIZATION /// @@ -37,7 +40,8 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { /// @param _strategyName the name of the strategy /// @param _curveLpPool The address of the strategy's main Curve pool /// @param _beefyVault The address of the strategy's Beefy vault - /// @param _curve3pool The address of the strategy's Curve 3pool + /// @param _curveTriPool The address of the strategy's Curve Tripool + /// @param _crvTriPoolToken The address of the Curve Tripool's token function initialize( IMaxApyVault _vault, address[] calldata _keepers, @@ -45,7 +49,8 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { address _strategist, ICurveLpPool _curveLpPool, IBeefyVault _beefyVault, - ICurve3pool _curve3pool + ICurveTriPool _curveTriPool, + address _crvTriPoolToken ) public virtual @@ -55,17 +60,32 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { // Curve init curveLpPool = _curveLpPool; - curve3pool = _curve3pool; + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:60 ~ _curveLpPool:"); - underlyingAsset.safeApprove(address(curve3pool), type(uint256).max); - address(curve3pool).safeApprove(address(curveLpPool), type(uint256).max); - address(curveLpPool).safeApprove(address(beefyVault), type(uint256).max); + curveTriPool = _curveTriPool; + crvTriPoolToken = _crvTriPoolToken; + + console2.log("### 2"); + underlyingAsset.safeApprove(address(curveTriPool), type(uint256).max); + console2.log("### 3"); + crvTriPoolToken.safeApprove(address(curveLpPool), type(uint256).max); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:85 ~ address(crvTriPoolToken):", crvTriPoolToken); + console2.log("### 4"); + address(curveLpPool).safeApprove(address(beefyVault), type(uint256).max); + console2.log("### 5"); /// min single trade by default minSingleTrade = 10e6; + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:74 ~ minSingleTrade:", minSingleTrade); + /// Unlimited max single trade by default maxSingleTrade = 100_000e6; - } + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:78 ~ maxSingleTrade:", maxSingleTrade); + + } + + + //////////////////////////////////////////////////////////////// /// INTERNAL CORE FUNCTIONS /// @@ -94,10 +114,16 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { uint256[3] memory amountsUsdc; amountsUsdc[1] = amount; - uint256 _before = _3crvToken.balanceOf(address(this)); - // Add liquidity to the curve3pool in underlying token [coin1 -> usdc] - curve3pool.add_liquidity(amountsUsdc, 0); - uint256 _after = _3crvToken.balanceOf(address(this)); + uint256 _before = ERC20(crvTriPoolToken).balanceOf(address(this)); + // uint256 _before = ERC20(_3crvToken).balanceOf(address(this)); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:111 ~ _invest ~ _before:", _before); + + // Add liquidity to the curveTriPool in underlying token [coin1 -> usdc] + curveTriPool.add_liquidity(amountsUsdc, 0); + uint256 _after = ERC20(crvTriPoolToken).balanceOf(address(this)); + // uint256 _after = ERC20(_3crvToken).balanceOf(address(this)); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:116 ~ _invest ~ _after:", _after); + uint256 _3crvTokenReceived; assembly ("memory-safe") { @@ -108,6 +134,8 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { amounts[1] = _3crvTokenReceived; // Add liquidity to the curve Metapool in 3crv token [coin1 -> 3crv] uint256 lpReceived = curveLpPool.add_liquidity(amounts, 0, address(this)); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:137 ~ _invest ~ lpReceived:", lpReceived); + _before = beefyVault.balanceOf(address(this)); @@ -126,10 +154,14 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { } } + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:161 ~ _invest ~ shares:", shares); + emit Invested(address(this), amount); return shares; } + + /// @dev care should be taken, as the `amount` parameter is not in terms of underlying, /// but in terms of Beefy's moo tokens @@ -156,17 +188,21 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { 0, address(this) ); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:185 ~ _divest ~ _3crvTokenReceived:", _3crvTokenReceived); + _before = underlyingAsset.balanceOf(address(this)); - curve3pool.remove_liquidity_one_coin( - lptokens, + curveTriPool.remove_liquidity_one_coin( + _3crvTokenReceived, 1, //usdce 0 ); amountDivested = underlyingAsset.balanceOf(address(this)) - _before; + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:198 ~ _divest ~ amountDivested:", amountDivested); + } //////////////////////////////////////////////////////////////// @@ -176,26 +212,62 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { /// @notice Determines the current value of `shares`. /// @return _assets the estimated amount of underlying computed from shares `shares` function _shareValue(uint256 shares) internal view virtual override returns (uint256 _assets) { - uint256 lpTokenAmount = super._shareValue(shares); - uint256 lpPrice = _lpPrice(); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:218 ~ _shareValue ~ shares:", shares); + + uint256 expectedCurveLp = shares * beefyVault.balance() / beefyVault.totalSupply(); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:219 ~ _shareValue ~ expectedCurveLp:", expectedCurveLp); - // lp price add get function _lpPrice() - assembly { - let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal - _assets := div(mul(lpTokenAmount, lpPrice), scale) + if (expectedCurveLp > 0) { + uint256 expected3Crv = curveLpPool.calc_withdraw_one_coin(expectedCurveLp, 1); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:222 ~ _shareValue ~ expected3Crv:", expected3Crv); + if (expected3Crv > 0) { + _assets = curveTriPool.calc_withdraw_one_coin(expected3Crv, 1); + } } + + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:227 ~ _shareValue ~ _assets:", _assets); + + // uint256 lpTokenAmount = super._shareValue(shares); + // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:216 ~ _shareValue ~ lpTokenAmount:", lpTokenAmount); + + // uint256 lpPrice = _lpPrice(); + // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:219 ~ _shareValue ~ lpPrice:", lpPrice); + + + // uint256 lpTriPoolPrice =_lpTriPoolPrice(); + // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:223 ~ _shareValue ~ lpTriPoolPrice:", lpTriPoolPrice); + + + // // lp price add get function _lpPrice() + // assembly { + // let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal + // _assets := div(mul(lpTokenAmount, lpPrice), scale) + // _assets := div(mul(_assets, lpTriPoolPrice), scale) + + // } } /// @notice Determines how many shares depositor of `amount` of underlying would receive. /// @return shares the estimated amount of shares computed in exchange for underlying `amount` function _sharesForAmount(uint256 amount) internal view virtual override returns (uint256 shares) { - uint256 lpTokenAmount; - uint256 lpPrice = _lpPrice(); - assembly { - let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal - lpTokenAmount := div(mul(amount, scale), lpPrice) - } + + uint256[3] memory amounts; + amounts[1] = amount; + + + uint256 lpTokenAmount = curveTriPool.calc_token_amount(amounts, true); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:228 ~ _sharesForAmount ~ lpTokenAmount:", lpTokenAmount); + + + uint256[2] memory _amounts; + _amounts[1] = lpTokenAmount; + + lpTokenAmount = curveLpPool.calc_token_amount(_amounts, true); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:235 ~ _sharesForAmount ~ lpTokenAmount:", lpTokenAmount); + shares = super._sharesForAmount(lpTokenAmount); + console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:245 ~ _sharesForAmount ~ shares:", shares); + } /// @notice Returns the estimated price for the strategy's curve's LP token @@ -203,4 +275,11 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { function _lpPrice() internal view returns (uint256) { return ((curveLpPool.get_virtual_price() * curveLpPool.get_dy(0, 1, 1 ether)) / 1 ether); } + + /// @notice Returns the estimated price for the strategy's curve's Tri pool LP token + /// @return returns the estimated lp token price + function _lpTriPoolPrice() internal view returns (uint256) { + return ((curveTriPool.get_virtual_price() + * Math.min(curveTriPool.get_dy(0, 1, 1 ether), curveTriPool.get_dy(1,0,1e6))) / 1 ether); + } } diff --git a/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol b/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol index 0390d75..62fcb48 100644 --- a/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol +++ b/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol @@ -22,6 +22,9 @@ import { StrategyEvents } from "test/helpers/StrategyEvents.sol"; // Compound v3 import { CompoundV3USDTStrategyWrapper } from "../mock/CompoundV3USDTStrategyWrapper.sol"; +// Beefy +import { BeefythUSDDAIUSDCUSDTStrategyWrapper } from "../mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol"; + // Vault fuzzer import { MaxApyVaultFuzzer } from "./fuzzers/MaxApyVaultFuzzer.t.sol"; import { StrategyFuzzer } from "./fuzzers/StrategyFuzzer.t.sol"; @@ -52,6 +55,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { //////////////////////////////////////////////////////////////// ICompoundV3StrategyWrapper public strategy1; // yearn weth + IStrategyWrapper public strategy2; IMaxApyVault public vault; ITransparentUpgradeableProxy public proxy; @@ -77,6 +81,11 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { address[] memory keepers = new address[](1); keepers[0] = users.keeper; + ///////////////////////////////////////////////////////////////////////// + /// STRATEGIES /// + ///////////////////////////////////////////////////////////////////////// + /// Deploy transparent upgradeable proxy admin + // Deploy strategy1 CompoundV3USDTStrategyWrapper implementation1 = new CompoundV3USDTStrategyWrapper(); TransparentUpgradeableProxy _proxy = new TransparentUpgradeableProxy( @@ -94,18 +103,37 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { UNISWAP_V3_ROUTER_MAINNET ) ); - proxy = ITransparentUpgradeableProxy(address(_proxy)); vm.label(COMPOUND_USDT_V3_COMMET_MAINNET, "CompoundV3USDT"); - vm.label(address(proxy), "CompoundV3USDTStrategy"); - strategy1 = ICompoundV3StrategyWrapper(address(_proxy)); - address[] memory strategyList = new address[](1); + // Deploy strategy2 + BeefythUSDDAIUSDCUSDTStrategyWrapper implementation2 = new BeefythUSDDAIUSDCUSDTStrategyWrapper(); + _proxy = new TransparentUpgradeableProxy( + address(implementation2), + address(proxyAdmin), + abi.encodeWithSignature( + "initialize(address,address[],bytes32,address,address,address,address,address)", + address(vault), + keepers, + bytes32("MaxApy thUSDDAIUSDCUSDT Strategy"), + users.alice, + CURVE_THUSD_DAI_USDC_USDT_MAINNET, + BEEFY_THUSD_DAI_USDC_USDT_MAINNET, + CURVE_3POOL_POOL_MAINNET, + CRV3POOL_MAINNET + ) + ); + vm.label(BEEFY_THUSD_DAI_USDC_USDT_MAINNET, "BeefythUSDTDAIUSDCUSDT"); + strategy2 = IStrategyWrapper(address(_proxy)); + + address[] memory strategyList = new address[](2); strategyList[0] = address(strategy1); + strategyList[1] = address(strategy2); // Add all the strategies vault.addStrategy(address(strategy1), 700, type(uint72).max, 0, 0); + vault.addStrategy(address(strategy2), 700, type(uint72).max, 0, 0); vm.label(address(USDC_MAINNET), "USDC"); /// Alice approves vault for deposits @@ -123,6 +151,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { uint256 _keeperRole = strategy1.KEEPER_ROLE(); strategy1.grantRoles(address(strategyFuzzer), _keeperRole); + strategy2.grantRoles(address(strategyFuzzer), _keeperRole); } function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithoutHarvests( @@ -190,7 +219,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { vaultFuzzer.redeem(actorSeedRNG, shares); } - function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithoutHarvests( + function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithoutHarvests_banana( uint256 actorSeed, uint256 strategySeed, uint256 gainsAndLossesSeed, @@ -225,7 +254,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { strategyFuzzer.harvest(strategyRNG); strategyFuzzer.harvest(strategyRNG); vaultFuzzer.redeem(actorSeedRNG, shares); - vaultFuzzer.redeem(actorSeedRNG, shares); + // vaultFuzzer.redeem(actorSeedRNG, shares); } function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithHarvests( diff --git a/test/interfaces/IStrategyWrapper.sol b/test/interfaces/IStrategyWrapper.sol index 8d361c1..d5b8c31 100644 --- a/test/interfaces/IStrategyWrapper.sol +++ b/test/interfaces/IStrategyWrapper.sol @@ -53,6 +53,8 @@ interface IStrategyWrapper is IStrategy { function curveLpPool() external view returns (address); + function curveTriPool() external view returns (address); + function curveEthFrxEthPool() external view returns (address); function curveUsdcCrvUsdPool() external view returns (address); diff --git a/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol index a91f1fa..3f8b1ea 100644 --- a/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol +++ b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol @@ -36,7 +36,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy function setUp() public { super._setUp("MAINNET"); - vm.rollFork(21270037); + vm.rollFork(21367091); TREASURY = makeAddr("treasury"); @@ -53,13 +53,15 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy address(implementation), address(proxyAdmin), abi.encodeWithSignature( - "initialize(address,address[],bytes32,address,address,address)", + "initialize(address,address[],bytes32,address,address,address,address,address)", address(vault), keepers, bytes32("MaxApy thUSDDAIUSDCUSDT Strategy"), users.alice, CURVE_THUSD_DAI_USDC_USDT_MAINNET, - BEEFY_THUSD_DAI_USDC_USDT_MAINNET + BEEFY_THUSD_DAI_USDC_USDT_MAINNET, + CURVE_3POOL_POOL_MAINNET, + CRV3POOL_MAINNET ) ); proxy = ITransparentUpgradeableProxy(address(_proxy)); @@ -85,13 +87,15 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy address(_implementation), address(_proxyAdmin), abi.encodeWithSignature( - "initialize(address,address[],bytes32,address,address,address)", + "initialize(address,address[],bytes32,address,address,address,address,address)", address(_vault), keepers, bytes32("MaxApy thUSDDAIUSDCUSDT Strategy"), users.alice, CURVE_THUSD_DAI_USDC_USDT_MAINNET, - BEEFY_THUSD_DAI_USDC_USDT_MAINNET + BEEFY_THUSD_DAI_USDC_USDT_MAINNET, + CURVE_3POOL_POOL_MAINNET, + CRV3POOL_MAINNET ) ); @@ -108,7 +112,8 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy assertEq(_strategy.strategyName(), bytes32("MaxApy thUSDDAIUSDCUSDT Strategy")); assertEq(_strategy.curveLpPool(), CURVE_THUSD_DAI_USDC_USDT_MAINNET, "hereee"); - assertEq(IERC20(USDC_MAINNET).allowance(address(_strategy), CURVE_THUSD_DAI_USDC_USDT_MAINNET), type(uint256).max); + assertEq(_strategy.curveTriPool(), CURVE_3POOL_POOL_MAINNET, "hereee"); + assertEq(IERC20(USDC_MAINNET).allowance(address(_strategy), CURVE_3POOL_POOL_MAINNET), type(uint256).max); assertEq(_proxyAdmin.owner(), users.alice); vm.startPrank(address(_proxyAdmin)); @@ -197,17 +202,17 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy } /*==================STRATEGY CORE LOGIC TESTS==================*/ - // function testBeefythUSDDAIUSDCUSDT__InvestmentSlippage() public { - // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + function testBeefythUSDDAIUSDCUSDT__InvestmentSlippage() public { + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); - // vault.deposit(100 * _1_USDC, users.alice); + vault.deposit(100 * _1_USDC, users.alice); - // vm.startPrank(users.keeper); + vm.startPrank(users.keeper); - // // Expect revert if output amount is gt amount obtained - // vm.expectRevert(abi.encodeWithSignature("MinOutputAmountNotReached()")); - // strategy.harvest(0, type(uint256).max, address(0), block.timestamp); - // } + // Expect revert if output amount is gt amount obtained + vm.expectRevert(abi.encodeWithSignature("MinOutputAmountNotReached()")); + strategy.harvest(0, type(uint256).max, address(0), block.timestamp); + } function testBeefythUSDDAIUSDCUSDT__PrepareReturn() public { uint256 snapshotId = vm.snapshot(); @@ -279,24 +284,24 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy } function testBeefythUSDDAIUSDCUSDT__Invest() public { - // uint256 returned = strategy.invest(0, 0); - // assertEq(returned, 0); - // assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); + uint256 returned = strategy.invest(0, 0); + assertEq(returned, 0); + assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); - // vm.expectRevert(abi.encodeWithSignature("NotEnoughFundsToInvest()")); - // returned = strategy.invest(1, 0); + vm.expectRevert(abi.encodeWithSignature("NotEnoughFundsToInvest()")); + returned = strategy.invest(1, 0); deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); - // uint256 expectedShares = strategy.sharesForAmount(10 * _1_USDC); + uint256 expectedShares = strategy.sharesForAmount(10 * _1_USDC); - // vm.expectEmit(); - // emit Invested(address(strategy), 10 * _1_USDC); + vm.expectEmit(); + emit Invested(address(strategy), 10 * _1_USDC); strategy.invest(10 * _1_USDC, 0); - // assertApproxEq( - // expectedShares, IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), expectedShares / 10 - // ); + assertApproxEq( + expectedShares, IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), expectedShares / 10 + ); } function testBeefythUSDDAIUSDCUSDT__Divest() public { @@ -313,6 +318,8 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy uint256 strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); uint256 amountDivested = strategy.divest(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy))); + console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:318 ~ testBeefythUSDDAIUSDCUSDT__Divest ~ amountDivested:", amountDivested); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountDivested); } @@ -334,7 +341,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy (liquidatedAmount, loss) = strategy.liquidatePosition(149 * _1_USDC / 10); - assertEq(liquidatedAmount, 149 * _1_USDC / 10); + assertApproxEq(liquidatedAmount, 149 * _1_USDC / 10, 5 * _1_USDC / 1000); assertLt(loss, 1 * _1_USDC / 5); deal({ token: USDC_MAINNET, to: address(strategy), give: 50 * _1_USDC }); @@ -342,7 +349,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy (liquidatedAmount, loss) = strategy.liquidatePosition(495 * _1_USDC / 10); - assertEq(liquidatedAmount, 495 * _1_USDC / 10); + assertApproxEq(liquidatedAmount, 495 * _1_USDC / 10, 5 * _1_USDC / 100); assertLt(loss, 1 * _1_USDC / 5); } @@ -373,17 +380,17 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy vm.expectEmit(); emit Invested(address(strategy), 100 * _1_USDC); strategy.invest(100 * _1_USDC, 0); - - assertApproxEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), shares, 1 * _1_USDC); + assertApproxEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), shares, 0.006 ether); strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); amountFreed = strategy.liquidateAllPositions(); - assertApproxEq(amountFreed, 100 * _1_USDC, 2 * _1_USDC); + assertApproxEq(amountFreed, 100 * _1_USDC, _1_USDC); assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountFreed); assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); } + function testBeefythUSDDAIUSDCUSDT__Harvest() public { vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); @@ -446,7 +453,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy vm.warp(block.timestamp + 1 days); strategy.harvest(0, 0, address(0), block.timestamp); - assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 109956498446582789159); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 109971091); assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); vm.revertTo(snapshotId); @@ -480,9 +487,10 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy StrategyData memory data = vault.strategies(address(strategy)); - assertEq(vault.debtRatio(), 2994); - assertEq(data.strategyDebtRatio, 2994); + assertEq(vault.debtRatio(), 3001); + assertEq(data.strategyDebtRatio, 3001); } + function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate() public { vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); @@ -493,9 +501,13 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy vm.stopPrank(); uint256 expected = strategy.previewLiquidate(30 * _1_USDC); + console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:500 ~ testBeefythUSDDAIUSDCUSDT__PreviewLiquidate ~ expected:", expected); + vm.startPrank(address(vault)); uint256 loss = strategy.liquidate(30 * _1_USDC); + console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:505 ~ testBeefythUSDDAIUSDCUSDT__PreviewLiquidate ~ loss:", loss); + assertLe(expected, 30 * _1_USDC - loss); } @@ -560,79 +572,79 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy strategy.harvest(expectedBalance, outputAfterInvestment, address(0), block.timestamp); } - // function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate__FUZZY(uint256 amount) public { - // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); - // deal(USDC_MAINNET, users.alice, amount); - // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); - // vault.deposit(amount, users.alice); - // vm.startPrank(users.keeper); - - // strategy.harvest(0, 0, address(0), block.timestamp); - - // vm.stopPrank(); - // uint256 expected = strategy.previewLiquidate(amount / 3); - // vm.startPrank(address(vault)); - - // uint256 loss = strategy.liquidate(amount / 3); - - // assertLe(expected, amount / 3 - loss); - // } - - // function testBeefythUSDDAIUSDCUSDT__PreviewLiquidateExact__FUZZY(uint256 amount) public { - // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); - // deal(USDC_MAINNET, users.alice, amount); - // vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); - // vault.deposit(amount, users.alice); - // vm.startPrank(users.keeper); - // strategy.harvest(0, 0, address(0), block.timestamp); - // vm.stopPrank(); - // uint256 requestedAmount = strategy.previewLiquidateExact(amount / 3); - - // vm.startPrank(address(vault)); - // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); - - // strategy.liquidateExact(amount / 3); - // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; - - // // withdraw exactly what requested - // assertGe(withdrawn, amount / 3); - // // losses are equal or fewer than expected - // assertLe(withdrawn - (amount / 3), requestedAmount - (amount / 3)); - // } - - // function testBeefythUSDDAIUSDCUSDT__maxLiquidateExact_FUZZY(uint256 amount) public { - // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); - // deal(USDC_MAINNET, users.alice, amount); - // vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); - // vault.deposit(amount, users.alice); - // vm.startPrank(users.keeper); - // strategy.harvest(0, 0, address(0), block.timestamp); - // vm.stopPrank(); - // uint256 maxLiquidateExact = strategy.maxLiquidateExact(); - // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); - // uint256 requestedAmount = strategy.previewLiquidateExact(maxLiquidateExact); - // vm.startPrank(address(vault)); - // uint256 losses = strategy.liquidateExact(maxLiquidateExact); - // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; - // // withdraw exactly what requested - // assertGe(withdrawn, maxLiquidateExact); - // // losses are equal or fewer than expected - // assertLe(losses, requestedAmount - maxLiquidateExact); - // } - - // function testBeefythUSDDAIUSDCUSDT__MaxLiquidate_FUZZY(uint256 amount) public { - // vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); - // deal(USDC_MAINNET, users.alice, amount); - // vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); - // vault.deposit(amount, users.alice); - // vm.startPrank(users.keeper); - // strategy.harvest(0, 0, address(0), block.timestamp); - // vm.stopPrank(); - // uint256 maxWithdraw = strategy.maxLiquidate(); - // uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); - // vm.startPrank(address(vault)); - // strategy.liquidate(maxWithdraw); - // uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; - // assertLe(withdrawn, maxWithdraw); - // } + function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate__FUZZY(uint256 amount) public { + vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + deal(USDC_MAINNET, users.alice, amount); + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + vault.deposit(amount, users.alice); + vm.startPrank(users.keeper); + + strategy.harvest(0, 0, address(0), block.timestamp); + + vm.stopPrank(); + uint256 expected = strategy.previewLiquidate(amount / 3); + vm.startPrank(address(vault)); + + uint256 loss = strategy.liquidate(amount / 3); + + assertLe(expected, amount / 3 - loss); + } + + function testBeefythUSDDAIUSDCUSDT__PreviewLiquidateExact__FUZZY(uint256 amount) public { + vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + deal(USDC_MAINNET, users.alice, amount); + vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); + vault.deposit(amount, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 requestedAmount = strategy.previewLiquidateExact(amount / 3); + + vm.startPrank(address(vault)); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + + strategy.liquidateExact(amount / 3); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + + // withdraw exactly what requested + assertGe(withdrawn, amount / 3); + // losses are equal or fewer than expected + assertLe(withdrawn - (amount / 3), requestedAmount - (amount / 3)); + } + + function testBeefythUSDDAIUSDCUSDT__maxLiquidateExact_FUZZY(uint256 amount) public { + vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + deal(USDC_MAINNET, users.alice, amount); + vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + vault.deposit(amount, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 maxLiquidateExact = strategy.maxLiquidateExact(); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + uint256 requestedAmount = strategy.previewLiquidateExact(maxLiquidateExact); + vm.startPrank(address(vault)); + uint256 losses = strategy.liquidateExact(maxLiquidateExact); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + // withdraw exactly what requested + assertGe(withdrawn, maxLiquidateExact); + // losses are equal or fewer than expected + assertLe(losses, requestedAmount - maxLiquidateExact); + } + + function testBeefythUSDDAIUSDCUSDT__MaxLiquidate_FUZZY(uint256 amount) public { + vm.assume(amount > 1 * _1_USDC && amount < 100 * _1_USDC); + deal(USDC_MAINNET, users.alice, amount); + vault.addStrategy(address(strategy), 9000, type(uint72).max, 0, 0); + vault.deposit(amount, users.alice); + vm.startPrank(users.keeper); + strategy.harvest(0, 0, address(0), block.timestamp); + vm.stopPrank(); + uint256 maxWithdraw = strategy.maxLiquidate(); + uint256 balanceBefore = IERC20(USDC_MAINNET).balanceOf(address(vault)); + vm.startPrank(address(vault)); + strategy.liquidate(maxWithdraw); + uint256 withdrawn = IERC20(USDC_MAINNET).balanceOf(address(vault)) - balanceBefore; + assertLe(withdrawn, maxWithdraw); + } } From e042517c7e88b0f9b46c6604bc574f2e05e91f76 Mon Sep 17 00:00:00 2001 From: Shubhangi Date: Wed, 11 Dec 2024 12:55:21 +0530 Subject: [PATCH 3/3] updated share value estimations --- src/helpers/AddressBook.sol | 1 - src/interfaces/ICurve.sol | 3 +- .../base/BaseBeefyCurveMetaPoolStrategy.sol | 119 +++++++----------- .../beefy/BeefythUSDDAIUSDCUSDTStrategy.sol | 7 +- .../WETH/beefy/BeefyaltETHfrxETHStrategy.sol | 1 - ...xApyTestMainnetUSDC.integration.fuzz.t.sol | 4 +- test/interfaces/IStrategyWrapper.sol | 2 +- .../BeefythUSDDAIUSDCUSDTStrategyWrapper.sol | 5 +- .../BeefythUSDDAIUSDCUSDTStrategy.t.sol | 12 +- 9 files changed, 55 insertions(+), 99 deletions(-) diff --git a/src/helpers/AddressBook.sol b/src/helpers/AddressBook.sol index 4d5d5c8..e0e815e 100644 --- a/src/helpers/AddressBook.sol +++ b/src/helpers/AddressBook.sol @@ -69,7 +69,6 @@ address constant BEEFY_ALTETH_FRXETH_MAINNET = 0x26F44884D9744C0EDaB6283930DF325 address constant CURVE_THUSD_DAI_USDC_USDT_MAINNET = 0x91553BAD9Fbc8bD69Ff5d5678Cbf7D514d00De0b; address constant BEEFY_THUSD_DAI_USDC_USDT_MAINNET = 0x3f5e39bf80798cB94846B48f1c635001a2E43066; - //////////////////////////////// POLYGON //////////////////////////////// // Tokens address constant USDT_POLYGON = 0xc2132D05D31c914a87C6611C10748AEb04B58e8F; diff --git a/src/interfaces/ICurve.sol b/src/interfaces/ICurve.sol index e8f2537..69f8cc0 100644 --- a/src/interfaces/ICurve.sol +++ b/src/interfaces/ICurve.sol @@ -109,7 +109,6 @@ interface ICurveTriPool { function calc_token_amount(uint256[3] memory amounts, bool deposit) external view returns (uint256); function calc_withdraw_one_coin(uint256 token_amount, int128 i) external view returns (uint256); - } interface ICurveAtriCryptoZapper { @@ -126,4 +125,4 @@ interface ICurveAtriCryptoZapper { function get_dy(int128 i, int128 j, uint256 dx) external view returns (uint256); function exchange_underlying(uint256 i, uint256 j, uint256 _dx, uint256 _min_dy, address _receiver) external; function get_dy_underlying(uint256 i, uint256 j, uint256 _dx) external view returns (uint256); -} \ No newline at end of file +} diff --git a/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol index d742b1e..3e82a71 100644 --- a/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol +++ b/src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol @@ -1,14 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.19; +import { ERC20 } from "solady/tokens/ERC20.sol"; import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol"; +import { CRV3POOL_MAINNET } from "src/helpers/AddressBook.sol"; import { IBeefyVault } from "src/interfaces/IBeefyVault.sol"; import { ICurveLpPool, ICurveTriPool } from "src/interfaces/ICurve.sol"; import { BaseBeefyStrategy, IMaxApyVault, SafeTransferLib } from "src/strategies/base/BaseBeefyStrategy.sol"; -import { CRV3POOL_MAINNET } from "src/helpers/AddressBook.sol"; -import {ERC20} from "solady/tokens/ERC20.sol"; - -import {console2} from "forge-std/console2.sol"; /// @title BaseBeefyCurveMetaPoolStrategy /// @author Adapted from https://github.com/Grandthrax/yearn-steth-acc/blob/master/contracts/strategies.sol @@ -60,32 +58,17 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { // Curve init curveLpPool = _curveLpPool; - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:60 ~ _curveLpPool:"); - curveTriPool = _curveTriPool; crvTriPoolToken = _crvTriPoolToken; - console2.log("### 2"); - underlyingAsset.safeApprove(address(curveTriPool), type(uint256).max); - console2.log("### 3"); crvTriPoolToken.safeApprove(address(curveLpPool), type(uint256).max); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:85 ~ address(crvTriPoolToken):", crvTriPoolToken); - console2.log("### 4"); address(curveLpPool).safeApprove(address(beefyVault), type(uint256).max); - console2.log("### 5"); /// min single trade by default minSingleTrade = 10e6; - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:74 ~ minSingleTrade:", minSingleTrade); - /// Unlimited max single trade by default maxSingleTrade = 100_000e6; - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:78 ~ maxSingleTrade:", maxSingleTrade); - - } - - - + } //////////////////////////////////////////////////////////////// /// INTERNAL CORE FUNCTIONS /// @@ -110,20 +93,16 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { } amount = Math.min(maxSingleTrade, amount); - + uint256[3] memory amountsUsdc; amountsUsdc[1] = amount; uint256 _before = ERC20(crvTriPoolToken).balanceOf(address(this)); // uint256 _before = ERC20(_3crvToken).balanceOf(address(this)); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:111 ~ _invest ~ _before:", _before); - // Add liquidity to the curveTriPool in underlying token [coin1 -> usdc] curveTriPool.add_liquidity(amountsUsdc, 0); uint256 _after = ERC20(crvTriPoolToken).balanceOf(address(this)); // uint256 _after = ERC20(_3crvToken).balanceOf(address(this)); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:116 ~ _invest ~ _after:", _after); - uint256 _3crvTokenReceived; assembly ("memory-safe") { @@ -134,8 +113,6 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { amounts[1] = _3crvTokenReceived; // Add liquidity to the curve Metapool in 3crv token [coin1 -> 3crv] uint256 lpReceived = curveLpPool.add_liquidity(amounts, 0, address(this)); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:137 ~ _invest ~ lpReceived:", lpReceived); - _before = beefyVault.balanceOf(address(this)); @@ -153,15 +130,10 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { revert(0x1c, 0x04) } } - - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:161 ~ _invest ~ shares:", shares); - emit Invested(address(this), amount); - return shares; + return _shareValue(shares); } - - /// @dev care should be taken, as the `amount` parameter is not in terms of underlying, /// but in terms of Beefy's moo tokens @@ -181,15 +153,13 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { uint256 lptokens = _after - _before; // Remove liquidity and obtain usdce - uint256 _3crvTokenReceived = curveLpPool.remove_liquidity_one_coin( + uint256 _3crvTokenReceived = curveLpPool.remove_liquidity_one_coin( lptokens, 1, //usdce 0, address(this) ); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:185 ~ _divest ~ _3crvTokenReceived:", _3crvTokenReceived); - _before = underlyingAsset.balanceOf(address(this)); @@ -201,8 +171,37 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { ); amountDivested = underlyingAsset.balanceOf(address(this)) - _before; - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:198 ~ _divest ~ amountDivested:", amountDivested); + } + + ///////////////////////////////////////////////////////////////// + /// VIEW FUNCTIONS /// + //////////////////////////////////////////////////////////////// + /// @notice This function is meant to be called from the vault + /// @dev calculates the estimated real output of a withdrawal(including losses) for a @param requestedAmount + /// for the vault to be able to provide an accurate amount when calling `previewRedeem` + /// @return liquidatedAmount output in assets + function previewLiquidate(uint256 requestedAmount) + public + view + virtual + override + returns (uint256 liquidatedAmount) + { + uint256 loss; + uint256 underlyingBalance = _underlyingBalance(); + // If underlying balance currently held by strategy is not enough to cover + // the requested amount, we divest from the beefy Vault + if (underlyingBalance < requestedAmount) { + uint256 amountToWithdraw; + unchecked { + amountToWithdraw = requestedAmount - underlyingBalance; + } + uint256 shares = _sharesForAmount(amountToWithdraw); + uint256 withdrawn = _shareValue(shares); + if (withdrawn < amountToWithdraw) loss = amountToWithdraw - withdrawn; + } + liquidatedAmount = (requestedAmount - loss); } //////////////////////////////////////////////////////////////// @@ -212,62 +211,28 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { /// @notice Determines the current value of `shares`. /// @return _assets the estimated amount of underlying computed from shares `shares` function _shareValue(uint256 shares) internal view virtual override returns (uint256 _assets) { - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:218 ~ _shareValue ~ shares:", shares); - uint256 expectedCurveLp = shares * beefyVault.balance() / beefyVault.totalSupply(); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:219 ~ _shareValue ~ expectedCurveLp:", expectedCurveLp); - if (expectedCurveLp > 0) { uint256 expected3Crv = curveLpPool.calc_withdraw_one_coin(expectedCurveLp, 1); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:222 ~ _shareValue ~ expected3Crv:", expected3Crv); if (expected3Crv > 0) { _assets = curveTriPool.calc_withdraw_one_coin(expected3Crv, 1); } } - - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:227 ~ _shareValue ~ _assets:", _assets); - - // uint256 lpTokenAmount = super._shareValue(shares); - // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:216 ~ _shareValue ~ lpTokenAmount:", lpTokenAmount); - - // uint256 lpPrice = _lpPrice(); - // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:219 ~ _shareValue ~ lpPrice:", lpPrice); - - - // uint256 lpTriPoolPrice =_lpTriPoolPrice(); - // console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:223 ~ _shareValue ~ lpTriPoolPrice:", lpTriPoolPrice); - - - // // lp price add get function _lpPrice() - // assembly { - // let scale := 0xde0b6b3a7640000 // This is 1e18 in hexadecimal - // _assets := div(mul(lpTokenAmount, lpPrice), scale) - // _assets := div(mul(_assets, lpTriPoolPrice), scale) - - // } } /// @notice Determines how many shares depositor of `amount` of underlying would receive. /// @return shares the estimated amount of shares computed in exchange for underlying `amount` function _sharesForAmount(uint256 amount) internal view virtual override returns (uint256 shares) { - uint256[3] memory amounts; amounts[1] = amount; - - uint256 lpTokenAmount = curveTriPool.calc_token_amount(amounts, true); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:228 ~ _sharesForAmount ~ lpTokenAmount:", lpTokenAmount); - + uint256 lpTokenAmount = curveTriPool.calc_token_amount(amounts, true); uint256[2] memory _amounts; - _amounts[1] = lpTokenAmount; + _amounts[1] = lpTokenAmount; lpTokenAmount = curveLpPool.calc_token_amount(_amounts, true); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:235 ~ _sharesForAmount ~ lpTokenAmount:", lpTokenAmount); - shares = super._sharesForAmount(lpTokenAmount); - console2.log("### ~ file: BaseBeefyCurveMetaPoolStrategy.sol:245 ~ _sharesForAmount ~ shares:", shares); - } /// @notice Returns the estimated price for the strategy's curve's LP token @@ -279,7 +244,11 @@ contract BaseBeefyCurveMetaPoolStrategy is BaseBeefyStrategy { /// @notice Returns the estimated price for the strategy's curve's Tri pool LP token /// @return returns the estimated lp token price function _lpTriPoolPrice() internal view returns (uint256) { - return ((curveTriPool.get_virtual_price() - * Math.min(curveTriPool.get_dy(0, 1, 1 ether), curveTriPool.get_dy(1,0,1e6))) / 1 ether); + return ( + ( + curveTriPool.get_virtual_price() + * Math.min(curveTriPool.get_dy(0, 1, 1 ether), curveTriPool.get_dy(1, 0, 1e6)) + ) / 1 ether + ); } } diff --git a/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol b/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol index 2b6e823..d6c7473 100644 --- a/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol +++ b/src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol @@ -3,17 +3,12 @@ pragma solidity ^0.8.19; import { FixedPointMathLib as Math } from "solady/utils/FixedPointMathLib.sol"; -// import { FRXETH_MAINNET } from "src/helpers/AddressBook.sol"; -// import { IBeefyVault } from "src/interfaces/IBeefyVault.sol"; -// import { ICurveLpPool } from "src/interfaces/ICurve.sol"; -// import { IWETH } from "src/interfaces/IWETH.sol"; import { BaseBeefyCurveMetaPoolStrategy } from "src/strategies/base/BaseBeefyCurveMetaPoolStrategy.sol"; import { BaseBeefyStrategy, IMaxApyVault, SafeTransferLib } from "src/strategies/base/BaseBeefyStrategy.sol"; -// import { console2 } from "forge-std/console2.sol"; /// @title BeefythUSDDAIUSDCUSDTStrategy /// @author Adapted from https://github.com/Grandthrax/yearn-steth-acc/blob/master/contracts/strategies.sol /// @notice `BeefythUSDDAIUSDCUSDTStrategy` supplies an underlying token into a generic Beefy Vault, /// earning the Beefy Vault's yield -contract BeefythUSDDAIUSDCUSDTStrategy is BaseBeefyCurveMetaPoolStrategy {} +contract BeefythUSDDAIUSDCUSDTStrategy is BaseBeefyCurveMetaPoolStrategy { } diff --git a/src/strategies/mainnet/WETH/beefy/BeefyaltETHfrxETHStrategy.sol b/src/strategies/mainnet/WETH/beefy/BeefyaltETHfrxETHStrategy.sol index b52c74a..83f83fa 100644 --- a/src/strategies/mainnet/WETH/beefy/BeefyaltETHfrxETHStrategy.sol +++ b/src/strategies/mainnet/WETH/beefy/BeefyaltETHfrxETHStrategy.sol @@ -113,7 +113,6 @@ contract BeefyaltETHfrxETHStrategy is BaseBeefyCurveStrategy { // Add liquidity to the altETH<>frxETH pool in frxETH [coin1 -> frxETH] lpReceived = curveLpPool.add_liquidity(amounts, 0, address(this)); - uint256 _before = beefyVault.balanceOf(address(this)); // Deposit Curve LP tokens to Beefy vault diff --git a/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol b/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol index 62fcb48..2be6e48 100644 --- a/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol +++ b/test/fuzz/MaxApyTestMainnetUSDC.integration.fuzz.t.sol @@ -219,7 +219,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { vaultFuzzer.redeem(actorSeedRNG, shares); } - function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithoutHarvests_banana( + function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithoutHarvests( uint256 actorSeed, uint256 strategySeed, uint256 gainsAndLossesSeed, @@ -254,7 +254,7 @@ contract MaxApyIntegrationTestMainnet is BaseTest, StrategyEvents { strategyFuzzer.harvest(strategyRNG); strategyFuzzer.harvest(strategyRNG); vaultFuzzer.redeem(actorSeedRNG, shares); - // vaultFuzzer.redeem(actorSeedRNG, shares); + vaultFuzzer.redeem(actorSeedRNG, shares); } function testFuzzMaxApyIntegrationMainnet__DepositAndRedeemWithGainsAndLossesWithHarvests( diff --git a/test/interfaces/IStrategyWrapper.sol b/test/interfaces/IStrategyWrapper.sol index d5b8c31..e96b513 100644 --- a/test/interfaces/IStrategyWrapper.sol +++ b/test/interfaces/IStrategyWrapper.sol @@ -54,7 +54,7 @@ interface IStrategyWrapper is IStrategy { function curveLpPool() external view returns (address); function curveTriPool() external view returns (address); - + function curveEthFrxEthPool() external view returns (address); function curveUsdcCrvUsdPool() external view returns (address); diff --git a/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol b/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol index 393bd07..d9d0895 100644 --- a/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol +++ b/test/mock/BeefythUSDDAIUSDCUSDTStrategyWrapper.sol @@ -1,7 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity ^0.8.19; -import { BeefythUSDDAIUSDCUSDTStrategy, SafeTransferLib } from "src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol"; +import { + BeefythUSDDAIUSDCUSDTStrategy, + SafeTransferLib +} from "src/strategies/mainnet/USDC/beefy/BeefythUSDDAIUSDCUSDTStrategy.sol"; contract BeefythUSDDAIUSDCUSDTStrategyWrapper is BeefythUSDDAIUSDCUSDTStrategy { using SafeTransferLib for address; diff --git a/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol index 3f8b1ea..a1aeb4c 100644 --- a/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol +++ b/test/unit/strategies/BeefythUSDDAIUSDCUSDTStrategy.t.sol @@ -36,7 +36,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy function setUp() public { super._setUp("MAINNET"); - vm.rollFork(21367091); + vm.rollFork(21_367_091); TREASURY = makeAddr("treasury"); @@ -293,7 +293,6 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy deal({ token: USDC_MAINNET, to: address(strategy), give: 10 * _1_USDC }); uint256 expectedShares = strategy.sharesForAmount(10 * _1_USDC); - vm.expectEmit(); emit Invested(address(strategy), 10 * _1_USDC); @@ -318,8 +317,6 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy uint256 strategyBalanceBefore = IERC20(USDC_MAINNET).balanceOf(address(strategy)); uint256 amountDivested = strategy.divest(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy))); - console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:318 ~ testBeefythUSDDAIUSDCUSDT__Divest ~ amountDivested:", amountDivested); - assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountDivested); } @@ -390,7 +387,6 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy assertEq(IERC20(USDC_MAINNET).balanceOf(address(strategy)), strategyBalanceBefore + amountFreed); assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); } - function testBeefythUSDDAIUSDCUSDT__Harvest() public { vm.expectRevert(abi.encodeWithSignature("Unauthorized()")); @@ -453,7 +449,7 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy vm.warp(block.timestamp + 1 days); strategy.harvest(0, 0, address(0), block.timestamp); - assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 109971091); + assertEq(IERC20(USDC_MAINNET).balanceOf(address(vault)), 109_971_091); assertEq(IERC20(BEEFY_THUSD_DAI_USDC_USDT_MAINNET).balanceOf(address(strategy)), 0); vm.revertTo(snapshotId); @@ -490,7 +486,6 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy assertEq(vault.debtRatio(), 3001); assertEq(data.strategyDebtRatio, 3001); } - function testBeefythUSDDAIUSDCUSDT__PreviewLiquidate() public { vault.addStrategy(address(strategy), 4000, type(uint72).max, 0, 0); @@ -501,13 +496,10 @@ contract BeefythUSDDAIUSDCUSDTStrategyTest is BaseTest, ConvexdETHFrxETHStrategy vm.stopPrank(); uint256 expected = strategy.previewLiquidate(30 * _1_USDC); - console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:500 ~ testBeefythUSDDAIUSDCUSDT__PreviewLiquidate ~ expected:", expected); vm.startPrank(address(vault)); uint256 loss = strategy.liquidate(30 * _1_USDC); - console2.log("### ~ file: BeefythUSDDAIUSDCUSDTStrategy.t.sol:505 ~ testBeefythUSDDAIUSDCUSDT__PreviewLiquidate ~ loss:", loss); - assertLe(expected, 30 * _1_USDC - loss); }