LCOV - code coverage report
Current view: top level - v2 - CurveFeeOracle.sol (source / functions) Hit Total Coverage
Test: lcov.info Lines: 17 49 34.7 %
Date: 2023-06-20 21:04:08 Functions: 8 10 80.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             : // OZ imports
       7             : 
       8             : import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
       9             : import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
      10             : 
      11             : // Tornado imports
      12             : 
      13             : import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
      14             : 
      15             : // Local imports
      16             : 
      17             : import { IFeeOracle, InstanceWithFee } from "./interfaces/IFeeOracle.sol";
      18             : 
      19             : import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
      20             : 
      21             : import { UniswapFeeOracle } from "./UniswapFeeOracle.sol";
      22             : 
      23             : import { InstanceState } from "./InstanceRegistry.sol";
      24             : 
      25             : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FINANCE INTERFACE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      26             : 
      27             : interface ICurvePriceOracle {
      28             :     /// @dev For Plain2 and CryptoSwap2
      29             :     function price_oracle() external view returns (uint256);
      30             :     /// @dev For Tricrypto pools
      31             :     function price_oracle(uint256 k) external view returns (uint256);
      32             : }
      33             : 
      34             : /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FEE ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
      35             : 
      36             : /**
      37             :  * @title CurveChainedOracles
      38             :  * @author AlienTornadosaurusHex
      39             :  * @notice This library is designed to help us simplify the workflow and introduce at least some packing to
      40             :  * not waste large amounts of gas due to simplifying. Obviously, the best solution would be to just deploy a
      41             :  * per-coin oracle, but I've wanted to have a general implementation because it makes the Governance process
      42             :  * easier.
      43             :  */
      44             : library CurveChainedOracles {
      45             :     bytes4 public constant PRICE_ORACLE_SELECTOR = bytes4(keccak256(("price_oracle()")));
      46             :     bytes4 public constant PRICE_ORACLE_UINT256_SELECTOR = bytes4(keccak256(("price_oracle(uint256)")));
      47             : 
      48             :     /**
      49             :      * @notice This function prepares data for chain calling Curve oracles.
      50             :      * @dev The returns is called oracle because it contains sufficient data to execute the entire process,
      51             :      * meaning it represents an oracle in a sense.
      52             :      * @param _oracles Curve contracts compatible with either `price_oracle()` or `price_oracle(uint256)`.
      53             :      * @param _selectors Selectors for contract, each is only either `keccak256()` or
      54             :      * `keccak256('price_oracle(uint256)')`
      55             :      * @param _coins Curve pool coin ids which are used for `price_oracle(uint256)`
      56             :      * @param _invertPrice For each oracle capable pool, whether the token we are searching the price for is
      57             :      * token0 or not, or in general whether we want to take the inverse of the price.
      58             :      * @return oracle Packed bytes data which represents full data for a chained call to Curve oracles.
      59             :      */
      60             :     function createChainedOracle(
      61             :         ICurvePriceOracle[] memory _oracles,
      62             :         bytes4[] memory _selectors,
      63             :         uint8[] memory _coins,
      64             :         bool[] memory _invertPrice
      65             :     ) internal view returns (bytes memory oracle) {
      66           0 :         uint256 numOracles = _oracles.length;
      67             : 
      68           0 :         for (uint256 o = 0; o < numOracles; o++) {
      69             :             // The oracle which will be providing the price
      70           0 :             ICurvePriceOracle _oracle = _oracles[o];
      71             : 
      72             :             // The selector which determines the call
      73           0 :             bytes4 _selector = _selectors[o];
      74             : 
      75             :             // Index of the coin in the curve pool for second UINT256 selector
      76           0 :             uint8 _coin = _coins[o];
      77             : 
      78             :             // Check whether the config actually works
      79           0 :             if (_selector == PRICE_ORACLE_SELECTOR) {
      80           0 :                 try _oracle.price_oracle() returns (uint256) { /* Works, do nothing */ }
      81             :                 catch {
      82             :                     require(false, "CurveChainedOracles: test call price_oracle() failed");
      83             :                 }
      84           0 :             } else if (_selector == PRICE_ORACLE_UINT256_SELECTOR) {
      85           0 :                 try _oracle.price_oracle(_coin) returns (uint256) { /* Works, do nothing */ }
      86             :                 catch {
      87             :                     require(false, "CurveChainedOracles: test call price_oracle(uint256) failed");
      88             :                 }
      89             :             } else {
      90           0 :                 require(false, "CurveChainedOracles: none or invalid selector");
      91             :             }
      92             : 
      93             :             // We prepend the former iteration, to have it in proper left to right order
      94           0 :             oracle = abi.encodePacked(oracle, _oracle, _selector, _coin, _invertPrice[o]);
      95             :         }
      96             :     }
      97             : 
      98             :     /**
      99             :      * @notice Function which unpacks `_oracle` data and chains the price calls to return one final price.
     100             :      * @dev The name `price_oracle` was chosen because it reflects the Curve signature of the same name.
     101             :      * @param _oracle The packed bytes data.
     102             :      * @return Price of some token in some other token E18.
     103             :      */
     104             :     function price_oracle(bytes memory _oracle) internal view returns (uint256) {
     105           0 :         uint256 price = 1e18;
     106           0 :         uint256 priceDivisor = 1e18;
     107           0 :         uint256 inverseNumerator = 1e36;
     108             : 
     109             :         // We know that each oracle addition encodes to exactly 26 bytes
     110           0 :         uint256 numOracles = _oracle.length / 26;
     111             : 
     112           0 :         for (uint256 o = 0; o < numOracles; o++) {
     113           0 :             bytes32 chunk;
     114             : 
     115             :             // Packed bytes can't be decoded, have to do it ourselves
     116             :             // mload always reads bytes32, always offset by 26 bytes because data is always stored fully
     117             :             // packed from left to right
     118             :             assembly {
     119           0 :                 chunk := mload(add(_oracle, add(32, mul(26, o))))
     120             :             }
     121             : 
     122             :             // Properly read in all of the packed data
     123             : 
     124             :             // Always packed from left to right, first 20 bytes is address
     125           0 :             ICurvePriceOracle oracle = ICurvePriceOracle(address(bytes20(chunk)));
     126             : 
     127             :             // Then from 20 to 24 is selector, shift by 20x8=160
     128           0 :             bytes4 selector = bytes4(chunk << 160);
     129             : 
     130             :             // From 24 to 25 is coin index, shift by 160+4x8=192
     131           0 :             uint8 coin = uint8(bytes1(chunk << 192));
     132             : 
     133             :             // From 25 to 26 is whether to invert price, shift by 192+1x8=200
     134           0 :             bool invertPrice = bytes1(chunk << 200) == 0x01;
     135             : 
     136           0 :             uint256 priceFromOracle;
     137             : 
     138             :             // Execute according to selector
     139             :             // These are guaranteed to all be 1e18
     140           0 :             if (selector == PRICE_ORACLE_SELECTOR) {
     141           0 :                 priceFromOracle = ICurvePriceOracle(oracle).price_oracle();
     142           0 :             } else if (selector == PRICE_ORACLE_UINT256_SELECTOR) {
     143           0 :                 priceFromOracle = ICurvePriceOracle(oracle).price_oracle(coin);
     144             :             }
     145             : 
     146             :             // Check whether we need to invert price
     147           0 :             if (invertPrice) {
     148           0 :                 priceFromOracle = inverseNumerator / priceFromOracle;
     149             :             }
     150             : 
     151             :             // Calc price
     152           0 :             price = (price * priceFromOracle) / priceDivisor;
     153             :         }
     154             : 
     155           0 :         return price;
     156             :     }
     157             : }
     158             : 
     159             : /**
     160             :  * @title CurveFeeOracle
     161             :  * @author AlienTornadosaurusHex
     162             :  * @notice Many Curve contracts now have an EMA/TWAP Oracle which can be used instead of having to rely on
     163             :  * Chainlink (Offchain) Pricefeeds or Uniswap Twap Oracles. We do not use Metapools because they do not
     164             :  * have a safe enough TWAP oracle, instead we use the `price_oracle` capable contracts, which use a safer
     165             :  * analytical solution.
     166             :  */
     167             : contract CurveFeeOracle is IFeeOracle {
     168             :     using SafeMath for uint256;
     169             :     using CurveChainedOracles for *;
     170             : 
     171             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     172             : 
     173             :     /**
     174             :      * @notice The Governance Proxy address
     175             :      */
     176             :     address public immutable governanceProxyAddress;
     177             : 
     178             :     /**
     179             :      * @notice For each instance, a set of data which translates to a set of chained price oracle calls, we
     180             :      * call this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and
     181             :      * calc the price
     182             :      */
     183             :     mapping(ITornadoInstance => bytes) public chainedPriceOracles;
     184             : 
     185             :     /**
     186             :      * @notice When setting, store the names as a historical record, key is keccak256(bytes)
     187             :      */
     188             :     mapping(bytes32 => string) public chainedPriceOracleNames;
     189             : 
     190             :     /**
     191             :      * @notice Our Uniswap Fee Oracle, we will need it some time for TORN/ETH
     192             :      */
     193             :     UniswapFeeOracle public uniswapFeeOracle;
     194             : 
     195             :     /**
     196             :      * @notice We will not need the Uniswap Oracle forever though, TORN/ETH pools are possible on Curve too
     197             :      */
     198             :     bool public tornOracleIsUniswap;
     199             : 
     200             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     201             : 
     202             :     event TornOracleIsCurve();
     203             :     event TornOracleIsUniswapV3();
     204             :     event UniswapFeeOracleUpdated(address _newUniswapFeeOracleAddress);
     205             :     event ChainedOracleUpdated(address indexed instance, bytes32 newOracleHash, string newName);
     206             : 
     207             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     208             : 
     209             :     constructor(address _governanceProxyAddress) public {
     210             :         governanceProxyAddress = _governanceProxyAddress;
     211             :         tornOracleIsUniswap = true;
     212             :     }
     213             : 
     214             :     modifier onlyGovernance() {
     215             :         require(msg.sender == governanceProxyAddress, "UniswapFeeOracle: onlyGovernance");
     216             :         _;
     217             :     }
     218             : 
     219             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     220             : 
     221             :     function update(IERC20, InstanceWithFee memory) public virtual override { }
     222             : 
     223             :     function getFee(IERC20, InstanceWithFee memory _instance)
     224             :         public
     225             :         view
     226             :         virtual
     227             :         override
     228             :         returns (uint160)
     229             :     {
     230             :         // If fee is 0 return
     231          26 :         if (_instance.fee.percent == 0) return 0;
     232             : 
     233             :         // Either ETH PER TOKEN or TORN PER TOKEN
     234          16 :         uint256 price = chainedPriceOracles[_instance.logic].price_oracle();
     235             : 
     236             :         // ETH PER TOKEN * TORN PER ETH = TORN PER TOKEN
     237          16 :         if (tornOracleIsUniswap) {
     238          15 :             price = price
     239             :                 * uniswapFeeOracle.getTORNPerToken(
     240             :                     IERC20(UniswapV3OracleHelper.WETH), _instance.fee.updateInterval
     241             :                 ) / UniswapV3OracleHelper.RATIO_DIVIDER;
     242             :         }
     243             : 
     244             :         // DENOMINATION IN TOKEN * TORN PER TOKEN * FEE_NUMERATOR / FEE_DIVISOR = FEE IN TORN
     245          16 :         return uint160(
     246             :             _instance.logic.denomination().mul(price).div(UniswapV3OracleHelper.RATIO_DIVIDER).mul(
     247             :                 uint256(_instance.fee.percent)
     248             :             ).div(uint256(_instance.fee.divisor))
     249             :         );
     250             :     }
     251             : 
     252             :     function getChainedOracleNameForInstance(ITornadoInstance _instance)
     253             :         public
     254             :         view
     255             :         virtual
     256             :         returns (string memory)
     257             :     {
     258           1 :         return chainedPriceOracleNames[getChainedOracleHashForInstance(_instance)];
     259             :     }
     260             : 
     261             :     function getChainedOracleNameForOracleHash(bytes32 _oracleHash)
     262             :         public
     263             :         view
     264             :         virtual
     265             :         returns (string memory)
     266             :     {
     267           1 :         return chainedPriceOracleNames[_oracleHash];
     268             :     }
     269             : 
     270             :     function getChainedOracleHashForInstance(ITornadoInstance _instance)
     271             :         public
     272             :         view
     273             :         virtual
     274             :         returns (bytes32)
     275             :     {
     276           2 :         return keccak256(chainedPriceOracles[_instance]);
     277             :     }
     278             : 
     279             :     /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
     280             : 
     281             :     function modifyChainedOracleForInstance(
     282             :         ITornadoInstance _instance,
     283             :         ICurvePriceOracle[] memory _oracles,
     284             :         bytes4[] memory _selectors,
     285             :         uint8[] memory _coins,
     286             :         bool[] memory _invertPrice,
     287             :         string memory _name
     288             :     ) public virtual onlyGovernance {
     289          12 :         bytes memory oracle = _oracles.createChainedOracle(_selectors, _coins, _invertPrice);
     290          12 :         bytes32 oracleHash = keccak256(oracle);
     291             : 
     292          12 :         chainedPriceOracles[_instance] = oracle;
     293          12 :         chainedPriceOracleNames[oracleHash] = _name;
     294             : 
     295          12 :         emit ChainedOracleUpdated(address(_instance), oracleHash, _name);
     296             :     }
     297             : 
     298             :     function setUniswapFeeOracle(UniswapFeeOracle _uniswapFeeOracle) public virtual onlyGovernance {
     299           2 :         uniswapFeeOracle = _uniswapFeeOracle;
     300           2 :         emit UniswapFeeOracleUpdated(address(_uniswapFeeOracle));
     301             :     }
     302             : 
     303             :     function setTornOracleIsUniswap(bool _is) public virtual onlyGovernance {
     304           1 :         tornOracleIsUniswap = _is;
     305           1 :         if (_is) emit TornOracleIsUniswapV3();
     306           0 :         else emit TornOracleIsCurve();
     307             :     }
     308             : }

Generated by: LCOV version 1.16