use uniswap v2 for twap

Signed-off-by: AlienTornadosaurusHex <>
This commit is contained in:
AlienTornadosaurusHex 2023-06-16 01:12:39 +00:00
parent 60be3e5936
commit b25f8d569c
19 changed files with 1452 additions and 210 deletions

@ -1 +1,7 @@
MAINNET_RPC_URL=
# Network & other
MAINNET_RPC_URL=
ETHERSCAN_KEY=
PRIVATE_KEY=
# The one deployed from this repo
TORNADO_ROUTER_ADDRESS=

5
.gitignore vendored

@ -7,4 +7,7 @@ node_modules/
yarn-error.log
yarn.lock
cache_hardhat
artifacts
artifacts
broadcast
.yarn
yarn.lock

@ -2,7 +2,7 @@
# General
src = 'src'
out = 'out'
libs = ["node_modules", "lib"]
libs = [ "lib"]
# Compiler
evm_version = 'shanghai'
@ -18,21 +18,8 @@ rpc_endpoints = { mainnet = "${MAINNET_RPC_URL}" }
# Tests
verbosity = 2
# Remappings
remappings = [
'src/=src/',
'base/=src/base/',
'common/=src/common/',
'ds-test/=lib/ds-test/src/',
'solmate/=lib/solmate/src/',
'forge-std/=lib/forge-std/src/',
'torn-token/=lib/torn-token/',
'tornado-governance/=lib/tornado-governance/',
'tornado-anonymity-mining/=lib/tornado-anonymity-mining/',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
'@uniswap/v3-periphery/=lib/v3-periphery/',
'@uniswap/v3-core/=lib/v3-core/',
]
[etherscan]
mainnet = { key = "${ETHERSCAN_KEY}" }
[fmt]
line_length = 110

67
gas/all.md Normal file

@ -0,0 +1,67 @@
| src/proposals/CRVUSDInstancesProposal.sol:CRVUSDInstancesProposal contract | | | | | |
| -------------------------------------------------------------------------- | --------------- | ------- | ------- | ------- | ------- |
| Deployment Cost | Deployment Size | | | | |
| 743500 | 4033 | | | | |
| Function Name | min | avg | median | max | # calls |
| executeProposal | 1217647 | 1217647 | 1217647 | 1217647 | 1 |
| src/proposals/InfrastructureUpgradeProposal.sol:InfrastructureUpgradeProposal contract | | | | | |
| -------------------------------------------------------------------------------------- | --------------- | ------- | ------- | ------- | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1198424 | 6427 | | | | |
| Function Name | min | avg | median | max | # calls |
| executeProposal | 2630600 | 2630600 | 2630600 | 2630600 | 1 |
| src/v2/CurveFeeOracle.sol:CurveFeeOracle contract | | | | | |
| ------------------------------------------------- | --------------- | ----- | ------ | ------ | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1168992 | 5938 | | | | |
| Function Name | min | avg | median | max | # calls |
| getFee | 1463 | 1463 | 1463 | 1463 | 5 |
| modifyChainedOracleForInstance | 83128 | 93498 | 83128 | 132879 | 5 |
| setUniswapV3FeeOracle | 1713 | 2713 | 2713 | 3713 | 2 |
| src/v2/FeeOracleManager.sol:FeeOracleManager contract | | | | | |
| ----------------------------------------------------- | --------------- | ------ | ------ | ------ | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1420779 | 7415 | | | | |
| Function Name | min | avg | median | max | # calls |
| initialize | 847003 | 847003 | 847003 | 847003 | 1 |
| setFeeOracle | 54524 | 54524 | 54524 | 54524 | 5 |
| setFeePercentForInstance | 2466 | 2466 | 2466 | 2466 | 5 |
| src/v2/InstanceRegistry.sol:InstanceRegistry contract | | | | | |
| ----------------------------------------------------- | --------------- | ------- | ------- | ------- | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1340669 | 6890 | | | | |
| Function Name | min | avg | median | max | # calls |
| addInstance | 84801 | 85801 | 84801 | 89801 | 5 |
| getInstanceData | 1469 | 1469 | 1469 | 1469 | 5 |
| initialize | 1419165 | 1419165 | 1419165 | 1419165 | 1 |
| src/v2/TornadoRouter.sol:TornadoRouter contract | | | | | |
| ----------------------------------------------- | --------------- | ----- | ------ | ----- | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1079192 | 5584 | | | | |
| Function Name | min | avg | median | max | # calls |
| approveTokenForInstance | 27373 | 29130 | 27762 | 33647 | 18 |
| initialize | 45887 | 45887 | 45887 | 45887 | 1 |
| src/v2/UniswapV3FeeOracle.sol:UniswapV3FeeOracle contract | | | | | |
| --------------------------------------------------------- | --------------- | ----- | ------ | ----- | ------- |
| Deployment Cost | Deployment Size | | | | |
| 1357082 | 6972 | | | | |
| Function Name | min | avg | median | max | # calls |
| setGlobalMinObservationCardinality | 1773 | 1773 | 1773 | 1773 | 2 |
| setGlobalTornPoolFee | 45872 | 45872 | 45872 | 45872 | 1 |
| setGlobalTwapIntervalSeconds | 1666 | 1666 | 1666 | 1666 | 1 |
| setPoolFeeForToken | 2688 | 12087 | 5895 | 36795 | 17 |

14
remappings.txt Normal file

@ -0,0 +1,14 @@
src/=src/
base/=src/base/
common/=src/common/
ds-test/=lib/ds-test/src/
solmate/=lib/solmate/src/
forge-std/=lib/forge-std/src/
torn-token/=lib/torn-token/
tornado-governance/=lib/tornado-governance/
tornado-anonymity-mining/=lib/tornado-anonymity-mining/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@uniswap/v2-core/=node_modules/@uniswap/v2-core/contracts/
@uniswap/v2-periphery/=node_modules/@uniswap/v2-periphery/contracts/
@uniswap/v3-periphery/=lib/v3-periphery/
@uniswap/v3-core/=lib/v3-core/

138
script/Swap.sol.x Executable file

@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// STD imports
import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";
// Local imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
import { FeeManager } from "../src/v1/tornado-proxy/FeeManager.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// SPD
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical
/// UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by
/// the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by
/// the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata data) external;
}
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params)
external
payable
returns (uint256 amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in
/// calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params)
external
payable
returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path
/// (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in
/// calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
contract DeployScript is Script {
IERC20 public constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
function run() external {
uint256 key = vm.envUint("PRIVATE_KEY");
ISwapRouter router = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
uint256 bal = TORN.balanceOf(0x895CB4C75e9be8ABA117bF4E044416C855018ea0);
vm.startBroadcast(key);
TORN.approve(address(router), bal);
ISwapRouter.ExactInputParams memory params = ISwapRouter.ExactInputParams({
path: abi.encodePacked(TORN, uint24(10_000), WETH),
recipient: 0x895CB4C75e9be8ABA117bF4E044416C855018ea0,
deadline: block.timestamp + 6 minutes,
amountIn: bal,
amountOutMinimum: 0
});
router.exactInput(params);
vm.stopBroadcast();
}
}

28
script/TornadoRouter.s.sol.x Executable file

@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// STD imports
import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";
// Local imports
import { TornadoAddresses } from "../src/common/TornadoAddresses.sol";
import { TornadoRouter } from "../src/v2/TornadoRouter.sol";
contract DeployScript is TornadoAddresses, Script {
function run() external {
uint256 key = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(key);
TornadoRouter router = new TornadoRouter(getGovernanceProxyAddress());
vm.stopBroadcast();
}
}

38
script/Update.sol.x Executable file

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// STD imports
import { Script } from "forge-std/Script.sol";
import { console2 } from "forge-std/console2.sol";
// Local imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
import { FeeManager } from "../src/v1/tornado-proxy/FeeManager.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract DeployScript is Script {
IERC20 public constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
function run() external {
uint256 key = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(key);
FeeManager fees = FeeManager(0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7);
fees.updateFee(ITornadoInstance(0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF));
fees.updateFee(ITornadoInstance(0xA160cdAB225685dA1d56aa342Ad8841c3b53f291));
fees.updateFee(ITornadoInstance(0x07687e702b410Fa43f4cB4Af7FA097918ffD2730));
fees.updateFee(ITornadoInstance(0x23773E65ed146A459791799d01336DB287f25334));
vm.stopBroadcast();
}
}

@ -19,7 +19,7 @@ import { InstanceRegistry } from "../v2/InstanceRegistry.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "../v2/CurveFeeOracle.sol";
import { UniswapV3FeeOracle } from "../v2/UniswapV3FeeOracle.sol";
import { UniswapFeeOracle } from "../v2/UniswapFeeOracle.sol";
import { TornadoRouter } from "../v2/TornadoRouter.sol";
@ -47,7 +47,7 @@ contract CRVUSDInstancesProposal {
/* @dev This is the Uniswap V3 Oracle which we will use for all of our traditional instances, but it will
also help the Curve instances, the former must have been deployed with the address of this */
UniswapV3FeeOracle public immutable v3FeeOracle;
UniswapFeeOracle public immutable uniswapFeeOracle;
/* @dev This is the CurveFeeOracle contract which will be deployed beforehand and which will be able to
use multiple Curve pools as oracles, at once. */
@ -55,9 +55,9 @@ contract CRVUSDInstancesProposal {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _deployedCurveFeeOracleAddress, address _deployedUniswapV3FeeOracleAddress) public {
constructor(address _deployedCurveFeeOracleAddress, address _deployedUniswapFeeOracleAddress) public {
curveFeeOracle = CurveFeeOracle(_deployedCurveFeeOracleAddress);
v3FeeOracle = UniswapV3FeeOracle(_deployedUniswapV3FeeOracleAddress);
uniswapFeeOracle = UniswapFeeOracle(_deployedUniswapFeeOracleAddress);
}
/**
@ -74,7 +74,7 @@ contract CRVUSDInstancesProposal {
// Ok, first add the Uniswap V3 Oracle to the contract
curveFeeOracle.setUniswapV3FeeOracle(v3FeeOracle);
curveFeeOracle.setUniswapFeeOracle(uniswapFeeOracle);
// Then, add necessary oracles for the CRVUSD price, to the CurveFeeOracle

@ -18,7 +18,7 @@ import { FeeOracleManager } from "../v2/FeeOracleManager.sol";
import { InstanceRegistry } from "../v2/InstanceRegistry.sol";
import { UniswapV3FeeOracle, UniswapV3OracleHelper } from "../v2/UniswapV3FeeOracle.sol";
import { UniswapFeeOracle, UniswapV3OracleHelper } from "../v2/UniswapFeeOracle.sol";
import { TornadoRouter } from "../v2/TornadoRouter.sol";
@ -42,7 +42,7 @@ contract InfrastructureUpgradeProposal {
/* @dev This is the Uniswap V3 Oracle which we will use for all of our traditional instances, but it will
also help the CurveFeeOracle, the former must have been deployed witht the address of this */
address public immutable deployedUniswapV3FeeOracleAddress;
address public immutable deployedUniswapFeeOracleAddress;
/* @dev The implementation address of the FeeManager upgrade contract */
address public immutable deployedFeeOracleManagerImplementationAddress;
@ -56,12 +56,12 @@ contract InfrastructureUpgradeProposal {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(
address _deployedUniswapV3FeeOracleAddress,
address _deployedUniswapFeeOracleAddress,
address _deployedFeeOracleManagerImplementationAddress,
address _deployedInstanceRegistryImplementationAddress,
address _deployedTornadoRouterAddress
) public {
deployedUniswapV3FeeOracleAddress = _deployedUniswapV3FeeOracleAddress;
deployedUniswapFeeOracleAddress = _deployedUniswapFeeOracleAddress;
deployedFeeOracleManagerImplementationAddress = _deployedFeeOracleManagerImplementationAddress;
deployedInstanceRegistryImplementationAddress = _deployedInstanceRegistryImplementationAddress;
deployedTornadoRouterAddress = _deployedTornadoRouterAddress;
@ -91,7 +91,7 @@ contract InfrastructureUpgradeProposal {
// out from the on-chain instance registry.
FeeOracleManager(feeManagerProxyAddress).initialize(
deployedUniswapV3FeeOracleAddress,
deployedUniswapFeeOracleAddress,
instanceRegistryProxyAddress,
_getAllInstances(),
_getAllInstanceFeePercents()
@ -117,13 +117,11 @@ contract InfrastructureUpgradeProposal {
// legacy cDAI pools which are in a kind of stupid liquidity condition, in any case there is a small
// amount of funds in there so let's add them too
UniswapV3FeeOracle v3FeeOracle = UniswapV3FeeOracle(deployedUniswapV3FeeOracleAddress);
UniswapFeeOracle uniswapFeeOracle = UniswapFeeOracle(deployedUniswapFeeOracleAddress);
v3FeeOracle.setGlobalTornPoolFee(10_000, true); // Legacy value, still makes sense
uniswapFeeOracle.setTwapIntervalSeconds(5400); // Legacy value, still makes sense
v3FeeOracle.setGlobalTwapIntervalSeconds(5400); // Legacy value, still makes sense
v3FeeOracle.setGlobalMinObservationCardinality(1); // Set it to minimum so cDAI passes
uniswapFeeOracle.setMinObservationCardinality(1); // Set it to minimum so cDAI passes
// Each of the instances are going to require a Uniswap Pool Fee to be set such that we actually know
// what pools to lookup when querying for Oracle data. This data has also been read out from the
@ -131,12 +129,12 @@ contract InfrastructureUpgradeProposal {
// respectively "default" pool fees. This is not to be confused with pool protocol fees, these have
// been recorded in the (first) legacy initialization above
_setAllInstancePoolFeesInOracle(v3FeeOracle);
_setAllInstancePoolFeesInOracle(uniswapFeeOracle);
v3FeeOracle.setGlobalMinObservationCardinality(10); // Now set the cardinality to a reasonable value
uniswapFeeOracle.setMinObservationCardinality(10); // Now set the cardinality to a reasonable value
}
function _setAllInstancePoolFeesInOracle(UniswapV3FeeOracle _v3FeeOracle) internal {
function _setAllInstancePoolFeesInOracle(UniswapFeeOracle _uniswapFeeOracle) internal {
ITornadoInstance[] memory instances = _getAllInstances();
IERC20 weth = IERC20(UniswapV3OracleHelper.WETH);
@ -146,31 +144,31 @@ contract InfrastructureUpgradeProposal {
IERC20 wbtc = IERC20(instances[14].token());
/* ETH instances */
_v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_v3FeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_uniswapFeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_uniswapFeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_uniswapFeeOracle.setPoolFeeForToken(weth, uint24(0x000));
_uniswapFeeOracle.setPoolFeeForToken(weth, uint24(0x000));
/* DAI instances */
_v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(dai, uint24(0xbb8));
/* cDAI instances */
_v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(cdai, uint24(0xbb8));
/* USDT instances */
_v3FeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4));
_v3FeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4));
_uniswapFeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4));
_uniswapFeeOracle.setPoolFeeForToken(usdt, uint24(0x1f4));
/* WBTC instances */
_v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
_v3FeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
_uniswapFeeOracle.setPoolFeeForToken(wbtc, uint24(0xbb8));
}
function _getAllInstances() internal pure returns (ITornadoInstance[] memory _addresses) {

@ -18,7 +18,7 @@ import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { UniswapV3FeeOracle } from "./UniswapV3FeeOracle.sol";
import { UniswapFeeOracle } from "./UniswapFeeOracle.sol";
import { InstanceData } from "./InstanceRegistry.sol";
@ -170,28 +170,38 @@ contract CurveFeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The Governance Proxy address */
/**
* @notice The Governance Proxy address
*/
address public immutable governanceProxyAddress;
/* @dev For each instance, a set of data which translates to a set of chained price oracle calls, we call
this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and calc the
price */
/**
* @notice For each instance, a set of data which translates to a set of chained price oracle calls, we
* call this data "chainedPriceOracles" because it encodes all data necessary to execute the chain and
* calc the price
*/
mapping(ITornadoInstance => bytes) public chainedPriceOracles;
/* @dev When setting, store the names as a historical record, key is keccak256(bytes) */
/**
* @notice When setting, store the names as a historical record, key is keccak256(bytes)
*/
mapping(bytes32 => string) public chainedPriceOracleNames;
/* @dev Our Uniswap V3 Fee Oracle, we will need it some time for TORN/ETH */
UniswapV3FeeOracle public v3FeeOracle;
/**
* @notice Our Uniswap V3 Fee Oracle, we will need it some time for TORN/ETH
*/
UniswapFeeOracle public uniswapFeeOracle;
/* @dev We will not need the Uniswap V3 Oracle forever though, TORN/ETH pools are possible on Curve too */
/**
* @notice We will not need the Uniswap V3 Oracle forever though, TORN/ETH pools are possible on Curve too
*/
bool public tornOracleIsUniswapV3;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event TornOracleIsCurve();
event TornOracleIsUniswapV3();
event UniswapV3FeeOracleUpdated(address _newUniswapV3FeeOracleAddress);
event UniswapFeeOracleUpdated(address _newUniswapFeeOracleAddress);
event ChainedOracleUpdated(address indexed instance, bytes32 newOracleHash, string newName);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -202,18 +212,20 @@ contract CurveFeeOracle is IFeeOracle {
}
modifier onlyGovernance() {
require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance");
require(msg.sender == governanceProxyAddress, "UniswapFeeOracle: onlyGovernance");
_;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function update() public virtual override { }
function getFee(
IERC20 _torn,
IERC20,
ITornadoInstance _instance,
InstanceData memory,
uint64 _feePercent,
uint64 _feePercentDivisor
uint32 _feePercent,
uint32 _feePercentDivisor
) public view virtual override returns (uint160) {
// If fee is 0 return
if (_feePercent == 0) return 0;
@ -226,7 +238,7 @@ contract CurveFeeOracle is IFeeOracle {
// in TORN
if (tornOracleIsUniswapV3) {
price = price * UniswapV3OracleHelper.RATIO_DIVIDER
/ v3FeeOracle.getPriceRatioOfTokens(_torn, IERC20(UniswapV3OracleHelper.WETH));
/ uniswapFeeOracle.getTokenPerTORN(IERC20(UniswapV3OracleHelper.WETH));
}
// The price is now either prepared by the chained oracles or by Uniswap V3, in any case we do the
@ -284,9 +296,9 @@ contract CurveFeeOracle is IFeeOracle {
emit ChainedOracleUpdated(address(_instance), oracleHash, _name);
}
function setUniswapV3FeeOracle(UniswapV3FeeOracle _uniswapV3FeeOracle) public virtual onlyGovernance {
v3FeeOracle = _uniswapV3FeeOracle;
emit UniswapV3FeeOracleUpdated(address(_uniswapV3FeeOracle));
function setUniswapFeeOracle(UniswapFeeOracle _uniswapFeeOracle) public virtual onlyGovernance {
uniswapFeeOracle = _uniswapFeeOracle;
emit UniswapFeeOracleUpdated(address(_uniswapFeeOracle));
}
function setTornOracleIsUniswapV3(bool _is) public virtual onlyGovernance {

@ -25,19 +25,29 @@ import { IFeeOracle } from "./interfaces/IFeeOracle.sol";
* FeeOracleManager (formerly FeeManager).
*/
contract FeeManagerLegacyStorage {
/* @dev From first contract */
/**
* @dev From first contract
*/
uint24 private _deprecatedUniswapTornPoolSwappingFee;
/* @dev From first contract */
/**
* @dev From first contract
*/
uint32 private _deprecatedUniswapTimePeriod;
/* @dev From first contract, the only value we keep alive */
uint24 public feeUpdateInterval;
/**
* @dev From first contract, the only value we keep alive
*/
uint24 internal _oldFeeUpdateInterval;
/* @dev From first contract, only used for initialization to preserve old values */
/**
* @dev From first contract, only used for initialization to preserve old values
*/
mapping(ITornadoInstance => uint160) internal _oldFeesForInstance;
/* @dev From first contract, only used for initialization to preserve old values */
/**
* @dev From first contract, only used for initialization to preserve old values
*/
mapping(ITornadoInstance => uint256) internal _oldFeesForInstanceUpdateTime;
}
@ -45,9 +55,10 @@ contract FeeManagerLegacyStorage {
* @notice Fee data which is valid and also influences fee updating across all oracles.
*/
struct FeeData {
uint160 feeAmount;
uint64 feePercent;
uint32 lastUpdated;
uint160 amount;
uint32 percent;
uint32 updateInterval;
uint32 lastUpdateTime;
}
/**
@ -59,30 +70,42 @@ struct FeeData {
contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev Divide protocol fee by this to get the percent value */
uint64 public constant FEE_PERCENT_DIVISOR = 1 ether;
/**
* @notice Divide protocol fee by this to get the percent value
*/
uint32 public constant FEE_PERCENT_DIVISOR = 10_000;
/* @dev The Governance Proxy address */
/**
* @notice The Governance Proxy address
*/
address public immutable governanceProxyAddress;
/* @dev The TORN token */
/**
* @notice The TORN token
*/
IERC20 public immutable torn;
/* @dev The InstanceRegistry contract */
/**
* @notice The InstanceRegistry contract
*/
InstanceRegistry public instanceRegistry;
/* @dev Each instance has a dedicated fee oracle, these only compute the values */
/**
* @notice Each instance has a dedicated fee oracle, these only compute the values
*/
mapping(ITornadoInstance => IFeeOracle) public instanceFeeOracles;
/* @dev The data for each instance will be stored in this contract */
mapping(ITornadoInstance => FeeData) public feeDataForInstance;
/**
* @notice The data for each instance will be stored in this contract
*/
mapping(ITornadoInstance => FeeData) public feesByInstance;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event FeeUpdateIntervalUpdated(uint24 newLimit);
event FeeUpdated(address indexed instance, uint256 newFee);
event OracleUpdated(address indexed instance, address oracle);
event InstanceFeePercentUpdated(address indexed instance, uint64 newFeePercent);
event InstanceFeePercentUpdated(address indexed instance, uint32 newFeePercent);
event InstanceRegistryUpdated(address newAddress);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -102,10 +125,10 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
* making sure to not reference old data anywhere.
*/
function initialize(
address _uniswapV3FeeOracle,
address _uniswapFeeOracle,
address _instanceRegistryAddress,
ITornadoInstance[] memory _instances,
uint256[] memory _feePercents
uint256[] memory _percents
) external onlyGovernance initializer {
// Get num of existing instances
uint256 numInstances = _instances.length;
@ -115,14 +138,15 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
ITornadoInstance instance = _instances[i];
// Store it's old data and the percent fees which Governance will command
feeDataForInstance[instance] = FeeData({
feeAmount: _oldFeesForInstance[instance],
feePercent: uint64(_feePercents[i]),
lastUpdated: uint32(_oldFeesForInstanceUpdateTime[instance])
feesByInstance[instance] = FeeData({
amount: _oldFeesForInstance[instance],
percent: uint32(_percents[i]),
updateInterval: _oldFeeUpdateInterval,
lastUpdateTime: uint32(_oldFeesForInstanceUpdateTime[instance])
});
// All old pools use the uniswap fee oracle
instanceFeeOracles[instance] = IFeeOracle(_uniswapV3FeeOracle);
instanceFeeOracles[instance] = IFeeOracle(_uniswapFeeOracle);
}
// Finally also store the instance registry
@ -160,42 +184,45 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
virtual
returns (uint160)
{
// First get fee data
FeeData memory feeData = feeDataForInstance[_instance];
// Get fee data & oracle
FeeData memory fee = feesByInstance[_instance];
IFeeOracle oracle = instanceFeeOracles[_instance];
// Check whether the instance is registered
require(address(oracle) != address(0), "FeeOracleManager: instance has no oracle");
// Now update if we do not respect the interval or we respect it and are in the interval
if (!_respectFeeUpdateInterval || (feeUpdateInterval < -feeData.lastUpdated + now)) {
IFeeOracle oracle = instanceFeeOracles[_instance];
// Check whether the instance is registered
require(address(oracle) != address(0), "FeeOracleManager: instance has no oracle");
if (!_respectFeeUpdateInterval || (fee.updateInterval < now - fee.lastUpdateTime)) {
// Allow oracle to gate for fee manager and implement own logic
oracle.update();
// There must a be a fee set otherwise it's just 0
if (feeData.feePercent != 0) {
feeData.feeAmount = oracle.getFee(
if (fee.percent != 0) {
fee.amount = oracle.getFee(
torn,
_instance,
instanceRegistry.getInstanceData(_instance),
feeData.feePercent,
fee.percent,
FEE_PERCENT_DIVISOR
);
} else {
feeData.feeAmount = 0;
fee.amount = 0;
}
// Store
feeDataForInstance[_instance] = FeeData({
feeAmount: feeData.feeAmount,
feePercent: feeData.feePercent,
lastUpdated: uint32(now)
feesByInstance[_instance] = FeeData({
amount: fee.amount,
percent: fee.percent,
updateInterval: fee.updateInterval,
lastUpdateTime: uint32(now)
});
// Log
emit FeeUpdated(address(_instance), feeData.feeAmount);
emit FeeUpdated(address(_instance), fee.amount);
}
// In any case return a value
return feeData.feeAmount;
return fee.amount;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -212,45 +239,50 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
// Nominally fee percent should be set first for an instance, but we cannot be sure
// whether fee percent 0 is intentional, so we don't check
FeeData memory feeData = feeDataForInstance[instance];
// Fee which may be recalculated
uint160 fee;
FeeData memory fee = feesByInstance[instance];
// An address(0) oracle means we're removing an oracle for an instance
if (_oracleAddress != address(0)) {
// Reverts if oracle does not conform to interface
fee = oracle.getFee(
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeData.feePercent,
FEE_PERCENT_DIVISOR
// Reverts if oracle doesn't implement
oracle.update();
// Reverts if oracle doesn't implement
fee.amount = oracle.getFee(
torn, instance, instanceRegistry.getInstanceData(instance), fee.percent, FEE_PERCENT_DIVISOR
);
// Note down updated fee
feeDataForInstance[instance] =
FeeData({ feeAmount: fee, feePercent: feeData.feePercent, lastUpdated: uint32(now) });
feesByInstance[instance] = FeeData({
amount: fee.amount,
percent: fee.percent,
updateInterval: fee.updateInterval,
lastUpdateTime: uint32(now)
});
// Log fee update
emit FeeUpdated(_instanceAddress, fee.amount);
}
// Ok, set the oracle
instanceFeeOracles[instance] = oracle;
// Logs
// Log oracle update
emit OracleUpdated(_instanceAddress, _oracleAddress);
emit FeeUpdated(_instanceAddress, fee);
}
function setFeePercentForInstance(ITornadoInstance _instance, uint64 _newFeePercent)
function setFeePercentForInstance(ITornadoInstance _instance, uint32 _newFeePercent)
external
onlyGovernance
{
feeDataForInstance[_instance].feePercent = _newFeePercent;
feesByInstance[_instance].percent = _newFeePercent;
emit InstanceFeePercentUpdated(address(_instance), _newFeePercent);
}
function setFeeUpdateInterval(uint24 newLimit) external onlyGovernance {
feeUpdateInterval = newLimit;
function setFeeUpdateIntervalForInstance(ITornadoInstance _instance, uint24 newLimit)
external
onlyGovernance
{
feesByInstance[_instance].updateInterval = newLimit;
emit FeeUpdateIntervalUpdated(newLimit);
}
@ -265,21 +297,30 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeDataForInstance[instance].feePercent,
feesByInstance[instance].percent,
FEE_PERCENT_DIVISOR
);
}
function getLastFeeForInstance(ITornadoInstance instance) public view virtual returns (uint160) {
return feeDataForInstance[instance].feeAmount;
return feesByInstance[instance].amount;
}
function getLastUpdatedTimeForInstance(ITornadoInstance instance) public view virtual returns (uint32) {
return feeDataForInstance[instance].lastUpdated;
return feesByInstance[instance].lastUpdateTime;
}
function getFeePercentForInstance(ITornadoInstance instance) public view virtual returns (uint64) {
return feeDataForInstance[instance].feePercent;
function getFeePercentForInstance(ITornadoInstance instance) public view virtual returns (uint32) {
return feesByInstance[instance].percent;
}
function getFeeUpdateIntervalForInstance(ITornadoInstance instance)
public
view
virtual
returns (uint32)
{
return feesByInstance[instance].updateInterval;
}
function getAllFeeDeviations() public view virtual returns (int256[] memory) {
@ -299,23 +340,15 @@ contract FeeOracleManager is FeeManagerLegacyStorage, Initializable {
for (uint256 i = 0; i < numInstances; i++) {
ITornadoInstance instance = _instances[i];
FeeData memory feeData = feeDataForInstance[instance];
FeeData memory fee = feesByInstance[instance];
uint256 marketFee = instanceFeeOracles[instance].getFee(
torn,
instance,
instanceRegistry.getInstanceData(instance),
feeData.feePercent,
FEE_PERCENT_DIVISOR
torn, instance, instanceRegistry.getInstanceData(instance), fee.percent, FEE_PERCENT_DIVISOR
);
int256 deviation;
if (marketFee != 0) {
deviation = int256((feeData.feeAmount * 1000) / marketFee) - 1000;
deviations[i] = int256((fee.amount * 1000) / marketFee) - 1000;
}
deviations[i] = deviation;
}
}
}

@ -44,19 +44,29 @@ contract InstanceRegistryLegacyStorage {
uint32 deprecatedProtocolFeePercentage;
}
/* @dev From Initializable.sol of first contract */
/**
* @dev From Initializable.sol of first contract
*/
bool private _deprecatedInitialized;
/* @dev From Initializable.sol of first contract */
/**
* @dev From Initializable.sol of first contract
*/
bool private _deprecatedInitializing;
/* @dev From first contract */
/**
* @dev From first contract
*/
address private _deprecatedRouterAddress;
/* @dev From first contract */
/**
* @dev From first contract
*/
mapping(address => LegacyInstanceStructPlaceholder) private _deprecatedInstances;
/* @dev From first contract */
/**
* @dev From first contract
*/
ITornadoInstance[] private _deprecatedInstanceIds;
}
@ -82,16 +92,24 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The address of the Governance proxy */
/**
* @notice The address of the Governance proxy
*/
address public immutable governanceProxyAddress;
/* @dev Essential data regarding instances, see struct above */
/**
* @notice Essential data regarding instances, see struct above
*/
mapping(ITornadoInstance => InstanceData) public instanceData;
/* @dev All instances enumerable */
/**
* @notice All instances enumerable
*/
ITornadoInstance[] public instances;
/* @dev The router which processes txs which we must command to approve tokens */
/**
* @notice The router which processes txs which we must command to approve tokens
*/
TornadoRouter public router;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -255,10 +273,10 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
virtual
returns (ITornadoInstance[] memory allInstances)
{
allInstances = new ITornadoInstance[](-_inclusiveStartIndex + 1 + _inclusiveEndIndex);
allInstances = new ITornadoInstance[](1 + _inclusiveEndIndex - _inclusiveStartIndex);
for (uint256 i = _inclusiveStartIndex; i < _inclusiveEndIndex + 1; i++) {
allInstances[i] = instances[i];
allInstances[i - _inclusiveStartIndex] = instances[i];
}
}
@ -272,10 +290,10 @@ contract InstanceRegistry is InstanceRegistryLegacyStorage, EnsResolve, Initiali
virtual
returns (InstanceData[] memory data)
{
data = new InstanceData[](-_inclusiveStartIndex + 1 + _inclusiveEndIndex);
data = new InstanceData[](1 + _inclusiveEndIndex - _inclusiveStartIndex);
for (uint256 i = _inclusiveStartIndex; i < _inclusiveEndIndex + 1; i++) {
data[i] = instanceData[instances[i]];
data[i - _inclusiveStartIndex] = instanceData[instances[i]];
}
}

@ -30,13 +30,19 @@ contract TornadoRouter is Initializable {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The address of the Governance proxy */
/**
* @notice The address of the Governance proxy
*/
address public immutable governanceProxyAddress;
/* @dev The instance registry */
/**
* @notice The instance registry
*/
InstanceRegistry public instanceRegistry;
/* @dev The relayer registry */
/**
* @notice The relayer registry
*/
RelayerRegistry public relayerRegistry;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -72,6 +78,12 @@ contract TornadoRouter is Initializable {
relayerRegistry = RelayerRegistry(_relayerRegistryAddress);
}
/**
* @notice Function to deposit into a Tornado instance.
* @param _tornado The instance to deposit into (address).
* @param _commitment The commitment which will be added to the Merkle Tree.
* @param _encryptedNote An encrypted note tied to the commitment which may be logged.
*/
function deposit(ITornadoInstance _tornado, bytes32 _commitment, bytes calldata _encryptedNote)
public
payable
@ -79,6 +91,7 @@ contract TornadoRouter is Initializable {
{
(IERC20 token,, bool isERC20, bool isEnabled) = instanceRegistry.instanceData(_tornado);
// Better than having it revert at safeTransferFrom
require(isEnabled, "TornadoRouter: instance not enabled");
if (isERC20) {
@ -90,6 +103,28 @@ contract TornadoRouter is Initializable {
emit EncryptedNote(msg.sender, _encryptedNote);
}
/**
* @notice Withdraw from a Tornado Instance. NOTE: Until infrastructure, which will include a
* smart-contract relayer wallet, isn't deployed, this function will be the only gateway for withdrawing
* from the _new_ instances. But fear not! Each instance contains a permissionless function by the name of
* `checkInfrastructureIsDead()`, which can be used in the case that the infra is deactivated or
* malfunctions (this contract can't malfunction to block withdrawals). It makes the instance work just
* like a regular one again! The intention is to call the formerly mentioned function when the new
* infrastructure is deployed, and until then, use the simple fix in the new instances to route
* withdrawals
* through the registry. ALSO NOTE, that manual depositors and withdrawers will still be able to
* permissionlessly use the instances, they anyways do not rely on the `relayer` address field, and as
* such they will be able to claim their deposits no matter whether the check is effective or not!
* @param _tornado The Tornado instance to withdraw from.
* @param _proof Bytes proof data.
* @param _root A current or historical bytes32 root of the Merkle Tree within the proofs context.
* @param _nullifierHash The bytes32 nullifierHash for the deposit.
* @param _recipient The address of recipient for withdrawn funds.
* @param _relayer The address of the relayer which will be making the withdrawal.
* @param _fee The fee in bips to pay the relayer.
* @param _refund If swapping into ETH on the other side, use this to specify how much should be paid for
* it.
*/
function withdraw(
ITornadoInstance _tornado,
bytes calldata _proof,
@ -100,11 +135,19 @@ contract TornadoRouter is Initializable {
uint256 _fee,
uint256 _refund
) public payable virtual {
// If an instance is turned off, we still want withdrawals to pass
// so instead of requiring `isEnabled`, we will only use it to
// decide whether relayers should burn or not. This is beneficial
// for every party involved!
(,,, bool isEnabled) = instanceRegistry.instanceData(_tornado);
require(isEnabled, "TornadoRouter: instance not enabled");
relayerRegistry.burn(msg.sender, _relayer, _tornado);
// Remove the require and instead just skip if there is no
// relayer being used or the instance is not enabled. Note that,
// even if the registry gets hijacked, you can just do
// manual withdrawals (using the SDK, for example).
if (_relayer != address(0) && isEnabled) {
relayerRegistry.burn(msg.sender, _relayer, _tornado);
}
_tornado.withdraw{ value: msg.value }(
_proof, _root, _nullifierHash, _recipient, _relayer, _fee, _refund

394
src/v2/UniswapFeeOracle.sol Normal file

@ -0,0 +1,394 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
// OZ imports
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// 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 { IFeeOracle } from "./interfaces/IFeeOracle.sol";
import { UniswapV3OracleHelper } from "./libraries/UniswapV3OracleHelper.sol";
import { InstanceData } from "./InstanceRegistry.sol";
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UNISWAP INLINED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
interface IUniswapV2Pair {
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
function name() external pure returns (string memory);
function symbol() external pure returns (string memory);
function decimals() external pure returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address owner) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transfer(address to, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function DOMAIN_SEPARATOR() external view returns (bytes32);
function PERMIT_TYPEHASH() external pure returns (bytes32);
function nonces(address owner) external view returns (uint256);
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
event Mint(address indexed sender, uint256 amount0, uint256 amount1);
event Burn(address indexed sender, uint256 amount0, uint256 amount1, address indexed to);
event Swap(
address indexed sender,
uint256 amount0In,
uint256 amount1In,
uint256 amount0Out,
uint256 amount1Out,
address indexed to
);
event Sync(uint112 reserve0, uint112 reserve1);
function MINIMUM_LIQUIDITY() external pure returns (uint256);
function factory() external view returns (address);
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves()
external
view
returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function price0CumulativeLast() external view returns (uint256);
function price1CumulativeLast() external view returns (uint256);
function kLast() external view returns (uint256);
function mint(address to) external returns (uint256 liquidity);
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
function skim(address to) external;
function sync() external;
function initialize(address, address) external;
}
library FixedPoint {
// range: [0, 2**112 - 1]
// resolution: 1 / 2**112
struct uq112x112 {
uint224 _x;
}
// range: [0, 2**144 - 1]
// resolution: 1 / 2**112
struct uq144x112 {
uint256 _x;
}
uint8 private constant RESOLUTION = 112;
// encode a uint112 as a UQ112x112
function encode(uint112 x) internal pure returns (uq112x112 memory) {
return uq112x112(uint224(x) << RESOLUTION);
}
// encodes a uint144 as a UQ144x112
function encode144(uint144 x) internal pure returns (uq144x112 memory) {
return uq144x112(uint256(x) << RESOLUTION);
}
// divide a UQ112x112 by a uint112, returning a UQ112x112
function div(uq112x112 memory self, uint112 x) internal pure returns (uq112x112 memory) {
require(x != 0, "FixedPoint: DIV_BY_ZERO");
return uq112x112(self._x / uint224(x));
}
// multiply a UQ112x112 by a uint, returning a UQ144x112
// reverts on overflow
function mul(uq112x112 memory self, uint256 y) internal pure returns (uq144x112 memory) {
uint256 z;
require(
y == 0 || (z = uint256(self._x) * y) / y == uint256(self._x),
"FixedPoint: MULTIPLICATION_OVERFLOW"
);
return uq144x112(z);
}
// returns a UQ112x112 which represents the ratio of the numerator to the denominator
// equivalent to encode(numerator).div(denominator)
function fraction(uint112 numerator, uint112 denominator) internal pure returns (uq112x112 memory) {
require(denominator > 0, "FixedPoint: DIV_BY_ZERO");
return uq112x112((uint224(numerator) << RESOLUTION) / denominator);
}
// decode a UQ112x112 into a uint112 by truncating after the radix point
function decode(uq112x112 memory self) internal pure returns (uint112) {
return uint112(self._x >> RESOLUTION);
}
// decode a UQ144x112 into a uint144 by truncating after the radix point
function decode144(uq144x112 memory self) internal pure returns (uint144) {
return uint144(self._x >> RESOLUTION);
}
}
library UniswapV2OracleLibrary {
using FixedPoint for *;
// helper function that returns the current block timestamp within the range of uint32, i.e. [0, 2**32 -
// 1]
function currentBlockTimestamp() internal view returns (uint32) {
return uint32(block.timestamp % 2 ** 32);
}
// produces the cumulative price using counterfactuals to save gas and avoid a call to sync.
function currentCumulativePrices(address pair)
internal
view
returns (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp)
{
blockTimestamp = currentBlockTimestamp();
price0Cumulative = IUniswapV2Pair(pair).price0CumulativeLast();
price1Cumulative = IUniswapV2Pair(pair).price1CumulativeLast();
// if time has elapsed since the last update on the pair, mock the accumulated price values
(uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast) = IUniswapV2Pair(pair).getReserves();
if (blockTimestampLast != blockTimestamp) {
// subtraction overflow is desired
uint32 timeElapsed = blockTimestamp - blockTimestampLast;
// addition overflow is desired
// counterfactual
price0Cumulative += uint256(FixedPoint.fraction(reserve1, reserve0)._x) * timeElapsed;
// counterfactual
price1Cumulative += uint256(FixedPoint.fraction(reserve0, reserve1)._x) * timeElapsed;
}
}
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OUR CONTRACT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
struct TWAPData {
FixedPoint.uq112x112 averagePrice;
uint32 updatedTimestamp;
}
contract UniswapFeeOracle is IFeeOracle {
using SafeMath for uint256;
using FixedPoint for *;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
* @notice Address of the Uniswap V2 TORN pool
*/
address public constant v2TORNPoolAddress = 0x0C722a487876989Af8a05FFfB6e32e45cc23FB3A;
/**
* @notice The Governance Proxy address
*/
address public immutable governanceProxyAddress;
/**
* @notice The Fee Oracle Manager address
*/
address public feeOracleManagerAddress;
/**
* @notice The TWAP interval in seconds (this is common)
*/
uint32 public twapIntervalSeconds;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V3 ORACLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
* @notice Uniswap V3 pool fees for each token registered
*/
mapping(IERC20 => uint24) public poolFeesByToken;
/**
* @notice The minimum observation cardinality for other pools
*/
uint16 public minObservationCardinality;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ V2 TWAP ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/**
* @notice Uniswap V2 torn pool cumulative price last
*/
uint256 public lastCumulativeTORNPriceInETH;
/**
* @notice Last TWAP data
*/
TWAPData public last;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
event PoolFeeUpdated(IERC20 token, uint24 newPoolFee);
event GlobalTORNPoolFeeUpdated(uint24 newPoolFee);
event GlobalTwapIntervalSecondsUpdated(uint32 newUniswapTimePeriod);
event GlobalMinObservationCardinalityUpdated(uint16 newMinObservationCardinality);
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LOGIC ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
constructor(address _governanceProxyAddress, address _feeOracleManagerAddress) public {
governanceProxyAddress = _governanceProxyAddress;
feeOracleManagerAddress = _feeOracleManagerAddress;
(, uint256 _price1CumulativeLast, uint32 _timestamp) =
UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress);
lastCumulativeTORNPriceInETH = _price1CumulativeLast;
last.updatedTimestamp = _timestamp;
}
modifier onlyGovernance() {
require(msg.sender == governanceProxyAddress, "UniswapV3FeeOracle: onlyGovernance");
_;
}
modifier onlyFeeOracleManager() {
require(msg.sender == feeOracleManagerAddress, "UniswapV3FeeOracle: onlyGovernance");
_;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function getFee(
IERC20,
ITornadoInstance _instance,
InstanceData memory _data,
uint32 _feePercent,
uint32 _feePercentDivisor
) public view virtual override returns (uint160) {
// If 0%, 0
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);
// Now get the price ratio
uint256 priceRatio = getTokenPerTORN(_data.token);
// Denomination (tokens) times torn per token, through e18 because both are in e18,
// times fee percent and then through for proper value
return uint160(
_instance.denomination().mul(1e18).div(priceRatio).mul(uint256(_feePercent)).div(
uint256(_feePercentDivisor)
)
);
}
function getTokenPerTORN(IERC20 _token) public view virtual returns (uint256) {
// Get the average price of ETH in the TOKEN
uint256 ethPerTokenAverage = UniswapV3OracleHelper.getPriceOfTokenInWETH(
address(_token), poolFeesByToken[_token], twapIntervalSeconds
);
// Now get average price of TORN per TOKEN
return ethPerTokenAverage * last.averagePrice.decode() / 1e18;
}
function update() public virtual override onlyFeeOracleManager {
// Get the timestamp of the last update
uint32 timestampLastUpdate = last.updatedTimestamp;
// Get the current one by Uniswaps logic, has inbuilt overflow
uint32 currentTimestamp = UniswapV2OracleLibrary.currentBlockTimestamp();
// Calculate elapsed time, no matter whether overflow (uniswap logic)
uint32 elapsed = currentTimestamp - timestampLastUpdate;
// Get last token0/token1 meaning TORN per ETH
(, uint256 _price1CumulativeLast,) = UniswapV2OracleLibrary.currentCumulativePrices(v2TORNPoolAddress);
// Save TWAP data, meaning the average price (TORN per ETH)
last = TWAPData({
averagePrice: FixedPoint.uq112x112(
uint224((_price1CumulativeLast - lastCumulativeTORNPriceInETH) / elapsed)
),
updatedTimestamp: currentTimestamp
});
// Update the cumulative value
lastCumulativeTORNPriceInETH = _price1CumulativeLast;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setPoolFeeForToken(IERC20 _token, uint24 _tokenPoolFee) public virtual onlyGovernance {
// Get global config
// Check whether globals are initialized
require(twapIntervalSeconds != 0, "UniswapV3FeeOracle: time period not initialized");
require(minObservationCardinality != 0, "UniswapV3FeeOracle: cardinality not initialized");
// Only do this if not zeroing out
if (_tokenPoolFee != 0) {
// Check whether a pool exists for the token + fee combination
address poolAddress = UniswapV3OracleHelper.UniswapV3Factory.getPool(
address(_token), UniswapV3OracleHelper.WETH, _tokenPoolFee
);
require(poolAddress != address(0), "UniswapV3FeeOracle: pool for token and fee does not exist");
// Check whether the pool has a large enough observation cardinality
(,,,, uint16 observationCardinalityNext,,) = IUniswapV3PoolState(poolAddress).slot0();
require(
minObservationCardinality <= observationCardinalityNext,
"UniswapV3FeeOracle: pool observation cardinality low"
);
}
// Store & log
poolFeesByToken[_token] = _tokenPoolFee;
emit PoolFeeUpdated(_token, _tokenPoolFee);
}
function setTwapIntervalSeconds(uint32 _newGlobalTwapIntervalSeconds) public virtual onlyGovernance {
twapIntervalSeconds = _newGlobalTwapIntervalSeconds;
emit GlobalTwapIntervalSecondsUpdated(_newGlobalTwapIntervalSeconds);
}
function setMinObservationCardinality(uint16 _newGlobalMinObservationCardinality)
public
virtual
onlyGovernance
{
minObservationCardinality = _newGlobalMinObservationCardinality;
emit GlobalMinObservationCardinalityUpdated(_newGlobalMinObservationCardinality);
}
function setFeeOracleManagerAddress(address _newFeeOracleManagerAddress) public virtual onlyGovernance {
feeOracleManagerAddress = _newFeeOracleManagerAddress;
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function getAverageTORNPerETH() public view virtual returns (uint256) {
return last.averagePrice.decode();
}
}

@ -46,13 +46,19 @@ contract UniswapV3FeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
/* @dev The Governance Proxy address */
/**
* @notice The Governance Proxy address
*/
address public immutable governanceProxyAddress;
/* @dev Global configuration valid across all `getFee()` (and other) calls */
/**
* @notice Global configuration valid across all `getFee()` (and other) calls
*/
GlobalOracleConfig public globals;
/* @dev Uniswap pool fees for each token registered */
/**
* @notice Uniswap pool fees for each token registered
*/
mapping(IERC20 => uint24) public poolFeesByToken;
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EVENTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
@ -75,12 +81,14 @@ contract UniswapV3FeeOracle is IFeeOracle {
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GETTERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function update() public virtual override { }
function getFee(
IERC20 _torn,
ITornadoInstance _instance,
InstanceData memory _data,
uint64 _feePercent,
uint64 _feePercentDivisor
uint32 _feePercent,
uint32 _feePercentDivisor
) public view virtual override returns (uint160) {
// If fee is 0 return
if (_feePercent == 0) return 0;

@ -16,11 +16,13 @@ import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/
import { InstanceData } from "../InstanceRegistry.sol";
interface IFeeOracle {
function update() external;
function getFee(
IERC20 _torn,
ITornadoInstance _instance,
InstanceData memory _data,
uint64 _feePercent,
uint64 _feePercentDivisor
uint32 _feePercent,
uint32 _feePercentDivisor
) external view returns (uint160);
}

@ -19,18 +19,23 @@ import { console2 } from "forge-std/console2.sol";
// Local imports
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol";
import { IGovernance, Proposal } from "common/interfaces/IGovernance.sol";
import { TornadoAddresses } from "common/TornadoAddresses.sol";
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { UniswapFeeOracle } from "src/v2/UniswapFeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceData } from "src/v2/InstanceRegistry.sol";
contract OracleTests is Test {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
FeeOracleManager public constant feeOracleManager =
FeeOracleManager(0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7);
address public constant crvUSDUSDCStableswap2Pool = 0x4DEcE678ceceb27446b35C672dC7d61F30bAD69E;
@ -40,23 +45,22 @@ contract OracleTests is Test {
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
UniswapV3FeeOracle v3FeeOracle;
UniswapFeeOracle uniswapFeeOracle;
CurveFeeOracle feeOracle;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function setUp() public {
vm.createSelectFork(vm.envString("MAINNET_RPC_URL"));
v3FeeOracle = new UniswapV3FeeOracle(address(this));
v3FeeOracle.setGlobalTornPoolFee(10_000, true);
v3FeeOracle.setGlobalTwapIntervalSeconds(5400);
v3FeeOracle.setGlobalMinObservationCardinality(10);
uniswapFeeOracle = new UniswapFeeOracle(address(this), address(feeOracleManager));
uniswapFeeOracle.setTwapIntervalSeconds(5400);
uniswapFeeOracle.setMinObservationCardinality(10);
feeOracle = new CurveFeeOracle(address(this));
feeOracle.setTornOracleIsUniswapV3(false);
feeOracle.setUniswapV3FeeOracle(v3FeeOracle);
feeOracle.setUniswapFeeOracle(uniswapFeeOracle);
}
function test_curveFeeSingleTricrypto() public {
@ -89,7 +93,7 @@ contract OracleTests is Test {
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
function _setCurveFeeSimpleTricryptoOracleForInstance(
CurveFeeOracle _feeOracle,

@ -11,13 +11,17 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { console2 } from "forge-std/console2.sol";
// Tornado imports
import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol";
// Local imports
import { UniswapV3FeeOracle } from "src/v2/UniswapV3FeeOracle.sol";
import { UniswapFeeOracle } from "src/v2/UniswapFeeOracle.sol";
import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol";
import { InstanceRegistry } from "src/v2/InstanceRegistry.sol";
import { InstanceRegistry, InstanceData } from "src/v2/InstanceRegistry.sol";
import { FeeOracleManager } from "src/v2/FeeOracleManager.sol";
@ -31,49 +35,486 @@ import { CRVUSDInstancesProposal } from "src/proposals/CRVUSDInstancesProposal.s
import { TornadoProposalTest, ProposalState } from "./TornadoProposalTest.sol";
contract ProposalTests is TornadoProposalTest {
contract Instances {
/* ETH instances */
ITornadoInstance public constant eth01 = ITornadoInstance(0x12D66f87A04A9E220743712cE6d9bB1B5616B8Fc);
ITornadoInstance public constant eth1 = ITornadoInstance(0x47CE0C6eD5B0Ce3d3A51fdb1C52DC66a7c3c2936);
ITornadoInstance public constant eth10 = ITornadoInstance(0x910Cbd523D972eb0a6f4cAe4618aD62622b39DbF);
ITornadoInstance public constant eth100 = ITornadoInstance(0xA160cdAB225685dA1d56aa342Ad8841c3b53f291);
/* DAI instances */
ITornadoInstance public constant dai100 = ITornadoInstance(0xD4B88Df4D29F5CedD6857912842cff3b20C8Cfa3);
ITornadoInstance public constant dai1000 = ITornadoInstance(0xFD8610d20aA15b7B2E3Be39B396a1bC3516c7144);
ITornadoInstance public constant dai10000 = ITornadoInstance(0x07687e702b410Fa43f4cB4Af7FA097918ffD2730);
ITornadoInstance public constant dai100000 = ITornadoInstance(0x23773E65ed146A459791799d01336DB287f25334);
/* cDAI instances */
ITornadoInstance public constant cdai100 = ITornadoInstance(0x22aaA7720ddd5388A3c0A3333430953C68f1849b);
ITornadoInstance public constant cdai1000 = ITornadoInstance(0x03893a7c7463AE47D46bc7f091665f1893656003);
ITornadoInstance public constant cdai10000 = ITornadoInstance(0x2717c5e28cf931547B621a5dddb772Ab6A35B701);
ITornadoInstance public constant cdai100000 = ITornadoInstance(0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af);
/* USDT instances */
ITornadoInstance public constant usdt100 = ITornadoInstance(0x169AD27A470D064DEDE56a2D3ff727986b15D52B);
ITornadoInstance public constant usdt1000 = ITornadoInstance(0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f);
/* WBTC instances */
ITornadoInstance public constant wbtc01 = ITornadoInstance(0x178169B423a011fff22B9e3F3abeA13414dDD0F1);
ITornadoInstance public constant wbtc1 = ITornadoInstance(0x610B717796ad172B316836AC95a2ffad065CeaB4);
ITornadoInstance public constant wbtc10 = ITornadoInstance(0xbB93e510BbCD0B7beb5A853875f9eC60275CF498);
/* CRVUSD instances */
ITornadoInstance public constant cu100 = ITornadoInstance(0x913a73486Dc4AA3832A56d461542836C1eeB93be);
ITornadoInstance public constant cu1_000 = ITornadoInstance(0x5A6b3C829dB3e938C885000c6E93CF35E74876a4);
ITornadoInstance public constant cu10_000 = ITornadoInstance(0x49f173CDAB99a2C3800F1255393DF9B7a17B82Bb);
ITornadoInstance public constant cu100_000 = ITornadoInstance(0x4640Dffc9fD0B113B983e3A350b070a119CA143C);
ITornadoInstance public constant cu1_000_000 =
ITornadoInstance(0xc4eA8Bd3Fd76f3c255395793B47F7c55aD59d991);
}
contract ProposalTests is Instances, TornadoProposalTest {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VARIABLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C);
IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
CurveFeeOracle curveFeeOracle;
FeeOracleManager public constant feeOracleManager =
FeeOracleManager(0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7);
UniswapV3FeeOracle v3FeeOracle;
InstanceRegistry public constant instanceRegistry =
InstanceRegistry(0xB20c66C4DE72433F3cE747b58B86830c459CA911);
// Implementations
InstanceRegistry implInstanceRegisty;
FeeOracleManager feeOracleManager;
FeeOracleManager implFeeOracleManager;
// Actual contracts
CurveFeeOracle curveFeeOracle;
UniswapFeeOracle uniswapFeeOracle;
TornadoRouter router;
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
function setUp() public override {
super.setUp();
TornadoProposalTest.setUp();
vm.rollFork(17_438_199);
vm.rollFork(17_486_841);
curveFeeOracle = new CurveFeeOracle(address(governance));
v3FeeOracle = new UniswapV3FeeOracle(address(governance));
uniswapFeeOracle = new UniswapFeeOracle(address(governance), address(feeOracleManager));
vm.prank(address(governance));
curveFeeOracle.setUniswapV3FeeOracle(v3FeeOracle);
curveFeeOracle.setUniswapFeeOracle(uniswapFeeOracle);
implInstanceRegisty = new InstanceRegistry(address(governance));
feeOracleManager = new FeeOracleManager(address(TORN), address(governance));
implFeeOracleManager = new FeeOracleManager(address(TORN), address(governance));
router = new TornadoRouter(address(governance));
router = TornadoRouter(vm.envAddress("TORNADO_ROUTER_ADDRESS"));
}
function test_crvusdInstancesBasic() public {
// Do the proposal first
test_crvusdInstancesProposalBasic();
// Try to update crvusd 10000
feeOracleManager.updateFee(cu10_000, false);
// Try to update all
ITornadoInstance[] memory _toUpdate = new ITornadoInstance[](5);
_toUpdate[0] = cu100;
_toUpdate[1] = cu1_000;
_toUpdate[2] = cu10_000;
_toUpdate[3] = cu100_000;
_toUpdate[4] = cu1_000_000;
feeOracleManager.updateFees(_toUpdate, false);
// Print fees to console.
console2.log("\n~~~~~~~~~~~~~~~~~~ LAST FEES ~~~~~~~~~~~~~~~~~~\n");
console2.log("cu100: ", uint256(feeOracleManager.getLastFeeForInstance(cu100)));
console2.log("cu1_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000)));
console2.log("cu10_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu10_000)));
console2.log("cu100_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu100_000)));
console2.log("cu1_000_000: ", uint256(feeOracleManager.getLastFeeForInstance(cu1_000_000)));
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ UPDATED FEES ~~~~~~~~~~~~~~~~~~\n");
console2.log("cu100: ", uint256(feeOracleManager.instanceFeeWithUpdate(cu100)));
console2.log("cu1_000: ", uint256(feeOracleManager.instanceFeeWithUpdate(cu1_000)));
console2.log("cu10_000: ", uint256(feeOracleManager.instanceFeeWithUpdate(cu10_000)));
console2.log("cu100_000: ", uint256(feeOracleManager.instanceFeeWithUpdate(cu100_000)));
console2.log("cu1_000_000: ", uint256(feeOracleManager.instanceFeeWithUpdate(cu1_000_000)));
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n");
InstanceData memory data = instanceRegistry.getInstanceData(cu100);
delimit();
console2.log("cu100:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(cu1_000);
console2.log("cu1_000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(cu10_000);
console2.log("cu10_000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(cu100_000);
console2.log("cu100_000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(cu1_000_000);
console2.log("cu1_000_000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
// Some assertions
require(
strcomp(curveFeeOracle.getChainedOracleNameForInstance(cu100), "ETH/CRVUSD"),
"oracle name doesnt match"
);
require(
strcomp(
curveFeeOracle.getChainedOracleNameForOracleHash(
curveFeeOracle.getChainedOracleHashForInstance(cu1_000)
),
"ETH/CRVUSD"
),
"oracle name doesnt match"
);
// Should be able to update all fees
feeOracleManager.updateAllFees(true);
feeOracleManager.updateAllFees(false);
// Just for some gas values for this call
instanceRegistry.instanceData(cu100);
instanceRegistry.instanceData(cu1_000);
}
function test_infrastructureBasic() public {
// Do the proposal first
test_infrastructureUpgradeProposalBasic();
// Try to update eth10
feeOracleManager.updateFee(eth10, false);
// Try to update multiple
ITornadoInstance[] memory _toUpdate = new ITornadoInstance[](3);
_toUpdate[0] = eth100;
_toUpdate[1] = dai10000;
_toUpdate[2] = usdt100;
feeOracleManager.updateFees(_toUpdate, false);
require(feeOracleManager.getLastUpdatedTimeForInstance(eth100) == now, "timeup");
// Must not be able to re-initialize the InstanceRegistry
vm.expectRevert();
vm.prank(address(governance));
instanceRegistry.initialize(_toUpdate, router);
vm.expectRevert();
instanceRegistry.initialize(_toUpdate, router);
// Must not be able to re-initialize the FeeOracleManager
vm.expectRevert();
vm.prank(address(governance));
feeOracleManager.initialize(
address(uniswapFeeOracle), address(instanceRegistry), _toUpdate, feeArrayForTesting_1()
);
vm.expectRevert();
feeOracleManager.initialize(
address(uniswapFeeOracle), address(instanceRegistry), _toUpdate, feeArrayForTesting_1()
);
// Must not be able to re-initialize the router... the contract addresses are unimportant
vm.expectRevert();
vm.prank(address(governance));
router.initialize(address(instanceRegistry), address(instanceRegistry));
vm.expectRevert();
router.initialize(address(instanceRegistry), address(instanceRegistry));
// Check fee logic and print to console.
console2.log("\n~~~~~~~~~~~~~~~~~~ LAST FEES ~~~~~~~~~~~~~~~~~~\n");
console2.log("eth10: ", uint256(feeOracleManager.getLastFeeForInstance(eth10)));
console2.log("eth100: ", uint256(feeOracleManager.getLastFeeForInstance(eth100)));
console2.log("dai10000: ", uint256(feeOracleManager.getLastFeeForInstance(dai10000)));
console2.log("usdt100: ", uint256(feeOracleManager.getLastFeeForInstance(usdt100)));
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ UPDATED FEES ~~~~~~~~~~~~~~~~~~\n");
console2.log("eth10: ", uint256(feeOracleManager.instanceFeeWithUpdate(eth10)));
console2.log("eth100: ", uint256(feeOracleManager.instanceFeeWithUpdate(eth100)));
console2.log("dai10000: ", uint256(feeOracleManager.instanceFeeWithUpdate(dai10000)));
console2.log("usdt100: ", uint256(feeOracleManager.instanceFeeWithUpdate(usdt100)));
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n");
InstanceData memory data = instanceRegistry.getInstanceData(eth10);
delimit();
console2.log("eth10:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(eth100);
console2.log("eth100:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(dai10000);
console2.log("dai10000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(usdt100);
console2.log("usdt100:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
// Now should be able to rmemove an instance
vm.prank(address(governance));
instanceRegistry.removeInstanceByAddress(address(dai10000)); // DAI10k
vm.prank(address(governance));
instanceRegistry.removeInstanceByIndex(2); // ETH10
// Now log again
console2.log("\n~~~~~~~~~~~~~~~~~~ SHOULD HAVE CHANGED ~~~~~~~~~~~~~~~~~~\n");
delimit();
data = instanceRegistry.getInstanceData(2);
console2.log("NOT eth10:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
delimit();
data = instanceRegistry.getInstanceData(6);
console2.log("NOT dai10000:");
console2.log("token: ", address(data.token));
console2.log("index: ", uint256(data.index));
console2.log("iserc20: ", data.isERC20);
console2.log("isenabled: ", data.isEnabled);
console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
// Now add them back and only do assertions
vm.prank(address(governance));
instanceRegistry.addInstance(dai10000);
vm.prank(address(governance));
instanceRegistry.addInstance(eth10);
vm.prank(address(governance));
data = instanceRegistry.getInstanceData(dai10000);
require(data.token == DAI, "not dai");
require(data.index == 15, "not 15");
require(data.isERC20, "not token");
require(data.isEnabled, "not enabled");
vm.prank(address(governance));
data = instanceRegistry.getInstanceData(eth10);
require(data.token == IERC20(0), "not eth");
require(data.index == 16, "not last");
require(!data.isERC20, "not not token");
require(data.isEnabled, "not enabled");
// Some assertions to test getters
require(instanceRegistry.getInstanceIndex(eth100) == 3, "Wrong index");
require(instanceRegistry.getInstanceToken(eth100) == IERC20(address(0)), "Wrong token");
require(instanceRegistry.getInstanceToken(dai10000) == DAI, "Wrong token");
require(instanceRegistry.isEnabledInstance(dai10000) == true, "Wrong token");
// Just to get some gas values for this fn
instanceRegistry.instanceData(eth10);
instanceRegistry.instanceData(eth100);
instanceRegistry.instanceData(dai10000);
// Expect no update if in inside the time limit interval
uint256 startTimestamp = block.timestamp;
uint256 eth100Fee = feeOracleManager.getLastFeeForInstance(eth100);
uint256 interval = feeOracleManager.getFeeUpdateIntervalForInstance(eth100);
vm.prank(address(governance));
feeOracleManager.setFeePercentForInstance(eth100, 50);
feeOracleManager.updateFee(eth100, true);
require(eth100Fee == feeOracleManager.getLastFeeForInstance(eth100), "wrong fee 1");
vm.warp(startTimestamp + interval + 5 days);
feeOracleManager.updateFee(eth100, true);
uint256 eth100Fee2 = feeOracleManager.getLastFeeForInstance(eth100);
require(eth100Fee != eth100Fee2, "wrong fee 2");
vm.prank(address(governance));
feeOracleManager.setFeePercentForInstance(eth100, 30);
vm.warp(startTimestamp + 2 * interval + 10 days);
feeOracleManager.updateFee(eth100, true);
require(eth100Fee == feeOracleManager.getLastFeeForInstance(eth100), "wrong fee 3");
vm.warp(startTimestamp);
// Should be able to update all fees
feeOracleManager.updateAllFees(true);
feeOracleManager.updateAllFees(false);
// Deviations don't revert
feeOracleManager.getAllFeeDeviations();
// There is a deviation
vm.prank(address(governance));
feeOracleManager.setFeePercentForInstance(eth100, 10);
_toUpdate = new ITornadoInstance[](1);
_toUpdate[0] = eth100;
int256[] memory eth100Deviations = feeOracleManager.getFeeDeviationsForInstances(_toUpdate);
require(eth100Deviations[0] != 0, "deviations");
vm.prank(address(governance));
feeOracleManager.setFeePercentForInstance(eth100, 30);
feeOracleManager.updateFee(eth100, false);
// Should be able to deposit via router
address depositor = address(bytes20(bytes32(keccak256("depositor"))));
vm.deal(depositor, 1 ether); // for gas
vm.prank(address(DAI));
DAI.transfer(depositor, 100_000e18);
require(DAI.balanceOf(depositor) == 100_000e18, "not enough depo bal");
vm.prank(address(depositor));
DAI.approve(address(router), 100_000e18);
bytes32 commitment = 0x00eb84375e4bfd042468fe624e6d322dba4f601b37dd0a44f741d3cb04127df0;
bytes memory empty;
vm.prank(address(depositor));
router.deposit(dai100000, commitment, empty);
// Let's toss in some functions for coverage
vm.prank(address(governance));
feeOracleManager.setFeeUpdateIntervalForInstance(eth100, 60_000);
vm.prank(address(governance));
feeOracleManager.setFeeUpdateIntervalForInstance(eth100, uint24(interval));
require(feeOracleManager.getFeePercentForInstance(eth100) == 30, "fee percent eth100");
require(feeOracleManager.getLastUpdatedTimeForInstance(eth100) == now, "updated time eth100");
// Reverts
vm.expectRevert();
feeOracleManager.setInstanceRegistry(address(0));
vm.expectRevert();
feeOracleManager.setFeeOracle(address(eth100), address(0));
vm.expectRevert();
feeOracleManager.setFeePercentForInstance(eth100, 40);
vm.expectRevert();
feeOracleManager.setFeeUpdateIntervalForInstance(eth100, uint24(interval - 3));
// Should be able to delete and set an oracle
vm.prank(address(governance));
feeOracleManager.setFeeOracle(address(eth100), address(0));
require(address(feeOracleManager.instanceFeeOracles(eth100)) == address(0), "fee oracle deletion");
vm.prank(address(governance));
feeOracleManager.setFeeOracle(address(eth100), address(uniswapFeeOracle));
}
function test_crvusdInstancesProposalBasic() public {
// First pass the former proposal
test_infrastructureUpgradeProposalBasic();
// Then create the crvUSD proposal
address proposal =
address(new CRVUSDInstancesProposal(address(curveFeeOracle), address(uniswapFeeOracle)));
// Propose
uint256 id = easyPropose(proposal);
// Wait
waitUntilExecutable(id);
// Exec
governance.execute(id);
}
function test_infrastructureUpgradeProposalBasic() public {
// Create proposal
address proposal = address(
new InfrastructureUpgradeProposal(
address(v3FeeOracle),
address(feeOracleManager),
address(uniswapFeeOracle),
address(implFeeOracleManager),
address(implInstanceRegisty),
address(router)
)
@ -87,22 +528,30 @@ contract ProposalTests is TornadoProposalTest {
// Exec
governance.execute(id);
// Test whethe resolving names works... with the Governance contract
require(
address(instanceRegistry.getInstanceByENSName("governance.contract.tornadocash.eth"))
== address(governance),
"ENSResolving doesn't work"
);
}
function test_crvusdInstancesProposalBasic() public {
// First pass the former proposal
test_infrastructureUpgradeProposalBasic();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Then create the crvUSD proposal
address proposal = address(new CRVUSDInstancesProposal(address(curveFeeOracle), address(v3FeeOracle)));
function delimit() internal view {
console2.log();
}
// Propose
uint256 id = easyPropose(proposal);
function strcomp(string memory left, string memory right) internal pure returns (bool) {
return keccak256(abi.encode(left)) == keccak256(abi.encode(right));
}
// Wait
waitUntilExecutable(id);
// Exec
governance.execute(id);
function feeArrayForTesting_1() internal pure returns (uint256[] memory fees) {
fees = new uint256[](3);
fees[0] = 30;
fees[1] = 30;
fees[2] = 30;
}
}