From de862c8fdb64d7e5f5b0eccc6807d6732f22fc99 Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Fri, 9 Aug 2024 14:41:21 -0700 Subject: [PATCH] fix: remove stETH withdraws from Kelp in favor of ETH withdraws --- .../vaults/staking/PendlePTKelpVault.sol | 15 +-- contracts/vaults/staking/protocols/Kelp.sol | 80 ++---------- tests/BaseAcceptanceTest.sol | 4 +- tests/Staking/PendlePT.t.sol.j2 | 14 ++ tests/config.py | 3 +- .../mainnet/PendlePT_rsETH_ETH.t.sol | 120 +++--------------- 6 files changed, 49 insertions(+), 187 deletions(-) diff --git a/contracts/vaults/staking/PendlePTKelpVault.sol b/contracts/vaults/staking/PendlePTKelpVault.sol index e2d737ec..72a3f7a4 100644 --- a/contracts/vaults/staking/PendlePTKelpVault.sol +++ b/contracts/vaults/staking/PendlePTKelpVault.sol @@ -5,7 +5,7 @@ import {Constants} from "@contracts/global/Constants.sol"; import {TypeConvert} from "@contracts/global/TypeConvert.sol"; import {Deployments} from "@deployments/Deployments.sol"; import {PendlePrincipalToken, WithdrawRequest} from "./protocols/PendlePrincipalToken.sol"; -import { KelpLib, KelpCooldownHolder, rsETH, stETH } from "./protocols/Kelp.sol"; +import { KelpLib, KelpCooldownHolder, rsETH} from "./protocols/Kelp.sol"; contract PendlePTKelpVault is PendlePrincipalToken { using TypeConvert for int256; @@ -44,9 +44,9 @@ contract PendlePTKelpVault is PendlePrincipalToken { ) internal override view returns (uint256) { uint256 tokenOutSY = getTokenOutSYForWithdrawRequest(requestId); // NOTE: in this vault the tokenOutSy is known to be stETH. - (int256 stETHPrice, /* */) = TRADING_MODULE.getOraclePrice(TOKEN_OUT_SY, BORROW_TOKEN); - return (tokenOutSY * stETHPrice.toUint() * BORROW_PRECISION) / - (KelpLib.stETH_PRECISION * Constants.EXCHANGE_RATE_PRECISION); + (int256 ethPrice, /* */) = TRADING_MODULE.getOraclePrice(TOKEN_OUT_SY, BORROW_TOKEN); + return (tokenOutSY * ethPrice.toUint() * BORROW_PRECISION) / + (Constants.EXCHANGE_RATE_PRECISION * Constants.EXCHANGE_RATE_PRECISION); } function _initiateSYWithdraw( @@ -65,11 +65,4 @@ contract PendlePTKelpVault is PendlePrincipalToken { return KelpLib._canFinalizeWithdrawRequest(requestId); } - function canTriggerExtraStep(uint256 requestId) public view returns (bool) { - return KelpLib._canTriggerExtraStep(requestId); - } - - function triggerExtraStep(uint256 requestId) external { - KelpLib._triggerExtraStep(requestId); - } } \ No newline at end of file diff --git a/contracts/vaults/staking/protocols/Kelp.sol b/contracts/vaults/staking/protocols/Kelp.sol index a23db03d..cff69d70 100644 --- a/contracts/vaults/staking/protocols/Kelp.sol +++ b/contracts/vaults/staking/protocols/Kelp.sol @@ -26,28 +26,8 @@ interface IWithdrawalManager { ) external returns (uint256 rsETHBurned, uint256 assetAmountUnlocked); } -interface ILidoWithdraw { - struct WithdrawalRequestStatus { - uint256 amountOfStETH; - uint256 amountOfShares; - address owner; - uint256 timestamp; - bool isFinalized; - bool isClaimed; - } - - function requestWithdrawals(uint256[] memory _amounts, address _owner) external returns (uint256[] memory requestIds); - function getWithdrawalRequests(address _owner) external view returns (uint256[] memory requestsIds); - function getWithdrawalStatus(uint256[] memory _requestIds) external view returns (WithdrawalRequestStatus[] memory statuses); - function claimWithdrawal(uint256 _requestId) external; - function finalize(uint256 _lastRequestIdToBeFinalized, uint256 _maxShareRate) external payable; - function getLastRequestId() external view returns (uint256); -} - IERC20 constant rsETH = IERC20(0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7); IWithdrawalManager constant WithdrawManager = IWithdrawalManager(0x62De59c08eB5dAE4b7E6F7a8cAd3006d6965ec16); -address constant stETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; -ILidoWithdraw constant LidoWithdraw = ILidoWithdraw(0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1); contract KelpCooldownHolder is ClonedCoolDownHolder { bool public triggered = false; @@ -63,41 +43,18 @@ contract KelpCooldownHolder is ClonedCoolDownHolder { uint256 balance = rsETH.balanceOf(address(this)); rsETH.approve(address(WithdrawManager), balance); // initiate withdraw from Kelp - WithdrawManager.initiateWithdrawal(stETH, balance); - } - - /// @notice this method need to be called once withdraw on Kelp is finalized - /// to start withdraw process from Lido so we can unwrap stETH to ETH - /// since we are not able to withdraw ETH directly from Kelp - function triggerExtraStep() external { - require(!triggered); - (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(stETH, address(this), 0); - require(userWithdrawalRequestNonce < WithdrawManager.nextLockedNonce(stETH)); - - WithdrawManager.completeWithdrawal(stETH); - uint256 tokensClaimed = IERC20(stETH).balanceOf(address(this)); - - uint256[] memory amounts = new uint256[](1); - amounts[0] = tokensClaimed; - IERC20(stETH).approve(address(LidoWithdraw), amounts[0]); - LidoWithdraw.requestWithdrawals(amounts, address(this)); - - triggered = true; + WithdrawManager.initiateWithdrawal(Deployments.ALT_ETH_ADDRESS, balance); } function _finalizeCooldown() internal override returns (uint256 tokensClaimed, bool finalized) { - if (!triggered) { - return (0, false); - } - - uint256[] memory requestIds = LidoWithdraw.getWithdrawalRequests(address(this)); - ILidoWithdraw.WithdrawalRequestStatus[] memory withdrawsStatus = LidoWithdraw.getWithdrawalStatus(requestIds); - - if (!withdrawsStatus[0].isFinalized) { + (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(Deployments.ALT_ETH_ADDRESS, address(this), 0); + uint256 nextNonce = WithdrawManager.nextLockedNonce(Deployments.ALT_ETH_ADDRESS); + // Will revert when nextLockedNonce == userWithdrawalRequestNonce, so this must be strictly less than + if (nextNonce < userWithdrawalRequestNonce) { return (0, false); } - LidoWithdraw.claimWithdrawal(requestIds[0]); + WithdrawManager.completeWithdrawal(Deployments.ALT_ETH_ADDRESS); tokensClaimed = address(this).balance; (bool sent,) = vault.call{value: tokensClaimed}(""); @@ -109,8 +66,6 @@ contract KelpCooldownHolder is ClonedCoolDownHolder { library KelpLib { using TypeConvert for int256; - uint256 internal constant stETH_PRECISION = 1e18; - function _getValueOfWithdrawRequest( uint256 totalVaultShares, address borrowToken, @@ -141,25 +96,8 @@ library KelpLib { function _canFinalizeWithdrawRequest(uint256 requestId) internal view returns (bool) { address holder = address(uint160(requestId)); - if (!KelpCooldownHolder(payable(holder)).triggered()) return false; - - uint256[] memory requestIds = LidoWithdraw.getWithdrawalRequests(holder); - ILidoWithdraw.WithdrawalRequestStatus[] memory withdrawsStatus = LidoWithdraw.getWithdrawalStatus(requestIds); - - return withdrawsStatus[0].isFinalized; - } - - function _canTriggerExtraStep(uint256 requestId) internal view returns (bool) { - address holder = address(uint160(requestId)); - if (KelpCooldownHolder(payable(holder)).triggered()) return false; - - (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(stETH, holder, 0); - - return userWithdrawalRequestNonce < WithdrawManager.nextLockedNonce(stETH); - } - - function _triggerExtraStep(uint256 requestId) internal { - KelpCooldownHolder holder = KelpCooldownHolder(payable(address(uint160(requestId)))); - holder.triggerExtraStep(); + (/* */, /* */, /* */, uint256 userWithdrawalRequestNonce) = WithdrawManager.getUserWithdrawalRequest(Deployments.ALT_ETH_ADDRESS, holder, 0); + uint256 nextNonce = WithdrawManager.nextLockedNonce(Deployments.ALT_ETH_ADDRESS); + return userWithdrawalRequestNonce < nextNonce; } } \ No newline at end of file diff --git a/tests/BaseAcceptanceTest.sol b/tests/BaseAcceptanceTest.sol index 5cc169cb..ebd4fade 100644 --- a/tests/BaseAcceptanceTest.sol +++ b/tests/BaseAcceptanceTest.sol @@ -62,7 +62,9 @@ abstract contract BaseAcceptanceTest is Test { // NOTE: everything needs to run after create select fork deployCodeTo("VaultRewarderLib.sol", Deployments.VAULT_REWARDER_LIB); if (Deployments.CHAIN_ID == 1) { - vm.startPrank(0x22341fB5D92D3d801144aA5A925F401A91418A05); + if (FORK_BLOCK < 20492800) vm.startPrank(0x22341fB5D92D3d801144aA5A925F401A91418A05); + else vm.startPrank(Deployments.NOTIONAL.owner()); + address tradingModule = address(new TradingModule(Deployments.NOTIONAL, Deployments.TRADING_MODULE)); // NOTE: fixes curve router UUPSUpgradeable(address(Deployments.TRADING_MODULE)).upgradeTo(tradingModule); diff --git a/tests/Staking/PendlePT.t.sol.j2 b/tests/Staking/PendlePT.t.sol.j2 index 85be41e0..f726b0bb 100644 --- a/tests/Staking/PendlePT.t.sol.j2 +++ b/tests/Staking/PendlePT.t.sol.j2 @@ -46,6 +46,20 @@ contract Test_PendlePT_{{ stakeSymbol }}_{{ primaryBorrowCurrency }} is BasePend vm.prank(0x0EF8fa4760Db8f5Cd4d993f3e3416f30f942D705); // etherFi: admin WithdrawRequestNFT.finalizeRequests(w.requestId); } + {% elif contractName == 'PendlePTKelpVault' %} + function finalizeWithdrawRequest(address account) internal override { + // finalize withdraw request on Kelp + vm.deal(address(unstakingVault), 10_000e18); + vm.startPrank(0xCbcdd778AA25476F203814214dD3E9b9c46829A1); // kelp: operator + WithdrawManager.unlockQueue( + Deployments.ALT_ETH_ADDRESS, + type(uint256).max, + lrtOracle.getAssetPrice(Deployments.ALT_ETH_ADDRESS), + lrtOracle.rsETHPrice() + ); + vm.stopPrank(); + vm.roll(block.number + WithdrawManager.withdrawalDelayBlocks()); + } {% else %} function finalizeWithdrawRequest(address account) internal override {} {% endif %} diff --git a/tests/config.py b/tests/config.py index f74b9ee9..df25516e 100644 --- a/tests/config.py +++ b/tests/config.py @@ -111,7 +111,8 @@ "weETH": "0xE47F6c47DE1F1D93d8da32309D4dB90acDadeEaE", 'GHO': "0x3f12643D3f6f874d39C2a4c9f2Cd6f2DbAC877FC", 'USDe': "0xa569d910839Ae8865Da8F8e70FfFb0cBA869F961", - 'ezETH': "0xCa140AE5a361b7434A729dCadA0ea60a50e249dd" + 'ezETH': "0xCa140AE5a361b7434A729dCadA0ea60a50e249dd", + "rsETH": "0xb676EA4e0A54ffD579efFc1f1317C70d671f2028" }, "arbitrum": { "WETH": "0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612", diff --git a/tests/generated/mainnet/PendlePT_rsETH_ETH.t.sol b/tests/generated/mainnet/PendlePT_rsETH_ETH.t.sol index 0f0e7985..1f4f1011 100644 --- a/tests/generated/mainnet/PendlePT_rsETH_ETH.t.sol +++ b/tests/generated/mainnet/PendlePT_rsETH_ETH.t.sol @@ -9,7 +9,7 @@ import { } from "@contracts/vaults/staking/protocols/PendlePrincipalToken.sol"; import {PendlePTOracle} from "@contracts/oracles/PendlePTOracle.sol"; import "@interfaces/chainlink/AggregatorV2V3Interface.sol"; -import {WithdrawManager, stETH, LidoWithdraw, rsETH} from "@contracts/vaults/staking/protocols/Kelp.sol"; +import {WithdrawManager, rsETH} from "@contracts/vaults/staking/protocols/Kelp.sol"; import {PendlePTKelpVault} from "@contracts/vaults/staking/PendlePTKelpVault.sol"; /**** NOTE: this file is not auto-generated because there is lots of custom withdraw logic *****/ @@ -26,7 +26,7 @@ address constant unstakingVault = 0xc66830E2667bc740c0BED9A71F18B14B8c8184bA; contract Test_PendlePT_rsETH_ETH is BasePendleTest { function setUp() public override { - FORK_BLOCK = 20033103; + FORK_BLOCK = 20492805; harness = new Harness_PendlePT_rsETH_ETH(); @@ -41,45 +41,26 @@ contract Test_PendlePT_rsETH_ETH is BasePendleTest { deleverageCollateralDecreaseRatio = 930; defaultLiquidationDiscount = 955; withdrawLiquidationDiscount = 945; + splitWithdrawPriceDecrease = 450; + borrowTokenPriceIncrease = 1500; super.setUp(); } - function _finalizeFirstStep() private { + function finalizeWithdrawRequest(address /* account */) internal override { // finalize withdraw request on Kelp - address stETHWhale = 0x804a7934bD8Cd166D35D8Fb5A1eb1035C8ee05ce; - vm.prank(stETHWhale); - IERC20(stETH).transfer(unstakingVault, 10_000e18); + vm.deal(address(unstakingVault), 10_000e18); vm.startPrank(0xCbcdd778AA25476F203814214dD3E9b9c46829A1); // kelp: operator WithdrawManager.unlockQueue( - address(stETH), + Deployments.ALT_ETH_ADDRESS, type(uint256).max, - lrtOracle.getAssetPrice(address(stETH)), + lrtOracle.getAssetPrice(Deployments.ALT_ETH_ADDRESS), lrtOracle.rsETHPrice() ); vm.stopPrank(); vm.roll(block.number + WithdrawManager.withdrawalDelayBlocks()); } - function _finalizeSecondStep() private { - // finalize withdraw request on LIDO - address lidoAdmin = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; - deal(lidoAdmin, 1000e18); - vm.startPrank(lidoAdmin); - LidoWithdraw.finalize{value: 1000e18}(LidoWithdraw.getLastRequestId(), 1.1687147788880494e27); - vm.stopPrank(); - } - - function finalizeWithdrawRequest(address account) internal override { - _finalizeFirstStep(); - - // trigger withdraw from Kelp nad unstake from LIDO - WithdrawRequest memory w = v().getWithdrawRequest(account); - PendlePTKelpVault(payable(address(vault))).triggerExtraStep(w.requestId); - - _finalizeSecondStep(); - } - function getDepositParams( uint256 /* depositAmount */, uint256 /* maturity */ @@ -102,20 +83,6 @@ contract Test_PendlePT_rsETH_ETH is BasePendleTest { return abi.encode(d); } - function test_deleverageAccount_splitAccountWithdrawRequest( - uint8 maturityIndex - ) public override { - // list oracle - vm.startPrank(0x02479BFC7Dce53A02e26fE7baea45a0852CB0909); - Deployments.TRADING_MODULE.setPriceOracle( - address(rsETH), - AggregatorV2V3Interface(Harness_PendlePT_rsETH_ETH(address(harness)).baseToUSDOracle()) - ); - vm.stopPrank(); - - super.test_deleverageAccount_splitAccountWithdrawRequest(maturityIndex); - } - function test_exitVault_useWithdrawRequest_postExpiry( uint8 maturityIndex, uint256 depositAmount, bool useForce ) public override { @@ -157,57 +124,9 @@ contract Test_PendlePT_rsETH_ETH is BasePendleTest { "Valuation and Deposit" ); } - - function test_canTriggerExtraStep( - uint8 maturityIndex, uint256 depositAmount, bool useForce - ) public { - depositAmount = uint256(bound(depositAmount, minDeposit, maxDeposit)); - maturityIndex = uint8(bound(maturityIndex, 0, 2)); - address account = makeAddr("account"); - uint256 maturity = maturities[maturityIndex]; - - uint256 vaultShares = enterVault( - account, depositAmount, maturity, getDepositParams(depositAmount, maturity) - ); - - setMaxOracleFreshness(); - vm.warp(expires + 3600); - try Deployments.NOTIONAL.initializeMarkets(harness.getTestVaultConfig().borrowCurrencyId, false) {} catch {} - if (maturity < block.timestamp) { - // Push the vault shares to prime - totalVaultShares[maturity] -= vaultShares; - maturity = maturities[0]; - totalVaultShares[maturity] += vaultShares; - } - - if (useForce) { - _forceWithdraw(account); - } else { - vm.prank(account); - v().initiateWithdraw(""); - } - WithdrawRequest memory w = v().getWithdrawRequest(account); - - assertFalse(PendlePTKelpVault(payable(address(vault))).canTriggerExtraStep(w.requestId)); - assertFalse(PendlePTKelpVault(payable(address(vault))).canFinalizeWithdrawRequest(w.requestId)); - - _finalizeFirstStep(); - - assertTrue(PendlePTKelpVault(payable(address(vault))).canTriggerExtraStep(w.requestId)); - assertFalse(PendlePTKelpVault(payable(address(vault))).canFinalizeWithdrawRequest(w.requestId)); - - PendlePTKelpVault(payable(address(vault))).triggerExtraStep(w.requestId); - - assertFalse(PendlePTKelpVault(payable(address(vault))).canTriggerExtraStep(w.requestId)); - assertFalse(PendlePTKelpVault(payable(address(vault))).canFinalizeWithdrawRequest(w.requestId)); - - _finalizeSecondStep(); - - assertFalse(PendlePTKelpVault(payable(address(vault))).canTriggerExtraStep(w.requestId)); - assertTrue(PendlePTKelpVault(payable(address(vault))).canFinalizeWithdrawRequest(w.requestId)); - } } + contract Harness_PendlePT_rsETH_ETH is PendleStakingHarness { function getVaultName() public pure override returns (string memory) { return 'Pendle:PT rsETH 27JUN2024:[ETH]'; @@ -216,8 +135,8 @@ contract Harness_PendlePT_rsETH_ETH is PendleStakingHarness { function getRequiredOracles() public override view returns ( address[] memory token, address[] memory oracle ) { - token = new address[](2); - oracle = new address[](2); + token = new address[](3); + oracle = new address[](3); // Custom PT Oracle token[0] = ptAddress; @@ -227,10 +146,9 @@ contract Harness_PendlePT_rsETH_ETH is PendleStakingHarness { token[1] = 0x0000000000000000000000000000000000000000; oracle[1] = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419; - // TODO: required in order to support withdraw requests // rsETH - // token[2] = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7; - // oracle[2] = 0x150aab1C3D63a1eD0560B95F23d7905CE6544cCB; + token[2] = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7; + oracle[2] = 0xb676EA4e0A54ffD579efFc1f1317C70d671f2028; } function getTradingPermissions() public pure override returns ( @@ -250,25 +168,21 @@ contract Harness_PendlePT_rsETH_ETH is PendleStakingHarness { return address(new PendlePTKelpVault(marketAddress, ptAddress)); } - function withdrawToken(address /* vault */) public pure override returns (address) { - return stETH; - } - constructor() { - marketAddress = 0x4f43c77872Db6BA177c270986CD30c3381AF37Ee; - ptAddress = 0xB05cABCd99cf9a73b19805edefC5f67CA5d1895E; + marketAddress = 0x6b4740722e46048874d84306B2877600ABCea3Ae; + ptAddress = 0x7bAf258049cc8B9A78097723dc19a8b103D4098F; twapDuration = 15 minutes; // recommended 15 - 30 min useSyOracleRate = true; - baseToUSDOracle = 0x150aab1C3D63a1eD0560B95F23d7905CE6544cCB; tokenOutSy = 0xA1290d69c65A6Fe4DF752f95823fae25cB99e5A7; borrowToken = 0x0000000000000000000000000000000000000000; + baseToUSDOracle = 0xb676EA4e0A54ffD579efFc1f1317C70d671f2028; UniV3Adapter.UniV3SingleData memory u; u.fee = 500; // 0.05 % bytes memory exchangeData = abi.encode(u); uint8 primaryDexId = uint8(DexId.UNISWAP_V3); - setMetadata(StakingMetadata(1, primaryDexId, exchangeData, false)); + setMetadata(StakingMetadata(1, primaryDexId, exchangeData, true)); } } \ No newline at end of file