diff --git a/.gas-snapshot b/.gas-snapshot index e452ecb7..c54e95c2 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -417,6 +417,12 @@ SthEthLevPolygon_IntegrationTest:testPerformanceFee() (gas: 775743) SthEthLevPolygon_IntegrationTest:testVaultPause() (gas: 391282) SthEthLevPolygon_IntegrationTest:testWithdrawalFee() (gas: 313482) SthEthLevPolygon_IntegrationTest:testWithdrawalFeeWithNft() (gas: 1259503) +StrikeEthStrategyTest:testAddToPosition() (gas: 522476) +StrikeEthStrategyTest:testClosePosition() (gas: 756205) +StrikeEthStrategyTest:testLeverage() (gas: 539253) +StrikeEthStrategyTest:testMutateTotalLockedValue() (gas: 834439) +StrikeEthStrategyTest:testSetParams() (gas: 14303) +StrikeEthStrategyTest:testTotalLockedValue() (gas: 553314) TestBeefyAeroStrategy:testDepositAndWithdrawFromVault() (gas: 2013214) TestBeefyAeroStrategy:testDivestByStrategist() (gas: 1629793) TestBeefyAeroStrategy:testDivestHalf() (gas: 342116) diff --git a/src/strategies/StrikeEthStrategy.sol b/src/strategies/StrikeEthStrategy.sol index dfd4bbf3..892b8d6e 100644 --- a/src/strategies/StrikeEthStrategy.sol +++ b/src/strategies/StrikeEthStrategy.sol @@ -43,8 +43,8 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { } error onlyBalancerVault(); - /// @notice Callback called by balancer vault after flashloan is initiated. + /// @notice Callback called by balancer vault after flashloan is initiated. function receiveFlashLoan( ERC20[] memory, /* tokens */ uint256[] memory amounts, @@ -55,6 +55,9 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { uint256 ethBorrowed = amounts[0]; + // Convert wETH to ETH + WETH.withdraw(ethBorrowed); + (LoanType loan) = abi.decode(userData, (LoanType)); if (loan == LoanType.divest) { @@ -81,15 +84,12 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { recipient: IFlashLoanRecipient(address(this)), tokens: tokens, amounts: amounts, - userData: abi.encode(LoanType.invest, address(0)) + userData: abi.encode(LoanType.invest) }); } - /// @dev Add to leveraged position. Trade ETH to wstETH, deposit in AAVE, and borrow to repay balancer loan. + /// @dev Add to leveraged position. Deposit into compound and borrow to repay balancer loan. function _addToPosition(uint256 ethBorrowed) internal { - // Convert wETH to ETH - WETH.withdraw(ethBorrowed); - // Deposit ETH into compound cToken.mint{value: ethBorrowed}(); @@ -98,22 +98,20 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { uint256 borrowRes = cToken.borrow(amountToBorrow); if (borrowRes != 0) revert CompBorrowError(borrowRes); - // convert to wETH to pay back balancer loan + // Convert ETH to wETH for balancer repayment WETH.deposit{value: amountToBorrow}(); } /// @dev We need this to receive ETH when calling wETH.withdraw() receive() external payable {} - /// @dev Unlock wstETH collateral via flashloan, then repay balancer loan with unlocked collateral. + /// @dev Unlock ETH collateral via flashloan, then repay balancer loan with unlocked collateral. function _divest(uint256 amount) internal override returns (uint256) { - uint256 ethNeeded = _getDivestFlashLoanAmounts(amount); - ERC20[] memory tokens = new ERC20[](1); tokens[0] = WETH; uint256[] memory amounts = new uint256[](1); - amounts[0] = ethNeeded; + amounts[0] = _getDivestFlashLoanAmounts(amount); uint256 origAssets = WETH.balanceOf(address(this)); @@ -121,7 +119,7 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { recipient: IFlashLoanRecipient(address(this)), tokens: tokens, amounts: amounts, - userData: abi.encode(LoanType.divest, address(0)) + userData: abi.encode(LoanType.divest) }); // The loan has been paid, any other unlocked collateral belongs to user @@ -151,6 +149,9 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { // Withdraw same proportion of collateral from aave uint256 res = cToken.redeemUnderlying(ethToRedeem); if (res != 0) revert CompRedeemError(res); + + // Convert ETH to wETH for balancer repayment and payment to user + WETH.deposit{value: ethToRedeem}(); } /*////////////////////////////////////////////////////////////// @@ -187,7 +188,7 @@ contract StrikeEthStrategy is AccessStrategy, IFlashLoanRecipient { error CompRedeemError(uint256 errorCode); /// @notice The leverage factor of the position multiplied by 100. E.g. 150 would be 1.5x leverage. - uint256 public leverage = 992; // 9.92x + uint256 public leverage = 333; // 3.33x /// @notice Set the leverage factor. /// @param _leverage The new leverage. diff --git a/src/test/LidoLevL2.t.sol b/src/test/LidoLevL2.t.sol index 0db09ac2..e7fe635b 100644 --- a/src/test/LidoLevL2.t.sol +++ b/src/test/LidoLevL2.t.sol @@ -5,13 +5,7 @@ import {TestPlus} from "./TestPlus.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import { - LidoLevL2, - IBalancerVault, - IFlashLoanRecipient, - AffineVault, - FixedPointMathLib -} from "src/strategies/LidoLevL2.sol"; +import {LidoLevL2, AffineVault, FixedPointMathLib} from "src/strategies/LidoLevL2.sol"; import {console2} from "forge-std/console2.sol"; diff --git a/src/test/StrikeEthStrategy.t.sol b/src/test/StrikeEthStrategy.t.sol new file mode 100644 index 00000000..ada062a3 --- /dev/null +++ b/src/test/StrikeEthStrategy.t.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.16; + +import {TestPlus} from "./TestPlus.sol"; +import {stdStorage, StdStorage} from "forge-std/Test.sol"; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {StrikeEthStrategy, ICToken, AffineVault, FixedPointMathLib} from "src/strategies/StrikeEthStrategy.sol"; + +import {console2} from "forge-std/console2.sol"; + +contract MockStrikeEthStrategy is StrikeEthStrategy { + constructor(AffineVault _vault, ICToken _cToken, address[] memory strategists) + StrikeEthStrategy(_vault, _cToken, strategists) + {} + + function addToPosition(uint256 amount) external { + _afterInvest(amount); + } + + function collateral() external returns (uint256) { + return cToken.balanceOfUnderlying(address(this)); + } + + function debt() external returns (uint256) { + return cToken.balanceOfUnderlying(address(this)); + } +} + +contract StrikeEthStrategyTest is TestPlus { + using FixedPointMathLib for uint256; + + MockStrikeEthStrategy staking; + AffineVault vault; + + uint256 depSize = 10 ether; + + receive() external payable {} + + function setUp() public { + forkEth(); + + address[] memory strategists = new address[](1); + strategists[0] = address(this); + vault = AffineVault(address(deployL1Vault())); + + staking = new MockStrikeEthStrategy(vault, ICToken(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5), strategists); + vm.prank(governance); + vault.addStrategy(staking, 0); + } + + function _giveEther(uint256 amount) internal { + ERC20 weth = staking.WETH(); + deal(address(weth), address(staking), amount); + } + + function _divest(uint256 amount) internal { + vm.prank(address(vault)); + staking.divest(amount); + } + + function testAddToPosition() public { + _giveEther(depSize); + staking.addToPosition(depSize); + } + + function testLeverage() public { + testAddToPosition(); + uint256 collateral = staking.collateral(); + assertApproxEqRel(collateral, depSize * staking.leverage() / 100, 0.02e18); + } + + function testClosePosition() public { + ERC20 weth = staking.WETH(); + testAddToPosition(); + vm.warp(block.timestamp + 1 days); + vm.roll(block.number + 1); + _divest(1 ether); + console2.log("WETH balance: %s", weth.balanceOf(address(this))); + console2.log("WETH staking balance: %s", weth.balanceOf(address(staking))); + } + + function testTotalLockedValue() public { + testAddToPosition(); + uint256 tvl = staking.totalLockedValue(); + console2.log("TVL: %s", tvl); + assertApproxEqRel(tvl, depSize, 0.01e18); + } + + function testMutateTotalLockedValue() public { + testAddToPosition(); + uint256 tvl = staking.totalLockedValue(); + console2.log("Orig tvl: ", tvl); + console2.log("orig collateral: ", staking.collateral()); + console2.log("orig debt: ", staking.debt()); + + vm.prank(address(vault)); + staking.divest(1 ether); + + assertApproxEqRel(staking.totalLockedValue(), tvl - 1 ether, 0.01e18); + } + + function testSetParams() public { + staking.setLeverage(1000); + assertEq(staking.leverage(), 1000); + } +}