2023-11-03 22:31:24 +03:00
|
|
|
const { expect, assert } = require("chai");
|
|
|
|
const { ethers, network, config } = require("hardhat");
|
|
|
|
const {
|
|
|
|
sendMinimalStakeAmount,
|
|
|
|
resolveAddr,
|
|
|
|
getEnsRegistryContract,
|
|
|
|
getOldRelayerRegistryContract,
|
|
|
|
getRegisterRelayerParams,
|
|
|
|
getManyEth,
|
|
|
|
deployAndExecuteProposal,
|
|
|
|
unregisterRelayer,
|
|
|
|
getRelayerRegistryContract,
|
|
|
|
getRelayerBalance,
|
|
|
|
governanceAddr,
|
2023-11-04 15:32:26 +03:00
|
|
|
cheatingRelayers
|
2023-11-03 22:31:24 +03:00
|
|
|
} = 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]);
|
|
|
|
})
|
|
|
|
|
2023-11-04 15:32:26 +03:00
|
|
|
it("Should return before-proposal stake plus compensation to mistakenly unregistered relayers and register them again", async function() {
|
2023-11-03 22:31:24 +03:00
|
|
|
const relayerBalancesOld = await Promise.all(unregisteredAddrs.map(addr => getRelayerBalance(addr, blockBeforeProposal32Execution)));
|
2023-11-04 15:32:26 +03:00
|
|
|
const cheatingRelayersBalances = await Promise.all(cheatingRelayers.map((r) => getRelayerBalance(r)));
|
|
|
|
const compensationForOneRelayer = cheatingRelayersBalances.reduce((a, b) => a + b, 0n) / 3n;
|
2023-11-03 22:31:24 +03:00
|
|
|
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)));
|
|
|
|
|
2023-11-04 15:32:26 +03:00
|
|
|
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));
|
2023-11-03 22:31:24 +03:00
|
|
|
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");
|
|
|
|
})
|
|
|
|
})
|
2023-11-04 15:32:26 +03:00
|
|
|
|
|
|
|
describe("Operator", async function(){
|
|
|
|
it("Operator should be zero address after proposal", async function(){
|
|
|
|
const { relayerRegistryContract } = await deployAndExecuteProposal();
|
|
|
|
const operator = await relayerRegistryContract.operator();
|
|
|
|
|
|
|
|
expect(operator).to.be.equal("0x0000000000000000000000000000000000000000");
|
|
|
|
})
|
|
|
|
|
|
|
|
it("Operator should be set correctly by governance", async function(){
|
|
|
|
const { relayerRegistryContract } = await deployAndExecuteProposal();
|
|
|
|
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
|
|
|
|
await relayerRegistryContract.setOperator(me);
|
|
|
|
|
|
|
|
const operator = await relayerRegistryContract.operator();
|
|
|
|
|
|
|
|
expect(operator).to.be.equal(me);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("Operator should be able to nullify any relayer balance", async function(){
|
|
|
|
let { relayerRegistryContract } = await deployAndExecuteProposal();
|
|
|
|
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
|
|
|
|
await relayerRegistryContract.setOperator(me);
|
|
|
|
|
|
|
|
const meAsSigner = await ethers.getImpersonatedSigner(me);
|
|
|
|
relayerRegistryContract = await getRelayerRegistryContract(meAsSigner);
|
|
|
|
|
|
|
|
const realRelayer = await resolveAddr("torrelayer.eth");
|
|
|
|
expect(await relayerRegistryContract.getRelayerBalance(realRelayer)).to.be.greaterThan(10n ** 18n);
|
|
|
|
expect(await relayerRegistryContract.isRelayerRegistered(realRelayer, realRelayer)).to.be.equal(true);
|
|
|
|
|
|
|
|
await relayerRegistryContract.nullifyBalance(realRelayer);
|
|
|
|
expect(await relayerRegistryContract.getRelayerBalance(realRelayer)).to.be.equal(0);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("Governance also should be able to nullify any relayer balance", async function(){
|
|
|
|
const { relayerRegistryContract } = await deployAndExecuteProposal();
|
|
|
|
const realRelayer = await resolveAddr("torrelayer.eth");
|
|
|
|
expect(await relayerRegistryContract.getRelayerBalance(realRelayer)).to.be.greaterThan(10n ** 18n);
|
|
|
|
|
|
|
|
await relayerRegistryContract.nullifyBalance(realRelayer);
|
|
|
|
expect(await relayerRegistryContract.getRelayerBalance(realRelayer)).to.be.equal(0);
|
|
|
|
})
|
|
|
|
|
|
|
|
it("No one except governance and operator should be able to nullify relayers balance", async function(){
|
|
|
|
let { relayerRegistryContract } = await deployAndExecuteProposal();
|
|
|
|
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
|
|
|
|
|
|
|
|
const realRelayer = await resolveAddr("torrelayer.eth");
|
|
|
|
const relayerBalance = await relayerRegistryContract.getRelayerBalance(realRelayer)
|
|
|
|
expect(relayerBalance).to.be.greaterThan(10n ** 18n);
|
|
|
|
|
|
|
|
const meAsSigner = await ethers.getImpersonatedSigner(me);
|
|
|
|
relayerRegistryContract = await getRelayerRegistryContract(meAsSigner);
|
|
|
|
await expect(relayerRegistryContract.nullifyBalance(realRelayer)).to.be.revertedWith("only governance or operator");
|
|
|
|
|
|
|
|
expect(await relayerRegistryContract.getRelayerBalance(realRelayer)).to.be.equal(relayerBalance)
|
|
|
|
})
|
|
|
|
|
|
|
|
it("Only governance can set operator", async function(){
|
|
|
|
await deployAndExecuteProposal();
|
|
|
|
const me = "0xeb3E49Af2aB5D5D0f83A9289cF5a34d9e1f6C5b4";
|
|
|
|
const meAsSigner = await ethers.getImpersonatedSigner(me);
|
|
|
|
const relayerRegistryContract = await getRelayerRegistryContract(meAsSigner);
|
|
|
|
|
|
|
|
await expect(relayerRegistryContract.setOperator(me)).to.be.revertedWith("only governance")
|
|
|
|
})
|
|
|
|
})
|
2023-11-03 22:31:24 +03:00
|
|
|
});
|