Skip to content

Instantly share code, notes, and snippets.

@rayeaster
Last active September 26, 2022 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rayeaster/056f4b9abe6cb0965cdb301b103f8220 to your computer and use it in GitHub Desktop.
Save rayeaster/056f4b9abe6cb0965cdb301b103f8220 to your computer and use it in GitHub Desktop.
hardhat-based test for uniswap v3 twap oracle manipulation
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');
});
});
@rayeaster
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment