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 // Local Imports
import { InstanceRegistry } from "../v1/tornado-proxy/InstanceRegistry.sol"; import { InstanceRegistry } from "./InstanceRegistry.sol";
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
@ -31,13 +31,20 @@ struct FeeData {
* FeeOracleManager (formerly FeeManager). * FeeOracleManager (formerly FeeManager).
*/ */
contract FeeManagerLegacyStorage { contract FeeManagerLegacyStorage {
uint24 public deprecatedUniswapTornPoolSwappingFee; /* @dev From first contract */
uint32 public deprecatedUniswapTimePeriod; uint24 private _deprecatedUniswapTornPoolSwappingFee;
/* @dev From first contract */
uint32 private _deprecatedUniswapTimePeriod;
/* @dev From first contract, the only value we keep alive */
uint24 public feeUpdateInterval; uint24 public feeUpdateInterval;
mapping(ITornadoInstance => uint160) public oldFeesForInstance; /* @dev From first contract, only used for initialization to preserve old values in the moment */
mapping(ITornadoInstance => uint256) public oldFeesForInstanceUpdateTime; 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev Divide protocol fee by this to get the percent value */ /* @dev Divide protocol fee by this to get the percent value */
uint64 public constant PROTOCOL_FEE_DIVISOR = 1 ether; uint64 public constant FEE_PERCENT_DIVISOR = 1 ether;
/* @dev The address of the TORN token */
address public immutable tornTokenAddress;
/* @dev The Governance Proxy address */ /* @dev The Governance Proxy address */
address public immutable governanceProxyAddress; address public immutable governanceProxyAddress;
/* @dev The TORN token */
IERC20 public immutable torn;
/* @dev The InstanceRegistry contract */ /* @dev The InstanceRegistry contract */
InstanceRegistry public instanceRegistry; InstanceRegistry public instanceRegistry;
@ -69,14 +76,16 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event FeeUpdateIntervalUpdated(uint24 newLimit);
event FeeUpdated(address indexed instance, uint256 newFee); event FeeUpdated(address indexed instance, uint256 newFee);
event NewOracleSet(address indexed instance, address oracle); event OracleUpdated(address indexed instance, address oracle);
event NewFeeUpdateIntervalSet(uint24 limit); event InstanceFeePercentUpdated(address indexed instance, uint64 newFeePercent);
event InstanceRegistryUpdated(address newAddress);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _tornTokenAddress, address _governanceProxyAddress) public { constructor(address _tornTokenAddress, address _governanceProxyAddress) public {
tornTokenAddress = _tornTokenAddress; torn = IERC20(_tornTokenAddress);
governanceProxyAddress = _governanceProxyAddress; governanceProxyAddress = _governanceProxyAddress;
} }
@ -90,43 +99,46 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
* making sure to not reference old data anywhere. * making sure to not reference old data anywhere.
*/ */
function initialize( function initialize(
address _uniswapPoolFeeOracle, address _uniswapV3FeeOracle,
address _instanceRegistryAddress, address _instanceRegistryAddress,
address[] calldata _instanceAddresses, address[] calldata _instanceAddresses,
uint256[] calldata _feePercents uint256[] calldata _feePercents
) external onlyGovernance initializer { ) external onlyGovernance initializer {
// Get num of existing instances
uint256 numInstances = _instanceAddresses.length; uint256 numInstances = _instanceAddresses.length;
for (uint256 i = 0; i < numInstances; i++) { for (uint256 i = 0; i < numInstances; i++) {
// For each instance
ITornadoInstance instance = ITornadoInstance(_instanceAddresses[i]); ITornadoInstance instance = ITornadoInstance(_instanceAddresses[i]);
// Store it's old data and the percent fees which will Governance will command
feeDataForInstance[instance] = FeeData({ feeDataForInstance[instance] = FeeData({
feeAmount: oldFeesForInstance[instance], feeAmount: _oldFeesForInstance[instance],
feePercent: uint64(_feePercents[i]), 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); instanceRegistry = InstanceRegistry(_instanceRegistryAddress);
} }
function instanceFeeWithUpdate(ITornadoInstance _instance) public virtual returns (uint160) { function instanceFeeWithUpdate(ITornadoInstance _instance) public virtual returns (uint160) {
FeeData memory feeData = feeDataForInstance[_instance]; return updateFee(_instance, true);
if (feeUpdateInterval < now - feeData.lastUpdated) {
feeData.feeAmount = updateFee(_instance);
} }
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) { function updateFees(ITornadoInstance[] memory _instances, bool _respectFeeUpdateInterval)
return updateFees(instanceRegistry.getAllInstanceAddresses());
}
function updateFees(ITornadoInstance[] memory _instances)
public public
virtual virtual
returns (uint160[] memory newFees) returns (uint160[] memory newFees)
@ -136,49 +148,74 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
newFees = new uint160[](numInstances); newFees = new uint160[](numInstances);
for (uint256 i = 0; i < numInstances; i++) { 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 // 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({ feeDataForInstance[_instance] = FeeData({
feeAmount: newFee, feeAmount: feeData.feeAmount,
feePercent: feeDataForInstance[_instance].feePercent, feePercent: feeData.feePercent,
lastUpdated: uint32(now) 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setInstanceRegistry(address _newInstanceRegistryAddress) external onlyGovernance { function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
instanceRegistry = InstanceRegistry(_newInstanceRegistryAddress); instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
emit InstanceRegistryUpdated(_newInstanceRegistryProxyAddress);
} }
function setFeeOracle(address _instanceAddress, address _oracleAddress) external onlyGovernance { function setFeeOracle(address _instanceAddress, address _oracleAddress) external onlyGovernance {
ITornadoInstance instance = ITornadoInstance(_instanceAddress); ITornadoInstance instance = ITornadoInstance(_instanceAddress);
IFeeOracle oracle = IFeeOracle(_oracleAddress); IFeeOracle oracle = IFeeOracle(_oracleAddress);
FeeData memory feeData = feeDataForInstance[instance];
// Reverts if no oracle or does not conform to interface // 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 // Ok, set the oracle
instanceFeeOracles[instance] = oracle; instanceFeeOracles[instance] = oracle;
// Note down updated fee // Note down updated fee
feeDataForInstance[instance] = FeeData({ feeDataForInstance[instance] =
feeAmount: fee, FeeData({ feeAmount: fee, feePercent: feeData.feePercent, lastUpdated: uint32(now) });
feePercent: feeDataForInstance[instance].feePercent,
lastUpdated: uint32(now)
});
emit NewOracleSet(_instanceAddress, _oracleAddress); // Logs
emit OracleUpdated(_instanceAddress, _oracleAddress);
emit FeeUpdated(_instanceAddress, fee); emit FeeUpdated(_instanceAddress, fee);
} }
@ -187,11 +224,12 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
onlyGovernance onlyGovernance
{ {
feeDataForInstance[_instance].feePercent = _newFeePercent; feeDataForInstance[_instance].feePercent = _newFeePercent;
emit InstanceFeePercentUpdated(address(_instance), _newFeePercent);
} }
function setFeeUpdateInterval(uint24 newLimit) external onlyGovernance { function setFeeUpdateInterval(uint24 newLimit) external onlyGovernance {
feeUpdateInterval = newLimit; feeUpdateInterval = newLimit;
emit NewFeeUpdateIntervalSet(newLimit); emit FeeUpdateIntervalUpdated(newLimit);
} }
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -201,7 +239,13 @@ 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,
instance,
instanceRegistry.getInstanceData(instance),
feeDataForInstance[instance].feePercent,
FEE_PERCENT_DIVISOR
);
} }
function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) { function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
@ -212,17 +256,25 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
return feeDataForInstance[instance].lastUpdated; return feeDataForInstance[instance].lastUpdated;
} }
function getFeeDeviations() public view virtual returns (int256[] memory results) { function getFeeDeviations() public view virtual returns (int256[] memory deviations) {
ITornadoInstance[] memory instances = instanceRegistry.getAllInstanceAddresses(); ITornadoInstance[] memory instances = instanceRegistry.getAllInstances();
uint256 numInstances = instances.length; uint256 numInstances = instances.length;
results = new int256[](numInstances); deviations = new int256[](numInstances);
for (uint256 i = 0; i < numInstances; i++) { for (uint256 i = 0; i < numInstances; i++) {
ITornadoInstance instance = instances[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; int256 deviation;
@ -230,7 +282,7 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
deviation = int256((feeDataForInstance[instance].feeAmount * 1000) / marketFee) - 1000; 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 // Local imports
import { NameEncoder } from "./libraries/NameEncoder.sol";
import { TornadoRouter } from "./TornadoRouter.sol"; import { TornadoRouter } from "./TornadoRouter.sol";
/** /**
@ -23,17 +24,18 @@ import { TornadoRouter } from "./TornadoRouter.sol";
* @author AlienTornadosaurusHex * @author AlienTornadosaurusHex
* @notice A contract which enumerates Tornado Cash pool instances, and also stores essential data regarding * @notice A contract which enumerates Tornado Cash pool instances, and also stores essential data regarding
* them. * 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. * InstanceRegistry.
*/ */
contract InstanceRegistryLegacyStorage { contract InstanceRegistryLegacyStorage {
/* From first contract */ /* From first contract, just so right uint type is chosen */
enum LegacyStatePlaceholder { enum LegacyStatePlaceholder {
DISABLED, DISABLED,
ENABLED 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 { struct LegacyInstanceStructPlaceholder {
bool deprecatedIsERC20; bool deprecatedIsERC20;
address deprecatedToken; address deprecatedToken;
@ -42,22 +44,25 @@ contract InstanceRegistryLegacyStorage {
uint32 deprecatedProtocolFeePercentage; uint32 deprecatedProtocolFeePercentage;
} }
/* From Initializable.sol of first contract */ /* @dev From Initializable.sol of first contract */
bool private _deprecatedInitialized; bool private _deprecatedInitialized;
/* From Initializable.sol of first contract */ /* @dev From Initializable.sol of first contract */
bool private _deprecatedInitializing; bool private _deprecatedInitializing;
/* From first contract */ /* @dev From first contract */
address private _deprecatedRouterAddress; address private _deprecatedRouterAddress;
/* From first contract */ /* @dev From first contract */
mapping(address => LegacyInstanceStructPlaceholder) private _deprecatedInstances; mapping(address => LegacyInstanceStructPlaceholder) private _deprecatedInstances;
/* From first contract */ /* @dev From first contract */
ITornadoInstance[] private _deprecatedInstanceIds; ITornadoInstance[] private _deprecatedInstanceIds;
} }
/**
* @dev This struct holds barebones information regarding an instance and its data location in storage.
*/
struct InstanceData { struct InstanceData {
IERC20 token; IERC20 token;
uint80 index; uint80 index;
@ -91,7 +96,9 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event NewRouterRegistered(address newRouterAddress); event RouterRegistered(address newRouterAddress);
event InstanceAdded(address indexed instance, uint80 dataIndex, bool isERC20);
event InstanceRemoved(address indexed instance);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -120,9 +127,12 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
function addInstance(ITornadoInstance _instance) public virtual onlyGovernance { function addInstance(ITornadoInstance _instance) public virtual onlyGovernance {
bool isEnabled = instanceData[_instance].isEnabled; bool isEnabled = instanceData[_instance].isEnabled;
require(!isEnabled, "InstanceRegistry: can't add the same instance.");
bool isERC20 = false; bool isERC20 = false;
IERC20 token; IERC20 token = IERC20(address(0));
// ETH instances do not know of a `token()` call // ETH instances do not know of a `token()` call
try _instance.token() returns (address _tokenAddress) { try _instance.token() returns (address _tokenAddress) {
@ -132,8 +142,8 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* It's an ETH instance, do nothing */ /* 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) { if (isERC20) {
uint256 routerAllowanceForInstance = token.allowance(address(router), address(_instance)); 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); instances.push(_instance);
instanceData[_instance] = InstanceData({ uint64 instanceIndex = uint64(instances.length - 1);
token: token,
index: uint64(instances.length - 1), // Set data
isERC20: token != IERC20(address(0)), // TODO: Is this true? instanceData[_instance] =
isEnabled: isEnabled 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 { function removeInstanceByIndex(uint256 _instanceIndex) public virtual {
_removeInstanceByAddress(address(instances[_instanceIndex])); _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 { function removeInstanceByAddress(address _instanceAddress) public virtual {
_removeInstanceByAddress(_instanceAddress); _removeInstanceByAddress(_instanceAddress);
} }
@ -164,6 +187,8 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
ITornadoInstance instance = ITornadoInstance(_instanceAddress); ITornadoInstance instance = ITornadoInstance(_instanceAddress);
InstanceData memory data = instanceData[instance]; InstanceData memory data = instanceData[instance];
// Kill the allowance of the router first (arbitrary order)
if (data.isERC20) { if (data.isERC20) {
uint256 routerAllowanceForInstance = data.token.allowance(address(router), _instanceAddress); 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(); instances.pop();
// Delete for removed instance data
delete instanceData[instance]; delete instanceData[instance];
// Log
emit InstanceRemoved(_instanceAddress);
} }
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setTornadoRouter(address _newRouterAddress) external onlyGovernance { function setTornadoRouter(address _newRouterAddress) external onlyGovernance {
router = TornadoRouter(_newRouterAddress); router = TornadoRouter(_newRouterAddress);
emit NewRouterRegistered(_newRouterAddress); emit RouterRegistered(_newRouterAddress);
} }
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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) { function getInstanceToken(ITornadoInstance _instance) public view virtual returns (IERC20) {
return instanceData[_instance].token; return instanceData[_instance].token;
} }
@ -218,4 +269,37 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
function getInstanceIndex(ITornadoInstance _instance) public view virtual returns (uint80) { function getInstanceIndex(ITornadoInstance _instance) public view virtual returns (uint80) {
return instanceData[_instance].index; 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 { RelayerRegistry } from "../v1/RelayerRegistry.sol";
import { InstanceRegistry, InstanceData } from "./InstanceRegistry.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 { contract TornadoRouter is Initializable {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The address of the Governance proxy */
address public immutable governanceProxyAddress; address public immutable governanceProxyAddress;
/* @dev The instance registry */
InstanceRegistry public instanceRegistry; InstanceRegistry public instanceRegistry;
/* @dev The relayer registry */
RelayerRegistry public relayerRegistry; RelayerRegistry public relayerRegistry;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -50,7 +59,7 @@ contract TornadoRouter is Initializable {
_; _;
} }
function initalize(address _instanceRegistryAddress, address _relayerRegistryAddress) function initialize(address _instanceRegistryAddress, address _relayerRegistryAddress)
external external
onlyGovernance onlyGovernance
initializer initializer
@ -112,12 +121,12 @@ contract TornadoRouter is Initializable {
require(_to != address(0), "TORN: can not send to zero address"); require(_to != address(0), "TORN: can not send to zero address");
if (_token == IERC20(0)) { if (_token == IERC20(0)) {
// for Ether // For Ether
uint256 totalBalance = address(this).balance; uint256 totalBalance = address(this).balance;
uint256 balance = Math.min(totalBalance, _amount); uint256 balance = Math.min(totalBalance, _amount);
_to.transfer(balance); _to.transfer(balance);
} else { } else {
// any other erc20 // For any other ERC20
uint256 totalBalance = _token.balanceOf(address(this)); uint256 totalBalance = _token.balanceOf(address(this));
uint256 balance = Math.min(totalBalance, _amount); uint256 balance = Math.min(totalBalance, _amount);
require(balance > 0, "TORN: trying to send 0 balance"); require(balance > 0, "TORN: trying to send 0 balance");
@ -134,6 +143,14 @@ contract TornadoRouter is Initializable {
_token.safeApprove(_spender, _amount); _token.safeApprove(_spender, _amount);
} }
function setInstanceRegistry(address _newInstanceRegistryProxyAddress) external onlyGovernance {
instanceRegistry = InstanceRegistry(_newInstanceRegistryProxyAddress);
}
function setRelayerRegistry(address _newRelayerRegistryProxyAddress) external onlyGovernance {
relayerRegistry = RelayerRegistry(_newRelayerRegistryProxyAddress);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function version() public pure virtual returns (string memory) { function version() public pure virtual returns (string memory) {

@ -6,12 +6,160 @@ pragma experimental ABIEncoderV2;
// OZ imports // OZ imports
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; 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 // Local imports
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
contract UniswapV3FeeOracle is IFeeOracle { import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
function getFee() public view virtual override returns (uint160) { }
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 solidity ^0.6.12;
pragma experimental ABIEncoderV2; 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 { 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);
}
}