first commit
This commit is contained in:
commit
1b1779070e
11
.gitignore
vendored
Normal file
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
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
9
README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Proposal 36
|
||||||
|
|
||||||
|
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
|
74
contracts/Proposal.sol
Normal file
74
contracts/Proposal.sol
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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 getNullifiedTotal(address payable[15] memory relayers) public view returns (uint256) {
|
||||||
|
uint256 nullifiedTotal;
|
||||||
|
|
||||||
|
for (uint8 i = 0; i < relayers.length; i++) {
|
||||||
|
nullifiedTotal += IRelayerRegistry(relayerRegistryProxyAddr).getRelayerBalance(relayers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullifiedTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeProposal() public {
|
||||||
|
IRelayerRegistryProxy relayerRegistryProxy = IRelayerRegistryProxy(relayerRegistryProxyAddr);
|
||||||
|
relayerRegistryProxy.upgradeTo(newRelayerRegistry);
|
||||||
|
|
||||||
|
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
|
||||||
|
];
|
||||||
|
|
||||||
|
IRelayerRegistry relayerRegistry = IRelayerRegistry(relayerRegistryProxyAddr);
|
||||||
|
|
||||||
|
uint256 nullifiedTotal = getNullifiedTotal(cheatingRelayers);
|
||||||
|
uint256 compensation = nullifiedTotal / 3;
|
||||||
|
|
||||||
|
for (uint i = 0; i < cheatingRelayers.length; i++) {
|
||||||
|
relayerRegistry.unregisterRelayer(cheatingRelayers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
relayerRegistry.registerRelayerAdmin(
|
||||||
|
0x4750BCfcC340AA4B31be7e71fa072716d28c29C5,
|
||||||
|
"reltor.eth",
|
||||||
|
19612626855788464787775 + compensation
|
||||||
|
);
|
||||||
|
relayerRegistry.registerRelayerAdmin(
|
||||||
|
0xa0109274F53609f6Be97ec5f3052C659AB80f012,
|
||||||
|
"relayer007.eth",
|
||||||
|
15242825423346070140850 + compensation
|
||||||
|
);
|
||||||
|
relayerRegistry.registerRelayerAdmin(
|
||||||
|
0xC49415493eB3Ec64a0F13D8AA5056f1CfC4ce35c,
|
||||||
|
"k-relayer.eth",
|
||||||
|
11850064862377598277981 + compensation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
362
contracts/RelayerRegistry.sol
Normal file
362
contracts/RelayerRegistry.sol
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
8
contracts/interfaces/ENS.sol
Normal file
8
contracts/interfaces/ENS.sol
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.12;
|
||||||
|
|
||||||
|
|
||||||
|
interface IENS {
|
||||||
|
function owner(bytes32 node) external view returns (address);
|
||||||
|
}
|
11
contracts/interfaces/FeeManager.sol
Normal file
11
contracts/interfaces/FeeManager.sol
Normal file
@ -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);
|
||||||
|
}
|
13
contracts/interfaces/RelayerRegistry.sol
Normal file
13
contracts/interfaces/RelayerRegistry.sol
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
function setOperator(address newOperator) external;
|
||||||
|
|
||||||
|
function getRelayerBalance(address relayer) external view returns (uint256);
|
||||||
|
}
|
7
contracts/interfaces/RelayerRegistryProxy.sol
Normal file
7
contracts/interfaces/RelayerRegistryProxy.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.12;
|
||||||
|
|
||||||
|
interface IRelayerRegistryProxy {
|
||||||
|
function upgradeTo(address newImplementation) external;
|
||||||
|
}
|
17
contracts/interfaces/TORN.sol
Normal file
17
contracts/interfaces/TORN.sol
Normal file
@ -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;
|
||||||
|
}
|
21
contracts/interfaces/TornadoInstance.sol
Normal file
21
contracts/interfaces/TornadoInstance.sol
Normal file
@ -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;
|
||||||
|
}
|
7
contracts/interfaces/TornadoStakingRewards.sol
Normal file
7
contracts/interfaces/TornadoStakingRewards.sol
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pragma solidity ^0.6.12;
|
||||||
|
|
||||||
|
interface ITornadoStakingRewards {
|
||||||
|
function addBurnRewards(uint256 amount) external;
|
||||||
|
}
|
38
contracts/libraries/EnsNamehash.sol
Normal file
38
contracts/libraries/EnsNamehash.sol
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
contracts/libraries/Permit.sol
Normal file
41
contracts/libraries/Permit.sol
Normal file
@ -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))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
diffs/RelayerRegistry.diff
Normal file
35
diffs/RelayerRegistry.diff
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
*** diffs/RelayerRegistryOld.sol 2023-11-03 20:19:30.256007000 +0000
|
||||||
|
--- contracts/RelayerRegistry.sol 2023-11-09 13:30:36.652006673 +0000
|
||||||
|
***************
|
||||||
|
*** 200,205 ****
|
||||||
|
--- 200,229 ----
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
+ * @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
|
338
diffs/RelayerRegistryOld.sol
Normal file
338
diffs/RelayerRegistryOld.sol
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
// 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 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;
|
||||||
|
}
|
||||||
|
}
|
39
hardhat.config.js
Normal file
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: 18534743,
|
||||||
|
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
18179
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "proposal-36",
|
||||||
|
"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
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(32);
|
||||||
|
console.log("Deployment confirmed with 32 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(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: [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;
|
||||||
|
});
|
288
test/RelayerRegistry.js
Normal file
288
test/RelayerRegistry.js
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
const { expect, assert } = require("chai");
|
||||||
|
const { ethers, network, config } = require("hardhat");
|
||||||
|
const {
|
||||||
|
sendMinimalStakeAmount,
|
||||||
|
resolveAddr,
|
||||||
|
getEnsRegistryContract,
|
||||||
|
getOldRelayerRegistryContract,
|
||||||
|
getRegisterRelayerParams,
|
||||||
|
getManyEth,
|
||||||
|
deployAndExecuteProposal,
|
||||||
|
unregisterRelayer,
|
||||||
|
getRelayerRegistryContract,
|
||||||
|
getRelayerBalance,
|
||||||
|
governanceAddr,
|
||||||
|
cheatingRelayers
|
||||||
|
} = 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 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 plus compensation to mistakenly unregistered relayers and register them again", async function() {
|
||||||
|
const relayerBalancesOld = await Promise.all(unregisteredAddrs.map(addr => getRelayerBalance(addr, blockBeforeProposal32Execution)));
|
||||||
|
const cheatingRelayersBalances = await Promise.all(cheatingRelayers.map((r) => getRelayerBalance(r)));
|
||||||
|
const compensationForOneRelayer = cheatingRelayersBalances.reduce((a, b) => a + b, 0n) / 3n;
|
||||||
|
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.map(b => b + compensationForOneRelayer));
|
||||||
|
expect(relayerBalancesNew).satisfy((b) => b.every((b) => b < 20000n * 10n ** 18n && b > 10000n * 10n ** 18n));
|
||||||
|
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");
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
1
test/abi/aggregator.abi.json
Normal file
1
test/abi/aggregator.abi.json
Normal file
@ -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"}]
|
1
test/abi/ensRegistry.abi.json
Normal file
1
test/abi/ensRegistry.abi.json
Normal file
@ -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"}]
|
1
test/abi/ensResolver.abi.json
Normal file
1
test/abi/ensResolver.abi.json
Normal file
File diff suppressed because one or more lines are too long
1
test/abi/ensWrapper.abi.json
Normal file
1
test/abi/ensWrapper.abi.json
Normal file
File diff suppressed because one or more lines are too long
1
test/abi/governance.abi.json
Normal file
1
test/abi/governance.abi.json
Normal file
File diff suppressed because one or more lines are too long
696
test/abi/relayerRegistry.abi.json
Normal file
696
test/abi/relayerRegistry.abi.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
]
|
1
test/abi/relayerRegistryOld.abi.json
Normal file
1
test/abi/relayerRegistryOld.abi.json
Normal file
File diff suppressed because one or more lines are too long
1
test/abi/torn.abi.json
Normal file
1
test/abi/torn.abi.json
Normal file
File diff suppressed because one or more lines are too long
19
test/test
Normal file
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
|
196
test/utils.js
Normal file
196
test/utils.js
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
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 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,
|
||||||
|
cheatingRelayers
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user