Finalize Uniswap Oracle

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-04 22:37:03 +00:00
parent cd2b889c2f
commit e8b44ac3f8
7 changed files with 497 additions and 80 deletions

@ -14,7 +14,7 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local Imports
import { InstanceRegistry } from "../v1/tornado-proxy/InstanceRegistry.sol";
import { InstanceRegistry } from "./InstanceRegistry.sol";
import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
@ -31,13 +31,20 @@ struct FeeData {
* FeeOracleManager (formerly FeeManager).
*/
contract FeeManagerLegacyStorage {
uint24 public deprecatedUniswapTornPoolSwappingFee;
uint32 public deprecatedUniswapTimePeriod;
/* @dev From first contract */
uint24 private _deprecatedUniswapTornPoolSwappingFee;
/* @dev From first contract */
uint32 private _deprecatedUniswapTimePeriod;
/* @dev From first contract, the only value we keep alive */
uint24 public feeUpdateInterval;
mapping(ITornadoInstance => uint160) public oldFeesForInstance;
mapping(ITornadoInstance => uint256) public oldFeesForInstanceUpdateTime;
/* @dev From first contract, only used for initialization to preserve old values in the moment */
mapping(ITornadoInstance => uint160) internal _oldFeesForInstance;
/* @dev From first contract, only used for initialization to preserve old values in the moment */
mapping(ITornadoInstance => uint256) internal _oldFeesForInstanceUpdateTime;
}
/**
@ -50,14 +57,14 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev Divide protocol fee by this to get the percent value */
uint64 public constant PROTOCOL_FEE_DIVISOR = 1 ether;
/* @dev The address of the TORN token */
address public immutable tornTokenAddress;
uint64 public constant FEE_PERCENT_DIVISOR = 1 ether;
/* @dev The Governance Proxy address */
address public immutable governanceProxyAddress;
/* @dev The TORN token */
IERC20 public immutable torn;
/* @dev The InstanceRegistry contract */
InstanceRegistry public instanceRegistry;
@ -69,14 +76,16 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event FeeUpdateIntervalUpdated(uint24 newLimit);
event FeeUpdated(address indexed instance, uint256 newFee);
event NewOracleSet(address indexed instance, address oracle);
event NewFeeUpdateIntervalSet(uint24 limit);
event OracleUpdated(address indexed instance, address oracle);
event InstanceFeePercentUpdated(address indexed instance, uint64 newFeePercent);
event InstanceRegistryUpdated(address newAddress);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _tornTokenAddress, address _governanceProxyAddress) public {
tornTokenAddress = _tornTokenAddress;
torn = IERC20(_tornTokenAddress);
governanceProxyAddress = _governanceProxyAddress;
}
@ -90,43 +99,46 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
* making sure to not reference old data anywhere.
*/
function initialize(
address _uniswapPoolFeeOracle,
address _uniswapV3FeeOracle,
address _instanceRegistryAddress,
address[] calldata _instanceAddresses,
uint256[] calldata _feePercents
) external onlyGovernance initializer {
// Get num of existing instances
uint256 numInstances = _instanceAddresses.length;
for (uint256 i = 0; i < numInstances; i++) {
// For each instance
ITornadoInstance instance = ITornadoInstance(_instanceAddresses[i]);
// Store it's old data and the percent fees which will Governance will command
feeDataForInstance[instance] = FeeData({
feeAmount: oldFeesForInstance[instance],
feeAmount: _oldFeesForInstance[instance],
feePercent: uint64(_feePercents[i]),
lastUpdated: uint32(oldFeesForInstanceUpdateTime[instance])
lastUpdated: uint32(_oldFeesForInstanceUpdateTime[instance])
});
instanceFeeOracles[instance] = IFeeOracle(_uniswapPoolFeeOracle);
// All old pools use the uniswap fee oracle
instanceFeeOracles[instance] = IFeeOracle(_uniswapV3FeeOracle);
}
// Finally also store the instance registry
instanceRegistry = InstanceRegistry(_instanceRegistryAddress);
}
function instanceFeeWithUpdate(ITornadoInstance _instance) public virtual returns (uint160) {
FeeData memory feeData = feeDataForInstance[_instance];
if (feeUpdateInterval < now - feeData.lastUpdated) {
feeData.feeAmount = updateFee(_instance);
return updateFee(_instance, true);
}
return feeData.feeAmount;
function updateAllFees(bool _respectFeeUpdateInterval)
public
virtual
returns (uint160[] memory newFees)
{
return updateFees(instanceRegistry.getAllInstances(), _respectFeeUpdateInterval);
}
function updateAllFees() public virtual returns (uint160[] memory newFees) {
return updateFees(instanceRegistry.getAllInstanceAddresses());
}
function updateFees(ITornadoInstance[] memory _instances)
function updateFees(ITornadoInstance[] memory _instances, bool _respectFeeUpdateInterval)
public
virtual
returns (uint160[] memory newFees)
@ -136,49 +148,74 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
newFees = new uint160[](numInstances);
for (uint256 i = 0; i < numInstances; i++) {
newFees[i] = updateFee(_instances[i]);
newFees[i] = updateFee(_instances[i], _respectFeeUpdateInterval);
}
}
function updateFee(ITornadoInstance _instance) public virtual returns (uint160 newFee) {
function updateFee(ITornadoInstance _instance, bool _respectFeeUpdateInterval)
public
virtual
returns (uint160)
{
// First get fee data
FeeData memory feeData = feeDataForInstance[_instance];
// Now update if we do not respect the interval or we respect it and are in the interval
if (!_respectFeeUpdateInterval || feeUpdateInterval < -feeData.lastUpdated + now) {
// This will revert if no contract is set
newFee = instanceFeeOracles[_instance].getFee();
feeData.feeAmount = instanceFeeOracles[_instance].getFee(
torn,
_instance,
instanceRegistry.getInstanceData(_instance),
feeData.feePercent,
FEE_PERCENT_DIVISOR
);
// Store
feeDataForInstance[_instance] = FeeData({
feeAmount: newFee,
feePercent: feeDataForInstance[_instance].feePercent,
feeAmount: feeData.feeAmount,
feePercent: feeData.feePercent,
lastUpdated: uint32(now)
});
emit FeeUpdated(address(_instance), newFee);
// Log
emit FeeUpdated(address(_instance), feeData.feeAmount);
}
return newFee;
// In any case return a value
return feeData.feeAmount;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setInstanceRegistry(address _newInstanceRegistryAddress) external onlyGovernance {
instanceRegistry = InstanceRegistry(_newInstanceRegistryAddress);
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
emit InstanceRegistryUpdated(_newInstanceRegistryProxyAddress);
}
function setFeeOracle(address _instanceAddress, address _oracleAddress) external onlyGovernance {
ITornadoInstance instance = ITornadoInstance(_instanceAddress);
IFeeOracle oracle = IFeeOracle(_oracleAddress);
FeeData memory feeData = feeDataForInstance[instance];
// Reverts if no oracle or does not conform to interface
uint160 fee = oracle.getFee();
uint160 fee = oracle.getFee(
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeData.feePercent,
FEE_PERCENT_DIVISOR
);
// Ok, set the oracle
instanceFeeOracles[instance] = oracle;
// Note down updated fee
feeDataForInstance[instance] = FeeData({
feeAmount: fee,
feePercent: feeDataForInstance[instance].feePercent,
lastUpdated: uint32(now)
});
feeDataForInstance[instance] =
FeeData({ feeAmount: fee, feePercent: feeData.feePercent, lastUpdated: uint32(now) });
emit NewOracleSet(_instanceAddress, _oracleAddress);
// Logs
emit OracleUpdated(_instanceAddress, _oracleAddress);
emit FeeUpdated(_instanceAddress, fee);
}
@ -187,11 +224,12 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
onlyGovernance
{
feeDataForInstance[_instance].feePercent = _newFeePercent;
emit InstanceFeePercentUpdated(address(_instance), _newFeePercent);
}
function setFeeUpdateInterval(uint24 newLimit) external onlyGovernance {
feeUpdateInterval = newLimit;
emit NewFeeUpdateIntervalSet(newLimit);
emit FeeUpdateIntervalUpdated(newLimit);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -201,7 +239,13 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
}
function getUpdatedFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
return instanceFeeOracles[instance].getFee();
return instanceFeeOracles[instance].getFee(
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeDataForInstance[instance].feePercent,
FEE_PERCENT_DIVISOR
);
}
function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
@ -212,17 +256,25 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
return feeDataForInstance[instance].lastUpdated;
}
function getFeeDeviations() public view virtual returns (int256[] memory results) {
ITornadoInstance[] memory instances = instanceRegistry.getAllInstanceAddresses();
function getFeeDeviations() public view virtual returns (int256[] memory deviations) {
ITornadoInstance[] memory instances = instanceRegistry.getAllInstances();
uint256 numInstances = instances.length;
results = new int256[](numInstances);
deviations = new int256[](numInstances);
for (uint256 i = 0; i < numInstances; i++) {
ITornadoInstance instance = instances[i];
uint256 marketFee = instanceFeeOracles[instance].getFee();
FeeData memory feeData = feeDataForInstance[instance];
uint256 marketFee = instanceFeeOracles[instance].getFee(
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeData.feePercent,
FEE_PERCENT_DIVISOR
);
int256 deviation;
@ -230,7 +282,7 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
deviation = int256((feeDataForInstance[instance].feeAmount * 1000) / marketFee) - 1000;
}
results[i] = deviation;
deviations[i] = deviation;
}
}
}

@ -16,6 +16,7 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
// Local imports
import { NameEncoder } from "./libraries/NameEncoder.sol";
import { TornadoRouter } from "./TornadoRouter.sol";
/**
@ -23,17 +24,18 @@ import { TornadoRouter } from "./TornadoRouter.sol";
* @author AlienTornadosaurusHex
* @notice A contract which enumerates Tornado Cash pool instances, and also stores essential data regarding
* them.
* @dev This is contract will help us layout storage properly for a proxy upgrade for the impl
* @dev This contract will help us layout storage properly for a proxy upgrade for the impl
* InstanceRegistry.
*/
contract InstanceRegistryLegacyStorage {
/* From first contract */
/* From first contract, just so right uint type is chosen */
enum LegacyStatePlaceholder {
DISABLED,
ENABLED
}
/* From first contract */
/* From first contract, just if necessary to be able to properly unpack and determine value storage
addresses and offsets */
struct LegacyInstanceStructPlaceholder {
bool deprecatedIsERC20;
address deprecatedToken;
@ -42,22 +44,25 @@ contract InstanceRegistryLegacyStorage {
uint32 deprecatedProtocolFeePercentage;
}
/* From Initializable.sol of first contract */
/* @dev From Initializable.sol of first contract */
bool private _deprecatedInitialized;
/* From Initializable.sol of first contract */
/* @dev From Initializable.sol of first contract */
bool private _deprecatedInitializing;
/* From first contract */
/* @dev From first contract */
address private _deprecatedRouterAddress;
/* From first contract */
/* @dev From first contract */
mapping(address => LegacyInstanceStructPlaceholder) private _deprecatedInstances;
/* From first contract */
/* @dev From first contract */
ITornadoInstance[] private _deprecatedInstanceIds;
}
/**
* @dev This struct holds barebones information regarding an instance and its data location in storage.
*/
struct InstanceData {
IERC20 token;
uint80 index;
@ -91,7 +96,9 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event NewRouterRegistered(address newRouterAddress);
event RouterRegistered(address newRouterAddress);
event InstanceAdded(address indexed instance, uint80 dataIndex, bool isERC20);
event InstanceRemoved(address indexed instance);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -120,9 +127,12 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
function addInstance(ITornadoInstance _instance) public virtual onlyGovernance {
bool isEnabled = instanceData[_instance].isEnabled;
require(!isEnabled, "InstanceRegistry: can't add the same instance.");
bool isERC20 = false;
IERC20 token;
IERC20 token = IERC20(address(0));
// ETH instances do not know of a `token()` call
try _instance.token() returns (address _tokenAddress) {
@ -132,8 +142,8 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* It's an ETH instance, do nothing */
}
require(!isEnabled, "InstanceRegistry: can't add the same instance.");
// If it is ERC20 then make the router give an approval for the Tornado instance to allow the token
// amount... if it hasn't already done so
if (isERC20) {
uint256 routerAllowanceForInstance = token.allowance(address(router), address(_instance));
@ -142,20 +152,33 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
}
}
// Add it to the enumerable
instances.push(_instance);
instanceData[_instance] = InstanceData({
token: token,
index: uint64(instances.length - 1),
isERC20: token != IERC20(address(0)), // TODO: Is this true?
isEnabled: isEnabled
});
uint64 instanceIndex = uint64(instances.length - 1);
// Set data
instanceData[_instance] =
InstanceData({ token: token, index: instanceIndex, isERC20: isERC20, isEnabled: isEnabled });
// Log
emit InstanceAdded(address(_instance), instanceIndex, isERC20);
}
/**
* @notice Remove an instance, only callable by Governance.
* @dev The modifier is in the internal call.
* @param _instanceIndex The index of the instance to remove.
*/
function removeInstanceByIndex(uint256 _instanceIndex) public virtual {
_removeInstanceByAddress(address(instances[_instanceIndex]));
}
/**
* @notice Remove an instance, only callable by Governance.
* @dev The modifier is in the internal call.
* @param _instanceAddress The adress of the instance to remove.
*/
function removeInstanceByAddress(address _instanceAddress) public virtual {
_removeInstanceByAddress(_instanceAddress);
}
@ -164,6 +187,8 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
ITornadoInstance instance = ITornadoInstance(_instanceAddress);
InstanceData memory data = instanceData[instance];
// Kill the allowance of the router first (arbitrary order)
if (data.isERC20) {
uint256 routerAllowanceForInstance = data.token.allowance(address(router), _instanceAddress);
@ -172,17 +197,34 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
}
}
instances[data.index] = instances[instances.length - 1];
// We need to revert all changes, meaning modify position of last instance and change index in data
uint64 lastInstanceIndex = uint64(instances.length - 1);
ITornadoInstance lastInstance = instances[lastInstanceIndex];
// Remove & swap
instances[data.index] = lastInstance;
instanceData[lastInstance].index = data.index;
instances.pop();
// Delete for removed instance data
delete instanceData[instance];
// Log
emit InstanceRemoved(_instanceAddress);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setTornadoRouter(address _newRouterAddress) external onlyGovernance {
router = TornadoRouter(_newRouterAddress);
emit NewRouterRegistered(_newRouterAddress);
emit RouterRegistered(_newRouterAddress);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -211,6 +253,15 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
}
}
function getInstanceData(ITornadoInstance _instance)
public
view
virtual
returns (InstanceData memory data)
{
return instanceData[_instance];
}
function getInstanceToken(ITornadoInstance _instance) public view virtual returns (IERC20) {
return instanceData[_instance].token;
}
@ -218,4 +269,37 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
function getInstanceIndex(ITornadoInstance _instance) public view virtual returns (uint80) {
return instanceData[_instance].index;
}
function isRegisteredInstance(address _instanceAddress) public view virtual returns (bool) {
return isEnabledInstance(_instanceAddress);
}
function isEnabledInstance(address _instanceAddress) public view virtual returns (bool) {
return instanceData[ITornadoInstance(_instanceAddress)].isEnabled;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ENS GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function getInstanceByENSName(string memory _instanceENSName)
public
view
virtual
returns (ITornadoInstance)
{
(, bytes32 node) = NameEncoder.dnsEncodeName(_instanceENSName);
return ITornadoInstance(resolve(node));
}
function isRegisteredInstanceByENSName(string memory _instanceENSName)
public
view
virtual
returns (bool)
{
return isEnabledInstanceByENSName(_instanceENSName);
}
function isEnabledInstanceByENSName(string memory _instanceENSName) public view virtual returns (bool) {
return isEnabledInstance(address(getInstanceByENSName(_instanceENSName)));
}
}

@ -19,15 +19,24 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
import { RelayerRegistry } from "../v1/RelayerRegistry.sol";
import { InstanceRegistry, InstanceData } from "./InstanceRegistry.sol";
/**
* @title TornadoRouter
* @author AlienTornadosaurusHex
* @notice This contracts is a router for all Tornado Cash deposits and withdrawals
* @dev This is an improved version of the TornadoRouter with a modified design from the original contract.
*/
contract TornadoRouter is Initializable {
using SafeERC20 for IERC20;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The address of the Governance proxy */
address public immutable governanceProxyAddress;
/* @dev The instance registry */
InstanceRegistry public instanceRegistry;
/* @dev The relayer registry */
RelayerRegistry public relayerRegistry;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -50,7 +59,7 @@ contract TornadoRouter is Initializable {
_;
}
function initalize(address _instanceRegistryAddress, address _relayerRegistryAddress)
function initialize(address _instanceRegistryAddress, address _relayerRegistryAddress)
external
onlyGovernance
initializer
@ -112,12 +121,12 @@ contract TornadoRouter is Initializable {
require(_to != address(0), "TORN: can not send to zero address");
if (_token == IERC20(0)) {
// for Ether
// For Ether
uint256 totalBalance = address(this).balance;
uint256 balance = Math.min(totalBalance, _amount);
_to.transfer(balance);
} else {
// any other erc20
// For any other ERC20
uint256 totalBalance = _token.balanceOf(address(this));
uint256 balance = Math.min(totalBalance, _amount);
require(balance > 0, "TORN: trying to send 0 balance");
@ -134,6 +143,14 @@ contract TornadoRouter is Initializable {
_token.safeApprove(_spender, _amount);
}
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
}
function setRelayerRegistry(address _newRelayerRegistryProxyAddress) external onlyGovernance {
relayerRegistry = RelayerRegistry(_newRelayerRegistryProxyAddress);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function version() public pure virtual returns (string memory) {

@ -6,12 +6,160 @@ 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 { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
contract UniswapV3FeeOracle is IFeeOracle {
function getFee() public view virtual override returns (uint160) { }
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { InstanceData } from "./InstanceRegistry.sol";
struct UniswapV3FeeData {
uint24 tornPoolFee;
uint24 tokenPoolFee;
uint32 timePeriod;
}
/**
* @title UniswapV3FeeOracle
* @author AlienTornadosaurusHex
* @notice A TORN fee oracle for any Uniswap V3 token pool.
*/
contract UniswapV3FeeOracle is IFeeOracle {
using SafeMath for uint256;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The Governance Proxy address */
address public immutable governanceProxyAddress;
/* @dev The Uniswap V3 Factory */
IUniswapV3Factory public immutable v3Factory;
/* @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;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event UniswapPoolFeeUpdated(IERC20 token, uint24 newUniswapPoolFee);
event GlobalTornUniswapPoolFeeUpdated(uint24 newUniswapPoolFee);
event GlobalUniswapTimePeriodUpdated(uint32 newUniswapTimePeriod);
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _governanceProxyAddress, address _uniswapV3FactoryAddress) public {
governanceProxyAddress = _governanceProxyAddress;
v3Factory = IUniswapV3Factory(_uniswapV3FactoryAddress);
}
modifier onlyGovernance() {
require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance");
_;
}
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);
}
function getFee(
IERC20 _torn,
ITornadoInstance _instance,
InstanceData memory _data,
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];
// Calc price ratio
uint256 tokenPriceRatio = UniswapV3OracleHelper.getPriceRatioOfTokens(
[address(_torn), address(_data.token)],
[uniFeeData.tornPoolFee, uniFeeData.tokenPoolFee],
uniFeeData.timePeriod
);
// And now all according to legacy calculation
return uint160(
_instance.denomination().mul(UniswapV3OracleHelper.RATIO_DIVIDER).div(tokenPriceRatio).mul(
uint256(_feePercent)
).div(_feePercentDivisor)
);
}
}

@ -3,6 +3,24 @@
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ Imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Tornado imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
// Local imports
import { InstanceData } from "../InstanceRegistry.sol";
interface IFeeOracle {
function getFee() external view returns (uint160);
function getFee(
IERC20 _torn,
ITornadoInstance _instance,
InstanceData memory _data,
uint64 _feePercent,
uint64 _feePercentDivisor
) external view returns (uint160);
}

@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
library BytesUtils {
/*
* @dev Returns the keccak-256 hash of a byte range.
* @param self The byte string to hash.
* @param offset The position to start hashing at.
* @param len The number of bytes to hash.
* @return The hash of the byte range.
*/
function keccak(bytes memory self, uint256 offset, uint256 len) internal pure returns (bytes32 ret) {
require(offset + len <= self.length);
assembly {
ret := keccak256(add(add(self, 32), offset), len)
}
}
/**
* @dev Returns the ENS namehash of a DNS-encoded name.
* @param self The DNS-encoded name to hash.
* @param offset The offset at which to start hashing.
* @return The namehash of the name.
*/
function namehash(bytes memory self, uint256 offset) internal pure returns (bytes32) {
(bytes32 labelhash, uint256 newOffset) = readLabel(self, offset);
if (labelhash == bytes32(0)) {
require(offset == self.length - 1, "namehash: Junk at end of name");
return bytes32(0);
}
return keccak256(abi.encodePacked(namehash(self, newOffset), labelhash));
}
/**
* @dev Returns the keccak-256 hash of a DNS-encoded label, and the offset to the start of the next label.
* @param self The byte string to read a label from.
* @param idx The index to read a label at.
* @return labelhash The hash of the label at the specified index, or 0 if it is the last label.
* @return newIdx The index of the start of the next label.
*/
function readLabel(bytes memory self, uint256 idx)
internal
pure
returns (bytes32 labelhash, uint256 newIdx)
{
require(idx < self.length, "readLabel: Index out of bounds");
uint256 len = uint256(uint8(self[idx]));
if (len > 0) {
labelhash = keccak(self, idx + 1, len);
} else {
labelhash = bytes32(0);
}
newIdx = idx + len + 1;
}
}

@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { BytesUtils } from "./BytesUtils.sol";
library NameEncoder {
using BytesUtils for bytes;
function dnsEncodeName(string memory name) internal pure returns (bytes memory dnsName, bytes32 node) {
uint8 labelLength = 0;
bytes memory bytesName = bytes(name);
uint256 length = bytesName.length;
dnsName = new bytes(length + 2);
node = 0;
if (length == 0) {
dnsName[0] = 0;
return (dnsName, node);
}
for (uint256 i = length - 1; i >= 0; i--) {
if (bytesName[i] == ".") {
dnsName[i + 1] = bytes1(labelLength);
node = keccak256(abi.encodePacked(node, bytesName.keccak(i + 1, labelLength)));
labelLength = 0;
} else {
labelLength += 1;
dnsName[i + 1] = bytesName[i];
}
if (i == 0) {
break;
}
}
node = keccak256(abi.encodePacked(node, bytesName.keccak(0, labelLength)));
dnsName[0] = bytes1(labelLength);
return (dnsName, node);
}
}