5765376121
Signed-off-by: AlienTornadosaurusHex <>
215 lines
7.3 KiB
Plaintext
215 lines
7.3 KiB
Plaintext
// 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, FeeData } from "./interfaces/IFeeOracle.sol";
|
|
|
|
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
|
|
|
|
import { InstanceState } from "./InstanceRegistry.sol";
|
|
|
|
/**
|
|
* @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;
|
|
uint32 twapIntervalSeconds;
|
|
uint16 minObservationCardinality;
|
|
}
|
|
|
|
/**
|
|
* @title UniswapV3FeeOracle
|
|
* @author AlienTornadosaurusHex
|
|
* @notice A TORN fee oracle for any Uniswap V3 token pool.
|
|
*/
|
|
contract UniswapV3FeeOracle is IFeeOracle {
|
|
using SafeMath for uint256;
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
/**
|
|
* @notice The Governance Proxy address
|
|
*/
|
|
address public immutable governanceProxyAddress;
|
|
|
|
/**
|
|
* @notice Global configuration valid across all `getFee()` (and other) calls
|
|
*/
|
|
GlobalOracleConfig public globals;
|
|
|
|
/**
|
|
* @notice Uniswap pool fees for each token registered
|
|
*/
|
|
mapping(IERC20 => uint24) public poolFeesByToken;
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
event PoolFeeUpdated(IERC20 token, uint24 newPoolFee);
|
|
event GlobalTornPoolFeeUpdated(uint24 newPoolFee);
|
|
event GlobalTwapIntervalSecondsUpdated(uint32 newUniswapTimePeriod);
|
|
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
constructor(address _governanceProxyAddress) public {
|
|
governanceProxyAddress = _governanceProxyAddress;
|
|
}
|
|
|
|
modifier onlyGovernance() {
|
|
require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance");
|
|
_;
|
|
}
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
function update() public virtual override { }
|
|
|
|
function getFee(
|
|
IERC20 _torn,
|
|
ITornadoInstance _instance,
|
|
InstanceState memory _data,
|
|
FeeData memory _lastFee,
|
|
uint32 _feePercentDivisor
|
|
) public view virtual override returns (uint160) {
|
|
// If fee is 0 return
|
|
if (_lastFee.percent == 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);
|
|
|
|
// Get global config
|
|
GlobalOracleConfig memory global = globals;
|
|
|
|
// Get pool fee for the token and calc the price ratio
|
|
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
|
|
[address(_torn), address(_data.token)],
|
|
[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(_lastFee.percent)
|
|
).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 globals are initialized
|
|
|
|
require(global.twapIntervalSeconds != 0, "UniswapV3FeeOracle: time period not initialized");
|
|
require(global.minObservationCardinality != 0, "UniswapV3FeeOracle: cardinality not initialized");
|
|
|
|
// Only do this if not zeroing out
|
|
if (_tokenPoolFee != 0) {
|
|
// 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, bool _setSpecific)
|
|
public
|
|
virtual
|
|
onlyGovernance
|
|
{
|
|
globals.tornPoolFee = _newGlobalTornPoolFee;
|
|
|
|
// For `getPriceRatioOfTokens`
|
|
if (_setSpecific) {
|
|
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);
|
|
}
|
|
|
|
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
|
|
|
|
function getGlobalTornPoolFee() public view virtual returns (uint24) {
|
|
return globals.tornPoolFee;
|
|
}
|
|
|
|
function getGlobalTwapIntervalSeconds() public view virtual returns (uint32) {
|
|
return globals.twapIntervalSeconds;
|
|
}
|
|
|
|
function getGlobalMinObservationCardinality() public view virtual returns (uint16) {
|
|
return globals.minObservationCardinality;
|
|
}
|
|
}
|