Introduction
OP Deployer is a CLI tool that simplifies deploying and upgrading smart contracts for OP Stack chains. It also exposes a suite of libraries that allow developers to easily manage smart contracts from their applications.
Goals
Declarative
With OP Deployer, developers define their chain's desired configuration in a declarative configuration file. The tool then makes the minimum number of smart contract calls required to make the deployment match the configuration. This ensures that the implementation details of the deployment are abstracted away, and allows complex configurations to be expressed cleanly without concern for the underlying deployment process.
Portable
OP Deployer is designed to be small, portable, and easily installed. As such it is distributed as a standalone binary with no additional dependencies. This allows it to be used in a variety of contexts, including as a CLI tool, in CI pipelines, and as part of local development environments like Kurtosis.
Standard, But Extensible
OP Deployer aims to make doing the right thing easy, and doing dangerous things hard. As such its configuration and API are optimized for deploying and upgrading Standard OP Chains. However, it also exposes a lower-level set of primitives and configuration directives which users can use to deploy more complex configurations if the need arises.
Development Status
OP Deployer is undergoing active development and has been used for several mainnet deployments. It is considered production-ready. However, please keep in mind that OP Deployer has not been audited and that any chains deployed using OP Deployer should be checked thoroughly for correctness prior to launch.
Installation
OP Deployer can be installed both from pre-built binaries and from source. This guide will walk you through both methods.
Install From Binaries
Installing OP Deployer from pre-built binaries is the easiest and most preferred way to get started. To install from
binaries, download the latest release from the releases page and extract the binary to a directory in your
$PATH
.
Install From Source
To install from source, you will need Go, just
, and git
. Then, run the following:
git clone git@github.com:ethereum-optimism/ethereum-optimism.git # you can skip this if you already have the repo
cd ethereum-optimism/op-deployer
just build
cp ./bin/op-deployer /usr/local/bin/op-deployer # or any other directory in your $PATH
Usage
The OP Deployer CLI tool is used to deploy and manage your smart contracts. After installing OP
Deployer, you can use op-deployer help
to view the available commands.
This following sections provide in-depth information on the different commands available.
op-deployer bootstrap
: Deploys shared contract instances for use with future invocations of OP Deployer.op-deployer init
: Initializes a new intent and state file.op-deployer apply
: Deploys a new OP Chain based on the supplied intent.op-deployer verify
: Verifies the source code of deployed contracts on Etherscan.
The Bootstrap Commands
Bootstrap commands are used to deploy global singletons and implementation contracts for use with future invocations
of apply
. Most users won't need to use these commands, since op-deployer apply
will automatically use
predeployed contracts if they are available. However, you may need to use bootstrap commands if you're deploying
chains to an L1 that isn't natively supported by op-deployer
.
There are several bootstrap commands available, which you can view by running op-deployer bootstrap --help
. We'll
focus on the most important ones below.
Implementations
You can bootstrap implementations by running a command like this:
op-deployer bootstrap implementations \
--artifacts-locator <locator> \
--l1-contracts-release op-contracts/<your-release> \
--l1-rpc-url <rpc url> \
--mips-version <1 or 2, for MIPS32 or MIPS64> \
--private-key <some private key> \
--protocol-versions-proxy <protocol versions proxy address> \
--superchain-config-proxy <superchain config proxy address> \
--upgrade-controller <upgrade controller address>
This command will deploy implementations, blueprints, and the OPCM. Deployments are (for the most part)
deterministic, so contracts will only be deployed once per chain as long as the implementation and constructor args
remain the same. This applies to the op-deployer apply
pipeline - that is, if someone else ran op-deployer boostrap implementations
at some point on a given L1 chain, then the apply
pipeline will re-use those
implementations.
The command will output a JSON like the one below:
{
"Opcm": "0x4eeb114aaf812e21285e5b076030110e7e18fed9",
"DelayedWETHImpl": "0x5e40b9231b86984b5150507046e354dbfbed3d9e",
"OptimismPortalImpl": "0x2d7e764a0d9919e16983a46595cfa81fc34fa7cd",
"PreimageOracleSingleton": "0x1fb8cdfc6831fc866ed9c51af8817da5c287add3",
"MipsSingleton": "0xf027f4a985560fb13324e943edf55ad6f1d15dc1",
"SystemConfigImpl": "0x760c48c62a85045a6b69f07f4a9f22868659cbcc",
"L1CrossDomainMessengerImpl": "0x3ea6084748ed1b2a9b5d4426181f1ad8c93f6231",
"L1ERC721BridgeImpl": "0x276d3730f219f7ec22274f7263180b8452b46d47",
"L1StandardBridgeImpl": "0x78972e88ab8bbb517a36caea23b931bab58ad3c6",
"OptimismMintableERC20FactoryImpl": "0x5493f4677a186f64805fe7317d6993ba4863988f",
"DisputeGameFactoryImpl": "0x4bba758f006ef09402ef31724203f316ab74e4a0",
"AnchorStateRegistryImpl": "0x7b465370bb7a333f99edd19599eb7fb1c2d3f8d2",
"SuperchainConfigImpl": "0x4da82a327773965b8d4d85fa3db8249b387458e7",
"ProtocolVersionsImpl": "0x37e15e4d6dffa9e5e320ee1ec036922e563cb76c"
}
It is safe to call this command from a hot wallet. None of the contracts deployed by this command are "ownable," so the deployment address has no further control over the system.
The Init Command
The init
command is used to create a new intent and state file in the specified directory. This command is the
starting point of each new deployment.
The init
command is used like this:
op-deployer init \
--l1-chain-id <chain ID of your L1> \
--l2-chain-ids <comman separated list of chain IDs for your L2s> \
--outdir <directory to write the intent and state files> \
--intent-type <standard/custom/standard-overrides>
You should then see the following files appear in your output directory:
outdir
├── intent.toml
└── state.json
The intent.toml
file is where you specify the configuration for your deployment. The state.json
file is where OP
Deployer will output the current state of the deployment after each stage of the deployment.
Your intent should look something like this:
configType = "standard"
l1ChainID = 11155420
fundDevAccounts = false
useInterop = false
l1ContractsLocator = "tag://op-contracts/v1.8.0-rc.4"
l2ContractsLocator = "op-contracts/v1.7.0-beta.1+l2-contracts"
[superchainRoles]
proxyAdminOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
protocolVersionsOwner = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
guardian = "0xeAAA3fd0358F476c86C26AE77B7b89a069730570"
[[chains]]
id = "0x0000000000000000000000000000000000000000000000000000000000002390"
baseFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
l1FeeVaultRecipient = "0x0000000000000000000000000000000000000000"
sequencerFeeVaultRecipient = "0x0000000000000000000000000000000000000000"
eip1559DenominatorCanyon = 250
eip1559Denominator = 50
eip1559Elasticity = 6
[chains.roles]
l1ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
l2ProxyAdminOwner = "0x0000000000000000000000000000000000000000"
systemConfigOwner = "0x0000000000000000000000000000000000000000"
unsafeBlockSigner = "0x0000000000000000000000000000000000000000"
batcher = "0x0000000000000000000000000000000000000000"
proposer = "0x0000000000000000000000000000000000000000"
challenger = "0x0000000000000000000000000000000000000000"
Before you can use your intent file for a deployment, you will need to update all zero values to whatever is appropriate for your chain.
The Apply Command
Once you have initialized your intent and state files, you can use the apply
command to perform the
deployment.
You can call the apply
command like this:
op-deployer apply \
--workdir <directory containing the intent and state files> \
<... additional arguments ...>
You will need to specify additional arguments depending on what you're trying to do. See below for a reference of each supported CLI arg.
--deployment-target
Default: live
--deployment-target
specifies where each chain should be deployed to. It can be one of the following values:
live
: Deploys to a live L1. Concretely, this means that OP Deployer will send transactions identified byvm.broadcast
calls to L1.--l1-rpc-url
and--private-key
must be specified when using this target.genesis
: Deploys to an L1 genesis file. This is useful for testing or local development purposes. You do not need to specify any additional arguments when using this target.calldata
: Deploys to a calldata file. This is useful for generating inputs to multisig wallets for future execution.noop
: Doesn't deploy anything. This is useful for performing a dry-run of the deployment process prior to another deployment target.
--l1-rpc-url
Defines the RPC URL of the L1 chain to deploy to.
--private-key
Defines the private key to use for signing transactions. This is only required for deployment targets that involve sending live transactions. Note that ownership over each L2 is transferred to the proxy admin owner specified in the intent after the deployment completes, so it's OK to use a hot key for this purpose.
The Verify Command
Once you have deployed contracts via bootstrap, you can use the verify
command to verify the source code on Etherscan. Constructor args used in the verification request are extracted automatically from contract initcode via the tx that created the contract.
You can call the verify
command like this:
op-deployer verify \
--l1-rpc-url <l1 rpc url> \
--input-file <filepath to input .json file> \
--etherscan-api-key <your free etherscan api key> \
--artifacts-locator <l1 forge-artifacts locator>
CLI Args
--l1-rpc-url
Defines the RPC URL of the L1 chain to deploy to (currently only supports mainnet and sepolia).
--input-file
The full filepath to the input .json file. This file should be a key/value store where the key is a contract name and the value is the contract address. The output of the bootstrap superchain|implementations
commands is a good example of this format, and those output files can be fed directly into verify
. Unless the --contract-name
flag is passed, all contracts in the input file will be verified.
Example:
{
"opcmAddress": "0x437d303c20ea12e0edba02478127b12cbad54626",
"opcmContractsContainerAddress": "0xf89d7ce62fc3a18354b37b045017d585f7e332ab",
"opcmGameTypeAdderAddress": "0x9aa4b6c0575e978dbe6d6bc31b7e4403ea8bd81d",
"opcmDeployerAddress": "0x535388c15294dc77a287430926aba5ba5fe6016a",
"opcmUpgraderAddress": "0x68a7a93750eb56dd043f5baa41022306e6cd50fa",
"delayedWETHImplAddress": "0x33ddc90167c923651e5aef8b14bc197f3e8e7b56",
"optimismPortalImplAddress": "0x54b75cb6f44e36768912e070cd9cb995fc887e6c",
"ethLockboxImplAddress": "0x05484deeb3067a5332960ca77a5f5603df878ced",
"preimageOracleSingletonAddress": "0xfbcd4b365f97cb020208b5875ceaf6de76ec068b",
"mipsSingletonAddress": "0xcc50288ad0d79278397785607ed675292dce37b1",
"systemConfigImplAddress": "0xfb24aa6d99824b2c526768e97b23694aa3fe31d6",
"l1CrossDomainMessengerImplAddress": "0x957c0bf84fe541efe46b020a6797fb1fb2eaa6ac",
"l1ERC721BridgeImplAddress": "0x62786d16978436f5d85404735a28b9eb237e63d0",
"l1StandardBridgeImplAddress": "0x6c9b377c00ec7e6755aec402cd1cfff34fa75728",
"optimismMintableERC20FactoryImplAddress": "0x3842175f3af499c27593c772c0765f862b909b93",
"disputeGameFactoryImplAddress": "0x70ed1725abb48e96be9f610811e33ed8a0fa97f9",
"anchorStateRegistryImplAddress": "0xce2206af314e5ed99b48239559bdf8a47b7524d4",
"superchainConfigImplAddress": "0x77008cdc99fb1cf559ac33ca3a67a4a2f04cc5ef",
"protocolVersionsImplAddress": "0x32e07ddb36833cae3ca1ec5f73ca348a7e9467f4"
}
--contract-name
(optional)
Specifies a single contract name, matching a contract key within the input file, to verify. If not provided, all contracts in the input file will be verified.
--artifacts-locator
The locator to forge-artifacts containing the output of the forge build
command (i.e. compiled bytecode and solidity source code). This can be a local path (with a file://
prefix), remote URL (with a http://
or https://
prefix), or standard contracts tag (with a tag://op-contracts/v
prefix).
Output
Output logs will be printed to the console and look something like the following. If the final results show numFailed=0
, all contracts were verified successfully.
INFO [03-05|15:56:55.900] Formatting etherscan verify request name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:55.900] Opening artifact path=Proxy.sol/Proxy.json name=superchainConfigProxyAddress
INFO [03-05|15:56:55.905] contractName name=src/universal/Proxy.sol:Proxy
INFO [03-05|15:56:55.905] Extracting constructor args from initcode address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A argSlots=1
INFO [03-05|15:56:56.087] Contract creation tx hash txHash=0x71b377ccc11304afc32e1016c4828a34010a0d3d81701c7164fb19525ba4fbc4
INFO [03-05|15:56:56.494] Successfully extracted constructor args address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:56:56.683] Verification request submitted name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:02.035] Verification complete name=superchainConfigProxyAddress address=0x805fc6750ec23bdD58f7BBd6ce073649134C638A
INFO [03-05|15:57:02.208] Formatting etherscan verify request name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:02.208] Opening artifact path=ProtocolVersions.sol/ProtocolVersions.json name=protocolVersionsImplAddress
INFO [03-05|15:57:02.215] contractName name=src/L1/ProtocolVersions.sol:ProtocolVersions
INFO [03-05|15:57:02.418] Verification request submitted name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:07.789] Verification complete name=protocolVersionsImplAddress address=0x658812BEb9bF6286D03fBF1B5B936e1af490b768
INFO [03-05|15:57:07.971] Contract is already verified name=protocolVersionsProxyAddress address=0x17C64430Fa08475D41801Dfe36bAFeE9667c6fA7
INFO [03-05|15:57:07.971] --- COMPLETE ---
INFO [03-05|15:57:07.971] final results numVerified=4 numSkipped=1 numFailed=0
Known Limitations
-
Does not currently work for contracts in the
opchain
bundle (deployed viaop-deployer apply
) that have constructor args. Those constructors args cannot be extracted from the deploymenttx.Data()
sinceOPContractsManager.deploy()
uses factory pattern with CREATE2 to deploy those contracts. -
Currently only supports etherscan block explorers. Blockscout support is planned but not yet implemented.
Known Limitations
OP Deployer is subject to some known limitations which we're working on addressing in future releases.
Tagged Releases on New Chains
Fixed in all versions after v0.0.11.
It is not currently possible to deploy chains using tagged contract locators (i.e., those starting with tag://
)
anywhere except Sepolia and Ethereum mainnet. If you try to, you'll see an error like this:
####################### WARNING! WARNING WARNING! #######################
You are deploying a tagged release to a chain with no pre-deployed OPCM.
Due to a quirk of our contract version system, this can lead to deploying
contracts containing unaudited or untested code. As a result, this
functionality is currently disabled.
We will fix this in an upcoming release.
This process will now exit.
####################### WARNING! WARNING WARNING! #######################
Like the error says, this is due to a quirk of how we version our smart contracts. We currently follow a process like this:
- We tag a release, like op-contracts/v1.8.0.
- We update the release notes to reference which contracts are updated in that release.
- We manually deploy the updated contract implementations.
- We manually deploy a new OPCM to reference the newly-deployed implementations, as well as existing implementations for any contracts that have not been updated.
There's a flaw in this strategy, however. The release only includes the contracts that explicitly changed during that release. This means that any contract not referenced as "updated" in the release notes is "in-development," and has not been audited or approved by governance. Deploying all contracts from the release tag will therefore deploy a combination of prod-ready and in-development code. To get the version of the contract that will actually run in prod, OP Deployer would have to reference all previous releases to get the correct combination of contracts.
For example, to deploy on Holesky you will need to deploy contracts from versions op-contracts/v1.8.0
, op-contracts/v1.6.0
, and op-contracts/v1.3.0
. On
Sepolia and mainnet, we've been incrementally deploying implementation contracts so we just use the existing
¬implementations to work around this issue.
We plan on addressing this in our next release. In the meantime, as a workaround you can use a non-tagged locator for development chains, or use Sepolia or Ethereum mainnet as your L1.
Architecture
This section details OP Deployer's architecture and internals. Unless you're contributing directly to OP Deployer, you don't need to read this.
- Deployment Pipeline: Describes the stages of the deployment pipeline.
- Scripting Engine: Describes the scripting engine that OP Deployer uses to interact with the EVM.
Full architecture diagram (source)
Deployment Pipeline
OP Deployer is architected as a pipeline where each stage is responsible for a single piece of the deployment process. The pipeline consumes a configuration called an intent which describes the desired state of the chain, and produces a file called the state which describes the current state of the chain during and after the deployment. The steps of the pipeline are:
- Initialization
- Superchain Deployment
- Implementations Deployment
- OP Chain Deployment
- Alt-DA Deployment
- Dispute Game Deployment
- L2 Genesis Generation
- Setting Start Block
State is written to disk after each state. This allows the pipeline to be restarted from any point in the event of a recoverable error.
We'll cover each of these stages in more detail below.
Initialization
During this step, OP Deployer sets initial values for the pipeline based on the user's intent. These values will be used by downstream stages. For example, if the user is deploying using an existing set of Superchain contracts, those contracts will be inserted into the state during this step.
Superchain/Implementations Deployment
Next, the base contracts for the chain are deployed. This includes Superchain-wide contracts like the
SuperchainConfig
and ProtocolVersions
, as well as implementation contracts that will be used for the OP Chain
deployment in the future like the OP Contracts Manager (OPCM).
Most chains will be configured to use existing implementations. In this case, these steps will be skipped.
OP Chain Deployment
The OP Chain itself is deployed during this step. Multiple chains will be deployed if they are specified in the intent. The deployment works by calling into the OPCM, which will emit an event for each successfully-deployed chain.
Customizations Deployment
The next two steps (Alt-DA and Dispute Game) deploy customizations. As their names imply, they deploy Alt-DA and additional dispute game contracts. Typically, these steps will be skipped as they are mostly useful in testing.
L2 Genesis Generation
This step generates the L2 Genesis file which is used to initialize the chain. This file is generated by calling
into L2Genesis.sol
, and dumping the outputted state.
Setting Start Block
Lastly, the start block is set to the current block number on L1. This is done last to ensure that the start block is relatively recent, since the deployment process can take arbitrarily long.
Scripting Engine
One of OP Deployer's most powerful features is its in-memory EVM scripting engine. The scripting engine provides similar capabilities to Forge:
- It runs all on-chain calls in a simulated environment first, which allows the effects of on-chain calls to be validated before they cost gas.
- It exposes Foundry cheatcodes, which allow for deep instrumentation and customization of the EVM environment. These cheatcodes in turn allow OP Deployer to call into Solidity scripts.
The scripting engine is really the heart of OP Deployer. Without it, OP Deployer would be nothing more than a thin wrapper over Forge. The scripting engine enables:
- Easy integration with existing Solidity-based tooling.
- Detailed stack traces when deployments fail.
- Fast feedback loops that prevent sending on-chain transactions that may fail.
- Live chain forking.
For these reasons and more, the scripting engine is a critical part of OP Deployer's architecture. You will see that
almost all on-chain interactions initiated by OP Deployer use the scripting engine to call into a Solidity script.
The script then uses vm.broadcast
to signal a transaction that should be sent on-chain.
Aside: Why Use Solidity Scripts?
Solidity scripts are much more ergonomic than Go code for complex on-chain interactions. They allow for:
- Easy integration with existing Solidity-based tooling and libraries.
- Simple ABI encoding/decoding.
- Clear separation of concerns between inter-contract calls, and the underlying RPC calls that drive them.
The alternative is to encode all on-chain interactions in Go code. This is possible, but it is much more verbose and requires writing bindings between Go and the Solidity ABI. These bindings are error-prone and difficult to maintain.
Engine Implementation
The scripting engine is implemented in the op-chain-ops/script
package. It extends Geth's EVM implementation with
Forge cheatcodes, and defines some tools that allow Go structs to be etched into the EVM's memory. Geth exposes
hooks that drive most of the engine's behavior. The best way to understand these further is to read the code.
Using the Engine
OP Deployer uses the etching tooling described above to communicate between OP Deployer and the scripting engine. Most Solidity scripts define an input contract, an output contract, and the script itself. The script reads data from fields on the input contract, then sets fields on the output contract as it runs. OP Deployer defines the input and output contracts as Go structs, like this:
package foo_script
type FooInput struct {
Number uint64
Bytes []byte
}
type FooOutput struct {
Result uint64
Bytes []byte
}
The input and output contracts are then "etched" into the EVM's memory, like this:
package foo_script
// ... struct defs elided
func Run(host *script.Host, input FooInput) (FooOutput, error) {
// Create a variable to hold our output
var output FooOutput
// Make new addresses for our input/output contracts
inputAddr := host.NewScriptAddress()
outputAddr := host.NewScriptAddress()
// Inject the input/output contracts into the EVM as precompiles
cleanupInput, err := script.WithPrecompileAtAddress[*FooInput](host, inputAddr, &input)
if err != nil {
return output, fmt.Errorf("failed to insert input precompile: %w", err)
}
defer cleanupInput()
cleanupOutput, err := script.WithPrecompileAtAddress[*FooOutput](host, outputAddr, &output,
script.WithFieldSetter[*FooOutput])
if err != nil {
return output, fmt.Errorf("failed to insert output precompile: %w", err)
}
defer cleanupOutput()
// ... do stuff with the input/output contracts ...
}
The script engine will automatically generate getters and setters for the fields on the input and output contracts.
You can use the evm:
struct tag to customize the behavior of these getters and setters.
Finally, the script itself gets etched into the EVM's memory and executed, like this:
package foo_script
type FooScript struct {
Run func(input, output common.Address) error
}
func Run(host *script.Host, input FooInput) (FooOutput, error) {
// .. see implementation above...
deployScript, cleanupDeploy, err := script.WithScript[FooScript](host, "FooScript.s.sol", "FooScript")
if err != nil {
return output, fmt.Errorf("failed to load %s script: %w", scriptFile, err)
}
defer cleanupDeploy()
if err := deployScript.Run(inputAddr, outputAddr); err != nil {
return output, fmt.Errorf("failed to run %s script: %w", scriptFile, err)
}
return output, nil
}
You may notice that the script is loaded from a file. To run the scripting engine, contract artifacts (not source code) must exist somewhere on disk for the scripting engine to use. For more information on that, see the chapter on artifacts locators.
Artifacts Locators
OP Deployer calls into precompiled contract artifacts. To make this work, OP Deployer uses artifacts locators to point to the location of contract artifacts. While locators are nothing more than URLs, they do encode some additional behaviors which are described here.
Locator Types
Locators can be one of three types:
tag://
locators, which point to a versioned contracts release. These resolve to a known URL. Artifacts downloaded using a tagged locator are validated against a hardcoded checksum in the OP Deployer implementation. This prevents tampering with the contract artifacts once they have been tagged. Additionally, tagged locators are cached on disk to avoid repeated downloads.https://
locators, which point to a tarball of contract artifacts somewhere on the web. HTTP locators are cached just like tagged locators are, but they are not validated against a checksum.file://
locators, which point to a directory on disk containing the artifacts.
Releases
Versioning
For all releases after v0.0.11
, each minor version of OP Deployer will support a single release of the
governance-approved smart contracts. If you want to deploy an earlier version of the contracts (which may be
dangerous!), you should use an earlier version of OP Deployer. This setup allows our smart contract developers to make
breaking changes on develop
, while still allowing new chains to be deployed and upgraded using production-ready smart
contracts.
If you deploy from an HTTPS or file locator, the deployment behavior will match the
contract's tag. For example, if version v0.2.0
supports v2.0.0
then the deployment will work as if you were
deploying op-contracts/v2.0.0
. Typically, errors like unknown selector: <some hex>
imply that you're using the wrong
version of OP Deployer for your contract artifacts. If this happens, we recommend trying different versions until you
get one that works. Note that this workflow is not recommended for production chains.
Version Backports
From time to time, we may backport bugfixes from develop onto earlier versions of OP Deployer. The process for this is as follows:
- If one doesn't exist already, make a new branch for the version lineage you're patching (e.g.
v0.2.x
). This branch should be based on the latest release of that lineage. The branch should be named as follows:backports/op-deployer/<lineage, i.e. v0.2.0>
. - Open a PR with the backport against that branch. Be sure to reference the original commit in the backport.
- Make and push a new tag on that lineage.
Adding Support for New Contract Versions
Adding support for a new contract version is a multi-step process. Here's a high-level overview. For the sake of
simplicity we will assume you are adding support for a new rc
release.
Step 1: Add Support on develop
This section is designed for people developing OP Deployer itself.
First, you need to add support for the new contract version on the develop
branch. This means ensuring that the
deployment pipeline supports whatever changes are required for the new version. Typically, this means passing in new
deployment variables, and responding to ABI changes in the Solidity scripts/OPCM.
Step 2: Add the Published Artifacts
Run the following from the root of the monorepo:
cd packages/contracts-bedrock
just clean
just build
bash scripts/ops/calculate-checksum.sh
# copy the outputted checksum
cd ../../op-deployer
just calculate-artifacts-hash <checksum>
This will calculate the checksum of your artifacts as well as the hash of the artifacts tarball. OP Deployer uses these values to download and verify tagged contract locators.
Now, update standard/standard.go
with these values so that the new artifacts tarball can be downloaded:
// Add a new const for your release
const ContractsVXTag = "op-contracts/vX.Y.Z"
var taggedReleases = map[string]TaggedRelease{
// Other releases...
ContractsVXTag: {
ArtifactsHash: common.HexToHash("<the artifacts hash>"),
ContentHash: common.HexToHash("<the checksum>"),
},
}
// Update the L1/L2 versions accordingly
func IsSupportedL1Version(tag string) bool {
return tag == ContractsVXTag
}
Step 3: Update the SR With the New Release
Add the new RC to the standard versions in the Superchain Registry.
Step 4: Update the validation
Package
The SR is pulled into OP Deployer via the validation
package. Update it by running the following command from the
root of the monorepo:
go get -u github.com/ethereum-optimism/superchain-registry/validation@<SR commit SHA>
That should be it!