UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

378 lines 16.6 kB
import { expect } from 'chai'; import { BigNumber } from 'ethers'; import sinon from 'sinon'; import { AbstractRoutingIsm__factory, AmountRoutingIsm__factory, CCIPIsm__factory, DefaultFallbackRoutingIsm__factory, DomainRoutingIsm__factory, IInterchainSecurityModule__factory, IMultisigIsm__factory, IncrementalDomainRoutingIsm__factory, InterchainAccountRouter__factory, OPStackIsm__factory, Ownable__factory, PausableIsm__factory, RateLimitedIsm__factory, TestIsm__factory, TrustedRelayerIsm__factory, } from '@hyperlane-xyz/core'; import { TestChainName } from '../consts/testChains.js'; import { MultiProvider } from '../providers/MultiProvider.js'; import { missingSelectorError, networkError } from '../test/errors.js'; import { randomAddress } from '../test/testUtils.js'; import { EvmIsmReader } from './EvmIsmReader.js'; import { IsmType, ModuleType, } from './types.js'; describe('EvmIsmReader', () => { let evmIsmReader; let multiProvider; let sandbox; beforeEach(() => { sandbox = sinon.createSandbox(); multiProvider = MultiProvider.createTestMultiProvider(); evmIsmReader = new EvmIsmReader(multiProvider, TestChainName.test1); }); afterEach(() => { sandbox.restore(); }); it('should derive multisig config correctly', async () => { const mockAddress = randomAddress(); const mockValidators = [randomAddress(), randomAddress()]; const mockThreshold = 2; // Mocking the connect method + returned what we need from contract object const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.MESSAGE_ID_MULTISIG), validatorsAndThreshold: sandbox .stub() .resolves([mockValidators, mockThreshold]), }; sandbox .stub(IMultisigIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); const expectedConfig = { address: mockAddress, type: IsmType.MESSAGE_ID_MULTISIG, validators: mockValidators, threshold: mockThreshold, }; // top-level method infers ism type const ismConfig = await evmIsmReader.deriveIsmConfig(mockAddress); expect(ismConfig).to.deep.equal(expectedConfig); // should get same result if we call the specific method for the ism type const config = await evmIsmReader.deriveMultisigConfig(mockAddress); expect(config).to.deep.equal(ismConfig); }); it('should derive pausable config correctly', async () => { const mockAddress = randomAddress(); const mockOwner = randomAddress(); const mockPaused = true; // Mocking the connect method + returned what we need from contract object const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.NULL), owner: sandbox.stub().resolves(mockOwner), paused: sandbox.stub().resolves(mockPaused), trustedRelayer: sandbox.stub().rejects(missingSelectorError()), }; sandbox .stub(PausableIsm__factory, 'connect') .returns(mockContract); sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); const expectedConfig = { address: mockAddress, owner: mockOwner, type: IsmType.PAUSABLE, paused: mockPaused, }; // top-level method infers ism type const ismConfig = await evmIsmReader.deriveIsmConfig(mockAddress); expect(ismConfig).to.deep.equal(expectedConfig); // should get same result if we call the specific method for the ism type const config = await evmIsmReader.deriveNullConfig(mockAddress); expect(config).to.deep.equal(ismConfig); }); it('should derive test ISM config correctly', async () => { const mockAddress = randomAddress(); // Mocking the connect method + returned what we need from contract object const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.NULL), trustedRelayer: sandbox.stub().rejects(missingSelectorError()), paused: sandbox.stub().rejects(missingSelectorError()), owner: sandbox.stub().rejects(missingSelectorError()), ccipOrigin: sandbox.stub().rejects(missingSelectorError()), VERIFIED_MASK_INDEX: sandbox.stub().rejects(missingSelectorError()), recipient: sandbox.stub().rejects(missingSelectorError()), }; sandbox .stub(TestIsm__factory, 'connect') .returns(mockContract); sandbox .stub(OPStackIsm__factory, 'connect') .returns(mockContract); sandbox .stub(PausableIsm__factory, 'connect') .returns(mockContract); sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract); sandbox .stub(CCIPIsm__factory, 'connect') .returns(mockContract); sandbox .stub(RateLimitedIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); const expectedConfig = { address: mockAddress, type: IsmType.TEST_ISM, }; // top-level method infers ism type const ismConfig = await evmIsmReader.deriveIsmConfig(mockAddress); expect(ismConfig).to.deep.equal(expectedConfig); // should get same result if we call the specific method for the ism type const config = await evmIsmReader.deriveNullConfig(mockAddress); expect(config).to.deep.equal(ismConfig); }); it('should not classify transient pausable probe failures as test ISM', async () => { const mockAddress = randomAddress(); const transientError = networkError(); const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.NULL), trustedRelayer: sandbox.stub().rejects(missingSelectorError()), paused: sandbox.stub().resolves(false), owner: sandbox.stub().rejects(transientError), }; sandbox .stub(PausableIsm__factory, 'connect') .returns(mockContract); sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); let thrown; try { await evmIsmReader.deriveNullConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should prioritize transient pausable probe failures over missing selectors', async () => { const mockAddress = randomAddress(); const transientError = networkError(); const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.NULL), trustedRelayer: sandbox.stub().rejects(missingSelectorError()), paused: sandbox.stub().rejects(missingSelectorError()), owner: sandbox.stub().rejects(transientError), }; sandbox .stub(PausableIsm__factory, 'connect') .returns(mockContract); sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); let thrown; try { await evmIsmReader.deriveNullConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should not treat transient routing owner failures as non-ownable routing', async () => { const mockAddress = randomAddress(); const transientError = networkError(); sandbox.stub(AbstractRoutingIsm__factory, 'connect').returns({ moduleType: sandbox.stub().resolves(ModuleType.ROUTING), }); sandbox.stub(InterchainAccountRouter__factory, 'connect').returns({ CCIP_READ_ISM: sandbox.stub().rejects(missingSelectorError()), bytecodeHash: sandbox.stub().rejects(missingSelectorError()), }); sandbox.stub(Ownable__factory, 'connect').returns({ owner: sandbox.stub().rejects(transientError), }); let thrown; try { await evmIsmReader.deriveRoutingConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should not treat transient ICA probe failures as non-ICA routing', async () => { const mockAddress = randomAddress(); const transientError = networkError(); sandbox.stub(AbstractRoutingIsm__factory, 'connect').returns({ moduleType: sandbox.stub().resolves(ModuleType.ROUTING), }); sandbox.stub(InterchainAccountRouter__factory, 'connect').returns({ CCIP_READ_ISM: sandbox.stub().rejects(transientError), }); let thrown; try { await evmIsmReader.deriveRoutingConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should not classify transient AmountRoutingIsm probe failures as legacy ICA', async () => { const mockAddress = randomAddress(); const transientError = networkError(); sandbox.stub(AbstractRoutingIsm__factory, 'connect').returns({ moduleType: sandbox.stub().resolves(ModuleType.ROUTING), }); sandbox.stub(InterchainAccountRouter__factory, 'connect').returns({ CCIP_READ_ISM: sandbox.stub().rejects(missingSelectorError()), bytecodeHash: sandbox.stub().rejects(missingSelectorError()), }); sandbox.stub(Ownable__factory, 'connect').returns({ owner: sandbox.stub().rejects(missingSelectorError()), }); sandbox.stub(AmountRoutingIsm__factory, 'connect').returns({ lower: sandbox.stub().rejects(transientError), upper: sandbox.stub().resolves(randomAddress()), threshold: sandbox.stub().resolves(1), }); let thrown; try { await evmIsmReader.deriveRoutingConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should not treat transient fallback mailbox failures as plain routing', async () => { const mockAddress = randomAddress(); const transientError = networkError(); sandbox.stub(AbstractRoutingIsm__factory, 'connect').returns({ moduleType: sandbox.stub().resolves(ModuleType.ROUTING), }); sandbox.stub(InterchainAccountRouter__factory, 'connect').returns({ CCIP_READ_ISM: sandbox.stub().rejects(missingSelectorError()), bytecodeHash: sandbox.stub().rejects(missingSelectorError()), }); sandbox.stub(Ownable__factory, 'connect').returns({ owner: sandbox.stub().resolves(randomAddress()), }); sandbox.stub(DefaultFallbackRoutingIsm__factory, 'connect').returns({ domains: sandbox.stub().resolves([]), mailbox: sandbox.stub().rejects(transientError), }); let thrown; try { await evmIsmReader.deriveRoutingConfig(mockAddress); } catch (error) { thrown = error; } expect(thrown).to.equal(transientError); }); it('should derive the ICA ism correctly', async () => { const mockAddress = randomAddress(); const mockOwner = randomAddress(); const mockccipIsm = randomAddress(); // Mocking the connect method + returned what we need from contract object const mockContract = { moduleType: sandbox.stub().resolves(ModuleType.ROUTING), owner: sandbox.stub().resolves(mockOwner), CCIP_READ_ISM: sandbox.stub().resolves(mockccipIsm), }; const mockDefaultFallbackContract = { moduleType: sandbox.stub().resolves(ModuleType.ROUTING), owner: sandbox.stub().resolves(mockOwner), domains: sandbox.stub().resolves([]), }; sandbox .stub(AbstractRoutingIsm__factory, 'connect') .returns(mockContract); sandbox .stub(InterchainAccountRouter__factory, 'connect') .returns(mockContract); sandbox .stub(TrustedRelayerIsm__factory, 'connect') .returns(mockContract); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockContract); sandbox.stub(Ownable__factory, 'connect').returns(mockContract); sandbox .stub(DefaultFallbackRoutingIsm__factory, 'connect') .returns(mockDefaultFallbackContract); const expectedConfig = { address: mockAddress, type: IsmType.INTERCHAIN_ACCOUNT_ROUTING, isms: {}, owner: mockOwner, }; // top-level method infers ism type const ismConfig = await evmIsmReader.deriveIsmConfig(mockAddress); expect(ismConfig).to.deep.equal(expectedConfig); // should get same result if we call the specific method for the ism type const config = await evmIsmReader.deriveRoutingConfig(mockAddress); expect(config).to.deep.equal(ismConfig); }); it('should derive incremental routing ISM config correctly', async () => { const mockAddress = randomAddress(); const mockOwner = randomAddress(); const mockDomain = 1; const mockModule = randomAddress(); // Mock the routing ISM contract const mockRoutingContract = { moduleType: sandbox.stub().resolves(ModuleType.ROUTING), owner: sandbox.stub().resolves(mockOwner), domains: sandbox.stub().resolves([mockDomain]), module: sandbox.stub().resolves(mockModule), }; // Mock fallback routing to fail mailbox() call const mockFallbackContract = { mailbox: sandbox.stub().rejects(missingSelectorError()), domains: sandbox.stub().resolves([BigNumber.from(mockDomain)]), module: sandbox.stub().resolves(mockModule), }; const mockProvider = evmIsmReader['provider']; sandbox .stub(mockProvider, 'getCode') .resolves(IncrementalDomainRoutingIsm__factory.bytecode); sandbox .stub(AbstractRoutingIsm__factory, 'connect') .returns(mockRoutingContract); sandbox .stub(Ownable__factory, 'connect') .returns({ owner: sandbox.stub().resolves(mockOwner) }); sandbox .stub(DefaultFallbackRoutingIsm__factory, 'connect') .returns(mockFallbackContract); sandbox .stub(DomainRoutingIsm__factory, 'connect') .returns(mockRoutingContract); sandbox.stub(InterchainAccountRouter__factory, 'connect').returns({ CCIP_READ_ISM: sandbox.stub().rejects(missingSelectorError()), bytecodeHash: sandbox.stub().rejects(missingSelectorError()), }); sandbox .stub(IInterchainSecurityModule__factory, 'connect') .returns(mockRoutingContract); // Mock deriveIsmConfig for the nested module sandbox.stub(evmIsmReader, 'deriveIsmConfig').resolves({ type: IsmType.TEST_ISM, address: mockModule, }); const config = await evmIsmReader.deriveRoutingConfig(mockAddress); expect(config.type).to.equal(IsmType.INCREMENTAL_ROUTING); }); /* Testing for more nested ism types can be done manually by reading from existing contracts onchain. Examples of nested ism types include: - Aggregation - Routing - Fallback Domain Routing */ }); //# sourceMappingURL=EvmIsmReader.test.js.map