Commit 3c188103 authored by Matthew Slipper's avatar Matthew Slipper Committed by GitHub

Merge pull request #2845 from ethereum-optimism/develop

Merge develop into master
parents 3cbf56d3 df5eb9e7
---
'@eth-optimism/contracts-bedrock': patch
---
Separate the owner and sequencer roles in the OutputOracle
---
'@eth-optimism/contracts-bedrock': patch
---
Fix bug in bedrock deploy scripts
---
'@eth-optimism/contracts-bedrock': patch
---
Add genesis script
---
'@eth-optimism/contracts-bedrock': minor
---
Add separate sequencer role to Oracle
---
'@eth-optimism/sdk': minor
---
Have SDK use L2 chain ID as the source of truth.
---
'@eth-optimism/contracts-bedrock': patch
---
Fix order of args to L2OO constructor
---
'@eth-optimism/core-utils': minor
---
Update geth's Genesis type to work with modern geth
---
'@eth-optimism/contracts-bedrock': patch
---
Fix for incorrect constructor args in deploy config
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/contracts-governance': patch
'@eth-optimism/fault-detector': patch
'@eth-optimism/message-relayer': patch
---
Update SDK version and usage to account for new constructor
---
'@eth-optimism/contracts-bedrock': patch
---
Make the output oracle upgradeable.
---
'@eth-optimism/fault-detector': patch
---
Fix bug where FD would try to sync beyond local tip
---
'@eth-optimism/go-builder': patch
---
Upgrade golangci-lint version for go 1.18
---
'@eth-optimism/common-ts': minor
---
Add version to healthz for convenience
......@@ -410,6 +410,10 @@ jobs:
forge --version
forge snapshot --check
working_directory: packages/contracts-bedrock
- run:
name: storage snapshot
command: yarn storage-snapshot && git diff --exit-code .storage-layout
working_directory: packages/contracts-bedrock
- run:
name: check go bindings
command: make && git diff --exit-code
......
......@@ -7,6 +7,10 @@ coverage.json
*.tsbuildinfo
yarn-error.log
.yarn/*
!.yarn/releases
!.yarn/plugins
.pnp.*
dist
artifacts
......
This diff is collapsed.
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
yarn-path ".yarn/releases/yarn-1.22.19.cjs"
......@@ -3,7 +3,6 @@ COMPOSE_FILE=replica.yml:replica-shared.yml:replica-toml.yml
ETH_NETWORK=kovan
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=WONT_WORK_UNLESS_YOU_PROVIDE_A_VALID_ETHEREUM_L1_ENDPOINT
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT=https://kovan.optimism.io
REPLICA_HEALTHCHECK__ETH_NETWORK_RPC_PROVIDER=https://kovan.optimism.io
SEQUENCER_CLIENT_HTTP=https://kovan.optimism.io
SHARED_ENV_PATH=../envs/kovan
GCMODE=archive
......@@ -14,5 +13,7 @@ L2GETH_HTTP_PORT=9991
L2GETH_WS_PORT=9992
DTL_PORT=7878
GETH_INIT_SCRIPT=check-for-chaindata-berlin.sh
HEALTHCHECK__REFERENCE_RPC_PROVIDER=https://kovan.optimism.io
HEALTHCHECK__TARGET_RPC_PROVIDER=http://l2geth-replica:8545
RESTART=unless-stopped
......@@ -2,18 +2,19 @@ COMPOSE_PROJECT_NAME=op-replica
COMPOSE_FILE=replica.yml:replica-shared.yml:replica-toml.yml
ETH_NETWORK=mainnet
DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT=WONT_WORK_UNLESS_YOU_PROVIDE_A_VALID_ETHEREUM_L1_ENDPOINT
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT=https://mainnet.optimism.io
REPLICA_HEALTHCHECK__ETH_NETWORK_RPC_PROVIDER=https://mainnet.optimism.io
DATA_TRANSPORT_LAYER__L2_RPC_ENDPOINT=<infra-provider-l2-endpoint>
SEQUENCER_CLIENT_HTTP=https://mainnet.optimism.io
SHARED_ENV_PATH=../envs/mainnet
GCMODE=archive
L2GETH_IMAGE_TAG=0.5.14
DTL_IMAGE_TAG=0.5.21
L2GETH_IMAGE_TAG=0.5.22
DTL_IMAGE_TAG=0.5.36
HC_IMAGE_TAG=1.0.6
L2GETH_HTTP_PORT=9991
L2GETH_WS_PORT=9992
DTL_PORT=7878
GETH_INIT_SCRIPT=check-for-chaindata-berlin.sh
HEALTHCHECK__REFERENCE_RPC_PROVIDER=<infra-provider-l2-endpoiont>
HEALTHCHECK__TARGET_RPC_PROVIDER=http://l2geth-replica:8545
RESTART=unless-stopped
......
......@@ -8,9 +8,6 @@ configMapGenerator:
- name: l2geth-replica
envs:
- ./l2geth-replica.env
- name: replica-healthcheck
envs:
- ./replica-healthcheck.env
- name: geth-scripts
files:
- ./check-for-chaindata.sh
\ No newline at end of file
REPLICA_HEALTHCHECK__ETH_NETWORK=goerli-nightly
REPLICA_HEALTHCHECK__L2GETH_IMAGE_TAG=0.4.9
REPLICA_HEALTHCHECK__ETH_REPLICA_RPC_PROVIDER=http://l2geth-replica:8545
\ No newline at end of file
......@@ -8,6 +8,3 @@ configMapGenerator:
- name: l2geth-replica
envs:
- ./l2geth-replica.env
- name: replica-healthcheck
envs:
- ./replica-healthcheck.env
REPLICA_HEALTHCHECK__ETH_NETWORK=kovan
REPLICA_HEALTHCHECK__L2GETH_IMAGE_TAG=0.4.9
REPLICA_HEALTHCHECK__ETH_NETWORK_RPC_PROVIDER=http://sequencer.default:8545
REPLICA_HEALTHCHECK__ETH_REPLICA_RPC_PROVIDER=http://l2geth-replica:8545
\ No newline at end of file
......@@ -8,6 +8,3 @@ configMapGenerator:
- name: l2geth-replica
envs:
- ./l2geth-replica.env
- name: replica-healthcheck
envs:
- ./replica-healthcheck.env
REPLICA_HEALTHCHECK__ETH_NETWORK=mainnet
REPLICA_HEALTHCHECK__L2GETH_IMAGE_TAG=0.4.8
REPLICA_HEALTHCHECK__ETH_NETWORK_RPC_PROVIDER=http://sequencer.default:8545
REPLICA_HEALTHCHECK__ETH_REPLICA_RPC_PROVIDER=http://l2geth-replica:8545
\ No newline at end of file
......@@ -4,7 +4,7 @@ import {
TransactionResponse,
TransactionReceipt,
} from '@ethersproject/providers'
import { sleep } from '@eth-optimism/core-utils'
import { getChainId, sleep } from '@eth-optimism/core-utils'
import {
CrossChainMessenger,
MessageStatus,
......@@ -58,8 +58,6 @@ export class OptimismEnv {
}
static async new(): Promise<OptimismEnv> {
const network = await l1Provider.getNetwork()
let bridgeOverrides: BridgeAdapterData
if (envConfig.L1_STANDARD_BRIDGE) {
bridgeOverrides = {
......@@ -79,7 +77,8 @@ export class OptimismEnv {
const messenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2Wallet,
l1ChainId: network.chainId,
l1ChainId: await getChainId(l1Provider),
l2ChainId: await getChainId(l2Provider),
contracts: {
l1: {
AddressManager: envConfig.ADDRESS_MANAGER,
......
......@@ -1391,6 +1391,7 @@ func TestFakedSyncProgress64Fast(t *testing.T) { testFakedSyncProgress(t, 64, F
func TestFakedSyncProgress64Light(t *testing.T) { testFakedSyncProgress(t, 64, LightSync) }
func testFakedSyncProgress(t *testing.T, protocol int, mode SyncMode) {
t.Skip("Flakey tests unused by Optimism")
t.Parallel()
tester := newTester()
......
This diff is collapsed.
......@@ -402,7 +402,7 @@ func (cfg SystemConfig) start() (*System, error) {
// Deploy Deposit Contract
deployerPrivKey, err := sys.wallet.PrivateKey(accounts.Account{
URL: accounts.URL{
Path: sys.cfg.DeployerHDPath,
Path: cfg.DeployerHDPath,
},
})
if err != nil {
......@@ -425,6 +425,7 @@ func (cfg SystemConfig) start() (*System, error) {
sys.cfg.L2OOCfg.L2StartingTimeStamp,
sys.cfg.L2OOCfg.L2BlockTime,
l2OutputSubmitterAddr,
crypto.PubkeyToAddress(deployerPrivKey.PublicKey),
)
sys.cfg.DepositCFG.L2Oracle = sys.L2OOContractAddr
if err != nil {
......
......@@ -57,6 +57,7 @@ const (
l2OutputHDPath = "m/44'/60'/0'/0/3"
bssHDPath = "m/44'/60'/0'/0/4"
p2pSignerHDPath = "m/44'/60'/0'/0/5"
deployerHDPath = "m/44'/60'/0'/0/6"
)
var (
......@@ -81,19 +82,21 @@ func defaultSystemConfig(t *testing.T) SystemConfig {
transactorHDPath: 10000000,
l2OutputHDPath: 10000000,
bssHDPath: 10000000,
deployerHDPath: 10000000,
},
DepositCFG: DepositContractConfig{
FinalizationPeriod: big.NewInt(60 * 60 * 24),
},
L2OOCfg: L2OOContractConfig{
// L2 Start time is set based off of the L2 Genesis time
SubmissionFrequency: big.NewInt(2),
SubmissionFrequency: big.NewInt(4),
L2BlockTime: big.NewInt(2),
HistoricalTotalBlocks: big.NewInt(0),
},
L2OutputHDPath: l2OutputHDPath,
BatchSubmitterHDPath: bssHDPath,
P2PSignerHDPath: p2pSignerHDPath,
DeployerHDPath: l2OutputHDPath,
DeployerHDPath: deployerHDPath,
CliqueSignerDerivationPath: cliqueSignerHDPath,
L1InfoPredeployAddress: predeploys.L1BlockAddr,
L1BlockTime: 2,
......@@ -789,7 +792,7 @@ func TestWithdrawals(t *testing.T) {
require.Nil(t, err)
// Wait for finalization and then create the Finalized Withdrawal Transaction
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Duration(cfg.L1BlockTime)*time.Second)
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Duration(cfg.L1BlockTime)*time.Second)
defer cancel()
blockNumber, err := withdrawals.WaitForFinalizationPeriod(ctx, l1Client, sys.DepositContractAddr, receipt.BlockNumber)
require.Nil(t, err)
......
......@@ -33,6 +33,7 @@ L1_URL="http://localhost:8545"
L2_URL="http://localhost:9545"
CONTRACTS_BEDROCK=./packages/contracts-bedrock
NETWORK=devnetL1
# Helper method that waits for a given URL to be up. Can't use
# cURL's built-in retry logic because connection reset errors
......@@ -46,7 +47,7 @@ function wait_up {
sleep 0.25
((i=i+1))
if [ "$i" -eq 120 ]; then
if [ "$i" -eq 200 ]; then
echo " Timeout!" >&2
exit 0
fi
......@@ -54,15 +55,24 @@ function wait_up {
echo "Done!"
}
mkdir -p ./.devnet
if [ ! -f ./.devnet/rollup.json ]; then
GENESIS_TIMESTAMP=$(date +%s | xargs printf "0x%x")
else
GENESIS_TIMESTAMP=$(jq '.genesis.l2_time' < .devnet/rollup.json)
fi
# Regenerate the L1 genesis file if necessary. The existence of the genesis
# file is used to determine if we need to recreate the devnet's state folder.
if [ ! -f ./.devnet/genesis-l1.json ]; then
echo "Regenerating L1 genesis."
mkdir -p ./.devnet
GENESIS_TIMESTAMP=$(date +%s | xargs printf "0x%x")
jq ". | .timestamp = \"$GENESIS_TIMESTAMP\" " < ./ops-bedrock/genesis-l1.json > ./.devnet/genesis-l1.json
else
GENESIS_TIMESTAMP=$(jq -r '.timestamp' < ./.devnet/genesis-l1.json)
(
cd $CONTRACTS_BEDROCK
L2OO_STARTING_BLOCK_TIMESTAMP=$GENESIS_TIMESTAMP npx hardhat genesis-l1 \
--outfile genesis-l1.json
mv genesis-l1.json ../../.devnet/genesis-l1.json
)
fi
# Bring up L1.
......@@ -75,43 +85,27 @@ fi
)
# Deploy contracts using Hardhat.
if [ ! -f $CONTRACTS_BEDROCK/deployments/devnetL1/OptimismPortal.json ]; then
echo "Deploying contracts."
if [ ! -d $CONTRACTS_BEDROCK/deployments/$NETWORK ]; then
(
echo "Deploying contracts."
cd $CONTRACTS_BEDROCK
L2OO_STARTING_BLOCK_TIMESTAMP=$GENESIS_TIMESTAMP yarn hardhat --network devnetL1 deploy
L2OO_STARTING_BLOCK_TIMESTAMP=$GENESIS_TIMESTAMP yarn hardhat --network $NETWORK deploy
)
else
echo "Contracts already deployed, skipping."
fi
function get_deployed_bytecode() {
echo $(jq -r .deployedBytecode $CONTRACTS_BEDROCK/artifacts/contracts/$1)
}
# Pull out the necessary bytecode/addresses from the artifacts/deployments.
L2_TO_L1_MESSAGE_PASSER_BYTECODE=$(get_deployed_bytecode L2/L2ToL1MessagePasser.sol/L2ToL1MessagePasser.json)
L2_CROSS_DOMAIN_MESSENGER_BYTECODE=$(get_deployed_bytecode L2/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json)
OPTIMISM_MINTABLE_TOKEN_FACTORY_BYTECODE=$(get_deployed_bytecode universal/OptimismMintableTokenFactory.sol/OptimismMintableTokenFactory.json)
L2_STANDARD_BRIDGE_BYTECODE=$(get_deployed_bytecode L2/L2StandardBridge.sol/L2StandardBridge.json)
L1_BLOCK_INFO_BYTECODE=$(get_deployed_bytecode L2/L1Block.sol/L1Block.json)
DEPOSIT_CONTRACT_ADDRESS=$(jq -r .address < $CONTRACTS_BEDROCK/deployments/devnetL1/OptimismPortal.json)
L2OO_ADDRESS=$(jq -r .address < $CONTRACTS_BEDROCK/deployments/devnetL1/L2OutputOracle.json)
# Replace values in the L2 genesis file. It doesn't matter if this gets run every time,
# since the replaced values will be the same.
jq ". | .alloc.\"4200000000000000000000000000000000000015\".code = \"$L1_BLOCK_INFO_BYTECODE\"" < ./ops-bedrock/genesis-l2.json | \
jq ". | .alloc.\"4200000000000000000000000000000000000015\".balance = \"0x0\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000000\".code = \"$L2_TO_L1_MESSAGE_PASSER_BYTECODE\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000000\".balance = \"0x0\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000007\".code = \"$L2_CROSS_DOMAIN_MESSENGER_BYTECODE\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000007\".balance = \"0x0\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000012\".code = \"$OPTIMISM_MINTABLE_TOKEN_FACTORY_BYTECODE\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000012\".balance = \"0x0\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000010\".code = \"$L2_STANDARD_BRIDGE_BYTECODE\"" | \
jq ". | .alloc.\"4200000000000000000000000000000000000010\".balance = \"0x0\"" | \
jq ". | .timestamp = \"$GENESIS_TIMESTAMP\" " > ./.devnet/genesis-l2.json
if [ ! -f ./.devnet/genesis-l2.json ]; then
(
echo "Creating L2 genesis file."
cd $CONTRACTS_BEDROCK
L2OO_STARTING_BLOCK_TIMESTAMP=$GENESIS_TIMESTAMP npx hardhat --network $NETWORK genesis-l2
mv genesis.json ../../.devnet/genesis-l2.json
echo "Created L2 genesis."
)
else
echo "L2 genesis already exists."
fi
# Bring up L2.
(
......@@ -122,40 +116,19 @@ jq ". | .alloc.\"4200000000000000000000000000000000000015\".code = \"$L1_BLOCK_I
)
# Start putting together the rollup config.
echo "Building rollup config..."
# Grab the L1 genesis. We can use cURL here to retry.
L1_GENESIS=$(curl \
--silent \
--fail \
--retry 10 \
--retry-delay 2 \
--retry-connrefused \
-X POST \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0", false],"id":1}' \
$L1_URL)
# Grab the L2 genesis. We can use cURL here to retry.
L2_GENESIS=$(curl \
--silent \
--fail \
--retry 10 \
--retry-delay 2 \
--retry-connrefused \
-X POST \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x0", false],"id":1}' \
$L2_URL)
# Generate the rollup config.
jq ". | .genesis.l1.hash = \"$(echo $L1_GENESIS | jq -r '.result.hash')\"" < ./ops-bedrock/rollup.json | \
jq ". | .genesis.l2.hash = \"$(echo $L2_GENESIS | jq -r '.result.hash')\"" | \
jq ". | .genesis.l2_time = $(echo $L2_GENESIS | jq -r '.result.timestamp' | xargs printf "%d")" | \
jq ". | .deposit_contract_address = \"$DEPOSIT_CONTRACT_ADDRESS\"" > ./.devnet/rollup.json
SEQUENCER_GENESIS_HASH="$(echo $L2_GENESIS | jq -r '.result.hash')"
if [ ! -f ./.devnet/rollup.json ]; then
(
echo "Building rollup config..."
cd $CONTRACTS_BEDROCK
L2OO_STARTING_BLOCK_TIMESTAMP=$GENESIS_TIMESTAMP npx hardhat rollup-config --network $NETWORK
mv rollup.json ../../.devnet/rollup.json
)
else
echo "Rollup config already exists"
fi
L2OO_ADDRESS=$(jq -r .address < $CONTRACTS_BEDROCK/deployments/$NETWORK/L2OutputOracleProxy.json)
SEQUENCER_GENESIS_HASH="$(jq -r '.l2.hash' < .devnet/rollup.json)"
SEQUENCER_BATCH_INBOX_ADDRESS="$(cat ./ops-bedrock/rollup.json | jq -r '.batch_inbox_address')"
# Bring up everything else.
......
This diff is collapsed.
This diff is collapsed.
......@@ -6,6 +6,6 @@ COPY --from=geth /usr/local/bin/abigen /usr/local/bin/abigen
RUN apk add --no-cache make gcc musl-dev linux-headers git jq curl bash gzip ca-certificates openssh && \
go install gotest.tools/gotestsum@latest && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.46.2
CMD ["bash"]
......@@ -41,7 +41,8 @@
"postinstall": "patch-package",
"ready": "yarn lint && yarn test",
"prepare": "husky install",
"release": "yarn build && yarn changeset publish"
"release": "yarn build && yarn changeset publish",
"update:yarn": "yarn set version 1.x"
},
"devDependencies": {
"@babel/eslint-parser": "^7.18.2",
......
......@@ -144,7 +144,8 @@ export abstract class BaseServiceV2<
* @param params.port Port for the app server. Defaults to 7300.
* @param params.hostname Hostname for the app server. Defaults to 0.0.0.0.
*/
constructor(params: {
constructor(
private readonly params: {
name: string
version: string
optionsSpec: OptionsSpec<TOptions>
......@@ -154,7 +155,8 @@ export abstract class BaseServiceV2<
loopIntervalMs?: number
port?: number
hostname?: string
}) {
}
) {
this.loop = params.loop !== undefined ? params.loop : true
this.state = {} as TServiceState
......@@ -404,6 +406,7 @@ export abstract class BaseServiceV2<
app.get('/healthz', async (req, res) => {
return res.json({
ok: this.healthy,
version: this.params.version,
})
})
......@@ -429,7 +432,7 @@ export abstract class BaseServiceV2<
}
return '/invalid_path_not_a_real_route'
}
},
})
)
......
......@@ -8,3 +8,4 @@ forge-artifacts
cache
typechain
coverage*
deployments
......@@ -39,7 +39,7 @@ L1StandardBridge_Test:test_depositERC20() (gas: 452856)
L1StandardBridge_Test:test_depositERC20To() (gas: 454632)
L1StandardBridge_Test:test_depositETH() (gas: 247077)
L1StandardBridge_Test:test_depositETHTo() (gas: 204961)
L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 438752)
L1StandardBridge_Test:test_finalizeERC20Withdrawal() (gas: 438745)
L1StandardBridge_Test:test_finalizeETHWithdrawal() (gas: 48005)
L1StandardBridge_Test:test_initialize() (gas: 14885)
L1StandardBridge_Test:test_onlyEOADepositERC20() (gas: 12085)
......@@ -57,21 +57,27 @@ L2CrossDomainMessenger_Test:test_L2MessengerSendMessage() (gas: 119682)
L2CrossDomainMessenger_Test:test_L2MessengerTwiceSendMessage() (gas: 133142)
L2CrossDomainMessenger_Test:test_L2MessengerXDomainSenderReverts() (gas: 10599)
L2CrossDomainMessenger_Test:test_L2MessengerxDomainMessageSenderResets() (gas: 54881)
L2OutputOracleTest:testCannot_appendEmptyOutput() (gas: 18442)
L2OutputOracleTest:testCannot_appendFutureTimetamp() (gas: 20072)
L2OutputOracleTest:testCannot_appendOnWrongFork() (gas: 20710)
L2OutputOracleTest:testCannot_appendOutputIfNotSequencer() (gas: 17829)
L2OutputOracleTest:testCannot_appendUnexpectedBlockNumber() (gas: 20313)
L2OutputOracleTest:testCannot_deleteL2Output_ifNotSequencer() (gas: 18805)
L2OutputOracleTest:testCannot_deleteWrongL2Output() (gas: 79498)
L2OutputOracleTest:test_appendWithBlockhashAndHeight() (gas: 69365)
L2OutputOracleTest:test_appendingAnotherOutput() (gas: 70714)
L2OutputOracleTest:test_computeL2Timestamp() (gas: 19230)
L2OutputOracleTest:test_constructor() (gas: 33908)
L2OutputOracleTest:test_deleteL2Output() (gas: 66081)
L2OutputOracleTest:test_getL2Output() (gas: 76274)
L2OutputOracleTest:test_latestBlockNumber() (gas: 70075)
L2OutputOracleTest:test_nextBlockNumber() (gas: 9279)
L2OutputOracleTest:testCannot_appendEmptyOutput() (gas: 18216)
L2OutputOracleTest:testCannot_appendFutureTimetamp() (gas: 20183)
L2OutputOracleTest:testCannot_appendOnWrongFork() (gas: 20462)
L2OutputOracleTest:testCannot_appendOutputIfNotSequencer() (gas: 17607)
L2OutputOracleTest:testCannot_appendUnexpectedBlockNumber() (gas: 20086)
L2OutputOracleTest:testCannot_deleteL2Output_ifNotOwner() (gas: 18893)
L2OutputOracleTest:testCannot_deleteL2Output_withWrongRoot() (gas: 83353)
L2OutputOracleTest:testCannot_deleteL2Output_withWrongTime() (gas: 79346)
L2OutputOracleTest:test_appendWithBlockhashAndHeight() (gas: 69126)
L2OutputOracleTest:test_appendingAnotherOutput() (gas: 70475)
L2OutputOracleTest:test_changeSequencer() (gas: 35473)
L2OutputOracleTest:test_computeL2Timestamp() (gas: 19323)
L2OutputOracleTest:test_constructor() (gas: 40152)
L2OutputOracleTest:test_deleteL2Output() (gas: 69237)
L2OutputOracleTest:test_getL2Output() (gas: 76099)
L2OutputOracleTest:test_latestBlockNumber() (gas: 69878)
L2OutputOracleTest:test_nextBlockNumber() (gas: 9281)
L2OutputOracleTest:test_updateOwner() (gas: 24185)
L2OutputOracleUpgradeable_Test:test_cannotInitImpl() (gas: 8431)
L2OutputOracleUpgradeable_Test:test_cannotInitProxy() (gas: 13430)
L2OutputOracleUpgradeable_Test:test_initValuesOnProxy() (gas: 38906)
L2StandardBridge_Test:test_ERC20BridgeFailed_whenLocalTokenIsBridge() (gas: 133097)
L2StandardBridge_Test:test_cannotWithdrawEthWithoutSendingIt() (gas: 21611)
L2StandardBridge_Test:test_finalizeDeposit() (gas: 93100)
......@@ -110,7 +116,7 @@ OptimismMintableTokenFactory_Test:test_initializeShouldRevert() (gas: 12696)
OptimismPortal_Test:test_OptimismPortalConstructor() (gas: 11413)
OptimismPortal_Test:test_OptimismPortalContractCreationReverts() (gas: 9214)
OptimismPortal_Test:test_OptimismPortalReceiveEth() (gas: 121706)
OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 21886)
OptimismPortal_Test:test_cannotVerifyRecentWithdrawal() (gas: 21908)
OptimismPortal_Test:test_depositTransaction_NoValueContract() (gas: 70746)
OptimismPortal_Test:test_depositTransaction_NoValueEOA() (gas: 71114)
OptimismPortal_Test:test_depositTransaction_createWithZeroValueForContract() (gas: 70773)
......@@ -119,7 +125,7 @@ OptimismPortal_Test:test_depositTransaction_withEthValueAndContractContractCreat
OptimismPortal_Test:test_depositTransaction_withEthValueAndEOAContractCreation() (gas: 69947)
OptimismPortal_Test:test_depositTransaction_withEthValueFromContract() (gas: 77478)
OptimismPortal_Test:test_depositTransaction_withEthValueFromEOA() (gas: 78049)
OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 33702)
OptimismPortal_Test:test_invalidWithdrawalProof() (gas: 33769)
Proxy_Test:test_clashingFunctionSignatures() (gas: 101427)
Proxy_Test:test_implementationKey() (gas: 20942)
Proxy_Test:test_implementationProxyCallIfNotAdmin() (gas: 30021)
......
......@@ -9,6 +9,7 @@ forge-artifacts
cache
typechain
coverage*
deployments
contracts/L2/WETH9.sol
......
This diff is collapsed.
//SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import {
OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @title L2OutputOracle
......@@ -10,7 +12,12 @@ import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
* This contract should be deployed behind an upgradable proxy
*/
// slither-disable-next-line locked-ether
contract L2OutputOracle is Ownable {
contract L2OutputOracle is OwnableUpgradeable {
/**
* @notice Contract version number.
*/
uint8 public constant L2_OUTPUT_ORACLE_VERSION = 1;
/**
* @notice OutputProposal represents a commitment to the L2 state.
* The timestamp is the L1 timestamp that the output root is posted.
......@@ -48,6 +55,14 @@ contract L2OutputOracle is Ownable {
uint256 indexed _l2BlockNumber
);
/**
* @notice Emitted when the sequencer address is changed.
*
* @param previousSequencer The previous sequencer address.
* @param newSequencer The new sequencer address.
*/
event SequencerChanged(address indexed previousSequencer, address indexed newSequencer);
/**
* @notice The interval in L2 blocks at which checkpoints must be submitted.
*/
......@@ -73,6 +88,11 @@ contract L2OutputOracle is Ownable {
*/
uint256 public immutable L2_BLOCK_TIME;
/**
* @notice The address of the sequencer;
*/
address public sequencer;
/**
* @notice The number of the most recent L2 block recorded in this contract.
*/
......@@ -85,6 +105,14 @@ contract L2OutputOracle is Ownable {
*/
mapping(uint256 => OutputProposal) internal l2Outputs;
/**
* @notice Reverts if called by any account other than the sequencer.
*/
modifier onlySequencer() {
require(sequencer == msg.sender, "OutputOracle: caller is not the sequencer");
_;
}
/**
* @notice Initialize the L2OutputOracle contract.
*
......@@ -97,6 +125,7 @@ contract L2OutputOracle is Ownable {
* @param _startingTimestamp The timestamp of the first L2 block.
* @param _l2BlockTime The timestamp of the first L2 block.
* @param _sequencer The address of the sequencer.
* @param _owner The address of the owner.
*/
constructor(
uint256 _submissionInterval,
......@@ -105,7 +134,8 @@ contract L2OutputOracle is Ownable {
uint256 _startingBlockNumber,
uint256 _startingTimestamp,
uint256 _l2BlockTime,
address _sequencer
address _sequencer,
address _owner
) {
require(
_l2BlockTime < block.timestamp,
......@@ -117,10 +147,29 @@ contract L2OutputOracle is Ownable {
STARTING_TIMESTAMP = _startingTimestamp;
L2_BLOCK_TIME = _l2BlockTime;
initialize(_genesisL2Output, _startingBlockNumber, _sequencer, _owner);
}
/**
* @notice Initialize the L2OutputOracle contract.
*
* @param _genesisL2Output The initial L2 output of the L2 chain.
* @param _startingBlockNumber The timestamp to start L2 block at.
* @param _sequencer The address of the sequencer.
* @param _owner The address of the owner.
*/
function initialize(
bytes32 _genesisL2Output,
uint256 _startingBlockNumber,
address _sequencer,
address _owner
) public reinitializer(L2_OUTPUT_ORACLE_VERSION) {
l2Outputs[_startingBlockNumber] = OutputProposal(_genesisL2Output, block.timestamp);
latestBlockNumber = _startingBlockNumber;
__Ownable_init();
_transferOwnership(_sequencer);
changeSequencer(_sequencer);
_transferOwnership(_owner);
}
/**
......@@ -139,7 +188,7 @@ contract L2OutputOracle is Ownable {
uint256 _l2BlockNumber,
bytes32 _l1Blockhash,
uint256 _l1BlockNumber
) external payable onlyOwner {
) external payable onlySequencer {
require(
_l2BlockNumber == nextBlockNumber(),
"OutputOracle: Block number must be equal to next expected block number."
......@@ -172,8 +221,11 @@ contract L2OutputOracle is Ownable {
}
/**
* @notice Deletes the most recent output.
*
* @notice Deletes the most recent output. This is used to remove the most recent output in the
* event that an erreneous output is submitted. It can only be called by the contract's
* owner, not the sequencer. Longer term, this should be replaced with a more robust
* mechanism which will allow deletion of proposals shown to be invalid by a fault
* proof.
* @param _proposal Represents the output proposal to delete
*/
function deleteL2Output(OutputProposal memory _proposal) external onlyOwner {
......@@ -230,4 +282,15 @@ contract L2OutputOracle is Ownable {
return
STARTING_TIMESTAMP + ((_l2BlockNumber - STARTING_BLOCK_NUMBER) * SUBMISSION_INTERVAL);
}
/**
* @notice Transfers the sequencer role to a new account (`newSequencer`).
* Can only be called by the current owner.
*/
function changeSequencer(address _newSequencer) public onlyOwner {
require(_newSequencer != address(0), "OutputOracle: new sequencer is the zero address");
require(_newSequencer != owner(), "OutputOracle: sequencer cannot be same as the owner");
emit SequencerChanged(sequencer, _newSequencer);
sequencer = _newSequencer;
}
}
......@@ -42,18 +42,20 @@ contract CommonTest is Test {
vm.fee(1000000000);
}
}
contract L2OutputOracle_Initializer is CommonTest {
// Test target
L2OutputOracle oracle;
// Constructor arguments
uint256 submissionInterval = 42;
address sequencer = 0x000000000000000000000000000000000000AbBa;
address owner = 0x000000000000000000000000000000000000ACDC;
uint256 submissionInterval = 1800;
uint256 l2BlockTime = 2;
bytes32 genesisL2Output = keccak256(abi.encode(0));
uint256 historicalTotalBlocks = 199;
uint256 startingBlockNumber = 200;
uint256 startingTimestamp = 1000;
uint256 l2BlockTime = 2;
address sequencer = 0x000000000000000000000000000000000000AbBa;
// Test data
uint256 initL1Time;
......@@ -74,7 +76,8 @@ contract L2OutputOracle_Initializer is CommonTest {
startingBlockNumber,
startingTimestamp,
l2BlockTime,
sequencer
sequencer,
owner
);
}
}
......@@ -82,8 +85,10 @@ contract L2OutputOracle_Initializer is CommonTest {
contract Messenger_Initializer is L2OutputOracle_Initializer {
OptimismPortal op;
L1CrossDomainMessenger L1Messenger;
L2CrossDomainMessenger L2Messenger = L2CrossDomainMessenger(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER);
L2ToL1MessagePasser messagePasser = L2ToL1MessagePasser(payable(Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER));
L2CrossDomainMessenger L2Messenger =
L2CrossDomainMessenger(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER);
L2ToL1MessagePasser messagePasser =
L2ToL1MessagePasser(payable(Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER));
event SentMessage(
address indexed target,
......@@ -139,20 +144,11 @@ contract Messenger_Initializer is L2OutputOracle_Initializer {
address(new L2ToL1MessagePasser()).code
);
vm.label(
Lib_PredeployAddresses.OVM_ETH,
"OVM_ETH"
);
vm.label(Lib_PredeployAddresses.OVM_ETH, "OVM_ETH");
vm.label(
Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER,
"L2ToL1MessagePasser"
);
vm.label(Lib_PredeployAddresses.L2_TO_L1_MESSAGE_PASSER, "L2ToL1MessagePasser");
vm.label(
Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER,
"L2CrossDomainMessenger"
);
vm.label(Lib_PredeployAddresses.L2_CROSS_DOMAIN_MESSENGER, "L2CrossDomainMessenger");
vm.label(
AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger)),
......@@ -274,14 +270,8 @@ contract Bridge_Initializer is Messenger_Initializer {
function setUp() public virtual override {
super.setUp();
vm.label(
Lib_PredeployAddresses.L2_STANDARD_BRIDGE,
"L2StandardBridge"
);
vm.label(
Lib_PredeployAddresses.L2_STANDARD_TOKEN_FACTORY,
"L2StandardTokenFactory"
);
vm.label(Lib_PredeployAddresses.L2_STANDARD_BRIDGE, "L2StandardBridge");
vm.label(Lib_PredeployAddresses.L2_STANDARD_TOKEN_FACTORY, "L2StandardTokenFactory");
// Deploy the L1 bridge and initialize it with the address of the
// L1CrossDomainMessenger
......@@ -299,32 +289,34 @@ contract Bridge_Initializer is Messenger_Initializer {
// Set up the L2 mintable token factory
OptimismMintableTokenFactory factory = new OptimismMintableTokenFactory();
vm.etch(Lib_PredeployAddresses.L2_STANDARD_TOKEN_FACTORY, address(factory).code);
L2TokenFactory = OptimismMintableTokenFactory(Lib_PredeployAddresses.L2_STANDARD_TOKEN_FACTORY);
L2TokenFactory = OptimismMintableTokenFactory(
Lib_PredeployAddresses.L2_STANDARD_TOKEN_FACTORY
);
L2TokenFactory.initialize(Lib_PredeployAddresses.L2_STANDARD_BRIDGE);
vm.etch(
Lib_PredeployAddresses.OVM_ETH,
address(new OVM_ETH()).code
);
vm.etch(Lib_PredeployAddresses.OVM_ETH, address(new OVM_ETH()).code);
L1Token = new ERC20("Native L1 Token", "L1T");
// Deploy the L2 ERC20 now
L2Token = OptimismMintableERC20(L2TokenFactory.createStandardL2Token(
L2Token = OptimismMintableERC20(
L2TokenFactory.createStandardL2Token(
address(L1Token),
string(abi.encodePacked("L2-", L1Token.name())),
string(abi.encodePacked("L2-", L1Token.symbol()))
));
)
);
NativeL2Token = new ERC20("Native L2 Token", "L2T");
L1TokenFactory = new OptimismMintableTokenFactory();
L1TokenFactory.initialize(address(L1Bridge));
RemoteL1Token = OptimismMintableERC20(L1TokenFactory.createStandardL2Token(
RemoteL1Token = OptimismMintableERC20(
L1TokenFactory.createStandardL2Token(
address(NativeL2Token),
string(abi.encodePacked("L1-", NativeL2Token.name())),
string(abi.encodePacked("L1-", NativeL2Token.symbol()))
));
)
);
}
}
......@@ -19,8 +19,7 @@ contract OptimismPortal_Test is L2OutputOracle_Initializer {
bytes data
);
// Dependencies
// L2OutputOracle oracle;
// Test target
OptimismPortal op;
function setUp() public override {
......
import { ethers } from 'ethers'
const { env } = process
const startingTimestamp =
typeof env.L2OO_STARTING_BLOCK_TIMESTAMP === 'string'
? ethers.BigNumber.from(env.L2OO_STARTING_BLOCK_TIMESTAMP).toNumber()
: Date.now() / 1000
const config = {
submissionInterval: 6,
l2BlockTime: 2,
genesisOutput: ethers.constants.HashZero,
historicalBlocks: 0,
startingBlockTimestamp:
parseInt(process.env.L2OO_STARTING_BLOCK_TIMESTAMP, 10) || Date.now(),
startingBlockNumber: 0,
l2BlockTime: 2,
startingTimestamp,
sequencerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
l2CrossDomainMessengerOwner: ethers.constants.AddressZero,
gasPriceOracleOwner: ethers.constants.AddressZero,
gasPriceOracleOverhead: 2100,
gasPriceOracleScalar: 1000000,
gasPriceOracleDecimals: 6,
l1BlockInitialNumber: 0,
l1BlockInitialTimestamp: 0,
l1BlockInitialBasefee: 10,
l1BlockInitialHash: ethers.constants.HashZero,
l1BlockInitialSequenceNumber: 0,
proxyAdmin: '0x829BD824B016326A401d083B33D092293333A830',
genesisBlockExtradata: ethers.utils.hexConcat([
ethers.constants.HashZero,
'0xca062b0fd91172d89bcd4bb084ac4e21972cc467',
ethers.utils.hexZeroPad('0x', 65),
]),
genesisBlockGasLimit: ethers.BigNumber.from(15000000).toHexString(),
genesisBlockChainid: 901,
fundDevAccounts: true,
optimsismBaseFeeRecipient: '0xBcd4042DE499D14e55001CcbB24a551F3b954096',
optimismL1FeeRecipient: '0x71bE63f3384f5fb98995898A86B02Fb2426c5788',
deploymentWaitConfirmations: 1,
maxSequencerDrift: 10,
sequencerWindowSize: 2,
ownerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
}
export default config
......@@ -5,7 +5,7 @@ const config = {
l2BlockTime: 2,
genesisOutput: ethers.constants.HashZero,
historicalBlocks: 0,
startingBlockTimestamp: 1652907966,
startingTimestamp: 1652907966,
sequencerAddress: '0x7431310e026B69BFC676C0013E12A1A11411EEc9',
}
......
import { ethers } from 'ethers'
const { env } = process
const startingTimestamp =
typeof env.L2OO_STARTING_BLOCK_TIMESTAMP === 'string'
? ethers.BigNumber.from(env.L2OO_STARTING_BLOCK_TIMESTAMP).toNumber()
: Date.now() / 1000
const config = {
submissionInterval: 6,
l2BlockTime: 2,
genesisOutput: ethers.constants.HashZero,
historicalBlocks: 0,
startingBlockTimestamp:
parseInt(process.env.L2OO_STARTING_BLOCK_TIMESTAMP, 10) || Date.now(),
startingBlockNumber: 0,
l2BlockTime: 2,
startingTimestamp,
sequencerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
maxSequencerDrift: 10,
sequencerWindowSize: 2,
ownerAddress: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
}
export default config
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { BigNumber } from 'ethers'
import 'hardhat-deploy'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
const deployFn: DeployFunction = async (hre) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
const { deployConfig } = hre
if (
typeof hre.deployConfig.startingBlockTimestamp !== 'number' ||
isNaN(hre.deployConfig.startingBlockTimestamp)
typeof deployConfig.startingTimestamp !== 'number' ||
isNaN(deployConfig.startingTimestamp)
) {
throw new Error(
'Cannot deploy L2OutputOracle without specifying a valid startingBlockTimestamp.'
'Cannot deploy L2OutputOracle without specifying a valid startingTimestamp.'
)
}
await deploy('L2OutputOracleProxy', {
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
await deploy('L2OutputOracle', {
from: deployer,
args: [
hre.deployConfig.submissionInterval,
hre.deployConfig.l2BlockTime,
hre.deployConfig.genesisOutput,
hre.deployConfig.historicalBlocks,
hre.deployConfig.startingBlockTimestamp,
hre.deployConfig.sequencerAddress,
deployConfig.submissionInterval,
deployConfig.genesisOutput,
deployConfig.historicalBlocks,
deployConfig.startingBlockNumber,
deployConfig.startingTimestamp,
deployConfig.l2BlockTime,
deployConfig.sequencerAddress,
deployConfig.ownerAddress,
],
log: true,
waitConfirmations: 1,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const oracle = await hre.deployments.get('L2OutputOracle')
const proxy = await hre.deployments.get('L2OutputOracleProxy')
const Proxy = await hre.ethers.getContractAt('Proxy', proxy.address)
const L2OutputOracle = await hre.ethers.getContractAt(
'L2OutputOracle',
proxy.address
)
const tx = await Proxy.upgradeToAndCall(
oracle.address,
L2OutputOracle.interface.encodeFunctionData(
'initialize(bytes32,uint256,address,address)',
[
deployConfig.genesisOutput,
deployConfig.startingBlockNumber,
deployConfig.sequencerAddress,
deployConfig.ownerAddress,
]
)
)
await tx.wait()
const submissionInterval = await L2OutputOracle.SUBMISSION_INTERVAL()
if (!submissionInterval.eq(BigNumber.from(deployConfig.submissionInterval))) {
throw new Error('submission internal misconfigured')
}
const historicalBlocks = await L2OutputOracle.HISTORICAL_TOTAL_BLOCKS()
if (!historicalBlocks.eq(BigNumber.from(deployConfig.historicalBlocks))) {
throw new Error('historal total blocks misconfigured')
}
const startingBlockNumber = await L2OutputOracle.STARTING_BLOCK_NUMBER()
if (
!startingBlockNumber.eq(BigNumber.from(deployConfig.startingBlockNumber))
) {
throw new Error('starting block number misconfigured')
}
const startingTimestamp = await L2OutputOracle.STARTING_TIMESTAMP()
if (!startingTimestamp.eq(BigNumber.from(deployConfig.startingTimestamp))) {
throw new Error('starting timestamp misconfigured')
}
const l2BlockTime = await L2OutputOracle.L2_BLOCK_TIME()
if (!l2BlockTime.eq(BigNumber.from(deployConfig.l2BlockTime))) {
throw new Error('L2 block time misconfigured')
}
}
deployFn.tags = ['L2OutputOracle']
......
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import 'hardhat-deploy'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
const deployFn: DeployFunction = async (hre) => {
const { deploy, get } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
const { deployConfig } = hre
await deploy('OptimismPortalProxy', {
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const oracle = await get('L2OutputOracle')
await deploy('OptimismPortal', {
from: deployer,
args: [oracle.address, 2],
log: true,
waitConfirmations: 1,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const proxy = await hre.deployments.get('OptimismPortalProxy')
const Proxy = await hre.ethers.getContractAt('Proxy', proxy.address)
const portal = await hre.deployments.get('OptimismPortal')
const tx = await Proxy.upgradeTo(portal.address)
await tx.wait()
const OptimismPortal = await hre.ethers.getContractAt(
'OptimismPortal',
proxy.address
)
const l2Oracle = await OptimismPortal.L2_ORACLE()
if (l2Oracle !== oracle.address) {
throw new Error('L2 Oracle mismatch')
}
}
deployFn.tags = ['OptimismPortal']
......
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { Contract } from 'ethers'
import 'hardhat-deploy'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
const deployFn: DeployFunction = async (hre) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
const { deployConfig } = hre
await deploy('L1CrossDomainMessengerProxy', {
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
await deploy('L1CrossDomainMessenger', {
from: deployer,
args: [],
log: true,
waitConfirmations: 1,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const provider = hre.ethers.provider.getSigner(deployer)
const oracle = await hre.deployments.get('L2OutputOracle')
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 L1CrossDomainMessenger = await hre.ethers.getContractAt(
'L1CrossDomainMessenger',
proxy.address
)
const L1CrossDomainMessenger = new Contract(
const upgradeTx = await Proxy.upgradeToAndCall(
messenger.address,
messenger.abi,
provider
L1CrossDomainMessenger.interface.encodeFunctionData('initialize(address)', [
portal.address,
])
)
await upgradeTx.wait()
const tx = await L1CrossDomainMessenger.initialize(oracle.address)
const receipt = await tx.wait()
console.log(`${receipt.transactionHash}: initialize(${oracle.address})`)
const portalAddress = await L1CrossDomainMessenger.portal()
if (portalAddress !== portal.address) {
throw new Error('portal misconfigured')
}
}
deployFn.tags = ['L1CrossDomainMessenger']
......
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { Contract } from 'ethers'
import 'hardhat-deploy'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
const deployFn: DeployFunction = async (hre) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
const { deployConfig } = hre
await deploy('L1StandardBridgeProxy', {
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
await deploy('L1StandardBridge', {
from: deployer,
args: [],
log: true,
waitConfirmations: 1,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const provider = hre.ethers.provider.getSigner(deployer)
const messenger = await hre.deployments.get('L1CrossDomainMessenger')
const proxy = await hre.deployments.get('L1StandardBridgeProxy')
const Proxy = await hre.ethers.getContractAt('Proxy', proxy.address)
const bridge = await hre.deployments.get('L1StandardBridge')
const L1StandardBridge = new Contract(bridge.address, bridge.abi, provider)
const tx = await L1StandardBridge.initialize(messenger.address)
const receipt = await tx.wait()
console.log(`${receipt.transactionHash}: initialize(${messenger.address})`)
const messenger = await hre.deployments.get('L1CrossDomainMessengerProxy')
const L1StandardBridge = await hre.ethers.getContractAt(
'L1StandardBridge',
proxy.address
)
const upgradeTx = await Proxy.upgradeToAndCall(
bridge.address,
L1StandardBridge.interface.encodeFunctionData('initialize(address)', [
messenger.address,
])
)
await upgradeTx.wait()
if (messenger.address !== (await L1StandardBridge.messenger())) {
throw new Error('misconfigured messenger')
}
}
deployFn.tags = ['L1StandardBridge']
......
/* Imports: Internal */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { Contract } from 'ethers'
import 'hardhat-deploy'
import '@nomiclabs/hardhat-ethers'
import '@eth-optimism/hardhat-deploy-config'
const deployFn: DeployFunction = async (hre) => {
const { deploy } = hre.deployments
const { deployer } = await hre.getNamedAccounts()
const { deployConfig } = hre
await deploy('OptimismMintableTokenFactoryProxy', {
contract: 'Proxy',
from: deployer,
args: [deployer],
log: true,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
await deploy('OptimismMintableTokenFactory', {
from: deployer,
args: [],
log: true,
waitConfirmations: 1,
waitConfirmations: deployConfig.deploymentWaitConfirmations,
})
const provider = hre.ethers.provider.getSigner(deployer)
const factory = await hre.deployments.get('OptimismMintableTokenFactory')
const bridge = await hre.deployments.get('L1StandardBridge')
const bridge = await hre.deployments.get('L1StandardBridgeProxy')
const proxy = await hre.deployments.get('OptimismMintableTokenFactoryProxy')
const Proxy = await hre.ethers.getContractAt('Proxy', proxy.address)
const OptimismMintableTokenFactory = await hre.ethers.getContractAt(
'OptimismMintableTokenFactory',
proxy.address
)
const OptimismMintableTokenFactory = new Contract(
const upgradeTx = await Proxy.upgradeToAndCall(
factory.address,
factory.abi,
provider
OptimismMintableTokenFactory.interface.encodeFunctionData(
'initialize(address)',
[bridge.address]
)
)
await upgradeTx.wait()
const tx = await OptimismMintableTokenFactory.initialize(bridge.address)
const receipt = await tx.wait()
console.log(`${receipt.transactionHash}: initialize(${bridge.address})`)
if (bridge.address !== (await OptimismMintableTokenFactory.bridge())) {
throw new Error('bridge misconfigured')
}
}
deployFn.tags = ['OptimismMintableTokenFactory']
......
......@@ -13,4 +13,6 @@ remappings = [
'ds-test/=node_modules/ds-test/src'
]
extra_output = ['devdoc', 'userdoc', 'metadata', 'storageLayout']
bytecode_hash = "none"
bytecode_hash = 'none'
# build_info = true
......@@ -3,7 +3,7 @@ import { HardhatUserConfig, task, subtask } from 'hardhat/config'
import { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } from 'hardhat/builtin-tasks/task-names'
// Hardhat plugins
import '@nomiclabs/hardhat-waffle'
import '@nomiclabs/hardhat-ethers'
import '@typechain/hardhat'
import 'solidity-coverage'
import 'hardhat-deploy'
......@@ -11,7 +11,10 @@ import '@foundry-rs/hardhat-forge'
import '@eth-optimism/hardhat-deploy-config'
// Hardhat tasks
import './tasks/genesis-l1'
import './tasks/genesis-l2'
import './tasks/deposits'
import './tasks/rollup-config'
subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS).setAction(
async (_, __, runSuper) => {
......@@ -39,10 +42,8 @@ const config: HardhatUserConfig = {
},
goerli: {
chainId: 5,
url: (process.env.L1_RPC || ''),
accounts: [
(process.env.PRIVATE_KEY_DEPLOYER || ethers.constants.HashZero),
],
url: process.env.L1_RPC || '',
accounts: [process.env.PRIVATE_KEY_DEPLOYER || ethers.constants.HashZero],
},
},
paths: {
......@@ -73,12 +74,31 @@ const config: HardhatUserConfig = {
historicalBlocks: {
type: 'number',
},
startingBlockTimestamp: {
startingBlockNumber: {
type: 'number',
},
startingTimestamp: {
type: 'number',
},
sequencerAddress: {
type: 'address',
},
ownerAddress: {
type: 'address',
},
},
external: {
contracts: [
{
artifacts: '../contracts/artifacts',
},
{
artifacts: '../contracts-governance/artifacts',
},
],
deployments: {
goerli: ['../contracts/deployments/goerli'],
},
},
solidity: {
compilers: [
......
......@@ -20,9 +20,10 @@
"build:ts": "tsc",
"test": "forge test",
"gas-snapshot": "forge snapshot",
"storage-snapshot": "./scripts/storage-snapshot.sh",
"slither": "slither .",
"clean": "rm -rf ./dist ./artifacts ./forge-artifacts ./cache ./coverage ./tsconfig.tsbuildinfo",
"lint:ts:check": "eslint .",
"lint:ts:check": "eslint . --max-warnings=0",
"lint:contracts:check": "yarn solhint -f table 'contracts/**/*.sol'",
"lint:check": "yarn lint:contracts:check && yarn lint:ts:check",
"lint:ts:fix": "yarn prettier --write .",
......@@ -44,6 +45,7 @@
},
"devDependencies": {
"@eth-optimism/hardhat-deploy-config": "^0.2.0",
"@defi-wonderland/smock": "^2.0.2",
"@foundry-rs/hardhat-forge": "^0.1.7",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
......
#!/usr/bin/env bash
set -e
if ! command -v forge &> /dev/null
then
echo "forge could not be found. Please install forge by running:"
echo "curl -L https://foundry.paradigm.xyz | bash"
exit
fi
contracts=(
L1CrossDomainMessenger
L1StandardBridge
L2OutputOracle
OptimismPortal
DeployerWhitelist
GasPriceOracle
L1Block
L1BlockNumber
L2CrossDomainMessenger
L2StandardBridge
L2ToL1MessagePasser
OVM_ETH
SequencerFeeVault
WETH9
ProxyAdmin
Proxy
L1ChugSplashProxy
OptimismMintableERC20
OptimismMintableTokenFactory
)
dir=$(dirname "$0")
echo "Creating storage layout diagrams.."
echo "=======================" > $dir/../.storage-layout
echo "👁👁 STORAGE LAYOUT snapshot 👁👁" >> $dir/../.storage-layout
echo "=======================" >> $dir/../.storage-layout
for contract in ${contracts[@]}
do
echo -e "\n=======================" >> $dir/../.storage-layout
echo "➡ $contract">> $dir/../.storage-layout
echo -e "=======================\n" >> $dir/../.storage-layout
forge inspect --pretty $contract storage-layout >> $dir/../.storage-layout
done
echo "Storage layout snapshot stored at $dir/../.storage-layout"
/**
* Predeploys are Solidity contracts that are injected into the initial L2 state and provide
* various useful functions.
* Notes:
* 0x42...04 was the address of the OVM_ProxySequencerEntrypoint. This contract is no longer in
* use and has therefore been removed. We may place a new predeployed contract at this address
* in the future. See https://github.com/ethereum-optimism/optimism/pull/549 for more info.
*/
export const bedrockPredeploys = {
L1_BLOCK_ATTRIBUTES: '0x4200000000000000000000000000000000000015',
WITHDRAWER: '0x4200000000000000000000000000000000000016',
export const predeploys = {
L2ToL1MessagePasser: '0x4200000000000000000000000000000000000000',
DeployerWhitelist: '0x4200000000000000000000000000000000000002',
L2CrossDomainMessenger: '0x4200000000000000000000000000000000000007',
GasPriceOracle: '0x420000000000000000000000000000000000000F',
L2StandardBridge: '0x4200000000000000000000000000000000000010',
SequencerFeeVault: '0x4200000000000000000000000000000000000011',
OptimismMintableTokenFactory: '0x4200000000000000000000000000000000000012',
L1BlockNumber: '0x4200000000000000000000000000000000000013',
L1Block: '0x4200000000000000000000000000000000000015',
OVM_ETH: '0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000',
WETH9: '0x4200000000000000000000000000000000000006',
GovernanceToken: '0x4200000000000000000000000000000000000042',
}
export const futurePredeploys = {
System1: '0x4200000000000000000000000000000000000014',
}
......@@ -2,7 +2,7 @@ import { ethers } from 'ethers'
import { toHexString } from '@eth-optimism/core-utils'
import { TrieTestGenerator } from './trie-test-generator'
import { bedrockPredeploys } from './constants'
import { predeploys } from './constants'
interface WithdrawalArgs {
nonce: number
......@@ -63,7 +63,7 @@ export const generateMockWithdrawalProof = async (
const generator = await TrieTestGenerator.fromAccounts({
accounts: [
{
address: bedrockPredeploys.WITHDRAWER,
address: predeploys.L2ToL1MessagePasser,
nonce: 0,
balance: 0,
codeHash: ethers.utils.keccak256('0x1234'),
......
import fs from 'fs'
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import { Genesis, State } from '@eth-optimism/core-utils'
task('genesis-l1', 'create a genesis config')
.addOptionalParam(
'outfile',
'The file to write the output JSON to',
'genesis.json'
)
.setAction(async (args, hre) => {
// TODO: type needs to be updated to work with modern geth
const alloc: State | any = {}
// Give each predeploy a single wei
for (let i = 0; i <= 0xff; i++) {
const buf = Buffer.alloc(2)
buf.writeUInt16BE(i, 0)
const addr = ethers.utils.hexConcat([
'0x000000000000000000000000000000000000',
ethers.utils.hexZeroPad(buf, 2),
])
alloc[addr] = {
balance: '0x1',
}
}
// Prefund dev accounts
const accounts = [
'0x14dC79964da2C08b23698B3D3cc7Ca32193d9955',
'0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65',
'0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec',
'0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f',
'0x2546BcD3c84621e976D8185a91A922aE77ECEc30',
'0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
'0x71bE63f3384f5fb98995898A86B02Fb2426c5788',
'0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199',
'0x90F79bf6EB2c4f870365E785982E1f101E93b906',
'0x976EA74026E726554dB657fA54763abd0C3a0aa9',
'0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
'0xBcd4042DE499D14e55001CcbB24a551F3b954096',
'0xFABB0ac9d68B0B445fB7357272Ff202C5651694a',
'0xa0Ee7A142d267C1f36714E4a8F75612F20a79720',
'0xbDA5747bFD65F08deb54cb465eB87D40e51B197E',
'0xcd3B766CCDd6AE721141F452C550Ca635964ce71',
'0xdD2FD4581271e230360230F9337D5c0430Bf44C0',
'0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097',
'0xde3829a23df1479438622a08a116e8eb3f620bb5',
'0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
]
const signers = await hre.ethers.getSigners()
for (const signer of signers) {
accounts.push(signer.address)
}
for (const account of accounts) {
alloc[ethers.utils.getAddress(account)] = {
balance:
'0x200000000000000000000000000000000000000000000000000000000000000',
}
}
const timestamp = hre.deployConfig.startingTimestamp
if (timestamp === undefined) {
throw new Error('Must configure starting block timestamp')
}
const genesis: Genesis = {
config: {
chainId: 900,
homesteadBlock: 0,
eip150Block: 0,
eip150Hash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
eip155Block: 0,
eip158Block: 0,
byzantiumBlock: 0,
constantinopleBlock: 0,
petersburgBlock: 0,
istanbulBlock: 0,
muirGlacierBlock: 0,
berlinBlock: 0,
londonBlock: 0,
clique: {
period: 15,
epoch: 30000,
},
},
nonce: '0x0',
timestamp: ethers.BigNumber.from(timestamp).toHexString(),
extraData:
'0x0000000000000000000000000000000000000000000000000000000000000000ca062b0fd91172d89bcd4bb084ac4e21972cc4670000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
gasLimit: ethers.BigNumber.from(15_000_000).toHexString(),
difficulty: '0x1',
mixHash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
coinbase: '0x0000000000000000000000000000000000000000',
alloc,
}
fs.writeFileSync(args.outfile, JSON.stringify(genesis, null, 2))
})
import fs from 'fs'
import assert from 'assert'
import { OptimismGenesis, State } from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
import { task } from 'hardhat/config'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { predeploys } from '../src'
const prefix = '0x420000000000000000000000000000000000'
const implementationSlot =
'0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'
const adminSlot =
'0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103'
const toCodeAddr = (addr: string) => {
const address = ethers.utils.hexConcat([
'0xc0d3c0d3c03dc03dc03dc03dc03dc03dc03d',
'0x' + addr.slice(prefix.length),
])
return ethers.utils.getAddress(address)
}
const assertEvenLength = (str: string) => {
assert(str.length % 2 === 0, str)
}
// TODO: this can be replaced with the smock version after
// a new release of foundry-rs/hardhat
const getStorageLayout = async (
hre: HardhatRuntimeEnvironment,
name: string
) => {
const buildInfo = await hre.artifacts.getBuildInfo(name)
const key = Object.keys(buildInfo.output.contracts)[0]
return (buildInfo.output.contracts[key][name] as any).storageLayout
}
task('genesis-l2', 'create a genesis config')
.addOptionalParam(
'outfile',
'The file to write the output JSON to',
'genesis.json'
)
.setAction(async (args, hre) => {
const {
computeStorageSlots,
// eslint-disable-next-line @typescript-eslint/no-var-requires
} = require('@defi-wonderland/smock/dist/src/utils')
const { deployConfig } = hre
// Use the addresses of the proxies here instead of the implementations
// Be backwards compatible
let ProxyL1CrossDomainMessenger = await hre.deployments.getOrNull(
'Proxy__OVM_L1CrossDomainMessenger'
)
if (ProxyL1CrossDomainMessenger === undefined) {
ProxyL1CrossDomainMessenger = await hre.deployments.get(
'L1CrossDomainMessengerProxy'
)
}
// Be backwards compatible
let ProxyL1StandardBridge = await hre.deployments.getOrNull(
'Proxy__OVM_L1StandardBridge'
)
if (ProxyL1StandardBridge === undefined) {
ProxyL1StandardBridge = await hre.deployments.get('L1StandardBridgeProxy')
}
const variables = {
L2ToL1MessagePasser: {
nonce: 0,
},
L2CrossDomainMessenger: {
_initialized: 1,
_owner: deployConfig.l2CrossDomainMessengerOwner,
xDomainMsgSender: '0x000000000000000000000000000000000000dEaD',
msgNonce: 0,
otherMessenger: ProxyL1CrossDomainMessenger.address,
// TODO: handle blockedSystemAddresses mapping
// blockedSystemAddresses: [{key: '', value: ''}],
},
GasPriceOracle: {
_owner: deployConfig.gasPriceOracleOwner,
overhead: deployConfig.gasPriceOracleOverhead,
scalar: deployConfig.gasPriceOracleScalar,
decimals: deployConfig.gasPriceOracleDecimals,
},
L2StandardBridge: {
messenger: predeploys.L2CrossDomainMessenger,
otherBridge: ProxyL1StandardBridge.address,
},
SequencerFeeVault: {
l1FeeWallet: ethers.constants.AddressZero,
},
OptimismMintableTokenFactory: {
bridge: ethers.constants.AddressZero,
},
L1Block: {
number: deployConfig.l1BlockInitialNumber,
timestamp: deployConfig.l1BlockInitialTimestamp,
basefee: deployConfig.l1BlockInitialBasefee,
hash: deployConfig.l1BlockInitialHash,
sequenceNumber: deployConfig.l1BlockInitialSequenceNumber,
},
OVM_ETH: {
bridge: predeploys.L2StandardBridge,
remoteToken: ethers.constants.AddressZero,
_name: 'Ether',
_symbol: 'ETH',
},
WETH9: {
name: 'Wrapped Ether',
symbol: 'WETH',
decimals: 18,
},
GovernanceToken: {
name: 'Optimism',
symbol: 'OP',
_owner: deployConfig.proxyAdmin,
},
}
assertEvenLength(implementationSlot)
assertEvenLength(adminSlot)
assertEvenLength(deployConfig.proxyAdmin)
const predeployAddrs = new Set()
for (const addr of Object.values(predeploys)) {
predeployAddrs.add(ethers.utils.getAddress(addr))
}
// TODO: geth likes strings for nonce and balance now
const alloc: State = {}
// Set a proxy at each predeploy address
const proxy = await hre.artifacts.readArtifact('Proxy')
for (let i = 0; i <= 0xffff; i++) {
const num = ethers.utils.hexZeroPad('0x' + i.toString(16), 2)
const addr = ethers.utils.getAddress(
ethers.utils.hexConcat([prefix, num])
)
// There is no proxy at OVM_ETH or the GovernanceToken
if (
addr === ethers.utils.getAddress(predeploys.OVM_ETH) ||
addr === ethers.utils.getAddress(predeploys.GovernanceToken)
) {
continue
}
alloc[addr] = {
nonce: '0x0',
balance: '0x0',
code: proxy.deployedBytecode,
storage: {
[adminSlot]: deployConfig.proxyAdmin,
},
}
if (predeployAddrs.has(ethers.utils.getAddress(addr))) {
alloc[addr].storage[implementationSlot] = toCodeAddr(addr)
}
}
// Set the GovernanceToken in the state
// Cannot easily set storage due to no easy access to compiler
// output
const governanceToken = await hre.deployments.getArtifact('GovernanceToken')
alloc[predeploys.GovernanceToken] = {
nonce: '0x0',
balance: '0x0',
code: governanceToken.deployedBytecode,
}
// Give each predeploy a single wei
for (let i = 0; i <= 0xff; i++) {
const buf = Buffer.alloc(2)
buf.writeUInt16BE(i, 0)
const addr = ethers.utils.hexConcat([
'0x000000000000000000000000000000000000',
ethers.utils.hexZeroPad(buf, 2),
])
alloc[addr] = {
balance: '0x1',
}
}
if (deployConfig.fundDevAccounts) {
const accounts = [
'0xde3829a23df1479438622a08a116e8eb3f620bb5',
'0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266',
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
'0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
]
const signers = await hre.ethers.getSigners()
for (const signer of signers) {
accounts.push(signer.address)
}
for (const account of accounts) {
alloc[account] = {
balance:
'0x200000000000000000000000000000000000000000000000000000000000000',
}
}
}
// Set the predeploys in the state
for (const [name, addr] of Object.entries(predeploys)) {
if (name === 'GovernanceToken') {
continue
}
const artifact = await hre.artifacts.readArtifact(name)
assertEvenLength(artifact.deployedBytecode)
const allocAddr = name === 'OVM_ETH' ? addr : toCodeAddr(addr)
assertEvenLength(allocAddr)
alloc[allocAddr] = {
nonce: '0x00',
balance: '0x00',
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 = {
config: {
chainId: deployConfig.genesisBlockChainid,
homesteadBlock: 0,
eip150Block: 0,
eip155Block: 0,
eip158Block: 0,
byzantiumBlock: 0,
constantinopleBlock: 0,
petersburgBlock: 0,
istanbulBlock: 0,
muirGlacierBlock: 0,
berlinBlock: 0,
londonBlock: 0,
mergeForkBlock: 0,
terminalTotalDifficulty: 0,
clique: {
period: 0,
epoch: 30000,
},
},
nonce: '0x1234',
difficulty: '0x1',
timestamp: ethers.BigNumber.from(
deployConfig.startingTimestamp
).toHexString(),
gasLimit: deployConfig.genesisBlockGasLimit,
extraData: deployConfig.genesisBlockExtradata,
optimism: {
enabled: true,
baseFeeRecipient: deployConfig.optimsismBaseFeeRecipient,
l1FeeRecipient: deployConfig.optimismL1FeeRecipient,
},
alloc,
}
fs.writeFileSync(args.outfile, JSON.stringify(genesis, null, 2))
})
import fs from 'fs'
import { task } from 'hardhat/config'
import { OpNodeConfig, getChainId } from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
task('rollup-config', 'create a genesis config')
.addOptionalParam(
'outfile',
'The file to write the output JSON to',
'rollup.json'
)
.addOptionalParam('l1RpcUrl', 'The L1 RPC URL', 'http://127.0.0.1:8545')
.addOptionalParam('l2RpcUrl', 'The L2 RPC URL', 'http://127.0.0.1:9545')
.setAction(async (args, hre) => {
const { deployConfig } = hre
const l1 = new ethers.providers.StaticJsonRpcProvider(args.l1RpcUrl)
const l2 = new ethers.providers.StaticJsonRpcProvider(args.l2RpcUrl)
const l1Genesis = await l1.getBlock('earliest')
const l2Genesis = await l2.getBlock('earliest')
const portal = await hre.deployments.get('OptimismPortalProxy')
const config: OpNodeConfig = {
genesis: {
l1: {
hash: l1Genesis.hash,
number: 0,
},
l2: {
hash: l2Genesis.hash,
number: 0,
},
l2_time: deployConfig.startingTimestamp,
},
block_time: deployConfig.l2BlockTime,
max_sequencer_drift: deployConfig.maxSequencerDrift,
seq_window_size: deployConfig.sequencerWindowSize,
l1_chain_id: await getChainId(l1),
l2_chain_id: await getChainId(l2),
p2p_sequencer_address: '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc',
fee_recipient_address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
batch_inbox_address: '0xff00000000000000000000000000000000000002',
batch_sender_address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',
deposit_contract_address: portal.address,
}
fs.writeFileSync(args.outfile, JSON.stringify(config, null, 2))
})
......@@ -7,6 +7,7 @@ import 'solidity-coverage'
import { task, types } from 'hardhat/config'
import { providers, utils, Wallet } from 'ethers'
import { CrossChainMessenger } from '@eth-optimism/sdk'
import { getChainId } from '@eth-optimism/core-utils'
import './scripts/deploy-token'
import './scripts/multi-send'
......@@ -42,11 +43,13 @@ task('deposit', 'Deposits funds onto Optimism.')
}
const l1Provider = new providers.JsonRpcProvider(l1ProviderUrl)
const l2Provider = new providers.JsonRpcProvider(l2ProviderUrl)
const l1Wallet = new Wallet(privateKey, l1Provider)
const messenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet,
l2SignerOrProvider: l2ProviderUrl,
l1ChainId: (await l1Provider.getNetwork()).chainId,
l2SignerOrProvider: l2Provider,
l1ChainId: await getChainId(l1Provider),
l2ChainId: await getChainId(l2Provider),
})
const amountWei = utils.parseEther(amountEth)
......@@ -57,8 +60,6 @@ task('deposit', 'Deposits funds onto Optimism.')
console.log(`Got TX hash ${tx.hash}. Waiting...`)
await tx.wait()
const l2Provider = new providers.JsonRpcProvider(l2ProviderUrl)
const l1WalletOnL2 = new Wallet(privateKey, l2Provider)
await l1WalletOnL2.sendTransaction({
to,
......@@ -76,7 +77,16 @@ const privKey = process.env.PRIVATE_KEY || '0x' + '11'.repeat(32)
* @type import("hardhat/config").HardhatUserConfig
*/
module.exports = {
solidity: '0.8.12',
solidity: {
version: '0.8.12',
settings: {
outputSelection: {
'*': {
'*': ['metadata', 'storageLayout'],
},
},
},
},
networks: {
optimism: {
chainId: 17,
......
......@@ -28,6 +28,7 @@
"deploy:mainnet": "hardhat deploy-token --network 'optimism-mainnet'"
},
"dependencies": {
"@eth-optimism/core-utils": "^0.8.7",
"@eth-optimism/sdk": "^1.1.9",
"@ethersproject/hardware-wallets": "^5.6.1",
"@nomiclabs/hardhat-ethers": "^2.0.2",
......@@ -37,8 +38,8 @@
"@openzeppelin/contracts": "4.5.0",
"commander": "^9.3.0",
"csv-parse": "^5.0.4",
"ethereumjs-util": "^7.1.4",
"eth-sig-util": "^3.0.1",
"ethereumjs-util": "^7.1.4",
"ethers": "^5.6.8",
"hardhat": "^2.9.6"
},
......
......@@ -181,8 +181,8 @@ contract L1StandardBridge is IL1StandardBridge, CrossDomainEnabled {
bytes calldata _data
) internal {
// When a deposit is initiated on L1, the L1 Bridge transfers the funds to itself for future
// withdrawals. safeTransferFrom also checks if the contract has code, so this will fail if
// _from is an EOA or address(0).
// withdrawals. The use of safeTransferFrom enables support of "broken tokens" which do not
// return a boolean value.
// slither-disable-next-line reentrancy-events, reentrancy-benign
IERC20(_l1Token).safeTransferFrom(_from, address(this), _amount);
......
......@@ -5,8 +5,8 @@
*/
export interface State {
[address: string]: {
nonce: number
balance: string
nonce?: string
balance?: string
codeHash?: string
root?: string
code?: string
......@@ -24,6 +24,7 @@ export interface ChainConfig {
chainId: number
homesteadBlock: number
eip150Block: number
eip150Hash?: string
eip155Block: number
eip158Block: number
byzantiumBlock: number
......@@ -34,6 +35,7 @@ export interface ChainConfig {
berlinBlock: number
londonBlock?: number
arrowGlacierBlock?: number
grayGlacierBlock?: number
mergeForkBlock?: number
terminalTotalDifficulty?: number
clique?: {
......@@ -48,12 +50,31 @@ export interface ChainConfig {
*/
export interface Genesis {
config: ChainConfig
nonce?: number
timestamp?: number
nonce?: string
timestamp?: string
difficulty: string
mixHash?: string
coinbase?: string
number?: string
gasLimit: string
gasUsed?: string
parentHash?: string
extraData: string
alloc: State
}
/**
* Represents the chain config for an Optimism chain
*/
export interface OptimismChainConfig {
enabled: boolean
baseFeeRecipient: string
l1FeeRecipient: string
}
/**
* Represents the Genesis file format for an Optimism chain
*/
export interface OptimismGenesis extends Genesis {
optimism: OptimismChainConfig
}
......@@ -6,3 +6,4 @@ export * from './alias'
export * from './batch-encoding'
export * from './fees'
export * from './rollup-types'
export * from './op-node'
export interface OpNodeConfig {
genesis: {
l1: {
hash: string
number: number
}
l2: {
hash: string
number: number
}
l2_time: number
}
block_time: number
max_sequencer_drift: number
seq_window_size: number
l1_chain_id: number
l2_chain_id: number
p2p_sequencer_address: string
fee_recipient_address: string
batch_inbox_address: string
batch_sender_address: string
deposit_contract_address: string
}
......@@ -81,6 +81,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
l1SignerOrProvider: this.options.l1RpcProvider,
l2SignerOrProvider: this.options.l2RpcProvider,
l1ChainId: await getChainId(this.options.l1RpcProvider),
l2ChainId: await getChainId(this.options.l2RpcProvider),
})
// We use this a lot, a bit cleaner to pull out to the top level of the state object.
......@@ -135,6 +136,16 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
const batchStart = event.args._prevTotalElements.toNumber() + 1
const batchSize = event.args._batchSize.toNumber()
const batchEnd = batchStart + batchSize
const latestBlock = await this.options.l2RpcProvider.getBlockNumber()
if (latestBlock < batchEnd) {
this.logger.info(`node is behind, waiting for sync`, {
batchEnd,
latestBlock,
})
return
}
// `getBlockRange` has a limit of 1000 blocks, so we have to break this request out into
// multiple requests of maximum 1000 blocks in the case that batchSize > 1000.
......
......@@ -33,8 +33,8 @@ const getTargetOutput = async (
withdrawalTimestamp: number
) => {
const submissionInterval = (await oracle.SUBMISSION_INTERVAL()).toNumber()
const startingBlockTimestamp = (
await oracle.STARTING_BLOCK_TIMESTAMP()
const startingTimestamp = (
await oracle.STARTING_TIMESTAMP()
).toNumber()
const nextTimestamp = (await oracle.nextTimestamp()).toNumber()
let targetOutputTimestamp
......@@ -45,10 +45,10 @@ const getTargetOutput = async (
// Calculate the first timestamp greater than the burnBlock which will be appended.
targetOutputTimestamp =
Math.ceil(
(withdrawalTimestamp - startingBlockTimestamp) / submissionInterval
(withdrawalTimestamp - startingTimestamp) / submissionInterval
) *
submissionInterval +
startingBlockTimestamp
startingTimestamp
}
return targetOutputTimestamp
......
......@@ -89,6 +89,7 @@ export class MessageRelayerService extends BaseServiceV2<
l1SignerOrProvider: this.state.wallet,
l2SignerOrProvider: this.options.l2RpcProvider,
l1ChainId: await getChainId(this.state.wallet.provider),
l2ChainId: await getChainId(this.options.l2RpcProvider),
})
this.state.highestCheckedL2Tx = this.options.fromL2TransactionIndex || 1
......
......@@ -54,6 +54,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
public l1SignerOrProvider: Signer | Provider
public l2SignerOrProvider: Signer | Provider
public l1ChainId: number
public l2ChainId: number
public contracts: OEContracts
public bridges: BridgeAdapters
public depositConfirmationBlocks: number
......@@ -66,6 +67,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
* @param opts.l1SignerOrProvider Signer or Provider for the L1 chain, or a JSON-RPC url.
* @param opts.l2SignerOrProvider Signer or Provider for the L2 chain, or a JSON-RPC url.
* @param opts.l1ChainId Chain ID for the L1 chain.
* @param opts.l2ChainId Chain ID for the L2 chain.
* @param opts.depositConfirmationBlocks Optional number of blocks before a deposit is confirmed.
* @param opts.l1BlockTimeSeconds Optional estimated block time in seconds for the L1 chain.
* @param opts.contracts Optional contract address overrides.
......@@ -75,6 +77,7 @@ export class CrossChainMessenger implements ICrossChainMessenger {
l1SignerOrProvider: SignerOrProviderLike
l2SignerOrProvider: SignerOrProviderLike
l1ChainId: NumberLike
l2ChainId: NumberLike
depositConfirmationBlocks?: NumberLike
l1BlockTimeSeconds?: NumberLike
contracts?: DeepPartial<OEContractsLike>
......@@ -83,24 +86,25 @@ export class CrossChainMessenger implements ICrossChainMessenger {
this.l1SignerOrProvider = toSignerOrProvider(opts.l1SignerOrProvider)
this.l2SignerOrProvider = toSignerOrProvider(opts.l2SignerOrProvider)
this.l1ChainId = toNumber(opts.l1ChainId)
this.l2ChainId = toNumber(opts.l2ChainId)
this.depositConfirmationBlocks =
opts?.depositConfirmationBlocks !== undefined
? toNumber(opts.depositConfirmationBlocks)
: DEPOSIT_CONFIRMATION_BLOCKS[this.l1ChainId] || 0
: DEPOSIT_CONFIRMATION_BLOCKS[this.l2ChainId] || 0
this.l1BlockTimeSeconds =
opts?.l1BlockTimeSeconds !== undefined
? toNumber(opts.l1BlockTimeSeconds)
: CHAIN_BLOCK_TIMES[this.l1ChainId] || 1
this.contracts = getAllOEContracts(this.l1ChainId, {
this.contracts = getAllOEContracts(this.l2ChainId, {
l1SignerOrProvider: this.l1SignerOrProvider,
l2SignerOrProvider: this.l2SignerOrProvider,
overrides: opts.contracts,
})
this.bridges = getBridgeAdapters(this.l1ChainId, this, {
this.bridges = getBridgeAdapters(this.l2ChainId, this, {
overrides: opts.bridges,
})
}
......
......@@ -46,6 +46,11 @@ export interface ICrossChainMessenger {
*/
l1ChainId: number
/**
* Chain ID for the L2 network.
*/
l2ChainId: number
/**
* Contract objects attached to their respective providers and addresses.
*/
......
......@@ -10,15 +10,26 @@ import { ICrossChainMessenger } from './cross-chain-messenger'
import { IBridgeAdapter } from './bridge-adapter'
/**
* Commonly used Chain IDs
* L1 network chain IDs
*/
export enum Chain {
export enum L1ChainID {
MAINNET = 1,
GOERLI = 5,
KOVAN = 42,
HARDHAT_LOCAL = 31337,
}
/**
* L2 network chain IDs
*/
export enum L2ChainID {
OPTIMISM = 10,
OPTIMISM_GOERLI = 420,
OPTIMISM_KOVAN = 69,
OPTIMISM_HARDHAT_LOCAL = 31337,
OPTIMISM_HARDHAT_DEVNET = 17,
}
/**
* L1 contract references.
*/
......
import { Chain } from '../interfaces'
import { L1ChainID, L2ChainID } from '../interfaces'
export const DEPOSIT_CONFIRMATION_BLOCKS = {
[Chain.MAINNET]: 50 as const,
[Chain.GOERLI]: 12 as const,
[Chain.KOVAN]: 12 as const,
// 2 just for testing purposes
[Chain.HARDHAT_LOCAL]: 2 as const,
export const DEPOSIT_CONFIRMATION_BLOCKS: {
[ChainID in L2ChainID]: number
} = {
[L2ChainID.OPTIMISM]: 50 as const,
[L2ChainID.OPTIMISM_GOERLI]: 12 as const,
[L2ChainID.OPTIMISM_KOVAN]: 12 as const,
[L2ChainID.OPTIMISM_HARDHAT_LOCAL]: 2 as const,
[L2ChainID.OPTIMISM_HARDHAT_DEVNET]: 2 as const,
}
export const CHAIN_BLOCK_TIMES = {
[Chain.MAINNET]: 13 as const,
[Chain.GOERLI]: 15 as const,
[Chain.KOVAN]: 4 as const,
[Chain.HARDHAT_LOCAL]: 1 as const,
export const CHAIN_BLOCK_TIMES: {
[ChainID in L1ChainID]: number
} = {
[L1ChainID.MAINNET]: 13 as const,
[L1ChainID.GOERLI]: 15 as const,
[L1ChainID.KOVAN]: 4 as const,
[L1ChainID.HARDHAT_LOCAL]: 1 as const,
}
This diff is collapsed.
This diff is collapsed.
......@@ -231,9 +231,7 @@ func (m *MockBackend) Requests() []*RecordedRequest {
m.mtx.RLock()
defer m.mtx.RUnlock()
out := make([]*RecordedRequest, len(m.requests))
for i := 0; i < len(m.requests); i++ {
out[i] = m.requests[i]
}
copy(out, m.requests)
return out
}
......
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