Finalize Uniswap Oracle
Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
parent
cd2b889c2f
commit
e8b44ac3f8
@ -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);
|
||||
}
|
||||
|
57
src/v2/libraries/BytesUtils.sol
Normal file
57
src/v2/libraries/BytesUtils.sol
Normal file
@ -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;
|
||||
}
|
||||
}
|
41
src/v2/libraries/NameEncoder.sol
Normal file
41
src/v2/libraries/NameEncoder.sol
Normal file
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user