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
) )
......
This diff is collapsed.
...@@ -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 { 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