Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Wrapped Staked Eth hook #451

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/hooks/WstETHHook.sol
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i might just be thinking about this differently, but wouldn't it make more sense if this was called _getUnwrapInputRequired instead? Because you are specifying the amount of wrapped token that you want and this function is calculating how much of the unwrapped token you need to input

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking of it as - we're calling the operation wrap and want the result to be wrappedAmount. What's the input required to wrap operation to get wrappedAmount -> thus get wrap input required

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i see: its the input to the wrap function

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 {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last question: when would this be needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oop leftover from weth hook

}
15 changes: 15 additions & 0 deletions src/interfaces/external/IWstETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: 2021 Lido <[email protected]>
// 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);
}
Loading
Loading