Omniscia Euler Finance Audit
UniswapV3Oracle Manual Review Findings
UniswapV3Oracle Manual Review Findings
UVO-01M: Inexistent Validation of Observation Cardinality Length
Type | Severity | Location |
---|---|---|
Standard Conformity | UniswapV3Oracle.sol:L40 |
Description:
The Uniswap V3 TWAP system relies on a set of observations made within a finite-length array that is initialized at 1
. This means that a TWAP of a Uniswap V3 pool is insecure to utilize unless its cardinality has been expanded to accommodate for the TWAP window that the caller wishes to query for.
As detailed extensively in the Uniswap V3 documentation, the cardinality of a pool should be increased the longer the TWAP window is desired to be by invoking the IUniswapV3Pool::increaseObservationCardinalityNext
function.
Impact:
A UniswapV3Oracle
may fail to execute properly out-of-the-box due to not validating the cardinality of the pool that it is deployed for.
Example:
44/// @notice Get a quote by calling the pool's TWAP oracle.45/// @param inAmount The amount of `base` to convert.46/// @param base The token that is being priced. Either `tokenA` or `tokenB`.47/// @param quote The token that is the unit of account. Either `tokenB` or `tokenA`.48/// @return The converted amount.49function _getQuote(uint256 inAmount, address base, address quote) internal view override returns (uint256) {50 if (!((base == tokenA && quote == tokenB) || (base == tokenB && quote == tokenA))) {51 revert Errors.PriceOracle_NotSupported(base, quote);52 }53 // Size limitation enforced by the pool.54 if (inAmount > type(uint128).max) revert Errors.PriceOracle_Overflow();55
56 uint32[] memory secondsAgos = new uint32[](2);57 secondsAgos[0] = twapWindow;58
59 // Calculate the mean tick over the twap window.60 (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);61 int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int32(twapWindow));62 return OracleLibrary.getQuoteAtTick(tick, uint128(inAmount), base, quote);63}
Recommendation:
The cardinality required per minute varies from chain-to-chain, and represents the number of samples that should be taken per minute for a particular token to be considered accurate.
For example, in the Ethereum mainnet, a block is generated at a median of 12
seconds meaning that each minute has 5
blocks within. As such, a cardinality of 4-5
is considered acceptable per minute.
The cardinality at which the pool is set to should be evaluated during its UniswapV3Oracle::constructor
and as such, we advise an IUniswapV3Pool::increaseObservationCardinalityNext
function call to be introduced to it with an argument generated via a _cardinaltyPerMinute
input argument.
Alleviation:
The Euler Finance team opted to acknowledge this exhibit as they do not consider it a vulnerability given that the UniswapV3Oracle
requires further preparation beyond cardinality before it is deployed and utilized.
As such, we consider this exhibit as a safely acknowledged usability enhancement.
UVO-02M: Insecure Typecasting Operation (TWAP)
Type | Severity | Location |
---|---|---|
Input Sanitization | UniswapV3Oracle.sol:L61 |
Description:
The UniswapV3Oracle::_getQuote
function will cast its configured twapWindow
to an int32
data type which the uint32
data type may not fit into.
Impact:
An incorrectly configured twapWindow
would result in an incorrect calculation of the arithmetic mean tick. To note, a severity of informational
has been assigned as the likelihood of such an incident in a production environment is low due to the impossibly-high required number of observations necessary for the IUniswapV3Pool::observe
lookup to succeed.
Example:
44/// @notice Get a quote by calling the pool's TWAP oracle.45/// @param inAmount The amount of `base` to convert.46/// @param base The token that is being priced. Either `tokenA` or `tokenB`.47/// @param quote The token that is the unit of account. Either `tokenB` or `tokenA`.48/// @return The converted amount.49function _getQuote(uint256 inAmount, address base, address quote) internal view override returns (uint256) {50 if (!((base == tokenA && quote == tokenB) || (base == tokenB && quote == tokenA))) {51 revert Errors.PriceOracle_NotSupported(base, quote);52 }53 // Size limitation enforced by the pool.54 if (inAmount > type(uint128).max) revert Errors.PriceOracle_Overflow();55
56 uint32[] memory secondsAgos = new uint32[](2);57 secondsAgos[0] = twapWindow;58
59 // Calculate the mean tick over the twap window.60 (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);61 int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int32(twapWindow));62 return OracleLibrary.getQuoteAtTick(tick, uint128(inAmount), base, quote);63}
Recommendation:
We advise the UniswapV3Oracle::constructor
to ensure that the _twapWindow
being configured is less-than-or-equal-to the value of type(int32).max
which is less than the maximum value of a uint32
variable.
Alleviation:
The UniswapV3Oracle::constructor
was updated to further sanitize the _twapWindow
as being a value that does not exceed the maximum representable by the int32
data type, effectively alleviating this exhibit.
UVO-03M: Insecure Calculation of Mean Tick
Type | Severity | Location |
---|---|---|
Mathematical Operations | UniswapV3Oracle.sol:L61 |
Description:
In the original Uniswap V3 implementation of the OracleLibrary::consult
function, the delta between two cumulative tick values is permitted to underflow. Additionally, the median tick
calculated should always round towards negative infinity as otherwise a rounding error may result in a rounding operation that would over-evaluate the tick and thus potential quote.
Impact:
Observations by the UniswapV3Oracle::_getQuote
may fail to execute under extreme cumulative tick circumstances, and will incorrectly calculate the arithmetic mean tick when the delta between the cumulative values is negative.
Example:
44/// @notice Get a quote by calling the pool's TWAP oracle.45/// @param inAmount The amount of `base` to convert.46/// @param base The token that is being priced. Either `tokenA` or `tokenB`.47/// @param quote The token that is the unit of account. Either `tokenB` or `tokenA`.48/// @return The converted amount.49function _getQuote(uint256 inAmount, address base, address quote) internal view override returns (uint256) {50 if (!((base == tokenA && quote == tokenB) || (base == tokenB && quote == tokenA))) {51 revert Errors.PriceOracle_NotSupported(base, quote);52 }53 // Size limitation enforced by the pool.54 if (inAmount > type(uint128).max) revert Errors.PriceOracle_Overflow();55
56 uint32[] memory secondsAgos = new uint32[](2);57 secondsAgos[0] = twapWindow;58
59 // Calculate the mean tick over the twap window.60 (int56[] memory tickCumulatives,) = IUniswapV3Pool(pool).observe(secondsAgos);61 int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int32(twapWindow));62 return OracleLibrary.getQuoteAtTick(tick, uint128(inAmount), base, quote);63}
Recommendation:
We advise the code to be made functionally identical to its original Uniswap V3 counterpart, wrapping the subtraction in an unchecked
code block and ensuring that the resulting tick
rounds towards negative infinity by performing the conditional and subtraction referenced by this exhibit.
Alleviation:
The downward-rounding mechanism has been copied based on the OracleLibrary
of Uniswap V3 as advised, however, the unchecked
statement advised was not introduced as the official Uniswap V3 implementation deviates between its v0.8
and non-v0.8
variants.
We consider both the v0.8
and the non-v0.8
variants as correct with checked arithmetic providing additional peace of mind rendering this exhibit fully addressed.
UVO-04M: Potentially Insecure TWAP Window
Type | Severity | Location |
---|---|---|
Standard Conformity | UniswapV3Oracle.sol:L15 |
Description:
The minimum TWAP window enforced by the UniswapV3Oracle
is insecure for low liquidity pools and from a Proof-of-Stake perspective as it is likely for a single validator to generate all blocks within the minimum TWAP window.
Impact:
The present minimum TWAP window recommendation may be insufficient for certain use cases and under a Proof-of-Stake scheme.
Example:
14/// @dev The minimum length of the TWAP window is 1 minute.15uint32 internal constant MIN_TWAP_WINDOW = 60 seconds;
Recommendation:
We advise the minimum to be increased to a figure of 30 minutes
, ensuring a sufficient order book depth is observed in the AMM pair and minimizing the risk of a PoS validator manipulating the transactions within the blocks they are mining to execute a TWAP manipulation attack with no downside.
Alleviation:
The MIN_TWAP_WINDOW
was updated to 5 minutes
and PoS related warnings were introduced to the contract's documentation, rendering this exhibit as addressed to the greatest extent it can be.