|
|
@ -1,7 +1,7 @@ |
|
|
|
/* eslint-disable @typescript-eslint/no-floating-promises */ |
|
|
|
/* eslint-disable @typescript-eslint/no-floating-promises */ |
|
|
|
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
|
|
|
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; |
|
|
|
import { expect } from 'chai'; |
|
|
|
import { expect } from 'chai'; |
|
|
|
import { BigNumberish, ContractTransaction } from 'ethers'; |
|
|
|
import { BigNumberish } from 'ethers'; |
|
|
|
import { ethers } from 'hardhat'; |
|
|
|
import { ethers } from 'hardhat'; |
|
|
|
|
|
|
|
|
|
|
|
import { addressToBytes32 } from '@hyperlane-xyz/utils'; |
|
|
|
import { addressToBytes32 } from '@hyperlane-xyz/utils'; |
|
|
@ -9,6 +9,7 @@ import { addressToBytes32 } from '@hyperlane-xyz/utils'; |
|
|
|
import { |
|
|
|
import { |
|
|
|
TestInterchainGasPaymaster, |
|
|
|
TestInterchainGasPaymaster, |
|
|
|
TestInterchainGasPaymaster__factory, |
|
|
|
TestInterchainGasPaymaster__factory, |
|
|
|
|
|
|
|
TestIsm, |
|
|
|
TestIsm__factory, |
|
|
|
TestIsm__factory, |
|
|
|
TestMailbox, |
|
|
|
TestMailbox, |
|
|
|
TestMailbox__factory, |
|
|
|
TestMailbox__factory, |
|
|
@ -23,19 +24,11 @@ const destination = 2; |
|
|
|
const destinationWithoutRouter = 3; |
|
|
|
const destinationWithoutRouter = 3; |
|
|
|
const body = '0xdeadbeef'; |
|
|
|
const body = '0xdeadbeef'; |
|
|
|
|
|
|
|
|
|
|
|
interface GasPaymentParams { |
|
|
|
describe('Router', async () => { |
|
|
|
// The amount of destination gas being paid for
|
|
|
|
|
|
|
|
gasAmount: BigNumberish; |
|
|
|
|
|
|
|
// The amount of native tokens paid
|
|
|
|
|
|
|
|
payment: BigNumberish; |
|
|
|
|
|
|
|
refundAddress: string; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: update for v3
|
|
|
|
|
|
|
|
describe.skip('Router', async () => { |
|
|
|
|
|
|
|
let router: TestRouter, |
|
|
|
let router: TestRouter, |
|
|
|
mailbox: TestMailbox, |
|
|
|
mailbox: TestMailbox, |
|
|
|
igp: TestInterchainGasPaymaster, |
|
|
|
igp: TestInterchainGasPaymaster, |
|
|
|
|
|
|
|
defaultIsm: TestIsm, |
|
|
|
signer: SignerWithAddress, |
|
|
|
signer: SignerWithAddress, |
|
|
|
nonOwner: SignerWithAddress; |
|
|
|
nonOwner: SignerWithAddress; |
|
|
|
|
|
|
|
|
|
|
@ -46,191 +39,146 @@ describe.skip('Router', async () => { |
|
|
|
beforeEach(async () => { |
|
|
|
beforeEach(async () => { |
|
|
|
const mailboxFactory = new TestMailbox__factory(signer); |
|
|
|
const mailboxFactory = new TestMailbox__factory(signer); |
|
|
|
mailbox = await mailboxFactory.deploy(origin); |
|
|
|
mailbox = await mailboxFactory.deploy(origin); |
|
|
|
igp = await new TestInterchainGasPaymaster__factory(signer).deploy( |
|
|
|
igp = await new TestInterchainGasPaymaster__factory(signer).deploy(); |
|
|
|
nonOwner.address, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const requiredHook = await new TestMerkleTreeHook__factory(signer).deploy( |
|
|
|
const requiredHook = await new TestMerkleTreeHook__factory(signer).deploy( |
|
|
|
mailbox.address, |
|
|
|
mailbox.address, |
|
|
|
); |
|
|
|
); |
|
|
|
const defaultIsm = await new TestIsm__factory(signer).deploy(); |
|
|
|
defaultIsm = await new TestIsm__factory(signer).deploy(); |
|
|
|
await mailbox.initialize( |
|
|
|
await mailbox.initialize( |
|
|
|
signer.address, |
|
|
|
signer.address, |
|
|
|
defaultIsm.address, |
|
|
|
defaultIsm.address, |
|
|
|
igp.address, |
|
|
|
igp.address, |
|
|
|
requiredHook.address, |
|
|
|
requiredHook.address, |
|
|
|
); |
|
|
|
); |
|
|
|
router = await new TestRouter__factory(signer).deploy(); |
|
|
|
router = await new TestRouter__factory(signer).deploy(mailbox.address); |
|
|
|
|
|
|
|
await router.initialize(igp.address, defaultIsm.address); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('#initialize', () => { |
|
|
|
describe('#initialize', () => { |
|
|
|
it('should set the mailbox', async () => { |
|
|
|
it('should set the hook', async () => { |
|
|
|
await router.initialize(mailbox.address); |
|
|
|
expect(await router.hook()).to.equal(igp.address); |
|
|
|
expect(await router.mailbox()).to.equal(mailbox.address); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('should set the ism', async () => { |
|
|
|
|
|
|
|
expect(await router.interchainSecurityModule()).to.equal( |
|
|
|
|
|
|
|
defaultIsm.address, |
|
|
|
|
|
|
|
); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('should transfer owner to deployer', async () => { |
|
|
|
it('should transfer owner to deployer', async () => { |
|
|
|
await router.initialize(mailbox.address); |
|
|
|
|
|
|
|
expect(await router.owner()).to.equal(signer.address); |
|
|
|
expect(await router.owner()).to.equal(signer.address); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('cannot be initialized twice', async () => { |
|
|
|
it('cannot be initialized twice', async () => { |
|
|
|
await router.initialize(mailbox.address); |
|
|
|
await expect( |
|
|
|
await expect(router.initialize(mailbox.address)).to.be.revertedWith( |
|
|
|
router.initialize(mailbox.address, defaultIsm.address), |
|
|
|
'Initializable: contract is already initialized', |
|
|
|
).to.be.revertedWith('Initializable: contract is already initialized'); |
|
|
|
); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('when initialized', () => { |
|
|
|
it('accepts message from enrolled mailbox and router', async () => { |
|
|
|
beforeEach(async () => { |
|
|
|
const sender = addressToBytes32(nonOwner.address); |
|
|
|
await router.initialize(mailbox.address); |
|
|
|
await router.enrollRemoteRouter(origin, sender); |
|
|
|
}); |
|
|
|
const recipient = addressToBytes32(router.address); |
|
|
|
|
|
|
|
// Does not revert.
|
|
|
|
|
|
|
|
await mailbox.testHandle(origin, sender, recipient, body); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('accepts message from enrolled mailbox and router', async () => { |
|
|
|
it('rejects message from unenrolled mailbox', async () => { |
|
|
|
const sender = addressToBytes32(nonOwner.address); |
|
|
|
await expect( |
|
|
|
await router.enrollRemoteRouter(origin, sender); |
|
|
|
router.handle(origin, addressToBytes32(nonOwner.address), body), |
|
|
|
const recipient = addressToBytes32(router.address); |
|
|
|
).to.be.revertedWith('MailboxClient: sender not mailbox'); |
|
|
|
// Does not revert.
|
|
|
|
}); |
|
|
|
await mailbox.testHandle(origin, sender, recipient, body); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('rejects message from unenrolled mailbox', async () => { |
|
|
|
it('rejects message from unenrolled router', async () => { |
|
|
|
await expect( |
|
|
|
const sender = addressToBytes32(nonOwner.address); |
|
|
|
router.handle(origin, addressToBytes32(nonOwner.address), body), |
|
|
|
const recipient = addressToBytes32(router.address); |
|
|
|
).to.be.revertedWith('!mailbox'); |
|
|
|
await expect( |
|
|
|
}); |
|
|
|
mailbox.testHandle(origin, sender, recipient, body), |
|
|
|
|
|
|
|
).to.be.revertedWith(`No router enrolled for domain: ${origin}`); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('rejects message from unenrolled router', async () => { |
|
|
|
it('owner can enroll remote router', async () => { |
|
|
|
const sender = addressToBytes32(nonOwner.address); |
|
|
|
const remote = nonOwner.address; |
|
|
|
const recipient = addressToBytes32(router.address); |
|
|
|
const remoteBytes = addressToBytes32(nonOwner.address); |
|
|
|
await expect( |
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false); |
|
|
|
mailbox.testHandle(origin, sender, recipient, body), |
|
|
|
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith( |
|
|
|
).to.be.revertedWith( |
|
|
|
`No router enrolled for domain: ${origin}`, |
|
|
|
`No router enrolled for domain. Did you specify the right domain ID?`, |
|
|
|
); |
|
|
|
); |
|
|
|
await router.enrollRemoteRouter(origin, addressToBytes32(remote)); |
|
|
|
}); |
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true); |
|
|
|
|
|
|
|
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('owner can enroll remote router using batch function', async () => { |
|
|
|
|
|
|
|
const remote = nonOwner.address; |
|
|
|
|
|
|
|
const remoteBytes = addressToBytes32(nonOwner.address); |
|
|
|
|
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false); |
|
|
|
|
|
|
|
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith( |
|
|
|
|
|
|
|
`No router enrolled for domain: ${origin}`, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
await router.enrollRemoteRouters([origin], [addressToBytes32(remote)]); |
|
|
|
|
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true); |
|
|
|
|
|
|
|
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('owner can enroll remote router', async () => { |
|
|
|
describe('#domains', () => { |
|
|
|
const remote = nonOwner.address; |
|
|
|
it('returns the domains', async () => { |
|
|
|
const remoteBytes = addressToBytes32(nonOwner.address); |
|
|
|
await router.enrollRemoteRouters( |
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false); |
|
|
|
[origin, destination], |
|
|
|
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith( |
|
|
|
[ |
|
|
|
`No router enrolled for domain. Did you specify the right domain ID?`, |
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
], |
|
|
|
); |
|
|
|
); |
|
|
|
await router.enrollRemoteRouter(origin, addressToBytes32(remote)); |
|
|
|
expect(await router.domains()).to.deep.equal([origin, destination]); |
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true); |
|
|
|
|
|
|
|
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('non-owner cannot enroll remote router', async () => { |
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
router |
|
|
|
|
|
|
|
.connect(nonOwner) |
|
|
|
|
|
|
|
.enrollRemoteRouter(origin, addressToBytes32(nonOwner.address)), |
|
|
|
|
|
|
|
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('owner can enroll remote router using batch function', async () => { |
|
|
|
describe('#dispatch', () => { |
|
|
|
const remote = nonOwner.address; |
|
|
|
let payment: BigNumberish; |
|
|
|
const remoteBytes = addressToBytes32(nonOwner.address); |
|
|
|
|
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(false); |
|
|
|
beforeEach(async () => { |
|
|
|
await expect(router.mustHaveRemoteRouter(origin)).to.be.revertedWith( |
|
|
|
// Enroll a remote router on the destination domain.
|
|
|
|
`No router enrolled for domain. Did you specify the right domain ID?`, |
|
|
|
// The address is arbitrary because no messages will actually be processed.
|
|
|
|
|
|
|
|
await router.enrollRemoteRouter( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const recipient = addressToBytes32(router.address); |
|
|
|
|
|
|
|
payment = await mailbox['quoteDispatch(uint32,bytes32,bytes)']( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
recipient, |
|
|
|
|
|
|
|
body, |
|
|
|
); |
|
|
|
); |
|
|
|
await router.enrollRemoteRouters([origin], [addressToBytes32(remote)]); |
|
|
|
|
|
|
|
expect(await router.isRemoteRouter(origin, remoteBytes)).to.equal(true); |
|
|
|
|
|
|
|
expect(await router.mustHaveRemoteRouter(origin)).to.equal(remoteBytes); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('#domains', () => { |
|
|
|
it('dispatches a message', async () => { |
|
|
|
it('returns the domains', async () => { |
|
|
|
await expect( |
|
|
|
await router.enrollRemoteRouters( |
|
|
|
router.dispatch(destination, body, { value: payment }), |
|
|
|
[origin, destination], |
|
|
|
).to.emit(mailbox, 'Dispatch'); |
|
|
|
[ |
|
|
|
|
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
expect(await router.domains()).to.deep.equal([origin, destination]); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
it('non-owner cannot enroll remote router', async () => { |
|
|
|
it('reverts on insufficient payment', async () => { |
|
|
|
await expect( |
|
|
|
await expect( |
|
|
|
router |
|
|
|
router.dispatch(destination, body, { value: payment.sub(1) }), |
|
|
|
.connect(nonOwner) |
|
|
|
).to.be.revertedWith('insufficient interchain gas payment'); |
|
|
|
.enrollRemoteRouter(origin, addressToBytes32(nonOwner.address)), |
|
|
|
|
|
|
|
).to.be.revertedWith(ONLY_OWNER_REVERT_MSG); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
describe('dispatch functions', () => { |
|
|
|
it('reverts when dispatching a message to an unenrolled remote router', async () => { |
|
|
|
let payment: BigNumberish; |
|
|
|
await expect( |
|
|
|
|
|
|
|
router.dispatch(destinationWithoutRouter, body), |
|
|
|
beforeEach(async () => { |
|
|
|
).to.be.revertedWith( |
|
|
|
// Enroll a remote router on the destination domain.
|
|
|
|
`No router enrolled for domain: ${destinationWithoutRouter}`, |
|
|
|
// The address is arbitrary because no messages will actually be processed.
|
|
|
|
); |
|
|
|
await router.enrollRemoteRouter( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
addressToBytes32(nonOwner.address), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const recipient = utils.addressToBytes32(router.address); |
|
|
|
|
|
|
|
payment = await mailbox.quoteDispatch(destination, recipient, body); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe('#dispatch', () => { |
|
|
|
|
|
|
|
it('dispatches a message', async () => { |
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
router.dispatch(destination, body, { value: payment }), |
|
|
|
|
|
|
|
).to.emit(mailbox, 'Dispatch'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts on insufficient payment', async () => { |
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
router.dispatch(destination, body, { value: payment.sub(1) }), |
|
|
|
|
|
|
|
).to.be.revertedWith('insufficient interchain gas payment'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('reverts when dispatching a message to an unenrolled remote router', async () => { |
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
router.dispatch(destinationWithoutRouter, body), |
|
|
|
|
|
|
|
).to.be.revertedWith( |
|
|
|
|
|
|
|
`No router enrolled for domain. Did you specify the right domain ID?`, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
describe('#dispatchWithGas', () => { |
|
|
|
|
|
|
|
const testGasPaymentParams = { |
|
|
|
|
|
|
|
gasAmount: 4321, |
|
|
|
|
|
|
|
payment: 43210, |
|
|
|
|
|
|
|
refundAddress: '0xc0ffee0000000000000000000000000000000000', |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('dispatches a message', async () => { |
|
|
|
|
|
|
|
await expect( |
|
|
|
|
|
|
|
router.dispatchWithGas( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
body, |
|
|
|
|
|
|
|
testGasPaymentParams.gasAmount, |
|
|
|
|
|
|
|
testGasPaymentParams.payment, |
|
|
|
|
|
|
|
testGasPaymentParams.refundAddress, |
|
|
|
|
|
|
|
{ value: testGasPaymentParams.payment }, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
).to.emit(mailbox, 'Dispatch'); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
it('uses custom igp metadata', async () => { |
|
|
|
|
|
|
|
const tx = await router.dispatchWithGas( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
body, |
|
|
|
|
|
|
|
testGasPaymentParams.gasAmount, |
|
|
|
|
|
|
|
testGasPaymentParams.payment, |
|
|
|
|
|
|
|
testGasPaymentParams.refundAddress, |
|
|
|
|
|
|
|
{ value: testGasPaymentParams.payment }, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const messageId = await mailbox.latestDispatchedId(); |
|
|
|
|
|
|
|
const required = await igp.quoteGasPayment( |
|
|
|
|
|
|
|
destination, |
|
|
|
|
|
|
|
testGasPaymentParams.gasAmount, |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
expect(tx) |
|
|
|
|
|
|
|
.to.emit(igp, 'GasPayment') |
|
|
|
|
|
|
|
.withArgs(messageId, testGasPaymentParams.gasAmount, required); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |
|
|
|