diff --git a/.changeset/beige-zoos-roll.md b/.changeset/beige-zoos-roll.md
new file mode 100644
index 0000000000000..c9e68e9aea181
--- /dev/null
+++ b/.changeset/beige-zoos-roll.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/integration-tests': patch
+---
+
+Updates to support nightly actor tests
diff --git a/.changeset/chilled-mayflies-push.md b/.changeset/chilled-mayflies-push.md
new file mode 100644
index 0000000000000..e8bc942f44a3b
--- /dev/null
+++ b/.changeset/chilled-mayflies-push.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/proxyd': minor
+---
+
+Add request/response payload size metrics to proxyd
diff --git a/.changeset/clean-jars-check.md b/.changeset/clean-jars-check.md
new file mode 100644
index 0000000000000..19f026f377772
--- /dev/null
+++ b/.changeset/clean-jars-check.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/data-transport-layer': patch
+---
+
+Smaller filter query for searching for L1 start height. This number should be configured so that the search does not need to happen because using a smaller filter will cause it to take too long.
diff --git a/.changeset/cold-files-melt.md b/.changeset/cold-files-melt.md
new file mode 100644
index 0000000000000..efeab07c49397
--- /dev/null
+++ b/.changeset/cold-files-melt.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/proxyd': minor
+---
+
+cache immutable RPC responses in proxyd
diff --git a/.changeset/empty-deers-buy.md b/.changeset/empty-deers-buy.md
new file mode 100644
index 0000000000000..067cc1bcc47a1
--- /dev/null
+++ b/.changeset/empty-deers-buy.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/op-exporter': patch
+---
+
+Fixes panic caused by version initialized to nil
diff --git a/.changeset/fair-eyes-build.md b/.changeset/fair-eyes-build.md
new file mode 100644
index 0000000000000..e85c9e3aee401
--- /dev/null
+++ b/.changeset/fair-eyes-build.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/proxyd': minor
+---
+
+Add X-Forwarded-For header when proxying RPCs on proxyd
diff --git a/.changeset/fair-starfishes-admire.md b/.changeset/fair-starfishes-admire.md
new file mode 100644
index 0000000000000..146841d88384b
--- /dev/null
+++ b/.changeset/fair-starfishes-admire.md
@@ -0,0 +1,7 @@
+---
+'@eth-optimism/gas-oracle': patch
+'@eth-optimism/contracts': patch
+'@eth-optimism/data-transport-layer': patch
+---
+
+String update to change the system name from OE to Optimism
diff --git a/.changeset/fluffy-ghosts-heal.md b/.changeset/fluffy-ghosts-heal.md
new file mode 100644
index 0000000000000..4af9eccdd509c
--- /dev/null
+++ b/.changeset/fluffy-ghosts-heal.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/op-exporter': patch
+---
+
+Added version metrics
diff --git a/.changeset/happy-ears-rhyme.md b/.changeset/happy-ears-rhyme.md
new file mode 100644
index 0000000000000..a3aa43fe48bab
--- /dev/null
+++ b/.changeset/happy-ears-rhyme.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/l2geth': patch
+---
+
+Implement updated timestamp logic
diff --git a/.changeset/kind-poems-happen.md b/.changeset/kind-poems-happen.md
new file mode 100644
index 0000000000000..57eb15bb7d192
--- /dev/null
+++ b/.changeset/kind-poems-happen.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/l2geth': patch
+---
+
+changed the default address to be address(0) in `call`
diff --git a/.changeset/mean-llamas-speak.md b/.changeset/mean-llamas-speak.md
new file mode 100644
index 0000000000000..4fbdc87a19580
--- /dev/null
+++ b/.changeset/mean-llamas-speak.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/message-relayer': patch
+---
+
+Fix docker build
diff --git a/.changeset/pink-frogs-report.md b/.changeset/pink-frogs-report.md
new file mode 100644
index 0000000000000..d7d043571dbaa
--- /dev/null
+++ b/.changeset/pink-frogs-report.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/batch-submitter': patch
+---
+
+Properly clear state root batch txs on startup
diff --git a/.changeset/shaggy-brooms-shave.md b/.changeset/shaggy-brooms-shave.md
new file mode 100644
index 0000000000000..404a2284e2afd
--- /dev/null
+++ b/.changeset/shaggy-brooms-shave.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/contracts': patch
+---
+
+Update hardhat task for managing the gas oracle
diff --git a/.changeset/silent-keys-hunt.md b/.changeset/silent-keys-hunt.md
new file mode 100644
index 0000000000000..35bb377bb1e04
--- /dev/null
+++ b/.changeset/silent-keys-hunt.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/contracts': patch
+---
+
+Remove legacy bin/deploy.ts script
diff --git a/.changeset/tame-trains-relax.md b/.changeset/tame-trains-relax.md
new file mode 100644
index 0000000000000..b1f984fc3be07
--- /dev/null
+++ b/.changeset/tame-trains-relax.md
@@ -0,0 +1,5 @@
+---
+'@eth-optimism/integration-tests': patch
+---
+
+Update timestamp assertion for new logic
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 684c32284fb09..7b28f163dd33e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,6 +1,38 @@
version: 2.1
orbs:
gcp-gke: circleci/gcp-gke@1.3.0
+ slack: circleci/slack@4.5.1
+slack-fail-post-step: &slack-fail-post-step
+ post-steps:
+ - slack/notify:
+ channel: $SLACK_DEFAULT_CHANNEL
+ event: fail
+ custom: |
+ {
+ "text": "",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "🔴 Nightly build failed!"
+ }
+ },
+ {
+ "type": "actions",
+ "elements": [
+ {
+ "type": "button",
+ "text": {
+ "type": "plain_text",
+ "text": "View Job"
+ },
+ "url": "${CIRCLE_BUILD_URL}"
+ }
+ ]
+ }
+ ]
+ }
commands:
build-dockerfile:
parameters:
@@ -78,6 +110,13 @@ jobs:
image-name: integration-tests
target: integration-tests
dockerfile: ./ops/docker/Dockerfile.packages
+ build-proxyd:
+ docker:
+ - image: cimg/base:2021.04
+ steps:
+ - build-dockerfile:
+ image-name: proxyd
+ dockerfile: ./go/proxyd/Dockerfile
deploy-nightly:
docker:
- image: cimg/base:2021.04
@@ -101,6 +140,15 @@ jobs:
kubectl rollout restart statefulset nightly-go-batch-submitter --namespace nightly
kubectl rollout restart statefulset nightly-dtl --namespace nightly
kubectl rollout restart deployment nightly-gas-oracle --namespace nightly
+ kubectl rollout restart deployment edge-proxyd --namespace nightly
+ notify:
+ docker:
+ - image: cimg/base:2021.04
+ steps:
+ - run:
+ name: Success
+ command: |
+ echo "Dummy job."
workflows:
@@ -114,21 +162,50 @@ workflows:
- develop
jobs:
- build-dtl:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-batch-submitter:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-deployer:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-l2geth:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-gas-oracle:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-integration-tests:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- build-go-batch-submitter:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
+ - build-proxyd:
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
- deploy-nightly:
- context: optimism
+ context:
+ - optimism
+ - slack
+ <<: *slack-fail-post-step
requires:
- build-dtl
- build-batch-submitter
@@ -136,4 +213,38 @@ workflows:
- build-deployer
- build-l2geth
- build-gas-oracle
- - build-integration-tests
\ No newline at end of file
+ - build-integration-tests
+ - build-proxyd
+ - notify:
+ context: slack
+ requires:
+ - deploy-nightly
+ post-steps:
+ - slack/notify:
+ custom: |
+ {
+ "text": "",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "✅ Nightly successfully deployed."
+ }
+ },
+ {
+ "type": "actions",
+ "elements": [
+ {
+ "type": "button",
+ "text": {
+ "type": "plain_text",
+ "text": "View Job"
+ },
+ "url": "${CIRCLE_BUILD_URL}"
+ }
+ ]
+ }
+ ]
+ }
+ event: always
\ No newline at end of file
diff --git a/.github/workflows/ts-packages.yml b/.github/workflows/ts-packages.yml
index 2efec8bf488b0..ec38fa9a19f96 100644
--- a/.github/workflows/ts-packages.yml
+++ b/.github/workflows/ts-packages.yml
@@ -104,7 +104,7 @@ jobs:
- uses: codecov/codecov-action@v1
with:
files: ./packages/contracts/coverage.json
- fail_ci_if_error: false
+ fail_ci_if_error: true
verbose: true
flags: contracts
- uses: codecov/codecov-action@v1
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d4c554b2436ab..0413bff6c1231 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -98,7 +98,7 @@ Use the above commands to recompile the packages.
### Building the rest of the system
-If you want to run an Optimistic Ethereum node OR **if you want to run the integration tests**, you'll need to build the rest of the system.
+If you want to run an Optimism node OR **if you want to run the integration tests**, you'll need to build the rest of the system.
```bash
cd ops
diff --git a/README.md b/README.md
index 4f98ff9437e00..31660e8d72a60 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
## TL;DR
-This is the primary place where [Optimism](https://optimism.io) works on stuff related to [Optimistic Ethereum](https://optimistic.etherscan.io/).
+This is where [Optimism](https://optimism.io) gets built.
## Documentation
@@ -31,16 +31,16 @@ Then check out our list of [good first issues](https://github.com/ethereum-optim
root
├── packages
-│ ├── contracts: L1 and L2 smart contracts for Optimistic Ethereum
-│ ├── core-utils: Low-level utilities that make building Optimistic Ethereum easier
+│ ├── contracts: L1 and L2 smart contracts for Optimism
+│ ├── core-utils: Low-level utilities that make building Optimism easier
│ ├── common-ts: Common tools for building apps in TypeScript
-│ ├── data-transport-layer: Service for indexing Optimistic Ethereum-related L1 data
+│ ├── data-transport-layer: Service for indexing Optimism-related L1 data
│ ├── batch-submitter: Service for submitting batches of transactions and results to L1
│ ├── message-relayer: Tool for automatically relaying L1<>L2 messages in development
│ └── replica-healthcheck: Service for monitoring the health of a replica node
-├── l2geth: Optimistic Ethereum client software, a fork of geth v1.9.10
-├── integration-tests: Various integration tests for an Optimistic Ethereum network
-└── ops: Tools for running Optimistic Ethereum nodes and networks
+├── l2geth: Optimism client software, a fork of geth v1.9.10
+├── integration-tests: Various integration tests for the Optimism network
+└── ops: Tools for running Optimism nodes and networks
## Branching Model and Releases
@@ -64,7 +64,7 @@ Please read the linked post if you're planning to make frequent PRs into this re
The `master` branch contains the code for our latest "stable" releases.
Updates from `master` always come from the `develop` branch.
-We only ever update the `master` branch when we intend to deploy code within the `develop` to the Optimistic Ethereum mainnet.
+We only ever update the `master` branch when we intend to deploy code within the `develop` to the Optimism mainnet.
Our update process takes the form of a PR merging the `develop` branch into the `master` branch.
### The `develop` branch
diff --git a/go/batch-submitter/batch_submitter.go b/go/batch-submitter/batch_submitter.go
index 4731a98151e6a..1c03e4f1e77f1 100644
--- a/go/batch-submitter/batch_submitter.go
+++ b/go/batch-submitter/batch_submitter.go
@@ -4,7 +4,6 @@ import (
"context"
"crypto/ecdsa"
"fmt"
- "math/big"
"net/http"
"os"
"strconv"
@@ -13,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/proposer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/drivers/sequencer"
"github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
@@ -159,9 +159,9 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
}
txManagerConfig := txmgr.Config{
- MinGasPrice: gasPriceFromGwei(1),
- MaxGasPrice: gasPriceFromGwei(cfg.MaxGasPriceInGwei),
- GasRetryIncrement: gasPriceFromGwei(cfg.GasRetryIncrement),
+ MinGasPrice: utils.GasPriceFromGwei(1),
+ MaxGasPrice: utils.GasPriceFromGwei(cfg.MaxGasPriceInGwei),
+ GasRetryIncrement: utils.GasPriceFromGwei(cfg.GasRetryIncrement),
ResubmissionTimeout: cfg.ResubmissionTimeout,
ReceiptQueryInterval: time.Second,
}
@@ -186,6 +186,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
Context: ctx,
Driver: batchTxDriver,
PollInterval: cfg.PollInterval,
+ ClearPendingTx: cfg.ClearPendingTxs,
L1Client: l1Client,
TxManagerConfig: txManagerConfig,
})
@@ -212,6 +213,7 @@ func NewBatchSubmitter(cfg Config, gitVersion string) (*BatchSubmitter, error) {
Context: ctx,
Driver: batchStateDriver,
PollInterval: cfg.PollInterval,
+ ClearPendingTx: cfg.ClearPendingTxs,
L1Client: l1Client,
TxManagerConfig: txManagerConfig,
})
@@ -333,7 +335,3 @@ func traceRateToFloat64(rate time.Duration) float64 {
}
return rate64
}
-
-func gasPriceFromGwei(gasPriceInGwei uint64) *big.Int {
- return new(big.Int).SetUint64(gasPriceInGwei * 1e9)
-}
diff --git a/go/batch-submitter/drivers/clear_pending_tx.go b/go/batch-submitter/drivers/clear_pending_tx.go
new file mode 100644
index 0000000000000..b6cf18d0290ec
--- /dev/null
+++ b/go/batch-submitter/drivers/clear_pending_tx.go
@@ -0,0 +1,173 @@
+package drivers
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+ "strings"
+
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+// ErrClearPendingRetry signals that a transaction from a previous running
+// instance confirmed rather than our clearing transaction on startup. In this
+// case the caller should retry.
+var ErrClearPendingRetry = errors.New("retry clear pending txn")
+
+// ClearPendingTx publishes a NOOP transaction at the wallet's next unused
+// nonce. This is used on restarts in order to clear the mempool of any prior
+// publications and ensure the batch submitter starts submitting from a clean
+// slate.
+func ClearPendingTx(
+ name string,
+ ctx context.Context,
+ txMgr txmgr.TxManager,
+ l1Client L1Client,
+ walletAddr common.Address,
+ privKey *ecdsa.PrivateKey,
+ chainID *big.Int,
+) error {
+
+ // Query for the submitter's current nonce.
+ nonce, err := l1Client.NonceAt(ctx, walletAddr, nil)
+ if err != nil {
+ log.Error(name+" unable to get current nonce",
+ "err", err)
+ return err
+ }
+
+ ctx, cancel := context.WithCancel(ctx)
+ defer cancel()
+
+ // Construct the clearing transaction submission clousure that will attempt
+ // to send the a clearing transaction transaction at the given nonce and gas
+ // price.
+ sendTx := func(
+ ctx context.Context,
+ gasPrice *big.Int,
+ ) (*types.Transaction, error) {
+ log.Info(name+" clearing pending tx", "nonce", nonce,
+ "gasPrice", gasPrice)
+
+ signedTx, err := SignClearingTx(
+ ctx, walletAddr, nonce, gasPrice, l1Client, privKey, chainID,
+ )
+ if err != nil {
+ log.Error(name+" unable to sign clearing tx", "nonce", nonce,
+ "gasPrice", gasPrice, "err", err)
+ return nil, err
+ }
+ txHash := signedTx.Hash()
+
+ err = l1Client.SendTransaction(ctx, signedTx)
+ switch {
+
+ // Clearing transaction successfully confirmed.
+ case err == nil:
+ log.Info(name+" submitted clearing tx", "nonce", nonce,
+ "gasPrice", gasPrice, "txHash", txHash)
+
+ return signedTx, nil
+
+ // Getting a nonce too low error implies that a previous transaction in
+ // the mempool has confirmed and we should abort trying to publish at
+ // this nonce.
+ case strings.Contains(err.Error(), core.ErrNonceTooLow.Error()):
+ log.Info(name + " transaction from previous restart confirmed, " +
+ "aborting mempool clearing")
+ cancel()
+ return nil, context.Canceled
+
+ // An unexpected error occurred. This also handles the case where the
+ // clearing transaction has not yet bested the gas price a prior
+ // transaction in the mempool at this nonce. In such a case we will
+ // continue until our ratchetting strategy overtakes the old
+ // transaction, or abort if the old one confirms.
+ default:
+ log.Error(name+" unable to submit clearing tx",
+ "nonce", nonce, "gasPrice", gasPrice, "txHash", txHash,
+ "err", err)
+ return nil, err
+ }
+ }
+
+ receipt, err := txMgr.Send(ctx, sendTx)
+ switch {
+
+ // If the current context is canceled, a prior transaction in the mempool
+ // confirmed. The caller should retry, which will use the next nonce, before
+ // proceeding.
+ case err == context.Canceled:
+ log.Info(name + " transaction from previous restart confirmed, " +
+ "proceeding to startup")
+ return ErrClearPendingRetry
+
+ // Otherwise we were unable to confirm our transaction, this method should
+ // be retried by the caller.
+ case err != nil:
+ log.Warn(name+" unable to send clearing tx", "nonce", nonce,
+ "err", err)
+ return err
+
+ // We succeeded in confirming a clearing transaction. Proceed to startup as
+ // normal.
+ default:
+ log.Info(name+" cleared pending tx", "nonce", nonce,
+ "txHash", receipt.TxHash)
+ return nil
+ }
+}
+
+// SignClearingTx creates a signed clearing tranaction which sends 0 ETH back to
+// the sender's address. EstimateGas is used to set an appropriate gas limit.
+func SignClearingTx(
+ ctx context.Context,
+ walletAddr common.Address,
+ nonce uint64,
+ gasPrice *big.Int,
+ l1Client L1Client,
+ privKey *ecdsa.PrivateKey,
+ chainID *big.Int,
+) (*types.Transaction, error) {
+
+ gasLimit, err := l1Client.EstimateGas(ctx, ethereum.CallMsg{
+ To: &walletAddr,
+ GasPrice: gasPrice,
+ Value: nil,
+ Data: nil,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ tx := CraftClearingTx(walletAddr, nonce, gasPrice, gasLimit)
+
+ return types.SignTx(
+ tx, types.LatestSignerForChainID(chainID), privKey,
+ )
+}
+
+// CraftClearingTx creates an unsigned clearing transaction which sends 0 ETH
+// back to the sender's address.
+func CraftClearingTx(
+ walletAddr common.Address,
+ nonce uint64,
+ gasPrice *big.Int,
+ gasLimit uint64,
+) *types.Transaction {
+
+ return types.NewTx(&types.LegacyTx{
+ To: &walletAddr,
+ Nonce: nonce,
+ GasPrice: gasPrice,
+ Gas: gasLimit,
+ Value: nil,
+ Data: nil,
+ })
+}
diff --git a/go/batch-submitter/drivers/clear_pending_tx_test.go b/go/batch-submitter/drivers/clear_pending_tx_test.go
new file mode 100644
index 0000000000000..b2e70fe2ea159
--- /dev/null
+++ b/go/batch-submitter/drivers/clear_pending_tx_test.go
@@ -0,0 +1,192 @@
+package drivers_test
+
+import (
+ "context"
+ "crypto/ecdsa"
+ "errors"
+ "math/big"
+ "testing"
+ "time"
+
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/mock"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/stretchr/testify/require"
+)
+
+func init() {
+ privKey, err := crypto.GenerateKey()
+ if err != nil {
+ panic(err)
+ }
+ testPrivKey = privKey
+ testWalletAddr = crypto.PubkeyToAddress(privKey.PublicKey)
+ testChainID = new(big.Int).SetUint64(1)
+ testGasPrice = new(big.Int).SetUint64(3)
+}
+
+var (
+ testPrivKey *ecdsa.PrivateKey
+ testWalletAddr common.Address
+ testChainID *big.Int // 1
+ testNonce = uint64(2)
+ testGasPrice *big.Int // 3
+ testGasLimit = uint64(4)
+)
+
+// TestCraftClearingTx asserts that CraftClearingTx produces the expected
+// unsigned clearing transaction.
+func TestCraftClearingTx(t *testing.T) {
+ tx := drivers.CraftClearingTx(
+ testWalletAddr, testNonce, testGasPrice, testGasLimit,
+ )
+ require.Equal(t, &testWalletAddr, tx.To())
+ require.Equal(t, testNonce, tx.Nonce())
+ require.Equal(t, testGasPrice, tx.GasPrice())
+ require.Equal(t, testGasLimit, tx.Gas())
+ require.Equal(t, new(big.Int), tx.Value())
+ require.Nil(t, tx.Data())
+}
+
+// TestSignClearingTxSuccess asserts that we will sign a properly formed
+// clearing transaction when the call to EstimateGas succeeds.
+func TestSignClearingTxEstimateGasSuccess(t *testing.T) {
+ l1Client := mock.NewL1Client(mock.L1ClientConfig{
+ EstimateGas: func(_ context.Context, _ ethereum.CallMsg) (uint64, error) {
+ return testGasLimit, nil
+ },
+ })
+
+ tx, err := drivers.SignClearingTx(
+ context.Background(), testWalletAddr, testNonce, testGasPrice, l1Client,
+ testPrivKey, testChainID,
+ )
+ require.Nil(t, err)
+ require.NotNil(t, tx)
+ require.Equal(t, &testWalletAddr, tx.To())
+ require.Equal(t, testNonce, tx.Nonce())
+ require.Equal(t, testGasPrice, tx.GasPrice())
+ require.Equal(t, testGasLimit, tx.Gas())
+ require.Equal(t, new(big.Int), tx.Value())
+ require.Nil(t, tx.Data())
+
+ // Finally, ensure the sender is correct.
+ sender, err := types.Sender(types.LatestSignerForChainID(testChainID), tx)
+ require.Nil(t, err)
+ require.Equal(t, testWalletAddr, sender)
+}
+
+// TestSignClearingTxEstimateGasFail asserts that signing a clearing transaction
+// will fail if the underlying call to EstimateGas fails.
+func TestSignClearingTxEstimateGasFail(t *testing.T) {
+ errEstimateGas := errors.New("estimate gas")
+
+ l1Client := mock.NewL1Client(mock.L1ClientConfig{
+ EstimateGas: func(_ context.Context, _ ethereum.CallMsg) (uint64, error) {
+ return 0, errEstimateGas
+ },
+ })
+
+ tx, err := drivers.SignClearingTx(
+ context.Background(), testWalletAddr, testNonce, testGasPrice, l1Client,
+ testPrivKey, testChainID,
+ )
+ require.Equal(t, errEstimateGas, err)
+ require.Nil(t, tx)
+}
+
+type clearPendingTxHarness struct {
+ l1Client drivers.L1Client
+ txMgr txmgr.TxManager
+}
+
+func newClearPendingTxHarness(l1ClientConfig mock.L1ClientConfig) *clearPendingTxHarness {
+ if l1ClientConfig.NonceAt == nil {
+ l1ClientConfig.NonceAt = func(_ context.Context, _ common.Address, _ *big.Int) (uint64, error) {
+ return testNonce, nil
+ }
+ }
+ if l1ClientConfig.EstimateGas == nil {
+ l1ClientConfig.EstimateGas = func(_ context.Context, _ ethereum.CallMsg) (uint64, error) {
+ return testGasLimit, nil
+ }
+ }
+
+ l1Client := mock.NewL1Client(l1ClientConfig)
+ txMgr := txmgr.NewSimpleTxManager("test", txmgr.Config{
+ MinGasPrice: utils.GasPriceFromGwei(1),
+ MaxGasPrice: utils.GasPriceFromGwei(100),
+ GasRetryIncrement: utils.GasPriceFromGwei(5),
+ ResubmissionTimeout: time.Second,
+ ReceiptQueryInterval: 50 * time.Millisecond,
+ }, l1Client)
+
+ return &clearPendingTxHarness{
+ l1Client: l1Client,
+ txMgr: txMgr,
+ }
+}
+
+// TestClearPendingTxClearingTxÇonfirms asserts the happy path where our
+// clearing transactions confirms unobstructed.
+func TestClearPendingTxClearingTxConfirms(t *testing.T) {
+ h := newClearPendingTxHarness(mock.L1ClientConfig{
+ SendTransaction: func(_ context.Context, _ *types.Transaction) error {
+ return nil
+ },
+ TransactionReceipt: func(_ context.Context, txHash common.Hash) (*types.Receipt, error) {
+ return &types.Receipt{
+ TxHash: txHash,
+ }, nil
+ },
+ })
+
+ err := drivers.ClearPendingTx(
+ "test", context.Background(), h.txMgr, h.l1Client, testWalletAddr,
+ testPrivKey, testChainID,
+ )
+ require.Nil(t, err)
+}
+
+// TestClearPendingTx∏reviousTxConfirms asserts that if the mempool starts
+// rejecting our transactions because the nonce is too low that ClearPendingTx
+// will abort continuing to publish a clearing transaction.
+func TestClearPendingTxPreviousTxConfirms(t *testing.T) {
+ h := newClearPendingTxHarness(mock.L1ClientConfig{
+ SendTransaction: func(_ context.Context, _ *types.Transaction) error {
+ return core.ErrNonceTooLow
+ },
+ })
+
+ err := drivers.ClearPendingTx(
+ "test", context.Background(), h.txMgr, h.l1Client, testWalletAddr,
+ testPrivKey, testChainID,
+ )
+ require.Equal(t, drivers.ErrClearPendingRetry, err)
+}
+
+// TestClearPendingTxTimeout asserts that ClearPendingTx returns an
+// ErrPublishTimeout if the clearing transaction fails to confirm in a timely
+// manner and no prior transaction confirms.
+func TestClearPendingTxTimeout(t *testing.T) {
+ h := newClearPendingTxHarness(mock.L1ClientConfig{
+ SendTransaction: func(_ context.Context, _ *types.Transaction) error {
+ return nil
+ },
+ TransactionReceipt: func(_ context.Context, txHash common.Hash) (*types.Receipt, error) {
+ return nil, nil
+ },
+ })
+
+ err := drivers.ClearPendingTx(
+ "test", context.Background(), h.txMgr, h.l1Client, testWalletAddr,
+ testPrivKey, testChainID,
+ )
+ require.Equal(t, txmgr.ErrPublishTimeout, err)
+}
diff --git a/go/batch-submitter/drivers/interface.go b/go/batch-submitter/drivers/interface.go
new file mode 100644
index 0000000000000..99c2f1f55b21f
--- /dev/null
+++ b/go/batch-submitter/drivers/interface.go
@@ -0,0 +1,36 @@
+package drivers
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// L1Client is an abstraction over an L1 Ethereum client functionality required
+// by the batch submitter.
+type L1Client interface {
+ // EstimateGas tries to estimate the gas needed to execute a specific
+ // transaction based on the current pending state of the backend blockchain.
+ // There is no guarantee that this is the true gas limit requirement as
+ // other transactions may be added or removed by miners, but it should
+ // provide a basis for setting a reasonable default.
+ EstimateGas(context.Context, ethereum.CallMsg) (uint64, error)
+
+ // NonceAt returns the account nonce of the given account. The block number
+ // can be nil, in which case the nonce is taken from the latest known block.
+ NonceAt(context.Context, common.Address, *big.Int) (uint64, error)
+
+ // SendTransaction injects a signed transaction into the pending pool for
+ // execution.
+ //
+ // If the transaction was a contract creation use the TransactionReceipt
+ // method to get the contract address after the transaction has been mined.
+ SendTransaction(context.Context, *types.Transaction) error
+
+ // TransactionReceipt returns the receipt of a transaction by transaction
+ // hash. Note that the receipt is not available for pending transactions.
+ TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error)
+}
diff --git a/go/batch-submitter/drivers/proposer/driver.go b/go/batch-submitter/drivers/proposer/driver.go
index 44b8d277a3664..cc8ff1790cd99 100644
--- a/go/batch-submitter/drivers/proposer/driver.go
+++ b/go/batch-submitter/drivers/proposer/driver.go
@@ -5,13 +5,17 @@ import (
"crypto/ecdsa"
"fmt"
"math/big"
- "time"
+ "strings"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/scc"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
- l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
+ "github.com/ethereum-optimism/optimism/l2geth/log"
+ "github.com/ethereum-optimism/optimism/l2geth/params"
+ "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
@@ -19,6 +23,9 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
)
+// stateRootSize is the size in bytes of a state root.
+const stateRootSize = 32
+
var bigOne = new(big.Int).SetUint64(1) //nolint:unused
type Config struct {
@@ -34,11 +41,12 @@ type Config struct {
}
type Driver struct {
- cfg Config
- sccContract *scc.StateCommitmentChain
- ctcContract *ctc.CanonicalTransactionChain
- walletAddr common.Address
- metrics *metrics.Metrics
+ cfg Config
+ sccContract *scc.StateCommitmentChain
+ rawSccContract *bind.BoundContract
+ ctcContract *ctc.CanonicalTransactionChain
+ walletAddr common.Address
+ metrics *metrics.Metrics
}
func NewDriver(cfg Config) (*Driver, error) {
@@ -56,14 +64,26 @@ func NewDriver(cfg Config) (*Driver, error) {
return nil, err
}
+ parsed, err := abi.JSON(strings.NewReader(
+ scc.StateCommitmentChainABI,
+ ))
+ if err != nil {
+ return nil, err
+ }
+
+ rawSccContract := bind.NewBoundContract(
+ cfg.SCCAddr, parsed, cfg.L1Client, cfg.L1Client, cfg.L1Client,
+ )
+
walletAddr := crypto.PubkeyToAddress(cfg.PrivKey.PublicKey)
return &Driver{
- cfg: cfg,
- sccContract: sccContract,
- ctcContract: ctcContract,
- walletAddr: walletAddr,
- metrics: metrics.NewMetrics(cfg.Name),
+ cfg: cfg,
+ sccContract: sccContract,
+ rawSccContract: rawSccContract,
+ ctcContract: ctcContract,
+ walletAddr: walletAddr,
+ metrics: metrics.NewMetrics(cfg.Name),
}, nil
}
@@ -82,6 +102,21 @@ func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
+// ClearPendingTx a publishes a transaction at the next available nonce in order
+// to clear any transactions in the mempool left over from a prior running
+// instance of the batch submitter.
+func (d *Driver) ClearPendingTx(
+ ctx context.Context,
+ txMgr txmgr.TxManager,
+ l1Client *ethclient.Client,
+) error {
+
+ return drivers.ClearPendingTx(
+ d.cfg.Name, ctx, txMgr, l1Client, d.walletAddr, d.cfg.PrivKey,
+ d.cfg.ChainID,
+ )
+}
+
// GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed.
@@ -89,7 +124,6 @@ func (d *Driver) GetBatchBlockRange(
ctx context.Context) (*big.Int, *big.Int, error) {
blockOffset := new(big.Int).SetUint64(d.cfg.BlockOffset)
- maxBatchSize := new(big.Int).SetUint64(1)
start, err := d.sccContract.GetTotalElements(&bind.CallOpts{
Pending: false,
@@ -100,20 +134,14 @@ func (d *Driver) GetBatchBlockRange(
}
start.Add(start, blockOffset)
- totalElements, err := d.ctcContract.GetTotalElements(&bind.CallOpts{
+ end, err := d.ctcContract.GetTotalElements(&bind.CallOpts{
Pending: false,
Context: ctx,
})
if err != nil {
return nil, nil, err
}
- totalElements.Add(totalElements, blockOffset)
-
- // Take min(start + blockOffset + maxBatchSize, totalElements).
- end := new(big.Int).Add(start, maxBatchSize)
- if totalElements.Cmp(end) < 0 {
- end.Set(totalElements)
- }
+ end.Add(end, blockOffset)
if start.Cmp(end) > 0 {
return nil, nil, fmt.Errorf("invalid range, "+
@@ -123,36 +151,43 @@ func (d *Driver) GetBatchBlockRange(
return start, end, nil
}
-// SubmitBatchTx transforms the L2 blocks between start and end into a batch
-// transaction using the given nonce and gasPrice. The final transaction is
-// published and returned to the call.
-func (d *Driver) SubmitBatchTx(
+// CraftBatchTx transforms the L2 blocks between start and end into a batch
+// transaction using the given nonce. A dummy gas price is used in the resulting
+// transaction to use for size estimation.
+//
+// NOTE: This method SHOULD NOT publish the resulting transaction.
+func (d *Driver) CraftBatchTx(
ctx context.Context,
- start, end, nonce, gasPrice *big.Int) (*types.Transaction, error) {
+ start, end, nonce *big.Int,
+) (*types.Transaction, error) {
- batchTxBuildStart := time.Now()
+ name := d.cfg.Name
- var blocks []*l2types.Block
+ log.Info(name+" crafting batch tx", "start", start, "end", end,
+ "nonce", nonce)
+
+ var (
+ stateRoots [][stateRootSize]byte
+ totalStateRootSize uint64
+ )
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
+ // Consume state roots until reach our maximum tx size.
+ if totalStateRootSize+stateRootSize > d.cfg.MaxTxSize {
+ break
+ }
+
block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
if err != nil {
return nil, err
}
- blocks = append(blocks, block)
-
- // TODO(conner): remove when moving to multiple blocks
- break //nolint
- }
-
- var stateRoots = make([][32]byte, 0, len(blocks))
- for _, block := range blocks {
+ totalStateRootSize += stateRootSize
stateRoots = append(stateRoots, block.Root())
}
- batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
- d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
- d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
+ d.metrics.NumElementsPerBatch.Observe(float64(len(stateRoots)))
+
+ log.Info(name+" batch constructed", "num_state_roots", len(stateRoots))
opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID,
@@ -160,12 +195,35 @@ func (d *Driver) SubmitBatchTx(
if err != nil {
return nil, err
}
- opts.Nonce = nonce
opts.Context = ctx
- opts.GasPrice = gasPrice
+ opts.Nonce = nonce
+ opts.GasPrice = big.NewInt(params.GWei) // dummy
+ opts.NoSend = true
blockOffset := new(big.Int).SetUint64(d.cfg.BlockOffset)
offsetStartsAtIndex := new(big.Int).Sub(start, blockOffset)
return d.sccContract.AppendStateBatch(opts, stateRoots, offsetStartsAtIndex)
}
+
+// SubmitBatchTx using the passed transaction as a template, signs and publishes
+// an otherwise identical transaction after setting the provided gas price. The
+// final transaction is returned to the caller.
+func (d *Driver) SubmitBatchTx(
+ ctx context.Context,
+ tx *types.Transaction,
+ gasPrice *big.Int,
+) (*types.Transaction, error) {
+
+ opts, err := bind.NewKeyedTransactorWithChainID(
+ d.cfg.PrivKey, d.cfg.ChainID,
+ )
+ if err != nil {
+ return nil, err
+ }
+ opts.Context = ctx
+ opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
+ opts.GasPrice = gasPrice
+
+ return d.rawSccContract.RawTransact(opts, tx.Data())
+}
diff --git a/go/batch-submitter/drivers/sequencer/batch.go b/go/batch-submitter/drivers/sequencer/batch.go
index 85e5bede4e040..9653cbb0b45a6 100644
--- a/go/batch-submitter/drivers/sequencer/batch.go
+++ b/go/batch-submitter/drivers/sequencer/batch.go
@@ -27,7 +27,7 @@ type BatchElement struct {
// Tx is the optional transaction that was applied in this batch.
//
// NOTE: This field will only be populated for sequencer txs.
- Tx *l2types.Transaction
+ Tx *CachedTx
}
// IsSequencerTx returns true if this batch contains a tx that needs to be
@@ -54,14 +54,15 @@ func BatchElementFromBlock(block *l2types.Block) BatchElement {
isSequencerTx := tx.QueueOrigin() == l2types.QueueOriginSequencer
// Only include sequencer txs in the returned BatchElement.
- if !isSequencerTx {
- tx = nil
+ var cachedTx *CachedTx
+ if isSequencerTx {
+ cachedTx = NewCachedTx(tx)
}
return BatchElement{
Timestamp: block.Time(),
BlockNumber: l1BlockNumber,
- Tx: tx,
+ Tx: cachedTx,
}
}
@@ -82,7 +83,7 @@ func GenSequencerBatchParams(
var (
contexts []BatchContext
groupedBlocks []groupedBlock
- txs []*l2types.Transaction
+ txs []*CachedTx
lastBlockIsSequencerTx bool
lastTimestamp uint64
lastBlockNumber uint64
diff --git a/go/batch-submitter/drivers/sequencer/batch_test.go b/go/batch-submitter/drivers/sequencer/batch_test.go
index b34edc8c707a3..5a42c3c57f748 100644
--- a/go/batch-submitter/drivers/sequencer/batch_test.go
+++ b/go/batch-submitter/drivers/sequencer/batch_test.go
@@ -31,7 +31,7 @@ func TestBatchElementFromBlock(t *testing.T) {
require.Equal(t, element.Timestamp, expTime)
require.Equal(t, element.BlockNumber, expBlockNumber)
require.True(t, element.IsSequencerTx())
- require.Equal(t, element.Tx, expTx)
+ require.Equal(t, element.Tx.Tx(), expTx)
queueMeta := l2types.NewTransactionMeta(
new(big.Int).SetUint64(expBlockNumber), 0, nil,
diff --git a/go/batch-submitter/drivers/sequencer/cached_tx.go b/go/batch-submitter/drivers/sequencer/cached_tx.go
new file mode 100644
index 0000000000000..35ff0604f4a74
--- /dev/null
+++ b/go/batch-submitter/drivers/sequencer/cached_tx.go
@@ -0,0 +1,37 @@
+package sequencer
+
+import (
+ "bytes"
+ "fmt"
+
+ l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
+)
+
+type CachedTx struct {
+ tx *l2types.Transaction
+ rawTx []byte
+}
+
+func NewCachedTx(tx *l2types.Transaction) *CachedTx {
+ var txBuf bytes.Buffer
+ if err := tx.EncodeRLP(&txBuf); err != nil {
+ panic(fmt.Sprintf("Unable to encode tx: %v", err))
+ }
+
+ return &CachedTx{
+ tx: tx,
+ rawTx: txBuf.Bytes(),
+ }
+}
+
+func (t *CachedTx) Tx() *l2types.Transaction {
+ return t.tx
+}
+
+func (t *CachedTx) Size() int {
+ return len(t.rawTx)
+}
+
+func (t *CachedTx) RawTx() []byte {
+ return t.rawTx
+}
diff --git a/go/batch-submitter/drivers/sequencer/driver.go b/go/batch-submitter/drivers/sequencer/driver.go
index ba9d16a49972c..1a6f203621621 100644
--- a/go/batch-submitter/drivers/sequencer/driver.go
+++ b/go/batch-submitter/drivers/sequencer/driver.go
@@ -3,16 +3,16 @@ package sequencer
import (
"context"
"crypto/ecdsa"
- "encoding/hex"
"fmt"
"math/big"
"strings"
- "time"
"github.com/ethereum-optimism/optimism/go/batch-submitter/bindings/ctc"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/drivers"
"github.com/ethereum-optimism/optimism/go/batch-submitter/metrics"
- l2types "github.com/ethereum-optimism/optimism/l2geth/core/types"
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/txmgr"
l2ethclient "github.com/ethereum-optimism/optimism/l2geth/ethclient"
+ "github.com/ethereum-optimism/optimism/l2geth/params"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
@@ -100,6 +100,21 @@ func (d *Driver) Metrics() *metrics.Metrics {
return d.metrics
}
+// ClearPendingTx a publishes a transaction at the next available nonce in order
+// to clear any transactions in the mempool left over from a prior running
+// instance of the batch submitter.
+func (d *Driver) ClearPendingTx(
+ ctx context.Context,
+ txMgr txmgr.TxManager,
+ l1Client *ethclient.Client,
+) error {
+
+ return drivers.ClearPendingTx(
+ d.cfg.Name, ctx, txMgr, l1Client, d.walletAddr, d.cfg.PrivKey,
+ d.cfg.ChainID,
+ )
+}
+
// GetBatchBlockRange returns the start and end L2 block heights that need to be
// processed. Note that the end value is *exclusive*, therefore if the returned
// values are identical nothing needs to be processed.
@@ -133,66 +148,106 @@ func (d *Driver) GetBatchBlockRange(
return start, end, nil
}
-// SubmitBatchTx transforms the L2 blocks between start and end into a batch
-// transaction using the given nonce and gasPrice. The final transaction is
-// published and returned to the call.
-func (d *Driver) SubmitBatchTx(
+// CraftBatchTx transforms the L2 blocks between start and end into a batch
+// transaction using the given nonce. A dummy gas price is used in the resulting
+// transaction to use for size estimation.
+//
+// NOTE: This method SHOULD NOT publish the resulting transaction.
+func (d *Driver) CraftBatchTx(
ctx context.Context,
- start, end, nonce, gasPrice *big.Int) (*types.Transaction, error) {
+ start, end, nonce *big.Int,
+) (*types.Transaction, error) {
name := d.cfg.Name
- log.Info(name+" submitting batch tx", "start", start, "end", end,
- "gasPrice", gasPrice)
-
- batchTxBuildStart := time.Now()
+ log.Info(name+" crafting batch tx", "start", start, "end", end,
+ "nonce", nonce)
- var blocks []*l2types.Block
+ var (
+ batchElements []BatchElement
+ totalTxSize uint64
+ )
for i := new(big.Int).Set(start); i.Cmp(end) < 0; i.Add(i, bigOne) {
block, err := d.cfg.L2Client.BlockByNumber(ctx, i)
if err != nil {
return nil, err
}
- blocks = append(blocks, block)
-
- // TODO(conner): remove when moving to multiple blocks
- break //nolint
- }
+ // For each sequencer transaction, update our running total with the
+ // size of the transaction.
+ batchElement := BatchElementFromBlock(block)
+ if batchElement.IsSequencerTx() {
+ // Abort once the total size estimate is greater than the maximum
+ // configured size. This is a conservative estimate, as the total
+ // calldata size will be greater when batch contexts are included.
+ // Below this set will be further whittled until the raw call data
+ // size also adheres to this constraint.
+ txLen := batchElement.Tx.Size()
+ if totalTxSize+uint64(TxLenSize+txLen) > d.cfg.MaxTxSize {
+ break
+ }
+ totalTxSize += uint64(TxLenSize + txLen)
+ }
- var batchElements = make([]BatchElement, 0, len(blocks))
- for _, block := range blocks {
- batchElements = append(batchElements, BatchElementFromBlock(block))
+ batchElements = append(batchElements, batchElement)
}
shouldStartAt := start.Uint64()
- batchParams, err := GenSequencerBatchParams(
- shouldStartAt, d.cfg.BlockOffset, batchElements,
- )
- if err != nil {
- return nil, err
- }
+ var pruneCount int
+ for {
+ batchParams, err := GenSequencerBatchParams(
+ shouldStartAt, d.cfg.BlockOffset, batchElements,
+ )
+ if err != nil {
+ return nil, err
+ }
- log.Info(name+" batch params", "params", fmt.Sprintf("%#v", batchParams))
+ batchArguments, err := batchParams.Serialize()
+ if err != nil {
+ return nil, err
+ }
- batchArguments, err := batchParams.Serialize()
- if err != nil {
- return nil, err
- }
+ appendSequencerBatchID := d.ctcABI.Methods[appendSequencerBatchMethodName].ID
+ batchCallData := append(appendSequencerBatchID, batchArguments...)
+
+ // Continue pruning until calldata size is less than configured max.
+ if uint64(len(batchCallData)) > d.cfg.MaxTxSize {
+ oldLen := len(batchElements)
+ newBatchElementsLen := (oldLen * 9) / 10
+ batchElements = batchElements[:newBatchElementsLen]
+ log.Info(name+" pruned batch", "old_num_txs", oldLen, "new_num_txs", newBatchElementsLen)
+ pruneCount++
+ continue
+ }
- appendSequencerBatchID := d.ctcABI.Methods[appendSequencerBatchMethodName].ID
- batchCallData := append(appendSequencerBatchID, batchArguments...)
+ d.metrics.NumElementsPerBatch.Observe(float64(len(batchElements)))
+ d.metrics.BatchPruneCount.Set(float64(pruneCount))
- if uint64(len(batchCallData)) > d.cfg.MaxTxSize {
- panic("call data too large")
- }
+ log.Info(name+" batch constructed", "num_txs", len(batchElements), "length", len(batchCallData))
- // Record the batch_tx_build_time.
- batchTxBuildTime := float64(time.Since(batchTxBuildStart) / time.Millisecond)
- d.metrics.BatchTxBuildTime.Set(batchTxBuildTime)
- d.metrics.NumTxPerBatch.Observe(float64(len(blocks)))
+ opts, err := bind.NewKeyedTransactorWithChainID(
+ d.cfg.PrivKey, d.cfg.ChainID,
+ )
+ if err != nil {
+ return nil, err
+ }
+ opts.Context = ctx
+ opts.Nonce = nonce
+ opts.GasPrice = big.NewInt(params.GWei) // dummy
+ opts.NoSend = true
+
+ return d.rawCtcContract.RawTransact(opts, batchCallData)
+ }
+}
- log.Info(name+" batch call data", "data", hex.EncodeToString(batchCallData))
+// SubmitBatchTx using the passed transaction as a template, signs and publishes
+// an otherwise identical transaction after setting the provided gas price. The
+// final transaction is returned to the caller.
+func (d *Driver) SubmitBatchTx(
+ ctx context.Context,
+ tx *types.Transaction,
+ gasPrice *big.Int,
+) (*types.Transaction, error) {
opts, err := bind.NewKeyedTransactorWithChainID(
d.cfg.PrivKey, d.cfg.ChainID,
@@ -200,9 +255,9 @@ func (d *Driver) SubmitBatchTx(
if err != nil {
return nil, err
}
- opts.Nonce = nonce
opts.Context = ctx
+ opts.Nonce = new(big.Int).SetUint64(tx.Nonce())
opts.GasPrice = gasPrice
- return d.rawCtcContract.RawTransact(opts, batchCallData)
+ return d.rawCtcContract.RawTransact(opts, tx.Data())
}
diff --git a/go/batch-submitter/drivers/sequencer/encoding.go b/go/batch-submitter/drivers/sequencer/encoding.go
index 13aa339eaedbf..5266146774f08 100644
--- a/go/batch-submitter/drivers/sequencer/encoding.go
+++ b/go/batch-submitter/drivers/sequencer/encoding.go
@@ -11,6 +11,12 @@ import (
l2rlp "github.com/ethereum-optimism/optimism/l2geth/rlp"
)
+const (
+ // TxLenSize is the number of bytes used to represent the size of a
+ // serialized sequencer transaction.
+ TxLenSize = 3
+)
+
var byteOrder = binary.BigEndian
// BatchContext denotes a range of transactions that belong the same batch. It
@@ -88,7 +94,7 @@ type AppendSequencerBatchParams struct {
// Txs contains all sequencer txs that will be recorded in the L1 CTC
// contract.
- Txs []*l2types.Transaction
+ Txs []*CachedTx
}
// Write encodes the AppendSequencerBatchParams using the following format:
@@ -110,16 +116,9 @@ func (p *AppendSequencerBatchParams) Write(w *bytes.Buffer) error {
}
// Write each length-prefixed tx.
- var txBuf bytes.Buffer
for _, tx := range p.Txs {
- txBuf.Reset()
-
- if err := tx.EncodeRLP(&txBuf); err != nil {
- return err
- }
-
- writeUint64(w, uint64(txBuf.Len()), 3)
- _, _ = w.Write(txBuf.Bytes()) // can't fail for bytes.Buffer
+ writeUint64(w, uint64(tx.Size()), TxLenSize)
+ _, _ = w.Write(tx.RawTx()) // can't fail for bytes.Buffer
}
return nil
@@ -173,7 +172,7 @@ func (p *AppendSequencerBatchParams) Read(r io.Reader) error {
// from the encoding, loop until the stream is consumed.
for {
var txLen uint64
- err := readUint64(r, &txLen, 3)
+ err := readUint64(r, &txLen, TxLenSize)
// Getting an EOF when reading the txLen expected for a cleanly
// encoded object. Silece the error and return success.
if err == io.EOF {
@@ -187,7 +186,7 @@ func (p *AppendSequencerBatchParams) Read(r io.Reader) error {
return err
}
- p.Txs = append(p.Txs, tx)
+ p.Txs = append(p.Txs, NewCachedTx(tx))
}
}
diff --git a/go/batch-submitter/drivers/sequencer/encoding_test.go b/go/batch-submitter/drivers/sequencer/encoding_test.go
index d90c9deea8179..9fcd91a49213b 100644
--- a/go/batch-submitter/drivers/sequencer/encoding_test.go
+++ b/go/batch-submitter/drivers/sequencer/encoding_test.go
@@ -297,9 +297,9 @@ func testAppendSequencerBatchParamsEncodeDecode(
// compareTxs compares a list of two transactions, testing each pair by tx hash.
// This is used rather than require.Equal, since there `time` metadata on the
// decoded tx and the expected tx will differ, and can't be modified/ignored.
-func compareTxs(t *testing.T, a, b []*l2types.Transaction) {
+func compareTxs(t *testing.T, a []*l2types.Transaction, b []*sequencer.CachedTx) {
require.Equal(t, len(a), len(b))
for i, txA := range a {
- require.Equal(t, txA.Hash(), b[i].Hash())
+ require.Equal(t, txA.Hash(), b[i].Tx().Hash())
}
}
diff --git a/go/batch-submitter/metrics/metrics.go b/go/batch-submitter/metrics/metrics.go
index 13fed545a32b1..e7a282dd9c10e 100644
--- a/go/batch-submitter/metrics/metrics.go
+++ b/go/batch-submitter/metrics/metrics.go
@@ -12,12 +12,15 @@ type Metrics struct {
// BatchSizeInBytes tracks the size of batch submission transactions.
BatchSizeInBytes prometheus.Histogram
- // NumTxPerBatch tracks the number of L2 transactions in each batch
+ // NumElementsPerBatch tracks the number of L2 transactions in each batch
// submission.
- NumTxPerBatch prometheus.Histogram
+ NumElementsPerBatch prometheus.Histogram
+
+ // SubmissionTimestamp tracks the time at which each batch was confirmed.
+ SubmissionTimestamp prometheus.Gauge
// SubmissionGasUsed tracks the amount of gas used to submit each batch.
- SubmissionGasUsed prometheus.Histogram
+ SubmissionGasUsed prometheus.Gauge
// BatchsSubmitted tracks the total number of successful batch submissions.
BatchesSubmitted prometheus.Counter
@@ -32,6 +35,12 @@ type Metrics struct {
// BatchConfirmationTime tracks the duration it takes to confirm a batch
// transaction.
BatchConfirmationTime prometheus.Gauge
+
+ // BatchPruneCount tracks the number of times a batch of sequencer
+ // transactions is pruned in order to meet the desired size requirements.
+ //
+ // NOTE: This is currently only active in the sequencer driver.
+ BatchPruneCount prometheus.Gauge
}
func NewMetrics(subsystem string) *Metrics {
@@ -41,40 +50,65 @@ func NewMetrics(subsystem string) *Metrics {
Help: "ETH balance of the batch submitter",
Subsystem: subsystem,
}),
- BatchSizeInBytes: promauto.NewHistogram(prometheus.HistogramOpts{
- Name: "batch_submitter_batch_size_in_bytes",
- Help: "Size of batches in bytes",
+ BatchSizeInBytes: promauto.NewSummary(prometheus.SummaryOpts{
+ Name: "batch_size_bytes",
+ Help: "Size of batches in bytes",
+ Subsystem: subsystem,
+ Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
+ }),
+ NumElementsPerBatch: promauto.NewHistogram(prometheus.HistogramOpts{
+ Name: "num_elements_per_batch",
+ Help: "Number of transaction in each batch",
+ Buckets: []float64{
+ 250,
+ 500,
+ 750,
+ 1000,
+ 1250,
+ 1500,
+ 1750,
+ 2000,
+ 2250,
+ 2500,
+ 2750,
+ 3000,
+ },
Subsystem: subsystem,
}),
- NumTxPerBatch: promauto.NewHistogram(prometheus.HistogramOpts{
- Name: "batch_submitter_num_txs_per_batch",
- Help: "Number of transaction in each batch",
+ SubmissionTimestamp: promauto.NewGauge(prometheus.GaugeOpts{
+ Name: "submission_timestamp",
+ Help: "Timestamp of last batch submitter submission",
Subsystem: subsystem,
}),
- SubmissionGasUsed: promauto.NewHistogram(prometheus.HistogramOpts{
- Name: "batch_submitter_submission_gas_used",
+ SubmissionGasUsed: promauto.NewGauge(prometheus.GaugeOpts{
+ Name: "submission_gas_used",
Help: "Gas used to submit each batch",
Subsystem: subsystem,
}),
BatchesSubmitted: promauto.NewCounter(prometheus.CounterOpts{
- Name: "batch_submitter_batches_submitted",
+ Name: "batches_submitted",
Help: "Count of batches submitted",
Subsystem: subsystem,
}),
FailedSubmissions: promauto.NewCounter(prometheus.CounterOpts{
- Name: "batch_submitter_failed_submissions",
+ Name: "failed_submissions",
Help: "Count of failed batch submissions",
Subsystem: subsystem,
}),
BatchTxBuildTime: promauto.NewGauge(prometheus.GaugeOpts{
- Name: "batch_submitter_batch_tx_build_time",
+ Name: "batch_tx_build_time_ms",
Help: "Time to construct batch transactions",
Subsystem: subsystem,
}),
BatchConfirmationTime: promauto.NewGauge(prometheus.GaugeOpts{
- Name: "batch_submitter_batch_confirmation_time",
+ Name: "batch_submitter_batch_confirmation_time_ms",
Help: "Time to confirm batch transactions",
Subsystem: subsystem,
}),
+ BatchPruneCount: promauto.NewGauge(prometheus.GaugeOpts{
+ Name: "batch_submitter_batch_prune_count",
+ Help: "Number of times a batch is pruned",
+ Subsystem: subsystem,
+ }),
}
}
diff --git a/go/batch-submitter/mock/l1client.go b/go/batch-submitter/mock/l1client.go
new file mode 100644
index 0000000000000..c0f46102065b9
--- /dev/null
+++ b/go/batch-submitter/mock/l1client.go
@@ -0,0 +1,123 @@
+package mock
+
+import (
+ "context"
+ "math/big"
+ "sync"
+
+ "github.com/ethereum/go-ethereum"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+// L1ClientConfig houses the internal methods that are executed by the mock
+// L1Client. Any members left as nil will panic on execution.
+type L1ClientConfig struct {
+ // EstimateGas tries to estimate the gas needed to execute a specific
+ // transaction based on the current pending state of the backend blockchain.
+ // There is no guarantee that this is the true gas limit requirement as
+ // other transactions may be added or removed by miners, but it should
+ // provide a basis for setting a reasonable default.
+ EstimateGas func(context.Context, ethereum.CallMsg) (uint64, error)
+
+ // NonceAt returns the account nonce of the given account. The block number
+ // can be nil, in which case the nonce is taken from the latest known block.
+ NonceAt func(context.Context, common.Address, *big.Int) (uint64, error)
+
+ // SendTransaction injects a signed transaction into the pending pool for
+ // execution.
+ //
+ // If the transaction was a contract creation use the TransactionReceipt
+ // method to get the contract address after the transaction has been mined.
+ SendTransaction func(context.Context, *types.Transaction) error
+
+ // TransactionReceipt returns the receipt of a transaction by transaction
+ // hash. Note that the receipt is not available for pending transactions.
+ TransactionReceipt func(context.Context, common.Hash) (*types.Receipt, error)
+}
+
+// L1Client represents a mock L1Client.
+type L1Client struct {
+ cfg L1ClientConfig
+ mu sync.RWMutex
+}
+
+// NewL1Client returns a new L1Client using the mocked methods in the
+// L1ClientConfig.
+func NewL1Client(cfg L1ClientConfig) *L1Client {
+ return &L1Client{
+ cfg: cfg,
+ }
+}
+
+// EstimateGas executes the mock EstimateGas method.
+func (c *L1Client) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.cfg.EstimateGas(ctx, call)
+}
+
+// NonceAt executes the mock NonceAt method.
+func (c *L1Client) NonceAt(ctx context.Context, addr common.Address, blockNumber *big.Int) (uint64, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.cfg.NonceAt(ctx, addr, blockNumber)
+}
+
+// SendTransaction executes the mock SendTransaction method.
+func (c *L1Client) SendTransaction(ctx context.Context, tx *types.Transaction) error {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.cfg.SendTransaction(ctx, tx)
+}
+
+// TransactionReceipt executes the mock TransactionReceipt method.
+func (c *L1Client) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
+ c.mu.RLock()
+ defer c.mu.RUnlock()
+
+ return c.cfg.TransactionReceipt(ctx, txHash)
+}
+
+// SetEstimateGasFunc overrwrites the mock EstimateGas method.
+func (c *L1Client) SetEstimateGasFunc(
+ f func(context.Context, ethereum.CallMsg) (uint64, error)) {
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cfg.EstimateGas = f
+}
+
+// SetNonceAtFunc overrwrites the mock NonceAt method.
+func (c *L1Client) SetNonceAtFunc(
+ f func(context.Context, common.Address, *big.Int) (uint64, error)) {
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cfg.NonceAt = f
+}
+
+// SetSendTransactionFunc overrwrites the mock SendTransaction method.
+func (c *L1Client) SetSendTransactionFunc(
+ f func(context.Context, *types.Transaction) error) {
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cfg.SendTransaction = f
+}
+
+// SetTransactionReceiptFunc overwrites the mock TransactionReceipt method.
+func (c *L1Client) SetTransactionReceiptFunc(
+ f func(context.Context, common.Hash) (*types.Receipt, error)) {
+
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ c.cfg.TransactionReceipt = f
+}
diff --git a/go/batch-submitter/service.go b/go/batch-submitter/service.go
index b018b1e636586..812020f0eb1e5 100644
--- a/go/batch-submitter/service.go
+++ b/go/batch-submitter/service.go
@@ -1,6 +1,7 @@
package batchsubmitter
import (
+ "bytes"
"context"
"math/big"
"sync"
@@ -15,8 +16,8 @@ import (
)
var (
- // weiToGwei is the conversion rate from wei to gwei.
- weiToGwei = new(big.Float).SetFloat64(1e-18)
+ // weiToEth is the conversion rate from wei to ether.
+ weiToEth = new(big.Float).SetFloat64(1e-18)
)
// Driver is an interface for creating and submitting batch transactions for a
@@ -32,18 +33,34 @@ type Driver interface {
// Metrics returns the subservice telemetry object.
Metrics() *metrics.Metrics
+ // ClearPendingTx a publishes a transaction at the next available nonce in
+ // order to clear any transactions in the mempool left over from a prior
+ // running instance of the batch submitter.
+ ClearPendingTx(context.Context, txmgr.TxManager, *ethclient.Client) error
+
// GetBatchBlockRange returns the start and end L2 block heights that
// need to be processed. Note that the end value is *exclusive*,
// therefore if the returned values are identical nothing needs to be
// processed.
GetBatchBlockRange(ctx context.Context) (*big.Int, *big.Int, error)
- // SubmitBatchTx transforms the L2 blocks between start and end into a
- // batch transaction using the given nonce and gasPrice. The final
- // transaction is published and returned to the call.
+ // CraftBatchTx transforms the L2 blocks between start and end into a batch
+ // transaction using the given nonce. A dummy gas price is used in the
+ // resulting transaction to use for size estimation.
+ //
+ // NOTE: This method SHOULD NOT publish the resulting transaction.
+ CraftBatchTx(
+ ctx context.Context,
+ start, end, nonce *big.Int,
+ ) (*types.Transaction, error)
+
+ // SubmitBatchTx using the passed transaction as a template, signs and
+ // publishes an otherwise identical transaction after setting the provided
+ // gas price. The final transaction is returned to the caller.
SubmitBatchTx(
ctx context.Context,
- start, end, nonce, gasPrice *big.Int,
+ tx *types.Transaction,
+ gasPrice *big.Int,
) (*types.Transaction, error)
}
@@ -51,6 +68,7 @@ type ServiceConfig struct {
Context context.Context
Driver Driver
PollInterval time.Duration
+ ClearPendingTx bool
L1Client *ethclient.Client
TxManagerConfig txmgr.Config
}
@@ -99,6 +117,19 @@ func (s *Service) eventLoop() {
name := s.cfg.Driver.Name()
+ if s.cfg.ClearPendingTx {
+ const maxClearRetries = 3
+ for i := 0; i < maxClearRetries; i++ {
+ err := s.cfg.Driver.ClearPendingTx(s.ctx, s.txMgr, s.cfg.L1Client)
+ if err == nil {
+ break
+ } else if i < maxClearRetries-1 {
+ continue
+ }
+ log.Crit("Unable to confirm a clearing transaction", "err", err)
+ }
+ }
+
for {
select {
case <-time.After(s.cfg.PollInterval):
@@ -112,7 +143,7 @@ func (s *Service) eventLoop() {
log.Error(name+" unable to get current balance", "err", err)
continue
}
- s.metrics.ETHBalance.Set(weiToGwei64(balance))
+ s.metrics.ETHBalance.Set(weiToEth64(balance))
// Determine the range of L2 blocks that the batch submitter has not
// processed, and needs to take action on.
@@ -141,6 +172,26 @@ func (s *Service) eventLoop() {
}
nonce := new(big.Int).SetUint64(nonce64)
+ batchTxBuildStart := time.Now()
+ tx, err := s.cfg.Driver.CraftBatchTx(
+ s.ctx, start, end, nonce,
+ )
+ if err != nil {
+ log.Error(name+" unable to craft batch tx",
+ "err", err)
+ continue
+ }
+ batchTxBuildTime := time.Since(batchTxBuildStart) / time.Millisecond
+ s.metrics.BatchTxBuildTime.Set(float64(batchTxBuildTime))
+
+ // Record the size of the batch transaction.
+ var txBuf bytes.Buffer
+ if err := tx.EncodeRLP(&txBuf); err != nil {
+ log.Error(name+" unable to encode batch tx", "err", err)
+ continue
+ }
+ s.metrics.BatchSizeInBytes.Observe(float64(len(txBuf.Bytes())))
+
// Construct the transaction submission clousure that will attempt
// to send the next transaction at the given nonce and gas price.
sendTx := func(
@@ -151,14 +202,19 @@ func (s *Service) eventLoop() {
"end", end, "nonce", nonce,
"gasPrice", gasPrice)
- tx, err := s.cfg.Driver.SubmitBatchTx(
- ctx, start, end, nonce, gasPrice,
- )
+ tx, err := s.cfg.Driver.SubmitBatchTx(ctx, tx, gasPrice)
if err != nil {
return nil, err
}
- s.metrics.BatchSizeInBytes.Observe(float64(tx.Size()))
+ log.Info(
+ name+" submitted batch tx",
+ "start", start,
+ "end", end,
+ "nonce", nonce,
+ "tx_hash", tx.Hash(),
+ "gasPrice", gasPrice,
+ )
return tx, nil
}
@@ -181,7 +237,8 @@ func (s *Service) eventLoop() {
time.Millisecond
s.metrics.BatchConfirmationTime.Set(float64(batchConfirmationTime))
s.metrics.BatchesSubmitted.Inc()
- s.metrics.SubmissionGasUsed.Observe(float64(receipt.GasUsed))
+ s.metrics.SubmissionGasUsed.Set(float64(receipt.GasUsed))
+ s.metrics.SubmissionTimestamp.Set(float64(time.Now().UnixNano() / 1e6))
case err := <-s.ctx.Done():
log.Error(name+" service shutting down", "err", err)
@@ -190,9 +247,9 @@ func (s *Service) eventLoop() {
}
}
-func weiToGwei64(wei *big.Int) float64 {
- gwei := new(big.Float).SetInt(wei)
- gwei.Mul(gwei, weiToGwei)
- gwei64, _ := gwei.Float64()
- return gwei64
+func weiToEth64(wei *big.Int) float64 {
+ eth := new(big.Float).SetInt(wei)
+ eth.Mul(eth, weiToEth)
+ eth64, _ := eth.Float64()
+ return eth64
}
diff --git a/go/batch-submitter/utils/gas_price.go b/go/batch-submitter/utils/gas_price.go
new file mode 100644
index 0000000000000..7a97c572f40e4
--- /dev/null
+++ b/go/batch-submitter/utils/gas_price.go
@@ -0,0 +1,12 @@
+package utils
+
+import (
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/params"
+)
+
+// GasPriceFromGwei converts an uint64 gas price in gwei to a big.Int in wei.
+func GasPriceFromGwei(gasPriceInGwei uint64) *big.Int {
+ return new(big.Int).SetUint64(gasPriceInGwei * params.GWei)
+}
diff --git a/go/batch-submitter/utils/gas_price_test.go b/go/batch-submitter/utils/gas_price_test.go
new file mode 100644
index 0000000000000..284fdc511ca47
--- /dev/null
+++ b/go/batch-submitter/utils/gas_price_test.go
@@ -0,0 +1,18 @@
+package utils_test
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/ethereum-optimism/optimism/go/batch-submitter/utils"
+ "github.com/ethereum/go-ethereum/params"
+ "github.com/stretchr/testify/require"
+)
+
+// TestGasPriceFromGwei asserts that the integer value is scaled properly by
+// 10^9.
+func TestGasPriceFromGwei(t *testing.T) {
+ require.Equal(t, utils.GasPriceFromGwei(0), new(big.Int))
+ require.Equal(t, utils.GasPriceFromGwei(1), big.NewInt(params.GWei))
+ require.Equal(t, utils.GasPriceFromGwei(100), big.NewInt(100*params.GWei))
+}
diff --git a/go/gas-oracle/README.md b/go/gas-oracle/README.md
index 6720f5beced4a..3a66d34ae88da 100644
--- a/go/gas-oracle/README.md
+++ b/go/gas-oracle/README.md
@@ -40,7 +40,7 @@ options.
```
NAME:
- gas-oracle - Remotely Control the Optimistic Ethereum Gas Price
+ gas-oracle - Remotely Control the Optimism Gas Price
USAGE:
gas-oracle [global options] command [command options] [arguments...]
@@ -49,7 +49,7 @@ VERSION:
0.0.0-1.10.4-stable
DESCRIPTION:
- Configure with a private key and an Optimistic Ethereum HTTP endpoint to send transactions that update the L2 gas price.
+ Configure with a private key and an Optimism HTTP endpoint to send transactions that update the L2 gas price.
COMMANDS:
help, h Shows a list of commands or help for one command
diff --git a/go/gas-oracle/main.go b/go/gas-oracle/main.go
index fe81949b09106..dd5e16a9151f8 100644
--- a/go/gas-oracle/main.go
+++ b/go/gas-oracle/main.go
@@ -26,8 +26,8 @@ func main() {
app.Version = GitVersion + "-" + params.VersionWithCommit(GitCommit, GitDate)
app.Name = "gas-oracle"
- app.Usage = "Remotely Control the Optimistic Ethereum Gas Price"
- app.Description = "Configure with a private key and an Optimistic Ethereum HTTP endpoint " +
+ app.Usage = "Remotely Control the Optimism Gas Price"
+ app.Description = "Configure with a private key and an Optimism HTTP endpoint " +
"to send transactions that update the L2 gas price."
// Configure the logging
diff --git a/go/op-exporter/Makefile b/go/op-exporter/Makefile
index 101880c07ad3f..270f078dd51be 100644
--- a/go/op-exporter/Makefile
+++ b/go/op-exporter/Makefile
@@ -1,14 +1,18 @@
SHELL := /bin/bash
-VERSION := `git describe --abbrev=0`
+ifndef VERSION
+VERSION := `jq .version package.json `
+endif
+ifndef GITCOMMIT
GITCOMMIT := `git rev-parse HEAD`
+endif
+ifndef BUILDDATE
BUILDDATE := `date +%Y-%m-%d`
-BUILDUSER := `whoami`
+endif
LDFLAGSSTRING :=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.Version=$(VERSION)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.GitCommit=$(GITCOMMIT)
LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.BuildDate=$(BUILDDATE)
-LDFLAGSSTRING +=-X github.com/ethereum-optimism/optimism/go/op_exporter/version.BuildUser=$(BUILDUSER)
LDFLAGS :=-ldflags "$(LDFLAGSSTRING)"
diff --git a/go/op-exporter/README.md b/go/op-exporter/README.md
index 5ec4d7bfdb78b..038996f9dff82 100644
--- a/go/op-exporter/README.md
+++ b/go/op-exporter/README.md
@@ -1,6 +1,6 @@
# op_exporter
-A prometheus exporter to collect information from an Optimistic Ethereum node and serve metrics for collection
+A prometheus exporter to collect information from an Optimism node and serve metrics for collection
## Usage
diff --git a/go/op-exporter/collector.go b/go/op-exporter/collector.go
index b74a105d12877..1fd2a852e9dd8 100644
--- a/go/op-exporter/collector.go
+++ b/go/op-exporter/collector.go
@@ -24,6 +24,12 @@ var (
Help: "Is the sequencer healthy?"},
[]string{"network"},
)
+ opExporterVersion = prometheus.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "op_exporter_version",
+ Help: "Verion of the op-exporter software"},
+ []string{"version", "commit", "goVersion", "buildDate"},
+ )
)
func init() {
@@ -31,4 +37,6 @@ func init() {
prometheus.MustRegister(gasPrice)
prometheus.MustRegister(blockNumber)
prometheus.MustRegister(healthySequencer)
+ prometheus.MustRegister(opExporterVersion)
+
}
diff --git a/go/op-exporter/main.go b/go/op-exporter/main.go
index 07cb256fee7be..cc381d454f343 100644
--- a/go/op-exporter/main.go
+++ b/go/op-exporter/main.go
@@ -6,6 +6,7 @@ import (
"io/ioutil"
"net/http"
"os"
+ "strings"
"sync"
"time"
@@ -20,6 +21,8 @@ import (
"k8s.io/client-go/kubernetes"
)
+var UnknownStatus = "UNKNOWN"
+
var (
listenAddress = kingpin.Flag(
"web.listen-address",
@@ -48,7 +51,7 @@ var (
enableK8sQuery = kingpin.Flag(
"k8s.enable",
"Enable kubernetes info lookup.",
- ).Default("true").Bool()
+ ).Default("false").Bool()
)
type healthCheck struct {
@@ -79,14 +82,15 @@ func main() {
log.Infoln("exporter config", *listenAddress, *rpcProvider, *networkLabel)
log.Infoln("Starting op_exporter", version.Info())
log.Infoln("Build context", version.BuildContext())
-
+ opExporterVersion.WithLabelValues(
+ strings.Trim(version.Version, "\""), version.GitCommit, version.GoVersion, version.BuildDate).Inc()
health := healthCheck{
mu: new(sync.RWMutex),
height: 0,
healthy: false,
updateTime: time.Now(),
allowedMethods: nil,
- version: nil,
+ version: &UnknownStatus,
}
http.Handle("/metrics", promhttp.Handler())
http.Handle("/health", healthHandler(&health))
@@ -130,8 +134,7 @@ func getSequencerVersion(health *healthCheck, client *kubernetes.Clientset) {
}
sequencerStatefulSet, err := client.AppsV1().StatefulSets(string(ns)).Get(context.TODO(), "sequencer", getOpts)
if err != nil {
- unknownStatus := "UNKNOWN"
- health.version = &unknownStatus
+ health.version = &UnknownStatus
log.Errorf("Unable to retrieve a sequencer StatefulSet: %s", err)
continue
}
diff --git a/go/op-exporter/version/version.go b/go/op-exporter/version/version.go
index d86154a93014a..9e90dc62aa329 100644
--- a/go/op-exporter/version/version.go
+++ b/go/op-exporter/version/version.go
@@ -8,7 +8,6 @@ import (
var (
Version string
GitCommit string
- BuildUser string
BuildDate string
GoVersion = runtime.Version()
)
@@ -18,5 +17,5 @@ func Info() string {
}
func BuildContext() string {
- return fmt.Sprintf("(go=%s, user=%s, date=%s)", GoVersion, BuildUser, BuildDate)
+ return fmt.Sprintf("(go=%s, date=%s)", GoVersion, BuildDate)
}
diff --git a/go/proxyd/Dockerfile b/go/proxyd/Dockerfile
index 7da41910f3c11..3301fc074445e 100644
--- a/go/proxyd/Dockerfile
+++ b/go/proxyd/Dockerfile
@@ -4,7 +4,7 @@ ARG GITCOMMIT=docker
ARG GITDATE=docker
ARG GITVERSION=docker
-RUN apk add make jq git
+RUN apk add make jq git gcc musl-dev linux-headers
WORKDIR /app
COPY ./go/proxyd /app
diff --git a/go/proxyd/backend.go b/go/proxyd/backend.go
index e11312ecc1580..b0c7d066c6713 100644
--- a/go/proxyd/backend.go
+++ b/go/proxyd/backend.go
@@ -7,16 +7,18 @@ import (
"encoding/json"
"errors"
"fmt"
- "github.com/ethereum/go-ethereum/log"
- "github.com/gorilla/websocket"
- "github.com/prometheus/client_golang/prometheus"
"io"
"io/ioutil"
"math"
"math/rand"
"net/http"
"strconv"
+ "strings"
"time"
+
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/gorilla/websocket"
+ "github.com/prometheus/client_golang/prometheus"
)
const (
@@ -84,6 +86,8 @@ type Backend struct {
maxRPS int
maxWSConns int
outOfServiceInterval time.Duration
+ stripTrailingXFF bool
+ proxydIP string
}
type BackendOpt func(b *Backend)
@@ -140,6 +144,18 @@ func WithTLSConfig(tlsConfig *tls.Config) BackendOpt {
}
}
+func WithStrippedTrailingXFF() BackendOpt {
+ return func(b *Backend) {
+ b.stripTrailingXFF = true
+ }
+}
+
+func WithProxydIP(ip string) BackendOpt {
+ return func(b *Backend) {
+ b.proxydIP = ip
+ }
+}
+
func NewBackend(
name string,
rpcURL string,
@@ -163,6 +179,10 @@ func NewBackend(
opt(backend)
}
+ if !backend.stripTrailingXFF && backend.proxydIP == "" {
+ log.Warn("proxied requests' XFF header will not contain the proxyd ip address")
+ }
+
return backend
}
@@ -316,7 +336,18 @@ func (b *Backend) doForward(ctx context.Context, rpcReq *RPCReq) (*RPCRes, error
httpReq.SetBasicAuth(b.authUsername, b.authPassword)
}
+ xForwardedFor := GetXForwardedFor(ctx)
+ if b.stripTrailingXFF {
+ ipList := strings.Split(xForwardedFor, ", ")
+ if len(ipList) > 0 {
+ xForwardedFor = ipList[0]
+ }
+ } else if b.proxydIP != "" {
+ xForwardedFor = fmt.Sprintf("%s, %s", xForwardedFor, b.proxydIP)
+ }
+
httpReq.Header.Set("content-type", "application/json")
+ httpReq.Header.Set("X-Forwarded-For", xForwardedFor)
httpRes, err := b.client.Do(httpReq)
if err != nil {
diff --git a/go/proxyd/cache.go b/go/proxyd/cache.go
new file mode 100644
index 0000000000000..d7b89b700bb52
--- /dev/null
+++ b/go/proxyd/cache.go
@@ -0,0 +1,157 @@
+package proxyd
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/go-redis/redis/v8"
+ "github.com/golang/snappy"
+ lru "github.com/hashicorp/golang-lru"
+)
+
+type Cache interface {
+ Get(ctx context.Context, key string) (string, error)
+ Put(ctx context.Context, key string, value string) error
+}
+
+// assuming an average RPCRes size of 3 KB
+const (
+ memoryCacheLimit = 4096
+ numBlockConfirmations = 50
+)
+
+type cache struct {
+ lru *lru.Cache
+}
+
+func newMemoryCache() *cache {
+ rep, _ := lru.New(memoryCacheLimit)
+ return &cache{rep}
+}
+
+func (c *cache) Get(ctx context.Context, key string) (string, error) {
+ if val, ok := c.lru.Get(key); ok {
+ return val.(string), nil
+ }
+ return "", nil
+}
+
+func (c *cache) Put(ctx context.Context, key string, value string) error {
+ c.lru.Add(key, value)
+ return nil
+}
+
+type redisCache struct {
+ rdb *redis.Client
+}
+
+func newRedisCache(url string) (*redisCache, error) {
+ opts, err := redis.ParseURL(url)
+ if err != nil {
+ return nil, err
+ }
+ rdb := redis.NewClient(opts)
+ if err := rdb.Ping(context.Background()).Err(); err != nil {
+ return nil, wrapErr(err, "error connecting to redis")
+ }
+ return &redisCache{rdb}, nil
+}
+
+func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
+ val, err := c.rdb.Get(ctx, key).Result()
+ if err == redis.Nil {
+ return "", nil
+ } else if err != nil {
+ return "", err
+ }
+ return val, nil
+}
+
+func (c *redisCache) Put(ctx context.Context, key string, value string) error {
+ err := c.rdb.Set(ctx, key, value, 0).Err()
+ return err
+}
+
+type GetLatestBlockNumFn func(ctx context.Context) (uint64, error)
+
+type RPCCache interface {
+ GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error)
+ PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error
+}
+
+type rpcCache struct {
+ cache Cache
+ getLatestBlockNumFn GetLatestBlockNumFn
+ handlers map[string]RPCMethodHandler
+}
+
+func newRPCCache(cache Cache, getLatestBlockNumFn GetLatestBlockNumFn) RPCCache {
+ handlers := map[string]RPCMethodHandler{
+ "eth_chainId": &StaticRPCMethodHandler{"eth_chainId"},
+ "net_version": &StaticRPCMethodHandler{"net_version"},
+ "eth_getBlockByNumber": &EthGetBlockByNumberMethod{getLatestBlockNumFn},
+ "eth_getBlockRange": &EthGetBlockRangeMethod{getLatestBlockNumFn},
+ }
+ return &rpcCache{cache: cache, getLatestBlockNumFn: getLatestBlockNumFn, handlers: handlers}
+}
+
+func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
+ handler := c.handlers[req.Method]
+ if handler == nil {
+ return nil, nil
+ }
+ cacheable, err := handler.IsCacheable(req)
+ if err != nil {
+ return nil, err
+ }
+ if !cacheable {
+ return nil, nil
+ }
+
+ key := handler.CacheKey(req)
+ encodedVal, err := c.cache.Get(ctx, key)
+ if err != nil {
+ return nil, err
+ }
+ if encodedVal == "" {
+ return nil, nil
+ }
+ val, err := snappy.Decode(nil, []byte(encodedVal))
+ if err != nil {
+ return nil, err
+ }
+
+ res := new(RPCRes)
+ err = json.Unmarshal(val, res)
+ if err != nil {
+ return nil, err
+ }
+ res.ID = req.ID
+ return res, nil
+}
+
+func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error {
+ handler := c.handlers[req.Method]
+ if handler == nil {
+ return nil
+ }
+ cacheable, err := handler.IsCacheable(req)
+ if err != nil {
+ return err
+ }
+ if !cacheable {
+ return nil
+ }
+ requiresConfirmations, err := handler.RequiresUnconfirmedBlocks(ctx, req)
+ if err != nil {
+ return err
+ }
+ if requiresConfirmations {
+ return nil
+ }
+
+ key := handler.CacheKey(req)
+ val := mustMarshalJSON(res)
+ encodedVal := snappy.Encode(nil, val)
+ return c.cache.Put(ctx, key, string(encodedVal))
+}
diff --git a/go/proxyd/cache_test.go b/go/proxyd/cache_test.go
new file mode 100644
index 0000000000000..f3bcc20e0b1f2
--- /dev/null
+++ b/go/proxyd/cache_test.go
@@ -0,0 +1,393 @@
+package proxyd
+
+import (
+ "context"
+ "math"
+ "strconv"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestRPCCacheWhitelist(t *testing.T) {
+ const blockHead = math.MaxUint64
+ ctx := context.Background()
+
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ rpcs := []struct {
+ req *RPCReq
+ res *RPCRes
+ name string
+ }{
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_chainId",
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: "0xff",
+ ID: ID,
+ },
+ name: "eth_chainId",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "net_version",
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: "9999",
+ ID: ID,
+ },
+ name: "net_version",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["0x1", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ },
+ name: "eth_getBlockByNumber",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["earliest", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ },
+ name: "eth_getBlockByNumber earliest",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["0x1", "0x2", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "eth_getBlockRange",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["earliest", "0x2", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "eth_getBlockRange earliest",
+ },
+ }
+
+ for _, rpc := range rpcs {
+ t.Run(rpc.name, func(t *testing.T) {
+ err := cache.PutRPC(ctx, rpc.req, rpc.res)
+ require.NoError(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, rpc.req)
+ require.NoError(t, err)
+ require.Equal(t, rpc.res, cachedRes)
+ })
+ }
+}
+
+func TestRPCCacheUnsupportedMethod(t *testing.T) {
+ const blockHead = math.MaxUint64
+ ctx := context.Background()
+
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ req := &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_blockNumber",
+ ID: ID,
+ }
+ res := &RPCRes{
+ JSONRPC: "2.0",
+ Result: `0x1000`,
+ ID: ID,
+ }
+
+ err := cache.PutRPC(ctx, req, res)
+ require.NoError(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, req)
+ require.NoError(t, err)
+ require.Nil(t, cachedRes)
+}
+
+func TestRPCCacheEthGetBlockByNumberForRecentBlocks(t *testing.T) {
+ ctx := context.Background()
+
+ var blockHead uint64 = 2
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ rpcs := []struct {
+ req *RPCReq
+ res *RPCRes
+ name string
+ }{
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["0x1", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ },
+ name: "recent block num",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["latest", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ },
+ name: "latest block",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["pending", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ },
+ name: "pending block",
+ },
+ }
+
+ for _, rpc := range rpcs {
+ t.Run(rpc.name, func(t *testing.T) {
+ err := cache.PutRPC(ctx, rpc.req, rpc.res)
+ require.NoError(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, rpc.req)
+ require.NoError(t, err)
+ require.Nil(t, cachedRes)
+ })
+ }
+}
+
+func TestRPCCacheEthGetBlockByNumberInvalidRequest(t *testing.T) {
+ ctx := context.Background()
+
+ const blockHead = math.MaxUint64
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ req := &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockByNumber",
+ Params: []byte(`["0x1"]`), // missing required boolean param
+ ID: ID,
+ }
+ res := &RPCRes{
+ JSONRPC: "2.0",
+ Result: `{"difficulty": "0x1", "number": "0x1"}`,
+ ID: ID,
+ }
+
+ err := cache.PutRPC(ctx, req, res)
+ require.Error(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, req)
+ require.Error(t, err)
+ require.Nil(t, cachedRes)
+}
+
+func TestRPCCacheEthGetBlockRangeForRecentBlocks(t *testing.T) {
+ ctx := context.Background()
+
+ var blockHead uint64 = 0x1000
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ rpcs := []struct {
+ req *RPCReq
+ res *RPCRes
+ name string
+ }{
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["0x1", "0x1000", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "recent block num",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["0x1", "latest", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "latest block",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["0x1", "pending", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "pending block",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["latest", "0x1000", false]`),
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "latest block 2",
+ },
+ }
+
+ for _, rpc := range rpcs {
+ t.Run(rpc.name, func(t *testing.T) {
+ err := cache.PutRPC(ctx, rpc.req, rpc.res)
+ require.NoError(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, rpc.req)
+ require.NoError(t, err)
+ require.Nil(t, cachedRes)
+ })
+ }
+}
+
+func TestRPCCacheEthGetBlockRangeInvalidRequest(t *testing.T) {
+ ctx := context.Background()
+
+ const blockHead = math.MaxUint64
+ fn := func(ctx context.Context) (uint64, error) {
+ return blockHead, nil
+ }
+ cache := newRPCCache(newMemoryCache(), fn)
+ ID := []byte(strconv.Itoa(1))
+
+ rpcs := []struct {
+ req *RPCReq
+ res *RPCRes
+ name string
+ }{
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["0x1", "0x2"]`), // missing required boolean param
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "missing boolean param",
+ },
+ {
+ req: &RPCReq{
+ JSONRPC: "2.0",
+ Method: "eth_getBlockRange",
+ Params: []byte(`["abc", "0x2", true]`), // invalid block hex
+ ID: ID,
+ },
+ res: &RPCRes{
+ JSONRPC: "2.0",
+ Result: `[{"number": "0x1"}, {"number": "0x2"}]`,
+ ID: ID,
+ },
+ name: "invalid block hex",
+ },
+ }
+
+ for _, rpc := range rpcs {
+ t.Run(rpc.name, func(t *testing.T) {
+ err := cache.PutRPC(ctx, rpc.req, rpc.res)
+ require.Error(t, err)
+
+ cachedRes, err := cache.GetRPC(ctx, rpc.req)
+ require.Error(t, err)
+ require.Nil(t, cachedRes)
+ })
+ }
+}
diff --git a/go/proxyd/config.go b/go/proxyd/config.go
index 1df481c838486..68b2acbe1d597 100644
--- a/go/proxyd/config.go
+++ b/go/proxyd/config.go
@@ -14,6 +14,11 @@ type ServerConfig struct {
MaxBodySizeBytes int64 `toml:"max_body_size_bytes"`
}
+type CacheConfig struct {
+ Enabled bool `toml:"enabled"`
+ BlockSyncRPCURL string `toml:"block_sync_rpc_url"`
+}
+
type RedisConfig struct {
URL string `toml:"url"`
}
@@ -32,15 +37,16 @@ type BackendOptions struct {
}
type BackendConfig struct {
- Username string `toml:"username"`
- Password string `toml:"password"`
- RPCURL string `toml:"rpc_url"`
- WSURL string `toml:"ws_url"`
- MaxRPS int `toml:"max_rps"`
- MaxWSConns int `toml:"max_ws_conns"`
- CAFile string `toml:"ca_file"`
- ClientCertFile string `toml:"client_cert_file"`
- ClientKeyFile string `toml:"client_key_file"`
+ Username string `toml:"username"`
+ Password string `toml:"password"`
+ RPCURL string `toml:"rpc_url"`
+ WSURL string `toml:"ws_url"`
+ MaxRPS int `toml:"max_rps"`
+ MaxWSConns int `toml:"max_ws_conns"`
+ CAFile string `toml:"ca_file"`
+ ClientCertFile string `toml:"client_cert_file"`
+ ClientKeyFile string `toml:"client_key_file"`
+ StripTrailingXFF bool `toml:"strip_trailing_xff"`
}
type BackendsConfig map[string]*BackendConfig
@@ -56,6 +62,7 @@ type MethodMappingsConfig map[string]string
type Config struct {
WSBackendGroup string `toml:"ws_backend_group"`
Server *ServerConfig `toml:"server"`
+ Cache *CacheConfig `toml:"cache"`
Redis *RedisConfig `toml:"redis"`
Metrics *MetricsConfig `toml:"metrics"`
BackendOptions *BackendOptions `toml:"backend"`
diff --git a/go/proxyd/go.mod b/go/proxyd/go.mod
index 5c8d067923c88..6bd5a19538d36 100644
--- a/go/proxyd/go.mod
+++ b/go/proxyd/go.mod
@@ -6,8 +6,11 @@ require (
github.com/BurntSushi/toml v0.4.1
github.com/ethereum/go-ethereum v1.10.11
github.com/go-redis/redis/v8 v8.11.4
+ github.com/golang/snappy v0.0.4
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
+ github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/prometheus/client_golang v1.11.0
github.com/rs/cors v1.8.0
+ github.com/stretchr/testify v1.7.0
)
diff --git a/go/proxyd/go.sum b/go/proxyd/go.sum
index a62a130b253f3..2e134d90a1f2d 100644
--- a/go/proxyd/go.sum
+++ b/go/proxyd/go.sum
@@ -37,7 +37,9 @@ github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
+github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/VictoriaMetrics/fastcache v1.6.0/go.mod h1:0qHz5QP0GMX4pfmMA/zt5RgfNuXJrTP0zS7DqpHGGTw=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
@@ -64,6 +66,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
+github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
@@ -93,7 +96,9 @@ github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0=
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ=
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
@@ -107,17 +112,20 @@ github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r
github.com/dop251/goja v0.0.0-20211011172007-d99e4b8cbf48/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
+github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum/go-ethereum v1.10.11 h1:KKIcwpmur9iTaVbR2dxlHu+peHVhU+/KX//NWvT1n9U=
github.com/ethereum/go-ethereum v1.10.11/go.mod h1:W3yfrFyL9C1pHcwY5hmRHVDaorTiQxhYBkKyu5mEDHw=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
@@ -135,6 +143,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
@@ -173,6 +182,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -194,6 +204,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I=
github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
@@ -203,13 +214,18 @@ github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/graph-gophers/graphql-go v0.0.0-20201113091052-beb923fada29/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
+github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
+github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huin/goupnp v1.0.2 h1:RfGLP+h3mvisuWEyybxNq5Eft3NWhHLPeUN72kpKZoI=
github.com/huin/goupnp v1.0.2/go.mod h1:0dxJBVBHqTMjIUMkESDTNgOOx/Mw5wYIfyFmdzSamkM=
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@@ -225,6 +241,7 @@ github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19y
github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE=
github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0=
github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po=
+github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJyexcZ3pYbTI9jWx5tHo1Dee/tWbLMfPe2TA=
github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@@ -256,9 +273,11 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
@@ -272,20 +291,25 @@ github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIG
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -300,6 +324,7 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -322,8 +347,10 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@@ -346,8 +373,10 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
+github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
@@ -357,6 +386,7 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -368,6 +398,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -376,11 +407,16 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
+github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
+github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
+github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
@@ -407,6 +443,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -476,6 +513,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -534,6 +572,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -618,15 +657,18 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
+gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -637,6 +679,7 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/go/proxyd/latestblock.go b/go/proxyd/latestblock.go
new file mode 100644
index 0000000000000..afb97a41fed50
--- /dev/null
+++ b/go/proxyd/latestblock.go
@@ -0,0 +1,93 @@
+package proxyd
+
+import (
+ "context"
+ "sync"
+ "time"
+
+ "github.com/ethereum/go-ethereum/ethclient"
+ "github.com/ethereum/go-ethereum/log"
+)
+
+const blockHeadSyncPeriod = 1 * time.Second
+
+type LatestBlockHead struct {
+ url string
+ client *ethclient.Client
+ quit chan struct{}
+ done chan struct{}
+
+ mutex sync.RWMutex
+ blockNum uint64
+}
+
+func newLatestBlockHead(url string) (*LatestBlockHead, error) {
+ client, err := ethclient.Dial(url)
+ if err != nil {
+ return nil, err
+ }
+
+ return &LatestBlockHead{
+ url: url,
+ client: client,
+ quit: make(chan struct{}),
+ done: make(chan struct{}),
+ }, nil
+}
+
+func (h *LatestBlockHead) Start() {
+ go func() {
+ ticker := time.NewTicker(blockHeadSyncPeriod)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ blockNum, err := h.getBlockNum()
+ if err != nil {
+ log.Error("error retrieving latest block number", "error", err)
+ continue
+ }
+ log.Trace("polling block number", "blockNum", blockNum)
+ h.mutex.Lock()
+ h.blockNum = blockNum
+ h.mutex.Unlock()
+
+ case <-h.quit:
+ close(h.done)
+ return
+ }
+ }
+ }()
+}
+
+func (h *LatestBlockHead) getBlockNum() (uint64, error) {
+ const maxRetries = 5
+ var err error
+
+ for i := 0; i <= maxRetries; i++ {
+ var blockNum uint64
+ blockNum, err = h.client.BlockNumber(context.Background())
+ if err != nil {
+ backoff := calcBackoff(i)
+ log.Warn("http operation failed. retrying...", "error", err, "backoff", backoff)
+ time.Sleep(backoff)
+ continue
+ }
+ return blockNum, nil
+ }
+
+ return 0, wrapErr(err, "exceeded retries")
+}
+
+func (h *LatestBlockHead) Stop() {
+ close(h.quit)
+ <-h.done
+ h.client.Close()
+}
+
+func (h *LatestBlockHead) GetBlockNum() uint64 {
+ h.mutex.RLock()
+ defer h.mutex.RUnlock()
+ return h.blockNum
+}
diff --git a/go/proxyd/methods.go b/go/proxyd/methods.go
new file mode 100644
index 0000000000000..d6a49597386f1
--- /dev/null
+++ b/go/proxyd/methods.go
@@ -0,0 +1,195 @@
+package proxyd
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+)
+
+var errInvalidRPCParams = errors.New("invalid RPC params")
+
+type RPCMethodHandler interface {
+ CacheKey(req *RPCReq) string
+ IsCacheable(req *RPCReq) (bool, error)
+ RequiresUnconfirmedBlocks(ctx context.Context, req *RPCReq) (bool, error)
+}
+
+type StaticRPCMethodHandler struct {
+ method string
+}
+
+func (s *StaticRPCMethodHandler) CacheKey(req *RPCReq) string {
+ return fmt.Sprintf("method:%s", s.method)
+}
+
+func (s *StaticRPCMethodHandler) IsCacheable(*RPCReq) (bool, error) { return true, nil }
+func (s *StaticRPCMethodHandler) RequiresUnconfirmedBlocks(context.Context, *RPCReq) (bool, error) {
+ return false, nil
+}
+
+type EthGetBlockByNumberMethod struct {
+ getLatestBlockNumFn GetLatestBlockNumFn
+}
+
+func (e *EthGetBlockByNumberMethod) CacheKey(req *RPCReq) string {
+ input, includeTx, err := decodeGetBlockByNumberParams(req.Params)
+ if err != nil {
+ return ""
+ }
+ return fmt.Sprintf("method:eth_getBlockByNumber:%s:%t", input, includeTx)
+}
+
+func (e *EthGetBlockByNumberMethod) IsCacheable(req *RPCReq) (bool, error) {
+ blockNum, _, err := decodeGetBlockByNumberParams(req.Params)
+ if err != nil {
+ return false, err
+ }
+ return !isBlockDependentParam(blockNum), nil
+}
+
+func (e *EthGetBlockByNumberMethod) RequiresUnconfirmedBlocks(ctx context.Context, req *RPCReq) (bool, error) {
+ curBlock, err := e.getLatestBlockNumFn(ctx)
+ if err != nil {
+ return false, err
+ }
+ blockInput, _, err := decodeGetBlockByNumberParams(req.Params)
+ if err != nil {
+ return false, err
+ }
+ if isBlockDependentParam(blockInput) {
+ return true, nil
+ }
+ if blockInput == "earliest" {
+ return false, nil
+ }
+ blockNum, err := decodeBlockInput(blockInput)
+ if err != nil {
+ return false, err
+ }
+ return curBlock <= blockNum+numBlockConfirmations, nil
+}
+
+type EthGetBlockRangeMethod struct {
+ getLatestBlockNumFn GetLatestBlockNumFn
+}
+
+func (e *EthGetBlockRangeMethod) CacheKey(req *RPCReq) string {
+ start, end, includeTx, err := decodeGetBlockRangeParams(req.Params)
+ if err != nil {
+ return ""
+ }
+ return fmt.Sprintf("method:eth_getBlockRange:%s:%s:%t", start, end, includeTx)
+}
+
+func (e *EthGetBlockRangeMethod) IsCacheable(req *RPCReq) (bool, error) {
+ start, end, _, err := decodeGetBlockRangeParams(req.Params)
+ if err != nil {
+ return false, err
+ }
+ return !isBlockDependentParam(start) && !isBlockDependentParam(end), nil
+}
+
+func (e *EthGetBlockRangeMethod) RequiresUnconfirmedBlocks(ctx context.Context, req *RPCReq) (bool, error) {
+ curBlock, err := e.getLatestBlockNumFn(ctx)
+ if err != nil {
+ return false, err
+ }
+
+ start, end, _, err := decodeGetBlockRangeParams(req.Params)
+ if err != nil {
+ return false, err
+ }
+ if isBlockDependentParam(start) || isBlockDependentParam(end) {
+ return true, nil
+ }
+ if start == "earliest" && end == "earliest" {
+ return false, nil
+ }
+
+ if start != "earliest" {
+ startNum, err := decodeBlockInput(start)
+ if err != nil {
+ return false, err
+ }
+ if curBlock <= startNum+numBlockConfirmations {
+ return true, nil
+ }
+ }
+ if end != "earliest" {
+ endNum, err := decodeBlockInput(end)
+ if err != nil {
+ return false, err
+ }
+ if curBlock <= endNum+numBlockConfirmations {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+func isBlockDependentParam(s string) bool {
+ return s == "latest" || s == "pending"
+}
+
+func decodeGetBlockByNumberParams(params json.RawMessage) (string, bool, error) {
+ var list []interface{}
+ if err := json.Unmarshal(params, &list); err != nil {
+ return "", false, err
+ }
+ if len(list) != 2 {
+ return "", false, errInvalidRPCParams
+ }
+ blockNum, ok := list[0].(string)
+ if !ok {
+ return "", false, errInvalidRPCParams
+ }
+ includeTx, ok := list[1].(bool)
+ if !ok {
+ return "", false, errInvalidRPCParams
+ }
+ if !validBlockInput(blockNum) {
+ return "", false, errInvalidRPCParams
+ }
+ return blockNum, includeTx, nil
+}
+
+func decodeGetBlockRangeParams(params json.RawMessage) (string, string, bool, error) {
+ var list []interface{}
+ if err := json.Unmarshal(params, &list); err != nil {
+ return "", "", false, err
+ }
+ if len(list) != 3 {
+ return "", "", false, errInvalidRPCParams
+ }
+ startBlockNum, ok := list[0].(string)
+ if !ok {
+ return "", "", false, errInvalidRPCParams
+ }
+ endBlockNum, ok := list[1].(string)
+ if !ok {
+ return "", "", false, errInvalidRPCParams
+ }
+ includeTx, ok := list[2].(bool)
+ if !ok {
+ return "", "", false, errInvalidRPCParams
+ }
+ if !validBlockInput(startBlockNum) || !validBlockInput(endBlockNum) {
+ return "", "", false, errInvalidRPCParams
+ }
+ return startBlockNum, endBlockNum, includeTx, nil
+}
+
+func decodeBlockInput(input string) (uint64, error) {
+ return hexutil.DecodeUint64(input)
+}
+
+func validBlockInput(input string) bool {
+ if input == "earliest" || input == "pending" || input == "latest" {
+ return true
+ }
+ _, err := decodeBlockInput(input)
+ return err == nil
+}
diff --git a/go/proxyd/metrics.go b/go/proxyd/metrics.go
index 26576e1745e9b..e29823e56f7bb 100644
--- a/go/proxyd/metrics.go
+++ b/go/proxyd/metrics.go
@@ -2,10 +2,11 @@ package proxyd
import (
"context"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
"strconv"
"strings"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
)
const (
@@ -20,6 +21,8 @@ const (
MethodUnknown = "unknown"
)
+var PayloadSizeBuckets = []float64{10, 50, 100, 500, 1000, 5000, 10000, 100000, 1000000}
+
var (
rpcRequestsTotal = promauto.NewCounter(prometheus.CounterOpts{
Namespace: MetricsNamespace,
@@ -139,6 +142,25 @@ var (
"source",
})
+ requestPayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{
+ Namespace: MetricsNamespace,
+ Name: "request_payload_sizes",
+ Help: "Gauge of client request payload sizes.",
+ Buckets: PayloadSizeBuckets,
+ }, []string{
+ "auth",
+ "method_name",
+ })
+
+ responsePayloadSizesGauge = promauto.NewHistogramVec(prometheus.HistogramOpts{
+ Namespace: MetricsNamespace,
+ Name: "response_payload_sizes",
+ Help: "Gauge of client response payload sizes.",
+ Buckets: PayloadSizeBuckets,
+ }, []string{
+ "auth",
+ })
+
rpcSpecialErrors = []string{
"nonce too low",
"gas price too high",
@@ -185,3 +207,11 @@ func MaybeRecordSpecialRPCError(ctx context.Context, backendName, method string,
}
}
}
+
+func RecordRequestPayloadSize(ctx context.Context, method string, payloadSize int) {
+ requestPayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx), method).Observe(float64(payloadSize))
+}
+
+func RecordResponsePayloadSize(ctx context.Context, payloadSize int) {
+ responsePayloadSizesGauge.WithLabelValues(GetAuthCtx(ctx)).Observe(float64(payloadSize))
+}
diff --git a/go/proxyd/proxyd.go b/go/proxyd/proxyd.go
index 988673c056e3c..01e3c5dd03341 100644
--- a/go/proxyd/proxyd.go
+++ b/go/proxyd/proxyd.go
@@ -1,16 +1,18 @@
package proxyd
import (
+ "context"
"crypto/tls"
"errors"
"fmt"
- "github.com/ethereum/go-ethereum/log"
- "github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"os"
"os/signal"
"syscall"
"time"
+
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/prometheus/client_golang/prometheus/promhttp"
)
func Start(config *Config) error {
@@ -96,6 +98,10 @@ func Start(config *Config) error {
log.Info("using custom TLS config for backend", "name", name)
opts = append(opts, WithTLSConfig(tlsConfig))
}
+ if cfg.StripTrailingXFF {
+ opts = append(opts, WithStrippedTrailingXFF())
+ }
+ opts = append(opts, WithProxydIP(os.Getenv("PROXYD_IP")))
back := NewBackend(name, rpcURL, wsURL, lim, opts...)
backendNames = append(backendNames, name)
backendsByName[name] = back
@@ -149,6 +155,35 @@ func Start(config *Config) error {
}
}
+ var rpcCache RPCCache
+ if config.Cache != nil && config.Cache.Enabled {
+ var cache Cache
+ if config.Redis != nil {
+ if cache, err = newRedisCache(config.Redis.URL); err != nil {
+ return err
+ }
+ } else {
+ log.Warn("redis is not configured, using in-memory cache")
+ cache = newMemoryCache()
+ }
+
+ var getLatestBlockNumFn GetLatestBlockNumFn
+ if config.Cache.BlockSyncRPCURL == "" {
+ return fmt.Errorf("block sync node required for caching")
+ }
+ latestHead, err := newLatestBlockHead(config.Cache.BlockSyncRPCURL)
+ if err != nil {
+ return err
+ }
+ latestHead.Start()
+ defer latestHead.Stop()
+
+ getLatestBlockNumFn = func(ctx context.Context) (uint64, error) {
+ return latestHead.GetBlockNum(), nil
+ }
+ rpcCache = newRPCCache(cache, getLatestBlockNumFn)
+ }
+
srv := NewServer(
backendGroups,
wsBackendGroup,
@@ -156,9 +191,10 @@ func Start(config *Config) error {
config.RPCMethodMappings,
config.Server.MaxBodySizeBytes,
resolvedAuth,
+ rpcCache,
)
- if config.Metrics.Enabled {
+ if config.Metrics != nil && config.Metrics.Enabled {
addr := fmt.Sprintf("%s:%d", config.Metrics.Host, config.Metrics.Port)
log.Info("starting metrics server", "addr", addr)
go http.ListenAndServe(addr, promhttp.Handler())
diff --git a/go/proxyd/server.go b/go/proxyd/server.go
index 57e0e44cfa455..c883c8015d905 100644
--- a/go/proxyd/server.go
+++ b/go/proxyd/server.go
@@ -5,20 +5,23 @@ import (
"encoding/json"
"errors"
"fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
"github.com/ethereum/go-ethereum/log"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/cors"
- "io"
- "net/http"
- "strconv"
- "time"
)
const (
- ContextKeyAuth = "authorization"
- ContextKeyReqID = "req_id"
+ ContextKeyAuth = "authorization"
+ ContextKeyReqID = "req_id"
+ ContextKeyXForwardedFor = "x_forwarded_for"
)
type Server struct {
@@ -31,6 +34,7 @@ type Server struct {
upgrader *websocket.Upgrader
rpcServer *http.Server
wsServer *http.Server
+ cache RPCCache
}
func NewServer(
@@ -40,7 +44,11 @@ func NewServer(
rpcMethodMappings map[string]string,
maxBodySize int64,
authenticatedPaths map[string]string,
+ cache RPCCache,
) *Server {
+ if cache == nil {
+ cache = &NoopRPCCache{}
+ }
return &Server{
backendGroups: backendGroups,
wsBackendGroup: wsBackendGroup,
@@ -48,6 +56,7 @@ func NewServer(
rpcMethodMappings: rpcMethodMappings,
maxBodySize: maxBodySize,
authenticatedPaths: authenticatedPaths,
+ cache: cache,
upgrader: &websocket.Upgrader{
HandshakeTimeout: 5 * time.Second,
},
@@ -113,13 +122,15 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
"user_agent", r.Header.Get("user-agent"),
)
- req, err := ParseRPCReq(io.LimitReader(r.Body, s.maxBodySize))
+ bodyReader := &recordLenReader{Reader: io.LimitReader(r.Body, s.maxBodySize)}
+ req, err := ParseRPCReq(bodyReader)
if err != nil {
log.Info("rejected request with bad rpc request", "source", "rpc", "err", err)
RecordRPCError(ctx, BackendProxyd, MethodUnknown, err)
writeRPCError(ctx, w, nil, err)
return
}
+ RecordRequestPayloadSize(ctx, req.Method, bodyReader.Len)
group := s.rpcMethodMappings[req.Method]
if group == "" {
@@ -136,7 +147,21 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return
}
- backendRes, err := s.backendGroups[group].Forward(ctx, req)
+ var backendRes *RPCRes
+ backendRes, err = s.cache.GetRPC(ctx, req)
+ if err == nil && backendRes != nil {
+ writeRPCRes(ctx, w, backendRes)
+ return
+ }
+ if err != nil {
+ log.Warn(
+ "cache lookup error",
+ "req_id", GetReqID(ctx),
+ "err", err,
+ )
+ }
+
+ backendRes, err = s.backendGroups[group].Forward(ctx, req)
if err != nil {
log.Error(
"error forwarding RPC request",
@@ -148,6 +173,16 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) {
return
}
+ if backendRes.Error == nil {
+ if err = s.cache.PutRPC(ctx, req, backendRes); err != nil {
+ log.Warn(
+ "cache put error",
+ "req_id", GetReqID(ctx),
+ "err", err,
+ )
+ }
+ }
+
writeRPCRes(ctx, w, backendRes)
}
@@ -214,7 +249,16 @@ func (s *Server) populateContext(w http.ResponseWriter, r *http.Request) context
return nil
}
+ xff := r.Header.Get("X-Forwarded-For")
+ if xff == "" {
+ ipPort := strings.Split(r.RemoteAddr, ":")
+ if len(ipPort) == 2 {
+ xff = ipPort[0]
+ }
+ }
+
ctx := context.WithValue(r.Context(), ContextKeyAuth, s.authenticatedPaths[authorization])
+ ctx = context.WithValue(ctx, ContextKeyXForwardedFor, xff)
return context.WithValue(
ctx,
ContextKeyReqID,
@@ -237,14 +281,17 @@ func writeRPCRes(ctx context.Context, w http.ResponseWriter, res *RPCRes) {
if res.IsError() && res.Error.HTTPErrorCode != 0 {
statusCode = res.Error.HTTPErrorCode
}
+
w.WriteHeader(statusCode)
- enc := json.NewEncoder(w)
+ ww := &recordLenWriter{Writer: w}
+ enc := json.NewEncoder(ww)
if err := enc.Encode(res); err != nil {
log.Error("error writing rpc response", "err", err)
RecordRPCError(ctx, BackendProxyd, MethodUnknown, err)
return
}
httpResponseCodesTotal.WithLabelValues(strconv.Itoa(statusCode)).Inc()
+ RecordResponsePayloadSize(ctx, ww.Len)
}
func instrumentedHdlr(h http.Handler) http.HandlerFunc {
@@ -271,3 +318,43 @@ func GetReqID(ctx context.Context) string {
}
return reqId
}
+
+func GetXForwardedFor(ctx context.Context) string {
+ xff, ok := ctx.Value(ContextKeyXForwardedFor).(string)
+ if !ok {
+ return ""
+ }
+ return xff
+}
+
+type recordLenReader struct {
+ io.Reader
+ Len int
+}
+
+func (r *recordLenReader) Read(p []byte) (n int, err error) {
+ n, err = r.Reader.Read(p)
+ r.Len += n
+ return
+}
+
+type recordLenWriter struct {
+ io.Writer
+ Len int
+}
+
+func (w *recordLenWriter) Write(p []byte) (n int, err error) {
+ n, err = w.Writer.Write(p)
+ w.Len += n
+ return
+}
+
+type NoopRPCCache struct{}
+
+func (n *NoopRPCCache) GetRPC(context.Context, *RPCReq) (*RPCRes, error) {
+ return nil, nil
+}
+
+func (n *NoopRPCCache) PutRPC(context.Context, *RPCReq, *RPCRes) error {
+ return nil
+}
diff --git a/go/stackman/.dockerignore b/go/stackman/.dockerignore
new file mode 100644
index 0000000000000..0f046820f185f
--- /dev/null
+++ b/go/stackman/.dockerignore
@@ -0,0 +1,4 @@
+# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
+# Ignore build and test binaries.
+bin/
+testbin/
diff --git a/go/stackman/.gitignore b/go/stackman/.gitignore
new file mode 100644
index 0000000000000..c0a7a54cac5ad
--- /dev/null
+++ b/go/stackman/.gitignore
@@ -0,0 +1,25 @@
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+*.dll
+*.so
+*.dylib
+bin
+testbin/*
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Kubernetes Generated files - skip generated files, except for vendored files
+
+!vendor/**/zz_generated.*
+
+# editor and IDE paraphernalia
+.idea
+*.swp
+*.swo
+*~
diff --git a/go/stackman/Dockerfile b/go/stackman/Dockerfile
new file mode 100644
index 0000000000000..4152680b7425b
--- /dev/null
+++ b/go/stackman/Dockerfile
@@ -0,0 +1,27 @@
+# Build the manager binary
+FROM golang:1.16 as builder
+
+WORKDIR /workspace
+# Copy the Go Modules manifests
+COPY go.mod go.mod
+COPY go.sum go.sum
+# cache deps before building and copying source so that we don't need to re-download as much
+# and so that source changes don't invalidate our downloaded layer
+RUN go mod download
+
+# Copy the go source
+COPY main.go main.go
+COPY api/ api/
+COPY controllers/ controllers/
+
+# Build
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go
+
+# Use distroless as minimal base image to package the manager binary
+# Refer to https://github.com/GoogleContainerTools/distroless for more details
+FROM gcr.io/distroless/static:nonroot
+WORKDIR /
+COPY --from=builder /workspace/manager .
+USER 65532:65532
+
+ENTRYPOINT ["/manager"]
diff --git a/go/stackman/Makefile b/go/stackman/Makefile
new file mode 100644
index 0000000000000..aac5d2c5b02b6
--- /dev/null
+++ b/go/stackman/Makefile
@@ -0,0 +1,201 @@
+# VERSION defines the project version for the bundle.
+# Update this value when you upgrade the version of your project.
+# To re-generate a bundle for another specific version without changing the standard setup, you can:
+# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
+# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
+VERSION ?= 0.0.1
+
+# CHANNELS define the bundle channels used in the bundle.
+# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
+# To re-generate a bundle for other specific channels without changing the standard setup, you can:
+# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
+# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
+ifneq ($(origin CHANNELS), undefined)
+BUNDLE_CHANNELS := --channels=$(CHANNELS)
+endif
+
+# DEFAULT_CHANNEL defines the default channel used in the bundle.
+# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
+# To re-generate a bundle for any other default channel without changing the default setup, you can:
+# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
+# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
+ifneq ($(origin DEFAULT_CHANNEL), undefined)
+BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
+endif
+BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
+
+# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
+# This variable is used to construct full image tags for bundle and catalog images.
+#
+# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
+# optimism-stacks.net/stackman-bundle:$VERSION and optimism-stacks.net/stackman-catalog:$VERSION.
+IMAGE_TAG_BASE ?= optimism-stacks.net/stackman
+
+# BUNDLE_IMG defines the image:tag used for the bundle.
+# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:)
+BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
+
+# Image URL to use all building/pushing image targets
+IMG ?= ethereumoptimism/stackman-controller:latest
+# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
+ENVTEST_K8S_VERSION = 1.22
+
+# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
+ifeq (,$(shell go env GOBIN))
+GOBIN=$(shell go env GOPATH)/bin
+else
+GOBIN=$(shell go env GOBIN)
+endif
+
+# Setting SHELL to bash allows bash commands to be executed by recipes.
+# This is a requirement for 'setup-envtest.sh' in the test target.
+# Options are set to exit when a recipe line exits non-zero or a piped command fails.
+SHELL = /usr/bin/env bash -o pipefail
+.SHELLFLAGS = -ec
+
+all: build
+
+##@ General
+
+# The help target prints out all targets with their descriptions organized
+# beneath their categories. The categories are represented by '##@' and the
+# target descriptions by '##'. The awk commands is responsible for reading the
+# entire set of makefiles included in this invocation, looking for lines of the
+# file as xyz: ## something, and then pretty-format the target and help. Then,
+# if there's a line with ##@ something, that gets pretty-printed as a category.
+# More info on the usage of ANSI control characters for terminal formatting:
+# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
+# More info on the awk command:
+# http://linuxcommand.org/lc3_adv_awk.php
+
+help: ## Display this help.
+ @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
+
+##@ Development
+
+manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
+ $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
+
+generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
+ $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
+
+fmt: ## Run go fmt against code.
+ go fmt ./...
+
+vet: ## Run go vet against code.
+ go vet ./...
+
+test: manifests generate fmt vet envtest ## Run tests.
+ KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out
+
+##@ Build
+
+build: generate fmt vet ## Build manager binary.
+ go build -o bin/manager main.go
+
+run: manifests generate fmt vet ## Run a controller from your host.
+ go run ./main.go
+
+docker-build: test ## Build docker image with the manager.
+ docker build -t ${IMG} .
+
+docker-push: ## Push docker image with the manager.
+ docker push ${IMG}
+
+##@ Deployment
+
+install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
+ $(KUSTOMIZE) build config/crd | kubectl apply -f -
+
+uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config.
+ $(KUSTOMIZE) build config/crd | kubectl delete -f -
+
+deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
+ cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
+ $(KUSTOMIZE) build config/default | kubectl apply -f -
+
+undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config.
+ $(KUSTOMIZE) build config/default | kubectl delete -f -
+
+
+CONTROLLER_GEN = $(shell pwd)/bin/controller-gen
+controller-gen: ## Download controller-gen locally if necessary.
+ $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0)
+
+KUSTOMIZE = $(shell pwd)/bin/kustomize
+kustomize: ## Download kustomize locally if necessary.
+ $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7)
+
+ENVTEST = $(shell pwd)/bin/setup-envtest
+envtest: ## Download envtest-setup locally if necessary.
+ $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest)
+
+# go-get-tool will 'go get' any package $2 and install it to $1.
+PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST))))
+define go-get-tool
+@[ -f $(1) ] || { \
+set -e ;\
+TMP_DIR=$$(mktemp -d) ;\
+cd $$TMP_DIR ;\
+go mod init tmp ;\
+echo "Downloading $(2)" ;\
+GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\
+rm -rf $$TMP_DIR ;\
+}
+endef
+
+.PHONY: bundle
+bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files.
+ operator-sdk generate kustomize manifests -q
+ cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
+ $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
+ operator-sdk bundle validate ./bundle
+
+.PHONY: bundle-build
+bundle-build: ## Build the bundle image.
+ docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
+
+.PHONY: bundle-push
+bundle-push: ## Push the bundle image.
+ $(MAKE) docker-push IMG=$(BUNDLE_IMG)
+
+.PHONY: opm
+OPM = ./bin/opm
+opm: ## Download opm locally if necessary.
+ifeq (,$(wildcard $(OPM)))
+ifeq (,$(shell which opm 2>/dev/null))
+ @{ \
+ set -e ;\
+ mkdir -p $(dir $(OPM)) ;\
+ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
+ curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.15.1/$${OS}-$${ARCH}-opm ;\
+ chmod +x $(OPM) ;\
+ }
+else
+OPM = $(shell which opm)
+endif
+endif
+
+# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
+# These images MUST exist in a registry and be pull-able.
+BUNDLE_IMGS ?= $(BUNDLE_IMG)
+
+# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
+CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)
+
+# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
+ifneq ($(origin CATALOG_BASE_IMG), undefined)
+FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
+endif
+
+# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
+# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
+# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
+.PHONY: catalog-build
+catalog-build: opm ## Build a catalog image.
+ $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
+
+# Push the catalog image.
+.PHONY: catalog-push
+catalog-push: ## Push a catalog image.
+ $(MAKE) docker-push IMG=$(CATALOG_IMG)
diff --git a/go/stackman/PROJECT b/go/stackman/PROJECT
new file mode 100644
index 0000000000000..2255971210a24
--- /dev/null
+++ b/go/stackman/PROJECT
@@ -0,0 +1,73 @@
+domain: optimism-stacks.net
+layout:
+- go.kubebuilder.io/v3
+plugins:
+ manifests.sdk.operatorframework.io/v2: {}
+ scorecard.sdk.operatorframework.io/v2: {}
+projectName: stackman
+repo: github.com/ethereum-optimism/optimism/go/stackman
+resources:
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: CliqueL1
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: Deployer
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: DataTransportLayer
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: Sequencer
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: BatchSubmitter
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: GasOracle
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+- api:
+ crdVersion: v1
+ namespaced: true
+ controller: true
+ domain: optimism-stacks.net
+ group: stack
+ kind: Actor
+ path: github.com/ethereum-optimism/optimism/go/stackman/api/v1
+ version: v1
+version: "3"
diff --git a/go/stackman/api/v1/actor_types.go b/go/stackman/api/v1/actor_types.go
new file mode 100644
index 0000000000000..07489ee1a7563
--- /dev/null
+++ b/go/stackman/api/v1/actor_types.go
@@ -0,0 +1,74 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// ActorSpec defines the desired state of Actor
+type ActorSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url"`
+ L2URL string `json:"l2_url"`
+ PrivateKey *Valuer `json:"private_key,omitempty"`
+ AddressManagerAddress string `json:"address_manager_address"`
+ TestFilename string `json:"test_filename,omitempty"`
+ Concurrency int `json:"concurrency,omitempty"`
+ RunForMS int `json:"run_for_ms,omitempty"`
+ RunCount int `json:"run_count,omitempty"`
+ ThinkTimeMS int `json:"think_time_ms,omitempty"`
+ Env []corev1.EnvVar `json:"env,omitempty"`
+}
+
+// ActorStatus defines the observed state of Actor
+type ActorStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// Actor is the Schema for the actors API
+type Actor struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec ActorSpec `json:"spec,omitempty"`
+ Status ActorStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// ActorList contains a list of Actor
+type ActorList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []Actor `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&Actor{}, &ActorList{})
+}
diff --git a/go/stackman/api/v1/batchsubmitter_types.go b/go/stackman/api/v1/batchsubmitter_types.go
new file mode 100644
index 0000000000000..8126178948887
--- /dev/null
+++ b/go/stackman/api/v1/batchsubmitter_types.go
@@ -0,0 +1,71 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// BatchSubmitterSpec defines the desired state of BatchSubmitter
+type BatchSubmitterSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url,omitempty"`
+ L1TimeoutSeconds int `json:"l1_timeout_seconds,omitempty"`
+ L2URL string `json:"l2_url,omitempty"`
+ L2TimeoutSeconds int `json:"l2_timeout_seconds,omitempty"`
+ DeployerURL string `json:"deployer_url,omitempty"`
+ DeployerTimeoutSeconds int `json:"deployer_timeout_seconds,omitempty"`
+ Env []corev1.EnvVar `json:"env,omitempty"`
+}
+
+// BatchSubmitterStatus defines the observed state of BatchSubmitter
+type BatchSubmitterStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// BatchSubmitter is the Schema for the batchsubmitters API
+type BatchSubmitter struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec BatchSubmitterSpec `json:"spec,omitempty"`
+ Status BatchSubmitterStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// BatchSubmitterList contains a list of BatchSubmitter
+type BatchSubmitterList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []BatchSubmitter `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&BatchSubmitter{}, &BatchSubmitterList{})
+}
diff --git a/go/stackman/api/v1/cliquel1_types.go b/go/stackman/api/v1/cliquel1_types.go
new file mode 100644
index 0000000000000..583af5a4dc532
--- /dev/null
+++ b/go/stackman/api/v1/cliquel1_types.go
@@ -0,0 +1,69 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// CliqueL1Spec defines the desired state of CliqueL1
+type CliqueL1Spec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ GenesisFile *Valuer `json:"genesis_file,omitempty"`
+ SealerPrivateKey *Valuer `json:"sealer_private_key"`
+ SealerAddress string `json:"sealer_address,omitempty"`
+ ChainID int `json:"chain_id,omitempty"`
+ DataPVC *PVCConfig `json:"data_pvc,omitempty"`
+ AdditionalArgs []string `json:"additional_args,omitempty"`
+}
+
+// CliqueL1Status defines the observed state of CliqueL1
+type CliqueL1Status struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// CliqueL1 is the Schema for the cliquel1s API
+type CliqueL1 struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec CliqueL1Spec `json:"spec,omitempty"`
+ Status CliqueL1Status `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// CliqueL1List contains a list of CliqueL1
+type CliqueL1List struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []CliqueL1 `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&CliqueL1{}, &CliqueL1List{})
+}
diff --git a/go/stackman/api/v1/common.go b/go/stackman/api/v1/common.go
new file mode 100644
index 0000000000000..64053334ca08d
--- /dev/null
+++ b/go/stackman/api/v1/common.go
@@ -0,0 +1,33 @@
+package v1
+
+import (
+ "gopkg.in/yaml.v2"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+)
+
+type Valuer struct {
+ Value string `json:"value,omitempty"`
+ ValueFrom *corev1.EnvVarSource `json:"value_from,omitempty"`
+}
+
+func (v *Valuer) String() string {
+ out, err := yaml.Marshal(v)
+ if err != nil {
+ panic(err)
+ }
+ return string(out)
+}
+
+func (v *Valuer) EnvVar(name string) corev1.EnvVar {
+ return corev1.EnvVar{
+ Name: name,
+ Value: v.Value,
+ ValueFrom: v.ValueFrom,
+ }
+}
+
+type PVCConfig struct {
+ Name string `json:"name"`
+ Storage *resource.Quantity `json:"storage,omitempty"`
+}
diff --git a/go/stackman/api/v1/datatransportlayer_types.go b/go/stackman/api/v1/datatransportlayer_types.go
new file mode 100644
index 0000000000000..75894cf75818e
--- /dev/null
+++ b/go/stackman/api/v1/datatransportlayer_types.go
@@ -0,0 +1,70 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// DataTransportLayerSpec defines the desired state of DataTransportLayer
+type DataTransportLayerSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url,omitempty"`
+ L1TimeoutSeconds int `json:"l1_timeout_seconds,omitempty"`
+ DeployerURL string `json:"deployer_url,omitempty"`
+ DeployerTimeoutSeconds int `json:"deployer_timeout_seconds,omitempty"`
+ DataPVC *PVCConfig `json:"data_pvc,omitempty"`
+ Env []v1.EnvVar `json:"env,omitempty"`
+}
+
+// DataTransportLayerStatus defines the observed state of DataTransportLayer
+type DataTransportLayerStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// DataTransportLayer is the Schema for the datatransportlayers API
+type DataTransportLayer struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec DataTransportLayerSpec `json:"spec,omitempty"`
+ Status DataTransportLayerStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// DataTransportLayerList contains a list of DataTransportLayer
+type DataTransportLayerList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []DataTransportLayer `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&DataTransportLayer{}, &DataTransportLayerList{})
+}
diff --git a/go/stackman/api/v1/deployer_types.go b/go/stackman/api/v1/deployer_types.go
new file mode 100644
index 0000000000000..994f1e9b5a1e1
--- /dev/null
+++ b/go/stackman/api/v1/deployer_types.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// DeployerSpec defines the desired state of Deployer
+type DeployerSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url,omitempty"`
+ L1TimeoutSeconds int `json:"l1_timeout_seconds,omitempty"`
+ Env []corev1.EnvVar `json:"env,omitempty"`
+}
+
+// DeployerStatus defines the observed state of Deployer
+type DeployerStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// Deployer is the Schema for the deployers API
+type Deployer struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec DeployerSpec `json:"spec,omitempty"`
+ Status DeployerStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// DeployerList contains a list of Deployer
+type DeployerList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []Deployer `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&Deployer{}, &DeployerList{})
+}
diff --git a/go/stackman/api/v1/gasoracle_types.go b/go/stackman/api/v1/gasoracle_types.go
new file mode 100644
index 0000000000000..5b3ee4dc8b7a4
--- /dev/null
+++ b/go/stackman/api/v1/gasoracle_types.go
@@ -0,0 +1,69 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// GasOracleSpec defines the desired state of GasOracle
+type GasOracleSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url,omitempty"`
+ L1TimeoutSeconds int `json:"l1_timeout_seconds,omitempty"`
+ L2URL string `json:"l2_url,omitempty"`
+ L2TimeoutSeconds int `json:"l2_timeout_seconds,omitempty"`
+ Env []v1.EnvVar `json:"env,omitempty"`
+}
+
+// GasOracleStatus defines the observed state of GasOracle
+type GasOracleStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// GasOracle is the Schema for the gasoracles API
+type GasOracle struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec GasOracleSpec `json:"spec,omitempty"`
+ Status GasOracleStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// GasOracleList contains a list of GasOracle
+type GasOracleList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []GasOracle `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&GasOracle{}, &GasOracleList{})
+}
diff --git a/go/stackman/api/v1/groupversion_info.go b/go/stackman/api/v1/groupversion_info.go
new file mode 100644
index 0000000000000..54b9e0a82c15f
--- /dev/null
+++ b/go/stackman/api/v1/groupversion_info.go
@@ -0,0 +1,36 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Package v1 contains API Schema definitions for the stack v1 API group
+//+kubebuilder:object:generate=true
+//+groupName=stack.optimism-stacks.net
+package v1
+
+import (
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "sigs.k8s.io/controller-runtime/pkg/scheme"
+)
+
+var (
+ // GroupVersion is group version used to register these objects
+ GroupVersion = schema.GroupVersion{Group: "stack.optimism-stacks.net", Version: "v1"}
+
+ // SchemeBuilder is used to add go types to the GroupVersionKind scheme
+ SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
+
+ // AddToScheme adds the types in this group-version to the given scheme.
+ AddToScheme = SchemeBuilder.AddToScheme
+)
diff --git a/go/stackman/api/v1/sequencer_types.go b/go/stackman/api/v1/sequencer_types.go
new file mode 100644
index 0000000000000..a5a544725be4e
--- /dev/null
+++ b/go/stackman/api/v1/sequencer_types.go
@@ -0,0 +1,73 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package v1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
+
+// SequencerSpec defines the desired state of Sequencer
+type SequencerSpec struct {
+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+
+ Image string `json:"image,omitempty"`
+ L1URL string `json:"l1_url,omitempty"`
+ L1TimeoutSeconds int `json:"l1_timeout_seconds,omitempty"`
+ DeployerURL string `json:"deployer_url,omitempty"`
+ DeployerTimeoutSeconds int `json:"deployer_timeout_seconds,omitempty"`
+ DTLURL string `json:"dtl_url,omitempty"`
+ DTLTimeoutSeconds int `json:"dtl_timeout_seconds,omitempty"`
+ DataPVC *PVCConfig `json:"data_pvc,omitempty"`
+ Env []corev1.EnvVar `json:"env,omitempty"`
+ AdditionalArgs []string `json:"additional_args,omitempty"`
+}
+
+// SequencerStatus defines the observed state of Sequencer
+type SequencerStatus struct {
+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
+ // Important: Run "make" to regenerate code after modifying this file
+}
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// Sequencer is the Schema for the sequencers API
+type Sequencer struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec SequencerSpec `json:"spec,omitempty"`
+ Status SequencerStatus `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// SequencerList contains a list of Sequencer
+type SequencerList struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []Sequencer `json:"items"`
+}
+
+func init() {
+ SchemeBuilder.Register(&Sequencer{}, &SequencerList{})
+}
diff --git a/go/stackman/api/v1/zz_generated.deepcopy.go b/go/stackman/api/v1/zz_generated.deepcopy.go
new file mode 100644
index 0000000000000..fb61b6e65a478
--- /dev/null
+++ b/go/stackman/api/v1/zz_generated.deepcopy.go
@@ -0,0 +1,772 @@
+//go:build !ignore_autogenerated
+// +build !ignore_autogenerated
+
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by controller-gen. DO NOT EDIT.
+
+package v1
+
+import (
+ corev1 "k8s.io/api/core/v1"
+ runtime "k8s.io/apimachinery/pkg/runtime"
+)
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Actor) DeepCopyInto(out *Actor) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Actor.
+func (in *Actor) DeepCopy() *Actor {
+ if in == nil {
+ return nil
+ }
+ out := new(Actor)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Actor) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ActorList) DeepCopyInto(out *ActorList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Actor, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorList.
+func (in *ActorList) DeepCopy() *ActorList {
+ if in == nil {
+ return nil
+ }
+ out := new(ActorList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *ActorList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ActorSpec) DeepCopyInto(out *ActorSpec) {
+ *out = *in
+ if in.PrivateKey != nil {
+ in, out := &in.PrivateKey, &out.PrivateKey
+ *out = new(Valuer)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorSpec.
+func (in *ActorSpec) DeepCopy() *ActorSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ActorSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ActorStatus) DeepCopyInto(out *ActorStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ActorStatus.
+func (in *ActorStatus) DeepCopy() *ActorStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ActorStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BatchSubmitter) DeepCopyInto(out *BatchSubmitter) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchSubmitter.
+func (in *BatchSubmitter) DeepCopy() *BatchSubmitter {
+ if in == nil {
+ return nil
+ }
+ out := new(BatchSubmitter)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *BatchSubmitter) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BatchSubmitterList) DeepCopyInto(out *BatchSubmitterList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]BatchSubmitter, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchSubmitterList.
+func (in *BatchSubmitterList) DeepCopy() *BatchSubmitterList {
+ if in == nil {
+ return nil
+ }
+ out := new(BatchSubmitterList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *BatchSubmitterList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BatchSubmitterSpec) DeepCopyInto(out *BatchSubmitterSpec) {
+ *out = *in
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchSubmitterSpec.
+func (in *BatchSubmitterSpec) DeepCopy() *BatchSubmitterSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(BatchSubmitterSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BatchSubmitterStatus) DeepCopyInto(out *BatchSubmitterStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BatchSubmitterStatus.
+func (in *BatchSubmitterStatus) DeepCopy() *BatchSubmitterStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(BatchSubmitterStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CliqueL1) DeepCopyInto(out *CliqueL1) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CliqueL1.
+func (in *CliqueL1) DeepCopy() *CliqueL1 {
+ if in == nil {
+ return nil
+ }
+ out := new(CliqueL1)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CliqueL1) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CliqueL1List) DeepCopyInto(out *CliqueL1List) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]CliqueL1, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CliqueL1List.
+func (in *CliqueL1List) DeepCopy() *CliqueL1List {
+ if in == nil {
+ return nil
+ }
+ out := new(CliqueL1List)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *CliqueL1List) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CliqueL1Spec) DeepCopyInto(out *CliqueL1Spec) {
+ *out = *in
+ if in.GenesisFile != nil {
+ in, out := &in.GenesisFile, &out.GenesisFile
+ *out = new(Valuer)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.SealerPrivateKey != nil {
+ in, out := &in.SealerPrivateKey, &out.SealerPrivateKey
+ *out = new(Valuer)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.DataPVC != nil {
+ in, out := &in.DataPVC, &out.DataPVC
+ *out = new(PVCConfig)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.AdditionalArgs != nil {
+ in, out := &in.AdditionalArgs, &out.AdditionalArgs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CliqueL1Spec.
+func (in *CliqueL1Spec) DeepCopy() *CliqueL1Spec {
+ if in == nil {
+ return nil
+ }
+ out := new(CliqueL1Spec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CliqueL1Status) DeepCopyInto(out *CliqueL1Status) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CliqueL1Status.
+func (in *CliqueL1Status) DeepCopy() *CliqueL1Status {
+ if in == nil {
+ return nil
+ }
+ out := new(CliqueL1Status)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DataTransportLayer) DeepCopyInto(out *DataTransportLayer) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataTransportLayer.
+func (in *DataTransportLayer) DeepCopy() *DataTransportLayer {
+ if in == nil {
+ return nil
+ }
+ out := new(DataTransportLayer)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *DataTransportLayer) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DataTransportLayerList) DeepCopyInto(out *DataTransportLayerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]DataTransportLayer, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataTransportLayerList.
+func (in *DataTransportLayerList) DeepCopy() *DataTransportLayerList {
+ if in == nil {
+ return nil
+ }
+ out := new(DataTransportLayerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *DataTransportLayerList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DataTransportLayerSpec) DeepCopyInto(out *DataTransportLayerSpec) {
+ *out = *in
+ if in.DataPVC != nil {
+ in, out := &in.DataPVC, &out.DataPVC
+ *out = new(PVCConfig)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataTransportLayerSpec.
+func (in *DataTransportLayerSpec) DeepCopy() *DataTransportLayerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(DataTransportLayerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DataTransportLayerStatus) DeepCopyInto(out *DataTransportLayerStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DataTransportLayerStatus.
+func (in *DataTransportLayerStatus) DeepCopy() *DataTransportLayerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(DataTransportLayerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Deployer) DeepCopyInto(out *Deployer) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Deployer.
+func (in *Deployer) DeepCopy() *Deployer {
+ if in == nil {
+ return nil
+ }
+ out := new(Deployer)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Deployer) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DeployerList) DeepCopyInto(out *DeployerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Deployer, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployerList.
+func (in *DeployerList) DeepCopy() *DeployerList {
+ if in == nil {
+ return nil
+ }
+ out := new(DeployerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *DeployerList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DeployerSpec) DeepCopyInto(out *DeployerSpec) {
+ *out = *in
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployerSpec.
+func (in *DeployerSpec) DeepCopy() *DeployerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(DeployerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *DeployerStatus) DeepCopyInto(out *DeployerStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployerStatus.
+func (in *DeployerStatus) DeepCopy() *DeployerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(DeployerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GasOracle) DeepCopyInto(out *GasOracle) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GasOracle.
+func (in *GasOracle) DeepCopy() *GasOracle {
+ if in == nil {
+ return nil
+ }
+ out := new(GasOracle)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GasOracle) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GasOracleList) DeepCopyInto(out *GasOracleList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]GasOracle, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GasOracleList.
+func (in *GasOracleList) DeepCopy() *GasOracleList {
+ if in == nil {
+ return nil
+ }
+ out := new(GasOracleList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *GasOracleList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GasOracleSpec) DeepCopyInto(out *GasOracleSpec) {
+ *out = *in
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GasOracleSpec.
+func (in *GasOracleSpec) DeepCopy() *GasOracleSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(GasOracleSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *GasOracleStatus) DeepCopyInto(out *GasOracleStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GasOracleStatus.
+func (in *GasOracleStatus) DeepCopy() *GasOracleStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(GasOracleStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *PVCConfig) DeepCopyInto(out *PVCConfig) {
+ *out = *in
+ if in.Storage != nil {
+ in, out := &in.Storage, &out.Storage
+ x := (*in).DeepCopy()
+ *out = &x
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PVCConfig.
+func (in *PVCConfig) DeepCopy() *PVCConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(PVCConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Sequencer) DeepCopyInto(out *Sequencer) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ out.Status = in.Status
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Sequencer.
+func (in *Sequencer) DeepCopy() *Sequencer {
+ if in == nil {
+ return nil
+ }
+ out := new(Sequencer)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *Sequencer) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SequencerList) DeepCopyInto(out *SequencerList) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]Sequencer, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequencerList.
+func (in *SequencerList) DeepCopy() *SequencerList {
+ if in == nil {
+ return nil
+ }
+ out := new(SequencerList)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *SequencerList) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SequencerSpec) DeepCopyInto(out *SequencerSpec) {
+ *out = *in
+ if in.DataPVC != nil {
+ in, out := &in.DataPVC, &out.DataPVC
+ *out = new(PVCConfig)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Env != nil {
+ in, out := &in.Env, &out.Env
+ *out = make([]corev1.EnvVar, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+ if in.AdditionalArgs != nil {
+ in, out := &in.AdditionalArgs, &out.AdditionalArgs
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequencerSpec.
+func (in *SequencerSpec) DeepCopy() *SequencerSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(SequencerSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SequencerStatus) DeepCopyInto(out *SequencerStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SequencerStatus.
+func (in *SequencerStatus) DeepCopy() *SequencerStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(SequencerStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Valuer) DeepCopyInto(out *Valuer) {
+ *out = *in
+ if in.ValueFrom != nil {
+ in, out := &in.ValueFrom, &out.ValueFrom
+ *out = new(corev1.EnvVarSource)
+ (*in).DeepCopyInto(*out)
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Valuer.
+func (in *Valuer) DeepCopy() *Valuer {
+ if in == nil {
+ return nil
+ }
+ out := new(Valuer)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/go/stackman/bin/controller-gen b/go/stackman/bin/controller-gen
deleted file mode 100755
index cc853f3a1f78c..0000000000000
Binary files a/go/stackman/bin/controller-gen and /dev/null differ
diff --git a/go/stackman/bin/manager b/go/stackman/bin/manager
deleted file mode 100755
index e9ae6e7be05d9..0000000000000
Binary files a/go/stackman/bin/manager and /dev/null differ
diff --git a/go/stackman/config/crd/bases/stack.optimism-stacks.net_cliquel1s.yaml b/go/stackman/config/crd/bases/stack.optimism-stacks.net_cliquel1s.yaml
new file mode 100644
index 0000000000000..48e5709f1ab46
--- /dev/null
+++ b/go/stackman/config/crd/bases/stack.optimism-stacks.net_cliquel1s.yaml
@@ -0,0 +1,59 @@
+
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ controller-gen.kubebuilder.io/version: v0.7.0
+ creationTimestamp: null
+ name: cliquel1s.stack.optimism-stacks.net
+spec:
+ group: stack.optimism-stacks.net
+ names:
+ kind: CliqueL1
+ listKind: CliqueL1List
+ plural: cliquel1s
+ singular: cliquel1
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: CliqueL1 is the Schema for the cliquel1s API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: CliqueL1Spec defines the desired state of CliqueL1
+ type: object
+ status:
+ description: CliqueL1Status defines the observed state of CliqueL1
+ properties:
+ pod_names:
+ items:
+ type: string
+ type: array
+ required:
+ - pod_names
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
diff --git a/go/stackman/config/crd/kustomization.yaml b/go/stackman/config/crd/kustomization.yaml
new file mode 100644
index 0000000000000..fc099b3743e7e
--- /dev/null
+++ b/go/stackman/config/crd/kustomization.yaml
@@ -0,0 +1,39 @@
+# This kustomization.yaml is not intended to be run by itself,
+# since it depends on service name and namespace that are out of this kustomize package.
+# It should be run by config/default
+resources:
+- bases/stack.optimism-stacks.net_cliquel1s.yaml
+- bases/stack.optimism-stacks.net_deployers.yaml
+- bases/stack.optimism-stacks.net_datatransportlayers.yaml
+- bases/stack.optimism-stacks.net_sequencers.yaml
+- bases/stack.optimism-stacks.net_batchsubmitters.yaml
+- bases/stack.optimism-stacks.net_gasoracles.yaml
+- bases/stack.optimism-stacks.net_actors.yaml
+#+kubebuilder:scaffold:crdkustomizeresource
+
+patchesStrategicMerge:
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
+# patches here are for enabling the conversion webhook for each CRD
+#- patches/webhook_in_cliquel1s.yaml
+#- patches/webhook_in_deployers.yaml
+#- patches/webhook_in_datatransportlayers.yaml
+#- patches/webhook_in_sequencers.yaml
+#- patches/webhook_in_batchsubmitters.yaml
+#- patches/webhook_in_gasoracles.yaml
+#- patches/webhook_in_actors.yaml
+#+kubebuilder:scaffold:crdkustomizewebhookpatch
+
+# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
+# patches here are for enabling the CA injection for each CRD
+#- patches/cainjection_in_cliquel1s.yaml
+#- patches/cainjection_in_deployers.yaml
+#- patches/cainjection_in_datatransportlayers.yaml
+#- patches/cainjection_in_sequencers.yaml
+#- patches/cainjection_in_batchsubmitters.yaml
+#- patches/cainjection_in_gasoracles.yaml
+#- patches/cainjection_in_actors.yaml
+#+kubebuilder:scaffold:crdkustomizecainjectionpatch
+
+# the following config is for teaching kustomize how to do kustomization for CRDs.
+configurations:
+- kustomizeconfig.yaml
diff --git a/go/stackman/config/crd/kustomizeconfig.yaml b/go/stackman/config/crd/kustomizeconfig.yaml
new file mode 100644
index 0000000000000..ec5c150a9df25
--- /dev/null
+++ b/go/stackman/config/crd/kustomizeconfig.yaml
@@ -0,0 +1,19 @@
+# This file is for teaching kustomize how to substitute name and namespace reference in CRD
+nameReference:
+- kind: Service
+ version: v1
+ fieldSpecs:
+ - kind: CustomResourceDefinition
+ version: v1
+ group: apiextensions.k8s.io
+ path: spec/conversion/webhook/clientConfig/service/name
+
+namespace:
+- kind: CustomResourceDefinition
+ version: v1
+ group: apiextensions.k8s.io
+ path: spec/conversion/webhook/clientConfig/service/namespace
+ create: false
+
+varReference:
+- path: metadata/annotations
diff --git a/go/stackman/config/crd/patches/cainjection_in_actors.yaml b/go/stackman/config/crd/patches/cainjection_in_actors.yaml
new file mode 100644
index 0000000000000..4c3d6d38d590a
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_actors.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: actors.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_batchsubmitters.yaml b/go/stackman/config/crd/patches/cainjection_in_batchsubmitters.yaml
new file mode 100644
index 0000000000000..f415991b39482
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_batchsubmitters.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: batchsubmitters.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_cliquel1s.yaml b/go/stackman/config/crd/patches/cainjection_in_cliquel1s.yaml
new file mode 100644
index 0000000000000..4bec054d051b2
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_cliquel1s.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: cliquel1s.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_datatransportlayers.yaml b/go/stackman/config/crd/patches/cainjection_in_datatransportlayers.yaml
new file mode 100644
index 0000000000000..cc2f0d8a77804
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_datatransportlayers.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: datatransportlayers.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_deployers.yaml b/go/stackman/config/crd/patches/cainjection_in_deployers.yaml
new file mode 100644
index 0000000000000..d7f85e39485f9
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_deployers.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: deployers.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_gasoracles.yaml b/go/stackman/config/crd/patches/cainjection_in_gasoracles.yaml
new file mode 100644
index 0000000000000..a0e86c51e0870
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_gasoracles.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: gasoracles.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/cainjection_in_sequencers.yaml b/go/stackman/config/crd/patches/cainjection_in_sequencers.yaml
new file mode 100644
index 0000000000000..9a7dff0733728
--- /dev/null
+++ b/go/stackman/config/crd/patches/cainjection_in_sequencers.yaml
@@ -0,0 +1,7 @@
+# The following patch adds a directive for certmanager to inject CA into the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
+ name: sequencers.stack.optimism-stacks.net
diff --git a/go/stackman/config/crd/patches/webhook_in_actors.yaml b/go/stackman/config/crd/patches/webhook_in_actors.yaml
new file mode 100644
index 0000000000000..24b358d9bbecc
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_actors.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: actors.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_batchsubmitters.yaml b/go/stackman/config/crd/patches/webhook_in_batchsubmitters.yaml
new file mode 100644
index 0000000000000..e1d84f68384cb
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_batchsubmitters.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: batchsubmitters.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_cliquel1s.yaml b/go/stackman/config/crd/patches/webhook_in_cliquel1s.yaml
new file mode 100644
index 0000000000000..5c62467b7fd8b
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_cliquel1s.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: cliquel1s.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_datatransportlayers.yaml b/go/stackman/config/crd/patches/webhook_in_datatransportlayers.yaml
new file mode 100644
index 0000000000000..76112e42f86a9
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_datatransportlayers.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: datatransportlayers.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_deployers.yaml b/go/stackman/config/crd/patches/webhook_in_deployers.yaml
new file mode 100644
index 0000000000000..101f6c4d4c0d1
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_deployers.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: deployers.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_gasoracles.yaml b/go/stackman/config/crd/patches/webhook_in_gasoracles.yaml
new file mode 100644
index 0000000000000..fd401b21b867e
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_gasoracles.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: gasoracles.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/crd/patches/webhook_in_sequencers.yaml b/go/stackman/config/crd/patches/webhook_in_sequencers.yaml
new file mode 100644
index 0000000000000..7f7a00b1d1264
--- /dev/null
+++ b/go/stackman/config/crd/patches/webhook_in_sequencers.yaml
@@ -0,0 +1,16 @@
+# The following patch enables a conversion webhook for the CRD
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ name: sequencers.stack.optimism-stacks.net
+spec:
+ conversion:
+ strategy: Webhook
+ webhook:
+ clientConfig:
+ service:
+ namespace: system
+ name: webhook-service
+ path: /convert
+ conversionReviewVersions:
+ - v1
diff --git a/go/stackman/config/default/kustomization.yaml b/go/stackman/config/default/kustomization.yaml
new file mode 100644
index 0000000000000..16c3a646024c1
--- /dev/null
+++ b/go/stackman/config/default/kustomization.yaml
@@ -0,0 +1,74 @@
+# Adds namespace to all resources.
+namespace: stackman-system
+
+# Value of this field is prepended to the
+# names of all resources, e.g. a deployment named
+# "wordpress" becomes "alices-wordpress".
+# Note that it should also match with the prefix (text before '-') of the namespace
+# field above.
+namePrefix: stackman-
+
+# Labels to add to all resources and selectors.
+#commonLabels:
+# someName: someValue
+
+bases:
+- ../crd
+- ../rbac
+- ../manager
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
+# crd/kustomization.yaml
+#- ../webhook
+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
+#- ../certmanager
+# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
+- ../prometheus
+
+#patchesStrategicMerge:
+# Protect the /metrics endpoint by putting it behind auth.
+# If you want your controller-manager to expose the /metrics
+# endpoint w/o any authn/z, please comment the following line.
+#- manager_auth_proxy_patch.yaml
+
+# Mount the controller config file for loading manager configurations
+# through a ComponentConfig type
+#- manager_config_patch.yaml
+
+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
+# crd/kustomization.yaml
+#- manager_webhook_patch.yaml
+
+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
+# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
+# 'CERTMANAGER' needs to be enabled to use ca injection
+#- webhookcainjection_patch.yaml
+
+# the following config is for teaching kustomize how to do var substitution
+vars:
+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
+#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
+# objref:
+# kind: Certificate
+# group: cert-manager.io
+# version: v1
+# name: serving-cert # this name should match the one in certificate.yaml
+# fieldref:
+# fieldpath: metadata.namespace
+#- name: CERTIFICATE_NAME
+# objref:
+# kind: Certificate
+# group: cert-manager.io
+# version: v1
+# name: serving-cert # this name should match the one in certificate.yaml
+#- name: SERVICE_NAMESPACE # namespace of the service
+# objref:
+# kind: Service
+# version: v1
+# name: webhook-service
+# fieldref:
+# fieldpath: metadata.namespace
+#- name: SERVICE_NAME
+# objref:
+# kind: Service
+# version: v1
+# name: webhook-service
diff --git a/go/stackman/config/default/manager_auth_proxy_patch.yaml b/go/stackman/config/default/manager_auth_proxy_patch.yaml
new file mode 100644
index 0000000000000..4e2232fa1a935
--- /dev/null
+++ b/go/stackman/config/default/manager_auth_proxy_patch.yaml
@@ -0,0 +1,27 @@
+# This patch inject a sidecar container which is a HTTP proxy for the
+# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: controller-manager
+ namespace: system
+spec:
+ template:
+ spec:
+ containers:
+ - name: kube-rbac-proxy
+ image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0
+ args:
+ - "--secure-listen-address=0.0.0.0:8443"
+ - "--upstream=http://127.0.0.1:8080/"
+ - "--logtostderr=true"
+ - "--v=10"
+ ports:
+ - containerPort: 8443
+ protocol: TCP
+ name: https
+ - name: manager
+ args:
+ - "--health-probe-bind-address=:8081"
+ - "--metrics-bind-address=127.0.0.1:8080"
+ - "--leader-elect"
diff --git a/go/stackman/config/default/manager_config_patch.yaml b/go/stackman/config/default/manager_config_patch.yaml
new file mode 100644
index 0000000000000..6c400155cfbc8
--- /dev/null
+++ b/go/stackman/config/default/manager_config_patch.yaml
@@ -0,0 +1,20 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: controller-manager
+ namespace: system
+spec:
+ template:
+ spec:
+ containers:
+ - name: manager
+ args:
+ - "--config=controller_manager_config.yaml"
+ volumeMounts:
+ - name: manager-config
+ mountPath: /controller_manager_config.yaml
+ subPath: controller_manager_config.yaml
+ volumes:
+ - name: manager-config
+ configMap:
+ name: manager-config
diff --git a/go/stackman/config/manager/controller_manager_config.yaml b/go/stackman/config/manager/controller_manager_config.yaml
new file mode 100644
index 0000000000000..0c0a6b3832988
--- /dev/null
+++ b/go/stackman/config/manager/controller_manager_config.yaml
@@ -0,0 +1,11 @@
+apiVersion: controller-runtime.sigs.k8s.io/v1alpha1
+kind: ControllerManagerConfig
+health:
+ healthProbeBindAddress: :8081
+metrics:
+ bindAddress: 127.0.0.1:8080
+webhook:
+ port: 9443
+leaderElection:
+ leaderElect: true
+ resourceName: 8103f40b.optimism-stacks.net
diff --git a/go/stackman/config/manager/kustomization.yaml b/go/stackman/config/manager/kustomization.yaml
new file mode 100644
index 0000000000000..5e793dd19fcd6
--- /dev/null
+++ b/go/stackman/config/manager/kustomization.yaml
@@ -0,0 +1,16 @@
+resources:
+- manager.yaml
+
+generatorOptions:
+ disableNameSuffixHash: true
+
+configMapGenerator:
+- files:
+ - controller_manager_config.yaml
+ name: manager-config
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+images:
+- name: controller
+ newName: controller
+ newTag: latest
diff --git a/go/stackman/config/manager/manager.yaml b/go/stackman/config/manager/manager.yaml
new file mode 100644
index 0000000000000..202624427c27d
--- /dev/null
+++ b/go/stackman/config/manager/manager.yaml
@@ -0,0 +1,56 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ labels:
+ control-plane: controller-manager
+ name: system
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: controller-manager
+ namespace: system
+ labels:
+ control-plane: controller-manager
+spec:
+ selector:
+ matchLabels:
+ control-plane: controller-manager
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ control-plane: controller-manager
+ spec:
+ securityContext:
+ runAsNonRoot: true
+ containers:
+ - command:
+ - /manager
+ args:
+ - --leader-elect
+ image: controller:latest
+ name: manager
+ securityContext:
+ allowPrivilegeEscalation: false
+ livenessProbe:
+ httpGet:
+ path: /healthz
+ port: 8081
+ initialDelaySeconds: 15
+ periodSeconds: 20
+ readinessProbe:
+ httpGet:
+ path: /readyz
+ port: 8081
+ initialDelaySeconds: 5
+ periodSeconds: 10
+ resources:
+ limits:
+ cpu: 200m
+ memory: 100Mi
+ requests:
+ cpu: 100m
+ memory: 20Mi
+ serviceAccountName: controller-manager
+ terminationGracePeriodSeconds: 10
diff --git a/go/stackman/config/manifests/kustomization.yaml b/go/stackman/config/manifests/kustomization.yaml
new file mode 100644
index 0000000000000..de8f30bab0831
--- /dev/null
+++ b/go/stackman/config/manifests/kustomization.yaml
@@ -0,0 +1,27 @@
+# These resources constitute the fully configured set of manifests
+# used to generate the 'manifests/' directory in a bundle.
+resources:
+- bases/stackman.clusterserviceversion.yaml
+- ../default
+- ../samples
+- ../scorecard
+
+# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix.
+# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager.
+# These patches remove the unnecessary "cert" volume and its manager container volumeMount.
+#patchesJson6902:
+#- target:
+# group: apps
+# version: v1
+# kind: Deployment
+# name: controller-manager
+# namespace: system
+# patch: |-
+# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs.
+# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment.
+# - op: remove
+# path: /spec/template/spec/containers/1/volumeMounts/0
+# # Remove the "cert" volume, since OLM will create and mount a set of certs.
+# # Update the indices in this path if adding or removing volumes in the manager's Deployment.
+# - op: remove
+# path: /spec/template/spec/volumes/0
diff --git a/go/stackman/config/prometheus/kustomization.yaml b/go/stackman/config/prometheus/kustomization.yaml
new file mode 100644
index 0000000000000..ed137168a1dbc
--- /dev/null
+++ b/go/stackman/config/prometheus/kustomization.yaml
@@ -0,0 +1,2 @@
+resources:
+- monitor.yaml
diff --git a/go/stackman/config/prometheus/monitor.yaml b/go/stackman/config/prometheus/monitor.yaml
new file mode 100644
index 0000000000000..d19136ae71019
--- /dev/null
+++ b/go/stackman/config/prometheus/monitor.yaml
@@ -0,0 +1,20 @@
+
+# Prometheus Monitor Service (Metrics)
+apiVersion: monitoring.coreos.com/v1
+kind: ServiceMonitor
+metadata:
+ labels:
+ control-plane: controller-manager
+ name: controller-manager-metrics-monitor
+ namespace: system
+spec:
+ endpoints:
+ - path: /metrics
+ port: https
+ scheme: https
+ bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
+ tlsConfig:
+ insecureSkipVerify: true
+ selector:
+ matchLabels:
+ control-plane: controller-manager
diff --git a/go/stackman/config/rbac/actor_editor_role.yaml b/go/stackman/config/rbac/actor_editor_role.yaml
new file mode 100644
index 0000000000000..94eaa0684e1c0
--- /dev/null
+++ b/go/stackman/config/rbac/actor_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit actors.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: actor-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - actors
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - actors/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/actor_viewer_role.yaml b/go/stackman/config/rbac/actor_viewer_role.yaml
new file mode 100644
index 0000000000000..bd7924aae0852
--- /dev/null
+++ b/go/stackman/config/rbac/actor_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view actors.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: actor-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - actors
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - actors/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/auth_proxy_client_clusterrole.yaml b/go/stackman/config/rbac/auth_proxy_client_clusterrole.yaml
new file mode 100644
index 0000000000000..51a75db47a5b3
--- /dev/null
+++ b/go/stackman/config/rbac/auth_proxy_client_clusterrole.yaml
@@ -0,0 +1,9 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: metrics-reader
+rules:
+- nonResourceURLs:
+ - "/metrics"
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/auth_proxy_role.yaml b/go/stackman/config/rbac/auth_proxy_role.yaml
new file mode 100644
index 0000000000000..80e1857c594f8
--- /dev/null
+++ b/go/stackman/config/rbac/auth_proxy_role.yaml
@@ -0,0 +1,17 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: proxy-role
+rules:
+- apiGroups:
+ - authentication.k8s.io
+ resources:
+ - tokenreviews
+ verbs:
+ - create
+- apiGroups:
+ - authorization.k8s.io
+ resources:
+ - subjectaccessreviews
+ verbs:
+ - create
diff --git a/go/stackman/config/rbac/auth_proxy_role_binding.yaml b/go/stackman/config/rbac/auth_proxy_role_binding.yaml
new file mode 100644
index 0000000000000..ec7acc0a1b79c
--- /dev/null
+++ b/go/stackman/config/rbac/auth_proxy_role_binding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: proxy-rolebinding
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: proxy-role
+subjects:
+- kind: ServiceAccount
+ name: controller-manager
+ namespace: system
diff --git a/go/stackman/config/rbac/auth_proxy_service.yaml b/go/stackman/config/rbac/auth_proxy_service.yaml
new file mode 100644
index 0000000000000..71f1797279e56
--- /dev/null
+++ b/go/stackman/config/rbac/auth_proxy_service.yaml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ labels:
+ control-plane: controller-manager
+ name: controller-manager-metrics-service
+ namespace: system
+spec:
+ ports:
+ - name: https
+ port: 8443
+ protocol: TCP
+ targetPort: https
+ selector:
+ control-plane: controller-manager
diff --git a/go/stackman/config/rbac/batchsubmitter_editor_role.yaml b/go/stackman/config/rbac/batchsubmitter_editor_role.yaml
new file mode 100644
index 0000000000000..a5f374298bbe5
--- /dev/null
+++ b/go/stackman/config/rbac/batchsubmitter_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit batchsubmitters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: batchsubmitter-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - batchsubmitters
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - batchsubmitters/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/batchsubmitter_viewer_role.yaml b/go/stackman/config/rbac/batchsubmitter_viewer_role.yaml
new file mode 100644
index 0000000000000..ad073ced23669
--- /dev/null
+++ b/go/stackman/config/rbac/batchsubmitter_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view batchsubmitters.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: batchsubmitter-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - batchsubmitters
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - batchsubmitters/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/cliquel1_editor_role.yaml b/go/stackman/config/rbac/cliquel1_editor_role.yaml
new file mode 100644
index 0000000000000..265c340701ae2
--- /dev/null
+++ b/go/stackman/config/rbac/cliquel1_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit cliquel1s.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: cliquel1-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/cliquel1_viewer_role.yaml b/go/stackman/config/rbac/cliquel1_viewer_role.yaml
new file mode 100644
index 0000000000000..d85a5c77c44c6
--- /dev/null
+++ b/go/stackman/config/rbac/cliquel1_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view cliquel1s.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: cliquel1-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/datatransportlayer_editor_role.yaml b/go/stackman/config/rbac/datatransportlayer_editor_role.yaml
new file mode 100644
index 0000000000000..1e9e83f60cda7
--- /dev/null
+++ b/go/stackman/config/rbac/datatransportlayer_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit datatransportlayers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: datatransportlayer-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - datatransportlayers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - datatransportlayers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/datatransportlayer_viewer_role.yaml b/go/stackman/config/rbac/datatransportlayer_viewer_role.yaml
new file mode 100644
index 0000000000000..7908c2f3801ea
--- /dev/null
+++ b/go/stackman/config/rbac/datatransportlayer_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view datatransportlayers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: datatransportlayer-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - datatransportlayers
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - datatransportlayers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/deployer_editor_role.yaml b/go/stackman/config/rbac/deployer_editor_role.yaml
new file mode 100644
index 0000000000000..12b58a38a6b65
--- /dev/null
+++ b/go/stackman/config/rbac/deployer_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit deployers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: deployer-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - deployers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - deployers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/deployer_viewer_role.yaml b/go/stackman/config/rbac/deployer_viewer_role.yaml
new file mode 100644
index 0000000000000..7e42ea2b8a4fe
--- /dev/null
+++ b/go/stackman/config/rbac/deployer_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view deployers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: deployer-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - deployers
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - deployers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/gasoracle_editor_role.yaml b/go/stackman/config/rbac/gasoracle_editor_role.yaml
new file mode 100644
index 0000000000000..6de6b2b12d187
--- /dev/null
+++ b/go/stackman/config/rbac/gasoracle_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit gasoracles.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: gasoracle-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - gasoracles
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - gasoracles/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/gasoracle_viewer_role.yaml b/go/stackman/config/rbac/gasoracle_viewer_role.yaml
new file mode 100644
index 0000000000000..26761d97c70a2
--- /dev/null
+++ b/go/stackman/config/rbac/gasoracle_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view gasoracles.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: gasoracle-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - gasoracles
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - gasoracles/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/kustomization.yaml b/go/stackman/config/rbac/kustomization.yaml
new file mode 100644
index 0000000000000..731832a6ac3b6
--- /dev/null
+++ b/go/stackman/config/rbac/kustomization.yaml
@@ -0,0 +1,18 @@
+resources:
+# All RBAC will be applied under this service account in
+# the deployment namespace. You may comment out this resource
+# if your manager will use a service account that exists at
+# runtime. Be sure to update RoleBinding and ClusterRoleBinding
+# subjects if changing service account names.
+- service_account.yaml
+- role.yaml
+- role_binding.yaml
+- leader_election_role.yaml
+- leader_election_role_binding.yaml
+# Comment the following 4 lines if you want to disable
+# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
+# which protects your /metrics endpoint.
+- auth_proxy_service.yaml
+- auth_proxy_role.yaml
+- auth_proxy_role_binding.yaml
+- auth_proxy_client_clusterrole.yaml
diff --git a/go/stackman/config/rbac/leader_election_role.yaml b/go/stackman/config/rbac/leader_election_role.yaml
new file mode 100644
index 0000000000000..4190ec8059e2c
--- /dev/null
+++ b/go/stackman/config/rbac/leader_election_role.yaml
@@ -0,0 +1,37 @@
+# permissions to do leader election.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: leader-election-role
+rules:
+- apiGroups:
+ - ""
+ resources:
+ - configmaps
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+- apiGroups:
+ - coordination.k8s.io
+ resources:
+ - leases
+ verbs:
+ - get
+ - list
+ - watch
+ - create
+ - update
+ - patch
+ - delete
+- apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+ - patch
diff --git a/go/stackman/config/rbac/leader_election_role_binding.yaml b/go/stackman/config/rbac/leader_election_role_binding.yaml
new file mode 100644
index 0000000000000..1d1321ed4f020
--- /dev/null
+++ b/go/stackman/config/rbac/leader_election_role_binding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: leader-election-rolebinding
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: Role
+ name: leader-election-role
+subjects:
+- kind: ServiceAccount
+ name: controller-manager
+ namespace: system
diff --git a/go/stackman/config/rbac/role.yaml b/go/stackman/config/rbac/role.yaml
new file mode 100644
index 0000000000000..7fa662095761b
--- /dev/null
+++ b/go/stackman/config/rbac/role.yaml
@@ -0,0 +1,60 @@
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ creationTimestamp: null
+ name: manager-role
+rules:
+- apiGroups:
+ - apps
+ resources:
+ - deployments
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - ""
+ resources:
+ - configmaps
+ - pods
+ - services
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s/finalizers
+ verbs:
+ - update
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - cliquel1s/status
+ verbs:
+ - get
+ - patch
+ - update
diff --git a/go/stackman/config/rbac/role_binding.yaml b/go/stackman/config/rbac/role_binding.yaml
new file mode 100644
index 0000000000000..2070ede4462f1
--- /dev/null
+++ b/go/stackman/config/rbac/role_binding.yaml
@@ -0,0 +1,12 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: manager-rolebinding
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: manager-role
+subjects:
+- kind: ServiceAccount
+ name: controller-manager
+ namespace: system
diff --git a/go/stackman/config/rbac/sequencer_editor_role.yaml b/go/stackman/config/rbac/sequencer_editor_role.yaml
new file mode 100644
index 0000000000000..cb44de4caf782
--- /dev/null
+++ b/go/stackman/config/rbac/sequencer_editor_role.yaml
@@ -0,0 +1,24 @@
+# permissions for end users to edit sequencers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: sequencer-editor-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - sequencers
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - sequencers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/sequencer_viewer_role.yaml b/go/stackman/config/rbac/sequencer_viewer_role.yaml
new file mode 100644
index 0000000000000..e4c9f715a13c2
--- /dev/null
+++ b/go/stackman/config/rbac/sequencer_viewer_role.yaml
@@ -0,0 +1,20 @@
+# permissions for end users to view sequencers.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: sequencer-viewer-role
+rules:
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - sequencers
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - stack.optimism-stacks.net
+ resources:
+ - sequencers/status
+ verbs:
+ - get
diff --git a/go/stackman/config/rbac/service_account.yaml b/go/stackman/config/rbac/service_account.yaml
new file mode 100644
index 0000000000000..7cd6025bfc4af
--- /dev/null
+++ b/go/stackman/config/rbac/service_account.yaml
@@ -0,0 +1,5 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: controller-manager
+ namespace: system
diff --git a/go/stackman/config/samples/kustomization.yaml b/go/stackman/config/samples/kustomization.yaml
new file mode 100644
index 0000000000000..db434677c46fd
--- /dev/null
+++ b/go/stackman/config/samples/kustomization.yaml
@@ -0,0 +1,10 @@
+## Append samples you want in your CSV to this file as resources ##
+resources:
+- stack_v1_cliquel1.yaml
+- stack_v1_deployer.yaml
+- stack_v1_datatransportlayer.yaml
+- stack_v1_sequencer.yaml
+- stack_v1_batchsubmitter.yaml
+- stack_v1_gasoracle.yaml
+- stack_v1_actor.yaml
+#+kubebuilder:scaffold:manifestskustomizesamples
diff --git a/go/stackman/config/samples/stack_v1_actor.yaml b/go/stackman/config/samples/stack_v1_actor.yaml
new file mode 100644
index 0000000000000..d8483ba57835d
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_actor.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: Actor
+metadata:
+ name: actor-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_batchsubmitter.yaml b/go/stackman/config/samples/stack_v1_batchsubmitter.yaml
new file mode 100644
index 0000000000000..a000265651a37
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_batchsubmitter.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: BatchSubmitter
+metadata:
+ name: batchsubmitter-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_cliquel1.yaml b/go/stackman/config/samples/stack_v1_cliquel1.yaml
new file mode 100644
index 0000000000000..0bf9135e6a335
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_cliquel1.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: CliqueL1
+metadata:
+ name: cliquel1-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_datatransportlayer.yaml b/go/stackman/config/samples/stack_v1_datatransportlayer.yaml
new file mode 100644
index 0000000000000..5d2a61c7613b8
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_datatransportlayer.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: DataTransportLayer
+metadata:
+ name: datatransportlayer-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_deployer.yaml b/go/stackman/config/samples/stack_v1_deployer.yaml
new file mode 100644
index 0000000000000..96aa2e1b70714
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_deployer.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: Deployer
+metadata:
+ name: deployer-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_gasoracle.yaml b/go/stackman/config/samples/stack_v1_gasoracle.yaml
new file mode 100644
index 0000000000000..d4f2af31135a8
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_gasoracle.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: GasOracle
+metadata:
+ name: gasoracle-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/samples/stack_v1_sequencer.yaml b/go/stackman/config/samples/stack_v1_sequencer.yaml
new file mode 100644
index 0000000000000..622ff1f93ba2b
--- /dev/null
+++ b/go/stackman/config/samples/stack_v1_sequencer.yaml
@@ -0,0 +1,6 @@
+apiVersion: stack.optimism-stacks.net/v1
+kind: Sequencer
+metadata:
+ name: sequencer-sample
+spec:
+ # Add fields here
diff --git a/go/stackman/config/scorecard/bases/config.yaml b/go/stackman/config/scorecard/bases/config.yaml
new file mode 100644
index 0000000000000..c77047841ed8c
--- /dev/null
+++ b/go/stackman/config/scorecard/bases/config.yaml
@@ -0,0 +1,7 @@
+apiVersion: scorecard.operatorframework.io/v1alpha3
+kind: Configuration
+metadata:
+ name: config
+stages:
+- parallel: true
+ tests: []
diff --git a/go/stackman/config/scorecard/kustomization.yaml b/go/stackman/config/scorecard/kustomization.yaml
new file mode 100644
index 0000000000000..50cd2d084eb9a
--- /dev/null
+++ b/go/stackman/config/scorecard/kustomization.yaml
@@ -0,0 +1,16 @@
+resources:
+- bases/config.yaml
+patchesJson6902:
+- path: patches/basic.config.yaml
+ target:
+ group: scorecard.operatorframework.io
+ version: v1alpha3
+ kind: Configuration
+ name: config
+- path: patches/olm.config.yaml
+ target:
+ group: scorecard.operatorframework.io
+ version: v1alpha3
+ kind: Configuration
+ name: config
+#+kubebuilder:scaffold:patchesJson6902
diff --git a/go/stackman/config/scorecard/patches/basic.config.yaml b/go/stackman/config/scorecard/patches/basic.config.yaml
new file mode 100644
index 0000000000000..c19af06f96d92
--- /dev/null
+++ b/go/stackman/config/scorecard/patches/basic.config.yaml
@@ -0,0 +1,10 @@
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - basic-check-spec
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: basic
+ test: basic-check-spec-test
diff --git a/go/stackman/config/scorecard/patches/olm.config.yaml b/go/stackman/config/scorecard/patches/olm.config.yaml
new file mode 100644
index 0000000000000..0e4888c965029
--- /dev/null
+++ b/go/stackman/config/scorecard/patches/olm.config.yaml
@@ -0,0 +1,50 @@
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - olm-bundle-validation
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: olm
+ test: olm-bundle-validation-test
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - olm-crds-have-validation
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: olm
+ test: olm-crds-have-validation-test
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - olm-crds-have-resources
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: olm
+ test: olm-crds-have-resources-test
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - olm-spec-descriptors
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: olm
+ test: olm-spec-descriptors-test
+- op: add
+ path: /stages/0/tests/-
+ value:
+ entrypoint:
+ - scorecard-test
+ - olm-status-descriptors
+ image: quay.io/operator-framework/scorecard-test:v1.15.0
+ labels:
+ suite: olm
+ test: olm-status-descriptors-test
diff --git a/go/stackman/controllers/actor_controller.go b/go/stackman/controllers/actor_controller.go
new file mode 100644
index 0000000000000..9825b215d7bde
--- /dev/null
+++ b/go/stackman/controllers/actor_controller.go
@@ -0,0 +1,231 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "strconv"
+ "time"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// ActorReconciler reconciles a Actor object
+type ActorReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=actors,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=actors/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=actors/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the Actor object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *ActorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.Actor{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("actor resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting actor")
+ return ctrl.Result{}, err
+ }
+
+ deployment := &appsv1.Deployment{}
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.deployment(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "actor"), deployment)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.argsHash(crd)
+ if deployment.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.deployment(crd))
+ if err != nil {
+ lgr.Error(err, "error updating actor deployment")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "actor"), &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *ActorReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.Actor{}).
+ Complete(r)
+}
+
+func (r *ActorReconciler) labels(crd *stackv1.Actor) map[string]string {
+ return map[string]string{
+ "actor": crd.ObjectMeta.Name,
+ }
+}
+
+func (r *ActorReconciler) argsHash(crd *stackv1.Actor) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(crd.Spec.L2URL))
+ h.Write([]byte(crd.Spec.PrivateKey.String()))
+ h.Write([]byte(crd.Spec.AddressManagerAddress))
+ h.Write([]byte(crd.Spec.TestFilename))
+ h.Write([]byte(strconv.Itoa(crd.Spec.Concurrency)))
+ h.Write([]byte(strconv.Itoa(crd.Spec.RunForMS)))
+ h.Write([]byte(strconv.Itoa(crd.Spec.RunCount)))
+ h.Write([]byte(strconv.Itoa(crd.Spec.ThinkTimeMS)))
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (r *ActorReconciler) deployment(crd *stackv1.Actor) *appsv1.Deployment {
+ replicas := int32(1)
+ command := []string{
+ "yarn",
+ "test:actor",
+ "-f",
+ crd.Spec.TestFilename,
+ "-c",
+ strconv.Itoa(crd.Spec.Concurrency),
+ "--serve",
+ }
+ if crd.Spec.RunForMS != 0 {
+ command = append(command, "-t")
+ command = append(command, strconv.Itoa(crd.Spec.RunForMS))
+ } else if crd.Spec.RunCount != 0 {
+ command = append(command, "-r")
+ command = append(command, strconv.Itoa(crd.Spec.RunCount))
+ }
+ if crd.Spec.ThinkTimeMS != 0 {
+ command = append(command, "--think-time")
+ command = append(command, strconv.Itoa(crd.Spec.ThinkTimeMS))
+ }
+ env := append([]corev1.EnvVar{
+ crd.Spec.PrivateKey.EnvVar("PRIVATE_KEY"),
+ {
+ Name: "L1_URL",
+ Value: crd.Spec.L1URL,
+ },
+ {
+ Name: "L2_URL",
+ Value: crd.Spec.L2URL,
+ },
+ {
+ Name: "IS_LIVE_NETWORK",
+ Value: "true",
+ },
+ {
+ Name: "ADDRESS_MANAGER",
+ Value: crd.Spec.AddressManagerAddress,
+ },
+ }, crd.Spec.Env...)
+ deployment := &appsv1.Deployment{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "actor", map[string]string{
+ "args_hash": r.argsHash(crd),
+ "actor": crd.ObjectMeta.Name,
+ }),
+ Spec: appsv1.DeploymentSpec{
+ Replicas: &replicas,
+ Selector: &metav1.LabelSelector{
+ MatchLabels: r.labels(crd),
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: r.labels(crd),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ Containers: []corev1.Container{
+ {
+ Name: "actor",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ WorkingDir: "/opt/optimism/integration-tests",
+ Command: command,
+ Env: env,
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "metrics",
+ ContainerPort: 8545,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, deployment, r.Scheme)
+ return deployment
+}
+
+func (r *ActorReconciler) service(crd *stackv1.Actor) *corev1.Service {
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "actor", r.labels(crd)),
+ Spec: corev1.ServiceSpec{
+ Selector: r.labels(crd),
+ Type: corev1.ServiceTypeClusterIP,
+ Ports: []corev1.ServicePort{
+ {
+ Name: "metrics",
+ Port: 8545,
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
diff --git a/go/stackman/controllers/batchsubmitter_controller.go b/go/stackman/controllers/batchsubmitter_controller.go
new file mode 100644
index 0000000000000..e8abeca5c742e
--- /dev/null
+++ b/go/stackman/controllers/batchsubmitter_controller.go
@@ -0,0 +1,319 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "strconv"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// BatchSubmitterReconciler reconciles a BatchSubmitter object
+type BatchSubmitterReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=batchsubmitters,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=batchsubmitters/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=batchsubmitters/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the BatchSubmitter object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *BatchSubmitterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.BatchSubmitter{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("batch submitter resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting batch submitter")
+ return ctrl.Result{}, err
+ }
+
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.entrypointsCfgMap(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "batch-submitter-entrypoints"), &corev1.ConfigMap{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ statefulSet := &appsv1.StatefulSet{}
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.statefulSet(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "batch-submitter"), statefulSet)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.statefulSetArgsHash(crd)
+ if statefulSet.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.statefulSet(crd))
+ if err != nil {
+ lgr.Error(err, "error updating batch submitter statefulSet")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "batch-submitter"), &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *BatchSubmitterReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.BatchSubmitter{}).
+ Complete(r)
+}
+
+func (r *BatchSubmitterReconciler) labels() map[string]string {
+ return map[string]string{
+ "app": "batch-submitter",
+ }
+}
+
+func (r *BatchSubmitterReconciler) entrypointsCfgMap(crd *stackv1.BatchSubmitter) *corev1.ConfigMap {
+ cfgMap := &corev1.ConfigMap{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "batch-submitter-entrypoints", r.labels()),
+ Data: map[string]string{
+ "entrypoint.sh": BatchSubmitterEntrypoint,
+ },
+ }
+ ctrl.SetControllerReference(crd, cfgMap, r.Scheme)
+ return cfgMap
+}
+
+func (r *BatchSubmitterReconciler) statefulSetArgsHash(crd *stackv1.BatchSubmitter) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L1TimeoutSeconds)))
+ h.Write([]byte(crd.Spec.L2URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L2TimeoutSeconds)))
+ h.Write([]byte(crd.Spec.DeployerURL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.DeployerTimeoutSeconds)))
+ for _, ev := range crd.Spec.Env {
+ h.Write([]byte(ev.String()))
+ }
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (r *BatchSubmitterReconciler) statefulSet(crd *stackv1.BatchSubmitter) *appsv1.StatefulSet {
+ replicas := int32(1)
+ defaultMode := int32(0o777)
+ labels := r.labels()
+ labels["args_hash"] = r.statefulSetArgsHash(crd)
+ initContainers := []corev1.Container{
+ {
+ Name: "wait-for-l1",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L1URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L1TimeoutSeconds),
+ },
+ },
+ {
+ Name: "wait-for-l2",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L2URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L2TimeoutSeconds),
+ },
+ },
+ }
+ baseEnv := []corev1.EnvVar{
+ {
+ Name: "L1_NODE_WEB3_URL",
+ Value: crd.Spec.L1URL,
+ },
+ {
+ Name: "L2_NODE_WEB3_URL",
+ Value: crd.Spec.L2URL,
+ },
+ {
+ Name: "RUN_METRICS_SERVER",
+ Value: "true",
+ },
+ {
+ Name: "METRICS_HOSTNAME",
+ Value: "0.0.0.0",
+ },
+ {
+ Name: "METRICS_PORT",
+ Value: "9090",
+ },
+ }
+ if crd.Spec.DeployerURL != "" {
+ initContainers = append(initContainers, corev1.Container{
+ Name: "wait-for-deployer",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.DeployerURL),
+ "-t",
+ strconv.Itoa(crd.Spec.DeployerTimeoutSeconds),
+ },
+ })
+ baseEnv = append(baseEnv, []corev1.EnvVar{
+ {
+ Name: "ROLLUP_STATE_DUMP_PATH",
+ Value: "http://deployer:8081/state-dump.latest.json",
+ },
+ {
+ Name: "DEPLOYER_URL",
+ Value: crd.Spec.DeployerURL,
+ },
+ }...)
+ }
+ statefulSet := &appsv1.StatefulSet{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "batch-submitter", labels),
+ Spec: appsv1.StatefulSetSpec{
+ Replicas: &replicas,
+ Selector: &v1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "batch-submitter",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: v1.ObjectMeta{
+ Labels: r.labels(),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ InitContainers: initContainers,
+ Containers: []corev1.Container{
+ {
+ Name: "batch-submitter",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ WorkingDir: "/opt/optimism/packages/batch-submitter",
+ Command: []string{
+ "/bin/sh",
+ "/opt/entrypoints/entrypoint.sh",
+ "npm",
+ "run",
+ "start",
+ },
+ Env: append(baseEnv, crd.Spec.Env...),
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: "entrypoints",
+ MountPath: "/opt/entrypoints",
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "metrics",
+ ContainerPort: 9090,
+ },
+ },
+ },
+ },
+ Volumes: []corev1.Volume{
+ {
+ Name: "entrypoints",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: ObjectName(crd.ObjectMeta, "batch-submitter-entrypoints"),
+ },
+ DefaultMode: &defaultMode,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, statefulSet, r.Scheme)
+ return statefulSet
+}
+
+func (r *BatchSubmitterReconciler) service(crd *stackv1.BatchSubmitter) *corev1.Service {
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "batch-submitter", r.labels()),
+ Spec: corev1.ServiceSpec{
+ Selector: map[string]string{
+ "app": "batch-submitter",
+ },
+ Ports: []corev1.ServicePort{
+ {
+ Name: "metrics",
+ Port: 9090,
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
+
+const BatchSubmitterEntrypoint = `
+#!/bin/sh
+
+if [ -n "$DEPLOYER_URL" ]; then
+ echo "Loading addresses from $DEPLOYER_URL."
+ ADDRESSES=$(curl --fail --show-error --silent "$DEPLOYER_URL/addresses.json")
+ export ADDRESS_MANAGER_ADDRESS=$(echo $ADDRESSES | jq -r ".AddressManager")
+fi
+
+exec "$@"
+`
diff --git a/go/stackman/controllers/cliquel1_controller.go b/go/stackman/controllers/cliquel1_controller.go
new file mode 100644
index 0000000000000..74ab6040a2211
--- /dev/null
+++ b/go/stackman/controllers/cliquel1_controller.go
@@ -0,0 +1,330 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+ "strconv"
+
+ "k8s.io/apimachinery/pkg/api/errors"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// CliqueL1Reconciler reconciles a CliqueL1 object
+type CliqueL1Reconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=cliquel1s,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=cliquel1s/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=cliquel1s/finalizers,verbs=update
+//+kubebuilder:rbac:groups=apps,resources=deployments;statefulsets,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=core,resources=services;pods;configmaps,verbs=get;list;watch;create;update;patch;delete
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the CliqueL1 object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *CliqueL1Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.CliqueL1{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("clique resource, not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting clique")
+ return ctrl.Result{}, err
+ }
+
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.entrypointsCfgMap(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "clique-entrypoints"), &corev1.ConfigMap{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ nsName := ObjectNamespacedName(crd.ObjectMeta, "clique")
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.statefulSet(crd)
+ }, nsName, &appsv1.StatefulSet{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, nsName, &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *CliqueL1Reconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.CliqueL1{}).
+ Owns(&corev1.ConfigMap{}).
+ Owns(&appsv1.Deployment{}).
+ Owns(&corev1.Service{}).
+ Complete(r)
+}
+
+func (r *CliqueL1Reconciler) entrypointsCfgMap(crd *stackv1.CliqueL1) *corev1.ConfigMap {
+ cfgMap := &corev1.ConfigMap{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "clique-entrypoints", map[string]string{
+ "app": "clique",
+ }),
+ Data: map[string]string{
+ "entrypoint.sh": CliqueEntrypoint,
+ },
+ }
+ ctrl.SetControllerReference(crd, cfgMap, r.Scheme)
+ return cfgMap
+}
+
+func (r *CliqueL1Reconciler) statefulSet(crd *stackv1.CliqueL1) *appsv1.StatefulSet {
+ replicas := int32(1)
+ labels := map[string]string{
+ "app": "clique",
+ }
+ defaultMode := int32(0o777)
+ volumes := []corev1.Volume{
+ {
+ Name: "entrypoints",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: ObjectName(crd.ObjectMeta, "clique-entrypoints"),
+ },
+ DefaultMode: &defaultMode,
+ },
+ },
+ },
+ }
+ var volumeClaimTemplates []corev1.PersistentVolumeClaim
+ dbVolumeName := "db"
+ if crd.Spec.DataPVC != nil {
+ storage := resource.MustParse("128Gi")
+ dbVolumeName = crd.Spec.DataPVC.Name
+ if crd.Spec.DataPVC.Storage != nil {
+ storage = *crd.Spec.DataPVC.Storage
+ }
+ volumeClaimTemplates = []corev1.PersistentVolumeClaim{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: crd.Spec.DataPVC.Name,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{
+ corev1.ReadWriteOnce,
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceStorage: storage,
+ },
+ },
+ },
+ },
+ }
+ } else {
+ volumes = append(volumes, corev1.Volume{
+ Name: dbVolumeName,
+ VolumeSource: corev1.VolumeSource{
+ EmptyDir: &corev1.EmptyDirVolumeSource{},
+ },
+ })
+ }
+ statefulSet := &appsv1.StatefulSet{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "clique", labels),
+ Spec: appsv1.StatefulSetSpec{
+ Replicas: &replicas,
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "clique",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: labels,
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ Containers: []corev1.Container{
+ {
+ Name: "geth",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ Command: append([]string{
+ "/bin/sh",
+ "/opt/entrypoints/entrypoint.sh",
+ }, crd.Spec.AdditionalArgs...),
+ Env: []corev1.EnvVar{
+ crd.Spec.SealerPrivateKey.EnvVar("BLOCK_SIGNER_PRIVATE_KEY"),
+ crd.Spec.GenesisFile.EnvVar("GENESIS_DATA"),
+ {
+ Name: "BLOCK_SIGNER_ADDRESS",
+ Value: crd.Spec.SealerAddress,
+ },
+ {
+ Name: "CHAIN_ID",
+ Value: strconv.Itoa(crd.Spec.ChainID),
+ },
+ },
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: "entrypoints",
+ MountPath: "/opt/entrypoints",
+ },
+ {
+ Name: dbVolumeName,
+ MountPath: "/db",
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 8085,
+ },
+ {
+ ContainerPort: 8086,
+ },
+ },
+ },
+ },
+ Volumes: volumes,
+ },
+ },
+ VolumeClaimTemplates: volumeClaimTemplates,
+ },
+ }
+ ctrl.SetControllerReference(crd, statefulSet, r.Scheme)
+ return statefulSet
+}
+
+func (r *CliqueL1Reconciler) service(crd *stackv1.CliqueL1) *corev1.Service {
+ labels := map[string]string{
+ "app": "clique",
+ }
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "clique", labels),
+ Spec: corev1.ServiceSpec{
+ Selector: map[string]string{
+ "app": "clique",
+ },
+ Ports: []corev1.ServicePort{
+ {
+ Name: "rpc",
+ Port: 8545,
+ TargetPort: intstr.FromInt(8545),
+ },
+ {
+ Name: "ws",
+ Port: 8546,
+ TargetPort: intstr.FromInt(8546),
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
+
+const CliqueEntrypoint = `
+#!/bin/sh
+set -exu
+
+VERBOSITY=${VERBOSITY:-9}
+GETH_DATA_DIR=/db
+GETH_CHAINDATA_DIR="$GETH_DATA_DIR/geth/chaindata"
+GETH_KEYSTORE_DIR="$GETH_DATA_DIR/keystore"
+
+if [ ! -d "$GETH_KEYSTORE_DIR" ]; then
+ echo "$GETH_KEYSTORE_DIR missing, running account import"
+ echo -n "pwd" > "$GETH_DATA_DIR"/password
+ echo -n "$BLOCK_SIGNER_PRIVATE_KEY" | sed 's/0x//' > "$GETH_DATA_DIR"/block-signer-key
+ geth account import \
+ --datadir="$GETH_DATA_DIR" \
+ --password="$GETH_DATA_DIR"/password \
+ "$GETH_DATA_DIR"/block-signer-key
+else
+ echo "$GETH_KEYSTORE_DIR exists."
+fi
+
+if [ ! -d "$GETH_CHAINDATA_DIR" ]; then
+ echo "$GETH_CHAINDATA_DIR missing, running init"
+ echo "Creating genesis file."
+ echo -n "$GENESIS_DATA" > "$GETH_DATA_DIR/genesis.json"
+ geth --verbosity="$VERBOSITY" init \
+ --datadir="$GETH_DATA_DIR" \
+ "$GETH_DATA_DIR/genesis.json"
+else
+ echo "$GETH_CHAINDATA_DIR exists."
+fi
+
+geth \
+ --datadir="$GETH_DATA_DIR" \
+ --verbosity="$VERBOSITY" \
+ --http \
+ --http.corsdomain="*" \
+ --http.vhosts="*" \
+ --http.addr=0.0.0.0 \
+ --http.port=8545 \
+ --ws.addr=0.0.0.0 \
+ --ws.port=8546 \
+ --ws.origins="*" \
+ --syncmode=full \
+ --nodiscover \
+ --maxpeers=1 \
+ --networkid=$CHAIN_ID \
+ --unlock=$BLOCK_SIGNER_ADDRESS \
+ --mine \
+ --miner.etherbase=$BLOCK_SIGNER_ADDRESS \
+ --password="$GETH_DATA_DIR"/password \
+ --allow-insecure-unlock \
+ "$@"
+`
diff --git a/go/stackman/controllers/datatransportlayer_controller.go b/go/stackman/controllers/datatransportlayer_controller.go
new file mode 100644
index 0000000000000..b8618b71eb7fa
--- /dev/null
+++ b/go/stackman/controllers/datatransportlayer_controller.go
@@ -0,0 +1,343 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/resource"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+ "strconv"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// DataTransportLayerReconciler reconciles a DataTransportLayer object
+type DataTransportLayerReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=datatransportlayers,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=datatransportlayers/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=datatransportlayers/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the DataTransportLayer object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *DataTransportLayerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.DataTransportLayer{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("dtl resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting dtl")
+ return ctrl.Result{}, err
+ }
+
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.entrypointsCfgMap(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "dtl-entrypoints"), &corev1.ConfigMap{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ statefulSet := &appsv1.StatefulSet{}
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.statefulSet(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "dtl"), statefulSet)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.argsHash(crd)
+ if statefulSet.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.statefulSet(crd))
+ if err != nil {
+ lgr.Error(err, "error updating dtl statefulSet")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "dtl"), &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *DataTransportLayerReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.DataTransportLayer{}).
+ Owns(&corev1.ConfigMap{}).
+ Owns(&appsv1.Deployment{}).
+ Owns(&corev1.Service{}).
+ Complete(r)
+}
+
+func (r *DataTransportLayerReconciler) labels() map[string]string {
+ return map[string]string{
+ "app": "dtl",
+ }
+}
+
+func (r *DataTransportLayerReconciler) argsHash(crd *stackv1.DataTransportLayer) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L1TimeoutSeconds)))
+ h.Write([]byte(crd.Spec.DeployerURL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.DeployerTimeoutSeconds)))
+ if crd.Spec.DataPVC != nil {
+ h.Write([]byte(crd.Spec.DataPVC.Name))
+ h.Write([]byte(crd.Spec.DataPVC.Storage.String()))
+ }
+ for _, ev := range crd.Spec.Env {
+ h.Write([]byte(ev.String()))
+ }
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (r *DataTransportLayerReconciler) entrypointsCfgMap(crd *stackv1.DataTransportLayer) *corev1.ConfigMap {
+ cfgMap := &corev1.ConfigMap{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "dtl-entrypoints", r.labels()),
+ Data: map[string]string{
+ "entrypoint.sh": DTLEntrypoint,
+ },
+ }
+ ctrl.SetControllerReference(crd, cfgMap, r.Scheme)
+ return cfgMap
+}
+
+func (r *DataTransportLayerReconciler) statefulSet(crd *stackv1.DataTransportLayer) *appsv1.StatefulSet {
+ replicas := int32(1)
+ defaultMode := int32(0o777)
+ initContainers := []corev1.Container{
+ {
+ Name: "wait-for-l1",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L1URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L1TimeoutSeconds),
+ },
+ },
+ }
+ baseEnv := []corev1.EnvVar{
+ {
+ Name: "DATA_TRANSPORT_LAYER__L1_RPC_ENDPOINT",
+ Value: crd.Spec.L1URL,
+ },
+ }
+ if crd.Spec.DeployerURL != "" {
+ initContainers = append(initContainers, corev1.Container{
+ Name: "wait-for-deployer",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.DeployerURL),
+ "-t",
+ strconv.Itoa(crd.Spec.DeployerTimeoutSeconds),
+ },
+ })
+ baseEnv = append(baseEnv, corev1.EnvVar{
+ Name: "DEPLOYER_URL",
+ Value: crd.Spec.DeployerURL,
+ })
+ }
+ volumes := []corev1.Volume{
+ {
+ Name: "entrypoints",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: ObjectName(crd.ObjectMeta, "dtl-entrypoints"),
+ },
+ DefaultMode: &defaultMode,
+ },
+ },
+ },
+ }
+ var volumeClaimTemplates []corev1.PersistentVolumeClaim
+ dbVolumeName := "db"
+ if crd.Spec.DataPVC != nil {
+ storage := resource.MustParse("10Gi")
+ dbVolumeName = crd.Spec.DataPVC.Name
+ if crd.Spec.DataPVC.Storage != nil {
+ storage = *crd.Spec.DataPVC.Storage
+ }
+ volumeClaimTemplates = []corev1.PersistentVolumeClaim{
+ {
+ ObjectMeta: v1.ObjectMeta{
+ Name: crd.Spec.DataPVC.Name,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{
+ corev1.ReadWriteOnce,
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceStorage: storage,
+ },
+ },
+ },
+ },
+ }
+ } else {
+ volumes = append(volumes, corev1.Volume{
+ Name: dbVolumeName,
+ VolumeSource: corev1.VolumeSource{
+ EmptyDir: &corev1.EmptyDirVolumeSource{},
+ },
+ })
+ }
+ statefulSet := &appsv1.StatefulSet{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "dtl", r.labels()),
+ Spec: appsv1.StatefulSetSpec{
+ Replicas: &replicas,
+ Selector: &v1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "dtl",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: v1.ObjectMeta{
+ Labels: r.labels(),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ InitContainers: initContainers,
+ Containers: []corev1.Container{
+ {
+ Name: "dtl",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ Command: []string{
+ "/bin/sh",
+ "/opt/entrypoints/entrypoint.sh",
+ "node",
+ "/opt/optimism/packages/data-transport-layer/dist/src/services/run.js",
+ },
+ Env: append(baseEnv, crd.Spec.Env...),
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: dbVolumeName,
+ MountPath: "/db",
+ },
+ {
+ Name: "entrypoints",
+ MountPath: "/opt/entrypoints",
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ Name: "api",
+ ContainerPort: 7878,
+ },
+ {
+ Name: "metrics",
+ ContainerPort: 3000,
+ },
+ },
+ LivenessProbe: &corev1.Probe{
+ Handler: corev1.Handler{
+ HTTPGet: &corev1.HTTPGetAction{
+ Path: "/eth/syncing",
+ Port: intstr.FromInt(7878),
+ },
+ },
+ },
+ },
+ },
+ Volumes: volumes,
+ },
+ },
+ VolumeClaimTemplates: volumeClaimTemplates,
+ },
+ }
+ ctrl.SetControllerReference(crd, statefulSet, r.Scheme)
+ return statefulSet
+}
+
+func (r *DataTransportLayerReconciler) service(crd *stackv1.DataTransportLayer) *corev1.Service {
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "dtl", r.labels()),
+ Spec: corev1.ServiceSpec{
+ Selector: map[string]string{
+ "app": "dtl",
+ },
+ Ports: []corev1.ServicePort{
+ {
+ Port: 3000,
+ },
+ {
+ Port: 7878,
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
+
+const DTLEntrypoint = `
+#!/bin/sh
+
+if [ -n "$DEPLOYER_URL" ]; then
+ echo "Loading addresses from $DEPLOYER_URL."
+ ADDRESSES=$(curl --fail --show-error --silent "$DEPLOYER_URL/addresses.json")
+ export DATA_TRANSPORT_LAYER__ADDRESS_MANAGER=$(echo $ADDRESSES | jq -r ".AddressManager")
+fi
+
+exec "$@"
+`
diff --git a/go/stackman/controllers/deployer_controller.go b/go/stackman/controllers/deployer_controller.go
new file mode 100644
index 0000000000000..7d2b95ba967a1
--- /dev/null
+++ b/go/stackman/controllers/deployer_controller.go
@@ -0,0 +1,298 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "strconv"
+ "time"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// DeployerReconciler reconciles a Deployer object
+type DeployerReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=deployers,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=deployers/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=deployers/finalizers,verbs=update
+//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=core,resources=services;pods;configmaps,verbs=get;list;watch;create;update;patch;delete
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the Deployer object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *DeployerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.Deployer{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("deployer resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting deployer")
+ return ctrl.Result{}, err
+ }
+
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.entrypointsCfgMap(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "entrypoints"), &corev1.ConfigMap{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ deployment := &appsv1.Deployment{}
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.deployment(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "deployer"), deployment)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.deploymentArgsHash(crd)
+ if deployment.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.deployment(crd))
+ if err != nil {
+ lgr.Error(err, "error updating deployer deployment")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "deployer"), &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *DeployerReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.Deployer{}).
+ Owns(&corev1.ConfigMap{}).
+ Owns(&appsv1.Deployment{}).
+ Owns(&corev1.Service{}).
+ Complete(r)
+}
+
+func (r *DeployerReconciler) labels(crd *stackv1.Deployer) map[string]string {
+ return map[string]string{
+ "app": "deployer",
+ "deployer_crd": crd.Namespace,
+ }
+}
+
+func (r *DeployerReconciler) entrypointsCfgMap(crd *stackv1.Deployer) *corev1.ConfigMap {
+ cfgMap := &corev1.ConfigMap{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "entrypoints", r.labels(crd)),
+ Data: map[string]string{
+ "entrypoint.sh": DeployerEntrypoint,
+ },
+ }
+ ctrl.SetControllerReference(crd, cfgMap, r.Scheme)
+ return cfgMap
+}
+
+func (r *DeployerReconciler) deploymentArgsHash(crd *stackv1.Deployer) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L1TimeoutSeconds)))
+ for _, ev := range crd.Spec.Env {
+ h.Write([]byte(ev.String()))
+ }
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (r *DeployerReconciler) deployment(crd *stackv1.Deployer) *appsv1.Deployment {
+ replicas := int32(1)
+ defaultMode := int32(0o777)
+ deployment := &appsv1.Deployment{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "deployer", map[string]string{
+ "app": "deployer",
+ "deployer_crd": crd.Namespace,
+ "args_hash": r.deploymentArgsHash(crd),
+ }),
+ Spec: appsv1.DeploymentSpec{
+ Replicas: &replicas,
+ Selector: &v1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "deployer",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: v1.ObjectMeta{
+ Labels: r.labels(crd),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ InitContainers: []corev1.Container{
+ {
+ Name: "wait-for-l1",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L1URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L1TimeoutSeconds),
+ },
+ },
+ },
+ Containers: []corev1.Container{
+ {
+ Name: "deployer",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ Command: []string{
+ "/bin/bash",
+ "/opt/entrypoints/entrypoint.sh",
+ },
+ Env: append([]corev1.EnvVar{
+ {
+ Name: "L1_NODE_WEB3_URL",
+ Value: crd.Spec.L1URL,
+ },
+ {
+ Name: "NO_COMPILE",
+ Value: "1",
+ },
+ {
+ Name: "AUTOMATICALLY_TRANSFER_OWNERSHIP",
+ Value: "true",
+ },
+ }, crd.Spec.Env...),
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: "entrypoints",
+ MountPath: "/opt/entrypoints",
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 8081,
+ },
+ },
+ },
+ },
+ Volumes: []corev1.Volume{
+ {
+ Name: "entrypoints",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: ObjectName(crd.ObjectMeta, "entrypoints"),
+ },
+ DefaultMode: &defaultMode,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, deployment, r.Scheme)
+ return deployment
+}
+
+func (r *DeployerReconciler) service(crd *stackv1.Deployer) *corev1.Service {
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "deployer", r.labels(crd)),
+ Spec: corev1.ServiceSpec{
+ Selector: map[string]string{
+ "app": "deployer",
+ },
+ Ports: []corev1.ServicePort{
+ {
+ Name: "web",
+ Port: 8081,
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
+
+const DeployerEntrypoint = `
+#!/bin/bash
+set -e
+cd /optimism/packages/contracts
+yarn run deploy
+
+function envSet() {
+ VAR=$1
+ export $VAR=$(cat ./dist/dumps/addresses.json | jq -r ".$2")
+}
+
+# set the address to the proxy gateway if possible
+envSet L1_STANDARD_BRIDGE_ADDRESS Proxy__OVM_L1StandardBridge
+if [ $L1_STANDARD_BRIDGE_ADDRESS == null ]; then
+ envSet L1_STANDARD_BRIDGE_ADDRESS L1StandardBridge
+fi
+
+envSet L1_CROSS_DOMAIN_MESSENGER_ADDRESS Proxy__OVM_L1CrossDomainMessenger
+if [ $L1_CROSS_DOMAIN_MESSENGER_ADDRESS == null ]; then
+ envSet L1_CROSS_DOMAIN_MESSENGER_ADDRESS L1CrossDomainMessenger
+fi
+
+# build the dump file
+yarn run build:dump
+
+echo "Starting server."
+
+# service the addresses and dumps
+python3 -m http.server \
+ --bind "0.0.0.0" 8081 \
+ --directory ./dist/dumps
+`
diff --git a/go/stackman/controllers/gasoracle_controller.go b/go/stackman/controllers/gasoracle_controller.go
new file mode 100644
index 0000000000000..705cf74458c72
--- /dev/null
+++ b/go/stackman/controllers/gasoracle_controller.go
@@ -0,0 +1,186 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "strconv"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// GasOracleReconciler reconciles a GasOracle object
+type GasOracleReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=gasoracles,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=gasoracles/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=gasoracles/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the GasOracle object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *GasOracleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.GasOracle{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("gas oracle resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting gas oracle")
+ return ctrl.Result{}, err
+ }
+
+ deployment := &appsv1.Deployment{}
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.deployment(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "gas-oracle"), deployment)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.deploymentArgsHash(crd)
+ if deployment.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.deployment(crd))
+ if err != nil {
+ lgr.Error(err, "error updating gas oracle deployment")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *GasOracleReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.GasOracle{}).
+ Complete(r)
+}
+
+func (r *GasOracleReconciler) labels() map[string]string {
+ return map[string]string{
+ "app": "gas-oracle",
+ }
+}
+
+func (r *GasOracleReconciler) deploymentArgsHash(crd *stackv1.GasOracle) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L1TimeoutSeconds)))
+ h.Write([]byte(crd.Spec.L2URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L2TimeoutSeconds)))
+ for _, ev := range crd.Spec.Env {
+ h.Write([]byte(ev.String()))
+ }
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+func (r *GasOracleReconciler) deployment(crd *stackv1.GasOracle) *appsv1.Deployment {
+ replicas := int32(1)
+ labels := r.labels()
+ labels["args_hash"] = r.deploymentArgsHash(crd)
+ baseEnv := []corev1.EnvVar{
+ {
+ Name: "GAS_PRICE_ORACLE_ETHEREUM_HTTP_URL",
+ Value: crd.Spec.L1URL,
+ },
+ {
+ Name: "GAS_PRICE_ORACLE_LAYER_TWO_HTTP_URL",
+ Value: crd.Spec.L2URL,
+ },
+ }
+ deployment := &appsv1.Deployment{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "gas-oracle", labels),
+ Spec: appsv1.DeploymentSpec{
+ Replicas: &replicas,
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "gas-oracle",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: r.labels(),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ InitContainers: []corev1.Container{
+ {
+ Name: "wait-for-l1",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L1URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L1TimeoutSeconds),
+ },
+ },
+ {
+ Name: "wait-for-l2",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.L2URL),
+ "-t",
+ strconv.Itoa(crd.Spec.L2TimeoutSeconds),
+ },
+ },
+ },
+ Containers: []corev1.Container{
+ {
+ Name: "gpo",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ Env: append(baseEnv, crd.Spec.Env...),
+ },
+ },
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, deployment, r.Scheme)
+ return deployment
+}
diff --git a/go/stackman/controllers/sequencer_controller.go b/go/stackman/controllers/sequencer_controller.go
new file mode 100644
index 0000000000000..ad974eb3877c1
--- /dev/null
+++ b/go/stackman/controllers/sequencer_controller.go
@@ -0,0 +1,374 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "context"
+ "crypto/md5"
+ "encoding/hex"
+ "fmt"
+ appsv1 "k8s.io/api/apps/v1"
+ corev1 "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/log"
+ "strconv"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+)
+
+// SequencerReconciler reconciles a Sequencer object
+type SequencerReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=sequencers,verbs=get;list;watch;create;update;patch;delete
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=sequencers/status,verbs=get;update;patch
+//+kubebuilder:rbac:groups=stack.optimism-stacks.net,resources=sequencers/finalizers,verbs=update
+
+// Reconcile is part of the main kubernetes reconciliation loop which aims to
+// move the current state of the cluster closer to the desired state.
+// TODO(user): Modify the Reconcile function to compare the state specified by
+// the Sequencer object against the actual cluster state, and then
+// perform operations to make the cluster state reflect the state specified by
+// the user.
+//
+// For more details, check Reconcile and its Result here:
+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
+func (r *SequencerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ lgr := log.FromContext(ctx)
+
+ crd := &stackv1.Sequencer{}
+ if err := r.Get(ctx, req.NamespacedName, crd); err != nil {
+ if errors.IsNotFound(err) {
+ lgr.Info("sequencer resource not found, ignoring")
+ return ctrl.Result{}, nil
+ }
+
+ lgr.Error(err, "error getting sequencer")
+ return ctrl.Result{}, err
+ }
+
+ created, err := GetOrCreateResource(ctx, r, func() client.Object {
+ return r.entrypointsCfgMap(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "sequencer-entrypoints"), &corev1.ConfigMap{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ statefulSet := &appsv1.StatefulSet{}
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.statefulSet(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "sequencer"), statefulSet)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ argsHash := r.deploymentArgsHash(crd)
+ if statefulSet.Labels["args_hash"] != argsHash {
+ err := r.Update(ctx, r.statefulSet(crd))
+ if err != nil {
+ lgr.Error(err, "error updating sequencer statefulSet")
+ return ctrl.Result{}, err
+ }
+
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ created, err = GetOrCreateResource(ctx, r, func() client.Object {
+ return r.service(crd)
+ }, ObjectNamespacedName(crd.ObjectMeta, "sequencer"), &corev1.Service{})
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if created {
+ return ctrl.Result{Requeue: true}, nil
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// SetupWithManager sets up the controller with the Manager.
+func (r *SequencerReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&stackv1.Sequencer{}).
+ Complete(r)
+}
+
+func (r *SequencerReconciler) labels() map[string]string {
+ return map[string]string{
+ "app": "sequencer",
+ }
+}
+
+func (r *SequencerReconciler) entrypointsCfgMap(crd *stackv1.Sequencer) *corev1.ConfigMap {
+ cfgMap := &corev1.ConfigMap{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "sequencer-entrypoints", r.labels()),
+ Data: map[string]string{
+ "entrypoint.sh": SequencerEntrypoint,
+ },
+ }
+ ctrl.SetControllerReference(crd, cfgMap, r.Scheme)
+ return cfgMap
+}
+
+func (r *SequencerReconciler) statefulSet(crd *stackv1.Sequencer) *appsv1.StatefulSet {
+ replicas := int32(1)
+ defaultMode := int32(0o777)
+ om := ObjectMeta(crd.ObjectMeta, "sequencer", r.labels())
+ om.Labels["args_hash"] = r.deploymentArgsHash(crd)
+ initContainers := []corev1.Container{
+ {
+ Name: "wait-for-dtl",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.DTLURL),
+ "-t",
+ strconv.Itoa(crd.Spec.DTLTimeoutSeconds),
+ },
+ },
+ }
+ baseEnv := []corev1.EnvVar{
+ {
+ Name: "ROLLUP_CLIENT_HTTP",
+ Value: crd.Spec.DTLURL,
+ },
+ }
+ if crd.Spec.DeployerURL != "" {
+ initContainers = append(initContainers, corev1.Container{
+ Name: "wait-for-deployer",
+ Image: "mslipper/wait-for-it:latest",
+ ImagePullPolicy: corev1.PullAlways,
+ Args: []string{
+ Hostify(crd.Spec.DeployerURL),
+ "-t",
+ strconv.Itoa(crd.Spec.DeployerTimeoutSeconds),
+ },
+ })
+ baseEnv = append(baseEnv, []corev1.EnvVar{
+ {
+ Name: "L2GETH_GENESIS_URL",
+ Value: fmt.Sprintf("%s/state-dump.latest.json", crd.Spec.DeployerURL),
+ },
+ {
+ Name: "DEPLOYER_URL",
+ Value: crd.Spec.DeployerURL,
+ },
+ }...)
+ }
+ volumes := []corev1.Volume{
+ {
+ Name: "entrypoints",
+ VolumeSource: corev1.VolumeSource{
+ ConfigMap: &corev1.ConfigMapVolumeSource{
+ LocalObjectReference: corev1.LocalObjectReference{
+ Name: ObjectName(crd.ObjectMeta, "sequencer-entrypoints"),
+ },
+ DefaultMode: &defaultMode,
+ },
+ },
+ },
+ }
+ var volumeClaimTemplates []corev1.PersistentVolumeClaim
+ dbVolumeName := "db"
+ if crd.Spec.DataPVC != nil {
+ dbVolumeName = crd.Spec.DataPVC.Name
+ storage := resource.MustParse("128Gi")
+ if crd.Spec.DataPVC.Storage != nil {
+ storage = *crd.Spec.DataPVC.Storage
+ }
+ volumeClaimTemplates = []corev1.PersistentVolumeClaim{
+ {
+ ObjectMeta: metav1.ObjectMeta{
+ Name: crd.Spec.DataPVC.Name,
+ },
+ Spec: corev1.PersistentVolumeClaimSpec{
+ AccessModes: []corev1.PersistentVolumeAccessMode{
+ corev1.ReadWriteOnce,
+ },
+ Resources: corev1.ResourceRequirements{
+ Requests: corev1.ResourceList{
+ corev1.ResourceStorage: storage,
+ },
+ },
+ },
+ },
+ }
+ } else {
+ volumes = append(volumes, corev1.Volume{
+ Name: dbVolumeName,
+ VolumeSource: corev1.VolumeSource{
+ EmptyDir: &corev1.EmptyDirVolumeSource{},
+ },
+ })
+ }
+ statefulSet := &appsv1.StatefulSet{
+ ObjectMeta: om,
+ Spec: appsv1.StatefulSetSpec{
+ Replicas: &replicas,
+ Selector: &metav1.LabelSelector{
+ MatchLabels: map[string]string{
+ "app": "sequencer",
+ },
+ },
+ Template: corev1.PodTemplateSpec{
+ ObjectMeta: metav1.ObjectMeta{
+ Labels: r.labels(),
+ },
+ Spec: corev1.PodSpec{
+ RestartPolicy: corev1.RestartPolicyAlways,
+ InitContainers: initContainers,
+ Containers: []corev1.Container{
+ {
+ Name: "sequencer",
+ Image: crd.Spec.Image,
+ ImagePullPolicy: corev1.PullAlways,
+ Command: append([]string{
+ "/bin/sh",
+ "/opt/entrypoints/entrypoint.sh",
+ }, crd.Spec.AdditionalArgs...),
+ Env: append(baseEnv, crd.Spec.Env...),
+ VolumeMounts: []corev1.VolumeMount{
+ {
+ Name: dbVolumeName,
+ MountPath: "/db",
+ },
+ {
+ Name: "entrypoints",
+ MountPath: "/opt/entrypoints",
+ },
+ },
+ Ports: []corev1.ContainerPort{
+ {
+ ContainerPort: 8545,
+ },
+ {
+ ContainerPort: 8546,
+ },
+ },
+ },
+ },
+ Volumes: volumes,
+ },
+ },
+ VolumeClaimTemplates: volumeClaimTemplates,
+ },
+ }
+ ctrl.SetControllerReference(crd, statefulSet, r.Scheme)
+ return statefulSet
+}
+
+func (r *SequencerReconciler) service(crd *stackv1.Sequencer) *corev1.Service {
+ service := &corev1.Service{
+ ObjectMeta: ObjectMeta(crd.ObjectMeta, "sequencer", r.labels()),
+ Spec: corev1.ServiceSpec{
+ Selector: map[string]string{
+ "app": "sequencer",
+ },
+ Type: corev1.ServiceTypeClusterIP,
+ Ports: []corev1.ServicePort{
+ {
+ Name: "rpc",
+ Port: 8545,
+ },
+ {
+ Name: "websocket",
+ Port: 8546,
+ },
+ },
+ },
+ }
+ ctrl.SetControllerReference(crd, service, r.Scheme)
+ return service
+}
+
+func (r *SequencerReconciler) deploymentArgsHash(crd *stackv1.Sequencer) string {
+ h := md5.New()
+ h.Write([]byte(crd.Spec.Image))
+ h.Write([]byte(crd.Spec.L1URL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.L1TimeoutSeconds)))
+ h.Write([]byte(crd.Spec.DeployerURL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.DeployerTimeoutSeconds)))
+ h.Write([]byte(crd.Spec.DTLURL))
+ h.Write([]byte(strconv.Itoa(crd.Spec.DTLTimeoutSeconds)))
+ if crd.Spec.DataPVC != nil {
+ h.Write([]byte(crd.Spec.DataPVC.Name))
+ h.Write([]byte(crd.Spec.DataPVC.Storage.String()))
+ }
+ for _, ev := range crd.Spec.Env {
+ h.Write([]byte(ev.String()))
+ }
+ for _, arg := range crd.Spec.AdditionalArgs {
+ h.Write([]byte(arg))
+ }
+ return hex.EncodeToString(h.Sum(nil))
+}
+
+const SequencerEntrypoint = `
+#!/bin/sh
+set -exu
+
+VERBOSITY=${VERBOSITY:-9}
+GETH_DATA_DIR=/db
+GETH_CHAINDATA_DIR="$GETH_DATA_DIR/geth/chaindata"
+GETH_KEYSTORE_DIR="$GETH_DATA_DIR/keystore"
+
+if [ ! -d "$GETH_KEYSTORE_DIR" ]; then
+ echo "$GETH_KEYSTORE_DIR missing, running account import"
+ echo -n "pwd" > "$GETH_DATA_DIR"/password
+ echo -n "$BLOCK_SIGNER_PRIVATE_KEY" | sed 's/0x//' > "$GETH_DATA_DIR"/block-signer-key
+ geth account import \
+ --datadir="$GETH_DATA_DIR" \
+ --password="$GETH_DATA_DIR"/password \
+ "$GETH_DATA_DIR"/block-signer-key
+else
+ echo "$GETH_KEYSTORE_DIR exists."
+fi
+
+if [ ! -d "$GETH_CHAINDATA_DIR" ]; then
+ echo "$GETH_CHAINDATA_DIR missing, running init"
+ echo "Retrieving genesis file $L2GETH_GENESIS_URL"
+ curl --silent -o "$GETH_DATA_DIR/genesis.json" "$L2GETH_GENESIS_URL"
+ geth --verbosity="$VERBOSITY" init \
+ --datadir="$GETH_DATA_DIR" \
+ "$GETH_DATA_DIR/genesis.json"
+else
+ echo "$GETH_CHAINDATA_DIR exists."
+fi
+
+geth \
+ --verbosity="$VERBOSITY" \
+ --datadir="$GETH_DATA_DIR" \
+ --password="$GETH_DATA_DIR/password" \
+ --allow-insecure-unlock \
+ --unlock="$BLOCK_SIGNER_ADDRESS" \
+ --mine \
+ --miner.etherbase=$BLOCK_SIGNER_ADDRESS \
+ "$@"
+`
diff --git a/go/stackman/controllers/suite_test.go b/go/stackman/controllers/suite_test.go
new file mode 100644
index 0000000000000..9ff441db4e7fc
--- /dev/null
+++ b/go/stackman/controllers/suite_test.go
@@ -0,0 +1,98 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package controllers
+
+import (
+ "path/filepath"
+ "testing"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "k8s.io/client-go/kubernetes/scheme"
+ "k8s.io/client-go/rest"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/envtest"
+ "sigs.k8s.io/controller-runtime/pkg/envtest/printer"
+ logf "sigs.k8s.io/controller-runtime/pkg/log"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+ //+kubebuilder:scaffold:imports
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+
+func TestAPIs(t *testing.T) {
+ RegisterFailHandler(Fail)
+
+ RunSpecsWithDefaultAndCustomReporters(t,
+ "Controller Suite",
+ []Reporter{printer.NewlineReporter{}})
+}
+
+var _ = BeforeSuite(func() {
+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
+
+ By("bootstrapping test environment")
+ testEnv = &envtest.Environment{
+ CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},
+ ErrorIfCRDPathMissing: true,
+ }
+
+ cfg, err := testEnv.Start()
+ Expect(err).NotTo(HaveOccurred())
+ Expect(cfg).NotTo(BeNil())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ err = stackv1.AddToScheme(scheme.Scheme)
+ Expect(err).NotTo(HaveOccurred())
+
+ //+kubebuilder:scaffold:scheme
+
+ k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+ Expect(err).NotTo(HaveOccurred())
+ Expect(k8sClient).NotTo(BeNil())
+
+}, 60)
+
+var _ = AfterSuite(func() {
+ By("tearing down the test environment")
+ err := testEnv.Stop()
+ Expect(err).NotTo(HaveOccurred())
+})
diff --git a/go/stackman/controllers/util.go b/go/stackman/controllers/util.go
new file mode 100644
index 0000000000000..5c436fc267878
--- /dev/null
+++ b/go/stackman/controllers/util.go
@@ -0,0 +1,87 @@
+package controllers
+
+import (
+ "context"
+ "fmt"
+ "k8s.io/apimachinery/pkg/api/errors"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "regexp"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "strconv"
+ "strings"
+)
+
+var httpRe *regexp.Regexp
+var pathRe *regexp.Regexp
+var portRe *regexp.Regexp
+
+func Hostify(in string) string {
+ port := 80
+ if strings.HasPrefix(in, "https://") {
+ port = 443
+ }
+
+ out := httpRe.ReplaceAllString(in, "")
+ out = pathRe.ReplaceAllString(out, "")
+ if portRe.MatchString(out) {
+ return out
+ }
+
+ return out + ":" + strconv.Itoa(port)
+}
+
+func ObjectName(om v1.ObjectMeta, name string) string {
+ return fmt.Sprintf("%s-%s", om.Name, name)
+}
+
+func ObjectNamespacedName(om v1.ObjectMeta, name string) types.NamespacedName {
+ return types.NamespacedName{
+ Name: ObjectName(om, name),
+ Namespace: om.Namespace,
+ }
+}
+
+func ObjectMeta(om v1.ObjectMeta, name string, labels map[string]string) v1.ObjectMeta {
+ return v1.ObjectMeta{
+ Name: ObjectName(om, name),
+ Namespace: om.Namespace,
+ Labels: labels,
+ }
+}
+
+type Creator func() client.Object
+
+type ReaderWriter interface {
+ client.Reader
+ client.Writer
+}
+
+func GetOrCreateResource(
+ ctx context.Context,
+ client ReaderWriter,
+ creator Creator,
+ name types.NamespacedName,
+ proto client.Object,
+) (bool, error) {
+ err := client.Get(ctx, name, proto)
+ if err == nil {
+ return false, nil
+ }
+
+ if errors.IsNotFound(err) {
+ err = client.Create(ctx, creator())
+ if err != nil {
+ return false, err
+ }
+ return true, nil
+ }
+
+ return false, err
+}
+
+func init() {
+ httpRe = regexp.MustCompile("^http(s)?://")
+ pathRe = regexp.MustCompile("/(.*)$")
+ portRe = regexp.MustCompile(":([\\d]+)$")
+}
diff --git a/go/stackman/controllers/util_test.go b/go/stackman/controllers/util_test.go
new file mode 100644
index 0000000000000..8984adb48d1d6
--- /dev/null
+++ b/go/stackman/controllers/util_test.go
@@ -0,0 +1,36 @@
+package controllers
+
+import (
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestHostify(t *testing.T) {
+ tests := [][2]string{
+ {
+ "https://test.infura.io/v1/123456",
+ "test.infura.io:443",
+ },
+ {
+ "http://test.infura.io/v1/123456",
+ "test.infura.io:80",
+ },
+ {
+ "test.infura.io/v1/123456",
+ "test.infura.io:80",
+ },
+ {
+ "test.infura.io",
+ "test.infura.io:80",
+ },
+ {
+ "http://sequencer:8545",
+ "sequencer:8545",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt[0], func(t *testing.T) {
+ require.Equal(t, tt[1], Hostify(tt[0]))
+ })
+ }
+}
diff --git a/go/stackman/go.mod b/go/stackman/go.mod
new file mode 100644
index 0000000000000..897885f54722f
--- /dev/null
+++ b/go/stackman/go.mod
@@ -0,0 +1,14 @@
+module github.com/ethereum-optimism/optimism/go/stackman
+
+go 1.16
+
+require (
+ github.com/onsi/ginkgo v1.16.4
+ github.com/onsi/gomega v1.15.0
+ github.com/stretchr/testify v1.7.0
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ k8s.io/api v0.22.1
+ k8s.io/apimachinery v0.22.1
+ k8s.io/client-go v0.22.1
+ sigs.k8s.io/controller-runtime v0.10.0
+)
diff --git a/go/stackman/go.sum b/go/stackman/go.sum
new file mode 100644
index 0000000000000..80d4bda8be159
--- /dev/null
+++ b/go/stackman/go.sum
@@ -0,0 +1,781 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
+github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
+github.com/Azure/go-autorest/autorest v0.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM=
+github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
+github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q=
+github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
+github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
+github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
+github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
+github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
+github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
+github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
+github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
+github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
+github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
+github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
+github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
+github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
+github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM=
+github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
+github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
+github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
+github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
+github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
+github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
+github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
+github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
+github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
+github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
+github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
+go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
+go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
+go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
+go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
+go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
+go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
+go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
+go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
+go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
+go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
+go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
+go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
+go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
+go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=
+go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
+golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
+golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE=
+golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs=
+golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
+gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
+gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY=
+k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
+k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE=
+k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
+k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM=
+k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
+k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
+k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw=
+k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
+k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
+k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo=
+k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo=
+k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
+k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
+k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
+k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
+k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
+k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
+k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY=
+k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
+sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U=
+sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg=
+sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
+sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/go/stackman/hack/boilerplate.go.txt b/go/stackman/hack/boilerplate.go.txt
new file mode 100644
index 0000000000000..45dbbbbcf0986
--- /dev/null
+++ b/go/stackman/hack/boilerplate.go.txt
@@ -0,0 +1,15 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
\ No newline at end of file
diff --git a/go/stackman/main.go b/go/stackman/main.go
new file mode 100644
index 0000000000000..0abb12f6b7060
--- /dev/null
+++ b/go/stackman/main.go
@@ -0,0 +1,146 @@
+/*
+Copyright 2021.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package main
+
+import (
+ "flag"
+ "os"
+
+ // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
+ // to ensure that exec-entrypoint and run can make use of them.
+ _ "k8s.io/client-go/plugin/pkg/client/auth"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
+ clientgoscheme "k8s.io/client-go/kubernetes/scheme"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/healthz"
+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+ stackv1 "github.com/ethereum-optimism/optimism/go/stackman/api/v1"
+ "github.com/ethereum-optimism/optimism/go/stackman/controllers"
+ //+kubebuilder:scaffold:imports
+)
+
+var (
+ scheme = runtime.NewScheme()
+ setupLog = ctrl.Log.WithName("setup")
+)
+
+func init() {
+ utilruntime.Must(clientgoscheme.AddToScheme(scheme))
+
+ utilruntime.Must(stackv1.AddToScheme(scheme))
+ //+kubebuilder:scaffold:scheme
+}
+
+func main() {
+ var metricsAddr string
+ var enableLeaderElection bool
+ var probeAddr string
+ flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
+ flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
+ flag.BoolVar(&enableLeaderElection, "leader-elect", false,
+ "Enable leader election for controller manager. "+
+ "Enabling this will ensure there is only one active controller manager.")
+ opts := zap.Options{
+ Development: true,
+ }
+ opts.BindFlags(flag.CommandLine)
+ flag.Parse()
+
+ ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
+
+ mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
+ Scheme: scheme,
+ MetricsBindAddress: metricsAddr,
+ Port: 9443,
+ HealthProbeBindAddress: probeAddr,
+ LeaderElection: enableLeaderElection,
+ LeaderElectionID: "8103f40b.optimism-stacks.net",
+ })
+ if err != nil {
+ setupLog.Error(err, "unable to start manager")
+ os.Exit(1)
+ }
+
+ if err = (&controllers.CliqueL1Reconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "CliqueL1")
+ os.Exit(1)
+ }
+ if err = (&controllers.DeployerReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "Deployer")
+ os.Exit(1)
+ }
+ if err = (&controllers.DataTransportLayerReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "DataTransportLayer")
+ os.Exit(1)
+ }
+ if err = (&controllers.SequencerReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "Sequencer")
+ os.Exit(1)
+ }
+ if err = (&controllers.BatchSubmitterReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "BatchSubmitter")
+ os.Exit(1)
+ }
+ if err = (&controllers.GasOracleReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "GasOracle")
+ os.Exit(1)
+ }
+ if err = (&controllers.ActorReconciler{
+ Client: mgr.GetClient(),
+ Scheme: mgr.GetScheme(),
+ }).SetupWithManager(mgr); err != nil {
+ setupLog.Error(err, "unable to create controller", "controller", "Actor")
+ os.Exit(1)
+ }
+ //+kubebuilder:scaffold:builder
+
+ if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
+ setupLog.Error(err, "unable to set up health check")
+ os.Exit(1)
+ }
+ if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
+ setupLog.Error(err, "unable to set up ready check")
+ os.Exit(1)
+ }
+
+ setupLog.Info("starting manager")
+ if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
+ setupLog.Error(err, "problem running manager")
+ os.Exit(1)
+ }
+}
diff --git a/integration-tests/.prettierrc.js b/integration-tests/.prettierrc.js
new file mode 100644
index 0000000000000..0d9f53f687c7e
--- /dev/null
+++ b/integration-tests/.prettierrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ ...require('../.prettierrc.js'),
+}
diff --git a/integration-tests/.prettierrc.json b/integration-tests/.prettierrc.json
deleted file mode 120000
index 0e9207b846134..0000000000000
--- a/integration-tests/.prettierrc.json
+++ /dev/null
@@ -1 +0,0 @@
-../.prettierrc.json
\ No newline at end of file
diff --git a/integration-tests/actor-tests/lib/metrics.ts b/integration-tests/actor-tests/lib/metrics.ts
index dbcdb575ae4a4..7bb344f847593 100644
--- a/integration-tests/actor-tests/lib/metrics.ts
+++ b/integration-tests/actor-tests/lib/metrics.ts
@@ -1,5 +1,7 @@
import fs from 'fs'
import client from 'prom-client'
+import http from 'http'
+import url from 'url'
export const metricsRegistry = new client.Registry()
@@ -53,3 +55,18 @@ export const dumpMetrics = async (filename: string) => {
flag: 'w+',
})
}
+
+export const serveMetrics = (port: number) => {
+ const server = http.createServer(async (req, res) => {
+ const route = url.parse(req.url).pathname
+ if (route !== '/metrics') {
+ res.writeHead(404)
+ res.end()
+ return
+ }
+
+ res.setHeader('Content-Type', metricsRegistry.contentType)
+ res.end(await metricsRegistry.metrics())
+ })
+ server.listen(port)
+}
diff --git a/integration-tests/actor-tests/lib/runner.ts b/integration-tests/actor-tests/lib/runner.ts
index 1c78d7daefcf0..404fcfa35349a 100644
--- a/integration-tests/actor-tests/lib/runner.ts
+++ b/integration-tests/actor-tests/lib/runner.ts
@@ -3,7 +3,7 @@ import { defaultRuntime } from './convenience'
import { RunOpts } from './actor'
import { Command } from 'commander'
import pkg from '../../package.json'
-import { metricsRegistry } from './metrics'
+import { serveMetrics } from './metrics'
const program = new Command()
program.version(pkg.version)
@@ -18,6 +18,11 @@ program
)
.option('-c, --concurrency ', 'number of concurrent workers to spawn', '1')
.option('--think-time ', 'how long to wait between each run', '0')
+ .option(
+ '-s, --serve [port]',
+ 'Serve metrics with optional port number',
+ '8545'
+ )
program.parse(process.argv)
@@ -27,6 +32,8 @@ const runsNum = Number(options.runs)
const timeNum = Number(options.time)
const concNum = Number(options.concurrency)
const thinkNum = Number(options.thinkTime)
+const shouldServeMetrics = options.serve !== undefined
+const metricsPort = options.serve || 8545
if (isNaN(runsNum) && isNaN(timeNum)) {
console.error('Must define either a number of runs or how long to run.')
@@ -58,15 +65,19 @@ const opts: Partial = {
runs: runsNum,
}
+if (shouldServeMetrics) {
+ process.stderr.write(`Serving metrics on http://0.0.0.0:${metricsPort}.\n`)
+ serveMetrics(metricsPort)
+}
+
defaultRuntime
.run(opts)
- .then(() => metricsRegistry.metrics())
- .then((metrics) => {
- process.stderr.write('Run complete. Metrics:\n')
- console.log(metrics)
+ .then(() => {
+ process.stderr.write('Run complete.\n')
+ process.exit(0)
})
.catch((err) => {
- console.error('Error running:')
+ console.error('Error:')
console.error(err)
process.exit(1)
})
diff --git a/integration-tests/actor-tests/nft.test.ts b/integration-tests/actor-tests/nft.test.ts
index b4abaa12f8042..fc986342c1bc2 100644
--- a/integration-tests/actor-tests/nft.test.ts
+++ b/integration-tests/actor-tests/nft.test.ts
@@ -1,4 +1,4 @@
-import { utils, Wallet, Contract, ContractFactory } from 'ethers'
+import { utils, Wallet, Contract } from 'ethers'
import { actor, run, setupActor, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
import ERC721 from '../artifacts/contracts/NFT.sol/NFT.json'
@@ -16,14 +16,7 @@ actor('NFT claimer', () => {
setupActor(async () => {
env = await OptimismEnv.new()
-
- const factory = new ContractFactory(
- ERC721.abi,
- ERC721.bytecode,
- env.l2Wallet
- )
- contract = await factory.deploy()
- await contract.deployed()
+ contract = new Contract(process.env.ERC_721_ADDRESS, ERC721.abi)
})
setupRun(async () => {
diff --git a/integration-tests/actor-tests/uniswap.test.ts b/integration-tests/actor-tests/uniswap.test.ts
index a0ec675b98641..964435a3dc80a 100644
--- a/integration-tests/actor-tests/uniswap.test.ts
+++ b/integration-tests/actor-tests/uniswap.test.ts
@@ -1,22 +1,16 @@
-import { BigNumber, Contract, utils, Wallet, ContractFactory } from 'ethers'
+import { Contract, utils, Wallet } from 'ethers'
import { actor, run, setupActor, setupRun } from './lib/convenience'
import { OptimismEnv } from '../test/shared/env'
-import { UniswapV3Deployer } from 'uniswap-v3-deploy-plugin/dist/deployer/UniswapV3Deployer'
-import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
+import { FeeAmount } from '@uniswap/v3-sdk'
import ERC20 from '../artifacts/contracts/ERC20.sol/ERC20.json'
+import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
+import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
interface Context {
contracts: { [name: string]: Contract }
wallet: Wallet
}
-// Below methods taken from the Uniswap test suite, see
-// https://github.com/Uniswap/v3-periphery/blob/main/test/shared/ticks.ts
-export const getMinTick = (tickSpacing: number) =>
- Math.ceil(-887272 / tickSpacing) * tickSpacing
-export const getMaxTick = (tickSpacing: number) =>
- Math.floor(887272 / tickSpacing) * tickSpacing
-
actor('Uniswap swapper', () => {
let env: OptimismEnv
@@ -27,52 +21,29 @@ actor('Uniswap swapper', () => {
setupActor(async () => {
env = await OptimismEnv.new()
- const factory = new ContractFactory(ERC20.abi, ERC20.bytecode, env.l2Wallet)
- const tokenA = await factory.deploy(1000000000, 'OVM1', 8, 'OVM1')
- await tokenA.deployed()
- const tokenB = await factory.deploy(1000000000, 'OVM2', 8, 'OVM2')
- await tokenB.deployed()
-
- tokens =
- tokenA.address < tokenB.address ? [tokenA, tokenB] : [tokenB, tokenA]
- contracts = await UniswapV3Deployer.deploy(env.l2Wallet)
-
- let tx
- for (const token of tokens) {
- tx = await token.approve(contracts.positionManager.address, 1000000000)
- await tx.wait()
- tx = await token.approve(contracts.router.address, 1000000000)
- await tx.wait()
+ contracts = {
+ positionManager: new Contract(
+ process.env.UNISWAP_POSITION_MANAGER_ADDRESS,
+ NFTABI
+ ).connect(env.l2Wallet),
+ router: new Contract(
+ process.env.UNISWAP_ROUTER_ADDRESS,
+ RouterABI
+ ).connect(env.l2Wallet),
}
- tx = await contracts.positionManager.createAndInitializePoolIfNecessary(
- tokens[0].address,
- tokens[1].address,
- FeeAmount.MEDIUM,
- // initial ratio of 1/1
- BigNumber.from('79228162514264337593543950336')
- )
- await tx.wait()
-
- tx = await contracts.positionManager.mint(
- {
- token0: tokens[0].address,
- token1: tokens[1].address,
- tickLower: getMinTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
- tickUpper: getMaxTick(TICK_SPACINGS[FeeAmount.MEDIUM]),
- fee: FeeAmount.MEDIUM,
- recipient: env.l2Wallet.address,
- amount0Desired: 100000000,
- amount1Desired: 100000000,
- amount0Min: 0,
- amount1Min: 0,
- deadline: Date.now() * 2,
- },
- {
- gasLimit: 10000000,
- }
- )
- await tx.wait()
+ tokens = [
+ new Contract(process.env.UNISWAP_TOKEN_0_ADDRESS, ERC20.abi).connect(
+ env.l2Wallet
+ ),
+ new Contract(process.env.UNISWAP_TOKEN_1_ADDRESS, ERC20.abi).connect(
+ env.l2Wallet
+ ),
+ ]
+ tokens =
+ tokens[0].address.toLowerCase() < tokens[1].address.toLowerCase()
+ ? [tokens[0], tokens[1]]
+ : [tokens[1], tokens[0]]
})
setupRun(async () => {
diff --git a/integration-tests/contracts/ValueCalls.sol b/integration-tests/contracts/ValueCalls.sol
index 9817024eb98e8..101d4d3a4874f 100644
--- a/integration-tests/contracts/ValueCalls.sol
+++ b/integration-tests/contracts/ValueCalls.sol
@@ -23,6 +23,10 @@ contract ValueContext {
function getCallValue() public payable returns(uint256) {
return msg.value;
}
+
+ function getCaller() external view returns (address){
+ return msg.sender;
+ }
}
contract ValueCalls is ValueContext {
diff --git a/integration-tests/ext-test/snx.sh b/integration-tests/ext-test/snx.sh
index d02fc28e892d7..87db9fed93bc6 100755
--- a/integration-tests/ext-test/snx.sh
+++ b/integration-tests/ext-test/snx.sh
@@ -2,4 +2,4 @@
git clone --depth=1 --branch develop https://github.com/Synthetixio/synthetix.git
cd synthetix
npm install
-npx hardhat --config ./hardhat.config.js test:integration:l2 --compile --deploy
+npx hardhat --config ./hardhat.config.js test:integration:dual --compile --deploy
diff --git a/integration-tests/mocha.opts b/integration-tests/mocha.opts
deleted file mode 100644
index 3d063618887b9..0000000000000
--- a/integration-tests/mocha.opts
+++ /dev/null
@@ -1 +0,0 @@
---file ./test/setup-docker-compose-network.js
\ No newline at end of file
diff --git a/integration-tests/package.json b/integration-tests/package.json
index 4ee2f4949209e..46c873ebaf8e3 100644
--- a/integration-tests/package.json
+++ b/integration-tests/package.json
@@ -57,7 +57,6 @@
"envalid": "^7.1.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/integration-tests/test/basic-l1-l2-communication.spec.ts b/integration-tests/test/basic-l1-l2-communication.spec.ts
index 25adb249e04b9..2f84b823809e7 100644
--- a/integration-tests/test/basic-l1-l2-communication.spec.ts
+++ b/integration-tests/test/basic-l1-l2-communication.spec.ts
@@ -1,4 +1,4 @@
-import { expect } from 'chai'
+import { expect } from './shared/setup'
/* Imports: External */
import { Contract, ContractFactory } from 'ethers'
diff --git a/integration-tests/test/bridged-tokens.ts b/integration-tests/test/bridged-tokens.spec.ts
similarity index 99%
rename from integration-tests/test/bridged-tokens.ts
rename to integration-tests/test/bridged-tokens.spec.ts
index 5b466f659e54b..f992f2aad7769 100644
--- a/integration-tests/test/bridged-tokens.ts
+++ b/integration-tests/test/bridged-tokens.spec.ts
@@ -1,7 +1,8 @@
+import { expect } from './shared/setup'
+
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import * as L2Artifact from '@eth-optimism/contracts/artifacts/contracts/standards/L2StandardERC20.sol/L2StandardERC20.json'
-import { expect } from 'chai'
import { OptimismEnv } from './shared/env'
import { isLiveNetwork, isMainnet } from './shared/utils'
diff --git a/integration-tests/test/contracts.spec.ts b/integration-tests/test/contracts.spec.ts
index c5103ebdd0405..521a705be7699 100644
--- a/integration-tests/test/contracts.spec.ts
+++ b/integration-tests/test/contracts.spec.ts
@@ -1,7 +1,7 @@
+import { expect } from './shared/setup'
+
import { BigNumber, Contract, ContractFactory, utils, Wallet } from 'ethers'
import { ethers } from 'hardhat'
-import { solidity } from 'ethereum-waffle'
-import chai, { expect } from 'chai'
import { UniswapV3Deployer } from 'uniswap-v3-deploy-plugin/dist/deployer/UniswapV3Deployer'
import { OptimismEnv } from './shared/env'
@@ -10,8 +10,6 @@ import { FeeAmount, TICK_SPACINGS } from '@uniswap/v3-sdk'
import { abi as NFTABI } from '@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'
import { abi as RouterABI } from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'
-chai.use(solidity)
-
// Below methods taken from the Uniswap test suite, see
// https://github.com/Uniswap/v3-periphery/blob/main/test/shared/ticks.ts
const getMinTick = (tickSpacing: number) =>
diff --git a/integration-tests/test/fee-payment.spec.ts b/integration-tests/test/fee-payment.spec.ts
index b284eeda1a7f5..2dec3d69f51dc 100644
--- a/integration-tests/test/fee-payment.spec.ts
+++ b/integration-tests/test/fee-payment.spec.ts
@@ -1,6 +1,4 @@
-import chai, { expect } from 'chai'
-import chaiAsPromised from 'chai-as-promised'
-chai.use(chaiAsPromised)
+import { expect } from './shared/setup'
/* Imports: External */
import { BigNumber, utils } from 'ethers'
diff --git a/integration-tests/test/native-eth-ovm-calls.spec.ts b/integration-tests/test/native-eth-ovm-calls.spec.ts
index fd20c51214118..ae6261435baa8 100644
--- a/integration-tests/test/native-eth-ovm-calls.spec.ts
+++ b/integration-tests/test/native-eth-ovm-calls.spec.ts
@@ -1,15 +1,13 @@
+import { expect } from './shared/setup'
+
import { BigNumber, Contract, ContractFactory, Wallet } from 'ethers'
import { ethers } from 'hardhat'
-import chai, { expect } from 'chai'
import {
fundUser,
encodeSolidityRevertMessage,
gasPriceForL2,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
-import { solidity } from 'ethereum-waffle'
-
-chai.use(solidity)
describe('Native ETH value integration tests', () => {
let env: OptimismEnv
diff --git a/integration-tests/test/native-eth.spec.ts b/integration-tests/test/native-eth.spec.ts
index 2099d7eee960f..7df06492064a5 100644
--- a/integration-tests/test/native-eth.spec.ts
+++ b/integration-tests/test/native-eth.spec.ts
@@ -1,4 +1,4 @@
-import { expect } from 'chai'
+import { expect } from './shared/setup'
/* Imports: External */
import { Wallet, utils, BigNumber } from 'ethers'
diff --git a/integration-tests/test/ovmcontext.spec.ts b/integration-tests/test/ovmcontext.spec.ts
index 98cba0e2b90e6..1f6e2e03e991c 100644
--- a/integration-tests/test/ovmcontext.spec.ts
+++ b/integration-tests/test/ovmcontext.spec.ts
@@ -1,8 +1,8 @@
-import { expect } from 'chai'
+import { expect } from './shared/setup'
/* Imports: External */
import { ethers } from 'hardhat'
-import { injectL2Context } from '@eth-optimism/core-utils'
+import { injectL2Context, expectApprox } from '@eth-optimism/core-utils'
import { predeploys } from '@eth-optimism/contracts'
import { Contract, BigNumber } from 'ethers'
@@ -74,9 +74,11 @@ describe('OVM Context: Layer 2 EVM Context', () => {
const l1BlockNumber = await OVMContextStorage.l1BlockNumbers(i)
expect(l1BlockNumber.toNumber()).to.deep.equal(l1Block.number)
- // L1 and L2 blocks will have the same timestamp.
+ // L1 and L2 blocks will have approximately the same timestamp.
const timestamp = await OVMContextStorage.timestamps(i)
- expect(timestamp.toNumber()).to.deep.equal(l1Block.timestamp)
+ expectApprox(timestamp.toNumber(), l1Block.timestamp, {
+ percentUpperDeviation: 5,
+ })
expect(timestamp.toNumber()).to.deep.equal(l2Block.timestamp)
// Difficulty should always be zero.
diff --git a/integration-tests/test/predeploys.spec.ts b/integration-tests/test/predeploys.spec.ts
index a4710e3234756..57131585dc6b3 100644
--- a/integration-tests/test/predeploys.spec.ts
+++ b/integration-tests/test/predeploys.spec.ts
@@ -1,6 +1,4 @@
-import chai, { expect } from 'chai'
-import { solidity } from 'ethereum-waffle'
-chai.use(solidity)
+import { expect } from './shared/setup'
/* Imports: Internal */
import { ethers } from 'ethers'
diff --git a/integration-tests/test/queue-ingestion.spec.ts b/integration-tests/test/queue-ingestion.spec.ts
index 28ddec60ba860..c79de341600c2 100644
--- a/integration-tests/test/queue-ingestion.spec.ts
+++ b/integration-tests/test/queue-ingestion.spec.ts
@@ -1,4 +1,4 @@
-import { expect } from 'chai'
+import { expect } from './shared/setup'
/* Imports: Internal */
import { providers } from 'ethers'
diff --git a/integration-tests/test/replica.spec.ts b/integration-tests/test/replica.spec.ts
index b4ea35b8b4a0f..165461d90fe8c 100644
--- a/integration-tests/test/replica.spec.ts
+++ b/integration-tests/test/replica.spec.ts
@@ -1,3 +1,4 @@
+import { expect } from './shared/setup'
import { OptimismEnv } from './shared/env'
import {
defaultTransactionFactory,
@@ -5,7 +6,6 @@ import {
sleep,
isLiveNetwork,
} from './shared/utils'
-import { expect } from 'chai'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
describe('Replica Tests', () => {
diff --git a/integration-tests/test/rpc.spec.ts b/integration-tests/test/rpc.spec.ts
index 19936a2c7d296..b02dc0ac487e8 100644
--- a/integration-tests/test/rpc.spec.ts
+++ b/integration-tests/test/rpc.spec.ts
@@ -1,8 +1,9 @@
+import { expect } from './shared/setup'
+
import { expectApprox, injectL2Context } from '@eth-optimism/core-utils'
-import { Wallet, BigNumber, Contract, ContractFactory } from 'ethers'
+import { Wallet, BigNumber, Contract, ContractFactory, constants } from 'ethers'
import { serialize } from '@ethersproject/transactions'
import { ethers } from 'hardhat'
-import chai, { expect } from 'chai'
import {
sleep,
l2Provider,
@@ -12,18 +13,13 @@ import {
isLiveNetwork,
gasPriceForL2,
} from './shared/utils'
-import chaiAsPromised from 'chai-as-promised'
import { OptimismEnv } from './shared/env'
import {
TransactionReceipt,
TransactionRequest,
} from '@ethersproject/providers'
-import { solidity } from 'ethereum-waffle'
import simpleStorageJson from '../artifacts/contracts/SimpleStorage.sol/SimpleStorage.json'
-chai.use(chaiAsPromised)
-chai.use(solidity)
-
describe('Basic RPC tests', () => {
let env: OptimismEnv
let wallet: Wallet
@@ -237,6 +233,53 @@ describe('Basic RPC tests', () => {
expect(res).to.eq(BigNumber.from(value))
})
+
+ // https://github.com/ethereum-optimism/optimism/issues/1998
+ it('should use address(0) as the default "from" value', async () => {
+ // Deploy a contract to check msg.caller
+ const Factory__ValueContext: ContractFactory =
+ await ethers.getContractFactory('ValueContext', wallet)
+ const ValueContext: Contract = await Factory__ValueContext.deploy()
+ await ValueContext.deployTransaction.wait()
+
+ // Do the call and check msg.sender
+ const data = ValueContext.interface.encodeFunctionData('getCaller')
+ const res = await provider.call({
+ to: ValueContext.address,
+ data,
+ })
+
+ const [paddedRes] = ValueContext.interface.decodeFunctionResult(
+ 'getCaller',
+ res
+ )
+
+ expect(paddedRes).to.eq(constants.AddressZero)
+ })
+
+ it('should correctly use the "from" value', async () => {
+ // Deploy a contract to check msg.caller
+ const Factory__ValueContext: ContractFactory =
+ await ethers.getContractFactory('ValueContext', wallet)
+ const ValueContext: Contract = await Factory__ValueContext.deploy()
+ await ValueContext.deployTransaction.wait()
+
+ const from = wallet.address
+
+ // Do the call and check msg.sender
+ const data = ValueContext.interface.encodeFunctionData('getCaller')
+ const res = await provider.call({
+ to: ValueContext.address,
+ from,
+ data,
+ })
+
+ const [paddedRes] = ValueContext.interface.decodeFunctionResult(
+ 'getCaller',
+ res
+ )
+ expect(paddedRes).to.eq(from)
+ })
})
describe('eth_getTransactionReceipt', () => {
@@ -286,7 +329,7 @@ describe('Basic RPC tests', () => {
expect(receipt.status).to.eq(0)
})
- // Optimistic Ethereum special fields on the receipt
+ // Optimism special fields on the receipt
it('includes L1 gas price and L1 gas used', async () => {
const tx = await env.l2Wallet.populateTransaction({
to: env.l2Wallet.address,
diff --git a/integration-tests/test/setup-docker-compose-network.js b/integration-tests/test/setup-docker-compose-network.ts
similarity index 60%
rename from integration-tests/test/setup-docker-compose-network.js
rename to integration-tests/test/setup-docker-compose-network.ts
index 39efe8d444d23..12deb66966e5b 100644
--- a/integration-tests/test/setup-docker-compose-network.js
+++ b/integration-tests/test/setup-docker-compose-network.ts
@@ -1,4 +1,4 @@
-const { DockerComposeNetwork } = require('./shared/docker-compose')
+import { DockerComposeNetwork } from './shared/docker-compose'
before(async () => {
if (!process.env.NO_NETWORK) {
diff --git a/integration-tests/test/shared/env.ts b/integration-tests/test/shared/env.ts
index 41caae00388c8..98c54ab3bed5c 100644
--- a/integration-tests/test/shared/env.ts
+++ b/integration-tests/test/shared/env.ts
@@ -33,6 +33,7 @@ export class OptimismEnv {
addressManager: Contract
l1Bridge: Contract
l1Messenger: Contract
+ l1BlockNumber: Contract
ctc: Contract
scc: Contract
@@ -59,6 +60,7 @@ export class OptimismEnv {
this.addressManager = args.addressManager
this.l1Bridge = args.l1Bridge
this.l1Messenger = args.l1Messenger
+ this.l1BlockNumber = args.l1BlockNumber
this.ovmEth = args.ovmEth
this.l2Bridge = args.l2Bridge
this.l2Messenger = args.l2Messenger
@@ -113,12 +115,17 @@ export class OptimismEnv {
.connect(l2Wallet)
.attach(predeploys.OVM_SequencerFeeVault)
+ const l1BlockNumber = getContractFactory('iOVM_L1BlockNumber')
+ .connect(l2Wallet)
+ .attach(predeploys.OVM_L1BlockNumber)
+
return new OptimismEnv({
addressManager,
l1Bridge,
ctc,
scc,
l1Messenger,
+ l1BlockNumber,
ovmEth,
gasPriceOracle,
sequencerFeeVault,
diff --git a/integration-tests/test/shared/setup.ts b/integration-tests/test/shared/setup.ts
new file mode 100644
index 0000000000000..af83838b25967
--- /dev/null
+++ b/integration-tests/test/shared/setup.ts
@@ -0,0 +1,10 @@
+/* External Imports */
+import chai = require('chai')
+import chaiAsPromised from 'chai-as-promised'
+import { solidity } from 'ethereum-waffle'
+
+chai.use(solidity)
+chai.use(chaiAsPromised)
+const expect = chai.expect
+
+export { expect }
diff --git a/integration-tests/test/stress-tests.spec.ts b/integration-tests/test/stress-tests.spec.ts
index 56dc669c6317c..16873f0653f41 100644
--- a/integration-tests/test/stress-tests.spec.ts
+++ b/integration-tests/test/stress-tests.spec.ts
@@ -1,4 +1,4 @@
-import { expect } from 'chai'
+import { expect } from './shared/setup'
/* Imports: External */
import { Contract, ContractFactory, Wallet, utils } from 'ethers'
@@ -211,4 +211,29 @@ describe('stress tests', () => {
)
}).timeout(STRESS_TEST_TIMEOUT)
})
+
+ // These tests depend on an archive node due to the historical `eth_call`s
+ describe('Monotonicity Checks', () => {
+ it('should have monotonic timestamps and l1 blocknumbers', async () => {
+ const tip = await env.l2Provider.getBlock('latest')
+ const prev = {
+ block: await env.l2Provider.getBlock(0),
+ l1BlockNumber: await env.l1BlockNumber.getL1BlockNumber({
+ blockTag: 0,
+ }),
+ }
+ for (let i = 1; i < tip.number; i++) {
+ const block = await env.l2Provider.getBlock(i)
+ expect(block.timestamp).to.be.gte(prev.block.timestamp)
+
+ const l1BlockNumber = await env.l1BlockNumber.getL1BlockNumber({
+ blockTag: i,
+ })
+ expect(l1BlockNumber.gt(prev.l1BlockNumber))
+
+ prev.block = block
+ prev.l1BlockNumber = l1BlockNumber
+ }
+ })
+ })
})
diff --git a/integration-tests/test/whitelist.spec.ts b/integration-tests/test/whitelist.spec.ts
index dda34cf77f0b2..0e64d661d3371 100644
--- a/integration-tests/test/whitelist.spec.ts
+++ b/integration-tests/test/whitelist.spec.ts
@@ -1,16 +1,14 @@
+import { expect } from './shared/setup'
+
/* Imports: External */
import { ContractFactory } from 'ethers'
import { ethers } from 'hardhat'
-import chai, { expect } from 'chai'
-import { solidity } from 'ethereum-waffle'
import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */
import { OptimismEnv } from './shared/env'
import { l2Provider } from './shared/utils'
-chai.use(solidity)
-
describe('Whitelist', async () => {
const initialAmount = 1000
const tokenName = 'OVM Test'
diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json
index 4447e74044f08..8ef06dc0f7c66 100644
--- a/integration-tests/tsconfig.json
+++ b/integration-tests/tsconfig.json
@@ -8,6 +8,7 @@
"sync-tests/*.ts",
"./actor-tests/**/*.ts",
"./artifacts/**/*.json",
+ "./tasks/**/*.ts",
"./package.json"
],
"files": ["./hardhat.config.ts"]
diff --git a/l2geth/internal/ethapi/api.go b/l2geth/internal/ethapi/api.go
index 8c598fdbda94c..bbb33ea1a6b47 100644
--- a/l2geth/internal/ethapi/api.go
+++ b/l2geth/internal/ethapi/api.go
@@ -799,9 +799,11 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
// Set sender address or use a default if none specified
var addr common.Address
if args.From == nil {
- if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
- if accounts := wallets[0].Accounts(); len(accounts) > 0 {
- addr = accounts[0].Address
+ if !rcfg.UsingOVM {
+ if wallets := b.AccountManager().Wallets(); len(wallets) > 0 {
+ if accounts := wallets[0].Accounts(); len(accounts) > 0 {
+ addr = accounts[0].Address
+ }
}
}
} else {
diff --git a/l2geth/rollup/sync_service.go b/l2geth/rollup/sync_service.go
index d0cd50549617a..6e4b1eb2ab880 100644
--- a/l2geth/rollup/sync_service.go
+++ b/l2geth/rollup/sync_service.go
@@ -436,7 +436,7 @@ func (s *SyncService) SequencerLoop() {
}
s.txLock.Unlock()
- if err := s.updateContext(); err != nil {
+ if err := s.updateL1BlockNumber(); err != nil {
log.Error("Could not update execution context", "error", err)
}
}
@@ -599,17 +599,15 @@ func (s *SyncService) GasPriceOracleOwnerAddress() *common.Address {
/// Update the execution context's timestamp and blocknumber
/// over time. This is only necessary for the sequencer.
-func (s *SyncService) updateContext() error {
+func (s *SyncService) updateL1BlockNumber() error {
context, err := s.client.GetLatestEthContext()
if err != nil {
- return err
+ return fmt.Errorf("Cannot get eth context: %w", err)
}
- current := time.Unix(int64(s.GetLatestL1Timestamp()), 0)
- next := time.Unix(int64(context.Timestamp), 0)
- if next.Sub(current) > s.timestampRefreshThreshold {
- log.Info("Updating Eth Context", "timetamp", context.Timestamp, "blocknumber", context.BlockNumber)
+ latest := s.GetLatestL1BlockNumber()
+ if context.BlockNumber > latest {
+ log.Info("Updating L1 block number", "blocknumber", context.BlockNumber)
s.SetLatestL1BlockNumber(context.BlockNumber)
- s.SetLatestL1Timestamp(context.Timestamp)
}
return nil
}
@@ -798,31 +796,61 @@ func (s *SyncService) applyTransactionToTip(tx *types.Transaction) error {
return fmt.Errorf("Queue origin L1 to L2 transaction without a timestamp: %s", tx.Hash().Hex())
}
}
- // If there is no OVM timestamp assigned to the transaction, then assign a
- // timestamp and blocknumber to it. This should only be the case for queue
- // origin sequencer transactions that come in via RPC. The L1 to L2
- // transactions that come in via `enqueue` should have a timestamp set based
- // on the L1 block that it was included in.
- // Note that Ethereum Layer one consensus rules dictate that the timestamp
- // must be strictly increasing between blocks, so no need to check both the
- // timestamp and the blocknumber.
+
+ // If there is no L1 timestamp assigned to the transaction, then assign a
+ // timestamp to it. The property that L1 to L2 transactions have the same
+ // timestamp as the L1 block that it was included in is removed for better
+ // UX. This functionality can be added back in during a future release. For
+ // now, the sequencer will assign a timestamp to each transaction.
ts := s.GetLatestL1Timestamp()
bn := s.GetLatestL1BlockNumber()
- if tx.L1Timestamp() == 0 {
- tx.SetL1Timestamp(ts)
- tx.SetL1BlockNumber(bn)
- } else if tx.L1Timestamp() > s.GetLatestL1Timestamp() {
- // If the timestamp of the transaction is greater than the sync
- // service's locally maintained timestamp, update the timestamp and
- // blocknumber to equal that of the transaction's. This should happen
- // with `enqueue` transactions.
- s.SetLatestL1Timestamp(tx.L1Timestamp())
- s.SetLatestL1BlockNumber(tx.L1BlockNumber().Uint64())
- log.Debug("Updating OVM context based on new transaction", "timestamp", ts, "blocknumber", tx.L1BlockNumber().Uint64(), "queue-origin", tx.QueueOrigin())
+
+ // The L1Timestamp is 0 for QueueOriginSequencer transactions when
+ // running as the sequencer, the transactions are coming in via RPC.
+ // This code path also runs for replicas/verifiers so any logic involving
+ // `time.Now` can only run for the sequencer. All other nodes must listen
+ // to what the sequencer says is the timestamp, otherwise there will be a
+ // network split.
+ // Note that it should never be possible for the timestamp to be set to
+ // 0 when running as a verifier.
+ shouldMalleateTimestamp := !s.verifier && tx.QueueOrigin() == types.QueueOriginL1ToL2
+ if tx.L1Timestamp() == 0 || shouldMalleateTimestamp {
+ // Get the latest known timestamp
+ current := time.Unix(int64(ts), 0)
+ // Get the current clocktime
+ now := time.Now()
+ // If enough time has passed, then assign the
+ // transaction to have the timestamp now. Otherwise,
+ // use the current timestamp
+ if now.Sub(current) > s.timestampRefreshThreshold {
+ current = now
+ }
+ tx.SetL1Timestamp(uint64(current.Unix()))
+ } else if tx.L1Timestamp() == 0 && s.verifier {
+ // This should never happen
+ log.Error("No tx timestamp found when running as verifier", "hash", tx.Hash().Hex())
} else if tx.L1Timestamp() < s.GetLatestL1Timestamp() {
+ // This should never happen, but sometimes does
log.Error("Timestamp monotonicity violation", "hash", tx.Hash().Hex())
}
+ l1BlockNumber := tx.L1BlockNumber()
+ // Set the L1 blocknumber
+ if l1BlockNumber == nil {
+ tx.SetL1BlockNumber(bn)
+ } else if l1BlockNumber.Uint64() > s.GetLatestL1BlockNumber() {
+ s.SetLatestL1BlockNumber(l1BlockNumber.Uint64())
+ } else {
+ // l1BlockNumber < latest l1BlockNumber
+ // indicates an error
+ log.Error("Blocknumber monotonicity violation", "hash", tx.Hash().Hex())
+ }
+
+ // Store the latest timestamp value
+ if tx.L1Timestamp() > ts {
+ s.SetLatestL1Timestamp(tx.L1Timestamp())
+ }
+
index := s.GetLatestIndex()
if tx.GetMeta().Index == nil {
if index == nil {
@@ -1186,24 +1214,6 @@ func (s *SyncService) syncTransactionRange(start, end uint64, backend Backend) e
return nil
}
-// updateEthContext will update the OVM execution context's
-// timestamp and blocknumber if enough time has passed since
-// it was last updated. This is a sequencer only function.
-func (s *SyncService) updateEthContext() error {
- context, err := s.client.GetLatestEthContext()
- if err != nil {
- return fmt.Errorf("Cannot get eth context: %w", err)
- }
- current := time.Unix(int64(s.GetLatestL1Timestamp()), 0)
- next := time.Unix(int64(context.Timestamp), 0)
- if next.Sub(current) > s.timestampRefreshThreshold {
- log.Info("Updating Eth Context", "timetamp", context.Timestamp, "blocknumber", context.BlockNumber)
- s.SetLatestL1BlockNumber(context.BlockNumber)
- s.SetLatestL1Timestamp(context.Timestamp)
- }
- return nil
-}
-
// SubscribeNewTxsEvent registers a subscription of NewTxsEvent and
// starts sending event to the given channel.
func (s *SyncService) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
diff --git a/l2geth/rollup/sync_service_test.go b/l2geth/rollup/sync_service_test.go
index e2c4241e2b6cf..6d7f16677178e 100644
--- a/l2geth/rollup/sync_service_test.go
+++ b/l2geth/rollup/sync_service_test.go
@@ -25,71 +25,153 @@ import (
"github.com/ethereum-optimism/optimism/l2geth/rollup/rcfg"
)
-func setupLatestEthContextTest() (*SyncService, *EthContext) {
- service, _, _, _ := newTestSyncService(false, nil)
- resp := &EthContext{
- BlockNumber: uint64(10),
- BlockHash: common.Hash{},
- Timestamp: uint64(service.timestampRefreshThreshold.Seconds()) + 1,
+// Test that the timestamps are updated correctly.
+// This impacts execution, for `block.timestamp`
+func TestSyncServiceTimestampUpdate(t *testing.T) {
+ service, txCh, _, err := newTestSyncService(false, nil)
+ if err != nil {
+ t.Fatal(err)
}
- setupMockClient(service, map[string]interface{}{
- "GetLatestEthContext": resp,
- })
- return service, resp
-}
+ // Get the timestamp from the sync service
+ // It should be initialized to 0
+ ts := service.GetLatestL1Timestamp()
+ if ts != 0 {
+ t.Fatalf("Unexpected timestamp: %d", ts)
+ }
-// Test that if applying a transaction fails
-func TestSyncServiceContextUpdated(t *testing.T) {
- service, resp := setupLatestEthContextTest()
+ // Create a mock transaction and assert that its timestamp
+ // a value. This tests the case that the timestamp does
+ // not get malleated when it is set to a non zero value
+ timestamp := uint64(1)
+ tx1 := setMockTxL1Timestamp(mockTx(), timestamp)
+ if tx1.GetMeta().L1Timestamp != timestamp {
+ t.Fatalf("Expecting mock timestamp to be %d", timestamp)
+ }
+ if tx1.GetMeta().QueueOrigin != types.QueueOriginSequencer {
+ t.Fatalf("Expecting mock queue origin to be queue origin sequencer")
+ }
+
+ go func() {
+ err = service.applyTransactionToTip(tx1)
+ }()
+ event1 := <-txCh
- // should get the expected context
- expectedCtx := &OVMContext{
- blockNumber: 0,
- timestamp: 0,
+ // Ensure that the timestamp isn't malleated
+ if event1.Txs[0].GetMeta().L1Timestamp != timestamp {
+ t.Fatalf("Timestamp was malleated: %d", event1.Txs[0].GetMeta().L1Timestamp)
+ }
+ // Ensure that the timestamp in the sync service was updated
+ if service.GetLatestL1Timestamp() != timestamp {
+ t.Fatal("timestamp updated in sync service")
}
- if service.OVMContext != *expectedCtx {
- t.Fatal("context was not instantiated to the expected value")
+ // Now test the case for when a transaction is malleated.
+ // If the timestamp is 0, then it should be malleated and set
+ // equal to whatever the latestL1Timestamp is
+ tx2 := mockTx()
+ if tx2.GetMeta().L1Timestamp != 0 {
+ t.Fatal("Expecting mock timestamp to be 0")
}
+ go func() {
+ err = service.applyTransactionToTip(tx2)
+ }()
+ event2 := <-txCh
- // run the update context call once
- err := service.updateContext()
+ // Ensure that the sync service timestamp is updated
+ if service.GetLatestL1Timestamp() == 0 {
+ t.Fatal("timestamp not updated")
+ }
+ // Ensure that the timestamp is malleated to be equal to what the sync
+ // service has as the latest timestamp
+ if event2.Txs[0].GetMeta().L1Timestamp != service.GetLatestL1Timestamp() {
+ t.Fatal("unexpected timestamp update")
+ }
+
+ // L1ToL2 transactions should have their timestamp malleated
+ // Be sure to set the timestamp to a non zero value so that
+ // its specifically testing the fact its a deposit tx
+ tx3 := setMockQueueOrigin(setMockTxL1Timestamp(mockTx(), 100), types.QueueOriginL1ToL2)
+ // Get a reference to the timestamp before transaction execution
+ ts3 := service.GetLatestL1Timestamp()
+
+ go func() {
+ err = service.applyTransactionToTip(tx3)
+ }()
+ event3 := <-txCh
+
+ if event3.Txs[0].GetMeta().L1Timestamp != ts3 {
+ t.Fatal("bad malleation")
+ }
+ // Ensure that the timestamp didn't change
+ if ts3 != service.GetLatestL1Timestamp() {
+ t.Fatal("timestamp updated when it shouldn't have")
+ }
+}
+
+// Test that the L1 blocknumber is updated correctly
+func TestSyncServiceL1BlockNumberUpdate(t *testing.T) {
+ service, txCh, _, err := newTestSyncService(false, nil)
if err != nil {
t.Fatal(err)
}
- // should get the expected context
- expectedCtx = &OVMContext{
- blockNumber: resp.BlockNumber,
- timestamp: resp.Timestamp,
+ // Get the L1 blocknumber from the sync service
+ // It should be initialized to 0
+ bn := service.GetLatestL1BlockNumber()
+ if bn != 0 {
+ t.Fatalf("Unexpected timestamp: %d", bn)
+ }
+
+ tx1 := setMockTxL1BlockNumber(mockTx(), new(big.Int).SetUint64(1))
+ go func() {
+ err = service.applyTransactionToTip(tx1)
+ }()
+ event1 := <-txCh
+
+ // Ensure that the L1 blocknumber was not
+ // malleated
+ if event1.Txs[0].L1BlockNumber().Uint64() != 1 {
+ t.Fatal("wrong l1 blocknumber")
}
- if service.OVMContext != *expectedCtx {
- t.Fatal("context was not updated to the expected response even though enough time passed")
+ // Ensure that the latest L1 blocknumber was
+ // updated
+ if service.GetLatestL1BlockNumber() != 1 {
+ t.Fatal("sync service latest l1 blocknumber not updated")
}
- // updating the context should be a no-op if time advanced by less than
- // the refresh period
- resp.BlockNumber += 1
- resp.Timestamp += uint64(service.timestampRefreshThreshold.Seconds())
- setupMockClient(service, map[string]interface{}{
- "GetLatestEthContext": resp,
- })
+ // Ensure that a tx without a L1 blocknumber gets one
+ // assigned
+ tx2 := setMockTxL1BlockNumber(mockTx(), nil)
+ if tx2.L1BlockNumber() != nil {
+ t.Fatal("non nil l1 blocknumber")
+ }
+ go func() {
+ err = service.applyTransactionToTip(tx2)
+ }()
+ event2 := <-txCh
- // call it again
- err = service.updateContext()
- if err != nil {
- t.Fatal(err)
+ if event2.Txs[0].L1BlockNumber() == nil {
+ t.Fatal("tx not assigned an l1 blocknumber")
+ }
+ if event2.Txs[0].L1BlockNumber().Uint64() != service.GetLatestL1BlockNumber() {
+ t.Fatal("tx assigned incorrect l1 blocknumber")
}
- // should not get the context from the response because it was too soon
- unexpectedCtx := &OVMContext{
- blockNumber: resp.BlockNumber,
- timestamp: resp.Timestamp,
+ // Ensure that the latest L1 blocknumber doesn't go backwards
+ latest := service.GetLatestL1BlockNumber()
+ tx3 := setMockTxL1BlockNumber(mockTx(), new(big.Int).SetUint64(latest-1))
+ go func() {
+ err = service.applyTransactionToTip(tx3)
+ }()
+ event3 := <-txCh
+ if service.GetLatestL1BlockNumber() != latest {
+ t.Fatal("block number went backwards")
}
- if service.OVMContext == *unexpectedCtx {
- t.Fatal("context should not be updated because not enough time passed")
+
+ if event3.Txs[0].L1BlockNumber().Uint64() != latest-1 {
+ t.Fatal("l1 block number was malleated")
}
}
@@ -257,20 +339,32 @@ func TestTransactionToTipTimestamps(t *testing.T) {
}
}
+ // Ensure that the timestamp was updated correctly
+ ts := service.GetLatestL1Timestamp()
+ if ts != tx2.L1Timestamp() {
+ t.Fatal("timestamp not updated correctly")
+ }
+
// Send a transaction with no timestamp and then let it be updated
- // by the sync service. This will prevent monotonicity errors as well
+ // by the sync service. This will prevent monotonicity errors as well.
// as give timestamps to queue origin sequencer transactions
- ts := service.GetLatestL1Timestamp()
+ // Ensure that the timestamp is set to `time.Now`
+ // when it is not set.
tx3 := setMockTxL1Timestamp(mockTx(), 0)
+ now := time.Now()
go func() {
err = service.applyTransactionToTip(tx3)
}()
result := <-txCh
service.chainHeadCh <- core.ChainHeadEvent{}
- if result.Txs[0].L1Timestamp() != ts {
+ if result.Txs[0].L1Timestamp() != uint64(now.Unix()) {
t.Fatal("Timestamp not updated correctly")
}
+
+ if service.GetLatestL1Timestamp() != uint64(now.Unix()) {
+ t.Fatal("latest timestamp not updated correctly")
+ }
}
func TestApplyIndexedTransaction(t *testing.T) {
@@ -1036,6 +1130,13 @@ func setMockTxL1Timestamp(tx *types.Transaction, ts uint64) *types.Transaction {
return tx
}
+func setMockTxL1BlockNumber(tx *types.Transaction, bn *big.Int) *types.Transaction {
+ meta := tx.GetMeta()
+ meta.L1BlockNumber = bn
+ tx.SetTransactionMeta(meta)
+ return tx
+}
+
func setMockTxIndex(tx *types.Transaction, index uint64) *types.Transaction {
meta := tx.GetMeta()
meta.Index = &index
@@ -1050,6 +1151,13 @@ func setMockQueueIndex(tx *types.Transaction, index uint64) *types.Transaction {
return tx
}
+func setMockQueueOrigin(tx *types.Transaction, qo types.QueueOrigin) *types.Transaction {
+ meta := tx.GetMeta()
+ meta.QueueOrigin = qo
+ tx.SetTransactionMeta(meta)
+ return tx
+}
+
func newUint64(n uint64) *uint64 {
return &n
}
diff --git a/ops/docker-compose.yml b/ops/docker-compose.yml
index fb52356d0dc53..1c428bbebfda9 100644
--- a/ops/docker-compose.yml
+++ b/ops/docker-compose.yml
@@ -22,28 +22,33 @@ services:
target: deployer
entrypoint: ./deployer.sh
environment:
- FRAUD_PROOF_WINDOW_SECONDS: 0
- L1_NODE_WEB3_URL: http://l1_chain:8545
- # these keys are hardhat's first 3 accounts, DO NOT use in production
- DEPLOYER_PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
- SEQUENCER_PRIVATE_KEY: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
- PROPOSER_PRIVATE_KEY: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
- # Default hardhat account 5
- GAS_PRICE_ORACLE_OWNER: "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc"
- # setting the whitelist owner to address(0) disables the whitelist
- WHITELIST_OWNER: "0x0000000000000000000000000000000000000000"
- L1_FEE_WALLET_ADDRESS: "0x391716d440c151c42cdf1c95c1d83a5427bca52c"
- L2_CHAIN_ID: 420
- L2_BLOCK_GAS_LIMIT: 15000000
- BLOCK_SIGNER_ADDRESS: "0x00000398232E2064F896018496b4b44b3D62751F"
- GAS_PRICE_ORACLE_OVERHEAD: "2750"
- GAS_PRICE_ORACLE_SCALAR: "1500000"
- GAS_PRICE_ORACLE_L1_BASE_FEE: "1"
- GAS_PRICE_ORACLE_GAS_PRICE: "1"
- GAS_PRICE_ORACLE_DECIMALS: "6"
- # skip compilation when run in docker-compose, since the contracts
- # were already compiled in the builder step
- NO_COMPILE: 1
+ # Env vars for the deployment script.
+ CONTRACTS_RPC_URL: http://l1_chain:8545
+ CONTRACTS_DEPLOYER_KEY: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
+ CONTRACTS_TARGET_NETWORK: "custom"
+ OVM_ADDRESS_MANAGER_OWNER: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266"
+ OVM_PROPOSER_ADDRESS: "0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc"
+ OVM_SEQUENCER_ADDRESS: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8"
+ SCC_FRAUD_PROOF_WINDOW: 0
+ NUM_DEPLOY_CONFIRMATIONS: 0
+ # skip compilation when run in docker-compose, since the contracts
+ # were already compiled in the builder step
+ NO_COMPILE: 1
+
+ # Env vars for the dump script.
+ # Default hardhat account 5
+ GAS_PRICE_ORACLE_OWNER: "0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc"
+ # setting the whitelist owner to address(0) disables the whitelist
+ WHITELIST_OWNER: "0x0000000000000000000000000000000000000000"
+ L1_FEE_WALLET_ADDRESS: "0x391716d440c151c42cdf1c95c1d83a5427bca52c"
+ L2_CHAIN_ID: 420
+ L2_BLOCK_GAS_LIMIT: 15000000
+ BLOCK_SIGNER_ADDRESS: "0x00000398232E2064F896018496b4b44b3D62751F"
+ GAS_PRICE_ORACLE_OVERHEAD: 2750
+ GAS_PRICE_ORACLE_SCALAR: 1500000
+ GAS_PRICE_ORACLE_L1_BASE_FEE: 1
+ GAS_PRICE_ORACLE_GAS_PRICE: 1
+ GAS_PRICE_ORACLE_DECIMALS: 6
ports:
# expose the service to the host for getting the contract addrs
- ${DEPLOYER_PORT:-8080}:8081
diff --git a/ops/docker/Dockerfile.deployer b/ops/docker/Dockerfile.deployer
index c31ee0bf9a191..86ac125d3c1d5 100644
--- a/ops/docker/Dockerfile.deployer
+++ b/ops/docker/Dockerfile.deployer
@@ -34,4 +34,4 @@ COPY packages/contracts/test/helpers/constants.ts ./test/helpers/constants.ts
COPY packages/contracts/scripts ./scripts
COPY ./ops/scripts/deployer.sh .
-ENTRYPOINT yarn run deploy
+CMD ./ops/scripts/deployer.sh
diff --git a/ops/docker/Dockerfile.message-relayer b/ops/docker/Dockerfile.message-relayer
index 2e5d1a78ac07e..3b9cf0569fb0c 100644
--- a/ops/docker/Dockerfile.message-relayer
+++ b/ops/docker/Dockerfile.message-relayer
@@ -28,7 +28,6 @@ COPY --from=builder /optimism/packages/contracts/artifacts ./packages/contracts/
WORKDIR /opt/optimism/packages/message-relayer
COPY --from=builder /optimism/packages/message-relayer/dist ./dist
COPY --from=builder /optimism/packages/message-relayer/package.json .
-COPY --from=builder /optimism/packages/message-relayer/exec ./exec
COPY --from=builder /optimism/packages/message-relayer/node_modules ./node_modules
# copy this over in case you want to run alongside other services
diff --git a/ops/scripts/deployer.sh b/ops/scripts/deployer.sh
index a7fb16b859fec..9bb8f1c87806a 100755
--- a/ops/scripts/deployer.sh
+++ b/ops/scripts/deployer.sh
@@ -1,9 +1,14 @@
#!/bin/bash
-set -e
+set -euo
RETRIES=${RETRIES:-20}
JSON='{"jsonrpc":"2.0","id":0,"method":"net_version","params":[]}'
+if [ -z "$CONTRACTS_RPC_URL" ]; then
+ echo "Must specify \$CONTRACTS_RPC_URL."
+ exit 1
+fi
+
# wait for the base layer to be up
curl \
--fail \
@@ -14,30 +19,82 @@ curl \
--retry $RETRIES \
--retry-delay 1 \
-d $JSON \
- $L1_NODE_WEB3_URL
+ $CONTRACTS_RPC_URL > /dev/null
-yarn run deploy
+echo "Connected to L1."
+echo "Building deployment command."
-function envSet() {
- VAR=$1
- export $VAR=$(cat ./dist/dumps/addresses.json | jq -r ".$2")
+DEPLOY_CMD="npx hardhat deploy"
+
+# Helper method to concatenate things onto $DEPLOY_CMD.
+# Usage: concat_cmd "str-to-concat"
+function concat_cmd() {
+ DEPLOY_CMD="$DEPLOY_CMD $1"
}
-# set the address to the proxy gateway if possible
-envSet L1_STANDARD_BRIDGE_ADDRESS Proxy__OVM_L1StandardBridge
-if [ $L1_STANDARD_BRIDGE_ADDRESS == null ]; then
- envSet L1_STANDARD_BRIDGE_ADDRESS L1StandardBridge
-fi
+# Helper method to conditionally concatenate CLI arguments
+# when a given env var is defined.
+# Usage: concat_arg "--arg-name" "env-var-name"
+function concat_arg() {
+ ARG=$1
+ ENV_VAR=$2
+ if [ -n "${!ENV_VAR+x}" ]; then
+ echo "$ENV_VAR set to ${!ENV_VAR}, applying $ARG argument."
+ concat_cmd "$ARG \"${!ENV_VAR}\""
+ else
+ echo "$ENV_VAR is not not set, skipping $ARG argument."
+ fi
+}
+
+concat_arg "--network" "CONTRACTS_TARGET_NETWORK"
+concat_arg "--ovm-address-manager-owner" "OVM_ADDRESS_MANAGER_OWNER"
+concat_arg "--ovm-proposer-address" "OVM_PROPOSER_ADDRESS"
+concat_arg "--ovm-sequencer-address" "OVM_SEQUENCER_ADDRESS"
+concat_arg "--l1-block-time-seconds" "L1_BLOCK_TIME_SECONDS"
+concat_arg "--ctc-max-transaction-gas-limit" "CTC_MAX_TRANSACTION_GAS_LIMIT"
+concat_arg "--ctc-l2-gas-discount-divisor" "CTC_L2_GAS_DISCOUNT_DIVISOR"
+concat_arg "--ctc-enqueue-gas-cost" "CTC_ENQUEUE_GAS_COST"
+concat_arg "--scc-fraud-proof-window" "SCC_FRAUD_PROOF_WINDOW"
+concat_arg "--num-deploy-confirmations" "NUM_DEPLOY_CONFIRMATIONS"
+concat_arg "--forked" "FORKED"
+
+echo "Deploying contracts. Deployment command:"
+echo "$DEPLOY_CMD"
+eval "$DEPLOY_CMD"
+
+echo "Building addresses.json."
+export ADDRESS_MANAGER_ADDRESS=$(cat "./deployments/$CONTRACTS_TARGET_NETWORK/Lib_AddressManager.json" | jq -r .address)
+
+# First, create two files. One of them contains a list of addresses, the other contains a list of contract names.
+find "./deployments/$CONTRACTS_TARGET_NETWORK" -maxdepth 1 -name '*.json' | xargs cat | jq -r '.address' > addresses.txt
+find "./deployments/$CONTRACTS_TARGET_NETWORK" -maxdepth 1 -name '*.json' | sed -e "s/.\/deployments\/$CONTRACTS_TARGET_NETWORK\///g" | sed -e 's/.json//g' > filenames.txt
+
+# Start building addresses.json.
+echo "{" >> addresses.json
+# Zip the two files describe above together, then, switch their order and format as JSON.
+paste addresses.txt filenames.txt | sed -e "s/^\([^ ]\+\)\s\+\([^ ]\+\)/\"\2\": \"\1\",/" >> addresses.json
+# Add the address manager alias.
+echo "\"AddressManager\": \"$ADDRESS_MANAGER_ADDRESS\"" >> addresses.json
+# End addresses.json
+echo "}" >> addresses.json
+
+echo "Built addresses.json. Content:"
+jq . addresses.json
+
+echo "Env vars for the dump script:"
+export L1_STANDARD_BRIDGE_ADDRESS=$(cat "./addresses.json" | jq -r .Proxy__OVM_L1StandardBridge)
+export L1_CROSS_DOMAIN_MESSENGER_ADDRESS=$(cat "./addresses.json" | jq -r .Proxy__OVM_L1CrossDomainMessenger)
+echo "ADDRESS_MANAGER_ADDRESS=$ADDRESS_MANAGER_ADDRESS"
+echo "L1_STANDARD_BRIDGE_ADDRESS=$L1_STANDARD_BRIDGE_ADDRESS"
+echo "L1_CROSS_DOMAIN_MESSENGER_ADDRESS=$L1_CROSS_DOMAIN_MESSENGER_ADDRESS"
-envSet L1_CROSS_DOMAIN_MESSENGER_ADDRESS Proxy__OVM_L1CrossDomainMessenger
-if [ $L1_CROSS_DOMAIN_MESSENGER_ADDRESS == null ]; then
- envSet L1_CROSS_DOMAIN_MESSENGER_ADDRESS L1CrossDomainMessenger
-fi
-# build the dump file
+echo "Building dump file."
yarn run build:dump
+mv addresses.json ./dist/dumps
# service the addresses and dumps
+echo "Starting server."
python3 -m http.server \
--bind "0.0.0.0" 8081 \
--directory ./dist/dumps
diff --git a/package.json b/package.json
index 5aeab0ca09228..7d8a31f8ec361 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,6 @@
"babel-eslint": "^10.1.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/batch-submitter/package.json b/packages/batch-submitter/package.json
index 99a42b5b79120..d27152d2f6d62 100644
--- a/packages/batch-submitter/package.json
+++ b/packages/batch-submitter/package.json
@@ -63,7 +63,6 @@
"chai": "^4.3.4",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/batch-submitter/src/exec/run-batch-submitter.ts b/packages/batch-submitter/src/exec/run-batch-submitter.ts
index e9fb9e399a8c9..f7ba17dead3f8 100644
--- a/packages/batch-submitter/src/exec/run-batch-submitter.ts
+++ b/packages/batch-submitter/src/exec/run-batch-submitter.ts
@@ -430,20 +430,21 @@ export const run = async () => {
// Loops infinitely!
const loop = async (
- func: () => Promise
+ func: () => Promise,
+ signer: Signer
): Promise => {
// Clear all pending transactions
if (clearPendingTxs) {
try {
- const pendingTxs = await sequencerSigner.getTransactionCount('pending')
- const latestTxs = await sequencerSigner.getTransactionCount('latest')
+ const pendingTxs = await signer.getTransactionCount('pending')
+ const latestTxs = await signer.getTransactionCount('latest')
if (pendingTxs > latestTxs) {
logger.info(
'Detected pending transactions. Clearing all transactions!'
)
for (let i = latestTxs; i < pendingTxs; i++) {
- const response = await sequencerSigner.sendTransaction({
- to: await sequencerSigner.getAddress(),
+ const response = await signer.sendTransaction({
+ to: await signer.getAddress(),
value: 0,
nonce: i,
})
@@ -456,7 +457,7 @@ export const run = async () => {
logger.debug('empty transaction data', {
data: response.data,
})
- await sequencerSigner.provider.waitForTransaction(
+ await signer.provider.waitForTransaction(
response.hash,
requiredEnvVars.NUM_CONFIRMATIONS
)
@@ -508,10 +509,10 @@ export const run = async () => {
// Run batch submitters in two seperate infinite loops!
if (requiredEnvVars.RUN_TX_BATCH_SUBMITTER) {
- loop(() => txBatchSubmitter.submitNextBatch())
+ loop(() => txBatchSubmitter.submitNextBatch(), sequencerSigner)
}
if (requiredEnvVars.RUN_STATE_BATCH_SUBMITTER) {
- loop(() => stateBatchSubmitter.submitNextBatch())
+ loop(() => stateBatchSubmitter.submitNextBatch(), proposerSigner)
}
if (config.bool('run-metrics-server', env.RUN_METRICS_SERVER === 'true')) {
diff --git a/packages/common-ts/package.json b/packages/common-ts/package.json
index 9e613354fa4a9..eb3b63af5b497 100644
--- a/packages/common-ts/package.json
+++ b/packages/common-ts/package.json
@@ -50,7 +50,6 @@
"chai": "^4.3.4",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/contracts/.solcover.js b/packages/contracts/.solcover.js
index 25399fdaa130b..38129287e592d 100644
--- a/packages/contracts/.solcover.js
+++ b/packages/contracts/.solcover.js
@@ -1,7 +1,8 @@
module.exports = {
skipFiles: [
'./test-helpers',
- './test-libraries'
+ './test-libraries',
+ './L2/predeploys/OVM_DeployerWhitelist.sol'
],
mocha: {
grep: "@skip-on-coverage",
diff --git a/packages/contracts/README.md b/packages/contracts/README.md
index 322b0fc9daf15..f30d2f83d7c90 100644
--- a/packages/contracts/README.md
+++ b/packages/contracts/README.md
@@ -1,9 +1,9 @@
[](https://codecov.io/gh/ethereum-optimism/optimism)
-# Optimistic Ethereum Smart Contracts
+# Optimism Smart Contracts
-`@eth-optimism/contracts` contains the various Solidity smart contracts used within the Optimistic Ethereum system.
-Some of these contracts are deployed on Ethereum ("Layer 1"), while others are meant to be deployed to Optimistic Ethereum ("Layer 2").
+`@eth-optimism/contracts` contains the various Solidity smart contracts used within the Optimism system.
+Some of these contracts are deployed on Ethereum ("Layer 1"), while others are meant to be deployed to Optimism ("Layer 2").
Within each contract file you'll find a comment that lists:
1. The compiler with which a contract is intended to be compiled, `solc` or `optimistic-solc`.
diff --git a/packages/contracts/bin/deploy.ts b/packages/contracts/bin/deploy.ts
deleted file mode 100755
index d02c9c45b605f..0000000000000
--- a/packages/contracts/bin/deploy.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-// WARNING: DO NOT USE THIS FILE TO DEPLOY CONTRACTS TO PRODUCTION
-// WE ARE REMOVING THIS FILE IN A FUTURE RELEASE, IT IS ONLY TO BE USED AS PART OF THE LOCAL
-// DEPLOYMENT PROCESS. USE A DEPLOYMENT SCRIPT LOCATED IN scripts/deploy-scripts/ WHEN DEPLOYING
-// TO A PRODUCTION ENVIRONMENT.
-
-import { Wallet } from 'ethers'
-import path from 'path'
-import dirtree from 'directory-tree'
-import fs from 'fs'
-
-// Ensures that all relevant environment vars are properly set. These lines *must* come before the
-// hardhat import because importing will load the config (which relies on these vars). Necessary
-// because CI currently uses different var names than the ones we've chosen here.
-// TODO: Update CI so that we don't have to do this anymore.
-process.env.HARDHAT_NETWORK = 'custom' // "custom" here is an arbitrary name. only used for CI.
-process.env.CONTRACTS_TARGET_NETWORK = 'custom'
-process.env.CONTRACTS_DEPLOYER_KEY = process.env.DEPLOYER_PRIVATE_KEY
-process.env.CONTRACTS_RPC_URL =
- process.env.L1_NODE_WEB3_URL || 'http://127.0.0.1:8545'
-
-import hre from 'hardhat'
-
-const sequencer = new Wallet(process.env.SEQUENCER_PRIVATE_KEY)
-const proposer = new Wallet(process.env.PROPOSER_PRIVATE_KEY)
-const deployer = new Wallet(process.env.DEPLOYER_PRIVATE_KEY)
-
-const parseEnv = () => {
- const ensure = (env, type) => {
- if (typeof process.env[env] === 'undefined') {
- return undefined
- }
- if (type === 'number') {
- return parseInt(process.env[env], 10)
- }
- return process.env[env]
- }
-
- return {
- l1BlockTimeSeconds: ensure('BLOCK_TIME_SECONDS', 'number'),
- ctcMaxTransactionGasLimit: ensure('MAX_TRANSACTION_GAS_LIMIT', 'number'),
- ctcL2GasDiscountDivisor: ensure('L2_GAS_DISCOUNT_DIVISOR', 'number'),
- ctcEnqueueGasCost: ensure('ENQUEUE_GAS_COST', 'number'),
- sccFraudProofWindow: ensure('FRAUD_PROOF_WINDOW_SECONDS', 'number'),
- sccSequencerPublishWindow: ensure(
- 'SEQUENCER_PUBLISH_WINDOW_SECONDS',
- 'number'
- ),
- }
-}
-
-const main = async () => {
- // Just be really verbose about this...
- console.log(
- `WARNING: DO NOT USE THIS FILE IN PRODUCTION! FOR LOCAL DEVELOPMENT ONLY!`
- )
-
- const config = parseEnv()
-
- await hre.run('deploy', {
- l1BlockTimeSeconds: config.l1BlockTimeSeconds,
- ctcMaxTransactionGasLimit: config.ctcMaxTransactionGasLimit,
- ctcL2GasDiscountDivisor: config.ctcL2GasDiscountDivisor,
- ctcEnqueueGasCost: config.ctcEnqueueGasCost,
- sccFraudProofWindow: config.sccFraudProofWindow,
- sccSequencerPublishWindow: config.sccFraudProofWindow,
- ovmSequencerAddress: sequencer.address,
- ovmProposerAddress: proposer.address,
- ovmAddressManagerOwner: deployer.address,
- numDeployConfirmations: 0,
- noCompile: process.env.NO_COMPILE ? true : false,
- })
-
- // Stuff below this line is currently required for CI to work properly. We probably want to
- // update our CI so this is no longer necessary. But I'm adding it for backwards compat so we can
- // get the hardhat-deploy stuff merged. Woot.
- const nicknames = {
- Lib_AddressManager: 'AddressManager',
- }
-
- const contracts: any = dirtree(
- path.resolve(__dirname, `../deployments/custom`)
- )
- .children.filter((child) => {
- return child.extension === '.json'
- })
- .reduce((contractsAccumulator, child) => {
- const contractName = child.name.replace('.json', '')
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const artifact = require(path.resolve(
- __dirname,
- `../deployments/custom/${child.name}`
- ))
- contractsAccumulator[nicknames[contractName] || contractName] =
- artifact.address
- return contractsAccumulator
- }, {})
-
- contracts.OVM_Sequencer = await sequencer.getAddress()
- contracts.Deployer = await deployer.getAddress()
-
- const addresses = JSON.stringify(contracts, null, 2)
- const dumpsPath = path.resolve(__dirname, '../dist/dumps')
- if (!fs.existsSync(dumpsPath)) {
- fs.mkdirSync(dumpsPath)
- }
- const addrsPath = path.resolve(dumpsPath, 'addresses.json')
- fs.writeFileSync(addrsPath, addresses)
-}
-
-main()
- .then(() => process.exit(0))
- .catch((error) => {
- console.log(
- JSON.stringify({ error: error.message, stack: error.stack }, null, 2)
- )
- process.exit(1)
- })
diff --git a/packages/contracts/codechecks.yml b/packages/contracts/codechecks.yml
index 7dd55bd055420..e163cc5b2f820 100644
--- a/packages/contracts/codechecks.yml
+++ b/packages/contracts/codechecks.yml
@@ -1,2 +1,7 @@
checks:
- - name: eth-gas-reporter/codechecks
\ No newline at end of file
+ - name: eth-gas-reporter/codechecks
+settings:
+ speculativeBranchSelection: false
+ branches:
+ - develop
+ - master
diff --git a/packages/contracts/deployments/README.md b/packages/contracts/deployments/README.md
index 874e99d369613..3f609838417e5 100644
--- a/packages/contracts/deployments/README.md
+++ b/packages/contracts/deployments/README.md
@@ -1,4 +1,4 @@
-# Optimistic Ethereum Deployments
-- [Optimistic Ethereum (mainnet)](./mainnet#readme)
-- [Optimistic Kovan (public testnet)](./kovan#readme)
-- [Optimistic Goerli (internal devnet)](./goerli#readme)
+# Optimism Deployments
+- [Optimism (mainnet)](./mainnet#readme)
+- [Optimism Kovan (public testnet)](./kovan#readme)
+- [Optimism Goerli (internal devnet)](./goerli#readme)
diff --git a/packages/contracts/deployments/goerli/README.md b/packages/contracts/deployments/goerli/README.md
index 33f0efb602621..a1f7149de11e1 100644
--- a/packages/contracts/deployments/goerli/README.md
+++ b/packages/contracts/deployments/goerli/README.md
@@ -1,6 +1,6 @@
-# Optimistic Goerli (internal devnet)
+# Optimism Goerli (internal devnet)
## Notice
-Optimistic Goerli is an internal Optimism development network. You're probably looking for [Optimistic Kovan](../kovan#readme), the public Optimistic Ethereum testnet.
+Optimism Goerli is an internal Optimism development network. You're probably looking for [Optimism Kovan](../kovan#readme), the public Optimism testnet.
## Network Info
- **Chain ID**: 420
## Layer 1 Contracts
diff --git a/packages/contracts/deployments/kovan/README.md b/packages/contracts/deployments/kovan/README.md
index 795231cd1ebb1..574c43b13bea0 100644
--- a/packages/contracts/deployments/kovan/README.md
+++ b/packages/contracts/deployments/kovan/README.md
@@ -1,4 +1,4 @@
-# Optimistic Kovan (public testnet)
+# Optimism Kovan (public testnet)
## Network Info
- **Chain ID**: 69
- **Public RPC**: https://kovan.optimism.io
diff --git a/packages/contracts/deployments/mainnet/README.md b/packages/contracts/deployments/mainnet/README.md
index 2875a1a7f4830..9d9321b40a7cd 100644
--- a/packages/contracts/deployments/mainnet/README.md
+++ b/packages/contracts/deployments/mainnet/README.md
@@ -1,4 +1,4 @@
-# Optimistic Ethereum (mainnet)
+# Optimism (mainnet)
## Network Info
- **Chain ID**: 10
- **Public RPC**: https://mainnet.optimism.io
diff --git a/packages/contracts/docs/Address.md b/packages/contracts/docs/Address.md
new file mode 100644
index 0000000000000..927f4068e58df
--- /dev/null
+++ b/packages/contracts/docs/Address.md
@@ -0,0 +1,12 @@
+# Address
+
+
+
+
+
+
+
+*Collection of functions related to the address type*
+
+
+
diff --git a/packages/contracts/docs/AddressAliasHelper.md b/packages/contracts/docs/AddressAliasHelper.md
new file mode 100644
index 0000000000000..16502f089d34a
--- /dev/null
+++ b/packages/contracts/docs/AddressAliasHelper.md
@@ -0,0 +1,12 @@
+# AddressAliasHelper
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/AddressDictator.md b/packages/contracts/docs/AddressDictator.md
new file mode 100644
index 0000000000000..4402c39c55981
--- /dev/null
+++ b/packages/contracts/docs/AddressDictator.md
@@ -0,0 +1,88 @@
+# AddressDictator
+
+
+
+> AddressDictator
+
+
+
+*The AddressDictator (glory to Arstotzka) is a contract that allows us to safely manipulate many different addresses in the AddressManager without transferring ownership of the AddressManager to a hot wallet or hardware wallet.*
+
+## Methods
+
+### finalOwner
+
+```solidity
+function finalOwner() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### getNamedAddresses
+
+```solidity
+function getNamedAddresses() external view returns (struct AddressDictator.NamedAddress[])
+```
+
+Returns the full namedAddresses array.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | AddressDictator.NamedAddress[] | undefined
+
+### manager
+
+```solidity
+function manager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### returnOwnership
+
+```solidity
+function returnOwnership() external nonpayable
+```
+
+Transfers ownership of this contract to the finalOwner. Only callable by the Final Owner, which is intended to be our multisig. This function shouldn't be necessary, but it gives a sense of reassurance that we can recover if something really surprising goes wrong.
+
+
+
+
+### setAddresses
+
+```solidity
+function setAddresses() external nonpayable
+```
+
+Called to finalize the transfer, this function is callable by anyone, but will only result in an upgrade if this contract is the owner Address Manager.
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/BondManager.md b/packages/contracts/docs/BondManager.md
new file mode 100644
index 0000000000000..25d833fe450f4
--- /dev/null
+++ b/packages/contracts/docs/BondManager.md
@@ -0,0 +1,76 @@
+# BondManager
+
+
+
+> BondManager
+
+
+
+*This contract is, for now, a stub of the "real" BondManager that does nothing but allow the "OVM_Proposer" to submit state root batches.*
+
+## Methods
+
+### isCollateralized
+
+```solidity
+function isCollateralized(address _who) external view returns (bool)
+```
+
+Checks whether a given address is properly collateralized and can perform actions within the system.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _who | address | Address to check.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | true if the address is properly collateralized, false otherwise.
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+
+
+
diff --git a/packages/contracts/docs/CanonicalTransactionChain.md b/packages/contracts/docs/CanonicalTransactionChain.md
new file mode 100644
index 0000000000000..1a547b0eb97da
--- /dev/null
+++ b/packages/contracts/docs/CanonicalTransactionChain.md
@@ -0,0 +1,458 @@
+# CanonicalTransactionChain
+
+
+
+> CanonicalTransactionChain
+
+
+
+*The Canonical Transaction Chain (CTC) contract is an append-only log of transactions which must be applied to the rollup state. It defines the ordering of rollup transactions by writing them to the 'CTC:batches' instance of the Chain Storage Container. The CTC also allows any account to 'enqueue' an L2 transaction, which will require that the Sequencer will eventually append it to the rollup state.*
+
+## Methods
+
+### MAX_ROLLUP_TX_SIZE
+
+```solidity
+function MAX_ROLLUP_TX_SIZE() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### MIN_ROLLUP_TX_GAS
+
+```solidity
+function MIN_ROLLUP_TX_GAS() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### appendSequencerBatch
+
+```solidity
+function appendSequencerBatch() external nonpayable
+```
+
+Allows the sequencer to append a batch of transactions.
+
+*This function uses a custom encoding scheme for efficiency reasons. .param _shouldStartAtElement Specific batch we expect to start appending to. .param _totalElementsToAppend Total number of batch elements we expect to append. .param _contexts Array of batch contexts. .param _transactionDataFields Array of raw transaction data.*
+
+
+### batches
+
+```solidity
+function batches() external view returns (contract IChainStorageContainer)
+```
+
+Accesses the batch storage container.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract IChainStorageContainer | Reference to the batch storage container.
+
+### enqueue
+
+```solidity
+function enqueue(address _target, uint256 _gasLimit, bytes _data) external nonpayable
+```
+
+Adds a transaction to the queue.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target L2 contract to send the transaction to.
+| _gasLimit | uint256 | Gas limit for the enqueued L2 transaction.
+| _data | bytes | Transaction data.
+
+### enqueueGasCost
+
+```solidity
+function enqueueGasCost() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### enqueueL2GasPrepaid
+
+```solidity
+function enqueueL2GasPrepaid() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### getLastBlockNumber
+
+```solidity
+function getLastBlockNumber() external view returns (uint40)
+```
+
+Returns the blocknumber of the last transaction.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Blocknumber for the last transaction.
+
+### getLastTimestamp
+
+```solidity
+function getLastTimestamp() external view returns (uint40)
+```
+
+Returns the timestamp of the last transaction.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Timestamp for the last transaction.
+
+### getNextQueueIndex
+
+```solidity
+function getNextQueueIndex() external view returns (uint40)
+```
+
+Returns the index of the next element to be enqueued.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Index for the next queue element.
+
+### getNumPendingQueueElements
+
+```solidity
+function getNumPendingQueueElements() external view returns (uint40)
+```
+
+Get the number of queue elements which have not yet been included.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Number of pending queue elements.
+
+### getQueueElement
+
+```solidity
+function getQueueElement(uint256 _index) external view returns (struct Lib_OVMCodec.QueueElement _element)
+```
+
+Gets the queue element at a particular index.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Index of the queue element to access.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _element | Lib_OVMCodec.QueueElement | Queue element at the given index.
+
+### getQueueLength
+
+```solidity
+function getQueueLength() external view returns (uint40)
+```
+
+Retrieves the length of the queue, including both pending and canonical transactions.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Length of the queue.
+
+### getTotalBatches
+
+```solidity
+function getTotalBatches() external view returns (uint256 _totalBatches)
+```
+
+Retrieves the total number of batches submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalBatches | uint256 | Total submitted batches.
+
+### getTotalElements
+
+```solidity
+function getTotalElements() external view returns (uint256 _totalElements)
+```
+
+Retrieves the total number of elements submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalElements | uint256 | Total submitted elements.
+
+### l2GasDiscountDivisor
+
+```solidity
+function l2GasDiscountDivisor() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### maxTransactionGasLimit
+
+```solidity
+function maxTransactionGasLimit() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+### setGasParams
+
+```solidity
+function setGasParams(uint256 _l2GasDiscountDivisor, uint256 _enqueueGasCost) external nonpayable
+```
+
+Allows the Burn Admin to update the parameters which determine the amount of gas to burn. The value of enqueueL2GasPrepaid is immediately updated as well.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2GasDiscountDivisor | uint256 | undefined
+| _enqueueGasCost | uint256 | undefined
+
+
+
+## Events
+
+### L2GasParamsUpdated
+
+```solidity
+event L2GasParamsUpdated(uint256 l2GasDiscountDivisor, uint256 enqueueGasCost, uint256 enqueueL2GasPrepaid)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| l2GasDiscountDivisor | uint256 | undefined |
+| enqueueGasCost | uint256 | undefined |
+| enqueueL2GasPrepaid | uint256 | undefined |
+
+### QueueBatchAppended
+
+```solidity
+event QueueBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _startingQueueIndex | uint256 | undefined |
+| _numQueueElements | uint256 | undefined |
+| _totalElements | uint256 | undefined |
+
+### SequencerBatchAppended
+
+```solidity
+event SequencerBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _startingQueueIndex | uint256 | undefined |
+| _numQueueElements | uint256 | undefined |
+| _totalElements | uint256 | undefined |
+
+### TransactionBatchAppended
+
+```solidity
+event TransactionBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+| _batchSize | uint256 | undefined |
+| _prevTotalElements | uint256 | undefined |
+| _extraData | bytes | undefined |
+
+### TransactionEnqueued
+
+```solidity
+event TransactionEnqueued(address indexed _l1TxOrigin, address indexed _target, uint256 _gasLimit, bytes _data, uint256 indexed _queueIndex, uint256 _timestamp)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1TxOrigin `indexed` | address | undefined |
+| _target `indexed` | address | undefined |
+| _gasLimit | uint256 | undefined |
+| _data | bytes | undefined |
+| _queueIndex `indexed` | uint256 | undefined |
+| _timestamp | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/ChainStorageContainer.md b/packages/contracts/docs/ChainStorageContainer.md
new file mode 100644
index 0000000000000..9efcf3d0948a5
--- /dev/null
+++ b/packages/contracts/docs/ChainStorageContainer.md
@@ -0,0 +1,175 @@
+# ChainStorageContainer
+
+
+
+> ChainStorageContainer
+
+
+
+*The Chain Storage Container provides its owner contract with read, write and delete functionality. This provides gas efficiency gains by enabling it to overwrite storage slots which can no longer be used in a fraud proof due to the fraud window having passed, and the associated chain state or transactions being finalized. Three distinct Chain Storage Containers will be deployed on Layer 1: 1. Stores transaction batches for the Canonical Transaction Chain 2. Stores queued transactions for the Canonical Transaction Chain 3. Stores chain state batches for the State Commitment Chain*
+
+## Methods
+
+### deleteElementsAfterInclusive
+
+```solidity
+function deleteElementsAfterInclusive(uint256 _index) external nonpayable
+```
+
+Removes all objects after and including a given index. Also allows setting the global metadata field.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Object index to delete from.
+
+### get
+
+```solidity
+function get(uint256 _index) external view returns (bytes32)
+```
+
+Retrieves an object from the container.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Index of the particular object to access.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | 32 byte object value.
+
+### getGlobalMetadata
+
+```solidity
+function getGlobalMetadata() external view returns (bytes27)
+```
+
+Retrieves the container's global metadata field.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes27 | Container global metadata field.
+
+### length
+
+```solidity
+function length() external view returns (uint256)
+```
+
+Retrieves the number of objects stored in the container.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | Number of objects in the container.
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### owner
+
+```solidity
+function owner() external view returns (string)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### push
+
+```solidity
+function push(bytes32 _object) external nonpayable
+```
+
+Pushes an object into the container. Function allows setting the global metadata since we'll need to touch the "length" storage slot anyway, which also contains the global metadata (it's an optimization).
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _object | bytes32 | A 32 byte value to insert into the container.
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+### setGlobalMetadata
+
+```solidity
+function setGlobalMetadata(bytes27 _globalMetadata) external nonpayable
+```
+
+Sets the container's global metadata field. We're using `bytes27` here because we use five bytes to maintain the length of the underlying data structure, meaning we have an extra 27 bytes to store arbitrary data.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _globalMetadata | bytes27 | New global metadata to set.
+
+
+
+
diff --git a/packages/contracts/docs/ChugSplashDictator.md b/packages/contracts/docs/ChugSplashDictator.md
new file mode 100644
index 0000000000000..77df0ea32adb0
--- /dev/null
+++ b/packages/contracts/docs/ChugSplashDictator.md
@@ -0,0 +1,178 @@
+# ChugSplashDictator
+
+
+
+> ChugSplashDictator
+
+
+
+*Like the AddressDictator, but specifically for the Proxy__OVM_L1StandardBridge. We're working on a generalized version of this but this is good enough for the moment.*
+
+## Methods
+
+### bridgeSlotKey
+
+```solidity
+function bridgeSlotKey() external view returns (bytes32)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+### bridgeSlotVal
+
+```solidity
+function bridgeSlotVal() external view returns (bytes32)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+### codeHash
+
+```solidity
+function codeHash() external view returns (bytes32)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+### doActions
+
+```solidity
+function doActions(bytes _code) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _code | bytes | undefined
+
+### finalOwner
+
+```solidity
+function finalOwner() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### isUpgrading
+
+```solidity
+function isUpgrading() external view returns (bool)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### messengerSlotKey
+
+```solidity
+function messengerSlotKey() external view returns (bytes32)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+### messengerSlotVal
+
+```solidity
+function messengerSlotVal() external view returns (bytes32)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+### returnOwnership
+
+```solidity
+function returnOwnership() external nonpayable
+```
+
+Transfers ownership of this contract to the finalOwner. Only callable by the finalOwner, which is intended to be our multisig. This function shouldn't be necessary, but it gives a sense of reassurance that we can recover if something really surprising goes wrong.
+
+
+
+
+### target
+
+```solidity
+function target() external view returns (contract L1ChugSplashProxy)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract L1ChugSplashProxy | undefined
+
+
+
+
diff --git a/packages/contracts/docs/Context.md b/packages/contracts/docs/Context.md
new file mode 100644
index 0000000000000..4ab2a3548aaad
--- /dev/null
+++ b/packages/contracts/docs/Context.md
@@ -0,0 +1,12 @@
+# Context
+
+
+
+
+
+
+
+*Provides information about the current execution context, including the sender of the transaction and its data. While these are generally available via msg.sender and msg.data, they should not be accessed in such a direct manner, since when dealing with meta-transactions the account sending and paying for execution may not be the actual sender (as far as an application is concerned). This contract is only required for intermediate, library-like contracts.*
+
+
+
diff --git a/packages/contracts/docs/ContextUpgradeable.md b/packages/contracts/docs/ContextUpgradeable.md
new file mode 100644
index 0000000000000..18f33b80f1bc6
--- /dev/null
+++ b/packages/contracts/docs/ContextUpgradeable.md
@@ -0,0 +1,12 @@
+# ContextUpgradeable
+
+
+
+
+
+
+
+*Provides information about the current execution context, including the sender of the transaction and its data. While these are generally available via msg.sender and msg.data, they should not be accessed in such a direct manner, since when dealing with meta-transactions the account sending and paying for execution may not be the actual sender (as far as an application is concerned). This contract is only required for intermediate, library-like contracts.*
+
+
+
diff --git a/packages/contracts/docs/CrossDomainEnabled.md b/packages/contracts/docs/CrossDomainEnabled.md
new file mode 100644
index 0000000000000..eb129fa6ee1ba
--- /dev/null
+++ b/packages/contracts/docs/CrossDomainEnabled.md
@@ -0,0 +1,32 @@
+# CrossDomainEnabled
+
+
+
+> CrossDomainEnabled
+
+
+
+*Helper contract for contracts performing cross-domain communications Compiler used: defined by inheriting contract*
+
+## Methods
+
+### messenger
+
+```solidity
+function messenger() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+
diff --git a/packages/contracts/docs/ERC165Checker.md b/packages/contracts/docs/ERC165Checker.md
new file mode 100644
index 0000000000000..e9b3261cb8994
--- /dev/null
+++ b/packages/contracts/docs/ERC165Checker.md
@@ -0,0 +1,12 @@
+# ERC165Checker
+
+
+
+
+
+
+
+*Library used to query support of an interface declared via {IERC165}. Note that these functions return the actual result of the query: they do not `revert` if an interface is not supported. It is up to the caller to decide what to do in these cases.*
+
+
+
diff --git a/packages/contracts/docs/ERC20.md b/packages/contracts/docs/ERC20.md
new file mode 100644
index 0000000000000..5abee7190ccbd
--- /dev/null
+++ b/packages/contracts/docs/ERC20.md
@@ -0,0 +1,283 @@
+# ERC20
+
+
+
+
+
+
+
+*Implementation of the {IERC20} interface. This implementation is agnostic to the way tokens are created. This means that a supply mechanism has to be added in a derived contract using {_mint}. For a generic mechanism see {ERC20PresetMinterPauser}. TIP: For a detailed writeup see our guide https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How to implement supply mechanisms]. We have followed general OpenZeppelin Contracts guidelines: functions revert instead returning `false` on failure. This behavior is nonetheless conventional and does not conflict with the expectations of ERC20 applications. Additionally, an {Approval} event is emitted on calls to {transferFrom}. This allows applications to reconstruct the allowance for all accounts just by listening to said events. Other implementations of the EIP may not emit these events, as it isn't required by the specification. Finally, the non-standard {decreaseAllowance} and {increaseAllowance} functions have been added to mitigate the well-known issues around setting allowances. See {IERC20-approve}.*
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*See {IERC20-allowance}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-approve}. Requirements: - `spender` cannot be the zero address.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*See {IERC20-balanceOf}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint8)
+```
+
+
+
+*Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless this function is overridden; NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint8 | undefined
+
+### decreaseAllowance
+
+```solidity
+function decreaseAllowance(address spender, uint256 subtractedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| subtractedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### increaseAllowance
+
+```solidity
+function increaseAllowance(address spender, uint256 addedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| addedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### name
+
+```solidity
+function name() external view returns (string)
+```
+
+
+
+*Returns the name of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+
+
+*Returns the symbol of the token, usually a shorter version of the name.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*See {IERC20-totalSupply}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-transfer}. Requirements: - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `amount`. - the caller must have allowance for ``sender``'s tokens of at least `amount`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IBondManager.md b/packages/contracts/docs/IBondManager.md
new file mode 100644
index 0000000000000..af66f779d7f7e
--- /dev/null
+++ b/packages/contracts/docs/IBondManager.md
@@ -0,0 +1,37 @@
+# IBondManager
+
+
+
+> IBondManager
+
+
+
+
+
+## Methods
+
+### isCollateralized
+
+```solidity
+function isCollateralized(address _who) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _who | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+
diff --git a/packages/contracts/docs/ICanonicalTransactionChain.md b/packages/contracts/docs/ICanonicalTransactionChain.md
new file mode 100644
index 0000000000000..66e3465ab8779
--- /dev/null
+++ b/packages/contracts/docs/ICanonicalTransactionChain.md
@@ -0,0 +1,317 @@
+# ICanonicalTransactionChain
+
+
+
+> ICanonicalTransactionChain
+
+
+
+
+
+## Methods
+
+### appendSequencerBatch
+
+```solidity
+function appendSequencerBatch() external nonpayable
+```
+
+Allows the sequencer to append a batch of transactions.
+
+*This function uses a custom encoding scheme for efficiency reasons. .param _shouldStartAtElement Specific batch we expect to start appending to. .param _totalElementsToAppend Total number of batch elements we expect to append. .param _contexts Array of batch contexts. .param _transactionDataFields Array of raw transaction data.*
+
+
+### batches
+
+```solidity
+function batches() external view returns (contract IChainStorageContainer)
+```
+
+Accesses the batch storage container.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract IChainStorageContainer | Reference to the batch storage container.
+
+### enqueue
+
+```solidity
+function enqueue(address _target, uint256 _gasLimit, bytes _data) external nonpayable
+```
+
+Adds a transaction to the queue.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract to send the transaction to.
+| _gasLimit | uint256 | Gas limit for the given transaction.
+| _data | bytes | Transaction data.
+
+### getLastBlockNumber
+
+```solidity
+function getLastBlockNumber() external view returns (uint40)
+```
+
+Returns the blocknumber of the last transaction.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Blocknumber for the last transaction.
+
+### getLastTimestamp
+
+```solidity
+function getLastTimestamp() external view returns (uint40)
+```
+
+Returns the timestamp of the last transaction.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Timestamp for the last transaction.
+
+### getNextQueueIndex
+
+```solidity
+function getNextQueueIndex() external view returns (uint40)
+```
+
+Returns the index of the next element to be enqueued.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Index for the next queue element.
+
+### getNumPendingQueueElements
+
+```solidity
+function getNumPendingQueueElements() external view returns (uint40)
+```
+
+Get the number of queue elements which have not yet been included.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Number of pending queue elements.
+
+### getQueueElement
+
+```solidity
+function getQueueElement(uint256 _index) external view returns (struct Lib_OVMCodec.QueueElement _element)
+```
+
+Gets the queue element at a particular index.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Index of the queue element to access.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _element | Lib_OVMCodec.QueueElement | Queue element at the given index.
+
+### getQueueLength
+
+```solidity
+function getQueueLength() external view returns (uint40)
+```
+
+Retrieves the length of the queue, including both pending and canonical transactions.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint40 | Length of the queue.
+
+### getTotalBatches
+
+```solidity
+function getTotalBatches() external view returns (uint256 _totalBatches)
+```
+
+Retrieves the total number of batches submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalBatches | uint256 | Total submitted batches.
+
+### getTotalElements
+
+```solidity
+function getTotalElements() external view returns (uint256 _totalElements)
+```
+
+Retrieves the total number of elements submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalElements | uint256 | Total submitted elements.
+
+### setGasParams
+
+```solidity
+function setGasParams(uint256 _l2GasDiscountDivisor, uint256 _enqueueGasCost) external nonpayable
+```
+
+Allows the Burn Admin to update the parameters which determine the amount of gas to burn. The value of enqueueL2GasPrepaid is immediately updated as well.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2GasDiscountDivisor | uint256 | undefined
+| _enqueueGasCost | uint256 | undefined
+
+
+
+## Events
+
+### L2GasParamsUpdated
+
+```solidity
+event L2GasParamsUpdated(uint256 l2GasDiscountDivisor, uint256 enqueueGasCost, uint256 enqueueL2GasPrepaid)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| l2GasDiscountDivisor | uint256 | undefined |
+| enqueueGasCost | uint256 | undefined |
+| enqueueL2GasPrepaid | uint256 | undefined |
+
+### QueueBatchAppended
+
+```solidity
+event QueueBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _startingQueueIndex | uint256 | undefined |
+| _numQueueElements | uint256 | undefined |
+| _totalElements | uint256 | undefined |
+
+### SequencerBatchAppended
+
+```solidity
+event SequencerBatchAppended(uint256 _startingQueueIndex, uint256 _numQueueElements, uint256 _totalElements)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _startingQueueIndex | uint256 | undefined |
+| _numQueueElements | uint256 | undefined |
+| _totalElements | uint256 | undefined |
+
+### TransactionBatchAppended
+
+```solidity
+event TransactionBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+| _batchSize | uint256 | undefined |
+| _prevTotalElements | uint256 | undefined |
+| _extraData | bytes | undefined |
+
+### TransactionEnqueued
+
+```solidity
+event TransactionEnqueued(address indexed _l1TxOrigin, address indexed _target, uint256 _gasLimit, bytes _data, uint256 indexed _queueIndex, uint256 _timestamp)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1TxOrigin `indexed` | address | undefined |
+| _target `indexed` | address | undefined |
+| _gasLimit | uint256 | undefined |
+| _data | bytes | undefined |
+| _queueIndex `indexed` | uint256 | undefined |
+| _timestamp | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IChainStorageContainer.md b/packages/contracts/docs/IChainStorageContainer.md
new file mode 100644
index 0000000000000..5d46a12e6cd30
--- /dev/null
+++ b/packages/contracts/docs/IChainStorageContainer.md
@@ -0,0 +1,119 @@
+# IChainStorageContainer
+
+
+
+> IChainStorageContainer
+
+
+
+
+
+## Methods
+
+### deleteElementsAfterInclusive
+
+```solidity
+function deleteElementsAfterInclusive(uint256 _index) external nonpayable
+```
+
+Removes all objects after and including a given index. Also allows setting the global metadata field.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Object index to delete from.
+
+### get
+
+```solidity
+function get(uint256 _index) external view returns (bytes32)
+```
+
+Retrieves an object from the container.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _index | uint256 | Index of the particular object to access.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | 32 byte object value.
+
+### getGlobalMetadata
+
+```solidity
+function getGlobalMetadata() external view returns (bytes27)
+```
+
+Retrieves the container's global metadata field.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes27 | Container global metadata field.
+
+### length
+
+```solidity
+function length() external view returns (uint256)
+```
+
+Retrieves the number of objects stored in the container.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | Number of objects in the container.
+
+### push
+
+```solidity
+function push(bytes32 _object) external nonpayable
+```
+
+Pushes an object into the container. Function allows setting the global metadata since we'll need to touch the "length" storage slot anyway, which also contains the global metadata (it's an optimization).
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _object | bytes32 | A 32 byte value to insert into the container.
+
+### setGlobalMetadata
+
+```solidity
+function setGlobalMetadata(bytes27 _globalMetadata) external nonpayable
+```
+
+Sets the container's global metadata field. We're using `bytes27` here because we use five bytes to maintain the length of the underlying data structure, meaning we have an extra 27 bytes to store arbitrary data.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _globalMetadata | bytes27 | New global metadata to set.
+
+
+
+
diff --git a/packages/contracts/docs/ICrossDomainMessenger.md b/packages/contracts/docs/ICrossDomainMessenger.md
new file mode 100644
index 0000000000000..eb10ece390c8b
--- /dev/null
+++ b/packages/contracts/docs/ICrossDomainMessenger.md
@@ -0,0 +1,105 @@
+# ICrossDomainMessenger
+
+
+
+> ICrossDomainMessenger
+
+
+
+
+
+## Methods
+
+### sendMessage
+
+```solidity
+function sendMessage(address _target, bytes _message, uint32 _gasLimit) external nonpayable
+```
+
+Sends a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _message | bytes | Message to send to the target.
+| _gasLimit | uint32 | Gas limit for the provided message.
+
+### xDomainMessageSender
+
+```solidity
+function xDomainMessageSender() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### FailedRelayedMessage
+
+```solidity
+event FailedRelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### RelayedMessage
+
+```solidity
+event RelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### SentMessage
+
+```solidity
+event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| target `indexed` | address | undefined |
+| sender | address | undefined |
+| message | bytes | undefined |
+| messageNonce | uint256 | undefined |
+| gasLimit | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IERC165.md b/packages/contracts/docs/IERC165.md
new file mode 100644
index 0000000000000..30bb32277d4bf
--- /dev/null
+++ b/packages/contracts/docs/IERC165.md
@@ -0,0 +1,37 @@
+# IERC165
+
+
+
+
+
+
+
+*Interface of the ERC165 standard, as defined in the https://eips.ethereum.org/EIPS/eip-165[EIP]. Implementers can declare support of contract interfaces, which can then be queried by others ({ERC165Checker}). For an implementation, see {ERC165}.*
+
+## Methods
+
+### supportsInterface
+
+```solidity
+function supportsInterface(bytes4 interfaceId) external view returns (bool)
+```
+
+
+
+*Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| interfaceId | bytes4 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+
diff --git a/packages/contracts/docs/IERC20.md b/packages/contracts/docs/IERC20.md
new file mode 100644
index 0000000000000..8162eec359233
--- /dev/null
+++ b/packages/contracts/docs/IERC20.md
@@ -0,0 +1,186 @@
+# IERC20
+
+
+
+
+
+
+
+*Interface of the ERC20 standard as defined in the EIP.*
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Sets `amount` as the allowance of `spender` over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an {Approval} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens owned by `account`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens in existence.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from the caller's account to `recipient`. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+*Emitted when the allowance of a `spender` for an `owner` is set by a call to {approve}. `value` is the new allowance.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+*Emitted when `value` tokens are moved from one account (`from`) to another (`to`). Note that `value` may be zero.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IERC20Metadata.md b/packages/contracts/docs/IERC20Metadata.md
new file mode 100644
index 0000000000000..71070ef0a1dbe
--- /dev/null
+++ b/packages/contracts/docs/IERC20Metadata.md
@@ -0,0 +1,237 @@
+# IERC20Metadata
+
+
+
+
+
+
+
+*Interface for the optional metadata functions from the ERC20 standard. _Available since v4.1._*
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Sets `amount` as the allowance of `spender` over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an {Approval} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens owned by `account`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint8)
+```
+
+
+
+*Returns the decimals places of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint8 | undefined
+
+### name
+
+```solidity
+function name() external view returns (string)
+```
+
+
+
+*Returns the name of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+
+
+*Returns the symbol of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens in existence.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from the caller's account to `recipient`. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL1CrossDomainMessenger.md b/packages/contracts/docs/IL1CrossDomainMessenger.md
new file mode 100644
index 0000000000000..d903c331d0d35
--- /dev/null
+++ b/packages/contracts/docs/IL1CrossDomainMessenger.md
@@ -0,0 +1,146 @@
+# IL1CrossDomainMessenger
+
+
+
+> IL1CrossDomainMessenger
+
+
+
+
+
+## Methods
+
+### relayMessage
+
+```solidity
+function relayMessage(address _target, address _sender, bytes _message, uint256 _messageNonce, IL1CrossDomainMessenger.L2MessageInclusionProof _proof) external nonpayable
+```
+
+Relays a cross domain message to a contract.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Message sender address.
+| _message | bytes | Message to send to the target.
+| _messageNonce | uint256 | Nonce for the provided message.
+| _proof | IL1CrossDomainMessenger.L2MessageInclusionProof | Inclusion proof for the given message.
+
+### replayMessage
+
+```solidity
+function replayMessage(address _target, address _sender, bytes _message, uint256 _queueIndex, uint32 _oldGasLimit, uint32 _newGasLimit) external nonpayable
+```
+
+Replays a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Original sender address.
+| _message | bytes | Message to send to the target.
+| _queueIndex | uint256 | CTC Queue index for the message to replay.
+| _oldGasLimit | uint32 | Original gas limit used to send the message.
+| _newGasLimit | uint32 | New gas limit to be used for this message.
+
+### sendMessage
+
+```solidity
+function sendMessage(address _target, bytes _message, uint32 _gasLimit) external nonpayable
+```
+
+Sends a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _message | bytes | Message to send to the target.
+| _gasLimit | uint32 | Gas limit for the provided message.
+
+### xDomainMessageSender
+
+```solidity
+function xDomainMessageSender() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### FailedRelayedMessage
+
+```solidity
+event FailedRelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### RelayedMessage
+
+```solidity
+event RelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### SentMessage
+
+```solidity
+event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| target `indexed` | address | undefined |
+| sender | address | undefined |
+| message | bytes | undefined |
+| messageNonce | uint256 | undefined |
+| gasLimit | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL1ERC20Bridge.md b/packages/contracts/docs/IL1ERC20Bridge.md
new file mode 100644
index 0000000000000..eb1f5be8163dc
--- /dev/null
+++ b/packages/contracts/docs/IL1ERC20Bridge.md
@@ -0,0 +1,139 @@
+# IL1ERC20Bridge
+
+
+
+> IL1ERC20Bridge
+
+
+
+
+
+## Methods
+
+### depositERC20
+
+```solidity
+function depositERC20(address _l1Token, address _l2Token, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of the ERC20 to the caller's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _amount | uint256 | Amount of the ERC20 to deposit
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositERC20To
+
+```solidity
+function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of ERC20 to a recipient's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _to | address | L2 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### finalizeERC20Withdrawal
+
+```solidity
+function finalizeERC20Withdrawal(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the L1 ERC20 token. This call will fail if the initialized withdrawal from L2 has not been finalized.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of L1 token to finalizeWithdrawal for.
+| _l2Token | address | Address of L2 token where withdrawal was initiated.
+| _from | address | L2 address initiating the transfer.
+| _to | address | L1 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _data | bytes | Data provided by the sender on L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### l2TokenBridge
+
+```solidity
+function l2TokenBridge() external nonpayable returns (address)
+```
+
+
+
+*get the address of the corresponding L2 bridge contract.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address of the corresponding L2 bridge contract.
+
+
+
+## Events
+
+### ERC20DepositInitiated
+
+```solidity
+event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ERC20WithdrawalFinalized
+
+```solidity
+event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL1StandardBridge.md b/packages/contracts/docs/IL1StandardBridge.md
new file mode 100644
index 0000000000000..d028bf96165f5
--- /dev/null
+++ b/packages/contracts/docs/IL1StandardBridge.md
@@ -0,0 +1,231 @@
+# IL1StandardBridge
+
+
+
+> IL1StandardBridge
+
+
+
+
+
+## Methods
+
+### depositERC20
+
+```solidity
+function depositERC20(address _l1Token, address _l2Token, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of the ERC20 to the caller's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _amount | uint256 | Amount of the ERC20 to deposit
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositERC20To
+
+```solidity
+function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of ERC20 to a recipient's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _to | address | L2 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositETH
+
+```solidity
+function depositETH(uint32 _l2Gas, bytes _data) external payable
+```
+
+
+
+*Deposit an amount of the ETH to the caller's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositETHTo
+
+```solidity
+function depositETHTo(address _to, uint32 _l2Gas, bytes _data) external payable
+```
+
+
+
+*Deposit an amount of ETH to a recipient's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _to | address | L2 address to credit the withdrawal to.
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### finalizeERC20Withdrawal
+
+```solidity
+function finalizeERC20Withdrawal(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the L1 ERC20 token. This call will fail if the initialized withdrawal from L2 has not been finalized.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of L1 token to finalizeWithdrawal for.
+| _l2Token | address | Address of L2 token where withdrawal was initiated.
+| _from | address | L2 address initiating the transfer.
+| _to | address | L1 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _data | bytes | Data provided by the sender on L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### finalizeETHWithdrawal
+
+```solidity
+function finalizeETHWithdrawal(address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called before the withdrawal is finalized.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from | address | L2 address initiating the transfer.
+| _to | address | L1 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### l2TokenBridge
+
+```solidity
+function l2TokenBridge() external nonpayable returns (address)
+```
+
+
+
+*get the address of the corresponding L2 bridge contract.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address of the corresponding L2 bridge contract.
+
+
+
+## Events
+
+### ERC20DepositInitiated
+
+```solidity
+event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ERC20WithdrawalFinalized
+
+```solidity
+event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ETHDepositInitiated
+
+```solidity
+event ETHDepositInitiated(address indexed _from, address indexed _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from `indexed` | address | undefined |
+| _to `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ETHWithdrawalFinalized
+
+```solidity
+event ETHWithdrawalFinalized(address indexed _from, address indexed _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from `indexed` | address | undefined |
+| _to `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL2CrossDomainMessenger.md b/packages/contracts/docs/IL2CrossDomainMessenger.md
new file mode 100644
index 0000000000000..6da1d315661ea
--- /dev/null
+++ b/packages/contracts/docs/IL2CrossDomainMessenger.md
@@ -0,0 +1,124 @@
+# IL2CrossDomainMessenger
+
+
+
+> IL2CrossDomainMessenger
+
+
+
+
+
+## Methods
+
+### relayMessage
+
+```solidity
+function relayMessage(address _target, address _sender, bytes _message, uint256 _messageNonce) external nonpayable
+```
+
+Relays a cross domain message to a contract.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Message sender address.
+| _message | bytes | Message to send to the target.
+| _messageNonce | uint256 | Nonce for the provided message.
+
+### sendMessage
+
+```solidity
+function sendMessage(address _target, bytes _message, uint32 _gasLimit) external nonpayable
+```
+
+Sends a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _message | bytes | Message to send to the target.
+| _gasLimit | uint32 | Gas limit for the provided message.
+
+### xDomainMessageSender
+
+```solidity
+function xDomainMessageSender() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### FailedRelayedMessage
+
+```solidity
+event FailedRelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### RelayedMessage
+
+```solidity
+event RelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### SentMessage
+
+```solidity
+event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| target `indexed` | address | undefined |
+| sender | address | undefined |
+| message | bytes | undefined |
+| messageNonce | uint256 | undefined |
+| gasLimit | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL2ERC20Bridge.md b/packages/contracts/docs/IL2ERC20Bridge.md
new file mode 100644
index 0000000000000..e0be3b74402d6
--- /dev/null
+++ b/packages/contracts/docs/IL2ERC20Bridge.md
@@ -0,0 +1,158 @@
+# IL2ERC20Bridge
+
+
+
+> IL2ERC20Bridge
+
+
+
+
+
+## Methods
+
+### finalizeDeposit
+
+```solidity
+function finalizeDeposit(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a deposit from L1 to L2, and credits funds to the recipient's balance of this L2 token. This call will fail if it did not originate from a corresponding deposit in L1StandardTokenBridge.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address for the l1 token this is called with
+| _l2Token | address | Address for the l2 token this is called with
+| _from | address | Account to pull the deposit from on L2.
+| _to | address | Address to receive the withdrawal at
+| _amount | uint256 | Amount of the token to withdraw
+| _data | bytes | Data provider by the sender on L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### l1TokenBridge
+
+```solidity
+function l1TokenBridge() external nonpayable returns (address)
+```
+
+
+
+*get the address of the corresponding L1 bridge contract.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address of the corresponding L1 bridge contract.
+
+### withdraw
+
+```solidity
+function withdraw(address _l2Token, uint256 _amount, uint32 _l1Gas, bytes _data) external nonpayable
+```
+
+
+
+*initiate a withdraw of some tokens to the caller's account on L1*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Token | address | Address of L2 token where withdrawal was initiated.
+| _amount | uint256 | Amount of the token to withdraw. param _l1Gas Unused, but included for potential forward compatibility considerations.
+| _l1Gas | uint32 | undefined
+| _data | bytes | Optional data to forward to L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### withdrawTo
+
+```solidity
+function withdrawTo(address _l2Token, address _to, uint256 _amount, uint32 _l1Gas, bytes _data) external nonpayable
+```
+
+
+
+*initiate a withdraw of some token to a recipient's account on L1.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Token | address | Address of L2 token where withdrawal is initiated.
+| _to | address | L1 adress to credit the withdrawal to.
+| _amount | uint256 | Amount of the token to withdraw. param _l1Gas Unused, but included for potential forward compatibility considerations.
+| _l1Gas | uint32 | undefined
+| _data | bytes | Optional data to forward to L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+
+
+## Events
+
+### DepositFailed
+
+```solidity
+event DepositFailed(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### DepositFinalized
+
+```solidity
+event DepositFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### WithdrawalInitiated
+
+```solidity
+event WithdrawalInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/IL2StandardERC20.md b/packages/contracts/docs/IL2StandardERC20.md
new file mode 100644
index 0000000000000..37fecf5545142
--- /dev/null
+++ b/packages/contracts/docs/IL2StandardERC20.md
@@ -0,0 +1,293 @@
+# IL2StandardERC20
+
+
+
+
+
+
+
+
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through {transferFrom}. This is zero by default. This value changes when {approve} or {transferFrom} are called.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Sets `amount` as the allowance of `spender` over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an {Approval} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens owned by `account`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### burn
+
+```solidity
+function burn(address _from, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from | address | undefined
+| _amount | uint256 | undefined
+
+### l1Token
+
+```solidity
+function l1Token() external nonpayable returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### mint
+
+```solidity
+function mint(address _to, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _to | address | undefined
+| _amount | uint256 | undefined
+
+### supportsInterface
+
+```solidity
+function supportsInterface(bytes4 interfaceId) external view returns (bool)
+```
+
+
+
+*Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| interfaceId | bytes4 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*Returns the amount of tokens in existence.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from the caller's account to `recipient`. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Burn
+
+```solidity
+event Burn(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Mint
+
+```solidity
+event Mint(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/IStateCommitmentChain.md b/packages/contracts/docs/IStateCommitmentChain.md
new file mode 100644
index 0000000000000..0fdac3b7dbf0b
--- /dev/null
+++ b/packages/contracts/docs/IStateCommitmentChain.md
@@ -0,0 +1,185 @@
+# IStateCommitmentChain
+
+
+
+> IStateCommitmentChain
+
+
+
+
+
+## Methods
+
+### appendStateBatch
+
+```solidity
+function appendStateBatch(bytes32[] _batch, uint256 _shouldStartAtElement) external nonpayable
+```
+
+Appends a batch of state roots to the chain.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batch | bytes32[] | Batch of state roots.
+| _shouldStartAtElement | uint256 | Index of the element at which this batch should start.
+
+### deleteStateBatch
+
+```solidity
+function deleteStateBatch(Lib_OVMCodec.ChainBatchHeader _batchHeader) external nonpayable
+```
+
+Deletes all state roots after (and including) a given batch.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch to start deleting from.
+
+### getLastSequencerTimestamp
+
+```solidity
+function getLastSequencerTimestamp() external view returns (uint256 _lastSequencerTimestamp)
+```
+
+Retrieves the timestamp of the last batch submitted by the sequencer.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _lastSequencerTimestamp | uint256 | Last sequencer batch timestamp.
+
+### getTotalBatches
+
+```solidity
+function getTotalBatches() external view returns (uint256 _totalBatches)
+```
+
+Retrieves the total number of batches submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalBatches | uint256 | Total submitted batches.
+
+### getTotalElements
+
+```solidity
+function getTotalElements() external view returns (uint256 _totalElements)
+```
+
+Retrieves the total number of elements submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalElements | uint256 | Total submitted elements.
+
+### insideFraudProofWindow
+
+```solidity
+function insideFraudProofWindow(Lib_OVMCodec.ChainBatchHeader _batchHeader) external view returns (bool _inside)
+```
+
+Checks whether a given batch is still inside its fraud proof window.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch to check.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _inside | bool | Whether or not the batch is inside the fraud proof window.
+
+### verifyStateCommitment
+
+```solidity
+function verifyStateCommitment(bytes32 _element, Lib_OVMCodec.ChainBatchHeader _batchHeader, Lib_OVMCodec.ChainInclusionProof _proof) external view returns (bool _verified)
+```
+
+Verifies a batch inclusion proof.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _element | bytes32 | Hash of the element to verify a proof for.
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch in which the element was included.
+| _proof | Lib_OVMCodec.ChainInclusionProof | Merkle inclusion proof for the element.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _verified | bool | undefined
+
+
+
+## Events
+
+### StateBatchAppended
+
+```solidity
+event StateBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+| _batchSize | uint256 | undefined |
+| _prevTotalElements | uint256 | undefined |
+| _extraData | bytes | undefined |
+
+### StateBatchDeleted
+
+```solidity
+event StateBatchDeleted(uint256 indexed _batchIndex, bytes32 _batchRoot)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+
+
+
diff --git a/packages/contracts/docs/Initializable.md b/packages/contracts/docs/Initializable.md
new file mode 100644
index 0000000000000..bc953adafc465
--- /dev/null
+++ b/packages/contracts/docs/Initializable.md
@@ -0,0 +1,12 @@
+# Initializable
+
+
+
+
+
+
+
+*This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer function so it can only be called once. The {initializer} modifier provided by this contract will have this effect. TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}. CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.*
+
+
+
diff --git a/packages/contracts/docs/L1ChugSplashProxy.md b/packages/contracts/docs/L1ChugSplashProxy.md
new file mode 100644
index 0000000000000..dd4ae748fbc8a
--- /dev/null
+++ b/packages/contracts/docs/L1ChugSplashProxy.md
@@ -0,0 +1,98 @@
+# L1ChugSplashProxy
+
+
+
+> L1ChugSplashProxy
+
+
+
+*Basic ChugSplash proxy contract for L1. Very close to being a normal proxy but has added functions `setCode` and `setStorage` for changing the code or storage of the contract. Nifty! Note for future developers: do NOT make anything in this contract 'public' unless you know what you're doing. Anything public can potentially have a function signature that conflicts with a signature attached to the implementation contract. Public functions SHOULD always have the 'proxyCallIfNotOwner' modifier unless there's some *really* good reason not to have that modifier. And there almost certainly is not a good reason to not have that modifier. Beware!*
+
+## Methods
+
+### getImplementation
+
+```solidity
+function getImplementation() external nonpayable returns (address)
+```
+
+Queries the implementation address. Can only be called by the owner OR by making an eth_call and setting the "from" address to address(0).
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Implementation address.
+
+### getOwner
+
+```solidity
+function getOwner() external nonpayable returns (address)
+```
+
+Queries the owner of the proxy contract. Can only be called by the owner OR by making an eth_call and setting the "from" address to address(0).
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Owner address.
+
+### setCode
+
+```solidity
+function setCode(bytes _code) external nonpayable
+```
+
+Sets the code that should be running behind this proxy. Note that this scheme is a bit different from the standard proxy scheme where one would typically deploy the code separately and then set the implementation address. We're doing it this way because it gives us a lot more freedom on the client side. Can only be triggered by the contract owner.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _code | bytes | New contract code to run inside this contract.
+
+### setOwner
+
+```solidity
+function setOwner(address _owner) external nonpayable
+```
+
+Changes the owner of the proxy contract. Only callable by the owner.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _owner | address | New owner of the proxy contract.
+
+### setStorage
+
+```solidity
+function setStorage(bytes32 _key, bytes32 _value) external nonpayable
+```
+
+Modifies some storage slot within the proxy contract. Gives us a lot of power to perform upgrades in a more transparent way. Only callable by the owner.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _key | bytes32 | Storage key to modify.
+| _value | bytes32 | New value for the storage key.
+
+
+
+
diff --git a/packages/contracts/docs/L1CrossDomainMessenger.md b/packages/contracts/docs/L1CrossDomainMessenger.md
new file mode 100644
index 0000000000000..e7ae8abad46f1
--- /dev/null
+++ b/packages/contracts/docs/L1CrossDomainMessenger.md
@@ -0,0 +1,452 @@
+# L1CrossDomainMessenger
+
+
+
+> L1CrossDomainMessenger
+
+
+
+*The L1 Cross Domain Messenger contract sends messages from L1 to L2, and relays messages from L2 onto L1. In the event that a message sent from L1 to L2 is rejected for exceeding the L2 epoch gas limit, it can be resubmitted via this contract's replay function.*
+
+## Methods
+
+### allowMessage
+
+```solidity
+function allowMessage(bytes32 _xDomainCalldataHash) external nonpayable
+```
+
+Allow a message.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _xDomainCalldataHash | bytes32 | Hash of the message to block.
+
+### blockMessage
+
+```solidity
+function blockMessage(bytes32 _xDomainCalldataHash) external nonpayable
+```
+
+Block a message.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _xDomainCalldataHash | bytes32 | Hash of the message to block.
+
+### blockedMessages
+
+```solidity
+function blockedMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### initialize
+
+```solidity
+function initialize(address _libAddressManager) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _libAddressManager | address | Address of the Address Manager.
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+*Returns the address of the current owner.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### pause
+
+```solidity
+function pause() external nonpayable
+```
+
+Pause relaying.
+
+
+
+
+### paused
+
+```solidity
+function paused() external view returns (bool)
+```
+
+
+
+*Returns true if the contract is paused, and false otherwise.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### relayMessage
+
+```solidity
+function relayMessage(address _target, address _sender, bytes _message, uint256 _messageNonce, IL1CrossDomainMessenger.L2MessageInclusionProof _proof) external nonpayable
+```
+
+Relays a cross domain message to a contract.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Message sender address.
+| _message | bytes | Message to send to the target.
+| _messageNonce | uint256 | Nonce for the provided message.
+| _proof | IL1CrossDomainMessenger.L2MessageInclusionProof | Inclusion proof for the given message.
+
+### relayedMessages
+
+```solidity
+function relayedMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### renounceOwnership
+
+```solidity
+function renounceOwnership() external nonpayable
+```
+
+
+
+*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
+
+
+### replayMessage
+
+```solidity
+function replayMessage(address _target, address _sender, bytes _message, uint256 _queueIndex, uint32 _oldGasLimit, uint32 _newGasLimit) external nonpayable
+```
+
+Replays a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Original sender address.
+| _message | bytes | Message to send to the target.
+| _queueIndex | uint256 | CTC Queue index for the message to replay.
+| _oldGasLimit | uint32 | Original gas limit used to send the message.
+| _newGasLimit | uint32 | New gas limit to be used for this message.
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+### sendMessage
+
+```solidity
+function sendMessage(address _target, bytes _message, uint32 _gasLimit) external nonpayable
+```
+
+Sends a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _message | bytes | Message to send to the target.
+| _gasLimit | uint32 | Gas limit for the provided message.
+
+### successfulMessages
+
+```solidity
+function successfulMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferOwnership
+
+```solidity
+function transferOwnership(address newOwner) external nonpayable
+```
+
+
+
+*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| newOwner | address | undefined
+
+### xDomainMessageSender
+
+```solidity
+function xDomainMessageSender() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### FailedRelayedMessage
+
+```solidity
+event FailedRelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### MessageAllowed
+
+```solidity
+event MessageAllowed(bytes32 indexed _xDomainCalldataHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _xDomainCalldataHash `indexed` | bytes32 | undefined |
+
+### MessageBlocked
+
+```solidity
+event MessageBlocked(bytes32 indexed _xDomainCalldataHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _xDomainCalldataHash `indexed` | bytes32 | undefined |
+
+### OwnershipTransferred
+
+```solidity
+event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| previousOwner `indexed` | address | undefined |
+| newOwner `indexed` | address | undefined |
+
+### Paused
+
+```solidity
+event Paused(address account)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined |
+
+### RelayedMessage
+
+```solidity
+event RelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### SentMessage
+
+```solidity
+event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| target `indexed` | address | undefined |
+| sender | address | undefined |
+| message | bytes | undefined |
+| messageNonce | uint256 | undefined |
+| gasLimit | uint256 | undefined |
+
+### Unpaused
+
+```solidity
+event Unpaused(address account)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/L1StandardBridge.md b/packages/contracts/docs/L1StandardBridge.md
new file mode 100644
index 0000000000000..a88c837092b83
--- /dev/null
+++ b/packages/contracts/docs/L1StandardBridge.md
@@ -0,0 +1,299 @@
+# L1StandardBridge
+
+
+
+> L1StandardBridge
+
+
+
+*The L1 ETH and ERC20 Bridge is a contract which stores deposited L1 funds and standard tokens that are in use on L2. It synchronizes a corresponding L2 Bridge, informing it of deposits and listening to it for newly finalized withdrawals.*
+
+## Methods
+
+### depositERC20
+
+```solidity
+function depositERC20(address _l1Token, address _l2Token, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of the ERC20 to the caller's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _amount | uint256 | Amount of the ERC20 to deposit
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositERC20To
+
+```solidity
+function depositERC20To(address _l1Token, address _l2Token, address _to, uint256 _amount, uint32 _l2Gas, bytes _data) external nonpayable
+```
+
+
+
+*deposit an amount of ERC20 to a recipient's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the L1 ERC20 we are depositing
+| _l2Token | address | Address of the L1 respective L2 ERC20
+| _to | address | L2 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositETH
+
+```solidity
+function depositETH(uint32 _l2Gas, bytes _data) external payable
+```
+
+
+
+*Deposit an amount of the ETH to the caller's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### depositETHTo
+
+```solidity
+function depositETHTo(address _to, uint32 _l2Gas, bytes _data) external payable
+```
+
+
+
+*Deposit an amount of ETH to a recipient's balance on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _to | address | L2 address to credit the withdrawal to.
+| _l2Gas | uint32 | Gas limit required to complete the deposit on L2.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### deposits
+
+```solidity
+function deposits(address, address) external view returns (uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+| _1 | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### donateETH
+
+```solidity
+function donateETH() external payable
+```
+
+
+
+*Adds ETH balance to the account. This is meant to allow for ETH to be migrated from an old gateway to a new gateway. NOTE: This is left for one upgrade only so we are able to receive the migrated ETH from the old contract*
+
+
+### finalizeERC20Withdrawal
+
+```solidity
+function finalizeERC20Withdrawal(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the L1 ERC20 token. This call will fail if the initialized withdrawal from L2 has not been finalized.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of L1 token to finalizeWithdrawal for.
+| _l2Token | address | Address of L2 token where withdrawal was initiated.
+| _from | address | L2 address initiating the transfer.
+| _to | address | L1 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _data | bytes | Data provided by the sender on L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### finalizeETHWithdrawal
+
+```solidity
+function finalizeETHWithdrawal(address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a withdrawal from L2 to L1, and credit funds to the recipient's balance of the L1 ETH token. Since only the xDomainMessenger can call this function, it will never be called before the withdrawal is finalized.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from | address | L2 address initiating the transfer.
+| _to | address | L1 address to credit the withdrawal to.
+| _amount | uint256 | Amount of the ERC20 to deposit.
+| _data | bytes | Optional data to forward to L2. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### initialize
+
+```solidity
+function initialize(address _l1messenger, address _l2TokenBridge) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1messenger | address | L1 Messenger address being used for cross-chain communications.
+| _l2TokenBridge | address | L2 standard bridge address.
+
+### l2TokenBridge
+
+```solidity
+function l2TokenBridge() external view returns (address)
+```
+
+
+
+*get the address of the corresponding L2 bridge contract.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address of the corresponding L2 bridge contract.
+
+### messenger
+
+```solidity
+function messenger() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### ERC20DepositInitiated
+
+```solidity
+event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ERC20WithdrawalFinalized
+
+```solidity
+event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ETHDepositInitiated
+
+```solidity
+event ETHDepositInitiated(address indexed _from, address indexed _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from `indexed` | address | undefined |
+| _to `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### ETHWithdrawalFinalized
+
+```solidity
+event ETHWithdrawalFinalized(address indexed _from, address indexed _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from `indexed` | address | undefined |
+| _to `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/L2CrossDomainMessenger.md b/packages/contracts/docs/L2CrossDomainMessenger.md
new file mode 100644
index 0000000000000..a5a7599264d88
--- /dev/null
+++ b/packages/contracts/docs/L2CrossDomainMessenger.md
@@ -0,0 +1,224 @@
+# L2CrossDomainMessenger
+
+
+
+> L2CrossDomainMessenger
+
+
+
+*The L2 Cross Domain Messenger contract sends messages from L2 to L1, and is the entry point for L2 messages sent via the L1 Cross Domain Messenger.*
+
+## Methods
+
+### l1CrossDomainMessenger
+
+```solidity
+function l1CrossDomainMessenger() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### messageNonce
+
+```solidity
+function messageNonce() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### relayMessage
+
+```solidity
+function relayMessage(address _target, address _sender, bytes _message, uint256 _messageNonce) external nonpayable
+```
+
+Relays a cross domain message to a contract.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _sender | address | Message sender address.
+| _message | bytes | Message to send to the target.
+| _messageNonce | uint256 | Nonce for the provided message.
+
+### relayedMessages
+
+```solidity
+function relayedMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### sendMessage
+
+```solidity
+function sendMessage(address _target, bytes _message, uint32 _gasLimit) external nonpayable
+```
+
+Sends a cross domain message to the target messenger.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _target | address | Target contract address.
+| _message | bytes | Message to send to the target.
+| _gasLimit | uint32 | Gas limit for the provided message.
+
+### sentMessages
+
+```solidity
+function sentMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### successfulMessages
+
+```solidity
+function successfulMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### xDomainMessageSender
+
+```solidity
+function xDomainMessageSender() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+
+
+## Events
+
+### FailedRelayedMessage
+
+```solidity
+event FailedRelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### RelayedMessage
+
+```solidity
+event RelayedMessage(bytes32 indexed msgHash)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| msgHash `indexed` | bytes32 | undefined |
+
+### SentMessage
+
+```solidity
+event SentMessage(address indexed target, address sender, bytes message, uint256 messageNonce, uint256 gasLimit)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| target `indexed` | address | undefined |
+| sender | address | undefined |
+| message | bytes | undefined |
+| messageNonce | uint256 | undefined |
+| gasLimit | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/L2StandardBridge.md b/packages/contracts/docs/L2StandardBridge.md
new file mode 100644
index 0000000000000..e10faef99724d
--- /dev/null
+++ b/packages/contracts/docs/L2StandardBridge.md
@@ -0,0 +1,175 @@
+# L2StandardBridge
+
+
+
+> L2StandardBridge
+
+
+
+*The L2 Standard bridge is a contract which works together with the L1 Standard bridge to enable ETH and ERC20 transitions between L1 and L2. This contract acts as a minter for new tokens when it hears about deposits into the L1 Standard bridge. This contract also acts as a burner of the tokens intended for withdrawal, informing the L1 bridge to release L1 funds.*
+
+## Methods
+
+### finalizeDeposit
+
+```solidity
+function finalizeDeposit(address _l1Token, address _l2Token, address _from, address _to, uint256 _amount, bytes _data) external nonpayable
+```
+
+
+
+*Complete a deposit from L1 to L2, and credits funds to the recipient's balance of this L2 token. This call will fail if it did not originate from a corresponding deposit in L1StandardTokenBridge.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address for the l1 token this is called with
+| _l2Token | address | Address for the l2 token this is called with
+| _from | address | Account to pull the deposit from on L2.
+| _to | address | Address to receive the withdrawal at
+| _amount | uint256 | Amount of the token to withdraw
+| _data | bytes | Data provider by the sender on L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### l1TokenBridge
+
+```solidity
+function l1TokenBridge() external view returns (address)
+```
+
+
+
+*get the address of the corresponding L1 bridge contract.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address of the corresponding L1 bridge contract.
+
+### messenger
+
+```solidity
+function messenger() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### withdraw
+
+```solidity
+function withdraw(address _l2Token, uint256 _amount, uint32 _l1Gas, bytes _data) external nonpayable
+```
+
+
+
+*initiate a withdraw of some tokens to the caller's account on L1*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Token | address | Address of L2 token where withdrawal was initiated.
+| _amount | uint256 | Amount of the token to withdraw. param _l1Gas Unused, but included for potential forward compatibility considerations.
+| _l1Gas | uint32 | undefined
+| _data | bytes | Optional data to forward to L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+### withdrawTo
+
+```solidity
+function withdrawTo(address _l2Token, address _to, uint256 _amount, uint32 _l1Gas, bytes _data) external nonpayable
+```
+
+
+
+*initiate a withdraw of some token to a recipient's account on L1.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l2Token | address | Address of L2 token where withdrawal is initiated.
+| _to | address | L1 adress to credit the withdrawal to.
+| _amount | uint256 | Amount of the token to withdraw. param _l1Gas Unused, but included for potential forward compatibility considerations.
+| _l1Gas | uint32 | undefined
+| _data | bytes | Optional data to forward to L1. This data is provided solely as a convenience for external contracts. Aside from enforcing a maximum length, these contracts provide no guarantees about its content.
+
+
+
+## Events
+
+### DepositFailed
+
+```solidity
+event DepositFailed(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### DepositFinalized
+
+```solidity
+event DepositFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+### WithdrawalInitiated
+
+```solidity
+event WithdrawalInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+| _from `indexed` | address | undefined |
+| _to | address | undefined |
+| _amount | uint256 | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/L2StandardERC20.md b/packages/contracts/docs/L2StandardERC20.md
new file mode 100644
index 0000000000000..6f8102421ab5d
--- /dev/null
+++ b/packages/contracts/docs/L2StandardERC20.md
@@ -0,0 +1,407 @@
+# L2StandardERC20
+
+
+
+
+
+
+
+
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*See {IERC20-allowance}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-approve}. Requirements: - `spender` cannot be the zero address.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*See {IERC20-balanceOf}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### burn
+
+```solidity
+function burn(address _from, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from | address | undefined
+| _amount | uint256 | undefined
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint8)
+```
+
+
+
+*Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless this function is overridden; NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint8 | undefined
+
+### decreaseAllowance
+
+```solidity
+function decreaseAllowance(address spender, uint256 subtractedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| subtractedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### increaseAllowance
+
+```solidity
+function increaseAllowance(address spender, uint256 addedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| addedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### l1Token
+
+```solidity
+function l1Token() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### l2Bridge
+
+```solidity
+function l2Bridge() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### mint
+
+```solidity
+function mint(address _to, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _to | address | undefined
+| _amount | uint256 | undefined
+
+### name
+
+```solidity
+function name() external view returns (string)
+```
+
+
+
+*Returns the name of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### supportsInterface
+
+```solidity
+function supportsInterface(bytes4 _interfaceId) external pure returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _interfaceId | bytes4 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+
+
+*Returns the symbol of the token, usually a shorter version of the name.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*See {IERC20-totalSupply}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-transfer}. Requirements: - `recipient` cannot be the zero address. - the caller must have a balance of at least `amount`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*See {IERC20-transferFrom}. Emits an {Approval} event indicating the updated allowance. This is not required by the EIP. See the note at the beginning of {ERC20}. Requirements: - `sender` and `recipient` cannot be the zero address. - `sender` must have a balance of at least `amount`. - the caller must have allowance for ``sender``'s tokens of at least `amount`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Burn
+
+```solidity
+event Burn(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Mint
+
+```solidity
+event Mint(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/L2StandardTokenFactory.md b/packages/contracts/docs/L2StandardTokenFactory.md
new file mode 100644
index 0000000000000..6a3cdc21a7337
--- /dev/null
+++ b/packages/contracts/docs/L2StandardTokenFactory.md
@@ -0,0 +1,53 @@
+# L2StandardTokenFactory
+
+
+
+> L2StandardTokenFactory
+
+
+
+*Factory contract for creating standard L2 token representations of L1 ERC20s compatible with and working on the standard bridge.*
+
+## Methods
+
+### createStandardL2Token
+
+```solidity
+function createStandardL2Token(address _l1Token, string _name, string _symbol) external nonpayable
+```
+
+
+
+*Creates an instance of the standard ERC20 token on L2.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token | address | Address of the corresponding L1 token.
+| _name | string | ERC20 name.
+| _symbol | string | ERC20 symbol.
+
+
+
+## Events
+
+### StandardL2TokenCreated
+
+```solidity
+event StandardL2TokenCreated(address indexed _l1Token, address indexed _l2Token)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _l1Token `indexed` | address | undefined |
+| _l2Token `indexed` | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/Lib_AddressManager.md b/packages/contracts/docs/Lib_AddressManager.md
new file mode 100644
index 0000000000000..88b4cdb96f69b
--- /dev/null
+++ b/packages/contracts/docs/Lib_AddressManager.md
@@ -0,0 +1,136 @@
+# Lib_AddressManager
+
+
+
+> Lib_AddressManager
+
+
+
+
+
+## Methods
+
+### getAddress
+
+```solidity
+function getAddress(string _name) external view returns (address)
+```
+
+Retrieves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to retrieve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+*Returns the address of the current owner.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### renounceOwnership
+
+```solidity
+function renounceOwnership() external nonpayable
+```
+
+
+
+*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
+
+
+### setAddress
+
+```solidity
+function setAddress(string _name, address _address) external nonpayable
+```
+
+Changes the address associated with a particular name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | String name to associate an address with.
+| _address | address | Address to associate with the name.
+
+### transferOwnership
+
+```solidity
+function transferOwnership(address newOwner) external nonpayable
+```
+
+
+
+*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| newOwner | address | undefined
+
+
+
+## Events
+
+### AddressSet
+
+```solidity
+event AddressSet(string indexed _name, address _newAddress, address _oldAddress)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name `indexed` | string | undefined |
+| _newAddress | address | undefined |
+| _oldAddress | address | undefined |
+
+### OwnershipTransferred
+
+```solidity
+event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| previousOwner `indexed` | address | undefined |
+| newOwner `indexed` | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/Lib_AddressResolver.md b/packages/contracts/docs/Lib_AddressResolver.md
new file mode 100644
index 0000000000000..090f300e8cdc1
--- /dev/null
+++ b/packages/contracts/docs/Lib_AddressResolver.md
@@ -0,0 +1,54 @@
+# Lib_AddressResolver
+
+
+
+> Lib_AddressResolver
+
+
+
+
+
+## Methods
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+
+
+
diff --git a/packages/contracts/docs/Lib_Buffer.md b/packages/contracts/docs/Lib_Buffer.md
new file mode 100644
index 0000000000000..88d22e08b985a
--- /dev/null
+++ b/packages/contracts/docs/Lib_Buffer.md
@@ -0,0 +1,12 @@
+# Lib_Buffer
+
+
+
+> Lib_Buffer
+
+
+
+*This library implements a bytes32 storage array with some additional gas-optimized functionality. In particular, it encodes its length as a uint40, and tightly packs this with an overwritable "extra data" field so we can store more information with a single SSTORE.*
+
+
+
diff --git a/packages/contracts/docs/Lib_Bytes32Utils.md b/packages/contracts/docs/Lib_Bytes32Utils.md
new file mode 100644
index 0000000000000..12f8b4f0660db
--- /dev/null
+++ b/packages/contracts/docs/Lib_Bytes32Utils.md
@@ -0,0 +1,12 @@
+# Lib_Bytes32Utils
+
+
+
+> Lib_Byte32Utils
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_BytesUtils.md b/packages/contracts/docs/Lib_BytesUtils.md
new file mode 100644
index 0000000000000..ebd0c491e713a
--- /dev/null
+++ b/packages/contracts/docs/Lib_BytesUtils.md
@@ -0,0 +1,12 @@
+# Lib_BytesUtils
+
+
+
+> Lib_BytesUtils
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_CrossDomainUtils.md b/packages/contracts/docs/Lib_CrossDomainUtils.md
new file mode 100644
index 0000000000000..f60f77d2e1ba5
--- /dev/null
+++ b/packages/contracts/docs/Lib_CrossDomainUtils.md
@@ -0,0 +1,12 @@
+# Lib_CrossDomainUtils
+
+
+
+> Lib_CrossDomainUtils
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_DefaultValues.md b/packages/contracts/docs/Lib_DefaultValues.md
new file mode 100644
index 0000000000000..174cf216113f6
--- /dev/null
+++ b/packages/contracts/docs/Lib_DefaultValues.md
@@ -0,0 +1,12 @@
+# Lib_DefaultValues
+
+
+
+> Lib_DefaultValues
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_MerkleTree.md b/packages/contracts/docs/Lib_MerkleTree.md
new file mode 100644
index 0000000000000..056a16fb0daba
--- /dev/null
+++ b/packages/contracts/docs/Lib_MerkleTree.md
@@ -0,0 +1,12 @@
+# Lib_MerkleTree
+
+*River Keefer*
+
+> Lib_MerkleTree
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_MerkleTrie.md b/packages/contracts/docs/Lib_MerkleTrie.md
new file mode 100644
index 0000000000000..f64b16113c9e4
--- /dev/null
+++ b/packages/contracts/docs/Lib_MerkleTrie.md
@@ -0,0 +1,12 @@
+# Lib_MerkleTrie
+
+
+
+> Lib_MerkleTrie
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_OVMCodec.md b/packages/contracts/docs/Lib_OVMCodec.md
new file mode 100644
index 0000000000000..cd7d6c50c9952
--- /dev/null
+++ b/packages/contracts/docs/Lib_OVMCodec.md
@@ -0,0 +1,12 @@
+# Lib_OVMCodec
+
+
+
+> Lib_OVMCodec
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_PredeployAddresses.md b/packages/contracts/docs/Lib_PredeployAddresses.md
new file mode 100644
index 0000000000000..abd82c7b102ea
--- /dev/null
+++ b/packages/contracts/docs/Lib_PredeployAddresses.md
@@ -0,0 +1,12 @@
+# Lib_PredeployAddresses
+
+
+
+> Lib_PredeployAddresses
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_RLPReader.md b/packages/contracts/docs/Lib_RLPReader.md
new file mode 100644
index 0000000000000..c7a2b7c6f701a
--- /dev/null
+++ b/packages/contracts/docs/Lib_RLPReader.md
@@ -0,0 +1,12 @@
+# Lib_RLPReader
+
+
+
+> Lib_RLPReader
+
+
+
+*Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com).*
+
+
+
diff --git a/packages/contracts/docs/Lib_RLPWriter.md b/packages/contracts/docs/Lib_RLPWriter.md
new file mode 100644
index 0000000000000..2ded2234c63b4
--- /dev/null
+++ b/packages/contracts/docs/Lib_RLPWriter.md
@@ -0,0 +1,12 @@
+# Lib_RLPWriter
+
+*Bakaoh (with modifications)*
+
+> Lib_RLPWriter
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_ResolvedDelegateProxy.md b/packages/contracts/docs/Lib_ResolvedDelegateProxy.md
new file mode 100644
index 0000000000000..0150bb2bb2fad
--- /dev/null
+++ b/packages/contracts/docs/Lib_ResolvedDelegateProxy.md
@@ -0,0 +1,12 @@
+# Lib_ResolvedDelegateProxy
+
+
+
+> Lib_ResolvedDelegateProxy
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Lib_SecureMerkleTrie.md b/packages/contracts/docs/Lib_SecureMerkleTrie.md
new file mode 100644
index 0000000000000..05610153a76bd
--- /dev/null
+++ b/packages/contracts/docs/Lib_SecureMerkleTrie.md
@@ -0,0 +1,12 @@
+# Lib_SecureMerkleTrie
+
+
+
+> Lib_SecureMerkleTrie
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/OVM_DeployerWhitelist.md b/packages/contracts/docs/OVM_DeployerWhitelist.md
new file mode 100644
index 0000000000000..8b359dbd757b8
--- /dev/null
+++ b/packages/contracts/docs/OVM_DeployerWhitelist.md
@@ -0,0 +1,173 @@
+# OVM_DeployerWhitelist
+
+
+
+> OVM_DeployerWhitelist
+
+
+
+*The Deployer Whitelist is a temporary predeploy used to provide additional safety during the initial phases of our mainnet roll out. It is owned by the Optimism team, and defines accounts which are allowed to deploy contracts on Layer2. The Execution Manager will only allow an ovmCREATE or ovmCREATE2 operation to proceed if the deployer's address whitelisted.*
+
+## Methods
+
+### enableArbitraryContractDeployment
+
+```solidity
+function enableArbitraryContractDeployment() external nonpayable
+```
+
+Permanently enables arbitrary contract deployment and deletes the owner.
+
+
+
+
+### isDeployerAllowed
+
+```solidity
+function isDeployerAllowed(address _deployer) external view returns (bool)
+```
+
+Checks whether an address is allowed to deploy contracts.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _deployer | address | Address to check.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | _allowed Whether or not the address can deploy contracts.
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### setOwner
+
+```solidity
+function setOwner(address _owner) external nonpayable
+```
+
+Updates the owner of this contract.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _owner | address | Address of the new owner.
+
+### setWhitelistedDeployer
+
+```solidity
+function setWhitelistedDeployer(address _deployer, bool _isWhitelisted) external nonpayable
+```
+
+Adds or removes an address from the deployment whitelist.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _deployer | address | Address to update permissions for.
+| _isWhitelisted | bool | Whether or not the address is whitelisted.
+
+### whitelist
+
+```solidity
+function whitelist(address) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### OwnerChanged
+
+```solidity
+event OwnerChanged(address oldOwner, address newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| oldOwner | address | undefined |
+| newOwner | address | undefined |
+
+### WhitelistDisabled
+
+```solidity
+event WhitelistDisabled(address oldOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| oldOwner | address | undefined |
+
+### WhitelistStatusChanged
+
+```solidity
+event WhitelistStatusChanged(address deployer, bool whitelisted)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| deployer | address | undefined |
+| whitelisted | bool | undefined |
+
+
+
diff --git a/packages/contracts/docs/OVM_ETH.md b/packages/contracts/docs/OVM_ETH.md
new file mode 100644
index 0000000000000..113f39014e8d1
--- /dev/null
+++ b/packages/contracts/docs/OVM_ETH.md
@@ -0,0 +1,407 @@
+# OVM_ETH
+
+
+
+> OVM_ETH
+
+
+
+*The ETH predeploy provides an ERC20 interface for ETH deposited to Layer 2. Note that unlike on Layer 1, Layer 2 accounts do not have a balance field.*
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address owner, address spender) external view returns (uint256)
+```
+
+
+
+*See {IERC20-allowance}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner | address | undefined
+| spender | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address spender, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Sets `amount` as the allowance of `spender` over the caller's tokens. Returns a boolean value indicating whether the operation succeeded. IMPORTANT: Beware that changing an allowance with this method brings the risk that someone may use both the old and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 Emits an {Approval} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address account) external view returns (uint256)
+```
+
+
+
+*See {IERC20-balanceOf}.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### burn
+
+```solidity
+function burn(address _from, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _from | address | undefined
+| _amount | uint256 | undefined
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint8)
+```
+
+
+
+*Returns the number of decimals used to get its user representation. For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). Tokens usually opt for a value of 18, imitating the relationship between Ether and Wei. This is the value {ERC20} uses, unless this function is overridden; NOTE: This information is only used for _display_ purposes: it in no way affects any of the arithmetic of the contract, including {IERC20-balanceOf} and {IERC20-transfer}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint8 | undefined
+
+### decreaseAllowance
+
+```solidity
+function decreaseAllowance(address spender, uint256 subtractedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically decreases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address. - `spender` must have allowance for the caller of at least `subtractedValue`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| subtractedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### increaseAllowance
+
+```solidity
+function increaseAllowance(address spender, uint256 addedValue) external nonpayable returns (bool)
+```
+
+
+
+*Atomically increases the allowance granted to `spender` by the caller. This is an alternative to {approve} that can be used as a mitigation for problems described in {IERC20-approve}. Emits an {Approval} event indicating the updated allowance. Requirements: - `spender` cannot be the zero address.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| spender | address | undefined
+| addedValue | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### l1Token
+
+```solidity
+function l1Token() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### l2Bridge
+
+```solidity
+function l2Bridge() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### mint
+
+```solidity
+function mint(address _to, uint256 _amount) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _to | address | undefined
+| _amount | uint256 | undefined
+
+### name
+
+```solidity
+function name() external view returns (string)
+```
+
+
+
+*Returns the name of the token.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### supportsInterface
+
+```solidity
+function supportsInterface(bytes4 _interfaceId) external pure returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _interfaceId | bytes4 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+
+
+*Returns the symbol of the token, usually a shorter version of the name.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+*See {IERC20-totalSupply}.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from the caller's account to `recipient`. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address sender, address recipient, uint256 amount) external nonpayable returns (bool)
+```
+
+
+
+*Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns a boolean value indicating whether the operation succeeded. Emits a {Transfer} event.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| sender | address | undefined
+| recipient | address | undefined
+| amount | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed owner, address indexed spender, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| owner `indexed` | address | undefined |
+| spender `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+### Burn
+
+```solidity
+event Burn(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Mint
+
+```solidity
+event Mint(address indexed _account, uint256 _amount)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _account `indexed` | address | undefined |
+| _amount | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed from, address indexed to, uint256 value)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| from `indexed` | address | undefined |
+| to `indexed` | address | undefined |
+| value | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/OVM_GasPriceOracle.md b/packages/contracts/docs/OVM_GasPriceOracle.md
new file mode 100644
index 0000000000000..35ad7fe77d2a4
--- /dev/null
+++ b/packages/contracts/docs/OVM_GasPriceOracle.md
@@ -0,0 +1,368 @@
+# OVM_GasPriceOracle
+
+
+
+> OVM_GasPriceOracle
+
+
+
+*This contract exposes the current l2 gas price, a measure of how congested the network currently is. This measure is used by the Sequencer to determine what fee to charge for transactions. When the system is more congested, the l2 gas price will increase and fees will also increase as a result. All public variables are set while generating the initial L2 state. The constructor doesn't run in practice as the L2 state generation script uses the deployed bytecode instead of running the initcode.*
+
+## Methods
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### gasPrice
+
+```solidity
+function gasPrice() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### getL1Fee
+
+```solidity
+function getL1Fee(bytes _data) external view returns (uint256)
+```
+
+Computes the L1 portion of the fee based on the size of the RLP encoded tx and the current l1BaseFee
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _data | bytes | Unsigned RLP encoded tx, 6 elements
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | L1 fee that should be paid for the tx
+
+### getL1GasUsed
+
+```solidity
+function getL1GasUsed(bytes _data) external view returns (uint256)
+```
+
+Computes the amount of L1 gas used for a transaction The overhead represents the per batch gas overhead of posting both transaction and state roots to L1 given larger batch sizes. 4 gas for 0 byte https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L33 16 gas for non zero byte https://github.com/ethereum/go-ethereum/blob/9ada4a2e2c415e6b0b51c50e901336872e028872/params/protocol_params.go#L87 This will need to be updated if calldata gas prices change Account for the transaction being unsigned Padding is added to account for lack of signature on transaction 1 byte for RLP V prefix 1 byte for V 1 byte for RLP R prefix 32 bytes for R 1 byte for RLP S prefix 32 bytes for S Total: 68 bytes of padding
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _data | bytes | Unsigned RLP encoded tx, 6 elements
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | Amount of L1 gas used for a transaction
+
+### l1BaseFee
+
+```solidity
+function l1BaseFee() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### overhead
+
+```solidity
+function overhead() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+*Returns the address of the current owner.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### renounceOwnership
+
+```solidity
+function renounceOwnership() external nonpayable
+```
+
+
+
+*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
+
+
+### scalar
+
+```solidity
+function scalar() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### setDecimals
+
+```solidity
+function setDecimals(uint256 _decimals) external nonpayable
+```
+
+Allows the owner to modify the decimals.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _decimals | uint256 | New decimals
+
+### setGasPrice
+
+```solidity
+function setGasPrice(uint256 _gasPrice) external nonpayable
+```
+
+Allows the owner to modify the l2 gas price.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _gasPrice | uint256 | New l2 gas price.
+
+### setL1BaseFee
+
+```solidity
+function setL1BaseFee(uint256 _baseFee) external nonpayable
+```
+
+Allows the owner to modify the l1 base fee.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _baseFee | uint256 | New l1 base fee
+
+### setOverhead
+
+```solidity
+function setOverhead(uint256 _overhead) external nonpayable
+```
+
+Allows the owner to modify the overhead.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _overhead | uint256 | New overhead
+
+### setScalar
+
+```solidity
+function setScalar(uint256 _scalar) external nonpayable
+```
+
+Allows the owner to modify the scalar.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _scalar | uint256 | New scalar
+
+### transferOwnership
+
+```solidity
+function transferOwnership(address newOwner) external nonpayable
+```
+
+
+
+*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| newOwner | address | undefined
+
+
+
+## Events
+
+### DecimalsUpdated
+
+```solidity
+event DecimalsUpdated(uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined |
+
+### GasPriceUpdated
+
+```solidity
+event GasPriceUpdated(uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined |
+
+### L1BaseFeeUpdated
+
+```solidity
+event L1BaseFeeUpdated(uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined |
+
+### OverheadUpdated
+
+```solidity
+event OverheadUpdated(uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined |
+
+### OwnershipTransferred
+
+```solidity
+event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| previousOwner `indexed` | address | undefined |
+| newOwner `indexed` | address | undefined |
+
+### ScalarUpdated
+
+```solidity
+event ScalarUpdated(uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/OVM_L2ToL1MessagePasser.md b/packages/contracts/docs/OVM_L2ToL1MessagePasser.md
new file mode 100644
index 0000000000000..9626274e0de13
--- /dev/null
+++ b/packages/contracts/docs/OVM_L2ToL1MessagePasser.md
@@ -0,0 +1,74 @@
+# OVM_L2ToL1MessagePasser
+
+
+
+> OVM_L2ToL1MessagePasser
+
+
+
+*The L2 to L1 Message Passer is a utility contract which facilitate an L1 proof of the of a message on L2. The L1 Cross Domain Messenger performs this proof in its _verifyStorageProof function, which verifies the existence of the transaction hash in this contract's `sentMessages` mapping.*
+
+## Methods
+
+### passMessageToL1
+
+```solidity
+function passMessageToL1(bytes _message) external nonpayable
+```
+
+Passes a message to L1.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _message | bytes | Message to pass to L1.
+
+### sentMessages
+
+```solidity
+function sentMessages(bytes32) external view returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bytes32 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### L2ToL1Message
+
+```solidity
+event L2ToL1Message(uint256 _nonce, address _sender, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _nonce | uint256 | undefined |
+| _sender | address | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/docs/OVM_SequencerFeeVault.md b/packages/contracts/docs/OVM_SequencerFeeVault.md
new file mode 100644
index 0000000000000..f0c277925fa1b
--- /dev/null
+++ b/packages/contracts/docs/OVM_SequencerFeeVault.md
@@ -0,0 +1,60 @@
+# OVM_SequencerFeeVault
+
+
+
+> OVM_SequencerFeeVault
+
+
+
+*Simple holding contract for fees paid to the Sequencer. Likely to be replaced in the future but "good enough for now".*
+
+## Methods
+
+### MIN_WITHDRAWAL_AMOUNT
+
+```solidity
+function MIN_WITHDRAWAL_AMOUNT() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### l1FeeWallet
+
+```solidity
+function l1FeeWallet() external view returns (address)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### withdraw
+
+```solidity
+function withdraw() external nonpayable
+```
+
+
+
+
+
+
+
+
+
diff --git a/packages/contracts/docs/Ownable.md b/packages/contracts/docs/Ownable.md
new file mode 100644
index 0000000000000..7ec44aec40e16
--- /dev/null
+++ b/packages/contracts/docs/Ownable.md
@@ -0,0 +1,79 @@
+# Ownable
+
+
+
+
+
+
+
+*Contract module which provides a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions. By default, the owner account will be the one that deploys the contract. This can later be changed with {transferOwnership}. This module is used through inheritance. It will make available the modifier `onlyOwner`, which can be applied to your functions to restrict their use to the owner.*
+
+## Methods
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+*Returns the address of the current owner.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### renounceOwnership
+
+```solidity
+function renounceOwnership() external nonpayable
+```
+
+
+
+*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
+
+
+### transferOwnership
+
+```solidity
+function transferOwnership(address newOwner) external nonpayable
+```
+
+
+
+*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| newOwner | address | undefined
+
+
+
+## Events
+
+### OwnershipTransferred
+
+```solidity
+event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| previousOwner `indexed` | address | undefined |
+| newOwner `indexed` | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/OwnableUpgradeable.md b/packages/contracts/docs/OwnableUpgradeable.md
new file mode 100644
index 0000000000000..53a8b1603ec8b
--- /dev/null
+++ b/packages/contracts/docs/OwnableUpgradeable.md
@@ -0,0 +1,79 @@
+# OwnableUpgradeable
+
+
+
+
+
+
+
+*Contract module which provides a basic access control mechanism, where there is an account (an owner) that can be granted exclusive access to specific functions. By default, the owner account will be the one that deploys the contract. This can later be changed with {transferOwnership}. This module is used through inheritance. It will make available the modifier `onlyOwner`, which can be applied to your functions to restrict their use to the owner.*
+
+## Methods
+
+### owner
+
+```solidity
+function owner() external view returns (address)
+```
+
+
+
+*Returns the address of the current owner.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+### renounceOwnership
+
+```solidity
+function renounceOwnership() external nonpayable
+```
+
+
+
+*Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.*
+
+
+### transferOwnership
+
+```solidity
+function transferOwnership(address newOwner) external nonpayable
+```
+
+
+
+*Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| newOwner | address | undefined
+
+
+
+## Events
+
+### OwnershipTransferred
+
+```solidity
+event OwnershipTransferred(address indexed previousOwner, address indexed newOwner)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| previousOwner `indexed` | address | undefined |
+| newOwner `indexed` | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/PausableUpgradeable.md b/packages/contracts/docs/PausableUpgradeable.md
new file mode 100644
index 0000000000000..ed2f541504b2f
--- /dev/null
+++ b/packages/contracts/docs/PausableUpgradeable.md
@@ -0,0 +1,67 @@
+# PausableUpgradeable
+
+
+
+
+
+
+
+*Contract module which allows children to implement an emergency stop mechanism that can be triggered by an authorized account. This module is used through inheritance. It will make available the modifiers `whenNotPaused` and `whenPaused`, which can be applied to the functions of your contract. Note that they will not be pausable by simply including this module, only once the modifiers are put in place.*
+
+## Methods
+
+### paused
+
+```solidity
+function paused() external view returns (bool)
+```
+
+
+
+*Returns true if the contract is paused, and false otherwise.*
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### Paused
+
+```solidity
+event Paused(address account)
+```
+
+
+
+*Emitted when the pause is triggered by `account`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined |
+
+### Unpaused
+
+```solidity
+event Unpaused(address account)
+```
+
+
+
+*Emitted when the pause is lifted by `account`.*
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| account | address | undefined |
+
+
+
diff --git a/packages/contracts/docs/ReentrancyGuardUpgradeable.md b/packages/contracts/docs/ReentrancyGuardUpgradeable.md
new file mode 100644
index 0000000000000..6955ca2de5975
--- /dev/null
+++ b/packages/contracts/docs/ReentrancyGuardUpgradeable.md
@@ -0,0 +1,12 @@
+# ReentrancyGuardUpgradeable
+
+
+
+
+
+
+
+*Contract module that helps prevent reentrant calls to a function. Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier available, which can be applied to functions to make sure there are no nested (reentrant) calls to them. Note that because there is a single `nonReentrant` guard, functions marked as `nonReentrant` may not call one another. This can be worked around by making those functions `private`, and then adding `external` `nonReentrant` entry points to them. TIP: If you would like to learn more about reentrancy and alternative ways to protect against it, check out our blog post https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].*
+
+
+
diff --git a/packages/contracts/docs/SafeERC20.md b/packages/contracts/docs/SafeERC20.md
new file mode 100644
index 0000000000000..80367d00d6edd
--- /dev/null
+++ b/packages/contracts/docs/SafeERC20.md
@@ -0,0 +1,12 @@
+# SafeERC20
+
+
+
+> SafeERC20
+
+
+
+*Wrappers around ERC20 operations that throw on failure (when the token contract returns false). Tokens that return no value (and instead revert or throw on failure) are also supported, non-reverting calls are assumed to be successful. To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, which allows you to call the safe operations as `token.safeTransfer(...)`, etc.*
+
+
+
diff --git a/packages/contracts/docs/StateCommitmentChain.md b/packages/contracts/docs/StateCommitmentChain.md
new file mode 100644
index 0000000000000..3981d905fb410
--- /dev/null
+++ b/packages/contracts/docs/StateCommitmentChain.md
@@ -0,0 +1,275 @@
+# StateCommitmentChain
+
+
+
+> StateCommitmentChain
+
+
+
+*The State Commitment Chain (SCC) contract contains a list of proposed state roots which Proposers assert to be a result of each transaction in the Canonical Transaction Chain (CTC). Elements here have a 1:1 correspondence with transactions in the CTC, and should be the unique state root calculated off-chain by applying the canonical transactions one by one.*
+
+## Methods
+
+### FRAUD_PROOF_WINDOW
+
+```solidity
+function FRAUD_PROOF_WINDOW() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### SEQUENCER_PUBLISH_WINDOW
+
+```solidity
+function SEQUENCER_PUBLISH_WINDOW() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### appendStateBatch
+
+```solidity
+function appendStateBatch(bytes32[] _batch, uint256 _shouldStartAtElement) external nonpayable
+```
+
+Appends a batch of state roots to the chain.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batch | bytes32[] | Batch of state roots.
+| _shouldStartAtElement | uint256 | Index of the element at which this batch should start.
+
+### batches
+
+```solidity
+function batches() external view returns (contract IChainStorageContainer)
+```
+
+Accesses the batch storage container.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract IChainStorageContainer | Reference to the batch storage container.
+
+### deleteStateBatch
+
+```solidity
+function deleteStateBatch(Lib_OVMCodec.ChainBatchHeader _batchHeader) external nonpayable
+```
+
+Deletes all state roots after (and including) a given batch.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch to start deleting from.
+
+### getLastSequencerTimestamp
+
+```solidity
+function getLastSequencerTimestamp() external view returns (uint256 _lastSequencerTimestamp)
+```
+
+Retrieves the timestamp of the last batch submitted by the sequencer.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _lastSequencerTimestamp | uint256 | Last sequencer batch timestamp.
+
+### getTotalBatches
+
+```solidity
+function getTotalBatches() external view returns (uint256 _totalBatches)
+```
+
+Retrieves the total number of batches submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalBatches | uint256 | Total submitted batches.
+
+### getTotalElements
+
+```solidity
+function getTotalElements() external view returns (uint256 _totalElements)
+```
+
+Retrieves the total number of elements submitted.
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _totalElements | uint256 | Total submitted elements.
+
+### insideFraudProofWindow
+
+```solidity
+function insideFraudProofWindow(Lib_OVMCodec.ChainBatchHeader _batchHeader) external view returns (bool _inside)
+```
+
+Checks whether a given batch is still inside its fraud proof window.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch to check.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _inside | bool | Whether or not the batch is inside the fraud proof window.
+
+### libAddressManager
+
+```solidity
+function libAddressManager() external view returns (contract Lib_AddressManager)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | contract Lib_AddressManager | undefined
+
+### resolve
+
+```solidity
+function resolve(string _name) external view returns (address)
+```
+
+Resolves the address associated with a given name.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _name | string | Name to resolve an address for.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | Address associated with the given name.
+
+### verifyStateCommitment
+
+```solidity
+function verifyStateCommitment(bytes32 _element, Lib_OVMCodec.ChainBatchHeader _batchHeader, Lib_OVMCodec.ChainInclusionProof _proof) external view returns (bool)
+```
+
+Verifies a batch inclusion proof.
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _element | bytes32 | Hash of the element to verify a proof for.
+| _batchHeader | Lib_OVMCodec.ChainBatchHeader | Header of the batch in which the element was included.
+| _proof | Lib_OVMCodec.ChainInclusionProof | Merkle inclusion proof for the element.
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+## Events
+
+### StateBatchAppended
+
+```solidity
+event StateBatchAppended(uint256 indexed _batchIndex, bytes32 _batchRoot, uint256 _batchSize, uint256 _prevTotalElements, bytes _extraData)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+| _batchSize | uint256 | undefined |
+| _prevTotalElements | uint256 | undefined |
+| _extraData | bytes | undefined |
+
+### StateBatchDeleted
+
+```solidity
+event StateBatchDeleted(uint256 indexed _batchIndex, bytes32 _batchRoot)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _batchIndex `indexed` | uint256 | undefined |
+| _batchRoot | bytes32 | undefined |
+
+
+
diff --git a/packages/contracts/docs/WETH9.md b/packages/contracts/docs/WETH9.md
new file mode 100644
index 0000000000000..14939a49dfb74
--- /dev/null
+++ b/packages/contracts/docs/WETH9.md
@@ -0,0 +1,298 @@
+# WETH9
+
+
+
+
+
+
+
+
+
+## Methods
+
+### allowance
+
+```solidity
+function allowance(address, address) external view returns (uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+| _1 | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### approve
+
+```solidity
+function approve(address guy, uint256 wad) external nonpayable returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| guy | address | undefined
+| wad | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### balanceOf
+
+```solidity
+function balanceOf(address) external view returns (uint256)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | address | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### decimals
+
+```solidity
+function decimals() external view returns (uint8)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint8 | undefined
+
+### deposit
+
+```solidity
+function deposit() external payable
+```
+
+
+
+
+
+
+### name
+
+```solidity
+function name() external view returns (string)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### symbol
+
+```solidity
+function symbol() external view returns (string)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | string | undefined
+
+### totalSupply
+
+```solidity
+function totalSupply() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+### transfer
+
+```solidity
+function transfer(address dst, uint256 wad) external nonpayable returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| dst | address | undefined
+| wad | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### transferFrom
+
+```solidity
+function transferFrom(address src, address dst, uint256 wad) external nonpayable returns (bool)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| src | address | undefined
+| dst | address | undefined
+| wad | uint256 | undefined
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+### withdraw
+
+```solidity
+function withdraw(uint256 wad) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| wad | uint256 | undefined
+
+
+
+## Events
+
+### Approval
+
+```solidity
+event Approval(address indexed src, address indexed guy, uint256 wad)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| src `indexed` | address | undefined |
+| guy `indexed` | address | undefined |
+| wad | uint256 | undefined |
+
+### Deposit
+
+```solidity
+event Deposit(address indexed dst, uint256 wad)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| dst `indexed` | address | undefined |
+| wad | uint256 | undefined |
+
+### Transfer
+
+```solidity
+event Transfer(address indexed src, address indexed dst, uint256 wad)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| src `indexed` | address | undefined |
+| dst `indexed` | address | undefined |
+| wad | uint256 | undefined |
+
+### Withdrawal
+
+```solidity
+event Withdrawal(address indexed src, uint256 wad)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| src `indexed` | address | undefined |
+| wad | uint256 | undefined |
+
+
+
diff --git a/packages/contracts/docs/iL1ChugSplashDeployer.md b/packages/contracts/docs/iL1ChugSplashDeployer.md
new file mode 100644
index 0000000000000..151971ce9d826
--- /dev/null
+++ b/packages/contracts/docs/iL1ChugSplashDeployer.md
@@ -0,0 +1,32 @@
+# iL1ChugSplashDeployer
+
+
+
+> iL1ChugSplashDeployer
+
+
+
+
+
+## Methods
+
+### isUpgrading
+
+```solidity
+function isUpgrading() external view returns (bool)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | bool | undefined
+
+
+
+
diff --git a/packages/contracts/docs/iOVM_L1BlockNumber.md b/packages/contracts/docs/iOVM_L1BlockNumber.md
new file mode 100644
index 0000000000000..67a3c0e265858
--- /dev/null
+++ b/packages/contracts/docs/iOVM_L1BlockNumber.md
@@ -0,0 +1,32 @@
+# iOVM_L1BlockNumber
+
+
+
+> iOVM_L1BlockNumber
+
+
+
+
+
+## Methods
+
+### getL1BlockNumber
+
+```solidity
+function getL1BlockNumber() external view returns (uint256)
+```
+
+
+
+
+
+
+#### Returns
+
+| Name | Type | Description |
+|---|---|---|
+| _0 | uint256 | undefined
+
+
+
+
diff --git a/packages/contracts/docs/iOVM_L2ToL1MessagePasser.md b/packages/contracts/docs/iOVM_L2ToL1MessagePasser.md
new file mode 100644
index 0000000000000..373904aaaa7b0
--- /dev/null
+++ b/packages/contracts/docs/iOVM_L2ToL1MessagePasser.md
@@ -0,0 +1,52 @@
+# iOVM_L2ToL1MessagePasser
+
+
+
+> iOVM_L2ToL1MessagePasser
+
+
+
+
+
+## Methods
+
+### passMessageToL1
+
+```solidity
+function passMessageToL1(bytes _message) external nonpayable
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _message | bytes | undefined
+
+
+
+## Events
+
+### L2ToL1Message
+
+```solidity
+event L2ToL1Message(uint256 _nonce, address _sender, bytes _data)
+```
+
+
+
+
+
+#### Parameters
+
+| Name | Type | Description |
+|---|---|---|
+| _nonce | uint256 | undefined |
+| _sender | address | undefined |
+| _data | bytes | undefined |
+
+
+
diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts
index f5ec44d25b37e..d788060988a63 100644
--- a/packages/contracts/hardhat.config.ts
+++ b/packages/contracts/hardhat.config.ts
@@ -21,6 +21,8 @@ import './tasks/validate-chugsplash-dictator'
import './tasks/whitelist'
import './tasks/withdraw-fees'
import 'hardhat-gas-reporter'
+import '@primitivefi/hardhat-dodoc'
+import 'hardhat-output-validator'
// Load environment variables from .env
dotenv.config()
@@ -108,6 +110,34 @@ const config: HardhatUserConfig = {
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
+ dodoc: {
+ runOnCompile: true,
+ exclude: [
+ 'Helper_GasMeasurer',
+ 'Helper_SimpleProxy',
+ 'TestERC20',
+ 'TestLib_CrossDomainUtils',
+ 'TestLib_OVMCodec',
+ 'TestLib_RLPReader',
+ 'TestLib_RLPWriter',
+ 'TestLib_AddressAliasHelper',
+ 'TestLib_MerkleTrie',
+ 'TestLib_SecureMerkleTrie',
+ 'TestLib_Buffer',
+ 'TestLib_Bytes32Utils',
+ 'TestLib_BytesUtils',
+ 'TestLib_MerkleTree',
+ ],
+ },
+ outputValidator: {
+ runOnCompile: true,
+ errorMode: false,
+ checks: {
+ events: false,
+ variables: false,
+ },
+ exclude: ['contracts/test-helpers', 'contracts/test-libraries'],
+ },
}
if (
diff --git a/packages/contracts/package.json b/packages/contracts/package.json
index f2957a043a6ac..63e83ca86498b 100644
--- a/packages/contracts/package.json
+++ b/packages/contracts/package.json
@@ -1,7 +1,7 @@
{
"name": "@eth-optimism/contracts",
"version": "0.5.7",
- "description": "[Optimism] L1 and L2 smart contracts for Optimistic Ethereum",
+ "description": "[Optimism] L1 and L2 smart contracts for Optimism",
"main": "dist/index",
"types": "dist/index",
"files": [
@@ -25,7 +25,7 @@
"autogen:artifacts": "ts-node scripts/generate-artifacts.ts && ts-node scripts/generate-deployed-artifacts.ts",
"test": "yarn test:contracts",
"test:contracts": "hardhat test --show-stack-traces",
- "test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && istanbul check-coverage --statements 88 --branches 76 --functions 84 --lines 88",
+ "test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && istanbul check-coverage --statements 90 --branches 84 --functions 88 --lines 90",
"test:slither": "slither .",
"pretest:slither": "rm -f @openzeppelin && rm -f @ens && rm -f hardhat && ln -s ../../node_modules/@openzeppelin @openzeppelin && ln -s ../../node_modules/@ens @ens && ln -s ../../node_modules/hardhat hardhat",
"posttest:slither": "rm -f @openzeppelin && rm -f @ens && rm -f hardhat",
@@ -37,12 +37,12 @@
"lint:fix": "yarn lint:contracts:fix && yarn lint:ts:fix",
"lint": "yarn lint:fix && yarn lint:check",
"clean": "rm -rf ./dist ./artifacts ./cache ./coverage ./tsconfig.build.tsbuildinfo",
- "deploy": "ts-node bin/deploy.ts && yarn autogen:markdown",
"prepublishOnly": "yarn copyfiles -u 1 -e \"**/test-*/**/*\" \"contracts/**/*\" ./",
"postpublish": "rimraf chugsplash L1 L2 libraries standards",
"prepack": "yarn prepublishOnly",
"postpack": "yarn postpublish",
- "pre-commit": "lint-staged"
+ "pre-commit": "lint-staged",
+ "validateDocs": "hardhat validateOutput"
},
"keywords": [
"optimism",
@@ -73,6 +73,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts": "4.3.2",
"@openzeppelin/contracts-upgradeable": "4.3.2",
+ "@primitivefi/hardhat-dodoc": "^0.1.3",
"@typechain/ethers-v5": "^8.0.2",
"@typechain/hardhat": "^3.0.0",
"@types/chai": "^4.2.18",
@@ -88,7 +89,6 @@
"dotenv": "^10.0.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
@@ -101,6 +101,7 @@
"hardhat": "^2.3.0",
"hardhat-deploy": "^0.9.3",
"hardhat-gas-reporter": "^1.0.4",
+ "hardhat-output-validator": "^0.1.18",
"istanbul": "^0.4.5",
"lint-staged": "11.0.0",
"lodash": "^4.17.21",
diff --git a/packages/contracts/scripts/generate-artifacts.ts b/packages/contracts/scripts/generate-artifacts.ts
index 11a2eac3b5044..a68c0ec301b22 100644
--- a/packages/contracts/scripts/generate-artifacts.ts
+++ b/packages/contracts/scripts/generate-artifacts.ts
@@ -30,7 +30,11 @@ const main = async () => {
.map((artifactPath) => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const artifact = require(artifactPath)
- const relPath = path.relative(__dirname, 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 {
diff --git a/packages/contracts/scripts/generate-deployed-artifacts.ts b/packages/contracts/scripts/generate-deployed-artifacts.ts
index 91a4dc2ec0f17..ea128b74e2a75 100644
--- a/packages/contracts/scripts/generate-deployed-artifacts.ts
+++ b/packages/contracts/scripts/generate-deployed-artifacts.ts
@@ -27,6 +27,8 @@ const main = async () => {
})
const artifactNames = []
+ const pattern = /\\/g
+
for (const deploymentName of deploymentNames) {
const deploymentArtifacts = glob.sync(
path.join(
@@ -37,7 +39,9 @@ const main = async () => {
)
for (const artifactPath of deploymentArtifacts) {
- const relPath = path.relative(__dirname, artifactPath)
+ const relPath = path
+ .relative(__dirname, artifactPath)
+ .replace(pattern, '/')
const contractName = path.basename(artifactPath, '.json')
const artifactName = `${deploymentName}__${contractName}`.replace(
/-/g,
diff --git a/packages/contracts/scripts/generate-markdown.ts b/packages/contracts/scripts/generate-markdown.ts
index 75ff32328331d..67950b057f4ee 100644
--- a/packages/contracts/scripts/generate-markdown.ts
+++ b/packages/contracts/scripts/generate-markdown.ts
@@ -16,7 +16,7 @@ interface DeploymentInfo {
const PUBLIC_DEPLOYMENTS: DeploymentInfo[] = [
{
folder: 'mainnet',
- name: 'Optimistic Ethereum (mainnet)',
+ name: 'Optimism (mainnet)',
chainid: 10,
rpc: 'https://mainnet.optimism.io',
l1Explorer: 'https://etherscan.io',
@@ -24,7 +24,7 @@ const PUBLIC_DEPLOYMENTS: DeploymentInfo[] = [
},
{
folder: 'kovan',
- name: 'Optimistic Kovan (public testnet)',
+ name: 'Optimism Kovan (public testnet)',
chainid: 69,
rpc: 'https://kovan.optimism.io',
l1Explorer: 'https://kovan.etherscan.io',
@@ -32,9 +32,9 @@ const PUBLIC_DEPLOYMENTS: DeploymentInfo[] = [
},
{
folder: 'goerli',
- name: 'Optimistic Goerli (internal devnet)',
+ name: 'Optimism Goerli (internal devnet)',
chainid: 420,
- notice: `Optimistic Goerli is an internal Optimism development network. You're probably looking for [Optimistic Kovan](../kovan#readme), the public Optimistic Ethereum testnet.`,
+ notice: `Optimism Goerli is an internal Optimism development network. You're probably looking for [Optimism Kovan](../kovan#readme), the public Optimism testnet.`,
l1Explorer: 'https://goerli.etherscan.io',
},
]
@@ -221,7 +221,7 @@ const main = async () => {
}
let primary = ``
- primary = addline(primary, `# Optimistic Ethereum Deployments`)
+ primary = addline(primary, `# Optimism Deployments`)
for (const deployment of PUBLIC_DEPLOYMENTS) {
primary = addline(
primary,
diff --git a/packages/contracts/src/connect-contracts.ts b/packages/contracts/src/connect-contracts.ts
deleted file mode 100644
index a44561e395cd5..0000000000000
--- a/packages/contracts/src/connect-contracts.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { Signer, Contract } from 'ethers'
-import { Provider } from '@ethersproject/abstract-provider'
-import { getContractArtifact } from './contract-artifacts'
-import { getDeployedContractArtifact } from './contract-deployed-artifacts'
-import { predeploys } from './predeploys'
-
-export type Network = 'goerli' | 'kovan' | 'mainnet'
-interface L1Contracts {
- addressManager: Contract
- canonicalTransactionChain: Contract
- stateCommitmentChain: Contract
- xDomainMessengerProxy: Contract
- bondManager: Contract
-}
-
-interface L2Contracts {
- eth: Contract
- xDomainMessenger: Contract
- messagePasser: Contract
- deployerWhiteList: Contract
-}
-
-/**
- * Validates user provided a singer or provider & throws error if not
- *
- * @param signerOrProvider
- */
-const checkSignerType = (signerOrProvider: Signer | Provider) => {
- if (!signerOrProvider) {
- throw Error('signerOrProvider argument is undefined')
- }
- if (
- !Provider.isProvider(signerOrProvider) &&
- !Signer.isSigner(signerOrProvider)
- ) {
- throw Error('signerOrProvider argument is the wrong type')
- }
-}
-
-/**
- * Connects a signer/provider to layer 1 contracts on a given network
- *
- * @param signerOrProvider ethers signer or provider
- * @param network string denoting network
- * @returns l1 contracts connected to signer/provider
- */
-export const connectL1Contracts = async (
- signerOrProvider: Signer | Provider,
- network: Network
-): Promise => {
- checkSignerType(signerOrProvider)
-
- if (!['mainnet', 'kovan', 'goerli'].includes(network)) {
- throw Error('Must specify network: mainnet, kovan, or goerli.')
- }
-
- const getEthersContract = (name: string) => {
- const artifact = getDeployedContractArtifact(name, network)
- return new Contract(artifact.address, artifact.abi, signerOrProvider)
- }
-
- return {
- addressManager: getEthersContract('Lib_AddressManager'),
- canonicalTransactionChain: getEthersContract('CanonicalTransactionChain'),
- stateCommitmentChain: getEthersContract('StateCommitmentChain'),
- xDomainMessengerProxy: getEthersContract('Proxy__L1CrossDomainMessenger'),
- bondManager: getEthersContract('mockBondManager'),
- }
-}
-
-/**
- * Connects a signer/provider to layer 2 contracts (network agnostic)
- *
- * @param signerOrProvider ethers signer or provider
- * @returns l2 contracts connected to signer/provider
- */
-export const connectL2Contracts = async (
- signerOrProvider: any
-): Promise => {
- checkSignerType(signerOrProvider)
-
- const getEthersContract = (name: string, iface?: string) => {
- const artifact = getContractArtifact(iface || name)
- const address = predeploys[name]
- return new Contract(address, artifact.abi, signerOrProvider)
- }
-
- return {
- eth: getEthersContract('OVM_ETH'),
- xDomainMessenger: getEthersContract('L2CrossDomainMessenger'),
- messagePasser: getEthersContract('OVM_L2ToL1MessagePasser'),
- deployerWhiteList: getEthersContract('OVM_DeployerWhitelist'),
- }
-}
diff --git a/packages/contracts/src/contract-data.ts b/packages/contracts/src/contract-data.ts
deleted file mode 100644
index 83c6221c14167..0000000000000
--- a/packages/contracts/src/contract-data.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-/* eslint-disable @typescript-eslint/no-var-requires */
-import { predeploys as l2Addresses } from './predeploys'
-import { Network } from './connect-contracts'
-
-/**
- * This file is necessarily not DRY because it needs to be usable
- * in a browser context and can't take advantage of dynamic imports
- * (ie: the json needs to all be imported when transpiled)
- */
-
-const Mainnet__Lib_AddressManager = require('../deployments/mainnet/Lib_AddressManager.json')
-const Mainnet__CanonicalTransactionChain = require('../deployments/mainnet/CanonicalTransactionChain.json')
-const Mainnet__L1CrossDomainMessenger = require('../deployments/mainnet/L1CrossDomainMessenger.json')
-const Mainnet__StateCommitmentChain = require('../deployments/mainnet/StateCommitmentChain.json')
-const Mainnet__Proxy__L1CrossDomainMessenger = require('../deployments/mainnet/Proxy__L1CrossDomainMessenger.json')
-const Mainnet__BondManager = require('../deployments/mainnet/mockBondManager.json')
-
-const Kovan__Lib_AddressManager = require('../deployments/kovan/Lib_AddressManager.json')
-const Kovan__CanonicalTransactionChain = require('../deployments/kovan/CanonicalTransactionChain.json')
-const Kovan__L1CrossDomainMessenger = require('../deployments/kovan/L1CrossDomainMessenger.json')
-const Kovan__StateCommitmentChain = require('../deployments/kovan/StateCommitmentChain.json')
-const Kovan__Proxy__L1CrossDomainMessenger = require('../deployments/kovan/Proxy__L1CrossDomainMessenger.json')
-const Kovan__BondManager = require('../deployments/kovan/mockBondManager.json')
-
-const Goerli__Lib_AddressManager = require('../deployments/goerli/Lib_AddressManager.json')
-const Goerli__CanonicalTransactionChain = require('../deployments/goerli/CanonicalTransactionChain.json')
-const Goerli__L1CrossDomainMessenger = require('../deployments/goerli/L1CrossDomainMessenger.json')
-const Goerli__StateCommitmentChain = require('../deployments/goerli/StateCommitmentChain.json')
-const Goerli__Proxy__L1CrossDomainMessenger = require('../deployments/goerli/Proxy__L1CrossDomainMessenger.json')
-const Goerli__BondManager = require('../deployments/goerli/mockBondManager.json')
-
-export const getL1ContractData = (network: Network) => {
- return {
- Lib_AddressManager: {
- mainnet: Mainnet__Lib_AddressManager,
- kovan: Kovan__Lib_AddressManager,
- goerli: Goerli__Lib_AddressManager,
- }[network],
- CanonicalTransactionChain: {
- mainnet: Mainnet__CanonicalTransactionChain,
- kovan: Kovan__CanonicalTransactionChain,
- goerli: Goerli__CanonicalTransactionChain,
- }[network],
- L1CrossDomainMessenger: {
- mainnet: Mainnet__L1CrossDomainMessenger,
- kovan: Kovan__L1CrossDomainMessenger,
- goerli: Goerli__L1CrossDomainMessenger,
- }[network],
- StateCommitmentChain: {
- mainnet: Mainnet__StateCommitmentChain,
- kovan: Kovan__StateCommitmentChain,
- goerli: Goerli__StateCommitmentChain,
- }[network],
- Proxy__L1CrossDomainMessenger: {
- mainnet: Mainnet__Proxy__L1CrossDomainMessenger,
- kovan: Kovan__Proxy__L1CrossDomainMessenger,
- goerli: Goerli__Proxy__L1CrossDomainMessenger,
- }[network],
- BondManager: {
- mainnet: Mainnet__BondManager,
- kovan: Kovan__BondManager,
- goerli: Goerli__BondManager,
- }[network],
- }
-}
-
-const OVM_ETH = require('../artifacts/contracts/L2/predeploys/OVM_ETH.sol/OVM_ETH.json')
-const L2CrossDomainMessenger = require('../artifacts/contracts/L2/messaging/L2CrossDomainMessenger.sol/L2CrossDomainMessenger.json')
-const OVM_L2ToL1MessagePasser = require('../artifacts/contracts/L2/predeploys/OVM_L2ToL1MessagePasser.sol/OVM_L2ToL1MessagePasser.json')
-const OVM_DeployerWhitelist = require('../artifacts/contracts/L2/predeploys/OVM_DeployerWhitelist.sol/OVM_DeployerWhitelist.json')
-
-export const getL2ContractData = () => {
- return {
- OVM_ETH: {
- abi: OVM_ETH.abi,
- address: l2Addresses.OVM_ETH,
- },
- L2CrossDomainMessenger: {
- abi: L2CrossDomainMessenger.abi,
- address: l2Addresses.L2CrossDomainMessenger,
- },
- OVM_L2ToL1MessagePasser: {
- abi: OVM_L2ToL1MessagePasser.abi,
- address: l2Addresses.OVM_L2ToL1MessagePasser,
- },
- OVM_DeployerWhitelist: {
- abi: OVM_DeployerWhitelist.abi,
- address: l2Addresses.OVM_DeployerWhitelist,
- },
- }
-}
diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts
index 9d3016849736a..a36cee6231a00 100644
--- a/packages/contracts/src/index.ts
+++ b/packages/contracts/src/index.ts
@@ -1,3 +1,2 @@
export * from './contract-defs'
export * from './predeploys'
-export * from './connect-contracts'
diff --git a/packages/contracts/tasks/l2-gasprice.ts b/packages/contracts/tasks/l2-gasprice.ts
index 656d34ff97cad..9edb01acbf717 100644
--- a/packages/contracts/tasks/l2-gasprice.ts
+++ b/packages/contracts/tasks/l2-gasprice.ts
@@ -7,8 +7,25 @@ import { predeploys } from '../src/predeploys'
import { getContractDefinition } from '../src/contract-defs'
task('set-l2-gasprice')
- .addOptionalParam('l2GasPrice', 'Gas Price to set on L2', 0, types.int)
+ .addOptionalParam(
+ 'l2GasPrice',
+ 'Gas Price to set on L2',
+ undefined,
+ types.int
+ )
.addOptionalParam('transactionGasPrice', 'tx.gasPrice', undefined, types.int)
+ .addOptionalParam(
+ 'overhead',
+ 'amortized additional gas used by each batch that users must pay for',
+ undefined,
+ types.int
+ )
+ .addOptionalParam(
+ 'scalar',
+ 'amount to scale up the gas to charge',
+ undefined,
+ types.int
+ )
.addOptionalParam(
'contractsRpcUrl',
'Sequencer HTTP Endpoint',
@@ -42,15 +59,45 @@ task('set-l2-gasprice')
throw new Error(`Incorrect key. Owner ${owner}, Signer ${addr}`)
}
+ // List the current values
const gasPrice = await GasPriceOracle.callStatic.gasPrice()
- console.log(`Gas Price is currently ${gasPrice.toString()}`)
- console.log(`Setting Gas Price to ${args.l2GasPrice}`)
+ const scalar = await GasPriceOracle.callStatic.scalar()
+ const overhead = await GasPriceOracle.callStatic.overhead()
- const tx = await GasPriceOracle.connect(signer).setGasPrice(
- args.l2GasPrice,
- { gasPrice: args.transactionGasPrice }
- )
+ console.log('Current values:')
+ console.log(`Gas Price: ${gasPrice.toString()}`)
+ console.log(`Scalar: ${scalar.toString()}`)
+ console.log(`Overhead: ${overhead.toString()}`)
+
+ if (args.l2GasPrice !== undefined) {
+ console.log(`Setting gas price to ${args.l2GasPrice}`)
+ const tx = await GasPriceOracle.connect(signer).setGasPrice(
+ args.l2GasPrice,
+ { gasPrice: args.transactionGasPrice }
+ )
- const receipt = await tx.wait()
- console.log(`Success - ${receipt.transactionHash}`)
+ const receipt = await tx.wait()
+ console.log(`Success - ${receipt.transactionHash}`)
+ }
+
+ if (args.scalar !== undefined) {
+ console.log(`Setting scalar to ${args.scalar}`)
+ const tx = await GasPriceOracle.connect(signer).setScalar(args.scalar, {
+ gasPrice: args.transactionGasPrice,
+ })
+
+ const receipt = await tx.wait()
+ console.log(`Success - ${receipt.transactionHash}`)
+ }
+
+ if (args.overhead !== undefined) {
+ console.log(`Setting overhead to ${args.overhead}`)
+ const tx = await GasPriceOracle.connect(signer).setOverhead(
+ args.overhead,
+ { gasPrice: args.transactionGasPrice }
+ )
+
+ const receipt = await tx.wait()
+ console.log(`Success - ${receipt.transactionHash}`)
+ }
})
diff --git a/packages/contracts/test/connect-contracts.spec.ts b/packages/contracts/test/connect-contracts.spec.ts
deleted file mode 100644
index d736b293dcf87..0000000000000
--- a/packages/contracts/test/connect-contracts.spec.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { ethers } from 'hardhat'
-import { Signer, Contract } from 'ethers'
-import {
- connectL1Contracts,
- connectL2Contracts,
-} from '../dist/connect-contracts'
-import { expect } from './setup'
-
-// Skipping these tests as the FE work that relies on this logic was never finished.
-// Dedicated issue created in https://linear.app/optimism/issue/ENG-1451/decide-what-to-do-with-the-connectl1contracts-tests
-describe.skip('connectL1Contracts', () => {
- let user: Signer
- const l1ContractNames = [
- 'addressManager',
- 'canonicalTransactionChain',
- 'stateCommitmentChain',
- 'xDomainMessengerProxy',
- 'bondManager',
- ]
-
- const l2ContractNames = [
- 'eth',
- 'xDomainMessenger',
- 'messagePasser',
- 'messageSender',
- 'deployerWhiteList',
- ]
-
- before(async () => {
- ;[user] = await ethers.getSigners()
- })
-
- it(`connectL1Contracts should throw error if signer or provider isn't provided.`, async () => {
- try {
- await connectL1Contracts(undefined, 'mainnet')
- } catch (err) {
- expect(err.message).to.be.equal('signerOrProvider argument is undefined')
- }
- })
-
- for (const name of l1ContractNames) {
- it(`connectL1Contracts should return a contract assigned to a field named "${name}"`, async () => {
- const l1Contracts = await connectL1Contracts(user, 'mainnet')
- expect(l1Contracts[name]).to.be.an.instanceOf(Contract)
- })
- }
-
- for (const name of l2ContractNames) {
- it(`connectL2Contracts should return a contract assigned to a field named "${name}"`, async () => {
- const l2Contracts = await connectL2Contracts(user)
- expect(l2Contracts[name]).to.be.an.instanceOf(Contract)
- })
- }
-})
diff --git a/packages/contracts/test/contracts/L1/messaging/L1StandardBridge.spec.ts b/packages/contracts/test/contracts/L1/messaging/L1StandardBridge.spec.ts
index 7871081963311..988f71f4191b0 100644
--- a/packages/contracts/test/contracts/L1/messaging/L1StandardBridge.spec.ts
+++ b/packages/contracts/test/contracts/L1/messaging/L1StandardBridge.spec.ts
@@ -244,6 +244,30 @@ describe('L1StandardBridge', () => {
).to.be.revertedWith(ERR_INVALID_X_DOMAIN_MSG_SENDER)
})
+ it('should revert in nothing to withdraw', async () => {
+ // make sure no balance at start of test
+ expect(await ethers.provider.getBalance(NON_ZERO_ADDRESS)).to.be.equal(0)
+
+ const withdrawalAmount = 100
+ Mock__L1CrossDomainMessenger.smocked.xDomainMessageSender.will.return.with(
+ () => DUMMY_L2_BRIDGE_ADDRESS
+ )
+
+ await expect(
+ L1StandardBridge.finalizeETHWithdrawal(
+ NON_ZERO_ADDRESS,
+ NON_ZERO_ADDRESS,
+ withdrawalAmount,
+ NON_NULL_BYTES32,
+ {
+ from: Mock__L1CrossDomainMessenger.address,
+ }
+ )
+ ).to.be.revertedWith(
+ 'TransferHelper::safeTransferETH: ETH transfer failed'
+ )
+ })
+
it('should credit funds to the withdrawer and not use too much gas', async () => {
// make sure no balance at start of test
expect(await ethers.provider.getBalance(NON_ZERO_ADDRESS)).to.be.equal(0)
diff --git a/packages/contracts/test/contracts/L1/rollup/CanonicalTransactionChain.spec.ts b/packages/contracts/test/contracts/L1/rollup/CanonicalTransactionChain.spec.ts
index 977228b85ec63..bb4aa9103f433 100644
--- a/packages/contracts/test/contracts/L1/rollup/CanonicalTransactionChain.spec.ts
+++ b/packages/contracts/test/contracts/L1/rollup/CanonicalTransactionChain.spec.ts
@@ -679,6 +679,16 @@ describe('CanonicalTransactionChain', () => {
}
)
await res.wait()
+
+ expect(await CanonicalTransactionChain.getLastTimestamp()).to.equal(
+ timestamp
+ )
+ expect(await CanonicalTransactionChain.getLastBlockNumber()).to.equal(
+ blockNumber
+ )
+ expect(
+ await CanonicalTransactionChain.getNumPendingQueueElements()
+ ).to.equal(0)
})
it(`should return ${size}`, async () => {
@@ -686,7 +696,17 @@ describe('CanonicalTransactionChain', () => {
size
)
})
+
+ it('should return zero after queue is emptied', async () => {
+ expect(await CanonicalTransactionChain.getNextQueueIndex()).to.equal(
+ 0
+ )
+ })
})
}
+
+ it('should return zero', async () => {
+ expect(await CanonicalTransactionChain.getTotalBatches()).to.equal(0)
+ })
})
})
diff --git a/packages/contracts/test/contracts/L1/rollup/StateCommitmentChain.spec.ts b/packages/contracts/test/contracts/L1/rollup/StateCommitmentChain.spec.ts
index 5902227bbeb56..bbb21bdae6c72 100644
--- a/packages/contracts/test/contracts/L1/rollup/StateCommitmentChain.spec.ts
+++ b/packages/contracts/test/contracts/L1/rollup/StateCommitmentChain.spec.ts
@@ -181,6 +181,19 @@ describe('StateCommitmentChain', () => {
})
})
})
+ describe('when the proposer has not previously staked at the BondManager', () => {
+ before(() => {
+ Mock__BondManager.smocked.isCollateralized.will.return.with(false)
+ })
+
+ it('should revert', async () => {
+ await expect(
+ StateCommitmentChain.appendStateBatch(batch, 0)
+ ).to.be.revertedWith(
+ 'Proposer does not have enough collateral posted'
+ )
+ })
+ })
})
})
@@ -194,6 +207,10 @@ describe('StateCommitmentChain', () => {
extraData: ethers.constants.HashZero,
}
+ before(() => {
+ Mock__BondManager.smocked.isCollateralized.will.return.with(true)
+ })
+
beforeEach(async () => {
Mock__CanonicalTransactionChain.smocked.getTotalElements.will.return.with(
batch.length
@@ -253,6 +270,25 @@ describe('StateCommitmentChain', () => {
})
})
+ describe('when outside fraud proof window', () => {
+ beforeEach(async () => {
+ const FRAUD_PROOF_WINDOW =
+ await StateCommitmentChain.FRAUD_PROOF_WINDOW()
+ await increaseEthTime(
+ ethers.provider,
+ FRAUD_PROOF_WINDOW.toNumber() + 1
+ )
+ })
+
+ it('should revert', async () => {
+ await expect(
+ StateCommitmentChain.deleteStateBatch(batchHeader)
+ ).to.be.revertedWith(
+ 'State batches can only be deleted within the fraud proof window.'
+ )
+ })
+ })
+
describe('when the provided batch header is valid', () => {
it('should remove the batch and all following batches', async () => {
await expect(StateCommitmentChain.deleteStateBatch(batchHeader)).to
@@ -263,6 +299,27 @@ describe('StateCommitmentChain', () => {
})
})
+ describe('insideFraudProofWindow', () => {
+ const batchHeader = {
+ batchIndex: 0,
+ batchRoot: NON_NULL_BYTES32,
+ batchSize: 1,
+ prevTotalElements: 0,
+ extraData: ethers.constants.HashZero,
+ }
+ it('should revert when timestamp is zero', async () => {
+ await expect(
+ StateCommitmentChain.insideFraudProofWindow({
+ ...batchHeader,
+ extraData: ethers.utils.defaultAbiCoder.encode(
+ ['uint256', 'address'],
+ [0, await sequencer.getAddress()]
+ ),
+ })
+ ).to.be.revertedWith('Batch header timestamp cannot be zero')
+ })
+ })
+
describe('getTotalElements', () => {
describe('when no batch elements have been inserted', () => {
it('should return zero', async () => {
diff --git a/packages/contracts/test/data/json/libraries/rlp/Lib_RLPReader.test.json b/packages/contracts/test/data/json/libraries/rlp/Lib_RLPReader.test.json
index 87a99992b45c8..4ff6ab064d3f0 100644
--- a/packages/contracts/test/data/json/libraries/rlp/Lib_RLPReader.test.json
+++ b/packages/contracts/test/data/json/libraries/rlp/Lib_RLPReader.test.json
@@ -22,6 +22,12 @@
"0x02"
],
"revert": true
+ },
+ "input value > 1 length (should revert)": {
+ "in": [
+ "0x0101"
+ ],
+ "revert": "Invalid RLP boolean value."
}
},
"readAddress": {
@@ -32,6 +38,26 @@
"out": [
"0x1212121212121212121212121212121212121212"
]
+ },
+ "address length = 1": {
+ "in": [
+ "0x12"
+ ],
+ "out": [
+ "0x0000000000000000000000000000000000000000"
+ ]
+ },
+ "invalid address length > 21 (should revert)": {
+ "in": [
+ "0x94121212121212121212121212121212121212121212121212"
+ ],
+ "revert": "Invalid RLP address length."
+ },
+ "invalid address length < 21 (should revert)": {
+ "in": [
+ "0x94121212121212121212121212"
+ ],
+ "revert": "Invalid RLP address length."
}
},
"readBytes": {
@@ -58,6 +84,38 @@
"out": [
"0x7f"
]
+ },
+ "should revert if input is a list item": {
+ "in": [
+ "0xc7c0c1c0c3c0c1c0"
+ ],
+ "revert": "Invalid RLP bytes value."
+ },
+ "invalid bytes value, 0xb7 < prefix < 0xbf (should revert)": {
+ "in": [
+ "0xb9"
+ ],
+ "revert": "Invalid RLP long string length."
+ },
+ "invalid bytes value, prefix > 0xf7 (should revert)": {
+ "in": [
+ "0xff"
+ ],
+ "revert": "Invalid RLP long list length."
+ }
+ },
+ "readBytes32": {
+ "should revert if input is a list item": {
+ "in": [
+ "0xc7c0c1c0c3c0c1c0"
+ ],
+ "revert": "Invalid RLP bytes32 value."
+ },
+ "invalid length > 33 (should revert)": {
+ "in": [
+ "0x11110000000000000000000000000000000000000000000000000000000000000000"
+ ],
+ "revert": "Invalid RLP bytes32 length."
}
},
"readString": {
@@ -310,6 +368,56 @@
"0xca846b6579348476616c34"
]
]
+ },
+ "invalid rlp: not enough bytes for string length": {
+ "in": ["0xefdebd"],
+ "revert":"Invalid RLP short list."
+ },
+ "invalid rlp: expected string length to be greater than 55": {
+ "in": ["0xefb83600"],
+ "revert":"Invalid RLP short list."
+ },
+ "invalid rlp: not enough bytes for string": {
+ "in": ["0xefdebdaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
+ "revert":"Invalid RLP long list length."
+ },
+ "int32Overflow": {
+ "in": ["0xbf0f000000000000021111"],
+ "revert": "Invalid RLP long string."
+ },
+ "int32Overflow2": {
+ "in": ["0xff0f000000000000021111"],
+ "revert": "Invalid RLP long list."
+ },
+ "incorrectLengthInArray": {
+ "in": ["0xb9002100dc2b275d0f74e8a53e6f4ec61b27f24278820be3f82ea2110e582081b0565df0"],
+ "revert": "Invalid RLP list value."
+ },
+ "leadingZerosInLongLengthArray1": {
+ "in": ["0xb90040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"],
+ "revert": "Invalid RLP list value."
+ },
+ "leadingZerosInLongLengthArray2": {
+ "in": ["0xb800"],
+ "revert": "Invalid RLP list value."
+ },
+ "leadingZerosInLongLengthList1": {
+ "in": ["0xfb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"],
+ "revert": "Provided RLP list exceeds max list length."
+ },
+ "nonOptimalLongLengthArray1": {
+ "in": ["0xb81000112233445566778899aabbccddeeff"],
+ "revert": "Invalid RLP list value."
+ },
+ "nonOptimalLongLengthArray2": {
+ "in": ["0xb801ff"],
+ "revert": "Invalid RLP list value."
+ },
+ "invalid list value, 0x7f < prefix < 0xb7 (should revert)": {
+ "in": [
+ "0x91"
+ ],
+ "revert": "Invalid RLP short string."
}
}
}
diff --git a/packages/contracts/test/data/json/libraries/utils/Lib_BytesUtils.test.json b/packages/contracts/test/data/json/libraries/utils/Lib_BytesUtils.test.json
index a816094488083..409a34472f386 100644
--- a/packages/contracts/test/data/json/libraries/utils/Lib_BytesUtils.test.json
+++ b/packages/contracts/test/data/json/libraries/utils/Lib_BytesUtils.test.json
@@ -36,6 +36,14 @@
"start > input.length": {
"in": ["0x12345678", 5, 1],
"revert":"Read out of bounds"
+ },
+ "input.length + 31 >= input.length": {
+ "in": ["0x12345678", 5, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"],
+ "revert":"slice_overflow"
+ },
+ "input.start + input.length >= input.start": {
+ "in": ["0x12345678", "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 1],
+ "revert":"slice_overflow"
}
},
"toBytes32": {
diff --git a/packages/core-utils/package.json b/packages/core-utils/package.json
index 8fbc0d028f1da..2914b48560ede 100644
--- a/packages/core-utils/package.json
+++ b/packages/core-utils/package.json
@@ -49,7 +49,6 @@
"babel-eslint": "^10.1.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/data-transport-layer/README.md b/packages/data-transport-layer/README.md
index 89e22365c67b8..a18b32669122b 100644
--- a/packages/data-transport-layer/README.md
+++ b/packages/data-transport-layer/README.md
@@ -3,7 +3,7 @@
## What is this?
-The Optimistic Ethereum Data Transport Layer is a long-running software service (written in TypeScript) designed to reliably index Optimistic Ethereum transaction data from Layer 1 (Ethereum). Specifically, this service indexes:
+The Optimism Data Transport Layer is a long-running software service (written in TypeScript) designed to reliably index Optimism transaction data from Layer 1 (Ethereum). Specifically, this service indexes:
* Transactions that have been enqueued for submission to the CanonicalTransactionChain via [`CanonicalTransactionChain.enqueue`].
* Transactions that have been included in the CanonicalTransactionChain via [`CanonicalTransactionChain.appendQueueBatch`] or [`CanonicalTransactionChain.appendSequencerBatch`].
diff --git a/packages/data-transport-layer/package.json b/packages/data-transport-layer/package.json
index 3e0cf73cf3a44..b09e6a4b88ded 100644
--- a/packages/data-transport-layer/package.json
+++ b/packages/data-transport-layer/package.json
@@ -74,7 +74,6 @@
"chai-as-promised": "^7.1.1",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/data-transport-layer/src/services/l1-ingestion/service.ts b/packages/data-transport-layer/src/services/l1-ingestion/service.ts
index fd5905fac4759..c9efed02f0bca 100644
--- a/packages/data-transport-layer/src/services/l1-ingestion/service.ts
+++ b/packages/data-transport-layer/src/services/l1-ingestion/service.ts
@@ -167,7 +167,7 @@ export class L1IngestionService extends BaseService {
startingL1BlockNumber = this.options.l1StartHeight
} else {
this.logger.info(
- 'Attempting to find an appropriate L1 block height to begin sync...'
+ 'Attempting to find an appropriate L1 block height to begin sync. This may take a long time.'
)
startingL1BlockNumber = await this._findStartingL1BlockNumber()
}
@@ -453,12 +453,18 @@ export class L1IngestionService extends BaseService {
private async _findStartingL1BlockNumber(): Promise {
const currentL1Block = await this.state.l1RpcProvider.getBlockNumber()
+ const filter =
+ this.state.contracts.Lib_AddressManager.filters.OwnershipTransferred()
+
+ for (let i = 0; i < currentL1Block; i += 2000) {
+ const start = i
+ const end = Math.min(i + 2000, currentL1Block)
+ this.logger.info(`Searching for ${filter} from ${start} to ${end}`)
- for (let i = 0; i < currentL1Block; i += 1000000) {
const events = await this.state.contracts.Lib_AddressManager.queryFilter(
- this.state.contracts.Lib_AddressManager.filters.OwnershipTransferred(),
- i,
- Math.min(i + 1000000, currentL1Block)
+ filter,
+ start,
+ end
)
if (events.length > 0) {
diff --git a/packages/data-transport-layer/src/services/l2-ingestion/service.ts b/packages/data-transport-layer/src/services/l2-ingestion/service.ts
index 3652d963534fe..94dba657498f6 100644
--- a/packages/data-transport-layer/src/services/l2-ingestion/service.ts
+++ b/packages/data-transport-layer/src/services/l2-ingestion/service.ts
@@ -121,7 +121,7 @@ export class L2IngestionService extends BaseService {
}
this.logger.info(
- 'Synchronizing unconfirmed transactions from Layer 2 (Optimistic Ethereum)',
+ 'Synchronizing unconfirmed transactions from Layer 2 (Optimism)',
{
fromBlock: highestSyncedL2BlockNumber,
toBlock: targetL2Block,
diff --git a/packages/message-relayer/package.json b/packages/message-relayer/package.json
index b5cbde33ed17e..8a306fd90085d 100644
--- a/packages/message-relayer/package.json
+++ b/packages/message-relayer/package.json
@@ -59,7 +59,6 @@
"chai-as-promised": "^7.1.1",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/regenesis-surgery/test/eoa.spec.ts b/packages/regenesis-surgery/test/eoa.spec.ts
index 94467ee52a0aa..f9dbfd44d1552 100644
--- a/packages/regenesis-surgery/test/eoa.spec.ts
+++ b/packages/regenesis-surgery/test/eoa.spec.ts
@@ -68,7 +68,7 @@ describe('EOAs', () => {
// eslint-disable-next-line
before(function() {
if (env.surgeryDataSources.configs.l2NetworkName === 'kovan') {
- console.log('1inch deployer does not exist on optimistic kovan')
+ console.log('1inch deployer does not exist on Optimism Kovan')
this.skip()
}
diff --git a/packages/sdk/README.md b/packages/sdk/README.md
index 02aac2bd98b16..e69362b720c19 100644
--- a/packages/sdk/README.md
+++ b/packages/sdk/README.md
@@ -2,7 +2,7 @@
# @eth-optimism/sdk
-The `@eth-optimism/sdk` package provides a set of tools for interacting with Optimistic Ethereum.
+The `@eth-optimism/sdk` package provides a set of tools for interacting with Optimism.
## NOTICE
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index 5e1d91c0b412f..9d9128eeae95d 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -1,7 +1,7 @@
{
"name": "@eth-optimism/sdk",
"version": "0.0.3",
- "description": "[Optimism] Tools for working with Optimistic Ethereum",
+ "description": "[Optimism] Tools for working with Optimism",
"main": "dist/index",
"types": "dist/index",
"files": [
@@ -43,7 +43,6 @@
"chai-as-promised": "^7.1.1",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
- "eslint-plugin-ban": "^1.5.2",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-jsdoc": "^35.1.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
diff --git a/packages/sdk/src/cross-chain-provider.ts b/packages/sdk/src/cross-chain-provider.ts
index 5de41221404d0..469c36db01ac1 100644
--- a/packages/sdk/src/cross-chain-provider.ts
+++ b/packages/sdk/src/cross-chain-provider.ts
@@ -5,6 +5,7 @@ import {
TransactionReceipt,
} from '@ethersproject/abstract-provider'
import { ethers, BigNumber, Event } from 'ethers'
+import { sleep } from '@eth-optimism/core-utils'
import {
ICrossChainProvider,
OEContracts,
@@ -19,6 +20,7 @@ import {
MessageStatus,
TokenBridgeMessage,
MessageReceipt,
+ MessageReceiptStatus,
CustomBridges,
CustomBridgesLike,
} from './interfaces'
@@ -29,6 +31,7 @@ import {
DeepPartial,
getAllOEContracts,
getCustomBridges,
+ hashCrossChainMessage,
} from './utils'
export class CrossChainProvider implements ICrossChainProvider {
@@ -278,6 +281,59 @@ export class CrossChainProvider implements ICrossChainProvider {
})
}
+ public async toCrossChainMessage(
+ message: MessageLike
+ ): Promise {
+ // TODO: Convert these checks into proper type checks.
+ if ((message as CrossChainMessage).message) {
+ return message as CrossChainMessage
+ } else if (
+ (message as TokenBridgeMessage).l1Token &&
+ (message as TokenBridgeMessage).l2Token &&
+ (message as TokenBridgeMessage).transactionHash
+ ) {
+ const messages = await this.getMessagesByTransaction(
+ (message as TokenBridgeMessage).transactionHash
+ )
+
+ // The `messages` object corresponds to a list of SentMessage events that were triggered by
+ // the same transaction. We want to find the specific SentMessage event that corresponds to
+ // the TokenBridgeMessage (either a ETHDepositInitiated, ERC20DepositInitiated, or
+ // WithdrawalInitiated event). We expect the behavior of bridge contracts to be that these
+ // TokenBridgeMessage events are triggered and then a SentMessage event is triggered. Our
+ // goal here is therefore to find the first SentMessage event that comes after the input
+ // event.
+ const found = messages
+ .sort((a, b) => {
+ // Sort all messages in ascending order by log index.
+ return a.logIndex - b.logIndex
+ })
+ .find((m) => {
+ return m.logIndex > (message as TokenBridgeMessage).logIndex
+ })
+
+ if (!found) {
+ throw new Error(`could not find SentMessage event for message`)
+ }
+
+ return found
+ } else {
+ // TODO: Explicit TransactionLike check and throw if not TransactionLike
+ const messages = await this.getMessagesByTransaction(
+ message as TransactionLike
+ )
+
+ // We only want to treat TransactionLike objects as MessageLike if they only emit a single
+ // message (very common). It's unintuitive to treat a TransactionLike as a MessageLike if
+ // they emit more than one message (which message do you pick?), so we throw an error.
+ if (messages.length !== 1) {
+ throw new Error(`expected 1 message, got ${messages.length}`)
+ }
+
+ return messages[0]
+ }
+ }
+
public async getMessageStatus(message: MessageLike): Promise {
throw new Error('Not implemented')
}
@@ -285,18 +341,82 @@ export class CrossChainProvider implements ICrossChainProvider {
public async getMessageReceipt(
message: MessageLike
): Promise {
- throw new Error('Not implemented')
+ const resolved = await this.toCrossChainMessage(message)
+ const messageHash = hashCrossChainMessage(resolved)
+
+ // Here we want the messenger that will receive the message, not the one that sent it.
+ const messenger =
+ resolved.direction === MessageDirection.L1_TO_L2
+ ? this.contracts.l2.L2CrossDomainMessenger
+ : this.contracts.l1.L1CrossDomainMessenger
+
+ const relayedMessageEvents = await messenger.queryFilter(
+ messenger.filters.RelayedMessage(messageHash)
+ )
+
+ // Great, we found the message. Convert it into a transaction receipt.
+ if (relayedMessageEvents.length === 1) {
+ return {
+ receiptStatus: MessageReceiptStatus.RELAYED_SUCCEEDED,
+ transactionReceipt:
+ await relayedMessageEvents[0].getTransactionReceipt(),
+ }
+ } else if (relayedMessageEvents.length > 1) {
+ // Should never happen!
+ throw new Error(`multiple successful relays for message`)
+ }
+
+ // We didn't find a transaction that relayed the message. We now attempt to find
+ // FailedRelayedMessage events instead.
+ const failedRelayedMessageEvents = await messenger.queryFilter(
+ messenger.filters.FailedRelayedMessage(messageHash)
+ )
+
+ // A transaction can fail to be relayed multiple times. We'll always return the last
+ // transaction that attempted to relay the message.
+ // TODO: Is this the best way to handle this?
+ if (failedRelayedMessageEvents.length > 0) {
+ return {
+ receiptStatus: MessageReceiptStatus.RELAYED_FAILED,
+ transactionReceipt: await failedRelayedMessageEvents[
+ failedRelayedMessageEvents.length - 1
+ ].getTransactionReceipt(),
+ }
+ }
+
+ // TODO: If the user doesn't provide enough gas then there's a chance that FailedRelayedMessage
+ // will never be triggered. We should probably fix this at the contract level by requiring a
+ // minimum amount of input gas and designing the contracts such that the gas will always be
+ // enough to trigger the event. However, for now we need a temporary way to find L1 => L2
+ // transactions that fail but don't alert us because they didn't provide enough gas.
+ // TODO: Talk with the systems and protocol team about coordinating a hard fork that fixes this
+ // on both L1 and L2.
+
+ // Just return null if we didn't find a receipt. Slightly nicer than throwing an error.
+ return null
}
- public async waitForMessageReciept(
+ public async waitForMessageReceipt(
message: MessageLike,
- opts?: {
+ opts: {
confirmations?: number
pollIntervalMs?: number
timeoutMs?: number
- }
+ } = {}
): Promise {
- throw new Error('Not implemented')
+ let totalTimeMs = 0
+ while (totalTimeMs < (opts.timeoutMs || Infinity)) {
+ const tick = Date.now()
+ const receipt = await this.getMessageReceipt(message)
+ if (receipt !== null) {
+ return receipt
+ } else {
+ await sleep(opts.pollIntervalMs || 4000)
+ totalTimeMs += Date.now() - tick
+ }
+ }
+
+ throw new Error(`timed out waiting for message receipt`)
}
public async estimateL2MessageGasLimit(
diff --git a/packages/sdk/src/interfaces/cross-chain-provider.ts b/packages/sdk/src/interfaces/cross-chain-provider.ts
index 81340fc24a05e..6fa27c3895e90 100644
--- a/packages/sdk/src/interfaces/cross-chain-provider.ts
+++ b/packages/sdk/src/interfaces/cross-chain-provider.ts
@@ -145,6 +145,18 @@ export interface ICrossChainProvider {
}
): Promise
+ /**
+ * Resolves a MessageLike into a CrossChainMessage object.
+ * Unlike other coercion functions, this function is stateful and requires making additional
+ * requests. For now I'm going to keep this function here, but we could consider putting a
+ * similar function inside of utils/coercion.ts if people want to use this without having to
+ * create an entire CrossChainProvider object.
+ *
+ * @param message MessageLike to resolve into a CrossChainMessage.
+ * @returns Message coerced into a CrossChainMessage.
+ */
+ toCrossChainMessage(message: MessageLike): Promise
+
/**
* Retrieves the status of a particular message as an enum.
*
@@ -174,7 +186,7 @@ export interface ICrossChainProvider {
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
- waitForMessageReciept(
+ waitForMessageReceipt(
message: MessageLike,
opts?: {
confirmations?: number
diff --git a/packages/sdk/src/interfaces/types.ts b/packages/sdk/src/interfaces/types.ts
index 1b8fb4424c50c..81fa0fecf9178 100644
--- a/packages/sdk/src/interfaces/types.ts
+++ b/packages/sdk/src/interfaces/types.ts
@@ -34,7 +34,7 @@ export interface OEL2Contracts {
}
/**
- * Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate
+ * Represents Optimism contracts, assumed to be connected to their appropriate
* providers and addresses.
*/
export interface OEContracts {
@@ -188,15 +188,14 @@ export interface TokenBridgeMessage {
* Enum describing the status of a CrossDomainMessage message receipt.
*/
export enum MessageReceiptStatus {
- RELAYED_SUCCEEDED,
RELAYED_FAILED,
+ RELAYED_SUCCEEDED,
}
/**
* CrossDomainMessage receipt.
*/
export interface MessageReceipt {
- messageHash: string
receiptStatus: MessageReceiptStatus
transactionReceipt: TransactionReceipt
}
diff --git a/packages/sdk/src/utils/index.ts b/packages/sdk/src/utils/index.ts
index e7d02c4a7178a..a98c80fccae25 100644
--- a/packages/sdk/src/utils/index.ts
+++ b/packages/sdk/src/utils/index.ts
@@ -2,3 +2,4 @@ export * from './coercion'
export * from './contracts'
export * from './message-encoding'
export * from './type-utils'
+export * from './misc-utils'
diff --git a/packages/sdk/src/utils/message-encoding.ts b/packages/sdk/src/utils/message-encoding.ts
index 7bda7e3726657..a9f90819f2ce0 100644
--- a/packages/sdk/src/utils/message-encoding.ts
+++ b/packages/sdk/src/utils/message-encoding.ts
@@ -4,7 +4,7 @@ import { CoreCrossChainMessage } from '../interfaces'
/**
* Returns the canonical encoding of a cross chain message. This encoding is used in various
- * locations within the Optimistic Ethereum smart contracts.
+ * locations within the Optimism smart contracts.
*
* @param message Cross chain message to encode.
* @returns Canonical encoding of the message.
@@ -20,7 +20,7 @@ export const encodeCrossChainMessage = (
/**
* Returns the canonical hash of a cross chain message. This hash is used in various locations
- * within the Optimistic Ethereum smart contracts and is the keccak256 hash of the result of
+ * within the Optimism smart contracts and is the keccak256 hash of the result of
* encodeCrossChainMessage.
*
* @param message Cross chain message to hash.
diff --git a/packages/sdk/src/utils/misc-utils.ts b/packages/sdk/src/utils/misc-utils.ts
new file mode 100644
index 0000000000000..8c37c24d251b9
--- /dev/null
+++ b/packages/sdk/src/utils/misc-utils.ts
@@ -0,0 +1,17 @@
+// TODO: A lot of this stuff could probably live in core-utils instead.
+// Review this file eventually for stuff that could go into core-utils.
+
+/**
+ * Returns a copy of the given object ({ ...obj }) with the given keys omitted.
+ *
+ * @param obj Object to return with the keys omitted.
+ * @param keys Keys to omit from the returned object.
+ * @returns A copy of the given object with the given keys omitted.
+ */
+export const omit = (obj: any, ...keys: string[]) => {
+ const copy = { ...obj }
+ for (const key of keys) {
+ delete copy[key]
+ }
+ return copy
+}
diff --git a/packages/sdk/test/cross-chain-erc20-pair.spec.ts b/packages/sdk/test/cross-chain-erc20-pair.spec.ts
index 8c5d3acd3ec6a..867148d778185 100644
--- a/packages/sdk/test/cross-chain-erc20-pair.spec.ts
+++ b/packages/sdk/test/cross-chain-erc20-pair.spec.ts
@@ -1,96 +1,95 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainERC20Pair', () => {
describe('construction', () => {
- it('should have a messenger', () => {})
+ it('should have a messenger')
describe('when the token is a standard bridge token', () => {
- it('should resolve the correct bridge', () => {})
+ it('should resolve the correct bridge')
})
describe('when the token is SNX', () => {
- it('should resolve the correct bridge', () => {})
+ it('should resolve the correct bridge')
})
describe('when the token is DAI', () => {
- it('should resolve the correct bridge', () => {})
+ it('should resolve the correct bridge')
})
describe('when a custom adapter is provided', () => {
- it('should use the custom adapter', () => {})
+ it('should use the custom adapter')
})
})
describe('deposit', () => {
describe('when the user has enough balance and allowance', () => {
describe('when the token is a standard bridge token', () => {
- it('should trigger a token deposit', () => {})
+ it('should trigger a token deposit')
})
describe('when the token is ETH', () => {
- it('should trigger a token deposit', () => {})
+ it('should trigger a token deposit')
})
describe('when the token is SNX', () => {
- it('should trigger a token deposit', () => {})
+ it('should trigger a token deposit')
})
describe('when the token is DAI', () => {
- it('should trigger a token deposit', () => {})
+ it('should trigger a token deposit')
})
})
describe('when the user does not have enough balance', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
describe('when the user has not given enough allowance to the bridge', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('withdraw', () => {
describe('when the user has enough balance', () => {
describe('when the token is a standard bridge token', () => {
- it('should trigger a token withdrawal', () => {})
+ it('should trigger a token withdrawal')
})
describe('when the token is ETH', () => {
- it('should trigger a token withdrawal', () => {})
+ it('should trigger a token withdrawal')
})
describe('when the token is SNX', () => {
- it('should trigger a token withdrawal', () => {})
+ it('should trigger a token withdrawal')
})
describe('when the token is DAI', () => {
- it('should trigger a token withdrawal', () => {})
+ it('should trigger a token withdrawal')
})
})
describe('when the user does not have enough balance', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('populateTransaction', () => {
describe('deposit', () => {
- it('should populate the transaction with the correct values', () => {})
+ it('should populate the transaction with the correct values')
})
describe('withdraw', () => {
- it('should populate the transaction with the correct values', () => {})
+ it('should populate the transaction with the correct values')
})
})
describe('estimateGas', () => {
describe('deposit', () => {
- it('should estimate gas required for the transaction', () => {})
+ it('should estimate gas required for the transaction')
})
describe('withdraw', () => {
- it('should estimate gas required for the transaction', () => {})
+ it('should estimate gas required for the transaction')
})
})
})
diff --git a/packages/sdk/test/cross-chain-messenger.spec.ts b/packages/sdk/test/cross-chain-messenger.spec.ts
index 2575fde4b10fe..78d6fa9837c9b 100644
--- a/packages/sdk/test/cross-chain-messenger.spec.ts
+++ b/packages/sdk/test/cross-chain-messenger.spec.ts
@@ -1,44 +1,43 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import './setup'
describe('CrossChainMessenger', () => {
describe('sendMessage', () => {
describe('when no l2GasLimit is provided', () => {
- it('should send a message with an estimated l2GasLimit', () => {})
+ it('should send a message with an estimated l2GasLimit')
})
describe('when an l2GasLimit is provided', () => {
- it('should send a message with the provided l2GasLimit', () => {})
+ it('should send a message with the provided l2GasLimit')
})
})
describe('resendMessage', () => {
describe('when the message being resent exists', () => {
- it('should resend the message with the new gas limit', () => {})
+ it('should resend the message with the new gas limit')
})
describe('when the message being resent does not exist', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('finalizeMessage', () => {
describe('when the message being finalized exists', () => {
describe('when the message is ready to be finalized', () => {
- it('should finalize the message', () => {})
+ it('should finalize the message')
})
describe('when the message is not ready to be finalized', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
describe('when the message has already been finalized', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('when the message being finalized does not exist', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
})
diff --git a/packages/sdk/test/cross-chain-provider.spec.ts b/packages/sdk/test/cross-chain-provider.spec.ts
index 7244a4e9bed45..a620721a62e1f 100644
--- a/packages/sdk/test/cross-chain-provider.spec.ts
+++ b/packages/sdk/test/cross-chain-provider.spec.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/no-empty-function */
import { expect } from './setup'
import { Provider } from '@ethersproject/abstract-provider'
import { Contract } from 'ethers'
@@ -7,6 +6,8 @@ import {
CrossChainProvider,
MessageDirection,
CONTRACT_ADDRESSES,
+ hashCrossChainMessage,
+ omit,
} from '../src'
describe('CrossChainProvider', () => {
@@ -381,24 +382,26 @@ describe('CrossChainProvider', () => {
describe('getMessagesByAddress', () => {
describe('when the address has sent messages', () => {
describe('when no direction is specified', () => {
- it('should find all messages sent by the address', () => {})
+ it('should find all messages sent by the address')
})
describe('when a direction is specified', () => {
- it('should find all messages only in the given direction', () => {})
+ it('should find all messages only in the given direction')
})
describe('when a block range is specified', () => {
- it('should find all messages within the block range', () => {})
+ it('should find all messages within the block range')
})
describe('when both a direction and a block range are specified', () => {
- it('should find all messages only in the given direction and within the block range', () => {})
+ it(
+ 'should find all messages only in the given direction and within the block range'
+ )
})
})
describe('when the address has not sent messages', () => {
- it('should find nothing', () => {})
+ it('should find nothing')
})
})
@@ -644,132 +647,493 @@ describe('CrossChainProvider', () => {
})
})
+ describe('toCrossChainMessage', () => {
+ let l1Bridge: Contract
+ let l2Bridge: Contract
+ let l1Messenger: Contract
+ let l2Messenger: Contract
+ let provider: CrossChainProvider
+ beforeEach(async () => {
+ l1Messenger = (await (
+ await ethers.getContractFactory('MockMessenger')
+ ).deploy()) as any
+ l2Messenger = (await (
+ await ethers.getContractFactory('MockMessenger')
+ ).deploy()) as any
+ l1Bridge = (await (
+ await ethers.getContractFactory('MockBridge')
+ ).deploy(l1Messenger.address)) as any
+ l2Bridge = (await (
+ await ethers.getContractFactory('MockBridge')
+ ).deploy(l2Messenger.address)) as any
+
+ provider = new CrossChainProvider({
+ l1Provider: ethers.provider,
+ l2Provider: ethers.provider,
+ l1ChainId: 31337,
+ contracts: {
+ l1: {
+ L1CrossDomainMessenger: l1Messenger.address,
+ L1StandardBridge: l1Bridge.address,
+ },
+ l2: {
+ L2CrossDomainMessenger: l2Messenger.address,
+ L2StandardBridge: l2Bridge.address,
+ },
+ },
+ })
+ })
+
+ describe('when the input is a CrossChainMessage', () => {
+ it('should return the input', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ expect(await provider.toCrossChainMessage(message)).to.deep.equal(
+ message
+ )
+ })
+ })
+
+ describe('when the input is a TokenBridgeMessage', () => {
+ // TODO: There are some edge cases here with custom bridges that conform to the interface but
+ // not to the behavioral spec. Possibly worth testing those. For now this is probably
+ // sufficient.
+ it('should return the sent message event that came after the deposit or withdrawal', async () => {
+ const from = '0x' + '99'.repeat(20)
+ const deposit = {
+ l1Token: '0x' + '11'.repeat(20),
+ l2Token: '0x' + '22'.repeat(20),
+ from,
+ to: '0x' + '44'.repeat(20),
+ amount: ethers.BigNumber.from(1234),
+ data: '0x1234',
+ }
+
+ const tx = await l1Bridge.emitERC20DepositInitiated(deposit)
+
+ const foundCrossChainMessages = await provider.getMessagesByTransaction(
+ tx
+ )
+ const foundTokenBridgeMessages =
+ await provider.getTokenBridgeMessagesByAddress(from)
+ const resolved = await provider.toCrossChainMessage(
+ foundTokenBridgeMessages[0]
+ )
+
+ expect(resolved).to.deep.equal(foundCrossChainMessages[0])
+ })
+ })
+
+ describe('when the input is a TransactionLike', () => {
+ describe('when the transaction sent exactly one message', () => {
+ it('should return the CrossChainMessage sent in the transaction', async () => {
+ const message = {
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ gasLimit: 100000,
+ }
+
+ const tx = await l1Messenger.triggerSentMessageEvents([message])
+ const foundCrossChainMessages =
+ await provider.getMessagesByTransaction(tx)
+ const resolved = await provider.toCrossChainMessage(tx)
+ expect(resolved).to.deep.equal(foundCrossChainMessages[0])
+ })
+ })
+
+ describe('when the transaction sent more than one message', () => {
+ it('should throw an error', async () => {
+ const messages = [...Array(2)].map(() => {
+ return {
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ gasLimit: 100000,
+ }
+ })
+
+ const tx = await l1Messenger.triggerSentMessageEvents(messages)
+ await expect(provider.toCrossChainMessage(tx)).to.be.rejectedWith(
+ 'expected 1 message, got 2'
+ )
+ })
+ })
+
+ describe('when the transaction sent no messages', () => {
+ it('should throw an error', async () => {
+ const tx = await l1Messenger.triggerSentMessageEvents([])
+ await expect(provider.toCrossChainMessage(tx)).to.be.rejectedWith(
+ 'expected 1 message, got 0'
+ )
+ })
+ })
+ })
+ })
+
describe('getMessageStatus', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
- it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE', () => {})
+ it('should return a status of UNCONFIRMED_L1_TO_L2_MESSAGE')
})
describe('when the message has been executed on L2', () => {
- it('should return a status of RELAYED', () => {})
+ it('should return a status of RELAYED')
})
describe('when the message has been executed but failed', () => {
- it('should return a status of FAILED_L1_TO_L2_MESSAGE', () => {})
+ it('should return a status of FAILED_L1_TO_L2_MESSAGE')
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the message state root has not been published', () => {
- it('should return a status of STATE_ROOT_NOT_PUBLISHED', () => {})
+ it('should return a status of STATE_ROOT_NOT_PUBLISHED')
})
describe('when the message state root is still in the challenge period', () => {
- it('should return a status of IN_CHALLENGE_PERIOD', () => {})
+ it('should return a status of IN_CHALLENGE_PERIOD')
})
describe('when the message is no longer in the challenge period', () => {
describe('when the message has been relayed successfully', () => {
- it('should return a status of RELAYED', () => {})
+ it('should return a status of RELAYED')
})
describe('when the message has been relayed but the relay failed', () => {
- it('should return a status of READY_FOR_RELAY', () => {})
+ it('should return a status of READY_FOR_RELAY')
})
describe('when the message has not been relayed', () => {
- it('should return a status of READY_FOR_RELAY', () => {})
+ it('should return a status of READY_FOR_RELAY')
})
})
})
describe('when the message does not exist', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('getMessageReceipt', () => {
+ let l1Bridge: Contract
+ let l2Bridge: Contract
+ let l1Messenger: Contract
+ let l2Messenger: Contract
+ let provider: CrossChainProvider
+ beforeEach(async () => {
+ l1Messenger = (await (
+ await ethers.getContractFactory('MockMessenger')
+ ).deploy()) as any
+ l2Messenger = (await (
+ await ethers.getContractFactory('MockMessenger')
+ ).deploy()) as any
+ l1Bridge = (await (
+ await ethers.getContractFactory('MockBridge')
+ ).deploy(l1Messenger.address)) as any
+ l2Bridge = (await (
+ await ethers.getContractFactory('MockBridge')
+ ).deploy(l2Messenger.address)) as any
+
+ provider = new CrossChainProvider({
+ l1Provider: ethers.provider,
+ l2Provider: ethers.provider,
+ l1ChainId: 31337,
+ contracts: {
+ l1: {
+ L1CrossDomainMessenger: l1Messenger.address,
+ L1StandardBridge: l1Bridge.address,
+ },
+ l2: {
+ L2CrossDomainMessenger: l2Messenger.address,
+ L2StandardBridge: l2Bridge.address,
+ },
+ },
+ })
+ })
+
describe('when the message has been relayed', () => {
describe('when the relay was successful', () => {
- it('should return the receipt of the transaction that relayed the message', () => {})
+ it('should return the receipt of the transaction that relayed the message', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ const tx = await l2Messenger.triggerRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+
+ const messageReceipt = await provider.getMessageReceipt(message)
+ expect(messageReceipt.receiptStatus).to.equal(1)
+ expect(
+ omit(messageReceipt.transactionReceipt, 'confirmations')
+ ).to.deep.equal(
+ omit(
+ await ethers.provider.getTransactionReceipt(tx.hash),
+ 'confirmations'
+ )
+ )
+ })
})
describe('when the relay failed', () => {
- it('should return the receipt of the transaction that attempted to relay the message', () => {})
+ it('should return the receipt of the transaction that attempted to relay the message', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ const tx = await l2Messenger.triggerFailedRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+
+ const messageReceipt = await provider.getMessageReceipt(message)
+ expect(messageReceipt.receiptStatus).to.equal(0)
+ expect(
+ omit(messageReceipt.transactionReceipt, 'confirmations')
+ ).to.deep.equal(
+ omit(
+ await ethers.provider.getTransactionReceipt(tx.hash),
+ 'confirmations'
+ )
+ )
+ })
})
describe('when the relay failed more than once', () => {
- it('should return the receipt of the last transaction that attempted to relay the message', () => {})
+ it('should return the receipt of the last transaction that attempted to relay the message', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ await l2Messenger.triggerFailedRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+
+ const tx = await l2Messenger.triggerFailedRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+
+ const messageReceipt = await provider.getMessageReceipt(message)
+ expect(messageReceipt.receiptStatus).to.equal(0)
+ expect(
+ omit(messageReceipt.transactionReceipt, 'confirmations')
+ ).to.deep.equal(
+ omit(
+ await ethers.provider.getTransactionReceipt(tx.hash),
+ 'confirmations'
+ )
+ )
+ })
})
})
describe('when the message has not been relayed', () => {
- it('should return null', () => {})
+ it('should return null', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ await l2Messenger.doNothing()
+
+ const messageReceipt = await provider.getMessageReceipt(message)
+ expect(messageReceipt).to.equal(null)
+ })
})
- describe('when the message does not exist', () => {
- it('should throw an error', () => {})
- })
+ // TODO: Go over all of these tests and remove the empty functions so we can accurately keep
+ // track of
})
- describe('waitForMessageReciept', () => {
+ describe('waitForMessageReceipt', () => {
+ let l2Messenger: Contract
+ let provider: CrossChainProvider
+ beforeEach(async () => {
+ l2Messenger = (await (
+ await ethers.getContractFactory('MockMessenger')
+ ).deploy()) as any
+
+ provider = new CrossChainProvider({
+ l1Provider: ethers.provider,
+ l2Provider: ethers.provider,
+ l1ChainId: 31337,
+ contracts: {
+ l2: {
+ L2CrossDomainMessenger: l2Messenger.address,
+ },
+ },
+ })
+ })
+
describe('when the message receipt already exists', () => {
- it('should immediately return the receipt', () => {})
+ it('should immediately return the receipt', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ const tx = await l2Messenger.triggerRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+
+ const messageReceipt = await provider.waitForMessageReceipt(message)
+ expect(messageReceipt.receiptStatus).to.equal(1)
+ expect(
+ omit(messageReceipt.transactionReceipt, 'confirmations')
+ ).to.deep.equal(
+ omit(
+ await ethers.provider.getTransactionReceipt(tx.hash),
+ 'confirmations'
+ )
+ )
+ })
})
describe('when the message receipt does not exist already', () => {
describe('when no extra options are provided', () => {
- it('should wait for the receipt to be published', () => {})
- it('should wait forever for the receipt if the receipt is never published', () => {})
+ it('should wait for the receipt to be published', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
+
+ setTimeout(async () => {
+ await l2Messenger.triggerRelayedMessageEvents([
+ hashCrossChainMessage(message),
+ ])
+ }, 5000)
+
+ const tick = Date.now()
+ const messageReceipt = await provider.waitForMessageReceipt(message)
+ const tock = Date.now()
+ expect(messageReceipt.receiptStatus).to.equal(1)
+ expect(tock - tick).to.be.greaterThan(5000)
+ })
+
+ it('should wait forever for the receipt if the receipt is never published', () => {
+ // Not sure how to easily test this without introducing some sort of cancellation token
+ // I don't want the promise to loop forever and make the tests never finish.
+ })
})
describe('when a timeout is provided', () => {
- it('should throw an error if the timeout is reached', () => {})
- })
- })
+ it('should throw an error if the timeout is reached', async () => {
+ const message = {
+ direction: MessageDirection.L1_TO_L2,
+ target: '0x' + '11'.repeat(20),
+ sender: '0x' + '22'.repeat(20),
+ message: '0x' + '33'.repeat(64),
+ messageNonce: 1234,
+ logIndex: 0,
+ blockNumber: 1234,
+ transactionHash: '0x' + '44'.repeat(32),
+ }
- describe('when the message does not exist', () => {
- it('should throw an error', () => {})
+ await expect(
+ provider.waitForMessageReceipt(message, {
+ timeoutMs: 10000,
+ })
+ ).to.be.rejectedWith('timed out waiting for message receipt')
+ })
+ })
})
})
describe('estimateL2MessageGasLimit', () => {
- it('should perform a gas estimation of the L2 action', () => {})
+ it('should perform a gas estimation of the L2 action')
})
describe('estimateMessageWaitTimeBlocks', () => {
describe('when the message exists', () => {
describe('when the message is an L1 => L2 message', () => {
describe('when the message has not been executed on L2 yet', () => {
- it('should return the estimated blocks until the message will be confirmed on L2', () => {})
+ it(
+ 'should return the estimated blocks until the message will be confirmed on L2'
+ )
})
describe('when the message has been executed on L2', () => {
- it('should return 0', () => {})
+ it('should return 0')
})
})
describe('when the message is an L2 => L1 message', () => {
describe('when the state root has not been published', () => {
- it('should return the estimated blocks until the state root will be published and pass the challenge period', () => {})
+ it(
+ 'should return the estimated blocks until the state root will be published and pass the challenge period'
+ )
})
describe('when the state root is within the challenge period', () => {
- it('should return the estimated blocks until the state root passes the challenge period', () => {})
+ it(
+ 'should return the estimated blocks until the state root passes the challenge period'
+ )
})
describe('when the state root passes the challenge period', () => {
- it('should return 0', () => {})
+ it('should return 0')
})
})
})
describe('when the message does not exist', () => {
- it('should throw an error', () => {})
+ it('should throw an error')
})
})
describe('estimateMessageWaitTimeSeconds', () => {
- it('should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time', () => {})
+ it(
+ 'should be the result of estimateMessageWaitTimeBlocks multiplied by the L1 block time'
+ )
})
})
diff --git a/packages/sdk/test/l2-provider.spec.ts b/packages/sdk/test/l2-provider.spec.ts
new file mode 100644
index 0000000000000..51709d25a185e
--- /dev/null
+++ b/packages/sdk/test/l2-provider.spec.ts
@@ -0,0 +1,24 @@
+/* eslint-disable @typescript-eslint/no-empty-function */
+import './setup'
+
+describe('L2Provider', () => {
+ describe('getL1GasPrice', () => {
+ it('should query the GasPriceOracle contract', () => {})
+ })
+
+ describe('estimateL1Gas', () => {
+ it('should query the GasPriceOracle contract', () => {})
+ })
+
+ describe('estimateL1GasCost', () => {
+ it('should multiply the estimated L1 gas cost by the L1 gas price', () => {})
+ })
+
+ describe('estimateL2GasCost', () => {
+ it('should multiply the estimated L2 gas cost by the L1 gas price', () => {})
+ })
+
+ describe('estimateTotalGasCost', () => {
+ it('should be the sum of the L1 and L2 gas cost estimates', () => {})
+ })
+})
diff --git a/yarn.lock b/yarn.lock
index b039fb658ce48..d494a7e9264ed 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2566,6 +2566,13 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.4.0.tgz#4a1df71f736c31230bbbd634dfb006a756b51e6b"
integrity sha512-dlKiZmDvJnGRLHojrDoFZJmsQVeltVeoiRN7RK+cf2FmkhASDEblE0RiaYdxPNsUZa6mRG8393b9bfyp+V5IAw==
+"@primitivefi/hardhat-dodoc@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/@primitivefi/hardhat-dodoc/-/hardhat-dodoc-0.1.3.tgz#338ecff24b93d3b43fa35a98909f6840af86c27c"
+ integrity sha512-IM2rwyk9SHxnifHnoCKmB1K1su/d1BvF5C0zspCWH8rVrrNpS1NzLTjisDNJmbM69/cWcEX0vfk449LuTsQVaw==
+ dependencies:
+ squirrelly "^8.0.8"
+
"@resolver-engine/core@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967"
@@ -6571,13 +6578,6 @@ eslint-module-utils@^2.6.2:
debug "^3.2.7"
pkg-dir "^2.0.0"
-eslint-plugin-ban@^1.5.2:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/eslint-plugin-ban/-/eslint-plugin-ban-1.5.2.tgz#5ca01fa5acdecf79e7422e2876eb330c22b5de9a"
- integrity sha512-i6yjMbep866kREX8HfCPM32QyTZG4gfhlEFjL7s04P+sJjsM+oa0pejwyLOz/6s/oiW7BQqc6u3Dcr9tKz+svg==
- dependencies:
- requireindex "~1.2.0"
-
eslint-plugin-import@^2.23.4:
version "2.24.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.24.1.tgz#64aba8b567a1ba9921d5465586e86c491b8e2135"
@@ -8596,6 +8596,13 @@ hardhat-gas-reporter@^1.0.4:
eth-gas-reporter "^0.2.20"
sha1 "^1.1.1"
+hardhat-output-validator@^0.1.18:
+ version "0.1.18"
+ resolved "https://registry.yarnpkg.com/hardhat-output-validator/-/hardhat-output-validator-0.1.18.tgz#4ca065ee203da323a6360524c9cdc2577f850865"
+ integrity sha512-LAorI1/TO4M/UvfI19iGW/b+dh5h6rRyxLRd283AOa7HZAXx5YsC65ZDiuf41sJ4BoRG5p1Djisz4vla0DlDBg==
+ dependencies:
+ chalk "^4.1.2"
+
hardhat-watcher@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/hardhat-watcher/-/hardhat-watcher-2.1.1.tgz#8b05fec429ed45da11808bbf6054a90f3e34c51a"
@@ -13505,11 +13512,6 @@ require-main-filename@^2.0.0:
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
-requireindex@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef"
- integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==
-
reserved-words@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
@@ -14419,6 +14421,11 @@ sprintf-js@~1.0.2:
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+squirrelly@^8.0.8:
+ version "8.0.8"
+ resolved "https://registry.yarnpkg.com/squirrelly/-/squirrelly-8.0.8.tgz#d6704650b2170b8040d5de5bff9fa69cb62b5e0f"
+ integrity sha512-7dyZJ9Gw86MmH0dYLiESsjGOTj6KG8IWToTaqBuB6LwPI+hyNb6mbQaZwrfnAQ4cMDnSWMUvX/zAYDLTSWLk/w==
+
sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"