@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
221 lines • 9.79 kB
JavaScript
import { expect } from 'chai';
import { ethers } from 'ethers';
import { randomBytes } from 'ethers/lib/utils.js';
import sinon from 'sinon';
import { CCIPHook__factory, DefaultHook__factory, IPostDispatchHook__factory, MerkleTreeHook__factory, OPStackHook__factory, PausableHook__factory, ProtocolFee__factory, } from '@hyperlane-xyz/core';
import { TestChainName, test1 } from '../consts/testChains.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { randomAddress } from '../test/testUtils.js';
import { EvmHookReader } from './EvmHookReader.js';
import { HookType, OnchainHookType, } from './types.js';
describe('EvmHookReader', () => {
let evmHookReader;
let multiProvider;
let sandbox;
beforeEach(() => {
sandbox = sinon.createSandbox();
multiProvider = MultiProvider.createTestMultiProvider();
evmHookReader = new EvmHookReader(multiProvider, TestChainName.test1);
});
afterEach(() => {
sandbox.restore();
});
it('should derive merkle tree config correctly', async () => {
const mockAddress = randomAddress();
const mockOwner = randomAddress();
// Mocking the connect method + returned what we need from contract object
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.MERKLE_TREE),
owner: sandbox.stub().resolves(mockOwner),
};
sandbox
.stub(MerkleTreeHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const expectedConfig = {
address: mockAddress,
type: HookType.MERKLE_TREE,
};
// top-level method infers hook type
const hookConfig = await evmHookReader.deriveHookConfig(mockAddress);
expect(hookConfig).to.deep.equal(expectedConfig);
// should get same result if we call the specific method for the hook type
const config = await evmHookReader.deriveMerkleTreeConfig(mockAddress);
expect(config).to.deep.equal(hookConfig);
});
it('should derive protocol fee hook correctly', async () => {
const mockAddress = randomAddress();
const mockOwner = randomAddress();
const mockBeneficiary = randomAddress();
// Mocking the connect method + returned what we need from contract object
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.PROTOCOL_FEE),
owner: sandbox.stub().resolves(mockOwner),
MAX_PROTOCOL_FEE: sandbox.stub().resolves(ethers.BigNumber.from('1000')),
protocolFee: sandbox.stub().resolves(ethers.BigNumber.from('10')),
beneficiary: sandbox.stub().resolves(mockBeneficiary),
};
sandbox
.stub(ProtocolFee__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const expectedConfig = {
owner: mockOwner,
address: mockAddress,
type: HookType.PROTOCOL_FEE,
maxProtocolFee: '1000',
protocolFee: '10',
beneficiary: mockBeneficiary,
};
// top-level method infers hook type
const hookConfig = await evmHookReader.deriveHookConfig(mockAddress);
expect(hookConfig).to.deep.equal(expectedConfig);
// should get same result if we call the specific method for the hook type
const config = await evmHookReader.deriveProtocolFeeConfig(mockAddress);
expect(config).to.deep.equal(hookConfig);
});
it('should derive pausable config correctly', async () => {
const mockAddress = randomAddress();
const mockOwner = randomAddress();
const mockPaused = randomBytes(1)[0] % 2 === 0;
// Mocking the connect method + returned what we need from contract object
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.PAUSABLE),
owner: sandbox.stub().resolves(mockOwner),
paused: sandbox.stub().resolves(mockPaused),
};
sandbox
.stub(PausableHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const expectedConfig = {
owner: mockOwner,
paused: mockPaused,
address: mockAddress,
type: HookType.PAUSABLE,
};
// top-level method infers hook type
const hookConfig = await evmHookReader.deriveHookConfig(mockAddress);
expect(hookConfig).to.deep.equal(expectedConfig);
// should get same result if we call the specific method for the hook type
const config = await evmHookReader.derivePausableConfig(mockAddress);
expect(config).to.deep.equal(hookConfig);
});
it('should derive mailbox default hook config correctly', async () => {
const mockAddress = randomAddress();
const mockMailbox = randomAddress();
// Mocking the connect method + returned what we need from contract object
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.MAILBOX_DEFAULT_HOOK),
mailbox: sandbox.stub().resolves(mockMailbox),
};
sandbox
.stub(DefaultHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const expectedConfig = {
address: mockAddress,
type: HookType.MAILBOX_DEFAULT,
};
// top-level method infers hook type
const hookConfig = await evmHookReader.deriveHookConfig(mockAddress);
expect(hookConfig).to.deep.equal(expectedConfig);
// should get same result if we call the specific method for the hook type
const config = await evmHookReader.deriveMailboxDefaultHookConfig(mockAddress);
expect(config).to.deep.equal(hookConfig);
});
it('should derive op stack config correctly', async () => {
const mockAddress = randomAddress();
const mockOwner = randomAddress();
const l1Messenger = randomAddress();
// Mocking the connect method + returned what we need from contract object
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.ID_AUTH_ISM),
owner: sandbox.stub().resolves(mockOwner),
l1Messenger: sandbox.stub().resolves(l1Messenger),
destinationDomain: sandbox.stub().resolves(test1.domainId),
};
sandbox
.stub(OPStackHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const expectedConfig = {
owner: mockOwner,
address: mockAddress,
type: HookType.OP_STACK,
nativeBridge: l1Messenger,
destinationChain: TestChainName.test1,
};
// top-level method infers hook type
const hookConfig = await evmHookReader.deriveHookConfig(mockAddress);
expect(hookConfig).to.deep.equal(expectedConfig);
// should get same result if we call the specific method for the hook type
const config = await evmHookReader.deriveOpStackConfig(mockAddress);
expect(config).to.deep.equal(hookConfig);
});
it('should derive CCIPHook configuration correctly', async () => {
const ccipHookAddress = randomAddress();
const destinationDomain = test1.domainId;
const ism = randomAddress();
// Mock the CCIPHook contract
const mockContract = {
hookType: sandbox.stub().resolves(OnchainHookType.ID_AUTH_ISM),
destinationDomain: sandbox.stub().resolves(destinationDomain),
ism: sandbox.stub().resolves(ism),
};
sandbox
.stub(CCIPHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
const config = await evmHookReader.deriveCcipConfig(ccipHookAddress);
const expectedConfig = {
address: ccipHookAddress,
type: HookType.CCIP,
destinationChain: TestChainName.test1,
};
expect(config).to.deep.equal(expectedConfig);
});
it('should throw if derivation fails', async () => {
const mockAddress = randomAddress();
const mockOwner = randomAddress();
// Mocking the connect method + returned what we need from contract object
const mockContract = {
// No type
owner: sandbox.stub().resolves(mockOwner),
};
sandbox
.stub(MerkleTreeHook__factory, 'connect')
.returns(mockContract);
sandbox
.stub(IPostDispatchHook__factory, 'connect')
.returns(mockContract);
// top-level method infers hook type
try {
await evmHookReader.deriveHookConfig(mockAddress);
}
catch (e) {
expect(e.toString()).to.contain(`Failed to derive undefined hook (${mockAddress}):`);
}
});
/*
Testing for more nested hook types can be done manually by reading from existing contracts onchain.
Examples of nested hook types include:
- Aggregation
- Domain Routing
- Fallback Domain Routing
- Interchain Gas Paymaster
*/
});
//# sourceMappingURL=EvmHookReader.test.js.map