Code quality improvements

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-16 16:12:22 +00:00
parent b25f8d569c
commit 1578f5ac69
10 changed files with 214 additions and 162 deletions

@ -19,11 +19,11 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports // Local imports
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { IFeeOracle, FeeData } from "./interfaces/IFeeOracle.sol";
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol"; import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { InstanceData } from "./InstanceRegistry.sol"; import { InstanceState } from "./InstanceRegistry.sol";
/** /**
* @dev The global configuration for the `UniswapV3FeeOracle` contract. TORN is used in all `getFee()` calls, * @dev The global configuration for the `UniswapV3FeeOracle` contract. TORN is used in all `getFee()` calls,
@ -86,12 +86,12 @@ contract UniswapV3FeeOracle is IFeeOracle {
function getFee( function getFee(
IERC20 _torn, IERC20 _torn,
ITornadoInstance _instance, ITornadoInstance _instance,
InstanceData memory _data, InstanceState memory _data,
uint32 _feePercent, FeeData memory _lastFee,
uint32 _feePercentDivisor uint32 _feePercentDivisor
) public view virtual override returns (uint160) { ) public view virtual override returns (uint160) {
// If fee is 0 return // If fee is 0 return
if (_feePercent == 0) return 0; if (_lastFee.percent == 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);
@ -109,7 +109,7 @@ contract UniswapV3FeeOracle is IFeeOracle {
// 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(_lastFee.percent)
).div(uint256(_feePercentDivisor)) ).div(uint256(_feePercentDivisor))
); );
} }

@ -119,8 +119,6 @@ contract InfrastructureUpgradeProposal {
UniswapFeeOracle uniswapFeeOracle = UniswapFeeOracle(deployedUniswapFeeOracleAddress); UniswapFeeOracle uniswapFeeOracle = UniswapFeeOracle(deployedUniswapFeeOracleAddress);
uniswapFeeOracle.setTwapIntervalSeconds(5400); // Legacy value, still makes sense
uniswapFeeOracle.setMinObservationCardinality(1); // Set it to minimum so cDAI passes uniswapFeeOracle.setMinObservationCardinality(1); // Set it to minimum so cDAI passes
// Each of the instances are going to require a Uniswap Pool Fee to be set such that we actually know // Each of the instances are going to require a Uniswap Pool Fee to be set such that we actually know

@ -14,13 +14,13 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports // Local imports
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { IFeeOracle, InstanceWithFee } from "./interfaces/IFeeOracle.sol";
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol"; import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { UniswapFeeOracle } from "./UniswapFeeOracle.sol"; import { UniswapFeeOracle } from "./UniswapFeeOracle.sol";
import { InstanceData } from "./InstanceRegistry.sol"; import { InstanceState } from "./InstanceRegistry.sol";
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FINANCE INTERFACE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CURVE FINANCE INTERFACE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -218,35 +218,37 @@ contract CurveFeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function update() public virtual override { } function update(IERC20, InstanceWithFee memory) public virtual override { }
function getFee( function getFee(IERC20, InstanceWithFee memory _instance)
IERC20, public
ITornadoInstance _instance, view
InstanceData memory, virtual
uint32 _feePercent, override
uint32 _feePercentDivisor returns (uint160)
) public view virtual override returns (uint160) { {
// If fee is 0 return // If fee is 0 return
if (_feePercent == 0) return 0; if (_instance.fee.percent == 0) return 0;
// Either TOKEN price in ETH or TOKEN price in TORN // Either TOKEN price in ETH or TOKEN price in TORN
uint256 price = chainedPriceOracles[_instance].price_oracle(); uint256 price = chainedPriceOracles[_instance.logic].price_oracle();
// If the below is true, then this will finalize the price and give the TOKEN price in TORN // 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 // Above MUST be TOKEN price in ETH, then (ETH/TOKEN)/(ETH/TORN)=TORN/TOKEN, meaning the TOKEN price
// in TORN // in TORN
if (tornOracleIsUniswapV3) { if (tornOracleIsUniswapV3) {
price = price * UniswapV3OracleHelper.RATIO_DIVIDER price = price * UniswapV3OracleHelper.RATIO_DIVIDER
/ uniswapFeeOracle.getTokenPerTORN(IERC20(UniswapV3OracleHelper.WETH)); / uniswapFeeOracle.getTokenPerTORN(
IERC20(UniswapV3OracleHelper.WETH), _instance.fee.updateInterval
);
} }
// The price is now either prepared by the chained oracles or by Uniswap V3, in any case we do the // 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 // legacy calculation and return the fee value
return uint160( return uint160(
_instance.denomination().mul(price).div(UniswapV3OracleHelper.RATIO_DIVIDER).mul( _instance.logic.denomination().mul(price).div(UniswapV3OracleHelper.RATIO_DIVIDER).mul(
uint256(_feePercent) uint256(_instance.fee.percent)
).div(uint256(_feePercentDivisor)) ).div(uint256(_instance.fee.divisor))
); );
} }
@ -301,7 +303,7 @@ contract CurveFeeOracle is IFeeOracle {
emit UniswapFeeOracleUpdated(address(_uniswapFeeOracle)); emit UniswapFeeOracleUpdated(address(_uniswapFeeOracle));
} }
function setTornOracleIsUniswapV3(bool _is) public virtual onlyGovernance { function setTornOracleIsUniswap(bool _is) public virtual onlyGovernance {
tornOracleIsUniswapV3 = _is; tornOracleIsUniswapV3 = _is;
if (_is) emit TornOracleIsUniswapV3(); if (_is) emit TornOracleIsUniswapV3();
else emit TornOracleIsCurve(); else emit TornOracleIsCurve();

@ -14,9 +14,9 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local Imports // Local Imports
import { InstanceRegistry } from "./InstanceRegistry.sol"; import { IFeeOracle, FeeData, FeeDataForOracle, InstanceWithFee } from "./interfaces/IFeeOracle.sol";
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { InstanceRegistry } from "./InstanceRegistry.sol";
/** /**
* @title FeeManagerLegacyStorage * @title FeeManagerLegacyStorage
@ -51,16 +51,6 @@ contract FeeManagerLegacyStorage {
mapping(ITornadoInstance => uint256) internal _oldFeesForInstanceUpdateTime; mapping(ITornadoInstance => uint256) internal _oldFeesForInstanceUpdateTime;
} }
/**
* @notice Fee data which is valid and also influences fee updating across all oracles.
*/
struct FeeData {
uint160 amount;
uint32 percent;
uint32 updateInterval;
uint32 lastUpdateTime;
}
/** /**
* @title FeeOracleManager * @title FeeOracleManager
* @author AlienTornadosaurusHex * @author AlienTornadosaurusHex
@ -132,6 +122,7 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
) external onlyGovernance initializer { ) external onlyGovernance initializer {
// Get num of existing instances // Get num of existing instances
uint256 numInstances = _instances.length; uint256 numInstances = _instances.length;
uint32 feeUpdateInterval = _oldFeeUpdateInterval;
for (uint256 i = 0; i < numInstances; i++) { for (uint256 i = 0; i < numInstances; i++) {
// For each instance // For each instance
@ -141,7 +132,7 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
feesByInstance[instance] = FeeData({ feesByInstance[instance] = FeeData({
amount: _oldFeesForInstance[instance], amount: _oldFeesForInstance[instance],
percent: uint32(_percents[i]), percent: uint32(_percents[i]),
updateInterval: _oldFeeUpdateInterval, updateInterval: feeUpdateInterval,
lastUpdateTime: uint32(_oldFeesForInstanceUpdateTime[instance]) lastUpdateTime: uint32(_oldFeesForInstanceUpdateTime[instance])
}); });
@ -192,22 +183,15 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
require(address(oracle) != address(0), "FeeOracleManager: instance has no oracle"); require(address(oracle) != address(0), "FeeOracleManager: instance has no oracle");
// Now update if we do not respect the interval or we respect it and are in the interval // Now update if we do not respect the interval or we respect it and are in the interval
if (!_respectFeeUpdateInterval || (fee.updateInterval < now - fee.lastUpdateTime)) { if (!_respectFeeUpdateInterval || (fee.updateInterval <= now - fee.lastUpdateTime)) {
// Prepare data for the process
InstanceWithFee memory _feeInstance = populateInstanceWithFeeData(_instance, fee);
// Allow oracle to gate for fee manager and implement own logic // Allow oracle to gate for fee manager and implement own logic
oracle.update(); oracle.update(torn, _feeInstance);
// There must a be a fee set otherwise it's just 0 // There must a be a fee set otherwise it's just 0
if (fee.percent != 0) { fee.amount = fee.percent != 0 ? oracle.getFee(torn, _feeInstance) : 0;
fee.amount = oracle.getFee(
torn,
_instance,
instanceRegistry.getInstanceData(_instance),
fee.percent,
FEE_PERCENT_DIVISOR
);
} else {
fee.amount = 0;
}
// Store // Store
feesByInstance[_instance] = FeeData({ feesByInstance[_instance] = FeeData({
@ -243,13 +227,14 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
// An address(0) oracle means we're removing an oracle for an instance // An address(0) oracle means we're removing an oracle for an instance
if (_oracleAddress != address(0)) { if (_oracleAddress != address(0)) {
// Reverts if oracle doesn't implement // Prepare data for the process
oracle.update(); InstanceWithFee memory _feeInstance = populateInstanceWithFeeData(instance, fee);
// Reverts if oracle doesn't implement // Reverts if oracle doesn't implement
fee.amount = oracle.getFee( oracle.update(torn, _feeInstance);
torn, instance, instanceRegistry.getInstanceData(instance), fee.percent, FEE_PERCENT_DIVISOR
); // Reverts if oracle doesn't implement
fee.amount = oracle.getFee(torn, _feeInstance);
// Note down updated fee // Note down updated fee
feesByInstance[instance] = FeeData({ feesByInstance[instance] = FeeData({
@ -293,13 +278,35 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
} }
function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) { function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
return instanceFeeOracles[instance].getFee( return instanceFeeOracles[instance].getFee(torn, populateInstanceWithFeeData(instance));
torn, }
instance,
instanceRegistry.getInstanceData(instance), function populateInstanceWithFeeData(ITornadoInstance _regularInstance)
feesByInstance[instance].percent, public
FEE_PERCENT_DIVISOR view
); virtual
returns (InstanceWithFee memory)
{
return populateInstanceWithFeeData(_regularInstance, feesByInstance[_regularInstance]);
}
function populateInstanceWithFeeData(ITornadoInstance _regularInstance, FeeData memory _fee)
public
view
virtual
returns (InstanceWithFee memory)
{
return InstanceWithFee({
logic: _regularInstance,
state: instanceRegistry.getInstanceState(_regularInstance),
fee: FeeDataForOracle({
amount: _fee.amount,
percent: _fee.percent,
divisor: FEE_PERCENT_DIVISOR,
updateInterval: _fee.updateInterval,
lastUpdateTime: _fee.lastUpdateTime
})
});
} }
function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) { function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
@ -342,9 +349,8 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
FeeData memory fee = feesByInstance[instance]; FeeData memory fee = feesByInstance[instance];
uint256 marketFee = instanceFeeOracles[instance].getFee( uint256 marketFee =
torn, instance, instanceRegistry.getInstanceData(instance), fee.percent, FEE_PERCENT_DIVISOR instanceFeeOracles[instance].getFee(torn, populateInstanceWithFeeData(instance, fee));
);
if (marketFee != 0) { if (marketFee != 0) {
deviations[i] = int256((fee.amount * 1000) / marketFee) - 1000; deviations[i] = int256((fee.amount * 1000) / marketFee) - 1000;

@ -73,7 +73,7 @@ contract InstanceRegistryLegacyStorage {
/** /**
* @dev This struct holds barebones information regarding an instance and its data location in storage. * @dev This struct holds barebones information regarding an instance and its data location in storage.
*/ */
struct InstanceData { struct InstanceState {
IERC20 token; IERC20 token;
uint80 index; uint80 index;
bool isERC20; bool isERC20;
@ -100,7 +100,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/** /**
* @notice Essential data regarding instances, see struct above * @notice Essential data regarding instances, see struct above
*/ */
mapping(ITornadoInstance => InstanceData) public instanceData; mapping(ITornadoInstance => InstanceState) public instanceData;
/** /**
* @notice All instances enumerable * @notice All instances enumerable
@ -183,7 +183,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
// Store the collected data of the instance // Store the collected data of the instance
instanceData[_instance] = instanceData[_instance] =
InstanceData({ token: token, index: instanceIndex, isERC20: isERC20, isEnabled: true }); InstanceState({ token: token, index: instanceIndex, isERC20: isERC20, isEnabled: true });
// Log // Log
emit InstanceAdded(address(_instance), instanceIndex, isERC20); emit InstanceAdded(address(_instance), instanceIndex, isERC20);
@ -211,7 +211,7 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
// Grab data needed to remove the instance // Grab data needed to remove the instance
ITornadoInstance instance = ITornadoInstance(_instanceAddress); ITornadoInstance instance = ITornadoInstance(_instanceAddress);
InstanceData memory data = instanceData[instance]; InstanceState memory data = instanceData[instance];
// Checks whether already removed, so allowance can't be killed // Checks whether already removed, so allowance can't be killed
@ -280,32 +280,32 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
} }
} }
function getAllInstanceData() public view virtual returns (InstanceData[] memory data) { function getAllInstanceState() public view virtual returns (InstanceState[] memory data) {
return getInstanceData(0, instances.length - 1); return getInstanceState(0, instances.length - 1);
} }
function getInstanceData(uint256 _inclusiveStartIndex, uint256 _inclusiveEndIndex) function getInstanceState(uint256 _inclusiveStartIndex, uint256 _inclusiveEndIndex)
public public
view view
virtual virtual
returns (InstanceData[] memory data) returns (InstanceState[] memory data)
{ {
data = new InstanceData[](1 + _inclusiveEndIndex - _inclusiveStartIndex); data = new InstanceState[](1 + _inclusiveEndIndex - _inclusiveStartIndex);
for (uint256 i = _inclusiveStartIndex; i < _inclusiveEndIndex + 1; i++) { for (uint256 i = _inclusiveStartIndex; i < _inclusiveEndIndex + 1; i++) {
data[i - _inclusiveStartIndex] = instanceData[instances[i]]; data[i - _inclusiveStartIndex] = instanceData[instances[i]];
} }
} }
function getInstanceData(uint256 _index) public view virtual returns (InstanceData memory data) { function getInstanceState(uint256 _index) public view virtual returns (InstanceState memory data) {
return instanceData[instances[_index]]; return instanceData[instances[_index]];
} }
function getInstanceData(ITornadoInstance _instance) function getInstanceState(ITornadoInstance _instance)
public public
view view
virtual virtual
returns (InstanceData memory data) returns (InstanceState memory data)
{ {
return instanceData[_instance]; return instanceData[_instance];
} }

@ -17,7 +17,7 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports // Local imports
import { RelayerRegistry } from "../v1/RelayerRegistry.sol"; import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
import { InstanceRegistry, InstanceData } from "./InstanceRegistry.sol"; import { InstanceRegistry, InstanceState } from "./InstanceRegistry.sol";
/** /**
* @title TornadoRouter * @title TornadoRouter

@ -19,11 +19,11 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports // Local imports
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { IFeeOracle, InstanceWithFee } from "./interfaces/IFeeOracle.sol";
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol"; import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { InstanceData } from "./InstanceRegistry.sol"; import { InstanceState } from "./InstanceRegistry.sol";
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UNISWAP INLINED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UNISWAP INLINED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -210,11 +210,6 @@ contract UniswapFeeOracle is IFeeOracle {
*/ */
address public feeOracleManagerAddress; address public feeOracleManagerAddress;
/**
* @notice The TWAP interval in seconds (this is common)
*/
uint32 public twapIntervalSeconds;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V3 ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V3 ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/** /**
@ -252,10 +247,13 @@ contract UniswapFeeOracle is IFeeOracle {
governanceProxyAddress = _governanceProxyAddress; governanceProxyAddress = _governanceProxyAddress;
feeOracleManagerAddress = _feeOracleManagerAddress; feeOracleManagerAddress = _feeOracleManagerAddress;
// We're immediately doing this because we want to immediately be ready with a price on execution
(, uint256 _price1CumulativeLast, uint32 _timestamp) = (, uint256 _price1CumulativeLast, uint32 _timestamp) =
UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress); UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress);
lastCumulativeTORNPriceInETH = _price1CumulativeLast; lastCumulativeTORNPriceInETH = _price1CumulativeLast;
last.updatedTimestamp = _timestamp; last.updatedTimestamp = _timestamp;
} }
@ -271,42 +269,33 @@ contract UniswapFeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function getFee( function getFee(IERC20, InstanceWithFee memory _instance)
IERC20, public
ITornadoInstance _instance, view
InstanceData memory _data, virtual
uint32 _feePercent, override
uint32 _feePercentDivisor returns (uint160)
) public view virtual override returns (uint160) { {
// If 0%, 0 // If 0%, 0
if (_feePercent == 0) return 0; if (_instance.fee.percent == 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); _instance.state.token =
_instance.state.isERC20 ? _instance.state.token : IERC20(UniswapV3OracleHelper.WETH);
// Now get the price ratio // Now get the price ratio
uint256 priceRatio = getTokenPerTORN(_data.token); uint256 priceRatio = getTokenPerTORN(_instance.state.token, _instance.fee.updateInterval);
// Denomination (tokens) times torn per token, through e18 because both are in e18, // Denomination (tokens) times torn per token, through e18 because both are in e18,
// times fee percent and then through for proper value // times fee percent and then through for proper value
return uint160( return uint160(
_instance.denomination().mul(1e18).div(priceRatio).mul(uint256(_feePercent)).div( _instance.logic.denomination().mul(1e18).div(priceRatio).mul(uint256(_instance.fee.percent)).div(
uint256(_feePercentDivisor) uint256(_instance.fee.divisor)
) )
); );
} }
function getTokenPerTORN(IERC20 _token) public view virtual returns (uint256) { function update(IERC20, InstanceWithFee memory _instance) public virtual override onlyFeeOracleManager {
// Get the average price of ETH in the TOKEN
uint256 ethPerTokenAverage = UniswapV3OracleHelper.getPriceOfTokenInWETH(
address(_token), poolFeesByToken[_token], twapIntervalSeconds
);
// Now get average price of TORN per TOKEN
return ethPerTokenAverage * last.averagePrice.decode() / 1e18;
}
function update() public virtual override onlyFeeOracleManager {
// Get the timestamp of the last update // Get the timestamp of the last update
uint32 timestampLastUpdate = last.updatedTimestamp; uint32 timestampLastUpdate = last.updatedTimestamp;
@ -316,29 +305,39 @@ contract UniswapFeeOracle is IFeeOracle {
// Calculate elapsed time, no matter whether overflow (uniswap logic) // Calculate elapsed time, no matter whether overflow (uniswap logic)
uint32 elapsed = currentTimestamp - timestampLastUpdate; uint32 elapsed = currentTimestamp - timestampLastUpdate;
// Get last token0/token1 meaning TORN per ETH // Multiple instances may refer to this oracle
(, uint256 _price1CumulativeLast,) = UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress); // For this reason we will basically allow only one to update
if (_instance.fee.updateInterval <= elapsed) {
// Get last token0/token1 meaning TORN per ETH counterfactually
(, uint256 _price1CumulativeLast,) =
UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress);
// Save TWAP data, meaning the average price (TORN per ETH) // Save TWAP data, meaning the average price (TORN per ETH)
last = TWAPData({ last = TWAPData({
averagePrice: FixedPoint.uq112x112( averagePrice: FixedPoint.uq112x112(
uint224((_price1CumulativeLast - lastCumulativeTORNPriceInETH) / elapsed) uint224((_price1CumulativeLast - lastCumulativeTORNPriceInETH) / elapsed)
), ),
updatedTimestamp: currentTimestamp updatedTimestamp: currentTimestamp
}); });
// Update the cumulative value // Update the cumulative value
lastCumulativeTORNPriceInETH = _price1CumulativeLast; lastCumulativeTORNPriceInETH = _price1CumulativeLast;
}
}
function getTokenPerTORN(IERC20 _token, uint32 _interval) public view virtual returns (uint256) {
// Get the average price of TOKEN in WETH
uint256 ethPerTokenAverage =
UniswapV3OracleHelper.getPriceOfTokenInWETH(address(_token), poolFeesByToken[_token], _interval);
// TORN PER ETH * ETH PER TOKEN = TORN per TOKEN
return ethPerTokenAverage * last.averagePrice.decode() / 1e18;
} }
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setPoolFeeForToken(IERC20 _token, uint24 _tokenPoolFee) public virtual onlyGovernance { function setPoolFeeForToken(IERC20 _token, uint24 _tokenPoolFee) public virtual onlyGovernance {
// Get global config // Check whether cardinality is initialized
// Check whether globals are initialized
require(twapIntervalSeconds != 0, "UniswapV3FeeOracle: time period not initialized");
require(minObservationCardinality != 0, "UniswapV3FeeOracle: cardinality not initialized"); require(minObservationCardinality != 0, "UniswapV3FeeOracle: cardinality not initialized");
// Only do this if not zeroing out // Only do this if not zeroing out
@ -368,11 +367,6 @@ contract UniswapFeeOracle is IFeeOracle {
emit PoolFeeUpdated(_token, _tokenPoolFee); emit PoolFeeUpdated(_token, _tokenPoolFee);
} }
function setTwapIntervalSeconds(uint32 _newGlobalTwapIntervalSeconds) public virtual onlyGovernance {
twapIntervalSeconds = _newGlobalTwapIntervalSeconds;
emit GlobalTwapIntervalSecondsUpdated(_newGlobalTwapIntervalSeconds);
}
function setMinObservationCardinality(uint16 _newGlobalMinObservationCardinality) function setMinObservationCardinality(uint16 _newGlobalMinObservationCardinality)
public public
virtual virtual
@ -388,7 +382,15 @@ contract UniswapFeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function getTWAPData() public view virtual returns (TWAPData memory) {
return last;
}
function getAverageTORNPerETH() public view virtual returns (uint256) { function getAverageTORNPerETH() public view virtual returns (uint256) {
return last.averagePrice.decode(); return last.averagePrice.decode();
} }
function getLastUpdatedTimet() public view virtual returns (uint32) {
return last.updatedTimestamp;
}
} }

@ -13,16 +13,51 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports // Local imports
import { InstanceData } from "../InstanceRegistry.sol"; import { InstanceState } from "../InstanceRegistry.sol";
interface IFeeOracle { /**
function update() external; * @notice Fee data which is valid across all instances, stored in the fee oracle manager.
*/
function getFee( struct FeeData {
IERC20 _torn, uint160 amount;
ITornadoInstance _instance, uint32 percent;
InstanceData memory _data, uint32 updateInterval;
uint32 _feePercent, uint32 lastUpdateTime;
uint32 _feePercentDivisor }
) external view returns (uint160);
/**
* @notice Fee data which is only used and constructed when updating and oracle fee or getting it.
*/
struct FeeDataForOracle {
uint160 amount;
uint32 percent;
uint32 divisor;
uint32 updateInterval;
uint32 lastUpdateTime;
}
/**
* @notice This is the full bundled data for an instance.
*/
struct InstanceWithFee {
ITornadoInstance logic;
InstanceState state;
FeeDataForOracle fee;
}
/**
* @title IFeeOracle
* @author AlienTornadosaurusHex
* @notice The interface which all fee oracles for Tornado should implement.
*/
interface IFeeOracle {
/**
* @dev This function is intended to allow oracles to configure their state
*/
function update(IERC20 _torn, InstanceWithFee memory _instance) external;
/**
* @dev This function must return a uint160 compatible TORN fee
*/
function getFee(IERC20 _torn, InstanceWithFee memory _instance) external view returns (uint160);
} }

@ -19,7 +19,7 @@ import { console2 } from "forge-std/console2.sol";
// Local imports // Local imports
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol"; import { FeeOracleManager, FeeDataForOracle, InstanceWithFee } from "src/v2/FeeOracleManager.sol";
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol"; import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
@ -29,7 +29,7 @@ import { UniswapFeeOracle } from "src/v2/UniswapFeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol"; import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceData } from "src/v2/InstanceRegistry.sol"; import { InstanceState } from "src/v2/InstanceRegistry.sol";
contract OracleTests is Test { contract OracleTests is Test {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -55,11 +55,10 @@ contract OracleTests is Test {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL")); vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
uniswapFeeOracle = new UniswapFeeOracle(address(this), address(feeOracleManager)); uniswapFeeOracle = new UniswapFeeOracle(address(this), address(feeOracleManager));
uniswapFeeOracle.setTwapIntervalSeconds(5400);
uniswapFeeOracle.setMinObservationCardinality(10); uniswapFeeOracle.setMinObservationCardinality(10);
feeOracle = new CurveFeeOracle(address(this)); feeOracle = new CurveFeeOracle(address(this));
feeOracle.setTornOracleIsUniswapV3(false); feeOracle.setTornOracleIsUniswap(false);
feeOracle.setUniswapFeeOracle(uniswapFeeOracle); feeOracle.setUniswapFeeOracle(uniswapFeeOracle);
} }
@ -70,7 +69,12 @@ contract OracleTests is Test {
"\nShould be 30 * (ETH/USD) ------------------------------------------------\n", "\nShould be 30 * (ETH/USD) ------------------------------------------------\n",
uint256( uint256(
feeOracle.getFee( feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000 TORN,
InstanceWithFee({
logic: cu10_000,
state: InstanceState(IERC20(cu10_000.token()), 0, true, true),
fee: FeeDataForOracle(0, 30, 10_000, 2 days, 0)
})
) )
), ),
"\n------------------------------------------------------------------------\n" "\n------------------------------------------------------------------------\n"
@ -78,7 +82,7 @@ contract OracleTests is Test {
} }
function test_curveFeeChainedTORN() public { function test_curveFeeChainedTORN() public {
feeOracle.setTornOracleIsUniswapV3(true); feeOracle.setTornOracleIsUniswap(true);
_setCurveFeeChainedOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000 _setCurveFeeChainedOracleForInstance(feeOracle, cu10_000); // CRVUSD 10_000
@ -86,7 +90,12 @@ contract OracleTests is Test {
"\nTORN Fee calculated ------------------------------------------------------\n", "\nTORN Fee calculated ------------------------------------------------------\n",
uint256( uint256(
feeOracle.getFee( feeOracle.getFee(
TORN, cu10_000, InstanceData(IERC20(cu10_000.token()), 0, true, true), 30, 10_000 TORN,
InstanceWithFee({
logic: cu10_000,
state: InstanceState(IERC20(cu10_000.token()), 0, true, true),
fee: FeeDataForOracle(0, 30, 10_000, 2 days, 0)
})
) )
), ),
"\n------------------------------------------------------------------------\n" "\n------------------------------------------------------------------------\n"

@ -21,7 +21,7 @@ import { UniswapFeeOracle } from "src/v2/UniswapFeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol"; import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceRegistry, InstanceData } from "src/v2/InstanceRegistry.sol"; import { InstanceRegistry, InstanceState } from "src/v2/InstanceRegistry.sol";
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol"; import { FeeOracleManager } from "src/v2/FeeOracleManager.sol";
@ -161,7 +161,7 @@ contract ProposalTests is Instances, TornadoProposalTest {
delimit(); delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n"); console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n");
InstanceData memory data = instanceRegistry.getInstanceData(cu100); InstanceState memory data = instanceRegistry.getInstanceState(cu100);
delimit(); delimit();
console2.log("cu100:"); console2.log("cu100:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
@ -169,28 +169,28 @@ contract ProposalTests is Instances, TornadoProposalTest {
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(cu1_000); data = instanceRegistry.getInstanceState(cu1_000);
console2.log("cu1_000:"); console2.log("cu1_000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(cu10_000); data = instanceRegistry.getInstanceState(cu10_000);
console2.log("cu10_000:"); console2.log("cu10_000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(cu100_000); data = instanceRegistry.getInstanceState(cu100_000);
console2.log("cu100_000:"); console2.log("cu100_000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(cu1_000_000); data = instanceRegistry.getInstanceState(cu1_000_000);
console2.log("cu1_000_000:"); console2.log("cu1_000_000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
@ -294,7 +294,7 @@ contract ProposalTests is Instances, TornadoProposalTest {
delimit(); delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n"); console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n");
InstanceData memory data = instanceRegistry.getInstanceData(eth10); InstanceState memory data = instanceRegistry.getInstanceState(eth10);
delimit(); delimit();
console2.log("eth10:"); console2.log("eth10:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
@ -302,21 +302,21 @@ contract ProposalTests is Instances, TornadoProposalTest {
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(eth100); data = instanceRegistry.getInstanceState(eth100);
console2.log("eth100:"); console2.log("eth100:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(dai10000); data = instanceRegistry.getInstanceState(dai10000);
console2.log("dai10000:"); console2.log("dai10000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(usdt100); data = instanceRegistry.getInstanceState(usdt100);
console2.log("usdt100:"); console2.log("usdt100:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
@ -335,14 +335,14 @@ contract ProposalTests is Instances, TornadoProposalTest {
// Now log again // Now log again
console2.log("\n~~~~~~~~~~~~~~~~~~ SHOULD HAVE CHANGED ~~~~~~~~~~~~~~~~~~\n"); console2.log("\n~~~~~~~~~~~~~~~~~~ SHOULD HAVE CHANGED ~~~~~~~~~~~~~~~~~~\n");
delimit(); delimit();
data = instanceRegistry.getInstanceData(2); data = instanceRegistry.getInstanceState(2);
console2.log("NOT eth10:"); console2.log("NOT eth10:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20); console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled); console2.log("isenabled: ", data.isEnabled);
delimit(); delimit();
data = instanceRegistry.getInstanceData(6); data = instanceRegistry.getInstanceState(6);
console2.log("NOT dai10000:"); console2.log("NOT dai10000:");
console2.log("token: ", address(data.token)); console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index)); console2.log("index: ", uint256(data.index));
@ -358,14 +358,14 @@ contract ProposalTests is Instances, TornadoProposalTest {
instanceRegistry.addInstance(eth10); instanceRegistry.addInstance(eth10);
vm.prank(address(governance)); vm.prank(address(governance));
data = instanceRegistry.getInstanceData(dai10000); data = instanceRegistry.getInstanceState(dai10000);
require(data.token == DAI, "not dai"); require(data.token == DAI, "not dai");
require(data.index == 15, "not 15"); require(data.index == 15, "not 15");
require(data.isERC20, "not token"); require(data.isERC20, "not token");
require(data.isEnabled, "not enabled"); require(data.isEnabled, "not enabled");
vm.prank(address(governance)); vm.prank(address(governance));
data = instanceRegistry.getInstanceData(eth10); data = instanceRegistry.getInstanceState(eth10);
require(data.token == IERC20(0), "not eth"); require(data.token == IERC20(0), "not eth");
require(data.index == 16, "not last"); require(data.index == 16, "not last");
require(!data.isERC20, "not not token"); require(!data.isERC20, "not not token");