We audited the Uniswap/v4-periphery repository at commit 9628c36.
In scope were the following files:
src
├── base
│ └── hooks
│ └── BaseTokenWrapperHook.sol
├── hooks
│ ├── WETHHook.sol
│ └── WstETHHook.sol
└── utils
└── BaseHook.sol
This system implements a set of token wrapper hooks for Uniswap V4, enabling seamless wrapping and unwrapping of tokens within decentralized exchange pools. The architecture consists of a base hook contract that enforces core wrapping logic, with specialized implementations for specific tokens such as WETH and wstETH. It ensures that liquidity operations are restricted while allowing automated token conversions during swaps. By facilitating automated wrapping and unwrapping, these hooks enhance the usability and composability of Uniswap V4. The modular design provides flexibility for supporting additional token wrapping mechanisms in the future while maintaining robust security measures and trust assumptions.
BaseTokenWrapperHook
The BaseTokenWrapperHook
contract serves as the foundational layer for the token-wrapping logic in Uniswap V4 pools and performs the following key functions:
WETHHook
The WETHHook
contract extends BaseTokenWrapperHook
and performs the following key functions:
WstETHHook
The WstETHHook
contract extends BaseTokenWrapperHook
to support dynamic exchange rate conversions between stETH and wstETH, and performs the following key functions:
The security of the system under review depends on the correctness of the core infrastructure of Uniswap V4 and the Pool Manager’s execution of swaps without vulnerabilities. It assumes that external contracts, such as WETH and wstETH, function as intended and do not introduce security flaws. Furthermore, the system relies on the accuracy of token conversions and the expected exchange rates for stETH and wstETH. Any deviation from these assumptions could impact the system's intended functionality.
When performing exact output swaps, the _getWrapInputRequired
function calculates the amount of underlying tokens required to be wrapped in order to achieve the exact desired output amount. Since conversions for wrapping round down (in favor of the protocol), the inverse function, which determines how much underlying must be wrapped to achieve the exact output, must round up.
In the wstEthHook
contract, the getStETHByWstETH
function rounds in the wrong direction. The input amount is calculated by getStETHByWstETH
, which in turn calls getPooledEthByShares
, a function that rounds down. As a result, after wrapping, if one more asset is required to achieve the exact output, the pool manager attempts to swap through the pool. If the pool lacks sufficient liquidity, the swap attempt will revert. This issue also arises when unwrapping wstETH
to achieve an exact output, due to the rounding in the _getUnwrapInputRequired
function.
In order to account for the rounding direction, when wrapping, consider dividing the desired output by the conversion rate used for wrapping and adding 1 if there is a remainder. In addition, consider adding comments above the _getWrapInputRequired
and _getUnwrapInputRequired
functions to clarify the correct rounding direction when wrapping and unwrapping assets.
Update: Resolved in pull request #460. The Uniswap team stated:
After much evaluation of the rounding logic in stETH and fork tests, we came to the conclusion that it is not possible to safely and efficiently precalculate the input amount required precisely and robustly, so opted to disable exact output for this hook.
Within BaseHook.sol
, the getHookPermissions
function is missing documentation for the return value using @return
.
Consider adding documentation for the return value following the Ethereum Natural Specification Format (NatSpec).
Update: Resolved in pull request #461.
Pragma directives should be fixed to clearly identify the Solidity version with which the contracts will be compiled. Throughout the codebase, multiple instances of floating pragma directives were identified:
BaseHook.sol
has the solidity ^0.8.0
floating pragma directive.BaseTokenWrapperHook.sol
has the solidity ^0.8.0
floating pragma directive.WETHHook.sol
has the solidity ^0.8.0
floating pragma directive.WstETHHook.sol
has the solidity ^0.8.0
floating pragma directive.Consider using fixed pragma directives.
Update: Resolved in pull request #461.
Named return variables are a way to declare variables that are meant to be used within a function's body for the purpose of being returned as that function's output. They are an alternative to explicit in-line return
statements.
Within BaseTokenWrapperHook.sol
, multiple instances of unused named return variables were identified:
selector
return variable of the _beforeSwap
functionlpFeeOverride
return variable of the _beforeSwap
functionConsider either using or removing any unused named return variables.
Update: Resolved in pull request #461.
The token wrapper hooks enhance Uniswap V4 by enabling the automatic wrapping and unwrapping of tokens, thereby improving usability and composability. During the audit, one high-severity issue was identified, and various recommendations aimed at enhancing code consistency and readability were made.
Throughout the audit process, the Uniswap team was highly cooperative and provided clear explanations.