Compare commits

..

2 Commits

7 changed files with 1061 additions and 122 deletions

@ -130,7 +130,11 @@ contract RelayerRegistry is Initializable, EnsResolve {
address[] calldata workersToRegister address[] calldata workersToRegister
) internal { ) internal {
bytes32 ensHash = bytes(ensName).namehash(); bytes32 ensHash = bytes(ensName).namehash();
require(relayer == ens.owner(ensHash), "only ens owner"); 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"); require(workers[relayer] == address(0), "cant register again");
RelayerState storage metadata = relayers[relayer]; RelayerState storage metadata = relayers[relayer];

@ -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))
)
);
}
}

@ -1,27 +1,33 @@
require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config() require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */ /** @type import('hardhat/config').HardhatUserConfig */
module.exports = { module.exports = {
solidity: "0.6.12", solidity: "0.6.12",
mocha: {
timeout: 100000000,
},
networks: { networks: {
mainnet: { mainnet: {
url: "https://eth.llamarpc.com" url: "https://eth.llamarpc.com",
accounts: [process.env.REAL_PK],
}, },
testnet: { testnet: {
url: "https://ethereum-goerli.publicnode.com", url: "https://ethereum-goerli.publicnode.com",
accounts: [process.env.TEST_PK] accounts: [process.env.TEST_PK],
}, },
hardhat: { hardhat: {
forking: { forking: {
url: "https://eth.llamarpc.com", url: "https://eth.llamarpc.com",
enabled: true, enabled: true,
blockNumber: 18391425, blockNumber: 18431526,
accounts: [process.env.REAL_PK],
}, },
chainId: 1 chainId: 1,
} accounts: [{ privateKey: process.env.REAL_PK, balance: "10000000000000000000000000000000" }],
},
}, },
etherscan: { etherscan: {
apiKey: process.env.ETHERSCAN_KEY apiKey: process.env.ETHERSCAN_KEY,
} },
}; };

@ -1,89 +1,54 @@
const { time, loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const { expect, assert } = require("chai"); const { expect, assert } = require("chai");
const { ethers, network } = require("hardhat"); const { ethers, network, config } = require("hardhat");
const { relayerRegistry } = require("torn-token"); const {
sendMinimalStakeAmount,
resolveAddr,
getEnsRegistryContract,
getOldRelayerRegistryContract,
getRegisterRelayerParams,
getManyEth,
deployAndExecuteProposal,
unregisterRelayer,
getRelayerRegistryContract,
} = require("./utils");
describe("Registry update", function () { describe("Registry update", function () {
async function deployAndExecuteFixture() { beforeEach(async function () {
const RelayerRegistryFactory = await ethers.getContractFactory("RelayerRegistry"); await network.provider.request({
const constructorArgs = [ method: "hardhat_reset",
"0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", params: [
"0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce", {
"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", forking: {
"0x5B3f656C80E8ddb9ec01Dd9018815576E9238c29", jsonRpcUrl: config.networks.hardhat.forking.url,
"0x5f6c97C6AD7bdd0AE7E0Dd4ca33A4ED3fDabD4D7", blockNumber: config.networks.hardhat.forking.blockNumber,
]; },
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 network.provider.send("hardhat_setBalance", [
bigStakerAddr,
"0x116663015358483537"
]);
const stakerSigner = await ethers.getImpersonatedSigner(bigStakerAddr);
const governanceAddr = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
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 relayerRegistryProxyAddr = "0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2";
const relayerRegistryContract = await ethers.getContractAt("RelayerRegistry", relayerRegistryProxyAddr, governanceSigner);
return { relayerRegistryProxyAddr, deployedRegistryAddr, relayerRegistryContract, governanceSigner };
}
async function unregisterRelayer(ensNameOrAddress){
const { relayerRegistryContract } = await loadFixture(deployAndExecuteFixture);
const relayerAddr = ethers.isAddress(ensNameOrAddress) ? ensNameOrAddress : resolveAddr(ensNameOrAddress);
await relayerRegistryContract.unregisterRelayer(relayerAddr);
}
async function resolveAddr(ensName){
const ensNode = ethers.namehash(ensName);
const ensAddr = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
const registryContract = await ethers.getContractAt(require("./abi/ensRegistry.abi.json"), ensAddr);
const resolverAddr = await registryContract.resolver(ensNode);
const resolverContract = await ethers.getContractAt(require("./abi/ensResolver.abi.json"), resolverAddr);
return await resolverContract.addr(ensNode);
}
async function getOldRelayerRegistryContractFixture(){
const relayerRegistryProxyAddr = "0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2";
return await ethers.getContractAt(require("./abi/relayerRegistryOld.abi.json"), relayerRegistryProxyAddr);
}
it("Implementation address should be updated", async function () {
await loadFixture(getOldRelayerRegistryContractFixture);
const { relayerRegistryProxyAddr, deployedRegistryAddr } = await loadFixture(deployAndExecuteFixture);
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("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);
it("Uregister relayer function should work", async function(){ 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 testRelayerAddr = await resolveAddr("first-relayer.eth");
const relayerRegistryOldContract = await loadFixture(getOldRelayerRegistryContractFixture); const relayerRegistryOldContract = await getOldRelayerRegistryContract();
let isRelayer = await relayerRegistryOldContract.isRelayer(testRelayerAddr); let isRelayer = await relayerRegistryOldContract.isRelayer(testRelayerAddr);
let isRelayerRegistered = await relayerRegistryOldContract.isRelayerRegistered(testRelayerAddr, testRelayerAddr); let isRelayerRegistered = await relayerRegistryOldContract.isRelayerRegistered(testRelayerAddr, testRelayerAddr);
expect(isRelayer).to.equal(true); expect(isRelayer).to.equal(true);
expect(isRelayerRegistered).to.equal(true); expect(isRelayerRegistered).to.equal(true);
const { relayerRegistryContract } = await loadFixture(deployAndExecuteFixture); const { relayerRegistryContract } = await deployAndExecuteProposal();
await unregisterRelayer(testRelayerAddr); await unregisterRelayer(testRelayerAddr);
isRelayer = await relayerRegistryContract.isRelayer(testRelayerAddr); isRelayer = await relayerRegistryContract.isRelayer(testRelayerAddr);
@ -91,35 +56,53 @@ describe("Registry update", function () {
expect(isRelayer).to.equal(false); expect(isRelayer).to.equal(false);
expect(isRelayerRegistered).to.equal(false); expect(isRelayerRegistered).to.equal(false);
}) });
it("Unregistered relayer should have zero balance", async function(){ it("Unregistered relayer should have zero balance", async function () {
const testRelayerAddr = await resolveAddr("first-relayer.eth"); const testRelayerAddr = await resolveAddr("first-relayer.eth");
const { relayerRegistryContract } = await loadFixture(deployAndExecuteFixture); const { relayerRegistryContract } = await deployAndExecuteProposal();
await unregisterRelayer(testRelayerAddr); await unregisterRelayer(testRelayerAddr);
const relayerBalance = await relayerRegistryContract.getRelayerBalance(testRelayerAddr); const relayerBalance = await relayerRegistryContract.getRelayerBalance(testRelayerAddr);
expect(relayerBalance).to.equal(0); expect(relayerBalance).to.equal(0);
}) });
it("Tornado router address should be valid", async function(){ it("Unregister function should revert if called not by Governance", async function () {
const relayerRegistryOldContract = await loadFixture(getOldRelayerRegistryContractFixture); 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 oldRegistryRouterAddress = await relayerRegistryOldContract.tornadoRouter();
const { relayerRegistryContract } = await loadFixture(deployAndExecuteFixture); const { relayerRegistryContract } = await deployAndExecuteProposal();
expect(await relayerRegistryContract.tornadoRouter()).to.equal(oldRegistryRouterAddress); expect(await relayerRegistryContract.tornadoRouter()).to.equal(oldRegistryRouterAddress);
}) });
it("Aggregator contract data for unregistered relayers should be valid", async function(){ it("Aggregator contract data for unregistered relayers should be valid", async function () {
const aggregatorAddr = "0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49"; const aggregatorAddr = "0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49";
const aggregatorContract = await ethers.getContractAt(require("./abi/aggregator.abi.json"), aggregatorAddr); const aggregatorContract = await ethers.getContractAt(require("./abi/aggregator.abi.json"), aggregatorAddr);
const notRelayer = "🦋️-effect.eth"; const notRelayer = "🦋️-effect.eth";
const testRelayer = "first-relayer.eth"; const testRelayer = "first-relayer.eth";
const callAggr = async () => await aggregatorContract.relayersData([notRelayer, testRelayer].map(ethers.namehash), ["mainnet-tornado", "bsc-tornado"]); const callAggr = async () =>
await aggregatorContract.relayersData([notRelayer, testRelayer].map(ethers.namehash), [
"mainnet-tornado",
"bsc-tornado",
]);
const oldData = await callAggr(); const oldData = await callAggr();
await loadFixture(deployAndExecuteFixture); await deployAndExecuteProposal();
await unregisterRelayer(testRelayer); await unregisterRelayer(testRelayer);
const newData = await callAggr(); const newData = await callAggr();
@ -129,47 +112,88 @@ describe("Registry update", function () {
expect(newData[1][1]).to.equal(0); expect(newData[1][1]).to.equal(0);
expect(oldData[1][2]).to.equal(true); expect(oldData[1][2]).to.equal(true);
expect(newData[1][2]).to.equal(false); expect(newData[1][2]).to.equal(false);
}) });
it("Cheating relayers should be unregistered", async function(){ it("Cheating relayers should be unregistered", async function () {
const cheatingRelayers = await Promise.all([ const cheatingRelayers = await Promise.all(
"available-reliable-relayer.eth", [
"0xtornadocash.eth", "available-reliable-relayer.eth",
"0xtorn365.eth", "0xtornadocash.eth",
"tornrelayers.eth", "0xtorn365.eth",
"moon-relayer.eth" "tornrelayers.eth",
].map(resolveAddr)); "moon-relayer.eth",
].map(resolveAddr),
);
const relayerRegistryOldContract = await loadFixture(getOldRelayerRegistryContractFixture); const relayerRegistryOldContract = await getOldRelayerRegistryContract();
let areRegistered = await Promise.all(cheatingRelayers.map(r => relayerRegistryOldContract.isRelayer(r))); let areRegistered = await Promise.all(cheatingRelayers.map((r) => relayerRegistryOldContract.isRelayer(r)));
let balances = await Promise.all(cheatingRelayers.map(r => relayerRegistryOldContract.getRelayerBalance(r))); let balances = await Promise.all(cheatingRelayers.map((r) => relayerRegistryOldContract.getRelayerBalance(r)));
expect(areRegistered).satisfy(v => v.every(v => v === true)); expect(areRegistered).satisfy((v) => v.every((v) => v === true));
expect(balances).satisfy(v => v.every(v => v >= 0n)); expect(balances).satisfy((v) => v.every((v) => v >= 0n));
const { relayerRegistryContract } = await loadFixture(deployAndExecuteFixture); const { relayerRegistryContract } = await deployAndExecuteProposal();
areRegistered = await Promise.all(cheatingRelayers.map(r => relayerRegistryContract.isRelayer(r))); areRegistered = await Promise.all(cheatingRelayers.map((r) => relayerRegistryContract.isRelayer(r)));
balances = await Promise.all(cheatingRelayers.map(r => relayerRegistryContract.getRelayerBalance(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(){ expect(areRegistered).satisfy((v) => v.every((v) => v === false));
const { relayerRegistryContract, governanceSigner } = await loadFixture(deployAndExecuteFixture); 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 relayerEns = "moon-relayer.eth";
const unregisteredRelayer = await resolveAddr(relayerEns); const unregisteredRelayer = await resolveAddr(relayerEns);
const tornContract = await ethers.getContractAt(require("./abi/torn.abi.json"), "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C", governanceSigner); const toStake = await sendMinimalStakeAmount(unregisteredRelayer);
const toStake = 2000n * 10n ** 18n;
await tornContract.transfer(unregisteredRelayer, toStake);
const relayerSigner = await ethers.getImpersonatedSigner(unregisteredRelayer); const relayerSigner = await ethers.getImpersonatedSigner(unregisteredRelayer);
await relayerRegistryContract.connect(relayerSigner).register(relayerEns, toStake, []); await relayerRegistryContract.connect(relayerSigner).register(relayerEns, toStake, []);
const isRelayerRegistered = await relayerRegistryContract.isRelayerRegistered(unregisteredRelayer, unregisteredRelayer); const isRelayerRegistered = await relayerRegistryContract.isRelayerRegistered(
unregisteredRelayer,
unregisteredRelayer,
);
const relayerBalance = await relayerRegistryContract.getRelayerBalance(unregisteredRelayer); const relayerBalance = await relayerRegistryContract.getRelayerBalance(unregisteredRelayer);
expect(isRelayerRegistered).to.be.equal(true); expect(isRelayerRegistered).to.be.equal(true);
expect(relayerBalance).to.be.equal(toStake); 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);
});
});
}); });

File diff suppressed because one or more lines are too long

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

167
test/utils.js Normal file

@ -0,0 +1,167 @@
const { ethers, network } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
const ensAddr = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
const relayerRegistryProxyAddr = "0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2";
const tornAddr = "0x77777FeDdddFfC19Ff86DB637967013e6C6A116C";
const governanceAddr = "0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce";
async function getPermitSignature(signer, tokenContract, spender, value, deadline) {
const [nonce, name, version, chainId] = await Promise.all([
tokenContract.nonces(signer.address),
tokenContract.name(),
"1",
tokenContract.chainID(),
]);
const domain = {
name,
version,
chainId,
verifyingContract: tornAddr,
};
const types = {
Permit: [
{
name: "owner",
type: "address",
},
{
name: "spender",
type: "address",
},
{
name: "value",
type: "uint256",
},
{
name: "nonce",
type: "uint256",
},
{
name: "deadline",
type: "uint256",
},
],
};
const values = {
owner: signer.address,
spender,
value,
nonce,
deadline,
};
const signature = await signer.signTypedData(domain, types, values);
return ethers.Signature.from(signature);
}
async function getRegisterRelayerParams(ensDomain, additionalStake = 0, workerAddrs = []) {
const relayerAddr = await resolveAddr(ensDomain);
const toStake = await sendMinimalStakeAmount(relayerAddr, BigInt(additionalStake));
const tornContract = await ethers.getContractAt(require("./abi/torn.abi.json"), tornAddr);
const relayerSigner = await ethers.getSigner(relayerAddr);
const { v, r, s } = await getPermitSignature(
relayerSigner,
tornContract,
relayerRegistryProxyAddr,
toStake,
ethers.MaxUint256,
);
return [ensDomain, toStake, workerAddrs, relayerAddr, ethers.MaxUint256, v, r, s];
}
async function getEnsRegistryContract() {
return await ethers.getContractAt(require("./abi/ensRegistry.abi.json"), ensAddr);
}
async function resolveAddr(ensName) {
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 = [
tornAddr,
governanceAddr,
ensAddr,
"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);
}
module.exports = {
sendMinimalStakeAmount,
getEnsRegistryContract,
getOldRelayerRegistryContract,
getRelayerRegistryContract,
getPermitSignature,
resolveAddr,
getManyEth,
unregisterRelayer,
deployAndExecuteProposal,
getRegisterRelayerParams,
};