first commit

This commit is contained in:
ButterflyEffect 2023-11-10 16:30:48 +00:00
commit 79944ff21e
20 changed files with 19417 additions and 0 deletions

11
.gitignore vendored Normal file

@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types
# Hardhat files
cache
artifacts

19
.prettierrc Normal file

@ -0,0 +1,19 @@
{
"overrides": [
{
"files": "*.sol",
"options": {
"printWidth": 120,
"tabWidth": 4,
"useTabs": false,
"singleQuote": false,
"bracketSpacing": true
}
},
{"files": "*.js", "options": {
"bracketSpacing": true,
"printWidth": 120
}}
],
"bracketSpacing": true
}

9
README.md Normal file

@ -0,0 +1,9 @@
# Proposal 37
Unregister all cheating relayers
Deploy: `npx hardhat run --network mainnet script/deploy.js`
Tests: `npm run test`
Dont forget to fill env file

48
contracts/Proposal.sol Normal file

@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IRelayerRegistry } from "./interfaces/RelayerRegistry.sol";
import { ITornadoStakingRewards } from "./interfaces/TornadoStakingRewards.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract Proposal {
address public constant relayerRegistryAddr = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
address public constant stakingRewardsAddr = 0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29;
address public constant tornAddr = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
address public constant me = 0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4;
function executeProposal() public {
IRelayerRegistry relayerRegistry = IRelayerRegistry(relayerRegistryAddr);
address payable[15] memory cheatingRelayers = [
0x853281B7676DFB66B87e2f26c9cB9D10Ce883F37, // available-reliable-relayer.eth,
0x0000208a6cC0299dA631C08fE8c2EDe435Ea83B8, // 0xtornadocash.eth,
0xaaaaD0b504B4CD22348C4Db1071736646Aa314C6, // tornrelayers.eth
0x36DD7b862746fdD3eDd3577c8411f1B76FDC2Af5, // tornado-crypto-bot-exchange.eth
0x5007565e69E5c23C278c2e976beff38eF4D27B3d, // official-tornado.eth
0xa42303EE9B2eC1DB7E2a86Ed6C24AF7E49E9e8B9, // relayer-tornado.eth
0x18F516dD6D5F46b2875Fd822B994081274be2a8b, // torn69.eth
0x2ffAc4D796261ba8964d859867592B952b9FC158, // safe-tornado.eth
0x12D92FeD171F16B3a05ACB1542B40648E7CEd384, // torn-relayers.eth
0x996ad81FD83eD7A87FD3D03694115dff19db0B3b, // secure-tornado.eth
0x7853E027F37830790685622cdd8685fF0c8255A2, // tornado-secure.eth
0xf0D9b969925116074eF43e7887Bcf035Ff1e7B19, // lowfee-relayer.eth
0xEFa22d23de9f293B11e0c4aC865d7b440647587a, // tornado-relayer.eth
0x14812AE927e2BA5aA0c0f3C0eA016b3039574242, // pls-im-poor.eth
0x87BeDf6AD81A2907633Ab68D02c44f0415bc68C1 // tornrelayer.eth
];
uint256 nullifiedTotal = 0;
for (uint i = 0; i < cheatingRelayers.length; i++) {
nullifiedTotal += relayerRegistry.getRelayerBalance(cheatingRelayers[i]);
relayerRegistry.unregisterRelayer(cheatingRelayers[i]);
}
ITornadoStakingRewards(stakingRewardsAddr).withdrawTorn(nullifiedTotal);
// Deployment and execution cost
IERC20(tornAddr).transfer(me, 30 ether);
}
}

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IRelayerRegistry {
function unregisterRelayer(address relayer) external;
function getRelayerBalance(address relayer) external view returns (uint256);
}

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface ITornadoStakingRewards {
function withdrawTorn(uint256 amount) external;
}

39
hardhat.config.js Normal file

@ -0,0 +1,39 @@
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.6.12",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
mocha: {
timeout: 100000000,
},
networks: {
mainnet: {
url: "https://eth.llamarpc.com",
accounts: [process.env.REAL_PK],
},
testnet: {
url: "https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161",
accounts: [process.env.TEST_PK],
},
hardhat: {
forking: {
url: "https://eth.llamarpc.com",
enabled: true,
blockNumber: 18486955,
accounts: [process.env.REAL_PK],
},
chainId: 1,
accounts: [{ privateKey: process.env.REAL_PK, balance: "10000000000000000000000000000000" }],
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_KEY,
},
};

18179
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

43
package.json Normal file

@ -0,0 +1,43 @@
{
"name": "proposal-34",
"version": "1.0.0",
"description": "",
"main": "hardhat.config.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "npx hardhat test",
"deploy": "npx hardhat run --network mainnet scripts/deploy.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.2",
"@nomicfoundation/hardhat-ethers": "^3.0.4",
"@nomicfoundation/hardhat-network-helpers": "^1.0.9",
"@nomicfoundation/hardhat-toolbox": "^3.0.0",
"@nomicfoundation/hardhat-verify": "^1.1.1",
"@typechain/ethers-v6": "^0.4.3",
"@typechain/hardhat": "^8.0.3",
"@types/chai": "^4.3.9",
"@types/mocha": "^10.0.2",
"chai": "^4.3.10",
"chai-things": "^0.2.0",
"dotenv": "^16.3.1",
"ethers": "^6.8.0",
"hardhat": "^2.18.1",
"hardhat-gas-reporter": "^1.0.9",
"prettier": "^3.0.3",
"prettier-plugin-solidity": "^1.1.3",
"solidity-coverage": "^0.8.5",
"ts-node": "^10.9.1",
"typechain": "^8.3.2",
"typescript": "^5.2.2"
},
"dependencies": {
"@openzeppelin/contracts": "^3.2.0-rc.0",
"@openzeppelin/upgrades-core": "^1.30.1",
"torn-token": "^1.0.8"
}
}

32
scripts/deploy.js Normal file

@ -0,0 +1,32 @@
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// You can also run a script with `npx hardhat run <script>`. If you do that, Hardhat
// will compile your contracts, add the Hardhat Runtime Environment's members to the
// global scope, and execute the script.
const hre = require("hardhat");
const { ethers } = require("hardhat");
async function main() {
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy();
const deployedProposalAddr = await proposal.getAddress();
console.log(`Proposal contract deployed by address ${deployedProposalAddr}, waiting for blockchain confirmations...`);
tx = proposal.deploymentTransaction();
await tx.wait(32);
console.log("Deployment confirmed with 32 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify", {
address: deployedProposalAddr,
contract: "contracts/Proposal.sol:Proposal",
constructorArguments: [],
});
}
// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

@ -0,0 +1 @@
[{"inputs":[],"name":"ensRegistry","outputs":[{"internalType":"contract ENSRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract Governance","name":"governance","type":"address"}],"name":"getAllProposals","outputs":[{"components":[{"internalType":"address","name":"proposer","type":"address"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"forVotes","type":"uint256"},{"internalType":"uint256","name":"againstVotes","type":"uint256"},{"internalType":"bool","name":"executed","type":"bool"},{"internalType":"bool","name":"extended","type":"bool"},{"internalType":"enum Governance.ProposalState","name":"state","type":"uint8"}],"internalType":"struct GovernanceAggregator.Proposal[]","name":"proposals","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract Governance","name":"governance","type":"address"},{"internalType":"address[]","name":"accs","type":"address[]"}],"name":"getGovernanceBalances","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract Governance","name":"governance","type":"address"},{"internalType":"address","name":"account","type":"address"}],"name":"getUserData","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"latestProposalId","type":"uint256"},{"internalType":"uint256","name":"latestProposalIdState","type":"uint256"},{"internalType":"uint256","name":"timelock","type":"uint256"},{"internalType":"address","name":"delegatee","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"relayerRegistry","outputs":[{"internalType":"contract RelayerRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"_relayers","type":"bytes32[]"},{"internalType":"string[]","name":"_subdomains","type":"string[]"}],"name":"relayersData","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"bool","name":"isRegistered","type":"bool"},{"internalType":"string[20]","name":"records","type":"string[20]"}],"internalType":"struct Relayer[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}]

@ -0,0 +1 @@
[{"inputs":[{"internalType":"contract ENS","name":"_old","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"label","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"NewOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"resolver","type":"address"}],"name":"NewResolver","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"NewTTL","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"old","outputs":[{"internalType":"contract ENS","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"recordExists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"resolver","type":"address"}],"name":"setResolver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"}],"name":"setSubnodeOwner","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"label","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"resolver","type":"address"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setSubnodeRecord","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint64","name":"ttl","type":"uint64"}],"name":"setTTL","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"ttl","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,696 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_torn",
"type": "address"
},
{
"internalType": "address",
"name": "_governance",
"type": "address"
},
{
"internalType": "address",
"name": "_ens",
"type": "address"
},
{
"internalType": "address",
"name": "_staking",
"type": "address"
},
{
"internalType": "address",
"name": "_feeManager",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "minStakeAmount",
"type": "uint256"
}
],
"name": "MinimumStakeAmount",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "RelayerBalanceNullified",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "bytes32",
"name": "relayer",
"type": "bytes32"
},
{
"indexed": false,
"internalType": "string",
"name": "ensName",
"type": "string"
},
{
"indexed": false,
"internalType": "address",
"name": "relayerAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "stakedAmount",
"type": "uint256"
}
],
"name": "RelayerRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "RelayerUnregistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "tornadoRouter",
"type": "address"
}
],
"name": "RouterRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountStakeAdded",
"type": "uint256"
}
],
"name": "StakeAddedToRelayer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountBurned",
"type": "uint256"
}
],
"name": "StakeBurned",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "worker",
"type": "address"
}
],
"name": "WorkerRegistered",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "worker",
"type": "address"
}
],
"name": "WorkerUnregistered",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "domains",
"type": "bytes32[]"
}
],
"name": "bulkResolve",
"outputs": [
{
"internalType": "address[]",
"name": "result",
"type": "address[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "contract ITornadoInstance",
"name": "pool",
"type": "address"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "ens",
"outputs": [
{
"internalType": "contract IENS",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeManager",
"outputs": [
{
"internalType": "contract IFeeManager",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "getRelayerBalance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "getRelayerEnsHash",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "governance",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_tornadoRouter",
"type": "bytes32"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "toResolve",
"type": "address"
}
],
"name": "isRelayer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "address",
"name": "toResolve",
"type": "address"
}
],
"name": "isRelayerRegistered",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "minStakeAmount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "nullifyBalance",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "ensName",
"type": "string"
},
{
"internalType": "uint256",
"name": "stake",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "workersToRegister",
"type": "address[]"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "ensName",
"type": "string"
},
{
"internalType": "uint256",
"name": "stake",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "workersToRegister",
"type": "address[]"
},
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "registerPermit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "address",
"name": "worker",
"type": "address"
}
],
"name": "registerWorker",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "relayers",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "ensHash",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "node",
"type": "bytes32"
}
],
"name": "resolve",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "minAmount",
"type": "uint256"
}
],
"name": "setMinStakeAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "tornadoRouterAddress",
"type": "address"
}
],
"name": "setTornadoRouter",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "stake",
"type": "uint256"
}
],
"name": "stakeToRelayer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
},
{
"internalType": "uint256",
"name": "stake",
"type": "uint256"
},
{
"internalType": "address",
"name": "staker",
"type": "address"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "stakeToRelayerPermit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "staking",
"outputs": [
{
"internalType": "contract ITornadoStakingRewards",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "torn",
"outputs": [
{
"internalType": "contract TORN",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tornadoRouter",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "relayer",
"type": "address"
}
],
"name": "unregisterRelayer",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "worker",
"type": "address"
}
],
"name": "unregisterWorker",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "workers",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

File diff suppressed because one or more lines are too long

1
test/abi/torn.abi.json Normal file

File diff suppressed because one or more lines are too long

@ -0,0 +1,139 @@
const { expect, assert } = require("chai");
const { ethers, network, config } = require("hardhat");
const {
sendMinimalStakeAmount,
resolveAddr,
getOldRelayerRegistryContract,
getManyEth,
deployAndExecuteProposal,
unregisterRelayer,
governanceAddr,
cheatingRelayers,
} = require("./utils");
describe("Proposal", function () {
beforeEach(async function () {
await network.provider.request({
method: "hardhat_reset",
params: [
{
forking: {
jsonRpcUrl: config.networks.hardhat.forking.url,
blockNumber: config.networks.hardhat.forking.blockNumber,
},
},
],
});
});
describe("Unregister relayer", function () {
it("Uregister relayer function should work", async function () {
const testRelayerAddr = await resolveAddr("first-relayer.eth");
const relayerRegistryOldContract = await getOldRelayerRegistryContract();
let isRelayer = await relayerRegistryOldContract.isRelayer(testRelayerAddr);
let isRelayerRegistered = await relayerRegistryOldContract.isRelayerRegistered(testRelayerAddr, testRelayerAddr);
expect(isRelayer).to.equal(true);
expect(isRelayerRegistered).to.equal(true);
const { relayerRegistryContract } = await deployAndExecuteProposal();
await unregisterRelayer(testRelayerAddr);
isRelayer = await relayerRegistryContract.isRelayer(testRelayerAddr);
isRelayerRegistered = await relayerRegistryContract.isRelayerRegistered(testRelayerAddr, testRelayerAddr);
expect(isRelayer).to.equal(false);
expect(isRelayerRegistered).to.equal(false);
});
it("Unregistered relayer should have zero balance", async function () {
const testRelayerAddr = await resolveAddr("first-relayer.eth");
const { relayerRegistryContract } = await deployAndExecuteProposal();
await unregisterRelayer(testRelayerAddr);
const relayerBalance = await relayerRegistryContract.getRelayerBalance(testRelayerAddr);
expect(relayerBalance).to.equal(0);
});
it("Unregister function should revert if called not by Governance", async function () {
const { relayerRegistryContract } = await deployAndExecuteProposal();
const me = await resolveAddr("🦋️-effect.eth");
await getManyEth(me);
const meAsSigner = await ethers.getImpersonatedSigner(me);
const testRelayerAddr = await resolveAddr("first-relayer.eth");
const relayerRegistryWithMeAsSigner = relayerRegistryContract.connect(meAsSigner);
await expect(relayerRegistryWithMeAsSigner.unregisterRelayer(testRelayerAddr)).to.be.revertedWith(
"only governance",
);
});
it("Tornado router address should be valid", async function () {
const relayerRegistryOldContract = await getOldRelayerRegistryContract();
const oldRegistryRouterAddress = await relayerRegistryOldContract.tornadoRouter();
const { relayerRegistryContract } = await deployAndExecuteProposal();
expect(await relayerRegistryContract.tornadoRouter()).to.equal(oldRegistryRouterAddress);
});
it("Aggregator contract data for unregistered relayers should be valid", async function () {
const aggregatorAddr = "0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49";
const aggregatorContract = await ethers.getContractAt(require("./abi/aggregator.abi.json"), aggregatorAddr);
const notRelayer = "🦋️-effect.eth";
const testRelayer = "first-relayer.eth";
const callAggr = async () =>
await aggregatorContract.relayersData([notRelayer, testRelayer].map(ethers.namehash), [
"mainnet-tornado",
"bsc-tornado",
]);
const oldData = await callAggr();
await deployAndExecuteProposal();
await unregisterRelayer(testRelayer);
const newData = await callAggr();
assert.deepEqual(oldData[0], newData[0]);
expect(oldData[1][1]).to.greaterThan(500n * 10n ** 18n);
expect(newData[1][1]).to.equal(0);
expect(oldData[1][2]).to.equal(true);
expect(newData[1][2]).to.equal(false);
});
it("Cheating relayers should be unregistered", async function () {
const relayerRegistryOldContract = await getOldRelayerRegistryContract();
let areRegistered = await Promise.all(cheatingRelayers.map((r) => relayerRegistryOldContract.isRelayer(r)));
let balances = await Promise.all(cheatingRelayers.map((r) => relayerRegistryOldContract.getRelayerBalance(r)));
expect(areRegistered).satisfy((v) => v.every((v) => v === true));
expect(balances).satisfy((v) => v.every((v) => v >= 0n));
const { relayerRegistryContract } = await deployAndExecuteProposal();
areRegistered = await Promise.all(cheatingRelayers.map((r) => relayerRegistryContract.isRelayer(r)));
balances = await Promise.all(cheatingRelayers.map((r) => relayerRegistryContract.getRelayerBalance(r)));
expect(areRegistered).satisfy((v) => v.every((v) => v === false));
expect(balances).satisfy((v) => v.every((v) => v === 0n));
});
});
it("Unregistered relayers can register again", async function () {
const { relayerRegistryContract } = await deployAndExecuteProposal();
const relayerEns = "moon-relayer.eth";
const unregisteredRelayer = await resolveAddr(relayerEns);
const toStake = await sendMinimalStakeAmount(unregisteredRelayer);
const relayerSigner = await ethers.getImpersonatedSigner(unregisteredRelayer);
await relayerRegistryContract.connect(relayerSigner).register(relayerEns, toStake, []);
const isRelayerRegistered = await relayerRegistryContract.isRelayerRegistered(
unregisteredRelayer,
unregisteredRelayer,
);
const relayerBalance = await relayerRegistryContract.getRelayerBalance(unregisteredRelayer);
expect(isRelayerRegistered).to.be.equal(true);
expect(relayerBalance).to.be.equal(toStake);
});
});

179
test/utils.js Normal file

@ -0,0 +1,179 @@
const { ethers, network } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const ensAddr = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
const relayerRegistryProxyAddr = "0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2";
const tornAddr = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C";
const governanceAddr = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
const cheatingRelayers = [
"0x853281B7676DFB66B87e2f26c9cB9D10Ce883F37", // available-reliable-relayer.eth,
"0x0000208a6cC0299dA631C08fE8c2EDe435Ea83B8", // 0xtornadocash.eth,
"0xaaaaD0b504B4CD22348C4Db1071736646Aa314C6", // tornrelayers.eth
"0x36DD7b862746fdD3eDd3577c8411f1B76FDC2Af5", // tornado-crypto-bot-exchange.eth
"0x5007565e69E5c23C278c2e976beff38eF4D27B3d", // official-tornado.eth
"0xa42303EE9B2eC1DB7E2a86Ed6C24AF7E49E9e8B9", // relayer-tornado.eth
"0x18F516dD6D5F46b2875Fd822B994081274be2a8b", // torn69.eth
"0x2ffAc4D796261ba8964d859867592B952b9FC158", // safe-tornado.eth
"0x12D92FeD171F16B3a05ACB1542B40648E7CEd384", // torn-relayers.eth
"0x996ad81FD83eD7A87FD3D03694115dff19db0B3b", // secure-tornado.eth
"0x7853E027F37830790685622cdd8685fF0c8255A2", // tornado-secure.eth
"0xf0D9b969925116074eF43e7887Bcf035Ff1e7B19", // lowfee-relayer.eth
"0xEFa22d23de9f293B11e0c4aC865d7b440647587a", // tornado-relayer.eth
"0x14812AE927e2BA5aA0c0f3C0eA016b3039574242", // pls-im-poor.eth
"0x87BeDf6AD81A2907633Ab68D02c44f0415bc68C1", // tornrelayer.eth
];
async function getPermitSignature(signer, tokenContract, spender, value, deadline) {
const [nonce, name, version, chainId] = await Promise.all([
tokenContract.nonces(signer.address),
tokenContract.name(),
"1",
tokenContract.chainID(),
]);
const domain = {
name,
version,
chainId,
verifyingContract: tornAddr,
};
const types = {
Permit: [
{
name: "owner",
type: "address",
},
{
name: "spender",
type: "address",
},
{
name: "value",
type: "uint256",
},
{
name: "nonce",
type: "uint256",
},
{
name: "deadline",
type: "uint256",
},
],
};
const values = {
owner: signer.address,
spender,
value,
nonce,
deadline,
};
const signature = await signer.signTypedData(domain, types, values);
return ethers.Signature.from(signature);
}
async function getRegisterRelayerParams(ensDomain, additionalStake = 0, workerAddrs = []) {
const relayerAddr = await resolveAddr(ensDomain);
const toStake = await sendMinimalStakeAmount(relayerAddr, BigInt(additionalStake));
const tornContract = await ethers.getContractAt(require("./abi/torn.abi.json"), tornAddr);
const relayerSigner = await ethers.getSigner(relayerAddr);
const { v, r, s } = await getPermitSignature(
relayerSigner,
tornContract,
relayerRegistryProxyAddr,
toStake,
ethers.MaxUint256,
);
return [ensDomain, toStake, workerAddrs, relayerAddr, ethers.MaxUint256, v, r, s];
}
async function getEnsRegistryContract() {
return await ethers.getContractAt(require("./abi/ensRegistry.abi.json"), ensAddr);
}
async function resolveAddr(ensName) {
if (ethers.isAddress(ensName)) return ensName;
const ensNode = ethers.namehash(ensName);
const registryContract = await getEnsRegistryContract();
const resolverAddr = await registryContract.resolver(ensNode);
const resolverContract = await ethers.getContractAt(require("./abi/ensResolver.abi.json"), resolverAddr);
return await resolverContract.addr(ensNode);
}
async function getRelayerRegistryContract(signer) {
return await ethers.getContractAt("RelayerRegistry", relayerRegistryProxyAddr, signer);
}
async function getOldRelayerRegistryContract() {
return await ethers.getContractAt(require("./abi/relayerRegistryOld.abi.json"), relayerRegistryProxyAddr);
}
async function getManyEth(addr) {
await network.provider.send("hardhat_setBalance", [addr, "0x111166630153555558483537"]);
}
async function sendMinimalStakeAmount(addr, additionalStake = 0n) {
const relayerRegistryContract = await getRelayerRegistryContract();
const minRelayerStakeAmount = await relayerRegistryContract.minStakeAmount();
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const tornContract = await ethers.getContractAt(require("./abi/torn.abi.json"), tornAddr, governanceSigner);
await tornContract.transfer(addr, minRelayerStakeAmount + additionalStake);
return minRelayerStakeAmount + additionalStake;
}
async function deployAndExecuteProposal() {
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy();
const deployedProposalAddr = await proposal.getAddress();
const bigStakerAddr = "0xE4143f6377AEcd7193b9731d1C28815b57C4f5Ab";
await getManyEth(bigStakerAddr);
const stakerSigner = await ethers.getImpersonatedSigner(bigStakerAddr);
const governanceContract = await ethers.getContractAt(
require("./abi/governance.abi.json"),
governanceAddr,
stakerSigner,
);
await governanceContract.propose(deployedProposalAddr, "");
const proposalId = await governanceContract.proposalCount();
await time.increase(60 * 60);
await governanceContract.castVote(proposalId, true);
await time.increase(60 * 60 * 24 * 7 + 60);
await governanceContract.execute(proposalId);
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const relayerRegistryContract = await getRelayerRegistryContract(governanceSigner);
return { relayerRegistryProxyAddr, relayerRegistryContract, governanceSigner };
}
async function unregisterRelayer(ensNameOrAddress) {
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
const relayerRegistryContract = await getRelayerRegistryContract(governanceSigner);
const relayerAddr = ethers.isAddress(ensNameOrAddress) ? ensNameOrAddress : resolveAddr(ensNameOrAddress);
await relayerRegistryContract.unregisterRelayer(relayerAddr);
}
async function getRelayerBalance(relayerAddr, blockNumber) {
const relayerRegistryContract = await getRelayerRegistryContract();
return await relayerRegistryContract.getRelayerBalance(relayerAddr, { blockTag: blockNumber });
}
module.exports = {
sendMinimalStakeAmount,
getEnsRegistryContract,
getOldRelayerRegistryContract,
getRelayerRegistryContract,
getPermitSignature,
resolveAddr,
getManyEth,
unregisterRelayer,
deployAndExecuteProposal,
getRegisterRelayerParams,
getRelayerBalance,
governanceAddr,
cheatingRelayers,
};