LCOV - code coverage report
Current view: top level - v2 - UniswapFeeOracle.sol (source / functions) Hit Total Coverage
Test: lcov.info Lines: 30 30 100.0 %
Date: 2023-06-19 00:07:36 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // SPDX-License-Identifier: MIT
       2             : 
       3             : pragma solidity ^0.6.12;
       4             : pragma experimental ABIEncoderV2;
       5             : 
       6             : import { console2 } from "forge-std/console2.sol";
       7             : // OZ imports
       8             : 
       9             : import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
      10             : import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
      11             : 
      12             : // Uniswap imports
      13             : 
      14             : import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
      15             : import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol";
      16             : 
      17             : // Tornado imports
      18             : 
      19             : import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
      20             : 
      21             : // Local imports
      22             : 
      23             : import { IUniswapV2Pair } from "./interfaces/IUniswapV2Pair.sol";
      24             : 
      25             : import { IFeeOracle, InstanceWithFee } from "./interfaces/IFeeOracle.sol";
      26             : 
      27             : import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
      28             : 
      29             : import { UniswapV2OracleLibrary } from "./libraries/UniswapV2OracleLibrary.sol";
      30             : 
      31             : import { FixedPoint } from "./libraries/FixedPoint.sol";
      32             : 
      33             : import { InstanceState } from "./InstanceRegistry.sol";
      34             : 
      35             : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OUR CONTRACT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      36             : 
      37             : struct TWAPData {
      38             :     FixedPoint.uq112x112 averagePrice;
      39             :     uint32 updatedTimestamp;
      40             : }
      41             : 
      42             : contract UniswapFeeOracle is IFeeOracle {
      43             :     using SafeMath for uint256;
      44             :     using FixedPoint for *;
      45             : 
      46             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      47             : 
      48             :     /**
      49             :      * @notice The precision when calculating TWAP without some factor is 3 digits. We use this to scale,
      50             :      * while being aware of uint224 max size.
      51             :      */
      52             :     uint256 public constant Q112Ratio = 1_000_000_000;
      53             : 
      54             :     /**
      55             :      * @notice Address of the Uniswap V2 TORN/ETH pool
      56             :      */
      57             :     address public constant UNIV2_TORN_ETH_ADDR = 0x0C722a487876989Af8a05FFfB6e32e45cc23FB3A;
      58             : 
      59             :     /**
      60             :      * @notice The Governance Proxy address
      61             :      */
      62             :     address public immutable governanceProxyAddress;
      63             : 
      64             :     /**
      65             :      * @notice The Fee Oracle Manager address
      66             :      */
      67             :     address public feeOracleManagerAddress;
      68             : 
      69             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V3 ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      70             : 
      71             :     /**
      72             :      * @notice Uniswap V3 pool fees for each token registered
      73             :      */
      74             :     mapping(IERC20 => uint24) public poolFeesByToken;
      75             : 
      76             :     /**
      77             :      * @notice The minimum observation cardinality for other pools
      78             :      */
      79             :     uint16 public minObservationCardinality;
      80             : 
      81             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V2 TWAP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      82             : 
      83             :     /**
      84             :      * @notice Uniswap V2 torn pool cumulative price last
      85             :      */
      86             :     uint256 public lastCumulativeTORNPriceInETH;
      87             : 
      88             :     /**
      89             :      * @notice Last TWAP data
      90             :      */
      91             :     TWAPData public last;
      92             : 
      93             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      94             : 
      95             :     event PoolFeeUpdated(IERC20 token, uint24 newPoolFee);
      96             :     event MinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
      97             :     event FeeOracleManagerAddressUpdated(address newFeeOracleManagerAddress);
      98             : 
      99             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     100             : 
     101             :     constructor(address _governanceProxyAddress, address _feeOracleManagerAddress) public {
     102             :         governanceProxyAddress = _governanceProxyAddress;
     103             :         feeOracleManagerAddress = _feeOracleManagerAddress;
     104             : 
     105             :         // We're immediately doing this because we want to immediately be ready with a price on execution
     106             : 
     107             :         (, uint256 _price1CumulativeLast, uint32 _timestamp) =
     108             :             UniswapV2OracleLibrary.currentCumulativePrices(UNIV2_TORN_ETH_ADDR);
     109             : 
     110             :         lastCumulativeTORNPriceInETH = _price1CumulativeLast;
     111             : 
     112             :         last.updatedTimestamp = _timestamp;
     113             :     }
     114             : 
     115             :     modifier onlyGovernance() {
     116             :         require(msg.sender == governanceProxyAddress, "UniswapFeeOracle: onlyGovernance");
     117             :         _;
     118             :     }
     119             : 
     120             :     modifier onlyFeeOracleManager() {
     121             :         require(msg.sender == feeOracleManagerAddress, "UniswapFeeOracle: onlyFeeOracleManager");
     122             :         _;
     123             :     }
     124             : 
     125             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     126             : 
     127             :     function getFee(IERC20, InstanceWithFee memory _instance)
     128             :         public
     129             :         view
     130             :         virtual
     131             :         override
     132             :         returns (uint160)
     133             :     {
     134             :         // If 0%, 0
     135          58 :         if (_instance.fee.percent == 0) return 0;
     136             : 
     137             :         // If it's not an ERC20 it has to be ETH, use the WETH token
     138          49 :         _instance.state.token =
     139             :             _instance.state.isERC20 ? _instance.state.token : IERC20(UniswapV3OracleHelper.WETH);
     140             : 
     141             :         // Now get the number of TORN per TOKEN
     142          49 :         uint256 tornPerToken = getTORNPerToken(_instance.state.token, _instance.fee.updateInterval);
     143             : 
     144             :         // DENOMINATION IN TOKEN * TORN PER TOKEN * FEE_NUMERATOR / FEE_DIVISOR = FEE IN TORN
     145          49 :         return uint160(
     146             :             _instance.logic.denomination().mul(tornPerToken).div(1e18).mul(uint256(_instance.fee.percent)).div(
     147             :                 uint256(_instance.fee.divisor)
     148             :             )
     149             :         );
     150             :     }
     151             : 
     152             :     function update(IERC20, InstanceWithFee memory _instance) public virtual override onlyFeeOracleManager {
     153             :         // Get the timestamp of the last update
     154          77 :         uint32 timestampLastUpdate = last.updatedTimestamp;
     155             : 
     156             :         // Get the current one by Uniswaps logic, has inbuilt overflow
     157          77 :         uint32 currentTimestamp = UniswapV2OracleLibrary.currentBlockTimestamp();
     158             : 
     159             :         // Calculate elapsed time, no matter whether overflow (uniswap logic)
     160          77 :         uint32 elapsed = currentTimestamp - timestampLastUpdate;
     161             : 
     162             :         // Multiple instances may refer to this oracle
     163             :         // For this reason we will basically allow only one to update
     164          77 :         if (_instance.fee.updateInterval <= elapsed) {
     165             :             // Get last token0/token1 meaning TORN per ETH counterfactually
     166           8 :             (, uint256 _price1CumulativeLast,) =
     167           8 :                 UniswapV2OracleLibrary.currentCumulativePrices(UNIV2_TORN_ETH_ADDR);
     168             : 
     169             :             // Save TWAP data, meaning the average price (TORN per ETH)
     170           8 :             last = TWAPData({
     171             :                 averagePrice: FixedPoint.uq112x112(
     172             :                     uint224(((_price1CumulativeLast - lastCumulativeTORNPriceInETH) * Q112Ratio) / elapsed)
     173             :                     ),
     174             :                 updatedTimestamp: currentTimestamp
     175             :             });
     176             : 
     177             :             // Update the cumulative value
     178           8 :             lastCumulativeTORNPriceInETH = _price1CumulativeLast;
     179             :         }
     180             :     }
     181             : 
     182             :     function getTORNPerToken(IERC20 _token, uint32 _interval) public view virtual returns (uint256) {
     183             :         // Get the average price of TOKEN in WETH
     184          64 :         uint256 ethPerTokenAverage =
     185          64 :             UniswapV3OracleHelper.getPriceOfTokenInWETH(address(_token), poolFeesByToken[_token], _interval);
     186             : 
     187             :         // TORN PER ETH * ETH PER TOKEN = TORN per TOKEN
     188          64 :         return ethPerTokenAverage * last.averagePrice.decode() / Q112Ratio;
     189             :     }
     190             : 
     191             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     192             : 
     193             :     function setPoolFeeForToken(IERC20 _token, uint24 _tokenPoolFee) public virtual onlyGovernance {
     194             :         // Check whether cardinality is initialized
     195          68 :         require(minObservationCardinality != 0, "UniswapFeeOracle: cardinality not initialized");
     196             : 
     197             :         // Only do this if not zeroing out
     198          68 :         if (_tokenPoolFee != 0) {
     199             :             // Check whether a pool exists for the token + fee combination
     200             : 
     201          52 :             address poolAddress = UniswapV3OracleHelper.UniswapV3Factory.getPool(
     202             :                 address(_token), UniswapV3OracleHelper.WETH, _tokenPoolFee
     203             :             );
     204             : 
     205          52 :             require(poolAddress != address(0), "UniswapFeeOracle: pool for token and fee does not exist");
     206             : 
     207             :             // Check whether the pool has a large enough observation cardinality
     208             : 
     209          52 :             (,,,, uint16 observationCardinalityNext,,) = IUniswapV3PoolState(poolAddress).slot0();
     210             : 
     211          52 :             require(
     212             :                 minObservationCardinality <= observationCardinalityNext,
     213             :                 "UniswapFeeOracle: pool observation cardinality low"
     214             :             );
     215             :         }
     216             : 
     217             :         // Store & log
     218             : 
     219          68 :         poolFeesByToken[_token] = _tokenPoolFee;
     220             : 
     221          68 :         emit PoolFeeUpdated(_token, _tokenPoolFee);
     222             :     }
     223             : 
     224             :     function setMinObservationCardinality(uint16 _newGlobalMinObservationCardinality)
     225             :         public
     226             :         virtual
     227             :         onlyGovernance
     228             :     {
     229           8 :         minObservationCardinality = _newGlobalMinObservationCardinality;
     230           8 :         emit MinObservationCardinalityUpdated(_newGlobalMinObservationCardinality);
     231             :     }
     232             : 
     233             :     function setFeeOracleManagerAddress(address _newFeeOracleManagerAddress) public virtual onlyGovernance {
     234           1 :         feeOracleManagerAddress = _newFeeOracleManagerAddress;
     235           1 :         emit FeeOracleManagerAddressUpdated(_newFeeOracleManagerAddress);
     236             :     }
     237             : 
     238             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     239             : 
     240             :     function getTWAPData() public view virtual returns (TWAPData memory) {
     241           1 :         return last;
     242             :     }
     243             : 
     244             :     function getAverageTORNPerETH() public view virtual returns (uint256) {
     245           1 :         return last.averagePrice.decode();
     246             :     }
     247             : 
     248             :     function getLastUpdatedTime() public view virtual returns (uint32) {
     249           1 :         return last.updatedTimestamp;
     250             :     }
     251             : }

Generated by: LCOV version 1.16