Commit c9123af4 authored by smartcontracts's avatar smartcontracts Committed by GitHub

Merge pull request #1979 from ethereum-optimism/sc/sdk-wait-for-receipt

feat(sdk): implement waitForMessageReceipt
parents 9195dcf2 0b6eca57
......@@ -5,6 +5,7 @@ import {
TransactionReceipt,
} from '@ethersproject/abstract-provider'
import { ethers, BigNumber, Event } from 'ethers'
import { sleep } from '@eth-optimism/core-utils'
import {
ICrossChainProvider,
OEContracts,
......@@ -395,15 +396,27 @@ export class CrossChainProvider implements ICrossChainProvider {
return null
}
public async waitForMessageReciept(
public async waitForMessageReceipt(
message: MessageLike,
opts?: {
opts: {
confirmations?: number
pollIntervalMs?: number
timeoutMs?: number
}
} = {}
): Promise<MessageReceipt> {
throw new Error('Not implemented')
let totalTimeMs = 0
while (totalTimeMs < (opts.timeoutMs || Infinity)) {
const tick = Date.now()
const receipt = await this.getMessageReceipt(message)
if (receipt !== null) {
return receipt
} else {
await sleep(opts.pollIntervalMs || 4000)
totalTimeMs += Date.now() - tick
}
}
throw new Error(`timed out waiting for message receipt`)
}
public async estimateL2MessageGasLimit(
......
......@@ -186,7 +186,7 @@ export interface ICrossChainProvider {
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
waitForMessageReciept(
waitForMessageReceipt(
message: MessageLike,
opts?: {
confirmations?: number
......
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainERC20Pair', () => {
describe('construction', () => {
it('should have a messenger', () => {})
it('should have a messenger')
describe('when the token is a standard bridge token', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when the token is SNX', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when the token is DAI', () => {
it('should resolve the correct bridge', () => {})
it('should resolve the correct bridge')
})
describe('when a custom adapter is provided', () => {
it('should use the custom adapter', () => {})
it('should use the custom adapter')
})
})
describe('deposit', () => {
describe('when the user has enough balance and allowance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is ETH', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is SNX', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
describe('when the token is DAI', () => {
it('should trigger a token deposit', () => {})
it('should trigger a token deposit')
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
it('should throw an error')
})
describe('when the user has not given enough allowance to the bridge', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('withdraw', () => {
describe('when the user has enough balance', () => {
describe('when the token is a standard bridge token', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is ETH', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is SNX', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
describe('when the token is DAI', () => {
it('should trigger a token withdrawal', () => {})
it('should trigger a token withdrawal')
})
})
describe('when the user does not have enough balance', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('populateTransaction', () => {
describe('deposit', () => {
it('should populate the transaction with the correct values', () => {})
it('should populate the transaction with the correct values')
})
describe('withdraw', () => {
it('should populate the transaction with the correct values', () => {})
it('should populate the transaction with the correct values')
})
})
describe('estimateGas', () => {
describe('deposit', () => {
it('should estimate gas required for the transaction', () => {})
it('should estimate gas required for the transaction')
})
describe('withdraw', () => {
it('should estimate gas required for the transaction', () => {})
it('should estimate gas required for the transaction')
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainMessenger', () => {
describe('sendMessage', () => {
describe('when no l2GasLimit is provided', () => {
it('should send a message with an estimated l2GasLimit', () => {})
it('should send a message with an estimated l2GasLimit')
})
describe('when an l2GasLimit is provided', () => {
it('should send a message with the provided l2GasLimit', () => {})
it('should send a message with the provided l2GasLimit')
})
})
describe('resendMessage', () => {
describe('when the message being resent exists', () => {
it('should resend the message with the new gas limit', () => {})
it('should resend the message with the new gas limit')
})
describe('when the message being resent does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('finalizeMessage', () => {
describe('when the message being finalized exists', () => {
describe('when the message is ready to be finalized', () => {
it('should finalize the message', () => {})
it('should finalize the message')
})
describe('when the message is not ready to be finalized', () => {
it('should throw an error', () => {})
it('should throw an error')
})
describe('when the message has already been finalized', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('when the message being finalized does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
})
/* eslint-disable @typescript-eslint/no-empty-function */
import { expect } from './setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract } from 'ethers'
......@@ -383,24 +382,26 @@ describe('CrossChainProvider', () => {
describe('getMessagesByAddress', () => {
describe('when the address has sent messages', () => {
describe('when no direction is specified', () => {
it('should find all messages sent by the address', () => {})
it('should find all messages sent by the address')
})
describe('when a direction is specified', () => {
it('should find all messages only in the given direction', () => {})
it('should find all messages only in the given direction')
})
describe('when a block range is specified', () => {
it('should find all messages within the block range', () => {})
it('should find all messages within the block range')
})
describe('when both a direction and a block range are specified', () => {
it('should find all messages only in the given direction and within the block range', () => {})
it(
'should find all messages only in the given direction and within the block range'
)
})
})
describe('when the address has not sent messages', () => {
it('should find nothing', () => {})
it('should find nothing')
})
})
......@@ -784,44 +785,44 @@ describe('CrossChainProvider', () => {
describe('getMessageStatus', () => {
describe('when the message is an L1 => L2 message', () => {
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')
})
describe('when the message has been executed on L2', () => {
it('should return a status of RELAYED', () => {})
it('should return a status of RELAYED')
})
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')
})
})
describe('when the message is an L2 => L1 message', () => {
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')
})
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')
})
describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => {
it('should return a status of RELAYED', () => {})
it('should return a status of RELAYED')
})
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')
})
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')
})
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
......@@ -982,64 +983,157 @@ describe('CrossChainProvider', () => {
// track of
})
describe('waitForMessageReciept', () => {
describe('waitForMessageReceipt', () => {
let l2Messenger: Contract
let provider: CrossChainProvider
beforeEach(async () => {
l2Messenger = (await (
await ethers.getContractFactory('MockMessenger')
).deploy()) as any
provider = new CrossChainProvider({
l1Provider: ethers.provider,
l2Provider: ethers.provider,
l1ChainId: 31337,
contracts: {
l2: {
L2CrossDomainMessenger: l2Messenger.address,
},
},
})
})
describe('when the message receipt already exists', () => {
it('should immediately return the receipt', () => {})
it('should immediately return the receipt', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
const tx = await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
const messageReceipt = await provider.waitForMessageReceipt(message)
expect(messageReceipt.receiptStatus).to.equal(1)
expect(
omit(messageReceipt.transactionReceipt, 'confirmations')
).to.deep.equal(
omit(
await ethers.provider.getTransactionReceipt(tx.hash),
'confirmations'
)
)
})
})
describe('when the message receipt does not exist already', () => {
describe('when no extra options are provided', () => {
it('should wait for the receipt to be published', () => {})
it('should wait forever for the receipt if the receipt is never published', () => {})
it('should wait for the receipt to be published', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
setTimeout(async () => {
await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message),
])
}, 5000)
const tick = Date.now()
const messageReceipt = await provider.waitForMessageReceipt(message)
const tock = Date.now()
expect(messageReceipt.receiptStatus).to.equal(1)
expect(tock - tick).to.be.greaterThan(5000)
})
it('should wait forever for the receipt if the receipt is never published', () => {
// Not sure how to easily test this without introducing some sort of cancellation token
// I don't want the promise to loop forever and make the tests never finish.
})
})
describe('when a timeout is provided', () => {
it('should throw an error if the timeout is reached', () => {})
})
})
it('should throw an error if the timeout is reached', async () => {
const message = {
direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
}
describe('when the message does not exist', () => {
it('should throw an error', () => {})
await expect(
provider.waitForMessageReceipt(message, {
timeoutMs: 10000,
})
).to.be.rejectedWith('timed out waiting for message receipt')
})
})
})
})
describe('estimateL2MessageGasLimit', () => {
it('should perform a gas estimation of the L2 action', () => {})
it('should perform a gas estimation of the L2 action')
})
describe('estimateMessageWaitTimeBlocks', () => {
describe('when the message exists', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
it('should return the estimated blocks until the message will be confirmed on L2', () => {})
it(
'should return the estimated blocks until the message will be confirmed on L2'
)
})
describe('when the message has been executed on L2', () => {
it('should return 0', () => {})
it('should return 0')
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the state root has not been published', () => {
it('should return the estimated blocks until the state root will be published and pass the challenge period', () => {})
it(
'should return the estimated blocks until the state root will be published and pass the challenge period'
)
})
describe('when the state root is within the challenge period', () => {
it('should return the estimated blocks until the state root passes the challenge period', () => {})
it(
'should return the estimated blocks until the state root passes the challenge period'
)
})
describe('when the state root passes the challenge period', () => {
it('should return 0', () => {})
it('should return 0')
})
})
})
describe('when the message does not exist', () => {
it('should throw an error', () => {})
it('should throw an error')
})
})
describe('estimateMessageWaitTimeSeconds', () => {
it('should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time', () => {})
it(
'should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time'
)
})
})
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