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 */
|
/* @dev From first contract, the only value we keep alive */
|
||||||
uint24 public feeUpdateInterval;
|
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;
|
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;
|
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
|
external
|
||||||
onlyGovernance
|
onlyGovernance
|
||||||
initializer
|
initializer
|
||||||
{
|
{
|
||||||
uint256 numInstances = _instanceAddresses.length;
|
uint256 numInstances = _instances.length;
|
||||||
|
|
||||||
router = TornadoRouter(_router);
|
router = TornadoRouter(_router);
|
||||||
|
|
||||||
for (uint256 i = 0; i < numInstances; i++) {
|
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);
|
ITornadoInstance instance = ITornadoInstance(_instanceAddress);
|
||||||
InstanceData memory data = instanceData[instance];
|
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)
|
// Kill the allowance of the router first (arbitrary order)
|
||||||
|
|
||||||
if (data.isERC20) {
|
if (data.isERC20) {
|
||||||
@ -203,7 +207,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
|||||||
|
|
||||||
ITornadoInstance lastInstance = instances[lastInstanceIndex];
|
ITornadoInstance lastInstance = instances[lastInstanceIndex];
|
||||||
|
|
||||||
// Remove & swap
|
// Swap position of last instance with old
|
||||||
|
|
||||||
instances[data.index] = lastInstance;
|
instances[data.index] = lastInstance;
|
||||||
|
|
||||||
@ -211,7 +215,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
|
|||||||
|
|
||||||
instances.pop();
|
instances.pop();
|
||||||
|
|
||||||
// Delete for removed instance data
|
// Delete old instance data
|
||||||
|
|
||||||
delete instanceData[instance];
|
delete instanceData[instance];
|
||||||
|
|
||||||
|
@ -42,6 +42,10 @@ contract TornadoRouter is Initializable {
|
|||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
event EncryptedNote(address indexed sender, bytes encryptedNote);
|
||||||
|
event TokenApproved(address indexed spender, uint256 amount);
|
||||||
|
|
||||||
|
event InstanceRegistryUpdated(address newInstanceRegistryProxyAddress);
|
||||||
|
event RelayerRegistryUpdated(address newRelayerRegistryProxyAddress);
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
@ -141,14 +145,17 @@ contract TornadoRouter is Initializable {
|
|||||||
onlyInstanceRegistry
|
onlyInstanceRegistry
|
||||||
{
|
{
|
||||||
_token.safeApprove(_spender, _amount);
|
_token.safeApprove(_spender, _amount);
|
||||||
|
emit TokenApproved(_spender, _amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
|
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
|
||||||
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
|
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
|
||||||
|
emit InstanceRegistryUpdated(_newInstanceRegistryProxyAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRelayerRegistry(address _newRelayerRegistryProxyAddress) external onlyGovernance {
|
function setRelayerRegistry(address _newRelayerRegistryProxyAddress) external onlyGovernance {
|
||||||
relayerRegistry = RelayerRegistry(_newRelayerRegistryProxyAddress);
|
relayerRegistry = RelayerRegistry(_newRelayerRegistryProxyAddress);
|
||||||
|
emit RelayerRegistryUpdated(_newRelayerRegistryProxyAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
@ -25,10 +25,15 @@ import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
|
|||||||
|
|
||||||
import { InstanceData } from "./InstanceRegistry.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 tornPoolFee;
|
||||||
uint24 tokenPoolFee;
|
uint32 twapIntervalSeconds;
|
||||||
uint32 timePeriod;
|
uint16 minObservationCardinality;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -44,33 +49,23 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
|||||||
/* @dev The Governance Proxy address */
|
/* @dev The Governance Proxy address */
|
||||||
address public immutable governanceProxyAddress;
|
address public immutable governanceProxyAddress;
|
||||||
|
|
||||||
/* @dev The Uniswap V3 Factory */
|
/* @dev Global configuration valid across all `getFee()` (and other) calls */
|
||||||
IUniswapV3Factory public immutable v3Factory;
|
GlobalOracleConfig public globals;
|
||||||
|
|
||||||
/* @dev The global pool fee value which will always be used for TORN */
|
/* @dev Uniswap pool fees for each token registered */
|
||||||
uint24 public globalTornUniswapPoolFee;
|
mapping(IERC20 => uint24) public poolFeesByToken;
|
||||||
|
|
||||||
/* @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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
event UniswapPoolFeeUpdated(IERC20 token, uint24 newUniswapPoolFee);
|
event PoolFeeUpdated(IERC20 token, uint24 newPoolFee);
|
||||||
event GlobalTornUniswapPoolFeeUpdated(uint24 newUniswapPoolFee);
|
event GlobalTornPoolFeeUpdated(uint24 newPoolFee);
|
||||||
event GlobalUniswapTimePeriodUpdated(uint32 newUniswapTimePeriod);
|
event GlobalTwapIntervalSecondsUpdated(uint32 newUniswapTimePeriod);
|
||||||
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
|
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
|
||||||
|
|
||||||
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
|
|
||||||
constructor(address _governanceProxyAddress, address _uniswapV3FactoryAddress) public {
|
constructor(address _governanceProxyAddress) public {
|
||||||
governanceProxyAddress = _governanceProxyAddress;
|
governanceProxyAddress = _governanceProxyAddress;
|
||||||
v3Factory = IUniswapV3Factory(_uniswapV3FactoryAddress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier onlyGovernance() {
|
modifier onlyGovernance() {
|
||||||
@ -78,56 +73,7 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGlobalTornUniswapPoolFee(uint24 _newGlobalTornUniswapPoolFee) public virtual onlyGovernance {
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
||||||
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(
|
function getFee(
|
||||||
IERC20 _torn,
|
IERC20 _torn,
|
||||||
@ -136,30 +82,102 @@ contract UniswapV3FeeOracle is IFeeOracle {
|
|||||||
uint64 _feePercent,
|
uint64 _feePercent,
|
||||||
uint64 _feePercentDivisor
|
uint64 _feePercentDivisor
|
||||||
) public view virtual override returns (uint160) {
|
) 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 fee is 0 return
|
||||||
if (_feePercent == 0) return 0;
|
if (_feePercent == 0) return 0;
|
||||||
|
|
||||||
// If it's not an ERC20 it has to be ETH, use the WETH token
|
// If it's not an ERC20 it has to be ETH, use the WETH token
|
||||||
_data.token = _data.isERC20 ? _data.token : IERC20(UniswapV3OracleHelper.WETH);
|
_data.token = _data.isERC20 ? _data.token : IERC20(UniswapV3OracleHelper.WETH);
|
||||||
|
|
||||||
// After we have decided our token get fee data for it
|
// Get global config
|
||||||
UniswapV3FeeData memory uniFeeData = uniswapFeeData[_data.token];
|
GlobalOracleConfig memory global = globals;
|
||||||
|
|
||||||
// Calc price ratio
|
// Get pool fee for the token and calc the price ratio
|
||||||
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
|
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
|
||||||
[address(_torn), address(_data.token)],
|
[address(_torn), address(_data.token)],
|
||||||
[uniFeeData.tornPoolFee, uniFeeData.tokenPoolFee],
|
[global.tornPoolFee, poolFeesByToken[_data.token]],
|
||||||
uniFeeData.timePeriod
|
global.twapIntervalSeconds
|
||||||
);
|
);
|
||||||
|
|
||||||
// And now all according to legacy calculation
|
// And now all according to legacy calculation
|
||||||
return uint160(
|
return uint160(
|
||||||
_instance.denomination().mul(UniswapV3OracleHelper.RATIO_DIVIDER).div(tokenPriceRatio).mul(
|
_instance.denomination().mul(UniswapV3OracleHelper.RATIO_DIVIDER).div(tokenPriceRatio).mul(
|
||||||
uint256(_feePercent)
|
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