Commit 7ee8d8d3 authored by kf's avatar kf

feat: implement getMessageStatus

parent f8531d88
...@@ -23,6 +23,8 @@ import { ...@@ -23,6 +23,8 @@ import {
MessageReceiptStatus, MessageReceiptStatus,
CustomBridges, CustomBridges,
CustomBridgesLike, CustomBridgesLike,
StateRoot,
StateRootBatch,
} from './interfaces' } from './interfaces'
import { import {
toProvider, toProvider,
...@@ -38,6 +40,7 @@ export class CrossChainProvider implements ICrossChainProvider { ...@@ -38,6 +40,7 @@ export class CrossChainProvider implements ICrossChainProvider {
public l1Provider: Provider public l1Provider: Provider
public l2Provider: Provider public l2Provider: Provider
public l1ChainId: number public l1ChainId: number
public l1BlockTime: number
public contracts: OEContracts public contracts: OEContracts
public bridges: CustomBridges public bridges: CustomBridges
...@@ -48,18 +51,24 @@ export class CrossChainProvider implements ICrossChainProvider { ...@@ -48,18 +51,24 @@ export class CrossChainProvider implements ICrossChainProvider {
* @param opts.l1Provider Provider for the L1 chain, or a JSON-RPC url. * @param opts.l1Provider Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l2Provider Provider for the L2 chain, or a JSON-RPC url. * @param opts.l2Provider Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain. * @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.l1BlockTime Optional L1 block time in seconds. Defaults to 15 seconds.
* @param opts.contracts Optional contract address overrides. * @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list.
*/ */
constructor(opts: { constructor(opts: {
l1Provider: ProviderLike l1Provider: ProviderLike
l2Provider: ProviderLike l2Provider: ProviderLike
l1ChainId: NumberLike l1ChainId: NumberLike
l1BlockTime?: NumberLike
contracts?: DeepPartial<OEContractsLike> contracts?: DeepPartial<OEContractsLike>
bridges?: Partial<CustomBridgesLike> bridges?: Partial<CustomBridgesLike>
}) { }) {
this.l1Provider = toProvider(opts.l1Provider) this.l1Provider = toProvider(opts.l1Provider)
this.l2Provider = toProvider(opts.l2Provider) this.l2Provider = toProvider(opts.l2Provider)
this.l1ChainId = toBigNumber(opts.l1ChainId).toNumber() this.l1ChainId = toBigNumber(opts.l1ChainId).toNumber()
this.l1BlockTime = opts.l1BlockTime
? toBigNumber(opts.l1ChainId).toNumber()
: 15
this.contracts = getAllOEContracts(this.l1ChainId, { this.contracts = getAllOEContracts(this.l1ChainId, {
l1SignerOrProvider: this.l1Provider, l1SignerOrProvider: this.l1Provider,
l2SignerOrProvider: this.l2Provider, l2SignerOrProvider: this.l2Provider,
...@@ -335,7 +344,41 @@ export class CrossChainProvider implements ICrossChainProvider { ...@@ -335,7 +344,41 @@ export class CrossChainProvider implements ICrossChainProvider {
} }
public async getMessageStatus(message: MessageLike): Promise<MessageStatus> { public async getMessageStatus(message: MessageLike): Promise<MessageStatus> {
throw new Error('Not implemented') const resolved = await this.toCrossChainMessage(message)
const receipt = await this.getMessageReceipt(resolved)
if (resolved.direction === MessageDirection.L1_TO_L2) {
if (receipt === null) {
return MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE
} else {
if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) {
return MessageStatus.RELAYED
} else {
return MessageStatus.FAILED_L1_TO_L2_MESSAGE
}
}
} else {
if (receipt === null) {
const stateRoot = await this.getMessageStateRoot(resolved)
if (stateRoot === null) {
return MessageStatus.STATE_ROOT_NOT_PUBLISHED
} else {
const challengePeriod = await this.getChallengePeriodBlocks()
const latestBlock = await this.l1Provider.getBlockNumber()
if (stateRoot.blockNumber + challengePeriod > latestBlock) {
return MessageStatus.IN_CHALLENGE_PERIOD
} else {
return MessageStatus.READY_FOR_RELAY
}
}
} else {
if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) {
return MessageStatus.RELAYED
} else {
return MessageStatus.READY_FOR_RELAY
}
}
}
} }
public async getMessageReceipt( public async getMessageReceipt(
...@@ -436,4 +479,169 @@ export class CrossChainProvider implements ICrossChainProvider { ...@@ -436,4 +479,169 @@ export class CrossChainProvider implements ICrossChainProvider {
): Promise<number> { ): Promise<number> {
throw new Error('Not implemented') throw new Error('Not implemented')
} }
public async getChallengePeriodSeconds(): Promise<number> {
const challengePeriod =
await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW()
return challengePeriod.toNumber()
}
public async getChallengePeriodBlocks(): Promise<number> {
return Math.ceil(
(await this.getChallengePeriodSeconds()) / this.l1BlockTime
)
}
public async getMessageStateRoot(
message: MessageLike
): Promise<StateRoot | null> {
const resolved = await this.toCrossChainMessage(message)
// State roots are only a thing for L2 to L1 messages.
if (resolved.direction === MessageDirection.L1_TO_L2) {
throw new Error(`cannot get a state root for an L1 to L2 message`)
}
// We need the block number of the transaction that triggered the message so we can look up the
// state root batch that corresponds to that block number.
const messageTxReceipt = await this.l2Provider.getTransactionReceipt(
resolved.transactionHash
)
// Every block has exactly one transaction in it. Since there's a genesis block, the
// transaction index will always be one less than the block number.
const messageTxIndex = messageTxReceipt.blockNumber - 1
// Pull down the state root batch, we'll try to pick out the specific state root that
// corresponds to our message.
const stateRootBatch = await this.getStateRootBatchByTransactionIndex(
messageTxIndex
)
// No state root batch, no state root.
if (stateRootBatch === null) {
return null
}
// We have a state root batch, now we need to find the specific state root for our transaction.
// First we need to figure out the index of the state root within the batch we found. This is
// going to be the original transaction index offset by the total number of previous state
// roots.
const indexInBatch =
messageTxIndex - stateRootBatch.header.prevTotalElements.toNumber()
// Just a sanity check.
if (stateRootBatch.stateRoots.length <= indexInBatch) {
// Should never happen!
throw new Error(`state root does not exist in batch`)
}
return {
blockNumber: stateRootBatch.blockNumber,
header: stateRootBatch.header,
stateRoot: stateRootBatch.stateRoots[indexInBatch],
}
}
public async getStateBatchAppendedEventByBatchIndex(
batchIndex: number
): Promise<ethers.Event | null> {
const events = await this.contracts.l1.StateCommitmentChain.queryFilter(
this.contracts.l1.StateCommitmentChain.filters.StateBatchAppended(
batchIndex
)
)
if (events.length === 0) {
return null
} else if (events.length > 1) {
// Should never happen!
throw new Error(`found more than one StateBatchAppended event`)
} else {
return events[0]
}
}
public async getStateBatchAppendedEventByTransactionIndex(
transactionIndex: number
): Promise<ethers.Event | null> {
const isEventHi = (event: ethers.Event, index: number) => {
const prevTotalElements = event.args._prevTotalElements.toNumber()
return index < prevTotalElements
}
const isEventLo = (event: ethers.Event, index: number) => {
const prevTotalElements = event.args._prevTotalElements.toNumber()
const batchSize = event.args._batchSize.toNumber()
return index >= prevTotalElements + batchSize
}
const totalBatches: ethers.BigNumber =
await this.contracts.l1.StateCommitmentChain.getTotalBatches()
if (totalBatches.eq(0)) {
return null
}
let lowerBound = 0
let upperBound = totalBatches.toNumber() - 1
let batchEvent: ethers.Event | null =
await this.getStateBatchAppendedEventByBatchIndex(upperBound)
if (isEventLo(batchEvent, transactionIndex)) {
// Upper bound is too low, means this transaction doesn't have a corresponding state batch yet.
return null
} else if (!isEventHi(batchEvent, transactionIndex)) {
// Upper bound is not too low and also not too high. This means the upper bound event is the
// one we're looking for! Return it.
return batchEvent
}
// Binary search to find the right event. The above checks will guarantee that the event does
// exist and that we'll find it during this search.
while (lowerBound < upperBound) {
const middleOfBounds = Math.floor((lowerBound + upperBound) / 2)
batchEvent = await this.getStateBatchAppendedEventByBatchIndex(
middleOfBounds
)
if (isEventHi(batchEvent, transactionIndex)) {
upperBound = middleOfBounds
} else if (isEventLo(batchEvent, transactionIndex)) {
lowerBound = middleOfBounds
} else {
break
}
}
return batchEvent
}
public async getStateRootBatchByTransactionIndex(
transactionIndex: number
): Promise<StateRootBatch | null> {
const stateBatchAppendedEvent =
await this.getStateBatchAppendedEventByTransactionIndex(transactionIndex)
if (stateBatchAppendedEvent === null) {
return null
}
const stateBatchTransaction = await stateBatchAppendedEvent.getTransaction()
const [stateRoots] =
this.contracts.l1.StateCommitmentChain.interface.decodeFunctionData(
'appendStateBatch',
stateBatchTransaction.data
)
return {
blockNumber: stateBatchAppendedEvent.blockNumber,
stateRoots,
header: {
batchIndex: stateBatchAppendedEvent.args._batchIndex,
batchRoot: stateBatchAppendedEvent.args._batchRoot,
batchSize: stateBatchAppendedEvent.args._batchSize,
prevTotalElements: stateBatchAppendedEvent.args._prevTotalElements,
extraData: stateBatchAppendedEvent.args._extraData,
},
}
}
} }
import { BigNumber } from 'ethers' import { Event, BigNumber } from 'ethers'
import { Provider, BlockTag } from '@ethersproject/abstract-provider' import { Provider, BlockTag } from '@ethersproject/abstract-provider'
import { import {
MessageLike, MessageLike,
...@@ -12,6 +12,8 @@ import { ...@@ -12,6 +12,8 @@ import {
OEContracts, OEContracts,
MessageReceipt, MessageReceipt,
CustomBridges, CustomBridges,
StateRoot,
StateRootBatch,
} from './types' } from './types'
/** /**
...@@ -225,4 +227,62 @@ export interface ICrossChainProvider { ...@@ -225,4 +227,62 @@ export interface ICrossChainProvider {
* @returns Estimated amount of time remaining (in blocks) before the message can be executed. * @returns Estimated amount of time remaining (in blocks) before the message can be executed.
*/ */
estimateMessageWaitTimeBlocks(message: MessageLike): Promise<number> estimateMessageWaitTimeBlocks(message: MessageLike): Promise<number>
/**
* Queries the current challenge period in seconds from the StateCommitmentChain.
*
* @returns Current challenge period in seconds.
*/
getChallengePeriodSeconds(): Promise<number>
/**
* Queries the current challenge period in blocks from the StateCommitmentChain. Estimation is
* based on the challenge period in seconds divided by the L1 block time.
*
* @returns Current challenge period in blocks.
*/
getChallengePeriodBlocks(): Promise<number>
/**
* Returns the state root that corresponds to a given message. This is the state root for the
* block in which the transaction was included, as published to the StateCommitmentChain. If the
* state root for the given message has not been published yet, this function returns null.
*
* @param message Message to find a state root for.
* @returns State root for the block in which the message was created.
*/
getMessageStateRoot(message: MessageLike): Promise<StateRoot | null>
/**
* Returns the StateBatchAppended event that was emitted when the batch with a given index was
* created. Returns null if no such event exists (the batch has not been submitted).
*
* @param batchIndex Index of the batch to find an event for.
* @returns StateBatchAppended event for the batch, or null if no such batch exists.
*/
getStateBatchAppendedEventByBatchIndex(
batchIndex: number
): Promise<Event | null>
/**
* Returns the StateBatchAppended event for the batch that includes the transaction with the
* given index. Returns null if no such event exists.
*
* @param transactionIndex Index of the L2 transaction to find an event for.
* @returns StateBatchAppended event for the batch that includes the given transaction by index.
*/
getStateBatchAppendedEventByTransactionIndex(
transactionIndex: number
): Promise<Event | null>
/**
* Returns information about the state root batch that included the state root for the given
* transaction by index. Returns null if no such state root has been published yet.
*
* @param transactionIndex Index of the L2 transaction to find a state root batch for.
* @returns State root batch for the given transaction index, or null if none exists yet.
*/
getStateRootBatchByTransactionIndex(
transactionIndex: number
): Promise<StateRootBatch | null>
} }
...@@ -212,9 +212,19 @@ export interface StateRootBatchHeader { ...@@ -212,9 +212,19 @@ export interface StateRootBatchHeader {
} }
/** /**
* State root batch, including header and actual state roots. * Information about a state root, including header, block number, and root iself.
*/
export interface StateRoot {
blockNumber: number
header: StateRootBatchHeader
stateRoot: string
}
/**
* Information about a batch of state roots.
*/ */
export interface StateRootBatch { export interface StateRootBatch {
blockNumber: number
header: StateRootBatchHeader header: StateRootBatchHeader
stateRoots: string[] stateRoots: string[]
} }
......
pragma solidity ^0.8.9;
contract MockSCC {
event StateBatchAppended(
uint256 indexed _batchIndex,
bytes32 _batchRoot,
uint256 _batchSize,
uint256 _prevTotalElements,
bytes _extraData
);
struct StateBatchAppendedArgs {
uint256 batchIndex;
bytes32 batchRoot;
uint256 batchSize;
uint256 prevTotalElements;
bytes extraData;
}
// Window in seconds, will resolve to 100 blocks.
uint256 public FRAUD_PROOF_WINDOW = 1500;
uint256 public batches = 0;
StateBatchAppendedArgs public sbaParams;
function getTotalBatches() public view returns (uint256) {
return batches;
}
function setSBAParams(
StateBatchAppendedArgs memory _args
) public {
sbaParams = _args;
}
function appendStateBatch(
bytes32[] memory _roots,
uint256 _shouldStartAtIndex
) public {
batches++;
emit StateBatchAppended(
sbaParams.batchIndex,
sbaParams.batchRoot,
sbaParams.batchSize,
sbaParams.prevTotalElements,
sbaParams.extraData
);
}
}
...@@ -8,7 +8,10 @@ import { ...@@ -8,7 +8,10 @@ import {
CONTRACT_ADDRESSES, CONTRACT_ADDRESSES,
hashCrossChainMessage, hashCrossChainMessage,
omit, omit,
MessageStatus,
CrossChainMessage,
} from '../src' } from '../src'
import { DUMMY_MESSAGE } from './helpers'
describe('CrossChainProvider', () => { describe('CrossChainProvider', () => {
describe('construction', () => { describe('construction', () => {
...@@ -250,13 +253,7 @@ describe('CrossChainProvider', () => { ...@@ -250,13 +253,7 @@ describe('CrossChainProvider', () => {
for (const n of [1, 2, 4, 8]) { for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, async () => { it(`should find ${n} messages when the transaction emits ${n} messages`, async () => {
const messages = [...Array(n)].map(() => { const messages = [...Array(n)].map(() => {
return { return DUMMY_MESSAGE
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
}) })
const tx = await l1Messenger.triggerSentMessageEvents(messages) const tx = await l1Messenger.triggerSentMessageEvents(messages)
...@@ -309,13 +306,7 @@ describe('CrossChainProvider', () => { ...@@ -309,13 +306,7 @@ describe('CrossChainProvider', () => {
for (const n of [1, 2, 4, 8]) { for (const n of [1, 2, 4, 8]) {
it(`should find ${n} messages when the transaction emits ${n} messages`, async () => { it(`should find ${n} messages when the transaction emits ${n} messages`, async () => {
const messages = [...Array(n)].map(() => { const messages = [...Array(n)].map(() => {
return { return DUMMY_MESSAGE
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
}) })
const tx = await l1Messenger.triggerSentMessageEvents(messages) const tx = await l1Messenger.triggerSentMessageEvents(messages)
...@@ -736,15 +727,7 @@ describe('CrossChainProvider', () => { ...@@ -736,15 +727,7 @@ describe('CrossChainProvider', () => {
describe('when the input is a TransactionLike', () => { describe('when the input is a TransactionLike', () => {
describe('when the transaction sent exactly one message', () => { describe('when the transaction sent exactly one message', () => {
it('should return the CrossChainMessage sent in the transaction', async () => { it('should return the CrossChainMessage sent in the transaction', async () => {
const message = { const tx = await l1Messenger.triggerSentMessageEvents([DUMMY_MESSAGE])
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
const tx = await l1Messenger.triggerSentMessageEvents([message])
const foundCrossChainMessages = const foundCrossChainMessages =
await provider.getMessagesByTransaction(tx) await provider.getMessagesByTransaction(tx)
const resolved = await provider.toCrossChainMessage(tx) const resolved = await provider.toCrossChainMessage(tx)
...@@ -755,13 +738,7 @@ describe('CrossChainProvider', () => { ...@@ -755,13 +738,7 @@ describe('CrossChainProvider', () => {
describe('when the transaction sent more than one message', () => { describe('when the transaction sent more than one message', () => {
it('should throw an error', async () => { it('should throw an error', async () => {
const messages = [...Array(2)].map(() => { const messages = [...Array(2)].map(() => {
return { return DUMMY_MESSAGE
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
}) })
const tx = await l1Messenger.triggerSentMessageEvents(messages) const tx = await l1Messenger.triggerSentMessageEvents(messages)
...@@ -783,45 +760,203 @@ describe('CrossChainProvider', () => { ...@@ -783,45 +760,203 @@ describe('CrossChainProvider', () => {
}) })
describe('getMessageStatus', () => { describe('getMessageStatus', () => {
let scc: Contract
let l1Messenger: Contract
let l2Messenger: Contract
let provider: CrossChainProvider
beforeEach(async () => {
// TODO: Get rid of the nested awaits here. Could be a good first issue for someone.
scc = (await (await ethers.getContractFactory('MockSCC')).deploy()) as any
l1Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l1: {
L1CrossDomainMessenger: l1Messenger.address,
StateCommitmentChain: scc.address,
},
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
})
const sendAndGetDummyMessage = async (direction: MessageDirection) => {
const messenger =
direction === MessageDirection.L1_TO_L2 ? l1Messenger : l2Messenger
const tx = await messenger.triggerSentMessageEvents([DUMMY_MESSAGE])
return (
await provider.getMessagesByTransaction(tx, {
direction,
})
)[0]
}
const submitStateRootBatchForMessage = async (
message: CrossChainMessage
) => {
await scc.setSBAParams({
batchIndex: 0,
batchRoot: ethers.constants.HashZero,
batchSize: 1,
prevTotalElements: message.blockNumber,
extraData: '0x',
})
await scc.appendStateBatch([ethers.constants.HashZero], 0)
}
describe('when the message is an L1 => L2 message', () => { describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => { describe('when the message has not been executed on L2 yet', () => {
it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE') it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE
)
})
}) })
describe('when the message has been executed on L2', () => { describe('when the message has been executed on L2', () => {
it('should return a status of RELAYED') it('should return a status of RELAYED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.RELAYED
)
})
}) })
describe('when the message has been executed but failed', () => { describe('when the message has been executed but failed', () => {
it('should return a status of FAILED_L1_TO_L2_MESSAGE') it('should return a status of FAILED_L1_TO_L2_MESSAGE', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L1_TO_L2
)
await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.FAILED_L1_TO_L2_MESSAGE
)
})
}) })
}) })
describe('when the message is an L2 => L1 message', () => { describe('when the message is an L2 => L1 message', () => {
describe('when the message state root has not been published', () => { describe('when the message state root has not been published', () => {
it('should return a status of STATE_ROOT_NOT_PUBLISHED') it('should return a status of STATE_ROOT_NOT_PUBLISHED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.STATE_ROOT_NOT_PUBLISHED
)
})
}) })
describe('when the message state root is still in the challenge period', () => { describe('when the message state root is still in the challenge period', () => {
it('should return a status of IN_CHALLENGE_PERIOD') it('should return a status of IN_CHALLENGE_PERIOD', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.IN_CHALLENGE_PERIOD
)
})
}) })
describe('when the message is no longer in the challenge period', () => { describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => { describe('when the message has been relayed successfully', () => {
it('should return a status of RELAYED') it('should return a status of RELAYED', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodBlocks()
for (let x = 0; x < challengePeriod + 1; x++) {
await ethers.provider.send('evm_mine', [])
}
await l1Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.RELAYED
)
})
}) })
describe('when the message has been relayed but the relay failed', () => { describe('when the message has been relayed but the relay failed', () => {
it('should return a status of READY_FOR_RELAY') it('should return a status of READY_FOR_RELAY', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodBlocks()
for (let x = 0; x < challengePeriod + 1; x++) {
await ethers.provider.send('evm_mine', [])
}
await l1Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message),
])
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.READY_FOR_RELAY
)
})
}) })
describe('when the message has not been relayed', () => { describe('when the message has not been relayed', () => {
it('should return a status of READY_FOR_RELAY') it('should return a status of READY_FOR_RELAY', async () => {
const message = await sendAndGetDummyMessage(
MessageDirection.L2_TO_L1
)
await submitStateRootBatchForMessage(message)
const challengePeriod = await provider.getChallengePeriodBlocks()
for (let x = 0; x < challengePeriod + 1; x++) {
await ethers.provider.send('evm_mine', [])
}
expect(await provider.getMessageStatus(message)).to.equal(
MessageStatus.READY_FOR_RELAY
)
})
}) })
}) })
}) })
describe('when the message does not exist', () => { describe('when the message does not exist', () => {
// TODO: Figure out if this is the correct behavior. Mark suggests perhaps returning null.
it('should throw an error') it('should throw an error')
}) })
}) })
......
export const DUMMY_MESSAGE = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
}
export * from './constants'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment