Last active
September 26, 2022 15:13
-
-
Save rayeaster/056f4b9abe6cb0965cdb301b103f8220 to your computer and use it in GitHub Desktop.
hardhat-based test for uniswap v3 twap oracle manipulation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const hre = require("hardhat"); | |
// const { expect, assert } = require("chai"); | |
// const { BigNumber, utils } = ethers; | |
require("@nomiclabs/hardhat-ethers"); | |
const weth = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; | |
const looks = "0xf4d2888d29D722226FafA5d9B24F9164c092421E"; | |
const wbtc = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; | |
const vusd = "0x677ddbd918637E5F2c79e164D402454dE7dA8619" | |
const usdc = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; | |
const uniswap_v3_usdc_eth_5 = '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640'; | |
const uniswap_v3_usdc_vusd_5 = '0x8dDE0A1481b4A14bC1015A5a8b260ef059E9FD89'; | |
const uniswap_v3_usdc_float_5 = '0x7ee092fd479185dd741e3e6994f255bb3624f765'; | |
const uniswap_v3_wbtc_eth_5 = '0x4585fe77225b41b697c938b018e2ac67ac5a20c0'; | |
const uniswap_v3_looks_eth_30 = "0x4b5ab61593a2401b1075b90c04cbcdd3f87ce011"; | |
const float_whale = "0x372140e9705d6b6F1cf76C28D103637b01E804D9"; | |
const float_token = "0xb05097849BCA421A3f51B249BA6CCa4aF4b97cb9"; | |
const uniswap_v3_router = "0xE592427A0AEce92De3Edee1F18E0157C05861564";//"0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"; | |
describe("oracle-twap", function () { | |
it("Should read twap oracle", async function () { | |
this.timeout(1000000); | |
let blockNumber = await ethers.provider.getBlockNumber(); | |
let _pool = uniswap_v3_usdc_float_5; //uniswap_v3_usdc_vusd_5; | |
const _contract = await ethers.getContractAt("IUniswapV3Pool", _pool); | |
let _token0 = (await _contract.token0()).toString(); | |
let _token1 = (await _contract.token1()).toString(); | |
const token0ERC20 = await ethers.getContractAt("IERC20Metadata", _token0); | |
let _token0Decimal = ethers.utils.parseUnits((await token0ERC20.decimals()).toString(), 0); | |
const token1ERC20 = await ethers.getContractAt("IERC20Metadata", _token1); | |
let _token1Decimal = ethers.utils.parseUnits((await token1ERC20.decimals()).toString(), 0); | |
let _adjustDecimalFactor = Math.pow(10, Math.abs(_token1Decimal - _token0Decimal)); | |
_adjustDecimalFactor = _token1Decimal < _token0Decimal? _adjustDecimalFactor : 1 / _adjustDecimalFactor; | |
//// fetch oracle reading from pool | |
let _oracleAgo = 600;// around 10 minutes | |
let _observeDuration = [_oracleAgo, 0]; | |
let _observations = await _contract.observe(_observeDuration); | |
//// calculate TWAP tick | |
let _observedTick0 = ethers.utils.parseUnits(_observations[0][0].toString(), 0); | |
let _observedTick1 = ethers.utils.parseUnits(_observations[0][1].toString(), 0); | |
let _twapTick = (_observedTick1 - _observedTick0) / _oracleAgo; | |
let _twapPrice0To1 = Math.pow(1.0001, _twapTick) / _adjustDecimalFactor; | |
//// calculate TWAP liquidity https://github.com/Uniswap/v3-periphery/blob/main/contracts/libraries/OracleLibrary.sol#L40 | |
let _observedLiq0 = ethers.utils.parseUnits(_observations[1][0].toString(), 0); | |
let _observedLiq1 = ethers.utils.parseUnits(_observations[1][1].toString(), 0); | |
let _divLiq = (_observedLiq1.sub(_observedLiq0)).toString(); | |
let _twapLiq = _oracleAgo * ((Math.pow(2, 160) - 1) / Math.pow(2, 32)) / _divLiq; | |
//// fetch constant-product invariant (liquidity) reading from pool | |
let _sqrtK = ethers.utils.parseUnits((await _contract.liquidity()).toString(), 0); | |
//// fetch current tick reading from pool and calculate current price range | |
let _slot0 = await _contract.slot0(); | |
//// fetch spot price reading from pool | |
let _sqrtPrice0To1 = ethers.utils.parseUnits(_slot0[0].toString(), 0); | |
let _q0To1 = (Math.pow(_sqrtPrice0To1, 2) / Math.pow(2, 192)) / _adjustDecimalFactor; | |
console.log('Block#' + blockNumber + ' v3 pool[' + _pool + ']: t0=' + (_token0 == vusd? "vusd" : _token0) + ',t1=' + (_token1 == usdc? "usdc" : _token1) + ': _liq=' + _sqrtK + ', _twapLiq=' + _twapLiq + ', _q=' + _q0To1 + ', _twap=' + _twapPrice0To1); | |
//// manipulate the price of float | |
await hre.network.provider.request({method: "hardhat_impersonateAccount", params: [float_whale]}); | |
const floatWhaleSigner = await ethers.provider.getSigner(float_whale); | |
const floatERC20 = await ethers.getContractAt("IERC20AAA", float_token); | |
const whaleFloat = ethers.utils.parseUnits((await floatERC20.balanceOf(float_whale)).toString(), 0); | |
console.log('Float in whale wallet=' + whaleFloat); | |
await floatERC20.connect(floatWhaleSigner).approve(uniswap_v3_router, ethers.utils.parseUnits("1000000", 18)); | |
const v3Router = await ethers.getContractAt("IUniswapV3SwapRouter", uniswap_v3_router); | |
const _minLimitX96 = Math.floor(Math.pow(Math.pow(1.0001, -887270), 0.5) * Math.pow(2, 96)); | |
const _maxLimitX96 = Math.floor(Math.pow(Math.pow(1.0001, 887270), 0.5) * Math.pow(2, 96)); | |
console.log('min limit price=' + _minLimitX96 + ', max limit price=' + _maxLimitX96); | |
await v3Router.connect(floatWhaleSigner).exactInputSingle([float_token, usdc, 500, float_whale, 1663520485, whaleFloat, 0, 0]); | |
await hre.network.provider.request({method: "evm_mine"});// mine a block | |
let _sqrtPrice0To1After = ethers.utils.parseUnits((await _contract.slot0())[0].toString(), 0); | |
let _q0To1After = (Math.pow(_sqrtPrice0To1After, 2) / Math.pow(2, 192)) / _adjustDecimalFactor; | |
let _observationsAfter = await _contract.observe(_observeDuration); | |
let _observedTick0After = ethers.utils.parseUnits(_observationsAfter[0][0].toString(), 0); | |
let _observedTick1After = ethers.utils.parseUnits(_observationsAfter[0][1].toString(), 0); | |
let _twapTickAfter = (_observedTick1After - _observedTick0After) / _oracleAgo; | |
let _twapPrice0To1After = Math.pow(1.0001, _twapTickAfter) / _adjustDecimalFactor; | |
let _observedLiq0After = ethers.utils.parseUnits(_observationsAfter[1][0].toString(), 0); | |
let _observedLiq1After = ethers.utils.parseUnits(_observationsAfter[1][1].toString(), 0); | |
let _divLiqAfter = (_observedLiq1After.sub(_observedLiq0After)).toString(); | |
let _twapLiqAfter = _oracleAgo * ((Math.pow(2, 160) - 1) / Math.pow(2, 32)) / _divLiqAfter; | |
let _sqrtKAfter = ethers.utils.parseUnits((await _contract.liquidity()).toString(), 0); | |
console.log('Block#' + (blockNumber + 1) + ' after manipulation: _liq=' + _sqrtKAfter + ', _twapLiq=' + _twapLiqAfter + ', _q=' + _q0To1After + ', _twap=' + _twapPrice0To1After); | |
assert(1 < 0, '!1 >= 0'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
detail note https://hackmd.io/@re73/HJygX-gbo#What-we-could-do