# Research about manipulation-protection for on-chain-pricer(V5) ###### tags: `DeFi` `Uniswap` `oracle` `price manipulation` ### What we are looking for - an on-chain oracle source for long-tail token which is not covered by Chainlink feeds to tell us **if the swap pricing is fair or manipulated** - the source should be **gas-efficient** so that it add acceptable overhead to [on-chain pricing for swap](https://github.com/Badger-Finance/on-chain-pricer) - current focus is on Uniswap V3 pools with its geometric-mean TWAP oracle, hopefully we could generalize similar methodology to other dex in future ### Related work from Euler Finance A study around the cost of price manipulation attack on Uniswap V3 pools is shared by Euler Finance https://github.com/euler-xyz/uni-v3-twap-manipulation/blob/master/cost-of-attack.pdf showing theoretically that the upper limit of attack cost per block (`roughly the slippage between the unmanipulated price and the manipulated one, under the assumption that arbitrageurs will profit from the attacker by returning the manipulated price back to normal after the attack`) is a function of these factors: - block numbers in the oracle window`n` (typically 144, e.g.,`30 minutes` which is default setting in [Euler Finance](https://docs.euler.finance/euler-protocol/eulers-default-parameters#twap-length)), - block numbers with manipulated price`m`(typically 1, i.e., one-block attack), - target manipulated oracle reading for given window `TWAP`, - spot price`q` after manipulation, - constant-product invariant`k` (whose square root stored as `liquidity` in Uniswap V3 pool), - **unmanipulated spot price`p`**(assume constant for the rest blocks in the oracle window), - **pool reserve of tokens x**(could be calculated from `liquidity` and `spot price`) $$ p = ({\frac{TWAP^{n}}{q^{m}}})^{\frac{1}{n-m}}=TWAP*(\frac{TWAP}{q})^\frac{m}{n-m}, q = ({\frac{TWAP^{n}}{p^{n -m}}})^{\frac{1}{m}}=TWAP*(\frac{TWAP}{p})^\frac{n-m}{m} $$ $$ cost_{pump} = (\sqrt{k * q} - x_{base}) - p * x_{target} * \frac{(\sqrt{k * q} - x_{base})}{\sqrt{k * q}} $$ $$ cost_{dump} = p * (\sqrt{\frac{k}{q}} - x_{target}) - x_{base} * \frac{(\sqrt{\frac{k}{q}} - x_{target})}{\sqrt{\frac{k}{q}}} $$ Notice the **per block cost** (in denomination of the `base` token) for both dump and pump cases could be reduced to a simpler form, along with **the relation between manipulated oracle reading and spot prices**: $$ cost = \sqrt{k} * \frac{p}{\sqrt{q}} - x_{base} - p * x_{target} + \sqrt{k*q} $$ $$ \frac{TWAP}{q} = (\frac{p}{TWAP})^\frac{n-m}{m} $$ Based on above formula, it might be possible to evaluate and gain insights on the pool oracle trustworthiness (fair or manipulated) for our specific use case of on-chain `self-defending` swap. Euler Finance also creates an **off-chain** tool to programmatically analyse above cost of price manipulation for Uniswap V3 pools https://oracle.euler.finance/ which iteratively binary-search the best trade amount to reach the target manipulated price using Uniswap V3 Quoter contract. From the trade amount and related price after the trade, the tool could estimate the cost for the price manipulation. The code for the tool is open-sourced https://github.com/euler-xyz/euler-oracle-tools A simplified code walk-through of the tool is as following (the core functionality reside in the file https://github.com/euler-xyz/euler-oracle-tools/blob/master/src/utils/trades.js#L133) - fill an array (`size=20`) from 0 to `1 billion`, evenly spaced as the simulation input amount USD value: `[50 million, 50 million * 2, 50 million * 3, ..., 1 billion]` - call Uniswap V3 Quoter **multiple** times using the array as the input amount with necessary conversion (i.e., from `USD $` denomination to `Ether Ξ`) - find the least-changed price (closest to target manipulated price) from Quoter response among all inputs in the array and narrow down binary-search repeatedly(maximum trade value is capped to `1000 Trillion` USD or dig into smaller trade range) until predefined termination condition is hit (either search range too small or trade value less than `$100`) - return the final search result which is also above (pump) or below (dump) the target manipulated price Another interesting work from Euler Finance would be the new https://github.com/euler-xyz/median-oracle which is still in phase of proof-of-concept development thus its actual performance in production is not battle-tested yet. The main goal of median oracle aims to improve current arithmetic/geometric mean based TWAP oracle by using the time-weighted median in a sorted observation window (QuickSelect algorithm) instead of the mean. Though this novel design allows more protection for typical price manipulation in theory (`"outliers", do not have significant impact on the output price until their time-in-effect approaches half of the window size`), there are still some open issues and implementation limits like its worst-case gas consumption. Since it is an oracle so that **periodic updates** (upon each swap likely) would be actively required to make it working. ### Learnings #1 Due to the nature of geometric-mean TWAP used by Uniswap V3 pool, it might require attacker to manipulate spot price to an absurdly gigantic level(as big as more than $10^9$ times of unmanipulated price) for even a relatively small change in TWAP (say`20%`). As shown in following chart for an oracle window around `30` minutes, the smaller block number the manipulation lasts(the less risk attacker suffer from arbitrageur), **the more the spot price change it requires from the attack**. ![Manipulated Spot Price vs target TWAP with n = 144](https://i.imgur.com/06JSJDO.png) Another example where the oracle window is around `10` minutes. It is obvious that the required spot price change for same level TWAP manipulation (say`20%`) is greatly reduced (but still very big compared to the resulting TWAP change) by the decrease of oracle window length, i.e., the shorter the window, the easier for attacker to manipulate. ![Manipulated Spot Price vs target TWAP with n = 46](https://i.imgur.com/VM81LBF.png) ### Learnings #2 Price manipulation would move across multiple ticks to change spot price thus the in-range-liquidity may also change drastically as a result. In a typical successful manipulation scenario, the victim distribution of LP is so tightly concentrated that attacker could manipulate spot price to any value as they wish once current concentrated liqiduity is wiped out (i.e., tick enters the range with near-to-zero liquidity). Consider the [hack case of Rari Pool#23](https://twitter.com/RariCapital/status/1455569653820973057) whose Uniswap V3 oracle(`10` minutes window length) was manipulated at [block#13537922](https://etherscan.io/tx/0x89d0ae4dc1743598a540c4e33917efdce24338723b0fabf34813b79cb0ecf4c5) in last December, the in-range liquidity quick drops to 0 as the spot price manipulated to extremely high (over $10^{50}$`usdc` per `vusd`). It might be also worthy to note that the time-weighted in-range `liquidity oracle` (novelly introduced in Uniswap V3 as `secondsPerLiquidityCumulativeX128s`) of that pool also drops significally as the oracle window passed. This pattern could be used to flag a useful alert when such manipulation occurs. ``` Block#13537921 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=4310550450748223102, _twapLiq=4310550450748108300, _q=0.9890256622060757, _twap=0.9889645411809168 Block#13537922 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=4310550450748108300, _q=3.4025678683638813e+50, _twap=0.9889645411809168 Block#13537923 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=16.216216216216218, _q=3.4025678683638813e+50, _twap=1293.014267803181 Block#13537924 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=12, _q=3.4025678683638813e+50, _twap=16090.243180124226 Block#13537925 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=11.320754716981131, _q=3.4025678683638813e+50, _twap=28790.255636131726 Block#13537926 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=11.11111111111111, _q=3.4025678683638813e+50, _twap=34952.09071347738 Block#13537927 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=9.375000000000002, _q=3.4025678683638813e+50, _twap=243080.17276089598 Block#13537928 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=7.317073170731708, _q=3.4025678683638813e+50, _twap=7977136.358470126 Block#13537929 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=6.0606060606060606, _q=3.4025678683638813e+50, _twap=215633810.52920207 Block#13537930 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=5.714285714285714, _q=3.4025678683638813e+50, _twap=690371228.9979908 Block#13537931 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=5.607476635514018, _q=3.4025678683638813e+50, _twap=1017508297.5742356 Block#13537932 v3 pool[0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89]: t0=vusd,t1=usdc: _liq=0, _twapLiq=4.109589041095891, _q=3.4025678683638813e+50, _twap=1960721141567.6138 ``` ![State Change in manipualtion hack of Rari Pool#23](https://i.imgur.com/Z7yD4Jn.png) ### Learnings #3 There are plenty of DeFi protocols currently use Uniswap V3 TWAP oracle in their core functionalities like lending or derivative trading. - Compound Finance new [`UniswapAnchoredView`](https://compound.finance/governance/proposals/117): default oracle window is [`1800` seconds](https://etherscan.io/address/0xad47d5a59b6d1ca4dc3ebd53693fda7d7449f165#code#F1#L33) - [Synthetix](https://sips.synthetix.io/sips/sip-120/#idexpriceaggregator) atomic exchange use a customized [DexPriceAggregator](https://etherscan.io/address/0xf120f029ac143633d1942e48ae2dfa2036c5786c#code): default oracle window is [`1800` seconds](https://etherscan.io/address/0x5ad055A1F8C936FB0deb7024f1539Bb3eAA8dc3E#code#L530) - [Squeeth of Opyn](https://opyn.gitbook.io/squeeth/contracts/core-contracts/contract-addresses#ethereum-mainnet): default oracle window is [`420` seconds](https://etherscan.io/address/0x3B960E47784150F5a63777201ee2B15253D713e8#code#F1#L86) - [Gelato rebalancing wrapper](https://medium.com/gelato-network/dive-into-g-uni-pools-now-available-on-sorbet-finance-223d48740c9) for Uniswap V3 pool: default oracle window is [`300` seconds](https://github.com/gelatodigital/g-uni-v1-core/blob/master/contracts/abstract/GUniPoolStorage.sol#L101) - Revert Finance [auto-compounder](https://docs.revert.finance/revert/technical-docs/auto-compounder#contract-deployer) for Uniswap V3 LP: default oracle window is [`60` seconds](https://etherscan.io/address/0x5411894842e610c4d0f6ed4c232da689400f94a1#code#F1#L43) Considering the use case for on-chain pricer, it seems `1800` seconds would be a reasonable choice for both oracle safety and freshness. ### What we could do Combined with Uniswap V3 TWAP oracle (for both price and liquidity), we could have boosted confidence in telling if the current pricing is fairly good for swap or not (manipulated): If any of the following two comparison **varies by exceeding some predefined thresholds**, then it suggests the pool is currently in manipulation thus the pricing is not trustworthy. - Comparison#1: the `difference` between reported spot price(tick) and its TWAP value - Comparison#2: the `ratio` of reported in-range liquidity to its TWAP value Let us take a look at another concrete case of [FLOAT-USDC pair](https://info.uniswap.org/#/pools/0x7ee092fd479185dd741e3e6994f255bb3624f765). The liquidity distribution is not optimal and susceptible to manipulation as it is at [block#15561442](https://etherscan.io/block/15561442). For example, one could easily dump `FLOAT` token to push the tick moving towards minimum, i.e., the price would be strikingly low for `USDC per FLOAT`. In the [simulation](https://gist.github.com/rayeaster/056f4b9abe6cb0965cdb301b103f8220) where we tried to sell around `10k` FLOAT token to the pool, it is shown that both above two comparisons deviate greatly in magnitudes after the dump. Note that `_q` and `_twap` (10 minutes window) are price of `FLOAT per USDC`. ``` Block#15561442 v3 pool[0x7ee092fd479185dd741e3e6994f255bb3624f765]: t0=usdc,t1=float: _liq=1583106977149294725, _twapLiq=1583106977149294600, _q=0.8586, _twap=0.8585 Block#15561443 after manipulation: _liq=0, _twapLiq=599.9999999999998, _q=3.402567868363881e+26, _twap=0.9508 ``` ![FLOAT-USDC liquidity distribution](https://i.imgur.com/m1IEQGS.jpg) In terms of default setting for above proposed comparisons, the threshold values need to be robust enough for both popular pairs and exotic ones even under intensely volatile market conditions. Let us take a look at the TWAP(30 minutes window) changing chart of 30+ blocks for [WBTC-WETH pool (0.05% fee)](https://info.uniswap.org/#/pools/0x4585fe77225b41b697c938b018e2ac67ac5a20c0) during market slump on [May 11th](https://etherscan.io/block/14755244) when [Terra collapse](https://blog.chainalysis.com/reports/how-terrausd-collapsed/). One could tell that the **tick difference (blue line)** is well below `100` ticks(around `1%` in pricing) while the **in-range liquidity(red line)** varies around `0.8 to 1.4` times of TWAP readings. ![Pool state change chart of WBTC-WETH 0.05% pool in May 2022](https://i.imgur.com/Z303pPu.png) Another study case with same oracle window(30 minutes) and observation block range(block #14755218 to #14755250) is for [LOOKS-WETH pair(0.3% fee)](https://info.uniswap.org/#/pools/0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011). This altcoin pair suffers bigger volatility when market crash thus the lagging of its TWAP (blue line) is a bit larger (`10%`) at the beginning of the observation period until settled down at the last. Interestingly, the in-range liquidity (red line) follows a similar pattern with a range of `0.79 to 6` times of TWAP readings. ![Pool state change chart of LOOKS-WETH 0.3% pool in May 2022](https://i.imgur.com/xkUecWT.png) A similar analysis could be found here https://github.com/Badger-Finance/on-chain-pricer/issues/6#issuecomment-1273172965 ### Reference: - Uniswap V3 whitepaper https://uniswap.org/whitepaper-v3.pdf - Uniswap V3 math by Atis Elsts https://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf - TWAP oracle Attack analysis by ETH Zurich https://eprint.iacr.org/2022/445.pdf - Uniswap market analysis by Angeris https://arxiv.org/pdf/1911.03380.pdf - Uniswap oracle usage by Alex Kroeger https://alexkroeger.mirror.xyz/87cOAsmHu86iDOZ0z_kqJkU6_iiBU41YBiv5h8fxLps