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

feat(sdk): add Bedrock support to SDK (#3086)

* core-utils: add bedrock types

* sdk: implement bedrock functionality

* tests: update for bedrock

* sdk: add hardhat deposit task

* circleci: run deposit task in ci

* tsconfig: cleanup

* contracts-bedrock: make commitment interval larger

* changeset: add
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent 28649d64
---
'@eth-optimism/sdk': minor
'@eth-optimism/contracts-bedrock': patch
'@eth-optimism/core-utils': patch
---
Updates the SDK to be compatible with Bedrock (via the "bedrock: true" constructor param). Updates the build pipeline for contracts-bedrock to export a properly formatted dist folder that matches our other packages.
...@@ -457,6 +457,10 @@ jobs: ...@@ -457,6 +457,10 @@ jobs:
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--network devnetL1 --network devnetL1
working_directory: packages/contracts-bedrock working_directory: packages/contracts-bedrock
- run:
name: Deposit ERC20 through the bridge
command: npx hardhat deposit --network devnetL1
working_directory: packages/sdk
- run: - run:
name: Check the status name: Check the status
command: npx hardhat check-op-node command: npx hardhat check-op-node
......
...@@ -8,7 +8,7 @@ const l1GenesisTimestamp = ...@@ -8,7 +8,7 @@ const l1GenesisTimestamp =
: Math.floor(Date.now() / 1000) : Math.floor(Date.now() / 1000)
const config = { const config = {
submissionInterval: 6, submissionInterval: 20,
genesisOutput: ethers.constants.HashZero, genesisOutput: ethers.constants.HashZero,
historicalBlocks: 0, historicalBlocks: 0,
l1StartingBlockTag: 'earliest', l1StartingBlockTag: 'earliest',
......
...@@ -9,6 +9,18 @@ import { ...@@ -9,6 +9,18 @@ import {
big1, big1,
} from './encoding' } from './encoding'
/**
* Bedrock output oracle data.
*/
export interface BedrockOutputData {
outputRoot: string
l1Timestamp: number
l2BlockNumber: number
}
/**
* Bedrock state commitment
*/
export interface OutputRootProof { export interface OutputRootProof {
version: string version: string
stateRoot: string stateRoot: string
...@@ -16,6 +28,23 @@ export interface OutputRootProof { ...@@ -16,6 +28,23 @@ export interface OutputRootProof {
latestBlockhash: string latestBlockhash: string
} }
/**
* Bedrock proof data required to finalize an L2 to L1 message.
*/
export interface BedrockCrossChainMessageProof {
outputRootProof: OutputRootProof
withdrawalProof: string
}
/**
* Parameters that govern the L2OutputOracle.
*/
export type L2OutputOracleParameters = {
submissionInterval: number
startingBlockNumber: number
l2BlockTime: number
}
/** /**
* Hahses a cross domain message. * Hahses a cross domain message.
* *
......
...@@ -2,6 +2,9 @@ import { HardhatUserConfig } from 'hardhat/types' ...@@ -2,6 +2,9 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers' import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle' import '@nomiclabs/hardhat-waffle'
import 'hardhat-deploy'
import './tasks/deposit'
const config: HardhatUserConfig = { const config: HardhatUserConfig = {
solidity: { solidity: {
...@@ -10,6 +13,25 @@ const config: HardhatUserConfig = { ...@@ -10,6 +13,25 @@ const config: HardhatUserConfig = {
paths: { paths: {
sources: './test/contracts', sources: './test/contracts',
}, },
networks: {
devnetL1: {
url: 'http://localhost:8545',
accounts: [
'ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
],
},
},
external: {
contracts: [
{
artifacts: '../contracts-bedrock/artifacts',
},
],
deployments: {
devnetL1: ['../contracts-bedrock/deployments/devnetL1'],
goerli: ['../contracts-bedrock/deployments/goerli'],
},
},
} }
export default config export default config
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
"ethereum-waffle": "^3.4.0", "ethereum-waffle": "^3.4.0",
"ethers": "^5.6.8", "ethers": "^5.6.8",
"hardhat": "^2.9.6", "hardhat": "^2.9.6",
"hardhat-deploy": "^0.11.4",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"typedoc": "^0.22.13", "typedoc": "^0.22.13",
"mocha": "^10.0.0" "mocha": "^10.0.0"
...@@ -48,6 +49,7 @@ ...@@ -48,6 +49,7 @@
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.5.31", "@eth-optimism/contracts": "0.5.31",
"@eth-optimism/core-utils": "0.9.2", "@eth-optimism/core-utils": "0.9.2",
"@eth-optimism/contracts-bedrock": "0.5.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"merkletreejs": "^0.2.27", "merkletreejs": "^0.2.27",
"rlp": "^2.2.7" "rlp": "^2.2.7"
......
...@@ -42,12 +42,12 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter { ...@@ -42,12 +42,12 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
.map((event) => { .map((event) => {
return { return {
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
from: event.args._from, from: event.args.from,
to: event.args._to, to: event.args.to,
l1Token: ethers.constants.AddressZero, l1Token: ethers.constants.AddressZero,
l2Token: predeploys.OVM_ETH, l2Token: predeploys.OVM_ETH,
amount: event.args._amount, amount: event.args.amount,
data: event.args._data, data: event.args.extraData,
logIndex: event.logIndex, logIndex: event.logIndex,
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
...@@ -76,19 +76,19 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter { ...@@ -76,19 +76,19 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
.filter((event) => { .filter((event) => {
// Only find ETH withdrawals. // Only find ETH withdrawals.
return ( return (
hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) && hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
hexStringEquals(event.args._l2Token, predeploys.OVM_ETH) hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
) )
}) })
.map((event) => { .map((event) => {
return { return {
direction: MessageDirection.L2_TO_L1, direction: MessageDirection.L2_TO_L1,
from: event.args._from, from: event.args.from,
to: event.args._to, to: event.args.to,
l1Token: event.args._l1Token, l1Token: event.args.l1Token,
l2Token: event.args._l2Token, l2Token: event.args.l2Token,
amount: event.args._amount, amount: event.args.amount,
data: event.args._data, data: event.args.extraData,
logIndex: event.logIndex, logIndex: event.logIndex,
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
...@@ -178,7 +178,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter { ...@@ -178,7 +178,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
amount, amount,
0, // L1 gas not required. 0, // L1 gas not required.
'0x', // No data. '0x', // No data.
opts?.overrides || {} {
...omit(opts?.overrides || {}, 'value'),
value: this.messenger.bedrock ? amount : 0,
}
) )
} else { } else {
return this.l2Bridge.populateTransaction.withdrawTo( return this.l2Bridge.populateTransaction.withdrawTo(
...@@ -187,7 +190,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter { ...@@ -187,7 +190,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
amount, amount,
0, // L1 gas not required. 0, // L1 gas not required.
'0x', // No data. '0x', // No data.
opts?.overrides || {} {
...omit(opts?.overrides || {}, 'value'),
value: this.messenger.bedrock ? amount : 0,
}
) )
} }
}, },
......
...@@ -12,7 +12,8 @@ import { ...@@ -12,7 +12,8 @@ import {
TransactionResponse, TransactionResponse,
BlockTag, BlockTag,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
import { getContractInterface, predeploys } from '@eth-optimism/contracts' import { predeploys } from '@eth-optimism/contracts'
import { getContractInterface } from '@eth-optimism/contracts-bedrock'
import { hexStringEquals } from '@eth-optimism/core-utils' import { hexStringEquals } from '@eth-optimism/core-utils'
import { import {
...@@ -54,7 +55,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -54,7 +55,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
) )
this.l2Bridge = new Contract( this.l2Bridge = new Contract(
toAddress(opts.l2Bridge), toAddress(opts.l2Bridge),
getContractInterface('IL2ERC20Bridge'), getContractInterface('L2StandardBridge'),
this.messenger.l2Provider this.messenger.l2Provider
) )
} }
...@@ -82,19 +83,19 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -82,19 +83,19 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
// adapter. Bridges that are not the ETH bridge should not be able to handle or even // adapter. Bridges that are not the ETH bridge should not be able to handle or even
// present ETH deposits or withdrawals. // present ETH deposits or withdrawals.
return ( return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) && !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH) !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
) )
}) })
.map((event) => { .map((event) => {
return { return {
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
from: event.args._from, from: event.args.from,
to: event.args._to, to: event.args.to,
l1Token: event.args._l1Token, l1Token: event.args.l1Token,
l2Token: event.args._l2Token, l2Token: event.args.l2Token,
amount: event.args._amount, amount: event.args.amount,
data: event.args._data, data: event.args.extraData,
logIndex: event.logIndex, logIndex: event.logIndex,
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
...@@ -125,19 +126,19 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -125,19 +126,19 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
// adapter. Bridges that are not the ETH bridge should not be able to handle or even // adapter. Bridges that are not the ETH bridge should not be able to handle or even
// present ETH deposits or withdrawals. // present ETH deposits or withdrawals.
return ( return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) && !hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH) !hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
) )
}) })
.map((event) => { .map((event) => {
return { return {
direction: MessageDirection.L2_TO_L1, direction: MessageDirection.L2_TO_L1,
from: event.args._from, from: event.args.from,
to: event.args._to, to: event.args.to,
l1Token: event.args._l1Token, l1Token: event.args.l1Token,
l2Token: event.args._l2Token, l2Token: event.args.l2Token,
amount: event.args._amount, amount: event.args.amount,
data: event.args._data, data: event.args.extraData,
logIndex: event.logIndex, logIndex: event.logIndex,
blockNumber: event.blockNumber, blockNumber: event.blockNumber,
transactionHash: event.transactionHash, transactionHash: event.transactionHash,
...@@ -156,10 +157,9 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -156,10 +157,9 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
try { try {
const contract = new Contract( const contract = new Contract(
toAddress(l2Token), toAddress(l2Token),
getContractInterface('L2StandardERC20'), getContractInterface('OptimismMintableERC20'),
this.messenger.l2Provider this.messenger.l2Provider
) )
// Don't support ETH deposits or withdrawals via this bridge. // Don't support ETH deposits or withdrawals via this bridge.
if ( if (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) || hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
...@@ -170,6 +170,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -170,6 +170,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
// Make sure the L1 token matches. // Make sure the L1 token matches.
const remoteL1Token = await contract.l1Token() const remoteL1Token = await contract.l1Token()
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) { if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false return false
} }
...@@ -203,7 +204,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -203,7 +204,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
const token = new Contract( const token = new Contract(
toAddress(l1Token), toAddress(l1Token),
getContractInterface('L2StandardERC20'), // Any ERC20 will do getContractInterface('OptimismMintableERC20'), // Any ERC20 will do
this.messenger.l1Provider this.messenger.l1Provider
) )
...@@ -270,7 +271,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter { ...@@ -270,7 +271,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
const token = new Contract( const token = new Contract(
toAddress(l1Token), toAddress(l1Token),
getContractInterface('L2StandardERC20'), // Any ERC20 will do getContractInterface('OptimismMintableERC20'), // Any ERC20 will do
this.messenger.l1Provider this.messenger.l1Provider
) )
......
...@@ -5,13 +5,27 @@ import { ...@@ -5,13 +5,27 @@ import {
TransactionReceipt, TransactionReceipt,
TransactionResponse, TransactionResponse,
TransactionRequest, TransactionRequest,
Log,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer' import { Signer } from '@ethersproject/abstract-signer'
import { ethers, BigNumber, Overrides, CallOverrides } from 'ethers' import { ethers, BigNumber, Overrides, CallOverrides } from 'ethers'
import { sleep, remove0x } from '@eth-optimism/core-utils' import {
import { predeploys } from '@eth-optimism/contracts' sleep,
remove0x,
toHexString,
toRpcHexString,
hashWithdrawal,
encodeCrossDomainMessageV0,
hashCrossDomainMessage,
L2OutputOracleParameters,
BedrockOutputData,
BedrockCrossChainMessageProof,
} from '@eth-optimism/core-utils'
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import * as rlp from 'rlp'
import { import {
CoreCrossChainMessage,
ICrossChainMessenger, ICrossChainMessenger,
OEContracts, OEContracts,
OEContractsLike, OEContractsLike,
...@@ -42,10 +56,8 @@ import { ...@@ -42,10 +56,8 @@ import {
DeepPartial, DeepPartial,
getAllOEContracts, getAllOEContracts,
getBridgeAdapters, getBridgeAdapters,
hashCrossChainMessage,
makeMerkleTreeProof, makeMerkleTreeProof,
makeStateTrieProof, makeStateTrieProof,
encodeCrossChainMessage,
DEPOSIT_CONFIRMATION_BLOCKS, DEPOSIT_CONFIRMATION_BLOCKS,
CHAIN_BLOCK_TIMES, CHAIN_BLOCK_TIMES,
} from './utils' } from './utils'
...@@ -59,6 +71,9 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -59,6 +71,9 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public bridges: BridgeAdapters public bridges: BridgeAdapters
public depositConfirmationBlocks: number public depositConfirmationBlocks: number
public l1BlockTimeSeconds: number public l1BlockTimeSeconds: number
public bedrock: boolean
private _l2OutputOracleParameters: L2OutputOracleParameters
/** /**
* Creates a new CrossChainProvider instance. * Creates a new CrossChainProvider instance.
...@@ -72,6 +87,7 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -72,6 +87,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
* @param opts.l1BlockTimeSeconds Optional estimated block time in seconds for the L1 chain. * @param opts.l1BlockTimeSeconds Optional estimated block time in seconds for the L1 chain.
* @param opts.contracts Optional contract address overrides. * @param opts.contracts Optional contract address overrides.
* @param opts.bridges Optional bridge address list. * @param opts.bridges Optional bridge address list.
* @param opts.bedrock Whether or not to enable Bedrock compatibility.
*/ */
constructor(opts: { constructor(opts: {
l1SignerOrProvider: SignerOrProviderLike l1SignerOrProvider: SignerOrProviderLike
...@@ -82,7 +98,9 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -82,7 +98,9 @@ export class CrossChainMessenger implements ICrossChainMessenger {
l1BlockTimeSeconds?: NumberLike l1BlockTimeSeconds?: NumberLike
contracts?: DeepPartial<OEContractsLike> contracts?: DeepPartial<OEContractsLike>
bridges?: BridgeAdapterData bridges?: BridgeAdapterData
bedrock?: boolean
}) { }) {
this.bedrock = opts.bedrock ?? false
this.l1SignerOrProvider = toSignerOrProvider(opts.l1SignerOrProvider) this.l1SignerOrProvider = toSignerOrProvider(opts.l1SignerOrProvider)
this.l2SignerOrProvider = toSignerOrProvider(opts.l2SignerOrProvider) this.l2SignerOrProvider = toSignerOrProvider(opts.l2SignerOrProvider)
...@@ -151,6 +169,26 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -151,6 +169,26 @@ export class CrossChainMessenger implements ICrossChainMessenger {
} }
} }
public async getL2OutputOracleParameters(): Promise<L2OutputOracleParameters> {
if (this._l2OutputOracleParameters) {
return this._l2OutputOracleParameters
}
this._l2OutputOracleParameters = {
submissionInterval: (
await this.contracts.l1.L2OutputOracle.SUBMISSION_INTERVAL()
).toNumber(),
startingBlockNumber: (
await this.contracts.l1.L2OutputOracle.STARTING_BLOCK_NUMBER()
).toNumber(),
l2BlockTime: (
await this.contracts.l1.L2OutputOracle.L2_BLOCK_TIME()
).toNumber(),
}
return this._l2OutputOracleParameters
}
public async getMessagesByTransaction( public async getMessagesByTransaction(
transaction: TransactionLike, transaction: TransactionLike,
opts: { opts: {
...@@ -203,6 +241,19 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -203,6 +241,19 @@ export class CrossChainMessenger implements ICrossChainMessenger {
return parsed.name === 'SentMessage' return parsed.name === 'SentMessage'
}) })
.map((log) => { .map((log) => {
// Try to pull out the value field, but only if the very next log is a SentMessageExtraData
// event which was introduced in the Bedrock upgrade.
let value = ethers.BigNumber.from(0)
if (receipt.logs.length > log.logIndex + 1) {
const next = receipt.logs[log.logIndex + 1]
if (next.address === messenger.address) {
const nextParsed = messenger.interface.parseLog(next)
if (nextParsed.name === 'SentMessageExtension1') {
value = nextParsed.args.value
}
}
}
// Convert each SentMessage log into a message object // Convert each SentMessage log into a message object
const parsed = messenger.interface.parseLog(log) const parsed = messenger.interface.parseLog(log)
return { return {
...@@ -211,7 +262,8 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -211,7 +262,8 @@ export class CrossChainMessenger implements ICrossChainMessenger {
sender: parsed.args.sender, sender: parsed.args.sender,
message: parsed.args.message, message: parsed.args.message,
messageNonce: parsed.args.messageNonce, messageNonce: parsed.args.messageNonce,
gasLimit: parsed.args.gasLimit, value,
minGasLimit: parsed.args.gasLimit,
logIndex: log.logIndex, logIndex: log.logIndex,
blockNumber: log.blockNumber, blockNumber: log.blockNumber,
transactionHash: log.transactionHash, transactionHash: log.transactionHash,
...@@ -373,20 +425,32 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -373,20 +425,32 @@ export class CrossChainMessenger implements ICrossChainMessenger {
} }
} else { } else {
if (receipt === null) { if (receipt === null) {
const stateRoot = await this.getMessageStateRoot(resolved) let timestamp: number
if (stateRoot === null) { if (this.bedrock) {
return MessageStatus.STATE_ROOT_NOT_PUBLISHED const output = await this.getMessageBedrockOutput(resolved)
if (output === null) {
return MessageStatus.STATE_ROOT_NOT_PUBLISHED
}
timestamp = output.l1Timestamp
} else { } else {
const challengePeriod = await this.getChallengePeriodSeconds() const stateRoot = await this.getMessageStateRoot(resolved)
const targetBlock = await this.l1Provider.getBlock( if (stateRoot === null) {
stateRoot.batch.blockNumber return MessageStatus.STATE_ROOT_NOT_PUBLISHED
)
const latestBlock = await this.l1Provider.getBlock('latest')
if (targetBlock.timestamp + challengePeriod > latestBlock.timestamp) {
return MessageStatus.IN_CHALLENGE_PERIOD
} else {
return MessageStatus.READY_FOR_RELAY
} }
const bn = stateRoot.batch.blockNumber
const block = await this.l1Provider.getBlock(bn)
timestamp = block.timestamp
}
const challengePeriod = await this.getChallengePeriodSeconds()
const latestBlock = await this.l1Provider.getBlock('latest')
if (timestamp + challengePeriod > latestBlock.timestamp) {
return MessageStatus.IN_CHALLENGE_PERIOD
} else {
return MessageStatus.READY_FOR_RELAY
} }
} else { } else {
if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) { if (receipt.receiptStatus === MessageReceiptStatus.RELAYED_SUCCEEDED) {
...@@ -402,7 +466,14 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -402,7 +466,14 @@ export class CrossChainMessenger implements ICrossChainMessenger {
message: MessageLike message: MessageLike
): Promise<MessageReceipt> { ): Promise<MessageReceipt> {
const resolved = await this.toCrossChainMessage(message) const resolved = await this.toCrossChainMessage(message)
const messageHash = hashCrossChainMessage(resolved) const messageHash = hashCrossDomainMessage(
resolved.messageNonce,
resolved.sender,
resolved.target,
resolved.value,
resolved.minGasLimit,
resolved.message
)
// Here we want the messenger that will receive the message, not the one that sent it. // Here we want the messenger that will receive the message, not the one that sent it.
const messenger = const messenger =
...@@ -642,11 +713,56 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -642,11 +713,56 @@ export class CrossChainMessenger implements ICrossChainMessenger {
} }
public async getChallengePeriodSeconds(): Promise<number> { public async getChallengePeriodSeconds(): Promise<number> {
const challengePeriod = const challengePeriod = this.bedrock
await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW() ? await this.contracts.l1.OptimismPortal.FINALIZATION_PERIOD_SECONDS()
: await this.contracts.l1.StateCommitmentChain.FRAUD_PROOF_WINDOW()
return challengePeriod.toNumber() return challengePeriod.toNumber()
} }
public async getMessageBedrockOutput(
message: MessageLike
): Promise<BedrockOutputData | null> {
const resolved = await this.toCrossChainMessage(message)
// Outputs 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`)
}
const l2OutputOracleParameters = await this.getL2OutputOracleParameters()
// TODO: better way to do this
let number =
resolved.blockNumber - l2OutputOracleParameters.startingBlockNumber
while (number % l2OutputOracleParameters.submissionInterval !== 0) {
number++
}
// TODO: Handle old messages from before Bedrock upgrade.
const events = await this.contracts.l1.L2OutputOracle.queryFilter(
this.contracts.l1.L2OutputOracle.filters.OutputProposed(
undefined,
undefined,
number
)
)
if (events.length === 0) {
return null
}
// Should not happen
if (events.length > 1) {
throw new Error(`multiple output roots found for message`)
}
return {
outputRoot: events[0].args.l2Output,
l1Timestamp: events[0].args.l1Timestamp.toNumber(),
l2BlockNumber: events[0].args.l2BlockNumber.toNumber(),
}
}
public async getMessageStateRoot( public async getMessageStateRoot(
message: MessageLike message: MessageLike
): Promise<StateRoot | null> { ): Promise<StateRoot | null> {
...@@ -826,8 +942,12 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -826,8 +942,12 @@ export class CrossChainMessenger implements ICrossChainMessenger {
// https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#mappings-and-dynamic-arrays // https://docs.soliditylang.org/en/v0.8.4/internals/layout_in_storage.html#mappings-and-dynamic-arrays
const messageSlot = ethers.utils.keccak256( const messageSlot = ethers.utils.keccak256(
ethers.utils.keccak256( ethers.utils.keccak256(
encodeCrossChainMessage(resolved) + encodeCrossDomainMessageV0(
remove0x(this.contracts.l2.L2CrossDomainMessenger.address) resolved.target,
resolved.sender,
resolved.message,
resolved.messageNonce
) + remove0x(this.contracts.l2.L2CrossDomainMessenger.address)
) + '00'.repeat(32) ) + '00'.repeat(32)
) )
...@@ -848,9 +968,142 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -848,9 +968,142 @@ export class CrossChainMessenger implements ICrossChainMessenger {
stateRoot.stateRootIndexInBatch stateRoot.stateRootIndexInBatch
), ),
}, },
stateTrieWitness: stateTrieProof.accountProof, stateTrieWitness: toHexString(rlp.encode(stateTrieProof.accountProof)),
storageTrieWitness: stateTrieProof.storageProof, storageTrieWitness: toHexString(rlp.encode(stateTrieProof.storageProof)),
}
}
public async getBedrockMessageProof(
message: MessageLike
): Promise<
[BedrockCrossChainMessageProof, BedrockOutputData, CoreCrossChainMessage]
> {
const resolved = await this.toCrossChainMessage(message)
if (resolved.direction === MessageDirection.L1_TO_L2) {
throw new Error(`can only generate proofs for L2 to L1 messages`)
}
const output = await this.getMessageBedrockOutput(resolved)
if (output === null) {
throw new Error(`state root for message not yet published`)
}
const receipt = await this.l2Provider.getTransactionReceipt(
resolved.transactionHash
)
interface WithdrawalEntry {
withdrawalInitiated: any
withdrawalInitiatedExtension1: any
}
// Handle multiple withdrawals in the same tx and be backwards
// compatible without WithdrawalInitiatedExtension1
const logs: Partial<{ number: WithdrawalEntry }> = {}
for (const [i, log] of Object.entries(receipt.logs)) {
if (log.address === predeploys.OVM_L2ToL1MessagePasser) {
const decoded =
this.contracts.l2.L2ToL1MessagePasser.interface.parseLog(log)
// Find the withdrawal initiated events
if (decoded.name === 'WithdrawalInitiated') {
logs[log.logIndex] = {
withdrawalInitiated: decoded.args,
withdrawalInitiatedExtension1: null,
}
if (receipt.logs[i + 1]) {
const next =
this.contracts.l2.L2ToL1MessagePasser.interface.parseLog(
receipt.logs[i + 1]
)
if (next.name === 'WithdrawalInitiatedExtension1') {
logs[log.logIndex].withdrawalInitiatedExtension1 = next.args
}
}
}
}
}
// TODO(tynes): be able to handle transactions that do multiple withdrawals
// in a single transaction. Right now just go for the first one.
const withdrawal = Object.values(logs)[0]
if (!withdrawal) {
throw new Error(
`Cannot find withdrawal logs for ${resolved.transactionHash}`
)
}
const withdrawalHash = hashWithdrawal(
withdrawal.withdrawalInitiated.nonce,
withdrawal.withdrawalInitiated.sender,
withdrawal.withdrawalInitiated.target,
withdrawal.withdrawalInitiated.value,
withdrawal.withdrawalInitiated.gasLimit,
withdrawal.withdrawalInitiated.data
)
// Sanity check
if (withdrawal.withdrawalInitiatedExtension1) {
if (withdrawal.withdrawalInitiatedExtension1.hash !== withdrawalHash) {
throw new Error(`Mismatched withdrawal hashes`)
}
} }
// TODO: turn into util
const preimage = ethers.utils.defaultAbiCoder.encode(
['bytes32', 'uint256'],
[withdrawalHash, ethers.constants.HashZero]
)
const isMessageSent =
await this.contracts.l2.L2ToL1MessagePasser.sentMessages(withdrawalHash)
if (!isMessageSent) {
throw new Error(`Withdrawal not initiated on L2`)
}
const messageSlot = ethers.utils.keccak256(preimage)
const stateTrieProof = await makeStateTrieProof(
this.l2Provider as ethers.providers.JsonRpcProvider,
output.l2BlockNumber,
this.contracts.l2.OVM_L2ToL1MessagePasser.address,
messageSlot
)
// Sanity check that the value is set to 1 in the state
if (!stateTrieProof.storageValue.eq(1)) {
throw new Error(`Withdrawal hash ${withdrawalHash} is not set in state`)
}
const block = await (
this.l2Provider as ethers.providers.JsonRpcProvider
).send('eth_getBlockByNumber', [
toRpcHexString(output.l2BlockNumber),
false,
])
return [
{
outputRootProof: {
// TODO: Handle multiple versions in the future
version: ethers.constants.HashZero,
stateRoot: block.stateRoot,
withdrawerStorageRoot: stateTrieProof.storageRoot,
latestBlockhash: block.hash,
},
withdrawalProof: ethers.utils.RLP.encode(stateTrieProof.storageProof),
// withdrawalProof: toHexString(rlp.encode(stateTrieProof.storageProof)),
},
output,
// TODO(tynes): use better type, typechain?
{
messageNonce: withdrawal.withdrawalInitiated.nonce,
sender: withdrawal.withdrawalInitiated.sender,
target: withdrawal.withdrawalInitiated.target,
value: withdrawal.withdrawalInitiated.value,
minGasLimit: withdrawal.withdrawalInitiated.gasLimit,
message: withdrawal.withdrawalInitiated.data,
},
]
} }
public async sendMessage( public async sendMessage(
...@@ -1033,15 +1286,30 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -1033,15 +1286,30 @@ export class CrossChainMessenger implements ICrossChainMessenger {
throw new Error(`cannot resend L2 to L1 message`) throw new Error(`cannot resend L2 to L1 message`)
} }
return this.contracts.l1.L1CrossDomainMessenger.populateTransaction.replayMessage( if (this.bedrock) {
resolved.target, return this.populateTransaction.finalizeMessage(resolved, {
resolved.sender, ...(opts || {}),
resolved.message, overrides: {
resolved.messageNonce, ...opts?.overrides,
resolved.gasLimit, gasLimit: messageGasLimit,
messageGasLimit, },
opts?.overrides || {} })
) } else {
const legacyL1XDM = new ethers.Contract(
this.contracts.l1.L1CrossDomainMessenger.address,
getContractInterface('L1CrossDomainMessenger'),
this.l1SignerOrProvider
)
return legacyL1XDM.populateTransaction.replayMessage(
resolved.target,
resolved.sender,
resolved.message,
resolved.messageNonce,
resolved.minGasLimit,
messageGasLimit,
opts?.overrides || {}
)
}
}, },
finalizeMessage: async ( finalizeMessage: async (
...@@ -1055,15 +1323,48 @@ export class CrossChainMessenger implements ICrossChainMessenger { ...@@ -1055,15 +1323,48 @@ export class CrossChainMessenger implements ICrossChainMessenger {
throw new Error(`cannot finalize L1 to L2 message`) throw new Error(`cannot finalize L1 to L2 message`)
} }
const proof = await this.getMessageProof(resolved) if (this.bedrock) {
return this.contracts.l1.L1CrossDomainMessenger.populateTransaction.relayMessage( const [proof, output, withdrawalTx] = await this.getBedrockMessageProof(
resolved.target, message
resolved.sender, )
resolved.message,
resolved.messageNonce, return this.contracts.l1.OptimismPortal.populateTransaction.finalizeWithdrawalTransaction(
proof, [
opts?.overrides || {} withdrawalTx.messageNonce,
) withdrawalTx.sender,
withdrawalTx.target,
withdrawalTx.value,
withdrawalTx.minGasLimit,
withdrawalTx.message,
],
output.l2BlockNumber,
[
proof.outputRootProof.version,
proof.outputRootProof.stateRoot,
proof.outputRootProof.withdrawerStorageRoot,
proof.outputRootProof.latestBlockhash,
],
proof.withdrawalProof
)
} else {
// L1CrossDomainMessenger relayMessage is the only method that isn't fully backwards
// compatible, so we need to use the legacy interface. When we fully upgrade to Bedrock we
// should be able to remove this code.
const proof = await this.getMessageProof(resolved)
const legacyL1XDM = new ethers.Contract(
this.contracts.l1.L1CrossDomainMessenger.address,
getContractInterface('L1CrossDomainMessenger'),
this.l1SignerOrProvider
)
return legacyL1XDM.populateTransaction.relayMessage(
resolved.target,
resolved.sender,
resolved.message,
resolved.messageNonce,
proof,
opts?.overrides || {}
)
}
}, },
depositETH: async ( depositETH: async (
......
...@@ -6,8 +6,13 @@ import { ...@@ -6,8 +6,13 @@ import {
TransactionResponse, TransactionResponse,
} from '@ethersproject/abstract-provider' } from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer' import { Signer } from '@ethersproject/abstract-signer'
import {
BedrockCrossChainMessageProof,
BedrockOutputData,
} from '@eth-optimism/core-utils'
import { import {
CoreCrossChainMessage,
MessageLike, MessageLike,
MessageRequestLike, MessageRequestLike,
TransactionLike, TransactionLike,
...@@ -91,6 +96,11 @@ export interface ICrossChainMessenger { ...@@ -91,6 +96,11 @@ export interface ICrossChainMessenger {
*/ */
l1BlockTimeSeconds: number l1BlockTimeSeconds: number
/**
* Whether or not Bedrock compatibility is enabled.
*/
bedrock: boolean
/** /**
* Retrieves all cross chain messages sent within a given transaction. * Retrieves all cross chain messages sent within a given transaction.
* *
...@@ -291,6 +301,16 @@ export interface ICrossChainMessenger { ...@@ -291,6 +301,16 @@ export interface ICrossChainMessenger {
*/ */
getChallengePeriodSeconds(): Promise<number> getChallengePeriodSeconds(): Promise<number>
/**
* Returns the Bedrock output root that corresponds to the given message.
*
* @param message Message to get the Bedrock output root for.
* @returns Bedrock output root.
*/
getMessageBedrockOutput(
message: MessageLike
): Promise<BedrockOutputData | null>
/** /**
* Returns the state root that corresponds to a given message. This is the state root for the * 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 * block in which the transaction was included, as published to the StateCommitmentChain. If the
...@@ -342,6 +362,18 @@ export interface ICrossChainMessenger { ...@@ -342,6 +362,18 @@ export interface ICrossChainMessenger {
*/ */
getMessageProof(message: MessageLike): Promise<CrossChainMessageProof> getMessageProof(message: MessageLike): Promise<CrossChainMessageProof>
/**
* Generates the bedrock proof required to finalize an L2 to L1 message.
*
* @param message Message to generate a proof for.
* @returns Proof that can be used to finalize the message.
*/
getBedrockMessageProof(
message: MessageLike
): Promise<
[BedrockCrossChainMessageProof, BedrockOutputData, CoreCrossChainMessage]
>
/** /**
* Sends a given cross chain message. Where the message is sent depends on the direction attached * Sends a given cross chain message. Where the message is sent depends on the direction attached
* to the message itself. * to the message itself.
......
...@@ -17,6 +17,7 @@ export enum L1ChainID { ...@@ -17,6 +17,7 @@ export enum L1ChainID {
GOERLI = 5, GOERLI = 5,
KOVAN = 42, KOVAN = 42,
HARDHAT_LOCAL = 31337, HARDHAT_LOCAL = 31337,
BEDROCK_LOCAL_DEVNET = 900,
} }
/** /**
...@@ -28,6 +29,7 @@ export enum L2ChainID { ...@@ -28,6 +29,7 @@ export enum L2ChainID {
OPTIMISM_KOVAN = 69, OPTIMISM_KOVAN = 69,
OPTIMISM_HARDHAT_LOCAL = 31337, OPTIMISM_HARDHAT_LOCAL = 31337,
OPTIMISM_HARDHAT_DEVNET = 17, OPTIMISM_HARDHAT_DEVNET = 17,
OPTIMISM_BEDROCK_LOCAL_DEVNET = 901,
} }
/** /**
...@@ -40,6 +42,9 @@ export interface OEL1Contracts { ...@@ -40,6 +42,9 @@ export interface OEL1Contracts {
StateCommitmentChain: Contract StateCommitmentChain: Contract
CanonicalTransactionChain: Contract CanonicalTransactionChain: Contract
BondManager: Contract BondManager: Contract
// Bedrock
OptimismPortal: Contract
L2OutputOracle: Contract
} }
/** /**
...@@ -48,6 +53,7 @@ export interface OEL1Contracts { ...@@ -48,6 +53,7 @@ export interface OEL1Contracts {
export interface OEL2Contracts { export interface OEL2Contracts {
L2CrossDomainMessenger: Contract L2CrossDomainMessenger: Contract
L2StandardBridge: Contract L2StandardBridge: Contract
L2ToL1MessagePasser: Contract
OVM_L1BlockNumber: Contract OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract OVM_DeployerWhitelist: Contract
...@@ -174,7 +180,9 @@ export interface CoreCrossChainMessage { ...@@ -174,7 +180,9 @@ export interface CoreCrossChainMessage {
sender: string sender: string
target: string target: string
message: string message: string
messageNonce: number messageNonce: BigNumber
value: BigNumber
minGasLimit: BigNumber
} }
/** /**
...@@ -183,7 +191,6 @@ export interface CoreCrossChainMessage { ...@@ -183,7 +191,6 @@ export interface CoreCrossChainMessage {
*/ */
export interface CrossChainMessage extends CoreCrossChainMessage { export interface CrossChainMessage extends CoreCrossChainMessage {
direction: MessageDirection direction: MessageDirection
gasLimit: number
logIndex: number logIndex: number
blockNumber: number blockNumber: number
transactionHash: string transactionHash: string
......
...@@ -8,6 +8,7 @@ export const DEPOSIT_CONFIRMATION_BLOCKS: { ...@@ -8,6 +8,7 @@ export const DEPOSIT_CONFIRMATION_BLOCKS: {
[L2ChainID.OPTIMISM_KOVAN]: 12 as const, [L2ChainID.OPTIMISM_KOVAN]: 12 as const,
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const, [L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const,
[L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const, [L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const,
[L2ChainID.OPTIMISM_BEDROCK_LOCAL_DEVNET]: 2 as const,
} }
export const CHAIN_BLOCK_TIMES: { export const CHAIN_BLOCK_TIMES: {
...@@ -17,4 +18,5 @@ export const CHAIN_BLOCK_TIMES: { ...@@ -17,4 +18,5 @@ export const CHAIN_BLOCK_TIMES: {
[L1ChainID.GOERLI]: 15 as const, [L1ChainID.GOERLI]: 15 as const,
[L1ChainID.KOVAN]: 4 as const, [L1ChainID.KOVAN]: 4 as const,
[L1ChainID.HARDHAT_LOCAL]: 1 as const, [L1ChainID.HARDHAT_LOCAL]: 1 as const,
[L1ChainID.BEDROCK_LOCAL_DEVNET]: 15 as const,
} }
import { getContractInterface, predeploys } from '@eth-optimism/contracts' import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { getContractInterface as getContractInterfaceBedrock } from '@eth-optimism/contracts-bedrock'
import { ethers, Contract } from 'ethers' import { ethers, Contract } from 'ethers'
import { toAddress } from './coercion' import { toAddress } from './coercion'
...@@ -23,9 +24,11 @@ import { ...@@ -23,9 +24,11 @@ import {
/** /**
* Full list of default L2 contract addresses. * Full list of default L2 contract addresses.
* TODO(tynes): migrate to predeploys from contracts-bedrock
*/ */
export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = { export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger, L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger,
L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
L2StandardBridge: predeploys.L2StandardBridge, L2StandardBridge: predeploys.L2StandardBridge,
OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber, OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber,
OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser, OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
...@@ -65,6 +68,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -65,6 +68,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain: CanonicalTransactionChain:
'0x5E4e65926BA27467555EB562121fac00D24E9dD2' as const, '0x5E4e65926BA27467555EB562121fac00D24E9dD2' as const,
BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1' as const, BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -79,6 +84,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -79,6 +84,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain: CanonicalTransactionChain:
'0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74' as const, '0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74' as const,
BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740' as const, BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -93,6 +100,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -93,6 +100,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain: CanonicalTransactionChain:
'0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D' as const, '0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D' as const,
BondManager: '0xfC2ab6987C578218f99E85d61Dcf4814A26637Bd' as const, BondManager: '0xfC2ab6987C578218f99E85d61Dcf4814A26637Bd' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -107,6 +116,8 @@ export const CONTRACT_ADDRESSES: { ...@@ -107,6 +116,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain: CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const, '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const, BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -121,6 +132,24 @@ export const CONTRACT_ADDRESSES: { ...@@ -121,6 +132,24 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain: CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const, '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const, BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[L2ChainID.OPTIMISM_BEDROCK_LOCAL_DEVNET]: {
l1: {
AddressManager: '0x5FbDB2315678afecb367f032d93F642f64180aa3' as const,
L1CrossDomainMessenger:
'0x0165878A594ca255338adfa4d48449f69242Eb8F' as const,
L1StandardBridge: '0x8A791620dd6260079BF849Dc5567aDC3F2FdC318' as const,
StateCommitmentChain:
'0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9' as const,
CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
OptimismPortal: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
L2OutputOracle: '0x5FbDB2315678afecb367f032d93F642f64180aa3' as const,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}, },
...@@ -196,11 +225,21 @@ export const getOEContract = ( ...@@ -196,11 +225,21 @@ export const getOEContract = (
) )
} }
// Bedrock interfaces are backwards compatible. We can prefer Bedrock interfaces over legacy
// interfaces if they exist.
const name = NAME_REMAPPING[contractName] || contractName
let iface: ethers.utils.Interface
try {
iface = getContractInterfaceBedrock(name)
} catch (err) {
iface = getContractInterface(name)
}
return new Contract( return new Contract(
toAddress( toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName] opts.address || addresses.l1[contractName] || addresses.l2[contractName]
), ),
getContractInterface(NAME_REMAPPING[contractName] || contractName), iface,
opts.signerOrProvider opts.signerOrProvider
) )
} }
...@@ -235,6 +274,8 @@ export const getAllOEContracts = ( ...@@ -235,6 +274,8 @@ export const getAllOEContracts = (
StateCommitmentChain: undefined, StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined, CanonicalTransactionChain: undefined,
BondManager: undefined, BondManager: undefined,
OptimismPortal: undefined,
L2OutputOracle: undefined,
}, },
l2: DEFAULT_L2_CONTRACT_ADDRESSES, l2: DEFAULT_L2_CONTRACT_ADDRESSES,
} }
......
export * from './coercion' export * from './coercion'
export * from './contracts' export * from './contracts'
export * from './message-encoding'
export * from './type-utils' export * from './type-utils'
export * from './misc-utils' export * from './misc-utils'
export * from './merkle-utils' export * from './merkle-utils'
......
/* Imports: External */ /* Imports: External */
import { ethers } from 'ethers' import { ethers, BigNumber } from 'ethers'
import { import {
fromHexString, fromHexString,
toHexString, toHexString,
toRpcHexString, toRpcHexString,
} from '@eth-optimism/core-utils' } from '@eth-optimism/core-utils'
import { MerkleTree } from 'merkletreejs' import { MerkleTree } from 'merkletreejs'
import * as rlp from 'rlp'
/** /**
* Generates a Merkle proof (using the particular scheme we use within Lib_MerkleTree). * Generates a Merkle proof (using the particular scheme we use within Lib_MerkleTree).
...@@ -60,8 +59,10 @@ export const makeStateTrieProof = async ( ...@@ -60,8 +59,10 @@ export const makeStateTrieProof = async (
address: string, address: string,
slot: string slot: string
): Promise<{ ): Promise<{
accountProof: string accountProof: string[]
storageProof: string storageProof: string[]
storageValue: BigNumber
storageRoot: string
}> => { }> => {
const proof = await provider.send('eth_getProof', [ const proof = await provider.send('eth_getProof', [
address, address,
...@@ -70,7 +71,9 @@ export const makeStateTrieProof = async ( ...@@ -70,7 +71,9 @@ export const makeStateTrieProof = async (
]) ])
return { return {
accountProof: toHexString(rlp.encode(proof.accountProof)), accountProof: proof.accountProof,
storageProof: toHexString(rlp.encode(proof.storageProof[0].proof)), storageProof: proof.storageProof[0].proof,
storageValue: BigNumber.from(proof.storageProof[0].value),
storageRoot: proof.storageHash,
} }
} }
import { getContractInterface } from '@eth-optimism/contracts'
import { ethers } from 'ethers'
import { CoreCrossChainMessage } from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
* locations within the Optimism smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
*/
export const encodeCrossChainMessage = (
message: CoreCrossChainMessage
): string => {
return getContractInterface('L2CrossDomainMessenger').encodeFunctionData(
'relayMessage',
[message.target, message.sender, message.message, message.messageNonce]
)
}
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
* within the Optimism smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
* @returns Canonical hash of the message.
*/
export const hashCrossChainMessage = (
message: CoreCrossChainMessage
): string => {
return ethers.utils.solidityKeccak256(
['bytes'],
[encodeCrossChainMessage(message)]
)
}
import { task, types } from 'hardhat/config'
import '@nomiclabs/hardhat-ethers'
import 'hardhat-deploy'
import {
predeploys,
getContractInterface,
} from '@eth-optimism/contracts-bedrock'
import { Event } from 'ethers'
import {
CrossChainMessenger,
StandardBridgeAdapter,
MessageStatus,
} from '../src'
// TODO(tynes): this task could be modularized in the future
// so that it can deposit an arbitrary token. Right now it
// deploys a WETH9 contract, mints some WETH9 and then
// deposits that into L2 through the StandardBridge
task('deposit', 'Deposits WETH9 onto L2.')
.addParam(
'l2ProviderUrl',
'L2 provider URL.',
'http://localhost:9545',
types.string
)
.addParam(
'opNodeProviderUrl',
'op-node provider URL',
'http://localhost:7545',
types.string
)
.setAction(async (args, hre) => {
const { utils } = hre.ethers
const signers = await hre.ethers.getSigners()
if (signers.length === 0) {
throw new Error('No configured signers')
}
// Use the first configured signer for simplicity
const signer = signers[0]
const address = await signer.getAddress()
console.log(`Using signer ${address}`)
// Ensure that the signer has a balance before trying to
// do anything
const balance = await signer.getBalance()
if (balance.eq(0)) {
throw new Error('Signer has no balance')
}
const l2Provider = new hre.ethers.providers.StaticJsonRpcProvider(
args.l2ProviderUrl
)
const Deployment__L2OutputOracleProxy = await hre.deployments.get(
'L2OutputOracleProxy'
)
const l2Signer = new hre.ethers.Wallet(
hre.network.config.accounts[0],
l2Provider
)
const Artifact__WETH9 = await hre.deployments.getArtifact('WETH9')
const Factory__WETH9 = new hre.ethers.ContractFactory(
Artifact__WETH9.abi,
Artifact__WETH9.bytecode,
signer
)
const Deployment__OptimismMintableERC20TokenFactory =
await hre.deployments.get('OptimismMintableERC20Factory')
const Deployment__OptimismPortalProxy = await hre.deployments.get(
'OptimismPortalProxy'
)
const Deployment__L1StandardBridgeProxy = await hre.deployments.get(
'L1StandardBridgeProxy'
)
const Deployment__L1CrossDomainMessengerProxy = await hre.deployments.get(
'L1CrossDomainMessengerProxy'
)
const messenger = new CrossChainMessenger({
l1SignerOrProvider: signer,
l2SignerOrProvider: l2Signer,
l1ChainId: await signer.getChainId(),
l2ChainId: await l2Signer.getChainId(),
bridges: {
Standard: {
Adapter: StandardBridgeAdapter,
l1Bridge: Deployment__L1StandardBridgeProxy.address,
l2Bridge: predeploys.L2StandardBridge,
},
},
contracts: {
l1: {
L1StandardBridge: Deployment__L1StandardBridgeProxy.address,
L1CrossDomainMessenger:
Deployment__L1CrossDomainMessengerProxy.address,
L2OutputOracle: Deployment__L2OutputOracleProxy.address,
OptimismPortal: Deployment__OptimismPortalProxy.address,
},
},
bedrock: true,
})
const OptimismMintableERC20TokenFactory = await hre.ethers.getContractAt(
Deployment__OptimismMintableERC20TokenFactory.abi,
predeploys.OptimismMintableERC20Factory,
l2Signer
)
console.log('Deploying WETH9 to L1')
const WETH9 = await Factory__WETH9.deploy()
await WETH9.deployTransaction.wait()
console.log(`Deployed to ${WETH9.address}`)
console.log('Creating L2 WETH9')
const deployTx =
await OptimismMintableERC20TokenFactory.createOptimismMintableERC20(
WETH9.address,
'L2 Wrapped Ether',
'L2-WETH9'
)
const receipt = await deployTx.wait()
const event = receipt.events.find(
(e: Event) => e.event === 'OptimismMintableERC20Created'
)
if (!event) {
throw new Error('Unable to find OptimismMintableERC20Created event')
}
// TODO(tynes): may need to be updated based on
// https://github.com/ethereum-optimism/optimism/pull/3104
const l2WethAddress = event.args.remoteToken
console.log(`Deployed to ${l2WethAddress}`)
console.log('Wrapping ETH')
const deposit = await signer.sendTransaction({
value: utils.parseEther('1'),
to: WETH9.address,
})
await deposit.wait()
console.log('ETH wrapped')
console.log(`Approving WETH9 for deposit`)
const approvalTx = await messenger.approveERC20(
WETH9.address,
l2WethAddress,
hre.ethers.constants.MaxUint256
)
await approvalTx.wait()
console.log('WETH9 approved')
console.log('Depositing WETH9 to L2')
const depositTx = await messenger.depositERC20(
WETH9.address,
l2WethAddress,
utils.parseEther('1')
)
await depositTx.wait()
console.log('ERC20 deposited')
const messageReceipt = await messenger.waitForMessageReceipt(depositTx)
if (messageReceipt.receiptStatus !== 1) {
throw new Error('deposit failed')
}
const L2WETH9 = new hre.ethers.Contract(
l2WethAddress,
getContractInterface('OptimismMintableERC20'),
l2Signer
)
const l2Balance = await L2WETH9.balanceOf(await signer.getAddress())
if (l2Balance.lt(utils.parseEther('1'))) {
throw new Error('bad deposit')
}
console.log('Deposit success')
console.log('Starting withdrawal')
const preBalance = await WETH9.balanceOf(signer.address)
const tx = await messenger.withdrawERC20(
WETH9.address,
l2WethAddress,
utils.parseEther('1')
)
await tx.wait()
setInterval(async () => {
const currentStatus = await messenger.getMessageStatus(tx)
console.log(`Message status: ${MessageStatus[currentStatus]}`)
}, 3000)
const now = Math.floor(Date.now() / 1000)
console.log('Waiting for message to be able to be relayed')
await messenger.waitForMessageStatus(tx, MessageStatus.READY_FOR_RELAY)
const finalize = await messenger.finalizeMessage(tx)
await finalize.wait()
console.log(`Took ${Math.floor(Date.now() / 1000) - now} seconds`)
const postBalance = await WETH9.balanceOf(signer.address)
const expectedBalance = preBalance.add(utils.parseEther('1'))
if (!expectedBalance.eq(postBalance)) {
throw new Error('Balance mismatch')
}
console.log('Withdrawal success')
})
...@@ -80,7 +80,8 @@ contract MockBridge { ...@@ -80,7 +80,8 @@ contract MockBridge {
address(0), address(0),
hex"1234", hex"1234",
1234, 1234,
12345678 12345678,
0
) )
); );
} }
...@@ -101,7 +102,8 @@ contract MockBridge { ...@@ -101,7 +102,8 @@ contract MockBridge {
address(0), address(0),
hex"1234", hex"1234",
1234, 1234,
12345678 12345678,
0
) )
); );
} }
......
...@@ -48,7 +48,8 @@ contract MockMessenger is ICrossDomainMessenger { ...@@ -48,7 +48,8 @@ contract MockMessenger is ICrossDomainMessenger {
address sender; address sender;
bytes message; bytes message;
uint256 messageNonce; uint256 messageNonce;
uint256 gasLimit; uint256 minGasLimit;
uint256 value;
} }
function doNothing() public { function doNothing() public {
...@@ -63,7 +64,7 @@ contract MockMessenger is ICrossDomainMessenger { ...@@ -63,7 +64,7 @@ contract MockMessenger is ICrossDomainMessenger {
_params.sender, _params.sender,
_params.message, _params.message,
_params.messageNonce, _params.messageNonce,
_params.gasLimit _params.minGasLimit
); );
} }
......
import { Provider } from '@ethersproject/abstract-provider' import { Provider } from '@ethersproject/abstract-provider'
import { expectApprox } from '@eth-optimism/core-utils' import { expectApprox, hashCrossDomainMessage } from '@eth-optimism/core-utils'
import { predeploys } from '@eth-optimism/contracts' import { predeploys } from '@eth-optimism/contracts'
import { Contract } from 'ethers' import { Contract } from 'ethers'
import { ethers } from 'hardhat' import { ethers } from 'hardhat'
...@@ -8,7 +8,6 @@ import { expect } from './setup' ...@@ -8,7 +8,6 @@ import { expect } from './setup'
import { import {
MessageDirection, MessageDirection,
CONTRACT_ADDRESSES, CONTRACT_ADDRESSES,
hashCrossChainMessage,
omit, omit,
MessageStatus, MessageStatus,
CrossChainMessage, CrossChainMessage,
...@@ -18,7 +17,7 @@ import { ...@@ -18,7 +17,7 @@ import {
L1ChainID, L1ChainID,
L2ChainID, L2ChainID,
} from '../src' } from '../src'
import { DUMMY_MESSAGE } from './helpers' import { DUMMY_MESSAGE, DUMMY_EXTENDED_MESSAGE } from './helpers'
describe('CrossChainMessenger', () => { describe('CrossChainMessenger', () => {
let l1Signer: any let l1Signer: any
...@@ -202,6 +201,8 @@ describe('CrossChainMessenger', () => { ...@@ -202,6 +201,8 @@ describe('CrossChainMessenger', () => {
StateCommitmentChain: '0x' + '14'.repeat(20), StateCommitmentChain: '0x' + '14'.repeat(20),
CanonicalTransactionChain: '0x' + '15'.repeat(20), CanonicalTransactionChain: '0x' + '15'.repeat(20),
BondManager: '0x' + '16'.repeat(20), BondManager: '0x' + '16'.repeat(20),
OptimismPortal: '0x' + '17'.repeat(20),
L2OutputOracle: '0x' + '18'.repeat(20),
}, },
l2: { l2: {
L2CrossDomainMessenger: '0x' + '22'.repeat(20), L2CrossDomainMessenger: '0x' + '22'.repeat(20),
...@@ -318,7 +319,8 @@ describe('CrossChainMessenger', () => { ...@@ -318,7 +319,8 @@ describe('CrossChainMessenger', () => {
target: message.target, target: message.target,
message: message.message, message: message.message,
messageNonce: ethers.BigNumber.from(message.messageNonce), messageNonce: ethers.BigNumber.from(message.messageNonce),
gasLimit: ethers.BigNumber.from(message.gasLimit), minGasLimit: ethers.BigNumber.from(message.minGasLimit),
value: ethers.BigNumber.from(message.value),
logIndex: i, logIndex: i,
blockNumber: tx.blockNumber, blockNumber: tx.blockNumber,
transactionHash: tx.hash, transactionHash: tx.hash,
...@@ -370,7 +372,8 @@ describe('CrossChainMessenger', () => { ...@@ -370,7 +372,8 @@ describe('CrossChainMessenger', () => {
target: message.target, target: message.target,
message: message.message, message: message.message,
messageNonce: ethers.BigNumber.from(message.messageNonce), messageNonce: ethers.BigNumber.from(message.messageNonce),
gasLimit: ethers.BigNumber.from(message.gasLimit), minGasLimit: ethers.BigNumber.from(message.minGasLimit),
value: ethers.BigNumber.from(message.value),
logIndex: i, logIndex: i,
blockNumber: tx.blockNumber, blockNumber: tx.blockNumber,
transactionHash: tx.hash, transactionHash: tx.hash,
...@@ -497,15 +500,8 @@ describe('CrossChainMessenger', () => { ...@@ -497,15 +500,8 @@ describe('CrossChainMessenger', () => {
describe('when the input is a CrossChainMessage', () => { describe('when the input is a CrossChainMessage', () => {
it('should return the input', async () => { it('should return the input', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
expect(await messenger.toCrossChainMessage(message)).to.deep.equal( expect(await messenger.toCrossChainMessage(message)).to.deep.equal(
...@@ -655,7 +651,14 @@ describe('CrossChainMessenger', () => { ...@@ -655,7 +651,14 @@ describe('CrossChainMessenger', () => {
) )
await l2Messenger.triggerRelayedMessageEvents([ await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect(await messenger.getMessageStatus(message)).to.equal( expect(await messenger.getMessageStatus(message)).to.equal(
...@@ -671,7 +674,14 @@ describe('CrossChainMessenger', () => { ...@@ -671,7 +674,14 @@ describe('CrossChainMessenger', () => {
) )
await l2Messenger.triggerFailedRelayedMessageEvents([ await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect(await messenger.getMessageStatus(message)).to.equal( expect(await messenger.getMessageStatus(message)).to.equal(
...@@ -722,7 +732,14 @@ describe('CrossChainMessenger', () => { ...@@ -722,7 +732,14 @@ describe('CrossChainMessenger', () => {
ethers.provider.send('evm_mine', []) ethers.provider.send('evm_mine', [])
await l1Messenger.triggerRelayedMessageEvents([ await l1Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect(await messenger.getMessageStatus(message)).to.equal( expect(await messenger.getMessageStatus(message)).to.equal(
...@@ -744,7 +761,14 @@ describe('CrossChainMessenger', () => { ...@@ -744,7 +761,14 @@ describe('CrossChainMessenger', () => {
ethers.provider.send('evm_mine', []) ethers.provider.send('evm_mine', [])
await l1Messenger.triggerFailedRelayedMessageEvents([ await l1Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect(await messenger.getMessageStatus(message)).to.equal( expect(await messenger.getMessageStatus(message)).to.equal(
...@@ -821,19 +845,19 @@ describe('CrossChainMessenger', () => { ...@@ -821,19 +845,19 @@ describe('CrossChainMessenger', () => {
describe('when the relay was successful', () => { describe('when the relay was successful', () => {
it('should return the receipt of the transaction that relayed the message', async () => { it('should return the receipt of the transaction that relayed the message', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
const tx = await l2Messenger.triggerRelayedMessageEvents([ const tx = await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
const messageReceipt = await messenger.getMessageReceipt(message) const messageReceipt = await messenger.getMessageReceipt(message)
...@@ -852,19 +876,19 @@ describe('CrossChainMessenger', () => { ...@@ -852,19 +876,19 @@ describe('CrossChainMessenger', () => {
describe('when the relay failed', () => { describe('when the relay failed', () => {
it('should return the receipt of the transaction that attempted to relay the message', async () => { it('should return the receipt of the transaction that attempted to relay the message', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
const tx = await l2Messenger.triggerFailedRelayedMessageEvents([ const tx = await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
const messageReceipt = await messenger.getMessageReceipt(message) const messageReceipt = await messenger.getMessageReceipt(message)
...@@ -883,23 +907,30 @@ describe('CrossChainMessenger', () => { ...@@ -883,23 +907,30 @@ describe('CrossChainMessenger', () => {
describe('when the relay failed more than once', () => { describe('when the relay failed more than once', () => {
it('should return the receipt of the last transaction that attempted to relay the message', async () => { it('should return the receipt of the last transaction that attempted to relay the message', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
await l2Messenger.triggerFailedRelayedMessageEvents([ await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
const tx = await l2Messenger.triggerFailedRelayedMessageEvents([ const tx = await l2Messenger.triggerFailedRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
const messageReceipt = await messenger.getMessageReceipt(message) const messageReceipt = await messenger.getMessageReceipt(message)
...@@ -919,15 +950,8 @@ describe('CrossChainMessenger', () => { ...@@ -919,15 +950,8 @@ describe('CrossChainMessenger', () => {
describe('when the message has not been relayed', () => { describe('when the message has not been relayed', () => {
it('should return null', async () => { it('should return null', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
await l2Messenger.doNothing() await l2Messenger.doNothing()
...@@ -965,19 +989,19 @@ describe('CrossChainMessenger', () => { ...@@ -965,19 +989,19 @@ describe('CrossChainMessenger', () => {
describe('when the message receipt already exists', () => { describe('when the message receipt already exists', () => {
it('should immediately return the receipt', async () => { it('should immediately return the receipt', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
const tx = await l2Messenger.triggerRelayedMessageEvents([ const tx = await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
const messageReceipt = await messenger.waitForMessageReceipt(message) const messageReceipt = await messenger.waitForMessageReceipt(message)
...@@ -997,20 +1021,20 @@ describe('CrossChainMessenger', () => { ...@@ -997,20 +1021,20 @@ describe('CrossChainMessenger', () => {
describe('when no extra options are provided', () => { describe('when no extra options are provided', () => {
it('should wait for the receipt to be published', async () => { it('should wait for the receipt to be published', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
setTimeout(async () => { setTimeout(async () => {
await l2Messenger.triggerRelayedMessageEvents([ await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
}, 5000) }, 5000)
...@@ -1030,15 +1054,8 @@ describe('CrossChainMessenger', () => { ...@@ -1030,15 +1054,8 @@ describe('CrossChainMessenger', () => {
describe('when a timeout is provided', () => { describe('when a timeout is provided', () => {
it('should throw an error if the timeout is reached', async () => { it('should throw an error if the timeout is reached', async () => {
const message = { const message = {
...DUMMY_EXTENDED_MESSAGE,
direction: MessageDirection.L1_TO_L2, direction: MessageDirection.L1_TO_L2,
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 0,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
await expect( await expect(
...@@ -1222,7 +1239,14 @@ describe('CrossChainMessenger', () => { ...@@ -1222,7 +1239,14 @@ describe('CrossChainMessenger', () => {
await l1Messenger.triggerSentMessageEvents([message]) await l1Messenger.triggerSentMessageEvents([message])
await l2Messenger.triggerRelayedMessageEvents([ await l2Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect( expect(
...@@ -1294,7 +1318,14 @@ describe('CrossChainMessenger', () => { ...@@ -1294,7 +1318,14 @@ describe('CrossChainMessenger', () => {
await l2Messenger.triggerSentMessageEvents([message]) await l2Messenger.triggerSentMessageEvents([message])
await l1Messenger.triggerRelayedMessageEvents([ await l1Messenger.triggerRelayedMessageEvents([
hashCrossChainMessage(message), hashCrossDomainMessage(
message.messageNonce,
message.sender,
message.target,
message.value,
message.minGasLimit,
message.message
),
]) ])
expect( expect(
......
import { ethers } from 'ethers'
export const DUMMY_MESSAGE = { export const DUMMY_MESSAGE = {
target: '0x' + '11'.repeat(20), target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20), sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64), message: '0x' + '33'.repeat(64),
messageNonce: 1234, messageNonce: ethers.BigNumber.from(1234),
gasLimit: 100000, value: ethers.BigNumber.from(0),
minGasLimit: ethers.BigNumber.from(5678),
}
export const DUMMY_EXTENDED_MESSAGE = {
...DUMMY_MESSAGE,
logIndex: 0,
blockNumber: 1234,
transactionHash: '0x' + '44'.repeat(32),
} }
import { Contract, Signer } from 'ethers'
import { ethers } from 'hardhat'
import { getContractFactory } from '@eth-optimism/contracts'
import { expect } from '../setup'
import {
CoreCrossChainMessage,
encodeCrossChainMessage,
hashCrossChainMessage,
} from '../../src'
describe('message encoding utils', () => {
let signers: Signer[]
before(async () => {
signers = (await ethers.getSigners()) as any
})
describe('encodeCrossChainMessage', () => {
let Lib_CrossDomainUtils: Contract
before(async () => {
Lib_CrossDomainUtils = (await getContractFactory(
'TestLib_CrossDomainUtils',
signers[0]
).deploy()) as any
})
it('should properly encode a message', async () => {
const message: CoreCrossChainMessage = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = encodeCrossChainMessage(message)
const expected = await Lib_CrossDomainUtils.encodeXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
describe('hashCrossChainMessage', () => {
let MessageEncodingHelper: Contract
before(async () => {
MessageEncodingHelper = (await (
await ethers.getContractFactory('MessageEncodingHelper')
).deploy()) as any
})
it('should properly hash a message', async () => {
const message: CoreCrossChainMessage = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '1234'.repeat(32),
messageNonce: 1234,
}
const actual = hashCrossChainMessage(message)
const expected = await MessageEncodingHelper.hashXDomainCalldata(
message.target,
message.sender,
message.message,
message.messageNonce
)
expect(actual).to.equal(expected)
})
})
})
{ {
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
"outDir": "./dist" "outDir": "./dist"
}, },
"include": [ "include": [
"src/**/*" "src/**/*"
] ]
......
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