// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; // OZ imports import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // Uniswap imports import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol"; // Tornado imports import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol"; // Local imports import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol"; import { InstanceData } from "./InstanceRegistry.sol"; struct UniswapV3FeeData { uint24 tornPoolFee; uint24 tokenPoolFee; uint32 timePeriod; } /** * @title UniswapV3FeeOracle * @author AlienTornadosaurusHex * @notice A TORN fee oracle for any Uniswap V3 token pool. */ contract UniswapV3FeeOracle is IFeeOracle { using SafeMath for uint256; /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* @dev The Governance Proxy address */ address public immutable governanceProxyAddress; /* @dev The Uniswap V3 Factory */ IUniswapV3Factory public immutable v3Factory; /* @dev The global pool fee value which will always be used for TORN */ uint24 public globalTornUniswapPoolFee; /* @dev The global time period value which will be used for all calculations */ uint32 public globalUniswapTimePeriod; /* @dev The minimum observation cardinality required for a token */ uint32 public globalMinObservationCardinality; /* @dev The feeData in which the above + token specific will be stored */ mapping(IERC20 => UniswapV3FeeData) public uniswapFeeData; /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ event UniswapPoolFeeUpdated(IERC20 token, uint24 newUniswapPoolFee); event GlobalTornUniswapPoolFeeUpdated(uint24 newUniswapPoolFee); event GlobalUniswapTimePeriodUpdated(uint32 newUniswapTimePeriod); event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ constructor(address _governanceProxyAddress, address _uniswapV3FactoryAddress) public { governanceProxyAddress = _governanceProxyAddress; v3Factory = IUniswapV3Factory(_uniswapV3FactoryAddress); } modifier onlyGovernance() { require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance"); _; } function setGlobalTornUniswapPoolFee(uint24 _newGlobalTornUniswapPoolFee) public virtual onlyGovernance { globalTornUniswapPoolFee = _newGlobalTornUniswapPoolFee; emit GlobalTornUniswapPoolFeeUpdated(_newGlobalTornUniswapPoolFee); } function setGlobalUniswapTimePeriod(uint32 _newGlobalUniswapTimePeriod) public virtual onlyGovernance { globalUniswapTimePeriod = _newGlobalUniswapTimePeriod; emit GlobalUniswapTimePeriodUpdated(_newGlobalUniswapTimePeriod); } function setGlobalMinObservationCardinality(uint16 _newGlobalMinObservationCardinality) public virtual onlyGovernance { globalMinObservationCardinality = _newGlobalMinObservationCardinality; emit GlobalMinObservationCardinalityUpdated(_newGlobalMinObservationCardinality); } function setUniswapPoolFeeForToken(IERC20 _token, uint24 _tokenUniswapPoolFee) public virtual onlyGovernance { uint24 _globalTornUniswapPoolFee = globalTornUniswapPoolFee; uint32 _globalUniswapTimePeriod = globalUniswapTimePeriod; require(_globalTornUniswapPoolFee != 0, "UniswapV3FeeOracle: torn pool fee not initialized"); require(_globalUniswapTimePeriod != 0, "UniswapV3FeeOracle: time period not initialized"); address poolAddress = v3Factory.getPool(address(_token), UniswapV3OracleHelper.WETH, _tokenUniswapPoolFee); require(poolAddress != address(0), "UniswapV3FeeOracle: pool for token and fee does not exist"); (,,,, uint16 observationCardinalityNext,,) = IUniswapV3PoolState(poolAddress).slot0(); require( globalMinObservationCardinality <= observationCardinalityNext, "UniswapV3FeeOracle: pool observation cardinality low" ); uniswapFeeData[_token] = UniswapV3FeeData({ tornPoolFee: _globalTornUniswapPoolFee, tokenPoolFee: _tokenUniswapPoolFee, timePeriod: _globalUniswapTimePeriod }); emit UniswapPoolFeeUpdated(_token, _tokenUniswapPoolFee); } function getFee( IERC20 _torn, ITornadoInstance _instance, InstanceData memory _data, uint64 _feePercent, uint64 _feePercentDivisor ) public view virtual override returns (uint160) { // For safety, we won't allow calculating fees for disabled instances require(_data.isEnabled, "UniswapV3FeeOracle: instance not enabled"); // If fee is 0 return if (_feePercent == 0) return 0; // If it's not an ERC20 it has to be ETH, use the WETH token _data.token = _data.isERC20 ? _data.token : IERC20(UniswapV3OracleHelper.WETH); // After we have decided our token get fee data for it UniswapV3FeeData memory uniFeeData = uniswapFeeData[_data.token]; // Calc price ratio uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens( [address(_torn), address(_data.token)], [uniFeeData.tornPoolFee, uniFeeData.tokenPoolFee], uniFeeData.timePeriod ); // And now all according to legacy calculation return uint160( _instance.denomination().mul(UniswapV3OracleHelper.RATIO_DIVIDER).div(tokenPriceRatio).mul( uint256(_feePercent) ).div(_feePercentDivisor) ); } }