UNPKG

@hyperlane-xyz/core

Version:

Core solidity contracts for Hyperlane

678 lines (615 loc) 18.8 kB
/* * Copyright 2019-2020, Offchain Labs, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-env node, mocha */ import { ethers, network } from 'hardhat' import { BigNumber } from '@ethersproject/bignumber' import { Block, TransactionReceipt } from '@ethersproject/providers' import { expect } from 'chai' import { Bridge, Bridge__factory, Inbox, Inbox__factory, MessageTester, RollupMock__factory, SequencerInbox, SequencerInbox__factory, TransparentUpgradeableProxy__factory, } from '../../build/types' import { applyAlias, initializeAccounts } from './utils' import { Event } from '@ethersproject/contracts' import { Interface } from '@ethersproject/abi' import { BridgeInterface, MessageDeliveredEvent, } from '../../build/types/src/bridge/Bridge' import { Signer } from 'ethers' import { Toolkit4844 } from './toolkit4844' import { data } from './batchData.json' const mineBlocks = async (count: number, timeDiffPerBlock = 14) => { const block = (await network.provider.send('eth_getBlockByNumber', [ 'latest', false, ])) as Block let timestamp = BigNumber.from(block.timestamp).toNumber() for (let i = 0; i < count; i++) { timestamp = timestamp + timeDiffPerBlock await network.provider.send('evm_mine', [timestamp]) } } describe('SequencerInboxForceInclude', async () => { const findMatchingLogs = <TInterface extends Interface, TEvent extends Event>( receipt: TransactionReceipt, iFace: TInterface, eventTopicGen: (i: TInterface) => string ): TEvent['args'][] => { const logs = receipt.logs.filter( log => log.topics[0] === eventTopicGen(iFace) ) return logs.map(l => iFace.parseLog(l).args as TEvent['args']) } const getMessageDeliveredEvents = (receipt: TransactionReceipt) => { const bridgeInterface = Bridge__factory.createInterface() return findMatchingLogs<BridgeInterface, MessageDeliveredEvent>( receipt, bridgeInterface, i => i.getEventTopic(i.getEvent('MessageDelivered')) ) } const sendDelayedTx = async ( sender: Signer, inbox: Inbox, bridge: Bridge, messageTester: MessageTester, l2Gas: number, l2GasPrice: number, nonce: number, destAddr: string, amount: BigNumber, data: string ) => { const countBefore = ( await bridge.functions.delayedMessageCount() )[0].toNumber() const sendUnsignedTx = await inbox .connect(sender) .sendUnsignedTransaction(l2Gas, l2GasPrice, nonce, destAddr, amount, data) const sendUnsignedTxReceipt = await sendUnsignedTx.wait() const countAfter = ( await bridge.functions.delayedMessageCount() )[0].toNumber() expect(countAfter, 'Unexpected inbox count').to.eq(countBefore + 1) const senderAddr = applyAlias(await sender.getAddress()) const messageDeliveredEvent = getMessageDeliveredEvents( sendUnsignedTxReceipt )[0] const l1BlockNumber = sendUnsignedTxReceipt.blockNumber const blockL1 = await sender.provider!.getBlock(l1BlockNumber) const baseFeeL1 = blockL1.baseFeePerGas!.toNumber() const l1BlockTimestamp = blockL1.timestamp const delayedAcc = await bridge.delayedInboxAccs(countBefore) // need to hex pad the address const messageDataHash = ethers.utils.solidityKeccak256( ['uint8', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'bytes'], [ 0, l2Gas, l2GasPrice, nonce, ethers.utils.hexZeroPad(destAddr, 32), amount, data, ] ) expect( messageDeliveredEvent.messageDataHash, 'Incorrect messageDataHash' ).to.eq(messageDataHash) const messageHash = ( await messageTester.functions.messageHash( 3, senderAddr, l1BlockNumber, l1BlockTimestamp, countBefore, baseFeeL1, messageDataHash ) )[0] const prevAccumulator = messageDeliveredEvent.beforeInboxAcc expect(prevAccumulator, 'Incorrect prev accumulator').to.eq( countBefore === 0 ? ethers.utils.hexZeroPad('0x', 32) : await bridge.delayedInboxAccs(countBefore - 1) ) const nextAcc = ( await messageTester.functions.accumulateInboxMessage( prevAccumulator, messageHash ) )[0] expect(delayedAcc, 'Incorrect delayed acc').to.eq(nextAcc) return { baseFeeL1: baseFeeL1, deliveredMessageEvent: messageDeliveredEvent, l1BlockNumber, l1BlockTimestamp, delayedAcc, l2Gas, l2GasPrice, nonce, destAddr, amount, data, senderAddr, inboxAccountLength: countAfter, } } const forceIncludeMessages = async ( sequencerInbox: SequencerInbox, newTotalDelayedMessagesRead: number, kind: number, l1blockNumber: number, l1Timestamp: number, l1BaseFee: number, senderAddr: string, messageDataHash: string, expectedErrorType?: string ) => { const inboxLengthBefore = (await sequencerInbox.batchCount()).toNumber() const forceInclusionTx = sequencerInbox.forceInclusion( newTotalDelayedMessagesRead, kind, [l1blockNumber, l1Timestamp], l1BaseFee, senderAddr, messageDataHash ) if (expectedErrorType) { await expect(forceInclusionTx).to.be.revertedWith( `reverted with custom error '${expectedErrorType}()'` ) } else { await (await forceInclusionTx).wait() const totalDelayedMessagsReadAfter = ( await sequencerInbox.totalDelayedMessagesRead() ).toNumber() expect( totalDelayedMessagsReadAfter, 'Incorrect totalDelayedMessagesRead after.' ).to.eq(newTotalDelayedMessagesRead) const inboxLengthAfter = (await sequencerInbox.batchCount()).toNumber() expect( inboxLengthAfter - inboxLengthBefore, 'Inbox not incremented' ).to.eq(1) } } const setupSequencerInbox = async (maxDelayBlocks = 10, maxDelayTime = 0) => { const accounts = await initializeAccounts() const admin = accounts[0] const adminAddr = await admin.getAddress() const user = accounts[1] // const dummyRollup = accounts[2] const rollupOwner = accounts[3] const batchPoster = accounts[4] // const batchPosterManager = accounts[5] const rollupMockFac = (await ethers.getContractFactory( 'RollupMock' )) as RollupMock__factory const rollup = await rollupMockFac.deploy(await rollupOwner.getAddress()) const reader4844 = await Toolkit4844.deployReader4844(admin) const sequencerInboxFac = (await ethers.getContractFactory( 'SequencerInbox' )) as SequencerInbox__factory const seqInboxTemplate = await sequencerInboxFac.deploy( 117964, reader4844.address, false ) const inboxFac = (await ethers.getContractFactory( 'Inbox' )) as Inbox__factory const inboxTemplate = await inboxFac.deploy(117964) const bridgeFac = (await ethers.getContractFactory( 'Bridge' )) as Bridge__factory const bridgeTemplate = await bridgeFac.deploy() const transparentUpgradeableProxyFac = (await ethers.getContractFactory( 'TransparentUpgradeableProxy' )) as TransparentUpgradeableProxy__factory const bridgeProxy = await transparentUpgradeableProxyFac.deploy( bridgeTemplate.address, adminAddr, '0x' ) const sequencerInboxProxy = await transparentUpgradeableProxyFac.deploy( seqInboxTemplate.address, adminAddr, '0x' ) const inboxProxy = await transparentUpgradeableProxyFac.deploy( inboxTemplate.address, adminAddr, '0x' ) const bridge = await bridgeFac.attach(bridgeProxy.address).connect(user) const bridgeAdmin = await bridgeFac .attach(bridgeProxy.address) .connect(rollupOwner) const sequencerInbox = await sequencerInboxFac .attach(sequencerInboxProxy.address) .connect(user) await bridge.initialize(rollup.address) await sequencerInbox.initialize(bridgeProxy.address, { delayBlocks: maxDelayBlocks, delaySeconds: maxDelayTime, futureBlocks: 10, futureSeconds: 3000, }) await ( await sequencerInbox .connect(rollupOwner) .setIsBatchPoster(await batchPoster.getAddress(), true) ).wait() const inbox = await inboxFac.attach(inboxProxy.address).connect(user) await inbox.initialize(bridgeProxy.address, sequencerInbox.address) await bridgeAdmin.setDelayedInbox(inbox.address, true) await bridgeAdmin.setSequencerInbox(sequencerInbox.address) await ( await sequencerInbox .connect(rollupOwner) .setIsBatchPoster(await batchPoster.getAddress(), true) ).wait() const messageTester = (await ( await ethers.getContractFactory('MessageTester') ).deploy()) as MessageTester return { user, bridge: bridge, inbox: inbox, sequencerInbox: sequencerInbox as SequencerInbox, messageTester, inboxProxy, inboxTemplate, batchPoster, bridgeProxy, rollup, rollupOwner, } } it('can add batch', async () => { const { user, inbox, bridge, messageTester, sequencerInbox, batchPoster } = await setupSequencerInbox() await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const messagesRead = await bridge.delayedMessageCount() const seqReportedMessageSubCount = await bridge.sequencerReportedSubMessageCount() await ( await sequencerInbox .connect(batchPoster) .functions[ 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' ]( 0, data, messagesRead, ethers.constants.AddressZero, seqReportedMessageSubCount, seqReportedMessageSubCount.add(10), { gasLimit: 10000000 } ) ).wait() }) it('can force-include', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox() const delayedTx = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() await mineBlocks(delayBlocks.toNumber()) await forceIncludeMessages( sequencerInbox, delayedTx.inboxAccountLength, delayedTx.deliveredMessageEvent.kind, delayedTx.l1BlockNumber, delayedTx.l1BlockTimestamp, delayedTx.baseFeeL1, delayedTx.senderAddr, delayedTx.deliveredMessageEvent.messageDataHash ) }) it('can force-include-with-max-seqReportedCount', async () => { const { user, inbox, bridge, messageTester, batchPoster, sequencerInbox } = await setupSequencerInbox() await sequencerInbox .connect(batchPoster) [ 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' ]( 0, '0x', 0, ethers.constants.AddressZero, 0, ethers.constants.MaxUint256 ) const delayedTx = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const maxTimeVariation = await sequencerInbox.maxTimeVariation() await mineBlocks(maxTimeVariation[0].toNumber()) await forceIncludeMessages( sequencerInbox, delayedTx.inboxAccountLength, delayedTx.deliveredMessageEvent.kind, delayedTx.l1BlockNumber, delayedTx.l1BlockTimestamp, delayedTx.baseFeeL1, delayedTx.senderAddr, delayedTx.deliveredMessageEvent.messageDataHash ) }) it('can force-include one after another', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox() const delayedTx = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const delayedTx2 = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 1, await user.getAddress(), BigNumber.from(10), '0xdeadface' ) const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() await mineBlocks(delayBlocks.toNumber()) await forceIncludeMessages( sequencerInbox, delayedTx.inboxAccountLength, delayedTx.deliveredMessageEvent.kind, delayedTx.l1BlockNumber, delayedTx.l1BlockTimestamp, delayedTx.baseFeeL1, delayedTx.senderAddr, delayedTx.deliveredMessageEvent.messageDataHash ) await forceIncludeMessages( sequencerInbox, delayedTx2.inboxAccountLength, delayedTx2.deliveredMessageEvent.kind, delayedTx2.l1BlockNumber, delayedTx2.l1BlockTimestamp, delayedTx2.baseFeeL1, delayedTx2.senderAddr, delayedTx2.deliveredMessageEvent.messageDataHash ) }) it('can force-include three at once', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox() await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 1, await user.getAddress(), BigNumber.from(10), '0x101010' ) const delayedTx3 = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 10, await user.getAddress(), BigNumber.from(10), '0x10101010' ) const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() await mineBlocks(delayBlocks.toNumber()) await forceIncludeMessages( sequencerInbox, delayedTx3.inboxAccountLength, delayedTx3.deliveredMessageEvent.kind, delayedTx3.l1BlockNumber, delayedTx3.l1BlockTimestamp, delayedTx3.baseFeeL1, delayedTx3.senderAddr, delayedTx3.deliveredMessageEvent.messageDataHash ) }) it('cannot include before max block delay', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox(10, 100) const delayedTx = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() await mineBlocks(delayBlocks.toNumber() - 1, 5) await forceIncludeMessages( sequencerInbox, delayedTx.inboxAccountLength, delayedTx.deliveredMessageEvent.kind, delayedTx.l1BlockNumber, delayedTx.l1BlockTimestamp, delayedTx.baseFeeL1, delayedTx.senderAddr, delayedTx.deliveredMessageEvent.messageDataHash, 'ForceIncludeBlockTooSoon' ) }) it('cannot include before max time delay', async () => { const { user, inbox, bridge, messageTester, sequencerInbox } = await setupSequencerInbox(10, 100) const delayedTx = await sendDelayedTx( user, inbox, bridge, messageTester, 1000000, 21000000000, 0, await user.getAddress(), BigNumber.from(10), '0x1010' ) const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() // mine a lot of blocks - but use a short time per block // this should mean enough blocks have passed, but not enough time await mineBlocks(delayBlocks.toNumber() + 1, 5) await forceIncludeMessages( sequencerInbox, delayedTx.inboxAccountLength, delayedTx.deliveredMessageEvent.kind, delayedTx.l1BlockNumber, delayedTx.l1BlockTimestamp, delayedTx.baseFeeL1, delayedTx.senderAddr, delayedTx.deliveredMessageEvent.messageDataHash, 'ForceIncludeTimeTooSoon' ) }) it('should fail to call sendL1FundedUnsignedTransactionToFork', async function () { const { inbox } = await setupSequencerInbox() await expect( inbox.sendL1FundedUnsignedTransactionToFork( 0, 0, 0, ethers.constants.AddressZero, '0x' ) ).to.revertedWith('NotForked()') }) it('should fail to call sendUnsignedTransactionToFork', async function () { const { inbox } = await setupSequencerInbox() await expect( inbox.sendUnsignedTransactionToFork( 0, 0, 0, ethers.constants.AddressZero, 0, '0x' ) ).to.revertedWith('NotForked()') }) it('should fail to call sendWithdrawEthToFork', async function () { const { inbox } = await setupSequencerInbox() await expect( inbox.sendWithdrawEthToFork(0, 0, 0, 0, ethers.constants.AddressZero) ).to.revertedWith('NotForked()') }) it('can upgrade Inbox', async () => { const { inboxProxy, inboxTemplate, bridgeProxy } = await setupSequencerInbox() const currentStorage = [] for (let i = 0; i < 1024; i++) { currentStorage[i] = await inboxProxy.provider!.getStorageAt( inboxProxy.address, i ) } await expect( inboxProxy.upgradeToAndCall( inboxTemplate.address, ( await inboxTemplate.populateTransaction.postUpgradeInit( bridgeProxy.address ) ).data! ) ).to.emit(inboxProxy, 'Upgraded') for (let i = 0; i < currentStorage.length; i++) { await expect( await inboxProxy.provider!.getStorageAt(inboxProxy.address, i) ).to.equal(currentStorage[i]) } }) })