@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
760 lines • 36 kB
JavaScript
import { expect } from 'chai';
import hre from 'hardhat';
import { assert, deepCopy, deepEquals, eqAddress, } from '@hyperlane-xyz/utils';
import { TestChainName, testChains } from '../consts/testChains.js';
import { TestCoreDeployer } from '../core/TestCoreDeployer.js';
import { HyperlaneProxyFactoryDeployer } from '../deploy/HyperlaneProxyFactoryDeployer.js';
import { HyperlaneIsmFactory } from '../ism/HyperlaneIsmFactory.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { DEFAULT_TOKEN_DECIMALS, hookTypesToFilter, randomAddress, randomHookConfig, randomInt, } from '../test/testUtils.js';
import { normalizeConfig } from '../utils/ism.js';
import { EvmHookModule } from './EvmHookModule.js';
import { HookType, MUTABLE_HOOK_TYPE, } from './types.js';
const hookTypes = Object.values(HookType);
describe('EvmHookModule', async () => {
const chain = TestChainName.test4;
let multiProvider;
let coreAddresses;
let signer;
let funder;
let proxyFactoryAddresses;
let factoryContracts;
let exampleRoutingConfig;
before(async () => {
[signer, funder] = await hre.ethers.getSigners();
multiProvider = MultiProvider.createTestMultiProvider({ signer });
const ismFactoryDeployer = new HyperlaneProxyFactoryDeployer(multiProvider);
const contractsMap = await ismFactoryDeployer.deploy(multiProvider.mapKnownChains(() => ({})));
// get addresses of factories for the chain
factoryContracts = contractsMap[chain];
proxyFactoryAddresses = Object.keys(factoryContracts).reduce((acc, key) => {
acc[key] =
contractsMap[chain][key].address;
return acc;
}, {});
// legacy HyperlaneIsmFactory is required to do a core deploy
const legacyIsmFactory = new HyperlaneIsmFactory(contractsMap, multiProvider);
// core deployer for tests
const testCoreDeployer = new TestCoreDeployer(multiProvider, legacyIsmFactory);
// mailbox and proxy admin for the core deploy
const { mailbox, proxyAdmin, validatorAnnounce } = (await testCoreDeployer.deployApp()).getContracts(chain);
coreAddresses = {
mailbox: mailbox.address,
proxyAdmin: proxyAdmin.address,
validatorAnnounce: validatorAnnounce.address,
};
// reusable for routing/fallback routing specific tests
exampleRoutingConfig = {
owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(),
domains: Object.fromEntries(testChains.map((c) => [
c,
{
type: HookType.MERKLE_TREE,
},
])),
type: HookType.FALLBACK_ROUTING,
fallback: { type: HookType.MERKLE_TREE },
};
});
// Helper method for create a new multiprovider with an impersonated account
async function impersonateAccount(account) {
await hre.ethers.provider.send('hardhat_impersonateAccount', [account]);
await funder.sendTransaction({
to: account,
value: hre.ethers.utils.parseEther('1.0'),
});
return MultiProvider.createTestMultiProvider({
signer: hre.ethers.provider.getSigner(account),
});
}
// Helper method to expect exactly N updates to be applied
async function expectTxsAndUpdate(hook, config, n) {
const txs = await hook.update(config);
expect(txs.length).to.equal(n);
for (const tx of txs) {
await multiProvider.sendTransaction(chain, tx);
}
}
// hook module and config for testing
let testHook;
let testConfig;
// expect that the hook matches the config after all tests
afterEach(async () => {
const normalizedDerivedConfig = normalizeConfig(await testHook.read());
const normalizedConfig = normalizeConfig(testConfig);
deepEquals(normalizedDerivedConfig, normalizedConfig);
});
// create a new Hook and verify that it matches the config
async function createHook(config) {
const hook = await EvmHookModule.create({
chain,
config,
proxyFactoryFactories: proxyFactoryAddresses,
coreAddresses,
multiProvider,
});
testHook = hook;
testConfig = config;
return { hook, initialHookAddress: hook.serialize().deployedHook };
}
describe('create', async () => {
// generate a random config for each hook type
const exampleHookConfigs = [
// include an address config
randomAddress(),
...hookTypes
// need to setup deploying/mocking IL1CrossDomainMessenger before this test can be enabled
.filter((hookType) => !hookTypesToFilter.includes(hookType))
// generate a random config for each hook type
.map((hookType) => {
return randomHookConfig(0, 1, hookType);
}),
];
// test deployment of each hookType, except OP_STACK and CUSTOM
// minimum depth only
for (const config of exampleHookConfigs) {
it(`deploys a hook of type ${typeof config === 'string' ? 'address' : config.type}`, async () => {
await createHook(config);
});
}
// manually include test for CUSTOM hook type
it('deploys a hook of type CUSTOM', async () => {
const config = randomAddress();
await createHook(config);
});
// random configs upto depth 2
for (let i = 0; i < 16; i++) {
it(`deploys a random hook config #${i}`, async () => {
// random config with depth 0-2
const config = randomHookConfig();
await createHook(config);
});
}
// manual test to catch regressions on a complex config type
it('regression test #1', async () => {
const config = {
type: HookType.AGGREGATION,
hooks: [
{
owner: '0xebe67f0a423fd1c4af21debac756e3238897c665',
type: HookType.INTERCHAIN_GAS_PAYMASTER,
beneficiary: '0xfe3be5940327305aded56f20359761ef85317554',
oracleKey: '0xebe67f0a423fd1c4af21debac756e3238897c665',
overhead: {
test1: 18,
test2: 85,
test3: 23,
test4: 69,
},
oracleConfig: {
test1: {
tokenExchangeRate: '1032586497157',
gasPrice: '1026942205817',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test2: {
tokenExchangeRate: '81451154935',
gasPrice: '1231220057593',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test3: {
tokenExchangeRate: '31347320275',
gasPrice: '21944956734',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test4: {
tokenExchangeRate: '1018619796544',
gasPrice: '1124484183261',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
},
},
{
owner: '0xcc803fc9e6551b9eaaebfabbdd5af3eccea252ff',
type: HookType.ROUTING,
domains: {
test1: {
type: HookType.MERKLE_TREE,
},
test2: {
owner: '0x7e43dfa88c4a5d29a8fcd69883b7f6843d465ca3',
type: HookType.INTERCHAIN_GAS_PAYMASTER,
beneficiary: '0x762e71a849a3825613cf5cbe70bfff27d0fe7766',
oracleKey: '0x7e43dfa88c4a5d29a8fcd69883b7f6843d465ca3',
overhead: {
test1: 46,
test2: 34,
test3: 47,
test4: 24,
},
oracleConfig: {
test1: {
tokenExchangeRate: '1132883204938',
gasPrice: '1219466305935',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test2: {
tokenExchangeRate: '938422264723',
gasPrice: '229134538568',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test3: {
tokenExchangeRate: '69699594189',
gasPrice: '475781234236',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test4: {
tokenExchangeRate: '1027245678936',
gasPrice: '502686418976',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
},
},
test3: {
type: HookType.MERKLE_TREE,
},
test4: {
owner: '0xa1ce72b70566f2cba6000bfe6af50f0f358f49d7',
type: HookType.INTERCHAIN_GAS_PAYMASTER,
beneficiary: '0x9796c0c49c61fe01eb1a8ba56d09b831f6da8603',
oracleKey: '0xa1ce72b70566f2cba6000bfe6af50f0f358f49d7',
overhead: {
test1: 71,
test2: 16,
test3: 37,
test4: 13,
},
oracleConfig: {
test1: {
tokenExchangeRate: '443874625350',
gasPrice: '799154764503',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test2: {
tokenExchangeRate: '915348561750',
gasPrice: '1124345797215',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test3: {
tokenExchangeRate: '930832717805',
gasPrice: '621743941770',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
test4: {
tokenExchangeRate: '147394981623',
gasPrice: '766494385983',
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
},
},
},
},
],
};
await createHook(config);
});
});
describe('update', async () => {
it('should update by deploying a new aggregation hook', async () => {
const config = {
type: HookType.AGGREGATION,
hooks: [randomHookConfig(0, 2), randomHookConfig(0, 2)],
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// change the hooks
config.hooks = [randomHookConfig(0, 2), randomHookConfig(0, 2)];
// expect 0 tx to be returned, as it should deploy a new aggregation hook
await expectTxsAndUpdate(hook, config, 0);
// expect the hook address to be different
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.false;
});
it('should not update if aggregation hook includes an address of an existing hook', async () => {
const config = {
type: HookType.AGGREGATION,
hooks: [randomHookConfig(0, 2), randomHookConfig(0, 2)],
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
const [firstChildHook, secondSecondHook] = (await hook.read()).hooks;
const expectedConfig = {
...config,
hooks: [firstChildHook.address, secondSecondHook],
};
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(initialHookAddress).to.be.equal(hook.serialize().deployedHook);
});
it('should not update if aggregation hook includes an address of an existing hook (depth 2)', async () => {
const owner = await multiProvider.getSignerAddress(chain);
const config = {
type: HookType.AGGREGATION,
hooks: [
{
type: HookType.AGGREGATION,
hooks: [
{
type: HookType.MERKLE_TREE,
},
{
owner,
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1',
protocolFee: '0',
beneficiary: owner,
},
],
},
],
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// Set the deepest hooks to their addresses
const expectedConfig = deepCopy(await hook.read());
expectedConfig.hooks[0].hooks[0] =
expectedConfig.hooks[0].hooks[0].address;
expectedConfig.hooks[0].hooks[1] =
expectedConfig.hooks[0].hooks[1].address;
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(initialHookAddress).to.be.equal(hook.serialize().deployedHook);
});
it('should not update if a domain routing hook includes an address of an existing hook', async () => {
const owner = await multiProvider.getSignerAddress(chain);
const config = {
type: HookType.ROUTING,
owner,
domains: {
9913371: randomHookConfig(0, 2),
9913372: randomHookConfig(0, 2),
},
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
const { test1: firstHook, test2: secondHook } = (await hook.read()).domains;
const expectedConfig = {
...config,
domains: {
test1: firstHook.address,
test2: secondHook,
},
};
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.true;
});
it('should not update if a domain routing hook includes an address of an existing hook (depth 2)', async () => {
const owner = await multiProvider.getSignerAddress(chain);
const config = {
type: HookType.ROUTING,
owner,
domains: {
9913371: {
type: HookType.AGGREGATION,
hooks: [
{
type: HookType.MERKLE_TREE,
},
{
owner,
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1',
protocolFee: '0',
beneficiary: owner,
},
],
},
9913372: {
type: HookType.AGGREGATION,
hooks: [
{
type: HookType.MERKLE_TREE,
},
{
owner,
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1',
protocolFee: '0',
beneficiary: owner,
},
],
},
},
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// Set the deepest hooks to their addresses
const expectedConfig = deepCopy(await hook.read());
expectedConfig.domains.test1.hooks[0] =
expectedConfig.domains.test1.hooks[0].address;
expectedConfig.domains.test2.hooks[0] =
expectedConfig.domains.test2.hooks[0].address;
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.true;
});
it('should not update if a fallback routing hook includes an address of an existing hook', async () => {
const owner = await multiProvider.getSignerAddress(chain);
const config = {
type: HookType.FALLBACK_ROUTING,
owner,
domains: {
9913371: randomHookConfig(0, 2),
9913372: randomHookConfig(0, 2),
},
fallback: randomHookConfig(0, 2),
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
const derivedHook = (await hook.read());
const { test1: firstHook, test2: secondHook } = derivedHook.domains;
const expectedConfig = {
...config,
domains: {
test1: firstHook.address,
test2: secondHook,
},
fallback: derivedHook.fallback.address,
};
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.true;
});
it('should not update if a fallback routing hook includes an address of an existing hook (depth 2)', async () => {
const owner = await multiProvider.getSignerAddress(chain);
const config = {
type: HookType.FALLBACK_ROUTING,
owner,
domains: {
9913371: randomHookConfig(0, 2),
9913372: randomHookConfig(0, 2),
},
fallback: {
type: HookType.AGGREGATION,
hooks: [
{
type: HookType.MERKLE_TREE,
},
{
owner,
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1',
protocolFee: '0',
beneficiary: owner,
},
],
},
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
const derivedHook = await hook.read();
const expectedConfig = {
...derivedHook,
fallback: {
type: HookType.AGGREGATION,
hooks: [
derivedHook.fallback.hooks[0],
derivedHook.fallback.hooks[1].address,
],
},
};
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(initialHookAddress).to.be.equal(hook.serialize().deployedHook);
});
it('should not update if a amount routing hook includes an address of an existing hook', async () => {
const config = {
type: HookType.AMOUNT_ROUTING,
threshold: 1,
lowerHook: randomHookConfig(0, 2),
upperHook: randomHookConfig(0, 2),
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
const derivedHook = (await hook.read());
const { lowerHook, upperHook } = derivedHook;
const expectedConfig = {
...config,
lowerHook: lowerHook.address,
upperHook: upperHook,
};
await expectTxsAndUpdate(hook, expectedConfig, 0);
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.true;
});
const createDeployerOwnedIgpHookConfig = async () => {
const owner = await multiProvider.getSignerAddress(chain);
return {
owner,
type: HookType.INTERCHAIN_GAS_PAYMASTER,
beneficiary: randomAddress(),
oracleKey: owner,
overhead: Object.fromEntries(testChains.map((c) => [c, Math.floor(Math.random() * 100)])),
oracleConfig: Object.fromEntries(testChains.map((c) => [
c,
{
tokenExchangeRate: randomInt(1234567891234).toString(),
gasPrice: randomInt(1234567891234).toString(),
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
])),
};
};
it('should update beneficiary in IGP', async () => {
const config = await createDeployerOwnedIgpHookConfig();
// create a new hook
const { hook } = await createHook(config);
// change the beneficiary
config.beneficiary = randomAddress();
// expect 1 tx to update the beneficiary
await expectTxsAndUpdate(hook, config, 1);
});
it('should update the overheads in IGP', async () => {
const config = await createDeployerOwnedIgpHookConfig();
// create a new hook
const { hook } = await createHook(config);
// change the overheads
config.overhead = Object.fromEntries(testChains.map((c) => [c, Math.floor(Math.random() * 100)]));
// expect 1 tx to update the overheads
await expectTxsAndUpdate(hook, config, 1);
});
it('should update the oracle config in IGP', async () => {
const config = await createDeployerOwnedIgpHookConfig();
// create a new hook
const { hook } = await createHook(config);
// change the oracle config
config.oracleConfig = Object.fromEntries(testChains.map((c) => [
c,
{
tokenExchangeRate: randomInt(987654321).toString(),
gasPrice: randomInt(987654321).toString(),
tokenDecimals: DEFAULT_TOKEN_DECIMALS,
},
]));
// expect 1 tx to update the oracle config
await expectTxsAndUpdate(hook, config, 1);
});
it('should update protocol fee in protocol fee hook', async () => {
const config = {
owner: await multiProvider.getSignerAddress(chain),
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1000',
protocolFee: '100',
beneficiary: randomAddress(),
};
// create a new hook
const { hook } = await createHook(config);
// change the protocol fee
config.protocolFee = '200';
// expect 1 tx to update the protocol fee
await expectTxsAndUpdate(hook, config, 1);
});
it('should update max fee in protocol fee hook', async () => {
const config = {
owner: await multiProvider.getSignerAddress(chain),
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1000',
protocolFee: '100',
beneficiary: randomAddress(),
};
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// change the protocol fee
config.maxProtocolFee = '2000';
// expect 0 tx to update the max protocol fee as it has to deploy a new hook
await expectTxsAndUpdate(hook, config, 0);
// expect the hook address to be different
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.false;
});
it('should update paused state of pausable hook', async () => {
const config = {
owner: randomAddress(),
type: HookType.PAUSABLE,
paused: false,
};
// create a new hook
const { hook } = await createHook(config);
// change the paused state
config.paused = true;
// impersonate the hook owner
multiProvider = await impersonateAccount(config.owner);
// expect 1 tx to update the paused state
await expectTxsAndUpdate(hook, config, 1);
});
for (const type of [HookType.ROUTING, HookType.FALLBACK_ROUTING]) {
beforeEach(() => {
exampleRoutingConfig.type = type;
});
it(`should skip deployment with warning if no chain metadata configured ${type}`, async () => {
// create a new hook
const { hook } = await createHook(exampleRoutingConfig);
// add config for a domain the multiprovider doesn't have
const updatedConfig = {
...exampleRoutingConfig,
domains: {
...exampleRoutingConfig.domains,
test5: { type: HookType.MERKLE_TREE },
},
};
// expect 0 txs, as adding test5 domain is no-op
await expectTxsAndUpdate(hook, updatedConfig, 0);
});
it(`no changes to an existing ${type} means no redeployment or updates`, async () => {
// create a new hook
const { hook, initialHookAddress } = await createHook(exampleRoutingConfig);
// expect 0 updates
await expectTxsAndUpdate(hook, exampleRoutingConfig, 0);
// expect the hook address to be the same
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
it(`updates an existing ${type} with new domains`, async () => {
exampleRoutingConfig = {
owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(),
domains: {
test1: {
type: HookType.MERKLE_TREE,
},
},
type: HookType.FALLBACK_ROUTING,
fallback: { type: HookType.MERKLE_TREE },
};
// create a new hook
const { hook, initialHookAddress } = await createHook(exampleRoutingConfig);
// add a new domain
exampleRoutingConfig.domains[TestChainName.test2] = {
type: HookType.MERKLE_TREE,
};
// expect 1 tx to update the domains
await expectTxsAndUpdate(hook, exampleRoutingConfig, 1);
// expect the hook address to be the same
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
it(`updates an existing ${type} with new domains`, async () => {
exampleRoutingConfig = {
owner: (await multiProvider.getSignerAddress(chain)).toLowerCase(),
domains: {
test1: {
type: HookType.MERKLE_TREE,
},
},
type: HookType.FALLBACK_ROUTING,
fallback: { type: HookType.MERKLE_TREE },
};
// create a new hook
const { hook, initialHookAddress } = await createHook(exampleRoutingConfig);
// add multiple new domains
exampleRoutingConfig.domains[TestChainName.test2] = {
type: HookType.MERKLE_TREE,
};
exampleRoutingConfig.domains[TestChainName.test3] = {
type: HookType.MERKLE_TREE,
};
exampleRoutingConfig.domains[TestChainName.test4] = {
type: HookType.MERKLE_TREE,
};
// expect 1 tx to update the domains
await expectTxsAndUpdate(hook, exampleRoutingConfig, 1);
// expect the hook address to be the same
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
}
it(`update fallback in an existing fallback routing hook`, async () => {
// create a new hook
const config = exampleRoutingConfig;
const { hook, initialHookAddress } = await createHook(config);
// change the fallback
config.fallback = {
type: HookType.PROTOCOL_FEE,
owner: randomAddress(),
maxProtocolFee: '9000',
protocolFee: '350',
beneficiary: randomAddress(),
};
// expect 0 tx as it will have to deploy a new fallback routing hook
await expectTxsAndUpdate(hook, config, 0);
// expect the hook address to be different
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to.be
.false;
});
it(`update fallback in an existing fallback routing hook with no change`, async () => {
// create a new hook
const config = exampleRoutingConfig;
const { hook } = await createHook(config);
// expect 0 updates
await expectTxsAndUpdate(hook, config, 0);
});
it('should not update a hook if given address matches actual config', async () => {
// create a new agg hook with the owner hardcoded
const hookConfig = {
type: HookType.AGGREGATION,
hooks: [
{
...randomHookConfig(0, 2, HookType.FALLBACK_ROUTING),
owner: '0xD1e6626310fD54Eceb5b9a51dA2eC329D6D4B68A',
},
],
};
const { hook } = await createHook(hookConfig);
const deployedHookBefore = (await hook.read());
// expect 0 updates, but actually deploys a new hook
await expectTxsAndUpdate(hook, hookConfig, 0);
const deployedHookAfter = (await hook.read());
expect(deployedHookBefore.address).to.equal(deployedHookAfter.address);
});
// generate a random config for each ownable hook type
const ownableHooks = hookTypes
.filter((hookType) => MUTABLE_HOOK_TYPE.includes(hookType))
.map((hookType) => {
return randomHookConfig(0, 1, hookType);
});
for (const config of ownableHooks) {
assert(typeof config !== 'string', 'Address is not an ownable hook config');
assert('owner' in config, 'Ownable hook config must have an owner property');
it(`updates owner in an existing ${config.type}`, async () => {
// hook owned by the deployer
config.owner = await multiProvider.getSignerAddress(chain);
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// change the config owner
config.owner = randomAddress();
// expect 1 tx to transfer ownership
await expectTxsAndUpdate(hook, config, 1);
// expect the hook address to be the same
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
it(`update owner in an existing ${config.type} not owned by deployer`, async () => {
// hook owner is not the deployer
config.owner = randomAddress();
const originalOwner = config.owner;
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// update the config owner and impersonate the original owner
config.owner = randomAddress();
multiProvider = await impersonateAccount(originalOwner);
// expect 1 tx to transfer ownership
await expectTxsAndUpdate(hook, config, 1);
// expect the hook address to be unchanged
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
it(`update owner in an existing ${config.type} not owned by deployer and no change`, async () => {
// hook owner is not the deployer
config.owner = randomAddress();
const originalOwner = config.owner;
// create a new hook
const { hook, initialHookAddress } = await createHook(config);
// impersonate the original owner
multiProvider = await impersonateAccount(originalOwner);
// expect 0 updates
await expectTxsAndUpdate(hook, config, 0);
// expect the hook address to be unchanged
expect(eqAddress(initialHookAddress, hook.serialize().deployedHook)).to
.be.true;
});
}
});
});
//# sourceMappingURL=EvmHookModule.hardhat-test.js.map