first commit

This commit is contained in:
ButterflyEffect 2023-11-03 19:31:24 +00:00
commit 5159e890be
29 changed files with 20134 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 34
Unregister all cheating relayers and restore mistakenly unregistered in last proposal
Deploy: `npx hardhat run --network mainnet script/deploy.js`
Tests: `npm run test`
Dont forget to fill env file

60
contracts/Proposal.sol Normal file

@ -0,0 +1,60 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { IRelayerRegistryProxy } from "./interfaces/RelayerRegistryProxy.sol";
import { IRelayerRegistry } from "./interfaces/RelayerRegistry.sol";
contract Proposal {
address public immutable newRelayerRegistry;
address public constant relayerRegistryProxyAddr = 0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2;
constructor(address _newRelayerRegistry) public {
newRelayerRegistry = _newRelayerRegistry;
}
function executeProposal() public {
IRelayerRegistryProxy relayerRegistryProxy = IRelayerRegistryProxy(relayerRegistryProxyAddr);
relayerRegistryProxy.upgradeTo(newRelayerRegistry);
address payable[14] 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
];
IRelayerRegistry relayerRegistry = IRelayerRegistry(relayerRegistryProxyAddr);
for (uint i = 0; i < cheatingRelayers.length; i++) {
relayerRegistry.unregisterRelayer(cheatingRelayers[i]);
}
relayerRegistry.registerRelayerAdmin(
0x4750BCfcC340AA4B31be7e71fa072716d28c29C5,
"reltor.eth",
19612626855788464787775
);
relayerRegistry.registerRelayerAdmin(
0xa0109274F53609f6Be97ec5f3052C659AB80f012,
"relayer007.eth",
15242825423346070140850
);
relayerRegistry.registerRelayerAdmin(
0xC49415493eB3Ec64a0F13D8AA5056f1CfC4ce35c,
"k-relayer.eth",
11850064862377598277981
);
}
}

@ -0,0 +1,362 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/Initializable.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import { EnsResolve } from "torn-token/contracts/ENS.sol";
import { TORN } from "torn-token/contracts/TORN.sol";
import { IENS } from "./interfaces/ENS.sol";
import { IFeeManager } from "./interfaces/FeeManager.sol";
import { ITornadoInstance } from "./interfaces/TornadoInstance.sol";
import { ITornadoStakingRewards } from "./interfaces/TornadoStakingRewards.sol";
import { ENSNamehash } from "./libraries/EnsNamehash.sol";
struct RelayerState {
uint256 balance;
bytes32 ensHash;
}
/**
* @notice Registry contract, one of the main contracts of this protocol upgrade.
* The contract should store relayers' addresses and data attributed to the
* master address of the relayer. This data includes the relayers stake and
* his ensHash.
* A relayers master address has a number of subaddresses called "workers",
* these are all addresses which burn stake in communication with the proxy.
* If a relayer is not registered, he is not displayed on the frontend.
* @dev CONTRACT RISKS:
* - if setter functions are compromised, relayer metadata would be at risk, including the noted amount of his balance
* - if burn function is compromised, relayers run the risk of being unable to handle withdrawals
* - the above risk also applies to the nullify balance function
*
*/
contract RelayerRegistry is Initializable, EnsResolve {
using SafeMath for uint256;
using SafeERC20 for TORN;
using ENSNamehash for bytes;
TORN public immutable torn;
address public immutable governance;
IENS public immutable ens;
ITornadoStakingRewards public immutable staking;
IFeeManager public immutable feeManager;
address public tornadoRouter;
uint256 public minStakeAmount;
mapping(address => RelayerState) public relayers;
mapping(address => address) public workers;
event RelayerBalanceNullified(address relayer);
event WorkerRegistered(address relayer, address worker);
event WorkerUnregistered(address relayer, address worker);
event StakeAddedToRelayer(address relayer, uint256 amountStakeAdded);
event StakeBurned(address relayer, uint256 amountBurned);
event MinimumStakeAmount(uint256 minStakeAmount);
event RouterRegistered(address tornadoRouter);
event RelayerRegistered(bytes32 relayer, string ensName, address relayerAddress, uint256 stakedAmount);
event RelayerUnregistered(address relayer);
modifier onlyGovernance() {
require(msg.sender == governance, "only governance");
_;
}
modifier onlyTornadoRouter() {
require(msg.sender == tornadoRouter, "only proxy");
_;
}
modifier onlyRelayer(address sender, address relayer) {
require(workers[sender] == relayer, "only relayer");
_;
}
constructor(address _torn, address _governance, address _ens, address _staking, address _feeManager) public {
torn = TORN(_torn);
governance = _governance;
ens = IENS(_ens);
staking = ITornadoStakingRewards(_staking);
feeManager = IFeeManager(_feeManager);
}
/**
* @notice initialize function for upgradeability
* @dev this contract will be deployed behind a proxy and should not assign values at logic address,
* params left out because self explainable
*
*/
function initialize(bytes32 _tornadoRouter) external initializer {
tornadoRouter = resolve(_tornadoRouter);
}
/**
* @notice This function should register a master address and optionally a set of workeres for a relayer + metadata
* @dev Relayer can't steal other relayers workers since they are registered, and a wallet (msg.sender check) can always unregister itself
* @param ensName ens name of the relayer
* @param stake the initial amount of stake in TORN the relayer is depositing
*
*/
function register(string calldata ensName, uint256 stake, address[] calldata workersToRegister) external {
_register(msg.sender, ensName, stake, workersToRegister);
}
/**
* @dev Register function equivalent with permit-approval instead of regular approve.
*
*/
function registerPermit(
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister,
address relayer,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(relayer, address(this), stake, deadline, v, r, s);
_register(relayer, ensName, stake, workersToRegister);
}
function _register(
address relayer,
string calldata ensName,
uint256 stake,
address[] calldata workersToRegister
) internal {
bytes32 ensHash = bytes(ensName).namehash();
address domainOwner = ens.owner(ensHash);
address ensNameWrapper = 0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401;
require(domainOwner != ensNameWrapper, "only unwrapped ens domains");
require(relayer == domainOwner, "only ens domain owner");
require(workers[relayer] == address(0), "cant register again");
RelayerState storage metadata = relayers[relayer];
require(metadata.ensHash == bytes32(0), "registered already");
require(stake >= minStakeAmount, "!min_stake");
torn.safeTransferFrom(relayer, address(staking), stake);
emit StakeAddedToRelayer(relayer, stake);
metadata.balance = stake;
metadata.ensHash = ensHash;
workers[relayer] = relayer;
for (uint256 i = 0; i < workersToRegister.length; i++) {
address worker = workersToRegister[i];
_registerWorker(relayer, worker);
}
emit RelayerRegistered(ensHash, ensName, relayer, stake);
}
/**
* @notice This function should allow relayers to register more workeres
* @param relayer Relayer which should send message from any worker which is already registered
* @param worker Address to register
*
*/
function registerWorker(address relayer, address worker) external onlyRelayer(msg.sender, relayer) {
_registerWorker(relayer, worker);
}
function _registerWorker(address relayer, address worker) internal {
require(workers[worker] == address(0), "can't steal an address");
workers[worker] = relayer;
emit WorkerRegistered(relayer, worker);
}
/**
* @notice This function should allow anybody to unregister an address they own
* @dev designed this way as to allow someone to unregister themselves in case a relayer misbehaves
* - this should be followed by an action like burning relayer stake
* - there was an option of allowing the sender to burn relayer stake in case of malicious behaviour, this feature was not included in the end
* - reverts if trying to unregister master, otherwise contract would break. in general, there should be no reason to unregister master at all
*
*/
function unregisterWorker(address worker) external {
if (worker != msg.sender) require(workers[worker] == msg.sender, "only owner of worker");
require(workers[worker] != worker, "cant unregister master");
emit WorkerUnregistered(workers[worker], worker);
workers[worker] = address(0);
}
/**
* @notice This function should allow governance to unregister relayer
* @param relayer Address of the relayer
*
*/
function unregisterRelayer(address relayer) external onlyGovernance {
nullifyBalance(relayer);
delete relayers[relayer];
delete workers[relayer];
emit RelayerUnregistered(relayer);
}
/**
* @notice This function should allow Governance to register relayer without real token transfer and permits
* @dev designed this way as to allow Governance register mistakenly unregistered relayers (or bugged in other way) again
* - reverts if relayer is already registered only, otherwise contract would break
* - reverts if relayer does not own provided ens domain, do not check wrapper
* - do not check worker, if someone except provided relayer registered this address as a worker, just ignore it
* - do not check stake, because Governance need to have possibility to register relayer with stake lower than current min_stake
*/
function registerRelayerAdmin(address relayer, string calldata ensName, uint256 stake) external onlyGovernance {
bytes32 ensHash = bytes(ensName).namehash();
address domainOwner = ens.owner(ensHash);
require(relayer == domainOwner, "only ens domain owner");
require(workers[relayer] != relayer, "cant register again");
RelayerState storage metadata = relayers[relayer];
require(metadata.ensHash == bytes32(0), "registered already");
metadata.balance = stake;
metadata.ensHash = ensHash;
workers[relayer] = relayer;
emit RelayerRegistered(ensHash, ensName, relayer, stake);
}
/**
* @notice This function should allow anybody to stake to a relayer more TORN
* @param relayer Relayer main address to stake to
* @param stake Stake to be added to relayer
*
*/
function stakeToRelayer(address relayer, uint256 stake) external {
_stakeToRelayer(msg.sender, relayer, stake);
}
/**
* @dev stakeToRelayer function equivalent with permit-approval instead of regular approve.
* @param staker address from that stake is paid
*
*/
function stakeToRelayerPermit(
address relayer,
uint256 stake,
address staker,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
torn.permit(staker, address(this), stake, deadline, v, r, s);
_stakeToRelayer(staker, relayer, stake);
}
function _stakeToRelayer(address staker, address relayer, uint256 stake) internal {
require(workers[relayer] == relayer, "!registered");
torn.safeTransferFrom(staker, address(staking), stake);
relayers[relayer].balance = stake.add(relayers[relayer].balance);
emit StakeAddedToRelayer(relayer, stake);
}
/**
* @notice This function should burn some relayer stake on withdraw and notify staking of this
* @dev IMPORTANT FUNCTION:
* - This should be only called by the tornado proxy
* - Should revert if relayer does not call proxy from valid worker
* - Should not overflow
* - Should underflow and revert (SafeMath) on not enough stake (balance)
* @param sender worker to check sender == relayer
* @param relayer address of relayer who's stake is being burned
* @param pool instance to get fee for
*
*/
function burn(address sender, address relayer, ITornadoInstance pool) external onlyTornadoRouter {
address masterAddress = workers[sender];
if (masterAddress == address(0)) {
require(workers[relayer] == address(0), "Only custom relayer");
return;
}
require(masterAddress == relayer, "only relayer");
uint256 toBurn = feeManager.instanceFeeWithUpdate(pool);
relayers[relayer].balance = relayers[relayer].balance.sub(toBurn);
staking.addBurnRewards(toBurn);
emit StakeBurned(relayer, toBurn);
}
/**
* @notice This function should allow governance to set the minimum stake amount
* @param minAmount new minimum stake amount
*
*/
function setMinStakeAmount(uint256 minAmount) external onlyGovernance {
minStakeAmount = minAmount;
emit MinimumStakeAmount(minAmount);
}
/**
* @notice This function should allow governance to set a new tornado proxy address
* @param tornadoRouterAddress address of the new proxy
*
*/
function setTornadoRouter(address tornadoRouterAddress) external onlyGovernance {
tornadoRouter = tornadoRouterAddress;
emit RouterRegistered(tornadoRouterAddress);
}
/**
* @notice This function should allow governance to nullify a relayers balance
* @dev IMPORTANT FUNCTION:
* - Should nullify the balance
* - Adding nullified balance as rewards was refactored to allow for the flexibility of these funds (for gov to operate with them)
* @param relayer address of relayer who's balance is to nullify
*
*/
function nullifyBalance(address relayer) public onlyGovernance {
address masterAddress = workers[relayer];
require(relayer == masterAddress, "must be master");
relayers[masterAddress].balance = 0;
emit RelayerBalanceNullified(relayer);
}
/**
* @notice This function should check if a worker is associated with a relayer
* @param toResolve address to check
* @return true if is associated
*
*/
function isRelayer(address toResolve) external view returns (bool) {
return workers[toResolve] != address(0);
}
/**
* @notice This function should check if a worker is registered to the relayer stated
* @param relayer relayer to check
* @param toResolve address to check
* @return true if registered
*
*/
function isRelayerRegistered(address relayer, address toResolve) external view returns (bool) {
return workers[toResolve] == relayer;
}
/**
* @notice This function should get a relayers ensHash
* @param relayer address to fetch for
* @return relayer's ensHash
*
*/
function getRelayerEnsHash(address relayer) external view returns (bytes32) {
return relayers[workers[relayer]].ensHash;
}
/**
* @notice This function should get a relayers balance
* @param relayer relayer who's balance is to fetch
* @return relayer's balance
*
*/
function getRelayerBalance(address relayer) external view returns (uint256) {
return relayers[workers[relayer]].balance;
}
}

@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IENS {
function owner(bytes32 node) external view returns (address);
}

@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
import { ITornadoInstance } from "./TornadoInstance.sol";
interface IFeeManager {
function instanceFeeWithUpdate(
ITornadoInstance _instance
) external returns (uint160);
}

@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IRelayerRegistry {
function unregisterRelayer(address relayer) external;
function registerRelayerAdmin(address relayer, string calldata ensName, uint256 stake) external;
}

@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface IRelayerRegistryProxy {
function upgradeTo(address newImplementation) external;
}

@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface TORN {
function permit(
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function safeTransferFrom(address from, address to, uint256 value) external;
}

@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
interface ITornadoInstance {
function token() external view returns (address);
function denomination() external view returns (uint256);
function deposit(bytes32 commitment) external payable;
function withdraw(
bytes calldata proof,
bytes32 root,
bytes32 nullifierHash,
address payable recipient,
address payable relayer,
uint256 fee,
uint256 refund
) external payable;
}

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

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
/*
* @dev Solidity implementation of the ENS namehash algorithm.
*
* Warning! Does not normalize or validate names before hashing.
* Original version can be found here https://github.com/JonahGroendal/ens-namehash/
*/
library ENSNamehash {
function namehash(bytes memory domain) internal pure returns (bytes32) {
return namehash(domain, 0);
}
function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) {
if (domain.length <= i) return 0x0000000000000000000000000000000000000000000000000000000000000000;
uint256 len = labelLength(domain, i);
return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len)));
}
function labelLength(bytes memory domain, uint256 i) private pure returns (uint256) {
uint256 len;
while (i + len != domain.length && domain[i + len] != 0x2e) {
len++;
}
return len;
}
function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) {
require(offset + len <= data.length);
assembly {
ret := keccak256(add(add(data, 32), offset), len)
}
}
}

@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
contract Permit {
function getPermitMessage(
string memory tokenName,
address tokenAddress,
string memory version,
address owner,
address spender,
uint256 chainId,
uint256 nonce,
uint256 amount,
uint256 deadline
) public pure returns (bytes32) {
bytes32 permitTypehash = keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
uint16 permitFuncSelector = uint16(0x1901);
bytes32 domain = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(tokenName)),
keccak256(bytes(version)),
chainId,
tokenAddress
)
);
return
keccak256(
abi.encodePacked(
permitFuncSelector,
domain,
keccak256(abi.encode(permitTypehash, owner, spender, amount, nonce, deadline))
)
);
}
}

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://ethereum-goerli.publicnode.com",
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"
}
}

53
scripts/deploy.js Normal file

@ -0,0 +1,53 @@
// 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 RelayerRegistryFactory = await ethers.getContractFactory("RelayerRegistry");
const constructorArgs = ["0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29", "0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7"];
const relayerRegistry = await RelayerRegistryFactory.deploy(...constructorArgs);
const deployedRegistryAddr = await relayerRegistry.getAddress();
console.log(`Relayer registry contract deployed by address ${deployedRegistryAddr}, waiting for blockchain confirmations...`)
let tx = relayerRegistry.deploymentTransaction();
await tx.wait(6);
console.log("Deployment confirmed with 6 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify",
{
address: deployedRegistryAddr,
contract: "contracts/RelayerRegistry.sol:RelayerRegistry",
constructorArguments: constructorArgs
});
console.log("Relayer registry contract deployed and verified!\n\n");
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy(deployedRegistryAddr);
const deployedProposalAddr = await proposal.getAddress();
console.log(`Proposal contract deployed by address ${deployedProposalAddr}, waiting for blockchain confirmations...`);
tx = proposal.deploymentTransaction();
await tx.wait(6);
console.log("Deployment confirmed with 6 blocks, waiting for verification on Etherscan");
await hre.run("verify:verify",
{
address: deployedProposalAddr,
contract: "contracts/RegistryUpgradeProposal.sol:Proposal",
constructorArguments: [deployedRegistryAddr]
});
}
// 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;
});

301
test/RelayerRegistry.js Normal file

@ -0,0 +1,301 @@
const { expect, assert } = require("chai");
const { ethers, network, config } = require("hardhat");
const {
sendMinimalStakeAmount,
resolveAddr,
getEnsRegistryContract,
getOldRelayerRegistryContract,
getRegisterRelayerParams,
getManyEth,
deployAndExecuteProposal,
unregisterRelayer,
getRelayerRegistryContract,
getRelayerBalance,
governanceAddr,
} = require("./utils");
describe("Registry update", 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,
},
},
],
});
});
it("Implementation address should be updated", async function () {
const { relayerRegistryProxyAddr, deployedRegistryAddr } = await deployAndExecuteProposal();
const implementation = await ethers.provider.getStorage(
relayerRegistryProxyAddr,
"0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc",
);
const [implementationAddr] = new ethers.AbiCoder().decode(["address"], implementation);
expect(implementationAddr).to.equal(deployedRegistryAddr);
});
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 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
]
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);
});
describe("Fix ENS owner checks", async function () {
it("ENS owner of wrapped domain should be a wrapper", async function () {
const wrappedDomain = "butterfly-attractor.eth";
const realOwner = await resolveAddr(wrappedDomain);
const ensRegistry = await getEnsRegistryContract();
const wrapperOwner = await ensRegistry.owner(ethers.namehash(wrappedDomain));
expect(wrapperOwner).to.be.not.equal(realOwner);
});
it("Registering relayer with wrapped ENS domain should revert", async function () {
const { relayerRegistryContract } = await deployAndExecuteProposal();
const relayerWrappedEns = "butterfly-attractor.eth";
const registerParams = await getRegisterRelayerParams(relayerWrappedEns);
const [relayer] = await ethers.getSigners();
const relayerRegistry = relayerRegistryContract.connect(relayer);
await expect(relayerRegistry.registerPermit(...registerParams)).to.be.revertedWith("only unwrapped ens domains");
});
it("After ENS domain unwrapping owner can register relayer", async function () {
await deployAndExecuteProposal();
const ensWrapperAddr = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401";
const wrappedEnsDomain = "butterfly-attractor.eth";
const addr = await resolveAddr(wrappedEnsDomain);
const wrapperContract = await ethers.getContractAt(require("./abi/ensWrapper.abi.json"), ensWrapperAddr);
const labelhash = ethers.keccak256(ethers.toUtf8Bytes(wrappedEnsDomain.split(".")[0]));
await wrapperContract.unwrapETH2LD(labelhash, addr, addr);
const relayerSigner = await ethers.getSigner(addr);
const relayerRegistryContract = await getRelayerRegistryContract(relayerSigner);
const registerParams = await getRegisterRelayerParams(wrappedEnsDomain);
await relayerRegistryContract.registerPermit(...registerParams);
expect(await relayerRegistryContract.isRelayerRegistered(addr, addr)).to.be.equal(true);
});
});
describe("Restore relayers", async function(){
const blockBeforeProposal32Execution = 18484836;
const unregisteredByMistake = ["reltor.eth", "relayer007.eth", "k-relayer.eth"];
const unregisteredAddrs = ["0x4750BCfcC340AA4B31be7e71fa072716d28c29C5", "0xa0109274F53609f6Be97ec5f3052C659AB80f012", "0xC49415493eB3Ec64a0F13D8AA5056f1CfC4ce35c"];
it("Relayer ENS names links to mistakenly unregistered relayers", async function(){
const maybeUnregisteredAddrs = await Promise.all(unregisteredByMistake.map(resolveAddr));
expect(maybeUnregisteredAddrs.map(ethers.getAddress)).to.deep.equal(unregisteredAddrs);
})
it("Current balance of mistakenly unregistered relayers should be zero", async function(){
const balances = await Promise.all(unregisteredAddrs.map(addr => getRelayerBalance(addr)));
expect(balances).to.deep.equal([0, 0, 0]);
})
it("Mistakenly unregistered relayers should be really unregistered before proposal", async function(){
const relayerRegistryContract = await getRelayerRegistryContract();
const areWorkers = await Promise.all(unregisteredAddrs.map(addr => relayerRegistryContract.isRelayer(addr)));
const areRelayers = await Promise.all(unregisteredAddrs.map(addr => relayerRegistryContract.isRelayerRegistered(addr, addr)));
expect(areWorkers).to.deep.equal([false, false, false]);
expect(areRelayers).to.deep.equal([false, false, false]);
})
it("Should return before-proposal stake to mistakenly unregistered relayers and register them again", async function() {
const relayerBalancesOld = await Promise.all(unregisteredAddrs.map(addr => getRelayerBalance(addr, blockBeforeProposal32Execution)));
const { relayerRegistryContract } = await deployAndExecuteProposal();
const relayerBalancesNew = await Promise.all(unregisteredAddrs.map(addr => getRelayerBalance(addr)));
const areWorkers = await Promise.all(unregisteredAddrs.map(addr => relayerRegistryContract.isRelayer(addr)));
const areRelayers = await Promise.all(unregisteredAddrs.map(addr => relayerRegistryContract.isRelayerRegistered(addr, addr)));
expect(relayerBalancesNew).to.deep.equal(relayerBalancesOld);
expect(areWorkers).to.deep.equal([true, true, true]);
expect(areRelayers).to.deep.equal([true, true, true]);
})
it("Should register relayer even if someone steal address as a worker", async function(){
await deployAndExecuteProposal();
const realRelayer = await resolveAddr("torrelayer.eth");
const relayerSigner = await ethers.getImpersonatedSigner(realRelayer);
let relayerRegistryContract = await getRelayerRegistryContract(relayerSigner);
expect(await relayerRegistryContract.isRelayerRegistered(realRelayer, realRelayer)).to.be.equal(true);
const ensWrapperAddr = "0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401";
const wrappedEnsDomain = "butterfly-attractor.eth";
const addr = await resolveAddr(wrappedEnsDomain);
await relayerRegistryContract.registerWorker(realRelayer, addr); // Register my address as a worker
const wrapperContract = await ethers.getContractAt(require("./abi/ensWrapper.abi.json"), ensWrapperAddr);
const labelhash = ethers.keccak256(ethers.toUtf8Bytes(wrappedEnsDomain.split(".")[0]));
await wrapperContract.unwrapETH2LD(labelhash, addr, addr);
const myRelayerSigner = await ethers.getSigner(addr);
relayerRegistryContract = relayerRegistryContract.connect(myRelayerSigner);
const registerParams = await getRegisterRelayerParams(wrappedEnsDomain);
await expect(relayerRegistryContract.registerPermit(...registerParams)).to.be.revertedWith("cant register again");
const governanceSigner = await ethers.getImpersonatedSigner(governanceAddr);
relayerRegistryContract = relayerRegistryContract.connect(governanceSigner);
await relayerRegistryContract.registerRelayerAdmin(addr, wrappedEnsDomain, 2000n * 10n ** 18n);
expect(await relayerRegistryContract.isRelayer(addr)).to.be.equal(true);
expect(await relayerRegistryContract.isRelayerRegistered(addr, addr)).to.be.equal(true);
expect(await getRelayerBalance(addr)).to.be.equal(2000n * 10n ** 18n);
})
it("Should revert if trying to register existing relayer via Governance", async function(){
const { relayerRegistryContract } = await deployAndExecuteProposal();
const realRelayerDomain = "torrelayer.eth";
const realRelayer = await resolveAddr(realRelayerDomain);
await expect(relayerRegistryContract.registerRelayerAdmin(realRelayer, realRelayerDomain, 1)).to.be.revertedWith("cant register again")
})
it("Should revert if trying to register relayer with domain that he not own", async function(){
const { relayerRegistryContract } = await deployAndExecuteProposal();
const someDomain = "vitalik.eth";
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
await expect(relayerRegistryContract.registerRelayerAdmin(me, someDomain, 1)).to.be.revertedWith("only ens domain owner");
})
it("Admin register function should fail, if called not from Governance", async function(){
await deployAndExecuteProposal();
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
const meAsSigner = await ethers.getImpersonatedSigner(me);
const relayerRegistryContract = await getRelayerRegistryContract(meAsSigner);
await expect(relayerRegistryContract.registerRelayerAdmin(me, "butterfly-attractor.eth", 1)).to.be.revertedWith("only governance");
})
})
});

@ -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

19
test/test Normal file

@ -0,0 +1,19 @@
available-reliable-relayer.eth
0xtorn365.eth
0xtornadocash.eth
tornrelayers.eth
relayer-secure.eth
tornado-crypto-bot-exchange.eth
official-tornado.eth
relayer-tornado.eth
moon-relayer.eth
torn69.eth
safe-tornado.eth
thornadope.eth
torn-relayers.eth
secure-tornado.eth
tornado-secure.eth
lowfee-relayer.eth
tornado-relayer.eth
tornrelayer.eth
pls-im-poor.eth

177
test/utils.js Normal file

@ -0,0 +1,177 @@
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";
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 RelayerRegistryFactory = await ethers.getContractFactory("RelayerRegistry");
const constructorArgs = [
"0x77777FeDdddFfC19Ff86DB637967013e6C6A116C",
"0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce",
"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
"0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29",
"0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7",
];
const relayerRegistry = await RelayerRegistryFactory.deploy(...constructorArgs);
const deployedRegistryAddr = await relayerRegistry.getAddress();
const proposalFactory = await ethers.getContractFactory("Proposal");
const proposal = await proposalFactory.deploy(deployedRegistryAddr);
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 ethers.getContractAt(
"RelayerRegistry",
relayerRegistryProxyAddr,
governanceSigner,
);
return { relayerRegistryProxyAddr, deployedRegistryAddr, relayerRegistryContract, governanceSigner, constructorArgs };
}
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
};