Finalize untested CurveFeeOracle

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-06 21:13:54 +00:00
parent e8b44ac3f8
commit bf352a3f67
5 changed files with 414 additions and 87 deletions

298
src/v2/CurveFeeOracle.sol Normal file
View 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();
}
}

View File

@ -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;
}

View File

@ -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];

View File

@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

View File

@ -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);
}
}