From 69dda630d96e59252223f0ae3d5ab303ac218e3e Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Wed, 19 Feb 2025 12:49:23 -0500 Subject: [PATCH 1/3] feat: add Wrapped Staked Eth hook --- src/hooks/WstETHHook.sol | 73 +++++++ src/interfaces/external/IWstETH.sol | 15 ++ test/hooks/WstETHHook.t.sol | 316 ++++++++++++++++++++++++++++ test/mocks/MockWstETH.sol | 84 ++++++++ 4 files changed, 488 insertions(+) create mode 100644 src/hooks/WstETHHook.sol create mode 100644 src/interfaces/external/IWstETH.sol create mode 100644 test/hooks/WstETHHook.t.sol create mode 100644 test/mocks/MockWstETH.sol diff --git a/src/hooks/WstETHHook.sol b/src/hooks/WstETHHook.sol new file mode 100644 index 000000000..03c9dc755 --- /dev/null +++ b/src/hooks/WstETHHook.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {BaseTokenWrapperHook} from "../base/hooks/BaseTokenWrapperHook.sol"; +import {IWstETH} from "../interfaces/external/IWstETH.sol"; + +/// @title Wrapped Staked ETH (wstETH) Hook +/// @notice Hook for wrapping/unwrapping stETH to wstETH in Uniswap V4 pools +/// @dev Implements dynamic exchange rate wrapping/unwrapping between stETH and wstETH +/// @dev wstETH represents stETH with accrued staking rewards, maintaining a dynamic exchange rate +contract WstETHHook is BaseTokenWrapperHook { + /// @notice Thrown when unwrapping wstETH to stETH fails + error WithdrawFailed(); + + /// @notice The wstETH contract used for wrapping/unwrapping operations + IWstETH public immutable wstETH; + + /// @notice Creates a new wstETH wrapper hook + /// @param _manager The Uniswap V4 pool manager + /// @param _wsteth The wstETH contract address + /// @dev Initializes with wstETH as wrapper token and stETH as underlying token + constructor(IPoolManager _manager, IWstETH _wsteth) + BaseTokenWrapperHook( + _manager, + Currency.wrap(address(_wsteth)), // wrapper token is wsteth + Currency.wrap(_wsteth.stETH()) // underlying token is stETH + ) + { + wstETH = _wsteth; + ERC20(Currency.unwrap(underlyingCurrency)).approve(address(wstETH), type(uint256).max); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Wraps stETH to wstETH + /// @param underlyingAmount Amount of stETH to wrap + /// @return Amount of wstETH received + function _deposit(uint256 underlyingAmount) internal override returns (uint256) { + return wstETH.wrap(underlyingAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Unwraps wstETH to stETH + /// @param wrapperAmount Amount of wstETH to unwrap + /// @return Amount of stETH received + function _withdraw(uint256 wrapperAmount) internal override returns (uint256) { + return wstETH.unwrap(wrapperAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Calculates how much stETH is needed to receive a specific amount of wstETH + /// @param wrappedAmount Desired amount of wstETH + /// @return Amount of stETH required + /// @dev Uses current stETH/wstETH exchange rate for calculation + function _getWrapInputRequired(uint256 wrappedAmount) internal view override returns (uint256) { + return wstETH.getStETHByWstETH(wrappedAmount); + } + + /// @inheritdoc BaseTokenWrapperHook + /// @notice Calculates how much wstETH is needed to receive a specific amount of stETH + /// @param underlyingAmount Desired amount of stETH + /// @return Amount of wstETH required + /// @dev Uses current stETH/wstETH exchange rate for calculation + function _getUnwrapInputRequired(uint256 underlyingAmount) internal view override returns (uint256) { + return wstETH.getWstETHByStETH(underlyingAmount); + } + + /// @notice Required to receive ETH from unwrapping operations + /// @dev Needed for protocol integrations that might send ETH to this contract + receive() external payable {} +} diff --git a/src/interfaces/external/IWstETH.sol b/src/interfaces/external/IWstETH.sol new file mode 100644 index 000000000..a8a76483f --- /dev/null +++ b/src/interfaces/external/IWstETH.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2021 Lido +// https://github.com/lidofinance/core/blob/master/contracts/0.6.12/WstETH.sol + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity ^0.8.0; + +interface IWstETH { + function wrap(uint256 _stETHAmount) external returns (uint256); + function unwrap(uint256 _wstETHAmount) external returns (uint256); + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256); + function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256); + function stETH() external view returns (address); +} diff --git a/test/hooks/WstETHHook.t.sol b/test/hooks/WstETHHook.t.sol new file mode 100644 index 000000000..42dc58564 --- /dev/null +++ b/test/hooks/WstETHHook.t.sol @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; +import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; +import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; +import {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.sol"; +import {CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol"; +import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; +import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; +import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; +import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; +import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; +import {Test} from "forge-std/Test.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; + +import {BaseTokenWrapperHook} from "../../src/base/hooks/BaseTokenWrapperHook.sol"; +import {WstETHHook} from "../../src/hooks/WstETHHook.sol"; +import {IWstETH} from "../../src/interfaces/external/IWstETH.sol"; +import {MockWstETH} from "../mocks/MockWstETH.sol"; + +contract WstETHHookTest is Test, Deployers { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + + WstETHHook public hook; + MockWstETH public wstETH; + MockERC20 public stETH; + PoolKey poolKey; + uint160 initSqrtPriceX96; + + // Users + address alice = makeAddr("alice"); + address bob = makeAddr("bob"); + + event Transfer(address indexed from, address indexed to, uint256 amount); + + function setUp() public { + deployFreshManagerAndRouters(); + + // Deploy mock stETH and wstETH + stETH = new MockERC20("Liquid staked Ether", "stETH", 18); + wstETH = new MockWstETH(address(stETH)); + + // Deploy WstETH hook + hook = WstETHHook( + payable( + address( + uint160( + type(uint160).max & clearAllHookPermissionsMask | Hooks.BEFORE_SWAP_FLAG + | Hooks.BEFORE_ADD_LIQUIDITY_FLAG | Hooks.BEFORE_SWAP_RETURNS_DELTA_FLAG + | Hooks.BEFORE_INITIALIZE_FLAG + ) + ) + ) + ); + deployCodeTo("WstETHHook", abi.encode(manager, wstETH), address(hook)); + + // Create pool key for stETH/wstETH + poolKey = PoolKey({ + currency0: Currency.wrap(address(stETH)), + currency1: Currency.wrap(address(wstETH)), + fee: 0, // Must be 0 for wrapper pools + tickSpacing: 60, + hooks: IHooks(address(hook)) + }); + + // Initialize pool at 1:1 price + initSqrtPriceX96 = uint160(TickMath.getSqrtPriceAtTick(0)); + manager.initialize(poolKey, initSqrtPriceX96); + + // Give users some tokens + stETH.mint(alice, 100 ether); + stETH.mint(bob, 100 ether); + stETH.mint(address(this), 200 ether); + stETH.mint(address(wstETH), 200 ether); + + wstETH.mint(alice, 100 ether); + wstETH.mint(bob, 100 ether); + wstETH.mint(address(this), 200 ether); + + _addUnrelatedLiquidity(); + } + + function test_initialization() public view { + assertEq(address(hook.wstETH()), address(wstETH)); + assertEq(Currency.unwrap(hook.wrapperCurrency()), address(wstETH)); + assertEq(Currency.unwrap(hook.underlyingCurrency()), address(stETH)); + } + + function test_wrap_exactInput() public { + uint256 wrapAmount = 1 ether; + uint256 expectedOutput = wstETH.getWstETHByStETH(wrapAmount); + + vm.startPrank(alice); + stETH.approve(address(swapRouter), type(uint256).max); + + uint256 aliceStethBefore = stETH.balanceOf(alice); + uint256 aliceWstethBefore = wstETH.balanceOf(alice); + uint256 managerStethBefore = stETH.balanceOf(address(manager)); + uint256 managerWstethBefore = wstETH.balanceOf(address(manager)); + + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + swapRouter.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: true, // stETH (0) to wstETH (1) + amountSpecified: -int256(wrapAmount), + sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1 + }), + testSettings, + "" + ); + + vm.stopPrank(); + + assertEq(aliceStethBefore - stETH.balanceOf(alice), wrapAmount); + assertEq(wstETH.balanceOf(alice) - aliceWstethBefore, expectedOutput); + assertEq(managerStethBefore, stETH.balanceOf(address(manager))); + assertEq(managerWstethBefore, wstETH.balanceOf(address(manager))); + } + + function test_unwrap_exactInput() public { + uint256 unwrapAmount = 1 ether; + uint256 expectedOutput = wstETH.getStETHByWstETH(unwrapAmount); + + vm.startPrank(alice); + wstETH.approve(address(swapRouter), type(uint256).max); + + uint256 aliceStethBefore = stETH.balanceOf(alice); + uint256 aliceWstethBefore = wstETH.balanceOf(alice); + uint256 managerStethBefore = stETH.balanceOf(address(manager)); + uint256 managerWstethBefore = wstETH.balanceOf(address(manager)); + + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + swapRouter.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: false, // wstETH (1) to stETH (0) + amountSpecified: -int256(unwrapAmount), + sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1 + }), + testSettings, + "" + ); + + vm.stopPrank(); + + assertEq(stETH.balanceOf(alice) - aliceStethBefore, expectedOutput); + assertEq(aliceWstethBefore - wstETH.balanceOf(alice), unwrapAmount); + assertEq(managerStethBefore, stETH.balanceOf(address(manager))); + assertEq(managerWstethBefore, wstETH.balanceOf(address(manager))); + } + + function test_wrap_exactOutput() public { + uint256 wrapAmount = 1 ether; + uint256 expectedInput = wstETH.getStETHByWstETH(wrapAmount); + + vm.startPrank(alice); + stETH.approve(address(swapRouter), type(uint256).max); + + uint256 aliceStethBefore = stETH.balanceOf(alice); + uint256 aliceWstethBefore = wstETH.balanceOf(alice); + uint256 managerStethBefore = stETH.balanceOf(address(manager)); + uint256 managerWstethBefore = wstETH.balanceOf(address(manager)); + + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + swapRouter.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: true, // stETH (0) to wstETH (1) + amountSpecified: int256(wrapAmount), + sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1 + }), + testSettings, + "" + ); + + vm.stopPrank(); + + assertEq(aliceStethBefore - stETH.balanceOf(alice), expectedInput); + assertEq(wstETH.balanceOf(alice) - aliceWstethBefore, wrapAmount); + assertEq(managerStethBefore, stETH.balanceOf(address(manager))); + assertEq(managerWstethBefore, wstETH.balanceOf(address(manager))); + } + + function test_unwrap_exactOutput() public { + uint256 unwrapAmount = 1 ether; + uint256 expectedInput = wstETH.getWstETHByStETH(unwrapAmount); + + vm.startPrank(alice); + wstETH.approve(address(swapRouter), type(uint256).max); + + uint256 aliceStethBefore = stETH.balanceOf(alice); + uint256 aliceWstethBefore = wstETH.balanceOf(alice); + uint256 managerStethBefore = stETH.balanceOf(address(manager)); + uint256 managerWstethBefore = wstETH.balanceOf(address(manager)); + + PoolSwapTest.TestSettings memory testSettings = + PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}); + swapRouter.swap( + poolKey, + IPoolManager.SwapParams({ + zeroForOne: false, // wstETH (1) to stETH (0) + amountSpecified: int256(unwrapAmount), + sqrtPriceLimitX96: TickMath.MAX_SQRT_PRICE - 1 + }), + testSettings, + "" + ); + + vm.stopPrank(); + + assertEq(stETH.balanceOf(alice) - aliceStethBefore, unwrapAmount); + assertEq(aliceWstethBefore - wstETH.balanceOf(alice), expectedInput); + assertEq(managerStethBefore, stETH.balanceOf(address(manager))); + assertEq(managerWstethBefore, wstETH.balanceOf(address(manager))); + } + + function test_revertAddLiquidity() public { + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hook), + IHooks.beforeAddLiquidity.selector, + abi.encodeWithSelector(BaseTokenWrapperHook.LiquidityNotAllowed.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + + modifyLiquidityRouter.modifyLiquidity( + poolKey, + IPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000e18, + salt: bytes32(0) + }), + "" + ); + } + + function test_revertInvalidPoolInitialization() public { + // Try to initialize with non-zero fee + PoolKey memory invalidKey = PoolKey({ + currency0: Currency.wrap(address(stETH)), + currency1: Currency.wrap(address(wstETH)), + fee: 3000, // Invalid: must be 0 + tickSpacing: 60, + hooks: IHooks(address(hook)) + }); + + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hook), + IHooks.beforeInitialize.selector, + abi.encodeWithSelector(BaseTokenWrapperHook.InvalidPoolFee.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + manager.initialize(invalidKey, initSqrtPriceX96); + + // Try to initialize with wrong token pair + MockERC20 randomToken = new MockERC20("Random", "RND", 18); + // sort tokens + (Currency currency0, Currency currency1) = address(randomToken) < address(wstETH) + ? (Currency.wrap(address(randomToken)), Currency.wrap(address(wstETH))) + : (Currency.wrap(address(wstETH)), Currency.wrap(address(randomToken))); + invalidKey = + PoolKey({currency0: currency0, currency1: currency1, fee: 0, tickSpacing: 60, hooks: IHooks(address(hook))}); + + vm.expectRevert( + abi.encodeWithSelector( + CustomRevert.WrappedError.selector, + address(hook), + IHooks.beforeInitialize.selector, + abi.encodeWithSelector(BaseTokenWrapperHook.InvalidPoolToken.selector), + abi.encodeWithSelector(Hooks.HookCallFailed.selector) + ) + ); + manager.initialize(invalidKey, initSqrtPriceX96); + } + + function _addUnrelatedLiquidity() internal { + // Create a hookless pool key for stETH/wstETH + PoolKey memory unrelatedPoolKey = PoolKey({ + currency0: Currency.wrap(address(stETH)), + currency1: Currency.wrap(address(wstETH)), + fee: 100, + tickSpacing: 60, + hooks: IHooks(address(0)) + }); + + manager.initialize(unrelatedPoolKey, uint160(TickMath.getSqrtPriceAtTick(0))); + + stETH.approve(address(modifyLiquidityRouter), type(uint256).max); + wstETH.approve(address(modifyLiquidityRouter), type(uint256).max); + modifyLiquidityRouter.modifyLiquidity( + unrelatedPoolKey, + IPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000e18, + salt: bytes32(0) + }), + "" + ); + } +} diff --git a/test/mocks/MockWstETH.sol b/test/mocks/MockWstETH.sol new file mode 100644 index 000000000..3f202f077 --- /dev/null +++ b/test/mocks/MockWstETH.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; +import {IWstETH} from "../../src/interfaces/external/IWstETH.sol"; + +/// @title Mock Wrapped Staked ETH +/// @notice Mock implementation of wstETH for testing +/// @dev Uses a fixed 1.1 stETH/wstETH exchange rate +contract MockWstETH is MockERC20, IWstETH { + /// @notice The underlying stETH token + address public immutable stETH; + + /// @notice Fixed exchange rate: 1 wstETH = 1.1 stETH + uint256 public constant EXCHANGE_RATE = 11e17; + + /// @notice Creates a new mock wstETH + /// @param _stETH Address of the mock stETH token + constructor(address _stETH) MockERC20("Wrapped Staked ETH", "wstETH", 18) { + stETH = _stETH; + } + + /// @notice Wraps stETH to wstETH + /// @param _stETHAmount Amount of stETH to wrap + /// @return Amount of wstETH received + function wrap(uint256 _stETHAmount) external returns (uint256) { + // Transfer stETH from sender + MockERC20(stETH).transferFrom(msg.sender, address(this), _stETHAmount); + + // Calculate wstETH amount using exchange rate + uint256 wstETHAmount = getWstETHByStETH(_stETHAmount); + + // Mint wstETH to sender + _mint(msg.sender, wstETHAmount); + + return wstETHAmount; + } + + /// @notice Unwraps wstETH to stETH + /// @param _wstETHAmount Amount of wstETH to unwrap + /// @return Amount of stETH received + function unwrap(uint256 _wstETHAmount) external returns (uint256) { + // Burn wstETH from sender + _burn(msg.sender, _wstETHAmount); + + // Calculate stETH amount using exchange rate + uint256 stETHAmount = getStETHByWstETH(_wstETHAmount); + + // Transfer stETH to sender + MockERC20(stETH).transfer(msg.sender, stETHAmount); + + return stETHAmount; + } + + function getWstETHByStETH(uint256 _stETHAmount) public pure returns (uint256) { + if (_stETHAmount == 0) return 0; + // Multiply first to maintain precision + uint256 numerator = _stETHAmount * 1e18; + // Add half the denominator for proper rounding + uint256 rounded = (numerator + EXCHANGE_RATE / 2) / EXCHANGE_RATE; + return rounded; + } + + function getStETHByWstETH(uint256 _wstETHAmount) public pure returns (uint256) { + if (_wstETHAmount == 0) return 0; + // Multiply first to maintain precision + uint256 numerator = _wstETHAmount * EXCHANGE_RATE; + // Add half the denominator for proper rounding + uint256 rounded = (numerator + 1e18 / 2) / 1e18; + return rounded; + } + + /// @notice Returns stETH per wstETH exchange rate + /// @return Exchange rate with 18 decimals + function stEthPerToken() external pure returns (uint256) { + return EXCHANGE_RATE; + } + + /// @notice Returns wstETH per stETH exchange rate + /// @return Exchange rate with 18 decimals + function tokensPerStEth() external pure returns (uint256) { + return 1e36 / EXCHANGE_RATE; + } +} From 7f4b055e336b704a14225aac80704cd941d3b9dd Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Thu, 20 Feb 2025 12:56:00 -0500 Subject: [PATCH 2/3] fix: respond to comments --- src/base/hooks/BaseTokenWrapperHook.sol | 1 + src/hooks/WETHHook.sol | 2 -- src/hooks/WstETHHook.sol | 5 +---- test/hooks/WstETHHook.t.sol | 1 - 4 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/base/hooks/BaseTokenWrapperHook.sol b/src/base/hooks/BaseTokenWrapperHook.sol index e70315517..ec439b8a6 100644 --- a/src/base/hooks/BaseTokenWrapperHook.sol +++ b/src/base/hooks/BaseTokenWrapperHook.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import { diff --git a/src/hooks/WETHHook.sol b/src/hooks/WETHHook.sol index c3faa2f69..923e55386 100644 --- a/src/hooks/WETHHook.sol +++ b/src/hooks/WETHHook.sol @@ -13,8 +13,6 @@ contract WETHHook is BaseTokenWrapperHook { /// @notice The WETH9 contract WETH public immutable weth; - error WithdrawFailed(); - /// @notice Creates a new WETH wrapper hook /// @param _manager The Uniswap V4 pool manager /// @param _weth The WETH9 contract address diff --git a/src/hooks/WstETHHook.sol b/src/hooks/WstETHHook.sol index 03c9dc755..359458784 100644 --- a/src/hooks/WstETHHook.sol +++ b/src/hooks/WstETHHook.sol @@ -8,13 +8,10 @@ import {BaseTokenWrapperHook} from "../base/hooks/BaseTokenWrapperHook.sol"; import {IWstETH} from "../interfaces/external/IWstETH.sol"; /// @title Wrapped Staked ETH (wstETH) Hook -/// @notice Hook for wrapping/unwrapping stETH to wstETH in Uniswap V4 pools +/// @notice Hook for wrapping/unwrapping stETH/wstETH in Uniswap V4 pools /// @dev Implements dynamic exchange rate wrapping/unwrapping between stETH and wstETH /// @dev wstETH represents stETH with accrued staking rewards, maintaining a dynamic exchange rate contract WstETHHook is BaseTokenWrapperHook { - /// @notice Thrown when unwrapping wstETH to stETH fails - error WithdrawFailed(); - /// @notice The wstETH contract used for wrapping/unwrapping operations IWstETH public immutable wstETH; diff --git a/test/hooks/WstETHHook.t.sol b/test/hooks/WstETHHook.t.sol index 42dc58564..cdb329f4d 100644 --- a/test/hooks/WstETHHook.t.sol +++ b/test/hooks/WstETHHook.t.sol @@ -13,7 +13,6 @@ import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol"; import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; -import {Test} from "forge-std/Test.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; From bed95d2f483fd38da63a30cc19b5e34f24d7f40b Mon Sep 17 00:00:00 2001 From: Mark Toda Date: Fri, 21 Feb 2025 18:15:40 -0500 Subject: [PATCH 3/3] feat: add staked eth pool --- src/hooks/WstETHHook.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/hooks/WstETHHook.sol b/src/hooks/WstETHHook.sol index 359458784..0836fc6f4 100644 --- a/src/hooks/WstETHHook.sol +++ b/src/hooks/WstETHHook.sol @@ -63,8 +63,4 @@ contract WstETHHook is BaseTokenWrapperHook { function _getUnwrapInputRequired(uint256 underlyingAmount) internal view override returns (uint256) { return wstETH.getWstETHByStETH(underlyingAmount); } - - /// @notice Required to receive ETH from unwrapping operations - /// @dev Needed for protocol integrations that might send ETH to this contract - receive() external payable {} }