Finalize untested CurveFeeOracle
Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
parent
e8b44ac3f8
commit
bf352a3f67
298
src/v2/CurveFeeOracle.sol
Normal file
298
src/v2/CurveFeeOracle.sol
Normal file
@ -0,0 +1,298 @@
|
||||
// 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";
|
||||
|
||||
// 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 { UniswapV3FeeOracle } from "./UniswapV3FeeOracle.sol";
|
||||
|
||||
import { InstanceData } from "./InstanceRegistry.sol";
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FINANCE INTERFACE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
interface ICurvePriceOracle {
|
||||
/// @dev For Plain2 and CryptoSwap2
|
||||
function price_oracle() external view returns (uint256);
|
||||
/// @dev For Tricrypto pools
|
||||
function price_oracle(uint256 k) external view returns (uint256);
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FEE ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
/**
|
||||
* @title CurveChainedOracles
|
||||
* @author AlienTornadosaurusHex
|
||||
* @notice This library is designed to help us simplify the workflow and introduce at least some packing to
|
||||
* not waste large amounts of gas due to simplifying. Obviously, the best solution would be to just deploy a
|
||||
* per-coin oracle, but I've wanted to have a general implementation because it makes the Governance process
|
||||
* easier.
|
||||
*/
|
||||
library CurveChainedOracles {
|
||||
bytes4 public constant PRICE_ORACLE_SELECTOR = bytes4(keccak256(("price_oracle()")));
|
||||
bytes4 public constant PRICE_ORACLE_UINT256_SELECTOR = bytes4(keccak256(("price_oracle(uint256)")));
|
||||
|
||||
/**
|
||||
* @notice This function prepares data for chain calling Curve oracles.
|
||||
* @dev The returns is called oracle because it contains sufficient data to execute the entire process,
|
||||
* meaning it represents an oracle in a sense.
|
||||
* @param _oracles Curve contracts compatible with either `price_oracle()` or `price_oracle(uint256)`.
|
||||
* @param _selectors Selectors for contract, each is only either `keccak256()` or
|
||||
* `keccak256('price_oracle(uint256)')`
|
||||
* @param _coins Curve pool coin ids which are used for `price_oracle(uint256)`
|
||||
* @param _invertPrice For each oracle capable pool, whether the token we are searching the price for is
|
||||
* token0 or not, or in general whether we want to take the inverse of the price.
|
||||
* @return oracle Packed bytes data which represents full data for a chained call to Curve oracles.
|
||||
*/
|
||||
function createChainedOracle(
|
||||
ICurvePriceOracle[] memory _oracles,
|
||||
bytes4[] memory _selectors,
|
||||
uint8[] memory _coins,
|
||||
bool[] memory _invertPrice
|
||||
) internal view returns (bytes memory oracle) {
|
||||
uint256 numOracles = _oracles.length;
|
||||
|
||||
for (uint256 o = 0; o < numOracles; o++) {
|
||||
// The oracle which will be providing the price
|
||||
ICurvePriceOracle _oracle = _oracles[o];
|
||||
|
||||
// The selector which determines the call
|
||||
bytes4 _selector = _selectors[o];
|
||||
|
||||
// Index of the coin in the curve pool for second UINT256 selector
|
||||
uint8 _coin = _coins[0];
|
||||
|
||||
// Check whether the config actually works
|
||||
if (_selector == PRICE_ORACLE_SELECTOR) {
|
||||
try _oracle.price_oracle() returns (uint256) { /* Works, do nothing */ }
|
||||
catch {
|
||||
require(false, "CurveChainedOracles: test call price_oracle() failed");
|
||||
}
|
||||
} else if (_selector == PRICE_ORACLE_UINT256_SELECTOR) {
|
||||
try _oracle.price_oracle(_coin) returns (uint256) { /* Works, do nothing */ }
|
||||
catch {
|
||||
require(false, "CurveChainedOracles: test call price_oracle(uint256) failed");
|
||||
}
|
||||
} else {
|
||||
require(false, "CurveChainedOracles: none or invalid selector");
|
||||
}
|
||||
|
||||
// We prepend the former iteration, to have it in proper left to right order
|
||||
oracle = abi.encodePacked(oracle, _oracle, _selector, _coin, _invertPrice[o]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Function which unpacks `_oracle` data and chains the price calls to return one final price.
|
||||
* @dev The name `price_oracle` was chosen because it reflects the Curve signature of the same name.
|
||||
* @param _oracle The packed bytes data.
|
||||
* @return Price of some token in some other token E18.
|
||||
*/
|
||||
function price_oracle(bytes memory _oracle) internal view returns (uint256) {
|
||||
uint256 price = 1e18;
|
||||
uint256 priceDivisor = 1e18;
|
||||
uint256 inverseNumerator = 1e36;
|
||||
|
||||
uint256 numOracles = _oracle.length;
|
||||
|
||||
for (uint256 o = 0; o < numOracles; o++) {
|
||||
bytes32 chunk;
|
||||
|
||||
// Packed bytes can't be decoded, have to do it ourselves
|
||||
// mload always reads bytes32, always offset by 26 bytes because data is always stored fully
|
||||
// packed from left to right
|
||||
assembly {
|
||||
chunk := mload(add(_oracle, mul(26, add(o, 1))))
|
||||
}
|
||||
|
||||
// Properly read in all of the packed data
|
||||
|
||||
// Always packed from left to right, first 20 bytes is address
|
||||
ICurvePriceOracle oracle = ICurvePriceOracle(address(bytes20(chunk)));
|
||||
|
||||
// Then from 20 to 24 is selector, shift by 20x8=160
|
||||
bytes4 selector = bytes4(chunk << 160);
|
||||
|
||||
// From 24 to 25 is coin index, shift by 160+4x8=192
|
||||
uint8 coin = uint8(bytes1(chunk << 192));
|
||||
|
||||
// From 25 to 26 is whether to invert price, shift by 192+1x8=200
|
||||
bool invertPrice = bytes1(chunk << 200) == 0x01;
|
||||
|
||||
uint256 priceFromOracle;
|
||||
|
||||
// Execute according to selector
|
||||
if (selector == PRICE_ORACLE_SELECTOR) {
|
||||
priceFromOracle = ICurvePriceOracle(oracle).price_oracle();
|
||||
} else if (selector == PRICE_ORACLE_UINT256_SELECTOR) {
|
||||
priceFromOracle = ICurvePriceOracle(oracle).price_oracle(coin);
|
||||
}
|
||||
|
||||
// Check whether we need to invert price
|
||||
if (invertPrice) {
|
||||
priceFromOracle = inverseNumerator / priceFromOracle;
|
||||
}
|
||||
|
||||
// Calc price
|
||||
price = (price * priceFromOracle) / priceDivisor;
|
||||
}
|
||||
|
||||
return price;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @title CurveFeeOracle
|
||||
* @author AlienTornadosaurusHex
|
||||
* @notice Many Curve contracts now have an EMA/TWAP Oracle which can be used instead of having to rely on
|
||||
* Chainlink (Offchain) Pricefeeds or Uniswap V3 Twap Oracles. We do not use Metapools because they do not
|
||||
* have a safe enough TWAP oracle, instead we use the `price_oracle` capable contracts, which use a safer
|
||||
* analytical solution.
|
||||
*/
|
||||
contract CurveFeeOracle is IFeeOracle {
|
||||
using SafeMath for uint256;
|
||||
using CurveChainedOracles for *;
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
/* @dev When dividing by `d*1e18` */
|
||||
uint256 public constant INVERSE_NUMERATOR = 1e36;
|
||||
|
||||
/* @dev The Governance Proxy address */
|
||||
address public immutable governanceProxyAddress;
|
||||
|
||||
/* @dev For each instance, a set of data which translates to a set of chained price oracle calls, we call
|
||||
this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and calc the
|
||||
price */
|
||||
mapping(ITornadoInstance => bytes) public chainedPriceOracles;
|
||||
|
||||
/* @dev When setting, store the names as a historical record, key is keccak256(bytes) */
|
||||
mapping(bytes32 => string) public chainedPriceOracleNames;
|
||||
|
||||
/* @dev Our Uniswap V3 Fee Oracle, we will need it some time for TORN/ETH */
|
||||
UniswapV3FeeOracle public v3FeeOracle;
|
||||
|
||||
/* @dev We will not need the Uniswap V3 Oracle forever though, TORN/ETH pools are possible on Curve too */
|
||||
bool public tornOracleIsUniswapV3;
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
event TornOracleIsCurve();
|
||||
event TornOracleIsUniswapV3();
|
||||
event UniswapV3FeeOracleUpdated(address _newUniswapV3FeeOracleAddress);
|
||||
event ChainedOracleUpdated(address indexed instance, bytes32 newOracleHash, string newName);
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
constructor(address _governanceProxyAddress) public {
|
||||
governanceProxyAddress = _governanceProxyAddress;
|
||||
tornOracleIsUniswapV3 = true;
|
||||
}
|
||||
|
||||
modifier onlyGovernance() {
|
||||
require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance");
|
||||
_;
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
function getFee(
|
||||
IERC20 _torn,
|
||||
ITornadoInstance _instance,
|
||||
InstanceData memory,
|
||||
uint64 _feePercent,
|
||||
uint64 _feePercentDivisor
|
||||
) public view virtual override returns (uint160) {
|
||||
// If fee is 0 return
|
||||
if (_feePercent == 0) return 0;
|
||||
|
||||
// Either TOKEN price in ETH or TOKEN price in TORN
|
||||
uint256 price = chainedPriceOracles[_instance].price_oracle();
|
||||
|
||||
// If the below is true, then this will finalize the price and give the TOKEN price in TORN
|
||||
// Above MUST be TOKEN price in ETH, then (ETH/TOKEN)/(ETH/TORN)=TORN/TOKEN, meaning the TOKEN price
|
||||
// in TORN
|
||||
if (tornOracleIsUniswapV3) {
|
||||
price = price * UniswapV3OracleHelper.RATIO_DIVIDER
|
||||
/ v3FeeOracle.getPriceRatioOfTokens(_torn, IERC20(UniswapV3OracleHelper.WETH));
|
||||
}
|
||||
|
||||
// The price is now either prepared by the chained oracles or by Uniswap V3, in any case we do the
|
||||
// legacy calculation and return the fee value
|
||||
return uint160(
|
||||
_instance.denomination().mul(price).div(UniswapV3OracleHelper.RATIO_DIVIDER).mul(
|
||||
uint256(_feePercent)
|
||||
).div(uint256(_feePercentDivisor))
|
||||
);
|
||||
}
|
||||
|
||||
function getChainedOracleNameForInstance(ITornadoInstance _instance)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
returns (string memory)
|
||||
{
|
||||
return chainedPriceOracleNames[getChainedOracleHashForInstance(_instance)];
|
||||
}
|
||||
|
||||
function getChainedOracleNameForOracleHash(bytes32 _oracleHash)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
returns (string memory)
|
||||
{
|
||||
return chainedPriceOracleNames[_oracleHash];
|
||||
}
|
||||
|
||||
function getChainedOracleHashForInstance(ITornadoInstance _instance)
|
||||
public
|
||||
view
|
||||
virtual
|
||||
returns (bytes32)
|
||||
{
|
||||
return keccak256(chainedPriceOracles[_instance]);
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
function setChainedOracleForInstance(
|
||||
ITornadoInstance _instance,
|
||||
ICurvePriceOracle[] memory _oracles,
|
||||
bytes4[] memory _selectors,
|
||||
uint8[] memory _coins,
|
||||
bool[] memory _invertPrice,
|
||||
string memory _name
|
||||
) public virtual onlyGovernance {
|
||||
bytes memory oracle = _oracles.createChainedOracle(_selectors, _coins, _invertPrice);
|
||||
bytes32 oracleHash = keccak256(oracle);
|
||||
|
||||
chainedPriceOracles[_instance] = oracle;
|
||||
chainedPriceOracleNames[oracleHash] = _name;
|
||||
|
||||
emit ChainedOracleUpdated(address(_instance), oracleHash, _name);
|
||||
}
|
||||
|
||||
function setUniswapV3FeeOracle(UniswapV3FeeOracle _uniswapV3FeeOracle) public virtual onlyGovernance {
|
||||
v3FeeOracle = _uniswapV3FeeOracle;
|
||||
emit UniswapV3FeeOracleUpdated(address(_uniswapV3FeeOracle));
|
||||
}
|
||||
|
||||
function setTornOracleIsUniswapV3(bool _is) public virtual onlyGovernance {
|
||||
tornOracleIsUniswapV3 = _is;
|
||||
if (_is) emit TornOracleIsUniswapV3();
|
||||
else emit TornOracleIsCurve();
|
||||
}
|
||||
}
|
@ -40,10 +40,10 @@ contract FeeManagerLegacyStorage {
|
||||
/* @dev From first contract, the only value we keep alive */
|
||||
uint24 public feeUpdateInterval;
|
||||
|
||||
/* @dev From first contract, only used for initialization to preserve old values in the moment */
|
||||
/* @dev From first contract, only used for initialization to preserve old values */
|
||||
mapping(ITornadoInstance => uint160) internal _oldFeesForInstance;
|
||||
|
||||
/* @dev From first contract, only used for initialization to preserve old values in the moment */
|
||||
/* @dev From first contract, only used for initialization to preserve old values */
|
||||
mapping(ITornadoInstance => uint256) internal _oldFeesForInstanceUpdateTime;
|
||||
}
|
||||
|
||||
|
@ -111,17 +111,17 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
||||
_;
|
||||
}
|
||||
|
||||
function initialize(address[] memory _instanceAddresses, address _router)
|
||||
function initialize(ITornadoInstance[] memory _instances, address _router)
|
||||
external
|
||||
onlyGovernance
|
||||
initializer
|
||||
{
|
||||
uint256 numInstances = _instanceAddresses.length;
|
||||
uint256 numInstances = _instances.length;
|
||||
|
||||
router = TornadoRouter(_router);
|
||||
|
||||
for (uint256 i = 0; i < numInstances; i++) {
|
||||
addInstance(ITornadoInstance(_instanceAddresses[i]));
|
||||
addInstance(_instances[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,6 +187,10 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
||||
ITornadoInstance instance = ITornadoInstance(_instanceAddress);
|
||||
InstanceData memory data = instanceData[instance];
|
||||
|
||||
// Checks whether already removed, so allowance can't be killed
|
||||
|
||||
require(data.isEnabled, "InstanceRegistry: already removed");
|
||||
|
||||
// Kill the allowance of the router first (arbitrary order)
|
||||
|
||||
if (data.isERC20) {
|
||||
@ -203,7 +207,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
||||
|
||||
ITornadoInstance lastInstance = instances[lastInstanceIndex];
|
||||
|
||||
// Remove & swap
|
||||
// Swap position of last instance with old
|
||||
|
||||
instances[data.index] = lastInstance;
|
||||
|
||||
@ -211,7 +215,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
||||
|
||||
instances.pop();
|
||||
|
||||
// Delete for removed instance data
|
||||
// Delete old instance data
|
||||
|
||||
delete instanceData[instance];
|
||||
|
||||
|
@ -42,6 +42,10 @@ contract TornadoRouter is Initializable {
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
||||
event TokenApproved(address indexed spender, uint256 amount);
|
||||
|
||||
event InstanceRegistryUpdated(address newInstanceRegistryProxyAddress);
|
||||
event RelayerRegistryUpdated(address newRelayerRegistryProxyAddress);
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
@ -141,14 +145,17 @@ contract TornadoRouter is Initializable {
|
||||
onlyInstanceRegistry
|
||||
{
|
||||
_token.safeApprove(_spender, _amount);
|
||||
emit TokenApproved(_spender, _amount);
|
||||
}
|
||||
|
||||
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
|
||||
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
|
||||
emit InstanceRegistryUpdated(_newInstanceRegistryProxyAddress);
|
||||
}
|
||||
|
||||
function setRelayerRegistry(address _newRelayerRegistryProxyAddress) external onlyGovernance {
|
||||
relayerRegistry = RelayerRegistry(_newRelayerRegistryProxyAddress);
|
||||
emit RelayerRegistryUpdated(_newRelayerRegistryProxyAddress);
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
@ -25,10 +25,15 @@ import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
|
||||
|
||||
import { InstanceData } from "./InstanceRegistry.sol";
|
||||
|
||||
struct UniswapV3FeeData {
|
||||
/**
|
||||
* @dev The global configuration for the `UniswapV3FeeOracle` contract. TORN is used in all `getFee()` calls,
|
||||
* the interval seconds for the TWAP oracle are global, and the minimum observation cardinality assures the
|
||||
* former interval can be requested.
|
||||
*/
|
||||
struct GlobalOracleConfig {
|
||||
uint24 tornPoolFee;
|
||||
uint24 tokenPoolFee;
|
||||
uint32 timePeriod;
|
||||
uint32 twapIntervalSeconds;
|
||||
uint16 minObservationCardinality;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,33 +49,23 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
||||
/* @dev The Governance Proxy address */
|
||||
address public immutable governanceProxyAddress;
|
||||
|
||||
/* @dev The Uniswap V3 Factory */
|
||||
IUniswapV3Factory public immutable v3Factory;
|
||||
/* @dev Global configuration valid across all `getFee()` (and other) calls */
|
||||
GlobalOracleConfig public globals;
|
||||
|
||||
/* @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;
|
||||
/* @dev Uniswap pool fees for each token registered */
|
||||
mapping(IERC20 => uint24) public poolFeesByToken;
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
event UniswapPoolFeeUpdated(IERC20 token, uint24 newUniswapPoolFee);
|
||||
event GlobalTornUniswapPoolFeeUpdated(uint24 newUniswapPoolFee);
|
||||
event GlobalUniswapTimePeriodUpdated(uint32 newUniswapTimePeriod);
|
||||
event PoolFeeUpdated(IERC20 token, uint24 newPoolFee);
|
||||
event GlobalTornPoolFeeUpdated(uint24 newPoolFee);
|
||||
event GlobalTwapIntervalSecondsUpdated(uint32 newUniswapTimePeriod);
|
||||
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
constructor(address _governanceProxyAddress, address _uniswapV3FactoryAddress) public {
|
||||
constructor(address _governanceProxyAddress) public {
|
||||
governanceProxyAddress = _governanceProxyAddress;
|
||||
v3Factory = IUniswapV3Factory(_uniswapV3FactoryAddress);
|
||||
}
|
||||
|
||||
modifier onlyGovernance() {
|
||||
@ -78,56 +73,7 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
||||
_;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
function getFee(
|
||||
IERC20 _torn,
|
||||
@ -136,30 +82,102 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
||||
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];
|
||||
// Get global config
|
||||
GlobalOracleConfig memory global = globals;
|
||||
|
||||
// Calc price ratio
|
||||
// Get pool fee for the token and calc the price ratio
|
||||
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
|
||||
[address(_torn), address(_data.token)],
|
||||
[uniFeeData.tornPoolFee, uniFeeData.tokenPoolFee],
|
||||
uniFeeData.timePeriod
|
||||
[global.tornPoolFee, poolFeesByToken[_data.token]],
|
||||
global.twapIntervalSeconds
|
||||
);
|
||||
|
||||
// And now all according to legacy calculation
|
||||
return uint160(
|
||||
_instance.denomination().mul(UniswapV3OracleHelper.RATIO_DIVIDER).div(tokenPriceRatio).mul(
|
||||
uint256(_feePercent)
|
||||
).div(_feePercentDivisor)
|
||||
).div(uint256(_feePercentDivisor))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function allows an external contract to rely on the selection mechanism of Uniswap pools
|
||||
* based on this contracts minimum observation cardinality requirement.
|
||||
*/
|
||||
function getPriceRatioOfTokens(IERC20 target, IERC20 quote) public view virtual returns (uint256) {
|
||||
return UniswapV3OracleHelper.getPriceRatioOfTokens(
|
||||
[address(target), address(quote)],
|
||||
[poolFeesByToken[target], poolFeesByToken[quote]],
|
||||
globals.twapIntervalSeconds
|
||||
);
|
||||
}
|
||||
|
||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||
|
||||
function setPoolFeeForToken(IERC20 _token, uint24 _tokenPoolFee) public virtual onlyGovernance {
|
||||
// Get global config
|
||||
|
||||
GlobalOracleConfig memory global = globals;
|
||||
|
||||
// Check whether all globals needed are initialized
|
||||
|
||||
require(global.tornPoolFee != 0, "UniswapV3FeeOracle: torn pool fee not initialized");
|
||||
require(global.twapIntervalSeconds != 0, "UniswapV3FeeOracle: time period not initialized");
|
||||
|
||||
// Check whether a pool exists for the token + fee combination
|
||||
|
||||
address poolAddress = UniswapV3OracleHelper.UniswapV3Factory.getPool(
|
||||
address(_token), UniswapV3OracleHelper.WETH, _tokenPoolFee
|
||||
);
|
||||
|
||||
require(poolAddress != address(0), "UniswapV3FeeOracle: pool for token and fee does not exist");
|
||||
|
||||
// Check whether the pool has a large enough observation cardinality
|
||||
|
||||
(,,,, uint16 observationCardinalityNext,,) = IUniswapV3PoolState(poolAddress).slot0();
|
||||
|
||||
require(
|
||||
global.minObservationCardinality <= observationCardinalityNext,
|
||||
"UniswapV3FeeOracle: pool observation cardinality low"
|
||||
);
|
||||
|
||||
// Store & log
|
||||
|
||||
poolFeesByToken[_token] = _tokenPoolFee;
|
||||
|
||||
emit PoolFeeUpdated(_token, _tokenPoolFee);
|
||||
}
|
||||
|
||||
function setGlobalTornPoolFee(uint24 _newGlobalTornPoolFee) public virtual onlyGovernance {
|
||||
globals.tornPoolFee = _newGlobalTornPoolFee;
|
||||
|
||||
// For `getPriceRatioOfTokens`
|
||||
poolFeesByToken[IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C)] = _newGlobalTornPoolFee;
|
||||
|
||||
emit GlobalTornPoolFeeUpdated(_newGlobalTornPoolFee);
|
||||
}
|
||||
|
||||
function setGlobalTwapIntervalSeconds(uint32 _newGlobalTwapIntervalSeconds)
|
||||
public
|
||||
virtual
|
||||
onlyGovernance
|
||||
{
|
||||
globals.twapIntervalSeconds = _newGlobalTwapIntervalSeconds;
|
||||
emit GlobalTwapIntervalSecondsUpdated(_newGlobalTwapIntervalSeconds);
|
||||
}
|
||||
|
||||
function setGlobalMinObservationCardinality(uint16 _newGlobalMinObservationCardinality)
|
||||
public
|
||||
virtual
|
||||
onlyGovernance
|
||||
{
|
||||
globals.minObservationCardinality = _newGlobalMinObservationCardinality;
|
||||
emit GlobalMinObservationCardinalityUpdated(_newGlobalMinObservationCardinality);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user