Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/real-bikes-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/sdk': minor
---

Adds Bedrock support to the SDK
5 changes: 5 additions & 0 deletions .changeset/wet-rivers-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@eth-optimism/contracts-bedrock': patch
---

Emit SentMessageV2 event with more information
1 change: 1 addition & 0 deletions packages/contracts-bedrock/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ coverage.out
deployments
broadcast
genesis.json
src/contract-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ contract L2OutputOracle is OwnableUpgradeable {
);

return
STARTING_TIMESTAMP + ((_l2BlockNumber - STARTING_BLOCK_NUMBER) * SUBMISSION_INTERVAL);
STARTING_TIMESTAMP + ((_l2BlockNumber - STARTING_BLOCK_NUMBER) * L2_BLOCK_TIME);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/contracts-bedrock/contracts/test/CommonTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ contract Messenger_Initializer is L2OutputOracle_Initializer {
uint256 gasLimit
);

event SentMessageExtraData(
address indexed sender,
uint256 value
);

event WithdrawalInitiated(
uint256 indexed nonce,
address indexed sender,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
OptimismPortal.depositTransaction.selector,
Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
0,
100 + L1Messenger.baseGas(hex"ff"),
L1Messenger.baseGas(hex"ff", 100),
false,
CrossDomainHashing.getVersionedEncoding(
L1Messenger.messageNonce(),
Expand All @@ -96,7 +96,7 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
0,
0,
100 + L1Messenger.baseGas(hex"ff"),
L1Messenger.baseGas(hex"ff", 100),
false,
CrossDomainHashing.getVersionedEncoding(
L1Messenger.messageNonce(),
Expand All @@ -112,6 +112,13 @@ contract L1CrossDomainMessenger_Test is Messenger_Initializer {
vm.expectEmit(true, true, true, true);
emit SentMessage(recipient, alice, hex"ff", L1Messenger.messageNonce(), 100);

// SentMessageExtraData event
vm.expectEmit(true, true, true, true);
emit SentMessageExtraData(
alice,
0
);

vm.prank(alice);
L1Messenger.sendMessage(recipient, hex"ff", uint32(100));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ contract L2CrossDomainMessenger_Test is Messenger_Initializer {
abi.encodeWithSelector(
L2ToL1MessagePasser.initiateWithdrawal.selector,
address(L1Messenger),
100 + L2Messenger.baseGas(hex"ff"),
L2Messenger.baseGas(hex"ff", 100),
CrossDomainHashing.getVersionedEncoding(
L2Messenger.messageNonce(),
alice,
Expand All @@ -63,7 +63,7 @@ contract L2CrossDomainMessenger_Test is Messenger_Initializer {
address(L2Messenger),
address(L1Messenger),
0,
100 + L2Messenger.baseGas(hex"ff"),
L2Messenger.baseGas(hex"ff", 100),
CrossDomainHashing.getVersionedEncoding(
L2Messenger.messageNonce(),
alice,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ abstract contract CrossDomainMessenger is
uint256 gasLimit
);

event SentMessageExtraData(address indexed sender, uint256 value);

event RelayedMessage(bytes32 indexed msgHash);

event FailedRelayedMessage(bytes32 indexed msgHash);
Expand All @@ -52,9 +54,13 @@ abstract contract CrossDomainMessenger is

uint16 public constant MESSAGE_VERSION = 1;

uint32 public constant MIN_GAS_DYNAMIC_OVERHEAD = 1;
uint32 public constant MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR = 1016;

uint32 public constant MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR = 1000;

uint32 public constant MIN_GAS_CALLDATA_OVERHEAD = 16;

uint32 public constant MIN_GAS_CONSTANT_OVERHEAD = 100_000;
uint32 public constant MIN_GAS_CONSTANT_OVERHEAD = 200_000;

/// @notice Minimum amount of gas required prior to relaying a message.
uint256 internal constant RELAY_GAS_REQUIRED = 45_000;
Expand Down Expand Up @@ -143,11 +149,15 @@ abstract contract CrossDomainMessenger is
* @param _message Message to compute base gas for.
* @return Base gas required for message.
*/
function baseGas(bytes memory _message) public pure returns (uint32) {
function baseGas(bytes memory _message, uint32 _minGasLimit) public pure returns (uint32) {
// TODO: Values here are meant to be good enough to get a devnet running. We need to do
// some simple experimentation with the smallest and largest possible message sizes to find
// the correct constant and dynamic overhead values.
return (uint32(_message.length) * MIN_GAS_DYNAMIC_OVERHEAD) + MIN_GAS_CONSTANT_OVERHEAD;
return
((_minGasLimit * MIN_GAS_DYNAMIC_OVERHEAD_NUMERATOR) /
MIN_GAS_DYNAMIC_OVERHEAD_DENOMINATOR) +
(uint32(_message.length) * MIN_GAS_CALLDATA_OVERHEAD) +
MIN_GAS_CONSTANT_OVERHEAD;
}

/**
Expand All @@ -166,7 +176,7 @@ abstract contract CrossDomainMessenger is
// the minimum gas limit specified by the user.
_sendMessage(
otherMessenger,
_minGasLimit + baseGas(_message),
baseGas(_message, _minGasLimit),
msg.value,
abi.encodeWithSelector(
this.relayMessage.selector,
Expand All @@ -180,6 +190,7 @@ abstract contract CrossDomainMessenger is
);

emit SentMessage(_target, msg.sender, _message, messageNonce(), _minGasLimit);
emit SentMessageExtraData(msg.sender, msg.value);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SentMessage is a legacy event that shouldn't be changed so we can preserve the interface. Instead of adding a completely new event, we add SentMessageExtraData.


unchecked {
++msgNonce;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const deployFn: DeployFunction = async (hre) => {
const proxy = await hre.deployments.get('L1CrossDomainMessengerProxy')
const Proxy = await hre.ethers.getContractAt('Proxy', proxy.address)
const messenger = await hre.deployments.get('L1CrossDomainMessenger')
const portal = await hre.deployments.get('OptimismPortal')
const portal = await hre.deployments.get('OptimismPortalProxy')

const L1CrossDomainMessenger = await hre.ethers.getContractAt(
'L1CrossDomainMessenger',
Expand Down
6 changes: 4 additions & 2 deletions packages/contracts-bedrock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
"dist/**/*.js",
"dist/**/*.d.ts",
"dist/types/*.ts",
"artifacts/src/**/*.json",
"artifacts/contracts/**/*.json",
"deployments/**/*.json",
"contracts/**/*.sol"
],
"scripts": {
"build:forge": "forge build",
"prebuild": "yarn ts-node scripts/verifyFoundryInstall.ts",
"build": "hardhat compile && tsc && hardhat typechain",
"build": "hardhat compile && tsc && hardhat typechain && yarn autogen:artifacts",
"build:ts": "tsc",
"autogen:artifacts": "ts-node scripts/generate-artifacts.ts",
"deploy": "hardhat deploy",
"test": "forge test",
"gas-snapshot": "forge snapshot",
Expand Down Expand Up @@ -59,6 +60,7 @@
"dotenv": "^16.0.0",
"ethereum-waffle": "^3.0.0",
"ethers": "^5.6.8",
"glob": "^7.1.6",
"hardhat-deploy": "^0.11.4",
"solhint": "^3.3.6",
"solhint-plugin-prettier": "^0.0.5",
Expand Down
67 changes: 67 additions & 0 deletions packages/contracts-bedrock/scripts/generate-artifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import path from 'path'
import fs from 'fs'

import glob from 'glob'

/**
* Script for automatically generating a file which has a series of `require` statements for
* importing JSON contract artifacts. We do this to preserve browser compatibility.
*/
const main = async () => {
const contractArtifactsFolder = path.resolve(
__dirname,
`../artifacts/contracts`
)

const artifactPaths = glob
.sync(`${contractArtifactsFolder}/**/*.json`)
.filter((match) => {
// Filter out the debug outputs.
return !match.endsWith('.dbg.json')
})

const content = `
/* eslint-disable @typescript-eslint/no-var-requires, no-empty */
/*
THIS FILE IS AUTOMATICALLY GENERATED.
DO NOT EDIT.
*/

${artifactPaths
.map((artifactPath) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const artifact = require(artifactPath)
// handles the case - '\u' (\utils folder) is considered as an unicode encoded char
const pattern = /\\/g
const relPath = path
.relative(__dirname, artifactPath)
.replace(pattern, '/')
return `
let ${artifact.contractName}
try {
${artifact.contractName} = require('${relPath}')
} catch {}
`
})
.join('\n')}

export const getContractArtifact = (name: string): any => {
return {
${artifactPaths
.map((artifactPath) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const artifact = require(artifactPath)
return `${artifact.contractName}`
})
.join(',\n')}
}[name]
}
`

fs.writeFileSync(
path.resolve(__dirname, `../src/contract-artifacts.ts`),
content
)
}

main()
52 changes: 52 additions & 0 deletions packages/contracts-bedrock/src/contract-defs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { ethers } from 'ethers'

/**
* Gets the hardhat artifact for the given contract name.
* Will throw an error if the contract artifact is not found.
*
* @param name Contract name.
* @returns The artifact for the given contract name.
*/
export const getContractDefinition = (name: string): any => {
// We import this using `require` because hardhat tries to build this file when compiling
// the contracts, but we need the contracts to be compiled before the contract-artifacts.ts
// file can be generated.
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { getContractArtifact } = require('./contract-artifacts')
const artifact = getContractArtifact(name)
if (artifact === undefined) {
throw new Error(`Unable to find artifact for contract: ${name}`)
}
return artifact
}

/**
* Gets an ethers Interface instance for the given contract name.
*
* @param name Contract name.
* @returns The interface for the given contract name.
*/
export const getContractInterface = (name: string): ethers.utils.Interface => {
const definition = getContractDefinition(name)
return new ethers.utils.Interface(definition.abi)
}

/**
* Gets an ethers ContractFactory instance for the given contract name.
*
* @param name Contract name.
* @param signer The signer for the ContractFactory to use.
* @returns The contract factory for the given contract name.
*/
export const getContractFactory = (
name: string,
signer?: ethers.Signer
): ethers.ContractFactory => {
const definition = getContractDefinition(name)
const contractInterface = getContractInterface(name)
return new ethers.ContractFactory(
contractInterface,
definition.bytecode,
signer
)
}
1 change: 1 addition & 0 deletions packages/contracts-bedrock/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './utils'
export * from './generateProofs'
export * from './constants'
export * from './contract-defs'
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/tasks/genesis-l1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ task('genesis-l1', 'create a genesis config')
berlinBlock: 0,
londonBlock: 0,
clique: {
period: 15,
period: 5,
epoch: 30000,
},
},
Expand Down
26 changes: 19 additions & 7 deletions packages/contracts-bedrock/tasks/genesis-l2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ task('genesis-l2', 'create a genesis config')
}

if (predeployAddrs.has(ethers.utils.getAddress(addr))) {
const predeploy = Object.entries(predeploys).find(([name, address]) => {
return ethers.utils.getAddress(address) === addr
})

// Really shouldn't happen, since predeployAddrs is a set generated from predeploys.
if (predeploy === undefined) {
throw new Error('could not find address')
}

const name = predeploy[0]
if (variables[name]) {
const storageLayout = await getStorageLayout(hre, name)
const slots = computeStorageSlots(storageLayout, variables[name])

for (const slot of slots) {
alloc[addr].storage[slot.key] = slot.val
}
}

alloc[addr].storage[implementationSlot] = toCodeAddr(addr)
}
}
Expand Down Expand Up @@ -245,13 +264,6 @@ task('genesis-l2', 'create a genesis config')
code: artifact.deployedBytecode,
storage: {},
}

const storageLayout = await getStorageLayout(hre, name)
const slots = computeStorageSlots(storageLayout, variables[name])

for (const slot of slots) {
alloc[allocAddr].storage[slot.key] = slot.val
}
}

const genesis: OptimismGenesis = {
Expand Down
2 changes: 1 addition & 1 deletion packages/sdk/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import '@nomiclabs/hardhat-waffle'

const config: HardhatUserConfig = {
solidity: {
version: '0.8.9',
version: '0.8.10',
},
paths: {
sources: './test/contracts',
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
},
"dependencies": {
"@eth-optimism/contracts": "0.5.29",
"@eth-optimism/contracts-bedrock": "0.4.1",
"@eth-optimism/core-utils": "0.9.0",
"lodash": "^4.17.21",
"merkletreejs": "^0.2.27",
Expand Down
Loading