This package contains the L1 and L2 smart contracts for the OP Stack. Detailed specifications for the contracts contained within this package can be found at specs.optimism.io. High-level information about these contracts can be found within this README and within the Optimism Developer Docs.
NOTE: Smart contract names in the architecture diagrams below are links to source code. Click them!
Below you'll find an architecture diagram describing the core L1 smart contracts for the OP Stack. Smart contracts that are considered "peripheral" and not core to the operation of the OP Stack system are described separately.
graph LR
subgraph "External Contracts"
ExternalERC20(External ERC20 Contracts)
ExternalERC721(External ERC721 Contracts)
end
subgraph "L1 Smart Contracts"
BatchDataEOA(<a href="https://etherscan.io/address/0xff00000000000000000000000000000000000010">Batch Inbox Address</a>)
L1StandardBridge(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/L1StandardBridge.sol">L1StandardBridge</a>)
L1ERC721Bridge(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/L1ERC721Bridge.sol">L1ERC721Bridge</a>)
L1CrossDomainMessenger(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/L1CrossDomainMessenger.sol">L1CrossDomainMessenger</a>)
L2OutputOracle(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/L2OutputOracle.sol">L2OutputOracle</a>)
OptimismPortal(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/OptimismPortal.sol">OptimismPortal</a>)
SuperchainConfig(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/SuperchainConfig.sol">SuperchainConfig</a>)
SystemConfig(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L1/SystemConfig.sol">SystemConfig</a>)
end
subgraph "User Interactions"
Users(Users)
end
subgraph "System Interactions"
Batcher(Batcher)
Proposer(Proposer)
Guardian(Guardian)
end
subgraph "Layer 2 Interactions"
L2Nodes(Layer 2 Nodes)
end
Batcher -->|publish transaction batches| BatchDataEOA
Proposer -->|propose state outputs| L2OutputOracle
Guardian -->|remove invalid state outputs| L2OutputOracle
ExternalERC20 <-->|mint/burn/transfer| L1StandardBridge
ExternalERC721 <-->|mint/burn/transfer| L1ERC721Bridge
L1StandardBridge <-->|send/receive message| L1CrossDomainMessenger
L1ERC721Bridge <-->|send/receive message| L1CrossDomainMessenger
L1CrossDomainMessenger <-->|package/send/receive message| OptimismPortal
L1StandardBridge -.->|query pause state| SuperchainConfig
L1ERC721Bridge -.->|query pause state| SuperchainConfig
L1CrossDomainMessenger -.->|query pause state| SuperchainConfig
OptimismPortal -.->|query pause state| SuperchainConfig
OptimismPortal -.->|query config| SystemConfig
OptimismPortal -.->|query proposed states| L2OutputOracle
Users <-->|deposit/withdraw ETH/ERC20| L1StandardBridge
Users <-->|deposit/withdraw ERC721| L1ERC721Bridge
Users -->|prove/execute withdrawal transactions| OptimismPortal
L2Nodes -.->|fetch transaction batches| BatchDataEOA
L2Nodes -.->|verify output roots| L2OutputOracle
L2Nodes -.->|fetch deposit events| OptimismPortal
classDef extContracts stroke:#ff9,stroke-width:2px;
classDef l1Contracts stroke:#bbf,stroke-width:2px;
classDef l1EOA stroke:#bbb,stroke-width:2px;
classDef userInt stroke:#f9a,stroke-width:2px;
classDef systemUser stroke:#f9a,stroke-width:2px;
classDef l2Nodes stroke:#333,stroke-width:2px
class ExternalERC20,ExternalERC721 extContracts;
class L1StandardBridge,L1ERC721Bridge,L1CrossDomainMessenger,L2OutputOracle,OptimismPortal,SuperchainConfig,SystemConfig l1Contracts;
class BatchDataEOA l1EOA;
class Users userInt;
class Batcher,Proposer,Guardian systemUser;
class L2Nodes l2Nodes;
- The
Batch Data Address
described above (highlighted in GREY) is not a smart contract and is instead simply an arbitrarily chosen account that is assumed to have no known private key. This account is typically chosen as the account0xFF0000....<L2 chain ID>
where<L2 chain ID>
is chain ID of the Layer 2 network for which the data is being posted. For instance, for OP Mainnet, this account is chosen as0xFF00000000000000000000000000000000000010
. However, this is not a strict requirement and some OP Stack chains may not follow this convention. - Smart contracts that sit behind
Proxy
contracts are highlighted in BLUE. Refer to the Smart Contract Proxies section below to understand how these proxies are designed.- The
L1CrossDomainMessenger
contract sits behind theResolvedDelegateProxy
contract, a legacy proxy contract type used within older versions of the OP Stack. This proxy type is used exclusively for theL1CrossDomainMessenger
to maintain backwards compatibility. - The
L1StandardBridge
contract sits behind theL1ChugSplashProxy
contract, a legacy proxy contract type used within older versions of the OP Stack. This proxy type is used exclusively for theL1StandardBridge
contract to maintain backwards compatibility.
- The
Here you'll find an architecture diagram describing the core OP Stack smart contracts that exist natively on the L2 chain itself.
graph LR
subgraph "Layer 1 (Ethereum)"
L1SmartContracts(L1 Smart Contracts)
end
subgraph "L2 Client"
L2Node(L2 Node)
end
subgraph "L2 System Contracts"
L1Block(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L1Block.sol">L1Block</a>)
GasPriceOracle(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/GasPriceOracle.sol">GasPriceOracle</a>)
L1FeeVault(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L1FeeVault.sol">L1FeeVault</a>)
BaseFeeVault(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/BaseFeeVault.sol">BaseFeeVault</a>)
SequencerFeeVault(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/SequencerFeeVault.sol">SequencerFeeVault</a>)
end
subgraph "L2 Bridge Contracts"
L2CrossDomainMessenger(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L2CrossDomainMessenger.sol">L2CrossDomainMessenger</a>)
L2ToL1MessagePasser(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L2ToL1MessagePasser.sol">L2ToL1MessagePasser</a>)
L2StandardBridge(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L2StandardBridge.sol">L2StandardBridge</a>)
L2ERC721Bridge(<a href="https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock/src/L2/L2ERC721Bridge.sol">L2ERC721Bridge</a>)
end
subgraph "Transactions"
DepositTransaction(Deposit Transaction)
UserTransaction(User Transaction)
end
subgraph "External Contracts"
ExternalERC20(External ERC20 Contracts)
ExternalERC721(External ERC721 Contracts)
end
subgraph "Remaining L2 Universe"
OtherContracts(Any Contracts and Addresses)
end
L2Node -.->|derives chain from| L1SmartContracts
L2Node -->|updates| L1Block
L2Node -->|distributes fees to| L1FeeVault
L2Node -->|distributes fees to| BaseFeeVault
L2Node -->|distributes fees to| SequencerFeeVault
L2Node -->|derives from deposits| DepositTransaction
L2Node -->|derives from chain data| UserTransaction
UserTransaction -->|can trigger| OtherContracts
DepositTransaction -->|maybe triggers| L2CrossDomainMessenger
DepositTransaction -->|can trigger| OtherContracts
ExternalERC20 <-->|mint/burn/transfer| L2StandardBridge
ExternalERC721 <-->|mint/burn/transfer| L2ERC721Bridge
L2StandardBridge <-->|sends/receives messages| L2CrossDomainMessenger
L2ERC721Bridge <-->|sends/receives messages| L2CrossDomainMessenger
GasPriceOracle -.->|queries| L1Block
L2CrossDomainMessenger -->|sends messages| L2ToL1MessagePasser
classDef extContracts stroke:#ff9,stroke-width:2px;
classDef l2Contracts stroke:#bbf,stroke-width:2px;
classDef transactions stroke:#fba,stroke-width:2px;
classDef l2Node stroke:#f9a,stroke-width:2px;
class ExternalERC20,ExternalERC721 extContracts;
class L2CrossDomainMessenger,L2ToL1MessagePasser,L2StandardBridge,L2ERC721Bridge l2Contracts;
class L1Block,L1FeeVault,BaseFeeVault,SequencerFeeVault,GasPriceOracle l2Contracts;
class UserTransaction,DepositTransaction transactions;
class L2Node l2Node;
- Contracts highlighted as "L2 System Contracts" are updated or mutated automatically as part of the chain derivation process. Users typically do not mutate these contracts directly, except in the case of the
FeeVault
contracts where any user may trigger a withdrawal of collected fees to the pre-determined withdrawal address. - Smart contracts that sit behind
Proxy
contracts are highlighted in BLUE. Refer to the Smart Contract Proxies section below to understand how these proxies are designed. - User interactions for the "L2 Bridge Contracts" have been omitted from this diagram but largely follow the same user interactions described in the architecture diagram for the Core L1 Smart Contracts.
Most L1 and L2 smart contracts for OP Stack chains today sit behind Proxy
contracts that themselves are managed by a ProxyAdmin
contract.
The ProxyAdmin
contract is controlled by some owner
address that can be any EOA or smart contract.
Below you'll find a diagram that explains the behavior of the typical proxy contract.
graph LR
ProxyAdminOwner(Proxy Admin Owner)
ProxyAdmin(<a href="https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/ProxyAdmin.sol">ProxyAdmin</a>)
subgraph "Logical Smart Contract"
Proxy(<a href="https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/universal/Proxy.sol">Proxy</a>)
Implementation(Implementation)
end
ProxyAdminOwner -->|manages| ProxyAdmin
ProxyAdmin -->|upgrades| Proxy
Proxy -->|delegatecall| Implementation
classDef l1Contracts stroke:#bbf,stroke-width:2px;
classDef systemUser stroke:#f9a,stroke-width:2px;
class Proxy l1Contracts;
class ProxyAdminOwner systemUser;
OP Stack smart contracts are published to NPM and can be installed via:
npm install @eth-optimism/contracts-bedrock.
Refer to the Optimism Developer Docs for additional information about how to use this package.
Contract ABIs and addresses are published to NPM in a separate package and can be installed via:
npm install @eth-optimism/contracts-ts
Refer to the Optimism Developer Docs for additional information about how to use this package.
See the Optimism Developer Docs for the deployed addresses of these smart contracts for OP Mainnet and OP Sepolia.
Contributions to the OP Stack are always welcome. Please refer to the CONTRIBUTING.md for more information about how to contribute to the OP Stack smart contracts.
OP Stack smart contracts should be written according to the STYLE_GUIDE.md found within this repository. Maintaining a consistent code style makes code easier to review and maintain, ultimately making the development process safer.
The smart contracts are deployed using foundry
with a hardhat-deploy
compatibility layer. When the contracts are deployed,
they will write a temp file to disk that can then be formatted into a hardhat-deploy
style artifact by calling another script.
The addresses in the deployments
directory will be read into the script based on the backend's chain id.
To manually define the set of addresses used in the script, set the CONTRACT_ADDRESSES_PATH
env var to a path on the local
filesystem that points to a JSON file full of key value pairs where the keys are names of contracts and the
values are addresses. This works well with the JSON files in superchain-ops
.
Create or modify a file <network-name>.json
inside of the deploy-config
folder.
By default, the network name will be selected automatically based on the chainid. Alternatively, the DEPLOYMENT_CONTEXT
env var can be used to override the network name.
The spec for the deploy config is defined by the deployConfigSpec
located inside of the hardhat.config.ts
.
Before deploying the contracts, you can verify the state diff produced by the deploy script using the runWithStateDiff()
function signature which produces the outputs inside snapshots/state-diff/
.
Run the deployment with state diffs by executing: forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'runWithStateDiff()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY
.
- Set the env vars
ETH_RPC_URL
,PRIVATE_KEY
andETHERSCAN_API_KEY
if contract verification is desired - Deploy the contracts with
forge script -vvv scripts/Deploy.s.sol:Deploy --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY
Pass the--verify
flag to verify the deployments automatically with Etherscan. - Generate the hardhat deploy artifacts with
forge script -vvv scripts/Deploy.s.sol:Deploy --sig 'sync()' --rpc-url $ETH_RPC_URL --broadcast --private-key $PRIVATE_KEY
All of the functions for deploying a single contract are public
meaning that the --sig
argument to forge script
can be used to
target the deployment of a single contract.
The Solidity unit tests use the same codepaths to set up state that are used in production. The same L1 deploy script is used to deploy the L1 contracts for the in memory tests
and the L2 state is set up using the same L2 genesis generation code that is used for production and then loaded into foundry via the vm.loadAllocs
cheatcode. This helps
to reduce the overhead of maintaining multiple ways to set up the state as well as give additional coverage to the "actual" way that the contracts are deployed.
The L1 contract addresses are held in deployments/hardhat/.deploy
and the L2 test state is held in a .testdata
directory. The L1 addresses are used to create the L2 state
and it is possible for stale addresses to be pulled into the L2 state, causing tests to fail. Stale addresses may happen if the order of the L1 deployments happen differently
since some contracts are deployed using CREATE
. Run pnpm clean
and rerun the tests if they are failing for an unknown reason.
contracts-bedrock
uses slither as its primary static analysis tool.
Slither will be run against PRs as part of CI, and new findings will be reported as a comment on the PR.
CI will fail if there are any new findings of medium or higher severity, as configured in the repo's Settings > Code Security and Analysis > Code Scanning > Protection rules setting.
There are two corresponding jobs in CI: one calls "Slither Analysis" and one called "Code scanning results / Slither". The former will always pass if Slither runs successfully, and the latter will fail if there are any new findings of medium or higher severity.
Existing findings can be found in the repo's Security tab > Code Scanning section.
You can view findings for a specific PR using the pr:{number}
filter, such pr:9405
.
For each finding, either fix it locally and push a new commit, or dismiss it through the PR comment's UI.
Note that you can run slither locally by running slither .
, but because it does not contain the triaged results from GitHub, it will be noisy.
Instead, you should run slither ./path/to/contract.sol
to run it against a specific file.