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:
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \
--network devnetL1
working_directory: packages/contracts-bedrock
- run:
name: Deposit ERC20 through the bridge
command: npx hardhat deposit --network devnetL1
working_directory: packages/sdk
- run:
name: Check the status
command: npx hardhat check-op-node
......
......@@ -8,7 +8,7 @@ const l1GenesisTimestamp =
: Math.floor(Date.now() / 1000)
const config = {
submissionInterval: 6,
submissionInterval: 20,
genesisOutput: ethers.constants.HashZero,
historicalBlocks: 0,
l1StartingBlockTag: 'earliest',
......
......@@ -9,6 +9,18 @@ import {
big1,
} from './encoding'
/**
* Bedrock output oracle data.
*/
export interface BedrockOutputData {
outputRoot: string
l1Timestamp: number
l2BlockNumber: number
}
/**
* Bedrock state commitment
*/
export interface OutputRootProof {
version: string
stateRoot: string
......@@ -16,6 +28,23 @@ export interface OutputRootProof {
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.
*
......
......@@ -2,6 +2,9 @@ import { HardhatUserConfig } from 'hardhat/types'
import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import 'hardhat-deploy'
import './tasks/deposit'
const config: HardhatUserConfig = {
solidity: {
......@@ -10,6 +13,25 @@ const config: HardhatUserConfig = {
paths: {
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
......@@ -41,6 +41,7 @@
"ethereum-waffle": "^3.4.0",
"ethers": "^5.6.8",
"hardhat": "^2.9.6",
"hardhat-deploy": "^0.11.4",
"nyc": "^15.1.0",
"typedoc": "^0.22.13",
"mocha": "^10.0.0"
......@@ -48,6 +49,7 @@
"dependencies": {
"@eth-optimism/contracts": "0.5.31",
"@eth-optimism/core-utils": "0.9.2",
"@eth-optimism/contracts-bedrock": "0.5.2",
"lodash": "^4.17.21",
"merkletreejs": "^0.2.27",
"rlp": "^2.2.7"
......
......@@ -42,12 +42,12 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
.map((event) => {
return {
direction: MessageDirection.L1_TO_L2,
from: event.args._from,
to: event.args._to,
from: event.args.from,
to: event.args.to,
l1Token: ethers.constants.AddressZero,
l2Token: predeploys.OVM_ETH,
amount: event.args._amount,
data: event.args._data,
amount: event.args.amount,
data: event.args.extraData,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
......@@ -76,19 +76,19 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
.filter((event) => {
// Only find ETH withdrawals.
return (
hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L2_TO_L1,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
from: event.args.from,
to: event.args.to,
l1Token: event.args.l1Token,
l2Token: event.args.l2Token,
amount: event.args.amount,
data: event.args.extraData,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
......@@ -178,7 +178,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
amount,
0, // L1 gas not required.
'0x', // No data.
opts?.overrides || {}
{
...omit(opts?.overrides || {}, 'value'),
value: this.messenger.bedrock ? amount : 0,
}
)
} else {
return this.l2Bridge.populateTransaction.withdrawTo(
......@@ -187,7 +190,10 @@ export class ETHBridgeAdapter extends StandardBridgeAdapter {
amount,
0, // L1 gas not required.
'0x', // No data.
opts?.overrides || {}
{
...omit(opts?.overrides || {}, 'value'),
value: this.messenger.bedrock ? amount : 0,
}
)
}
},
......
......@@ -12,7 +12,8 @@ import {
TransactionResponse,
BlockTag,
} 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 {
......@@ -54,7 +55,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
)
this.l2Bridge = new Contract(
toAddress(opts.l2Bridge),
getContractInterface('IL2ERC20Bridge'),
getContractInterface('L2StandardBridge'),
this.messenger.l2Provider
)
}
......@@ -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
// present ETH deposits or withdrawals.
return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
!hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L1_TO_L2,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
from: event.args.from,
to: event.args.to,
l1Token: event.args.l1Token,
l2Token: event.args.l2Token,
amount: event.args.amount,
data: event.args.extraData,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
......@@ -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
// present ETH deposits or withdrawals.
return (
!hexStringEquals(event.args._l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args._l2Token, predeploys.OVM_ETH)
!hexStringEquals(event.args.l1Token, ethers.constants.AddressZero) &&
!hexStringEquals(event.args.l2Token, predeploys.OVM_ETH)
)
})
.map((event) => {
return {
direction: MessageDirection.L2_TO_L1,
from: event.args._from,
to: event.args._to,
l1Token: event.args._l1Token,
l2Token: event.args._l2Token,
amount: event.args._amount,
data: event.args._data,
from: event.args.from,
to: event.args.to,
l1Token: event.args.l1Token,
l2Token: event.args.l2Token,
amount: event.args.amount,
data: event.args.extraData,
logIndex: event.logIndex,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
......@@ -156,10 +157,9 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
try {
const contract = new Contract(
toAddress(l2Token),
getContractInterface('L2StandardERC20'),
getContractInterface('OptimismMintableERC20'),
this.messenger.l2Provider
)
// Don't support ETH deposits or withdrawals via this bridge.
if (
hexStringEquals(toAddress(l1Token), ethers.constants.AddressZero) ||
......@@ -170,6 +170,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
// Make sure the L1 token matches.
const remoteL1Token = await contract.l1Token()
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false
}
......@@ -203,7 +204,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
const token = new Contract(
toAddress(l1Token),
getContractInterface('L2StandardERC20'), // Any ERC20 will do
getContractInterface('OptimismMintableERC20'), // Any ERC20 will do
this.messenger.l1Provider
)
......@@ -270,7 +271,7 @@ export class StandardBridgeAdapter implements IBridgeAdapter {
const token = new Contract(
toAddress(l1Token),
getContractInterface('L2StandardERC20'), // Any ERC20 will do
getContractInterface('OptimismMintableERC20'), // Any ERC20 will do
this.messenger.l1Provider
)
......
This diff is collapsed.
......@@ -6,8 +6,13 @@ import {
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { Signer } from '@ethersproject/abstract-signer'
import {
BedrockCrossChainMessageProof,
BedrockOutputData,
} from '@eth-optimism/core-utils'
import {
CoreCrossChainMessage,
MessageLike,
MessageRequestLike,
TransactionLike,
......@@ -91,6 +96,11 @@ export interface ICrossChainMessenger {
*/
l1BlockTimeSeconds: number
/**
* Whether or not Bedrock compatibility is enabled.
*/
bedrock: boolean
/**
* Retrieves all cross chain messages sent within a given transaction.
*
......@@ -291,6 +301,16 @@ export interface ICrossChainMessenger {
*/
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
* block in which the transaction was included, as published to the StateCommitmentChain. If the
......@@ -342,6 +362,18 @@ export interface ICrossChainMessenger {
*/
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
* to the message itself.
......
......@@ -17,6 +17,7 @@ export enum L1ChainID {
GOERLI = 5,
KOVAN = 42,
HARDHAT_LOCAL = 31337,
BEDROCK_LOCAL_DEVNET = 900,
}
/**
......@@ -28,6 +29,7 @@ export enum L2ChainID {
OPTIMISM_KOVAN = 69,
OPTIMISM_HARDHAT_LOCAL = 31337,
OPTIMISM_HARDHAT_DEVNET = 17,
OPTIMISM_BEDROCK_LOCAL_DEVNET = 901,
}
/**
......@@ -40,6 +42,9 @@ export interface OEL1Contracts {
StateCommitmentChain: Contract
CanonicalTransactionChain: Contract
BondManager: Contract
// Bedrock
OptimismPortal: Contract
L2OutputOracle: Contract
}
/**
......@@ -48,6 +53,7 @@ export interface OEL1Contracts {
export interface OEL2Contracts {
L2CrossDomainMessenger: Contract
L2StandardBridge: Contract
L2ToL1MessagePasser: Contract
OVM_L1BlockNumber: Contract
OVM_L2ToL1MessagePasser: Contract
OVM_DeployerWhitelist: Contract
......@@ -174,7 +180,9 @@ export interface CoreCrossChainMessage {
sender: string
target: string
message: string
messageNonce: number
messageNonce: BigNumber
value: BigNumber
minGasLimit: BigNumber
}
/**
......@@ -183,7 +191,6 @@ export interface CoreCrossChainMessage {
*/
export interface CrossChainMessage extends CoreCrossChainMessage {
direction: MessageDirection
gasLimit: number
logIndex: number
blockNumber: number
transactionHash: string
......
......@@ -8,6 +8,7 @@ export const DEPOSIT_CONFIRMATION_BLOCKS: {
[L2ChainID.OPTIMISM_KOVAN]: 12 as const,
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const,
[L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const,
[L2ChainID.OPTIMISM_BEDROCK_LOCAL_DEVNET]: 2 as const,
}
export const CHAIN_BLOCK_TIMES: {
......@@ -17,4 +18,5 @@ export const CHAIN_BLOCK_TIMES: {
[L1ChainID.GOERLI]: 15 as const,
[L1ChainID.KOVAN]: 4 as const,
[L1ChainID.HARDHAT_LOCAL]: 1 as const,
[L1ChainID.BEDROCK_LOCAL_DEVNET]: 15 as const,
}
import { getContractInterface, predeploys } from '@eth-optimism/contracts'
import { getContractInterface as getContractInterfaceBedrock } from '@eth-optimism/contracts-bedrock'
import { ethers, Contract } from 'ethers'
import { toAddress } from './coercion'
......@@ -23,9 +24,11 @@ import {
/**
* Full list of default L2 contract addresses.
* TODO(tynes): migrate to predeploys from contracts-bedrock
*/
export const DEFAULT_L2_CONTRACT_ADDRESSES: OEL2ContractsLike = {
L2CrossDomainMessenger: predeploys.L2CrossDomainMessenger,
L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
L2StandardBridge: predeploys.L2StandardBridge,
OVM_L1BlockNumber: predeploys.OVM_L1BlockNumber,
OVM_L2ToL1MessagePasser: predeploys.OVM_L2ToL1MessagePasser,
......@@ -65,6 +68,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0x5E4e65926BA27467555EB562121fac00D24E9dD2' as const,
BondManager: '0xcd626E1328b41fCF24737F137BcD4CE0c32bc8d1' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
......@@ -79,6 +84,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0xf7B88A133202d41Fe5E2Ab22e6309a1A4D50AF74' as const,
BondManager: '0xc5a603d273E28185c18Ba4d26A0024B2d2F42740' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
......@@ -93,6 +100,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0x607F755149cFEB3a14E1Dc3A4E2450Cde7dfb04D' as const,
BondManager: '0xfC2ab6987C578218f99E85d61Dcf4814A26637Bd' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
......@@ -107,6 +116,8 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' as const,
BondManager: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707' as const,
OptimismPortal: '0x0000000000000000000000000000000000000000' as const,
L2OutputOracle: '0x0000000000000000000000000000000000000000' as const,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
......@@ -121,6 +132,24 @@ export const CONTRACT_ADDRESSES: {
CanonicalTransactionChain:
'0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9' 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,
},
......@@ -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(
toAddress(
opts.address || addresses.l1[contractName] || addresses.l2[contractName]
),
getContractInterface(NAME_REMAPPING[contractName] || contractName),
iface,
opts.signerOrProvider
)
}
......@@ -235,6 +274,8 @@ export const getAllOEContracts = (
StateCommitmentChain: undefined,
CanonicalTransactionChain: undefined,
BondManager: undefined,
OptimismPortal: undefined,
L2OutputOracle: undefined,
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
}
......
export * from './coercion'
export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
export * from './misc-utils'
export * from './merkle-utils'
......
/* Imports: External */
import { ethers } from 'ethers'
import { ethers, BigNumber } from 'ethers'
import {
fromHexString,
toHexString,
toRpcHexString,
} from '@eth-optimism/core-utils'
import { MerkleTree } from 'merkletreejs'
import * as rlp from 'rlp'
/**
* Generates a Merkle proof (using the particular scheme we use within Lib_MerkleTree).
......@@ -60,8 +59,10 @@ export const makeStateTrieProof = async (
address: string,
slot: string
): Promise<{
accountProof: string
storageProof: string
accountProof: string[]
storageProof: string[]
storageValue: BigNumber
storageRoot: string
}> => {
const proof = await provider.send('eth_getProof', [
address,
......@@ -70,7 +71,9 @@ export const makeStateTrieProof = async (
])
return {
accountProof: toHexString(rlp.encode(proof.accountProof)),
storageProof: toHexString(rlp.encode(proof.storageProof[0].proof)),
accountProof: proof.accountProof,
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 {
address(0),
hex"1234",
1234,
12345678
12345678,
0
)
);
}
......@@ -101,7 +102,8 @@ contract MockBridge {
address(0),
hex"1234",
1234,
12345678
12345678,
0
)
);
}
......
......@@ -48,7 +48,8 @@ contract MockMessenger is ICrossDomainMessenger {
address sender;
bytes message;
uint256 messageNonce;
uint256 gasLimit;
uint256 minGasLimit;
uint256 value;
}
function doNothing() public {
......@@ -63,7 +64,7 @@ contract MockMessenger is ICrossDomainMessenger {
_params.sender,
_params.message,
_params.messageNonce,
_params.gasLimit
_params.minGasLimit
);
}
......
import { ethers } from 'ethers'
export const DUMMY_MESSAGE = {
target: '0x' + '11'.repeat(20),
sender: '0x' + '22'.repeat(20),
message: '0x' + '33'.repeat(64),
messageNonce: 1234,
gasLimit: 100000,
messageNonce: ethers.BigNumber.from(1234),
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",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": [
"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