// SPDX-License-Identifier: MIT pragma solidity ^0.6.12; pragma experimental ABIEncoderV2; // OZ Imports import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // STD Imports import { console2 } from "forge-std/console2.sol"; // Tornado imports import { ITornadoInstance } from "tornado-anonymity-mining/contracts/interfaces/ITornadoInstance.sol"; // Local imports import { IUniswapV2Pair } from "src/v2/interfaces/IUniswapV2Pair.sol"; import { UniswapFeeOracle } from "src/v2/UniswapFeeOracle.sol"; import { TornadoRouter } from "src/v2/TornadoRouter.sol"; import { RelayerRegistry } from "src/v2/RelayerRegistry.sol"; import { TornadoStakingRewards } from "src/v2/TornadoStakingRewards.sol"; import { InstanceRegistry, InstanceState } from "src/v2/InstanceRegistry.sol"; import { FeeOracleManager, FeeDataForOracle, InstanceWithFee } from "src/v2/FeeOracleManager.sol"; import { CurveFeeOracle, ICurvePriceOracle, CurveChainedOracles } from "src/v2/CurveFeeOracle.sol"; import { InfrastructureUpgradeProposal } from "src/proposals/InfrastructureUpgradeProposal.sol"; import { CRVUSDInstancesProposal } from "src/proposals/CRVUSDInstancesProposal.sol"; // Test imports import { TornadoProposalTest, ProposalState } from "./TornadoProposalTest.sol"; interface IWethDepositable { function deposit() external payable; } interface IENS { function setOwner(bytes32 node, address owner) external; function owner(bytes32 node) external view returns (address); } 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tokens IERC20 public constant TORN = IERC20(0x77777FeDdddFfC19Ff86DB637967013e6C6A116C); IERC20 public constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); IERC20 public constant CDAI = IERC20(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); IERC20 public constant USDT = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7); IERC20 internal constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); IERC20 internal constant WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599); IERC20 internal constant CRVUSD = IERC20(0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E); // Constant vars IENS public constant ENS = IENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e); FeeOracleManager public constant feeOracleManager = FeeOracleManager(0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7); InstanceRegistry public constant instanceRegistry = InstanceRegistry(0xB20c66C4DE72433F3cE747b58B86830c459CA911); RelayerRegistry public constant relayerRegistry = RelayerRegistry(0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2); IUniswapV2Pair public constant uniTornPool = IUniswapV2Pair(0x0C722a487876989Af8a05FFfB6e32e45cc23FB3A); address payable public constant stakingProxyAddress = 0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29; address payable public constant relayerRegistryProxyAddress = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2; // Implementations InstanceRegistry implInstanceRegistry; FeeOracleManager implFeeOracleManager; RelayerRegistry implRegistry; TornadoStakingRewards implStaking; // Infra contracts mutable CurveFeeOracle curveFeeOracle; UniswapFeeOracle uniswapFeeOracle; TornadoRouter router; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ TESTING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function setUp() public override { TornadoProposalTest.setUp(); vm.rollFork(17_522_762); curveFeeOracle = new CurveFeeOracle(address(governance)); uniswapFeeOracle = new UniswapFeeOracle(address(governance), address(feeOracleManager)); vm.prank(address(governance)); curveFeeOracle.setUniswapFeeOracle(uniswapFeeOracle); router = new TornadoRouter(address(governance)); implRegistry = new RelayerRegistry(address(governance), address(TORN)); implInstanceRegistry = new InstanceRegistry(address(governance)); implFeeOracleManager = new FeeOracleManager(address(TORN), address(governance)); implStaking = new TornadoStakingRewards(address(governance), address(TORN), address(router)); _advanceTORNETHMarket(); } function test_crvusdInstancesBasic() public { // Do the proposal first test_crvusdInstancesProposalBasic(); // We have to set to address this again _feeManagerShouldRevertAndSetFeeUpdater(); // Advance _advanceTORNETHMarket(); // 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; // We will need to prank an update on the uniswapFeeOracle // this should otherwise happen automatically though other tokens being withdrawn vm.startPrank(address(feeOracleManager)); for (uint256 i = 0; i < _toUpdate.length; i++) { uniswapFeeOracle.update(TORN, feeOracleManager.populateInstanceWithFeeData(_toUpdate[i])); } vm.stopPrank(); // Now update all fees uint160[] memory fees = 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~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n"); InstanceState memory data = instanceRegistry.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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"); // No access curveFeeOracle.update(TORN, feeOracleManager.populateInstanceWithFeeData(cu10_000)); // No access view curveFeeOracle.getFee(TORN, feeOracleManager.populateInstanceWithFeeData(cu10_000)); // 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" ); // Now since we will test updating all fees... the oracle will get updated // but first, warp forward vm.warp(now + 2 days); // Should be able to update all fees fees = feeOracleManager.updateAllFees(true); feeOracleManager.version(); delimit(); 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 💱 I would like to note that I have verified via calculation that post-price move, the fees work.\n Basically: (1000000*30/10000) == fee * 9 / 10^18, should be constant because block is fix. \n" ); // Just for some gas values for this call instanceRegistry.instanceData(cu100); instanceRegistry.instanceData(cu1_000); instanceRegistry.version(); router.version(); // Let's just check all instances one by one InstanceState[] memory states = instanceRegistry.getAllInstanceStates(); for (uint256 i = 0; i < states.length; i++) { if (i <= 3) { require(!states[i].isERC20); require(states[i].isEnabled); } else if (i <= 7) { require(states[i].token == DAI); require(states[i].isERC20); require(states[i].isEnabled); } else if (i <= 11) { require(states[i].token == CDAI); require(states[i].isERC20); require(states[i].isEnabled); } else if (i <= 13) { require(states[i].token == USDT); require(states[i].isERC20); require(states[i].isEnabled); } else if (i <= 16) { require(states[i].token == WBTC); require(states[i].isERC20); require(states[i].isEnabled); } else if (i <= 21) { require(states[i].token == CRVUSD); require(states[i].isERC20); require(states[i].isEnabled); } } // Now do a specific interval states = instanceRegistry.getInstanceState(8, 11); for (uint256 i = 0; i < 4; i++) { require(states[i].token == CDAI); require(states[i].isERC20); require(states[i].isEnabled); } // Other for coverage require(instanceRegistry.isRegisteredInstance(cu10_000)); vm.prank(address(governance)); instanceRegistry.setTornadoRouter(address(0)); require(address(instanceRegistry.router()) == address(0)); } function test_infrastructureBasic() public { // Do the proposal first test_infrastructureUpgradeProposalBasic(); // No re-initializing _contractsNotReinitializable(); // The instance registry must resolve ENS names _instanceRegistryShouldResolveENSNames(); // Setup and test fee oracle manager update revert _feeManagerShouldRevertAndSetFeeUpdater(); // Registry should be able to resolve wrapped names _registryShouldResolveWrappedENSNames(); // Proxy admin slot _relayerRegistyProxyAdminSlotShouldBeDead(); // Some leftover tests not included in others _leftoverRelayerRegistryTests(); // Test router deposits and withdrawals _shouldBeAbleToDepositAndWithdrawFromRouter(); // Router trivial for coverage _trivialRouterCoverage(); // Instance reg trivial _trivialInstanceRegistryCoverage(); // 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"); // 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.updateFee(eth10, true))); console2.log("eth100: ", uint256(feeOracleManager.updateFee(eth100, true))); console2.log("dai10000: ", uint256(feeOracleManager.updateFee(dai10000, true))); console2.log("usdt100: ", uint256(feeOracleManager.updateFee(usdt100, true))); console2.log("\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); delimit(); console2.log("\n~~~~~~~~~~~~~~~~~~ DATA ~~~~~~~~~~~~~~~~~~\n"); InstanceState memory data = instanceRegistry.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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.getInstanceState(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 these fns instanceRegistry.instanceData(eth10); instanceRegistry.instanceData(eth100); instanceRegistry.instanceData(dai10000); uniswapFeeOracle.getTWAPData(); uniswapFeeOracle.getLastUpdatedTime(); vm.expectRevert(); uniswapFeeOracle.update( TORN, InstanceWithFee({ logic: eth100, state: InstanceState(WETH, 0, false, false), fee: FeeDataForOracle(0, 0, 0, 2 days, 0) }) ); vm.expectRevert(); uniswapFeeOracle.setFeeOracleManagerAddress(address(0)); vm.expectRevert(); uniswapFeeOracle.setMinObservationCardinality(0); vm.expectRevert(); uniswapFeeOracle.setPoolFeeForToken(WETH, 0); // Expect no update if in inside the time limit interval uint256 startTimestamp = block.timestamp; uint256 interval = feeOracleManager.getFeeUpdateIntervalForInstance(eth100); vm.warp(startTimestamp + interval + 5 days); feeOracleManager.updateFee(eth100, true); uint256 eth100Fee = feeOracleManager.getLastFeeForInstance(eth100); vm.prank(address(governance)); feeOracleManager.setFeePercentForInstance(eth100, 50); // both shouldnt update feeOracleManager.updateFee(eth100, true); uint32 lastUpdatedTimeHere = feeOracleManager.getLastUpdatedTimeForInstance(eth100); vm.prank(address(feeOracleManager)); uniswapFeeOracle.update( TORN, InstanceWithFee({ logic: eth100, state: InstanceState(WETH, 0, false, false), fee: FeeDataForOracle(0, 0, 0, 2 days, lastUpdatedTimeHere) }) ); require(eth100Fee == feeOracleManager.getLastFeeForInstance(eth100), "wrong fee 1"); vm.warp(startTimestamp + 2 * interval + 10 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 + 3 * interval + 15 days); feeOracleManager.updateFee(eth100, true); require(eth100Fee == feeOracleManager.getLastFeeForInstance(eth100), "wrong fee 3"); require(eth100Fee == feeOracleManager.getUpdatedFeeForInstance(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); // 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)); // For coverage vm.prank(address(governance)); uniswapFeeOracle.setFeeOracleManagerAddress(address(feeOracleManager)); vm.prank(address(governance)); feeOracleManager.setInstanceRegistry(address(instanceRegistry)); } 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(router), address(implStaking), address(implRegistry), address(implInstanceRegistry), address(implFeeOracleManager), address(uniswapFeeOracle) ) ); // Propose uint256 id = easyPropose(proposal); // Wait waitUntilExecutable(id); // Exec governance.execute(id); } function _contractsNotReinitializable() internal { ITornadoInstance[] memory _toUpdate = new ITornadoInstance[](3); _toUpdate[0] = eth100; _toUpdate[1] = dai10000; _toUpdate[2] = usdt100; // Must not be able to reinit router, contract addresses are not important vm.expectRevert(); vm.prank(address(governance)); router.initialize( address(instanceRegistry), address(instanceRegistry), address(feeOracleManager), address(0) ); vm.expectRevert(); router.initialize( address(instanceRegistry), address(instanceRegistry), address(feeOracleManager), address(0) ); // 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), address(router), 6 hours, _toUpdate, feeArrayForTesting_1() ); vm.expectRevert(); feeOracleManager.initialize( address(uniswapFeeOracle), address(instanceRegistry), address(router), 6 hours, _toUpdate, feeArrayForTesting_1() ); } function _leftoverRelayerRegistryTests() internal { relayerRegistry.version(); require(relayerRegistry.minimumTornStake() == 2000 ether, "reg min st"); MinimumStakeOracle oracle = new MinimumStakeOracle(relayerRegistry); vm.prank(address(governance)); relayerRegistry.setMinimumTornStake(1000 ether); require(relayerRegistry.minimumTornStake() == 1000 ether, "reg min st2"); vm.expectRevert(); relayerRegistry.setMinimumStakeOracle(address(oracle)); vm.prank(address(governance)); relayerRegistry.setMinimumStakeOracle(address(oracle)); oracle.setAmount(); require(relayerRegistry.minimumTornStake() == 2000 ether, "reg min st3"); } function _trivialInstanceRegistryCoverage() internal view { instanceRegistry.version(); require(instanceRegistry.isRegisteredInstance(dai100000), "dai100k not reg"); } function _trivialRouterCoverage() internal { // This is just to fill up router coverage so I can have an overview // I am aware of the pros and cons of coverage router.version(); bytes[] memory fakenotes = new bytes[](1); fakenotes[0] = bytes("anydata"); router.backupNotes(fakenotes); vm.deal(address(router), 1 ether); vm.prank(address(governance)); router.rescueTokens(IERC20(0), payable(address(governance)), 1 ether); vm.prank(address(DAI)); DAI.transfer(address(router), 1 ether); vm.prank(address(governance)); router.rescueTokens(DAI, payable(address(governance)), 1 ether); vm.expectRevert(); router.setFeeOracleManager(address(0)); vm.expectRevert(); router.setInstanceRegistry(address(0)); vm.expectRevert(); router.setStakingRewards(address(0)); vm.prank(address(governance)); router.setFeeOracleManager(address(feeOracleManager)); vm.prank(address(governance)); router.setInstanceRegistry(address(instanceRegistry)); vm.prank(address(governance)); router.setStakingRewards(stakingProxyAddress); } function _instanceRegistryShouldResolveENSNames() internal view { require( address(instanceRegistry.getInstanceByENSName("governance.contract.tornadocash.eth")) == address(governance), "ENSResolving doesn't work" ); } function _feeManagerShouldRevertAndSetFeeUpdater() internal { vm.expectRevert(); feeOracleManager.updateFee(eth100, false); vm.prank(address(governance)); feeOracleManager.setFeeUpdater(address(this)); } function _registryShouldResolveWrappedENSNames() internal view { require( relayerRegistry.ownerByName("wrappedtestname.eth") == 0x895CB4C75e9be8ABA117bF4E044416C855018ea0, "ens name wrapper resolution" ); } function _relayerRegistyProxyAdminSlotShouldBeDead() internal view { require( address( bytes20( vm.load( address(relayerRegistry), 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 ) ) ) == address(0), "admin slot" ); } function _shouldBeAbleToDepositAndWithdrawFromRouter() internal { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~ DEPOSITING ~~~~~~~~~~~~~~~~~~~~~~~ */ 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); // This and other precomputed data you will see here // I've had to generate with the SDK for testing bytes32 commitment = 0x023fe5da10c6b6f3748e69c9b4bb83ee3c4de6fb7a9e174c69b34580fccebd11; bytes memory empty; vm.prank(address(depositor)); router.deposit(dai100000, commitment, empty); /* ~~~~~~~~~~~~~~~~~~~~~~~~~~ WITHDRAWING ~~~~~~~~~~~~~~~~~~~~~~~ */ // This and other precomputed data you will see here // I've had to generate with the SDK for testing address withdrawer = 0xc02D1C9620481387a211Bdcfe168f0653164AAf6; address[] memory workers = new address[](3); workers[0] = address(bytes20(keccak256(abi.encode("worker1")))); workers[1] = address(bytes20(keccak256(abi.encode("worker2")))); workers[2] = address(bytes20(keccak256(abi.encode("worker3")))); vm.deal(withdrawer, 1 ether); /* ~~~~~~~~~~~~~~~ HAVE TO REGISTER RELAYER FIRST ~~~~~~~~~~~~~~~ */ // vitalik.eth vm.prank(ENS.owner(0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835)); ENS.setOwner(0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835, withdrawer); _registerRelayer("vitalik.eth", withdrawer, workers); /* ~~~~~~~~~~~~~~~ DO WITHDRAW ~~~~~~~~~~~~~~~ */ vm.prank(address(governance)); feeOracleManager.setFeeUpdater(address(router)); vm.prank(address(workers[1])); router.withdraw( dai100000, // Instance // Proof hex"13d5278f72487a02217b02fa8533964103d0033b601498303a86d2896f2f44ec198716465105a255bed58cf8ddea2884d740cc71a0b5170777829c1ab3ee8ed21e0588b3a00d2109f92f0daed5a1ef6207f699650252ef8111cd2d8f8688f01603410dcfd4df3627bed16a600a3e15e3fa8443e6e931cb5405c4efbf0dcaff4014b6006964e9edff0039c3cef1dbaa043c8e51e947a3ab756d3a5c293c4707370ef218c427678700cbf61ce8cc2572363b4f6b0e7c50750e875d3e61485b4eaf14dd78c7d7e594f74e8e809cfebd6b3cb6c2e42573f5be96c726bc8eff3599002b1d397f9d208367545a3540736c5e95757b4365b1a3992ea3f75fee7257c70a", bytes32(0x29432414abc7ac658bbf2aa080ebac45774bd802b2ce727f0ae45840f587df00), // Merkle root bytes32(0x0ee7243a3a650bd3aa48f445597846275064207d0a3ff749e11f1ab8edfc630e), // Nullifier payable(0x0520fF8CF7824FCb4308300469D90688e267DC25), // Recipient address payable(0xc02D1C9620481387a211Bdcfe168f0653164AAf6), // Relayer address, should work uint256(0x0000000000000000000000000000000000000000000000d8f2a11699e9c607bc), // Fee 0 // Refund ); vm.prank(address(governance)); feeOracleManager.setFeeUpdater(address(this)); /* ~~~~~~~~~~~~~~~~ POST ASSERTIONS ~~~~~~~~~~~~~~~~~~~~ */ uint256 fee = uint256(0xd8f2a11699e9c607bc); require(DAI.balanceOf(0x0520fF8CF7824FCb4308300469D90688e267DC25) == 100_000 ether - fee, "recipient"); require(DAI.balanceOf(0xc02D1C9620481387a211Bdcfe168f0653164AAf6) == fee, "relayer fee"); require(relayerRegistry.getRelayerBalanceByName("vitalik.eth") < 2000 ether, "vitalik bal"); /* ~~~~~~~~~~~~~~~~ BONUS: UNREGISTER WORKERS ~~~~~~~~~~~~~~~~~~~~ */ vm.prank(withdrawer); relayerRegistry.unregisterWorker(workers[1]); vm.prank(workers[2]); vm.expectRevert(); relayerRegistry.unregisterWorker(workers[0]); vm.prank(workers[0]); relayerRegistry.unregisterWorker(workers[0]); vm.prank(workers[2]); relayerRegistry.unregisterWorker(workers[2]); /* ~~~~~~~~~~~~~~~~ BONUS: ADD STAKE ~~~~~~~~~~~~~~~~~~~~ */ address donator = address(bytes20(keccak256(abi.encode("donator to vitalik")))); giveTorn(donator, 1000 ether); vm.prank(donator); TORN.approve(address(relayerRegistry), 1000 ether); vm.prank(donator); relayerRegistry.stakeToRelayer(withdrawer, 1000 ether); require(2000 ether < relayerRegistry.getRelayerBalanceByName("vitalik.eth"), "vitalik bal"); } function _registerRelayer(string memory _name, address _relayer, address[] memory _workers) internal { giveTorn(_relayer, 2000 ether); vm.startPrank(_relayer); TORN.approve(address(relayerRegistry), 2000 ether); relayerRegistry.register(_name, 2000 ether, _workers); vm.stopPrank(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HELPERS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function _advanceTORNETHMarket() internal { console2.log("\n ♻️ Trader is now going to move the price. ♻️ \n"); uint256 lastPriceAvg0 = uniTornPool.price0CumulativeLast(); uint256 lastPriceAvg1 = uniTornPool.price0CumulativeLast(); uint256 rn = now; address trader = address(bytes20(keccak256("trader"))); vm.deal(trader, 20 ether); vm.prank(trader); IWethDepositable(address(WETH)).deposit{ value: 19 ether }(); require(WETH.balanceOf(trader) == 19 ether, "deposit for weth"); vm.warp(rn + 2 days + 1 hours); vm.prank(trader); WETH.transfer(address(uniTornPool), 19 ether); uint256 expected = 5_600_000_000_000_000_000_000; bool worked; while (!worked) { try uniTornPool.swap(expected, 0, trader, new bytes(0)) { worked = true; } catch { expected /= 2; } } require(lastPriceAvg0 != uniTornPool.price0CumulativeLast(), "twap moving 0 fail"); require(lastPriceAvg1 != uniTornPool.price1CumulativeLast(), "twap moving 1 fail"); console2.log( "\n 💰 TORN in ETH after market op: 💰 ", WETH.balanceOf(address(uniTornPool)) * 1e18 / TORN.balanceOf(address(uniTornPool)), "\n" ); } function delimit() internal view { console2.log(); } function strcomp(string memory left, string memory right) internal pure returns (bool) { return keccak256(abi.encode(left)) == keccak256(abi.encode(right)); } function feeArrayForTesting_1() internal pure returns (uint256[] memory fees) { fees = new uint256[](3); fees[0] = 30; fees[1] = 30; fees[2] = 30; } } contract MinimumStakeOracle { RelayerRegistry public registry; constructor(RelayerRegistry _registry) public { registry = _registry; } function setAmount() public { registry.setMinimumTornStake(2000 ether); } }