@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
210 lines • 10.6 kB
JavaScript
import chai, { expect } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import hre from 'hardhat';
import { TimelockController__factory } from '@hyperlane-xyz/core';
import { assert } from '@hyperlane-xyz/utils';
import { TestChainName } from '../../consts/testChains.js';
import { MultiProvider } from '../../providers/MultiProvider.js';
import { randomAddress } from '../../test/testUtils.js';
import { EvmTimelockDeployer } from './EvmTimelockDeployer.js';
import { CANCELLER_ROLE, EMPTY_BYTES_32, EXECUTOR_ROLE, PROPOSER_ROLE, } from './constants.js';
chai.use(chaiAsPromised);
describe('EvmTimelockDeployer', async () => {
let multiProvider;
let deployer;
let signer;
const signerAddress = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
const otherSignerAddress = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8';
let otherSigner;
beforeEach(async () => {
[signer, otherSigner] = await hre.ethers.getSigners();
multiProvider = MultiProvider.createTestMultiProvider({ signer });
deployer = new EvmTimelockDeployer(multiProvider);
assert(signer.address === signerAddress, 'Expected signer.address to be equal signerAddress');
assert(otherSigner.address === otherSignerAddress, 'Expected otherSigner.address address to be equal otherSignerAddress');
});
describe('basic config', async () => {
const testCases = [
{
title: 'should deploy TimelockController with correct parameters',
config: {
minimumDelay: 3600,
proposers: [signerAddress],
executors: [signerAddress],
admin: signerAddress,
},
},
{
title: 'should deploy TimelockController with multiple proposers and executors',
config: {
minimumDelay: 7200,
proposers: [signerAddress, randomAddress()],
executors: [signerAddress, randomAddress()],
admin: signerAddress,
},
},
{
title: 'should deploy with minimum delay of 0',
config: {
minimumDelay: 0,
proposers: [signerAddress],
executors: [signerAddress],
admin: signerAddress,
},
},
];
for (const { title, config } of testCases) {
it(title, async () => {
const { TimelockController } = await deployer.deployContracts(TestChainName.test2, config);
const timelockAddress = TimelockController.address;
const timelock = TimelockController__factory.connect(timelockAddress, multiProvider.getProvider(TestChainName.test2));
expect(await timelock.getMinDelay()).to.equal(config.minimumDelay);
for (const proposer of config.proposers) {
expect(await timelock.hasRole(PROPOSER_ROLE, proposer)).to.be.true;
}
// proposers are also cancellers by default
for (const proposer of config.proposers) {
expect(await timelock.hasRole(CANCELLER_ROLE, proposer)).to.be.true;
}
assert(config.executors, 'Expected executors to be defined');
for (const executor of config.executors) {
expect(await timelock.hasRole(EXECUTOR_ROLE, executor)).to.be.true;
}
});
}
});
describe('multichain deployments', () => {
const config = {
minimumDelay: 3600,
proposers: [signerAddress],
executors: [signerAddress],
admin: signerAddress,
};
it('should not redeploy if contract already exists', async () => {
const chainName = TestChainName.test1;
// Deploy first time
await deployer.deployContracts(chainName, config);
const firstAddress = deployer.deployedContracts[chainName];
// Deploy again with same config
await deployer.deployContracts(chainName, config);
const secondAddress = deployer.deployedContracts[chainName];
expect(firstAddress).to.equal(secondAddress);
});
it('should deploy different contracts for different chains', async () => {
const { TimelockController: { address: address1 }, } = await deployer.deployContracts(TestChainName.test1, config);
const { TimelockController: { address: address2 }, } = await deployer.deployContracts(TestChainName.test2, config);
expect(address1).to.not.equal(address2);
expect(address1).to.exist;
expect(address2).to.exist;
});
});
it('should allow anyone to execute a transaction if no one is set in the input config', async () => {
const openExecutorRoleConfig = {
minimumDelay: 0,
proposers: [signerAddress],
admin: signerAddress,
};
const { TimelockController } = await deployer.deployContracts(TestChainName.test4, openExecutorRoleConfig);
const timelockAddress = TimelockController.address;
const timelock = TimelockController__factory.connect(timelockAddress, signer);
// If the 0 address has the executor role anyone can execute
expect(await timelock.hasRole(EXECUTOR_ROLE, hre.ethers.constants.AddressZero)).to.be.true;
// Test that someone who does not have the executor role can execute proposed transactions
expect(await timelock.hasRole(EXECUTOR_ROLE, otherSignerAddress)).to.be
.false;
const updatedDelay = 100;
const testTxData = TimelockController__factory.createInterface().encodeFunctionData('updateDelay', [updatedDelay]);
const scheduleTx = await timelock.schedule(timelockAddress, 0, testTxData, EMPTY_BYTES_32, EMPTY_BYTES_32, 0);
await scheduleTx.wait();
const otherSignerTimelockInstance = TimelockController__factory.connect(timelockAddress, otherSigner);
const executeTx = await otherSignerTimelockInstance.execute(timelockAddress, 0, testTxData, EMPTY_BYTES_32, EMPTY_BYTES_32);
await executeTx.wait();
expect(await timelock.getMinDelay()).to.equal(updatedDelay);
});
describe('canceller config', () => {
it('should deploy with the correct canceller config', async () => {
const proposer = randomAddress();
const cancellerConfig = {
minimumDelay: 30,
proposers: [proposer],
executors: [signerAddress],
cancellers: [randomAddress(), randomAddress()],
};
const { TimelockController } = await deployer.deployContracts(TestChainName.test4, cancellerConfig);
// proposer should be a proposer but not a canceller if not in canceller config
assert(cancellerConfig.proposers, 'Expected proposers to be defined');
for (const proposer of cancellerConfig.proposers) {
expect(await TimelockController.hasRole(PROPOSER_ROLE, proposer)).to.be
.true;
expect(await TimelockController.hasRole(CANCELLER_ROLE, proposer)).to.be
.false;
}
assert(cancellerConfig.cancellers, 'Expected cancellers to be defined');
for (const canceller of cancellerConfig.cancellers) {
expect(await TimelockController.hasRole(CANCELLER_ROLE, canceller)).to
.be.true;
}
});
it('should not remove a proposer that it is also a canceller', async () => {
const proposer = randomAddress();
const cancellerConfig = {
minimumDelay: 30,
proposers: [proposer],
executors: [signerAddress],
cancellers: [proposer, randomAddress()],
};
const { TimelockController } = await deployer.deployContracts(TestChainName.test4, cancellerConfig);
expect(await TimelockController.hasRole(PROPOSER_ROLE, proposer)).to.be
.true;
assert(cancellerConfig.cancellers, 'Expected cancellers to be defined');
for (const canceller of cancellerConfig.cancellers) {
expect(await TimelockController.hasRole(CANCELLER_ROLE, canceller)).to
.be.true;
}
});
});
describe('admin config', () => {
const proposer = randomAddress();
const customAdmin = randomAddress();
const testCases = [
{
title: 'should deploy TimelockController with the timelock as the only admin when an admin address is not provided',
config: {
minimumDelay: 1800,
proposers: [signerAddress],
executors: [signerAddress],
},
},
{
title: 'should not revoke the admin role from the deployer if it is the expected admin',
config: {
minimumDelay: 30,
proposers: [randomAddress()],
executors: [signerAddress],
cancellers: [randomAddress()],
admin: signerAddress,
},
},
{
title: 'should set the expected admin from the config after applying the changes',
config: {
minimumDelay: 30,
proposers: [proposer],
executors: [signerAddress],
cancellers: [proposer, randomAddress()],
admin: customAdmin,
},
},
];
for (const { title, config } of testCases) {
it(title, async () => {
const { TimelockController } = await deployer.deployContracts(TestChainName.test3, config);
const timelockAdminRoleHash = await TimelockController.TIMELOCK_ADMIN_ROLE();
// if an admin was set in the config we expect the timelock to be the admin
const expectedAdmin = config.admin ?? TimelockController.address;
expect(await TimelockController.hasRole(timelockAdminRoleHash, expectedAdmin)).to.be.true;
});
}
});
});
//# sourceMappingURL=EvmTimelockDeployer.hardhat-test.js.map