Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5e10969
Allow preemptively revoking certs (#329)
roger-bai-coinbase Jun 8, 2026
506a54e
docs(tee): update Nitro verifier NatSpec comments (#330)
jackchuma Jun 8, 2026
666f6ec
docs: update README to reflect contracts now live in this repo (#337)
jackchuma Jun 10, 2026
bdf7ab0
Fix access control in fee vaults (#339)
roger-bai-coinbase Jun 11, 2026
d51bc64
chore: Add audit reports (#351)
awilliams1-cb Jun 24, 2026
0eff4c2
Lower min base fee (#361)
roger-bai-coinbase Jul 9, 2026
14237f2
feat(L1): add ProtocolVersions upgrade schedule contract (#353)
PelleKrab Jul 10, 2026
4848ec7
feat(L2): add BaseTime predeploy (#364)
0x00101010 Jul 10, 2026
a2c10a5
feat(L2): add system address refunding to FeeDisburser (#358)
0xth4nh Jul 14, 2026
ee6bac4
refactor(L1): drop MIN_NOTICE check from registerUpgrade (#376)
PelleKrab Jul 15, 2026
681d04c
feat(L1): bind aggregate proofs to ProtocolVersions schedule (#359)
PelleKrab Jul 17, 2026
6c530ef
chore(L1): remove EthLockbox (#378)
jackchuma Jul 20, 2026
1c744f5
feat(L1): add activated ProtocolVersions schedule commitments (#383)
PelleKrab Jul 27, 2026
7b881c3
feat(L1): pin proof games to their L2 activation schedule (#384)
PelleKrab Jul 28, 2026
99df7e6
docs(L1): correct ProtocolVersions owner references and document unca…
PelleKrab Jul 29, 2026
6141270
docs: add AGENTS.md and add mise.toml to repo for forge version cont…
PelleKrab Jul 29, 2026
57a8809
fix(L1): sanity-check protocol version fits in 128 bits (#387)
PelleKrab Jul 29, 2026
db08d3a
fix(protocol-versions): allow equal activation timestamps (#389)
PelleKrab Jul 31, 2026
84b3a6c
Merge branch 'main' into l3-changes
dguenther Aug 4, 2026
c20aee2
fix: pin AggregateVerifier schedule to match current prover
dguenther Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0
with:
version: stable
version: v1.5.1

- name: Install Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
Expand Down
15 changes: 15 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# mise tool versions — https://mise.jdx.dev
#
# This file pins the toolchain versions used across the contracts repo.
# Contributors should install mise and run `mise install` at the repo root so
# that everyone executes builds, tests, and snapshot generation with
# byte-identical tooling.
#
# When bumping any version here, also update the matching pin in
# `.github/workflows/test.yml` (or anywhere else CI installs the same tool) so
# that local and CI runs stay aligned.

[tools]
# Foundry (forge/cast/anvil/chisel) — pinned for deterministic build
# artifacts, ABI/storage snapshots, and semver-lock hashes.
foundry = "1.5.1"
82 changes: 82 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# AGENTS.md — base/contracts

Working conventions for agents on this repo, distilled from prior sessions.
These are defaults for new or touched work, not permission to churn unrelated
code. When this file conflicts with the current tree, nearby code and enforced
tests win.

## Tooling

- `just snapshots` — regenerates ABI, storage layout, and semver-lock in one shot. Run after source changes that affect API, storage, or semver snapshots.
- `just semver-lock` — full source build + semver hash. Run before pushing semver-tagged `src/` changes or when `snapshots/semver-lock.json` may change. Do not use `just semver-lock-no-build` after source edits; it reads existing artifacts and can write stale hashes.
- `just semver-lock-no-build` — hash only, from existing artifacts.
- CI regenerates `snapshots/semver-lock.json` and diffs it against the committed file. ABI/storage snapshots are generated by `scripts/autogen`, but check the current workflow before assuming CI diffs every snapshot type.
- Snapshot generation is handled by Go scripts under `scripts/autogen`.
- Tests: `just test` (full suite), `just test --match-path <file>` (single file), `just test --match-test <name>` (single test).
- mise is used to keep local and CI Foundry versions in sync. Run `mise install` once after cloning.

## Contract structure conventions

### Style-guide ordering inside a contract

For new or touched contract bodies, prefer:

Type declarations → Constants → Immutables → State variables → Events → Errors → Constructor → external → external view → external pure → public → public view → public pure → internal → internal view → internal pure → private → private view → private pure.

### Visibility

- A concrete implementation **not meant to be inherited** should mark implementation internals `private`, not `internal`.
- Inline single-caller and single-line helpers by default; extract only when logic is genuinely shared across multiple callers.

### Interface / implementation split

- Deploy-script-backed protocol implementations generally inherit base contracts and shared mixins (proxy-owned base, initializable, reinitializable, semver), not their own `I<Name>` interface.
- When a contract has a paired interface, keep ABI-relevant declarations in sync with that interface. Do not invent a new interface only to satisfy this note.
- The `__constructor__() external` declaration in interfaces is a tooling artifact for ABI/snapshot generation, not callable. Do not remove it.
- For new semver functions in recent protocol work, prefer `function version() public pure virtual returns (string memory)`.

### API / event surface

- Use explicit `return x;`, not named returns.
- Trim events: drop params already carried in log metadata (e.g. block number).
- Add indexed query getters when arbitrary entries need querying, not just tail reads.

### Naming

- Errors: prefer `ContractName_ErrorName` for new protocol contracts.
- Events: no prefix (e.g. `SomethingRegistered`, `TimestampSet`).
- Test contracts: `ContractName_FunctionName_Test` for standard contract behavior suites; standalone unit tests may use `NameTest`.
- Test functions: prefer `test_functionName_scenario_succeeds` / `_reverts` for new tests.
- Commits: conventional — `type(scope): description` (e.g. `refactor(L1):`, `fix(L1):`).
- Branding: prefer `offchain` / `onchain` in new proof/protocol wording.

## Access control

- The proxy-owned base mixin provides `_assertOnlyProxyAdminOwner()` and `_assertOnlyProxyAdminOrProxyAdminOwner()`.
- For `ProxyAdminOwnedBase`, owner = `proxyAdmin().owner()`, read from the proxy-owner storage slot.
- Secondary roles in proxy-owned protocol contracts are plain `address` fields with inline `msg.sender` checks. Inline one-line local role checks.

## Proxy deployment

- For fresh deployment of an initialized proxy, use `upgradeToAndCall` (atomic upgrade + init in one tx). Separate `upgradeTo` then `initialize` opens a window of uninitialized state; mutations in that window corrupt persistent state permanently. Separate upgrade-only paths can be valid for admin migrations, tests, or proxies that are not being initialized.
- Upgradeable implementation constructors call `_disableInitializers()`.
- Contracts that inherit `ReinitializableBase` initialize with `reinitializer(initVersion())`. The `reinitializer` modifier fires **before** the function body, so the "already initialized" revert precedes any owner/admin assert — any caller triggers it. Contracts that use plain OpenZeppelin `initializer` are valid exceptions.

## Adding a new `Initializable` contract

Standard deploy-script-backed `src/` contracts with `initialize()` must be accounted for in the repo-wide reinitialization test that scans `src/` via FFI. The test has explicit exclusions for categories such as L2/predeploys, periphery, and contracts with custom initialization state.

- **Deployed via the standard deploy script**: add both `<Name>Impl` and `<Name>Proxy` entries. Proxy address comes from the shared test setup; impl address via the EIP-1967 implementation-slot helper on the proxy.
- **Not yet in the deploy script**: add the source path to the excludes list (fixed capacity — mind the array size) with a comment.
- Proxied-contract detection uses the `@custom:proxied` devdoc NatSpec tag — a proxied contract missing that tag won't get its proxy entry checked.
- **Always run `forge test --match-test test_cannotReinitialize_succeeds`** when adding a standard initialized contract, in addition to the contract's own tests. A narrow `--match-path` run misses it.
- New production contracts must be wired into the standard deploy script, not permanently excluded from the tracking test.

## Test conventions

- Tests for standard deploy-script-deployed system contracts should inherit the shared `CommonTest` base and use the deploy-script-deployed instances it exposes (via the shared `Setup`) — **not** `new Contract()` + a hand-rolled proxy. This makes the deploy script a real dependency of those tests.
- Mirror an existing contract test of the same type: call `super.setUp()`, read parameters from the deploy config, and get the implementation via the EIP-1967 implementation-slot helper.
- If an initializer edge-case test for a deploy-script-backed contract truly needs a fresh uninitialized proxy, deploy **only** a proxy and point it at the already-deployed implementation (via the implementation-slot helper). Do not `new Contract()` in that test file; that dodges the deploy-script dependency.
- Re-initialize / initializer edge-case tests: reset the initializable slot with `vm.store(addr, bytes32(0), bytes32(0))` behind a small helper, then prank the proxy admin. Skip on forks with `skipIfForkTest(...)`.
- Run the **full** suite after adding a contract, not just its own file (cross-cutting registration tests live outside the contract's file).
- Kill redundant/duplicate tests: if an exact-value assertion subsumes a weaker one, keep one.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

# contracts

This repo contains contracts and scripts for Base.
Note that Base primarily utilizes Optimism's bedrock contracts located in Optimism's repo [here](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts-bedrock).
This repo contains the contracts and scripts for Base.
For contract deployment artifacts, see [base-org/contract-deployments](https://github.com/base-org/contract-deployments).

<!-- Badge row 1 - status -->
Expand Down
11 changes: 11 additions & 0 deletions audits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Audits

Audits of the formerly shared OP Stack components can be found in [Optimism’s repository](https://github.com/ethereum-optimism/optimism/tree/develop/docs/security-reviews). The audits of the OP Stack components through Upgrade 18 are relevant for Base. Going forward, audits of the Base Stack smart contracts and protocol can be found here.

| Date | Reviewer | Focus and Scope | Report Link | Commit Hash |
| :---- | :---- | :---- | :---- | :---- |
| 03/17/26 | [Cantina](https://cantina.xyz/portfolio/25ba64ea-d6f3-411e-8338-419ffc385ba6) | AggregateVerifier Multiproof Contracts | [Report](./cantina_coinbase_multiproof_mar2026.pdf) | [b6c4689b8f814bf23fb915ac1d68a537d707ae4e](https://github.com/base/contracts/tree/b6c4689b8f814bf23fb915ac1d68a537d707ae4e/) |
| 03/19/26 | [Cantina](https://cantina.xyz/portfolio/423a9f33-b710-4445-a125-950b0a7771d7) | TEE Multiproof Contracts | [Report](./cantina_coinbase_nitro_enclave_mar2026.pdf) | [2421afdd332a98e9b45c6caf0a2e26b896b17e0d](https://github.com/base/contracts/tree/2421afdd332a98e9b45c6caf0a2e26b896b17e0d) |
| 04/10/26 | [Cantina](https://cantina.xyz/portfolio/a4f952cf-1c5b-4e3c-8153-c3adff899613) | TEE Multiproof Contracts 2 | [Report](./cantina_coinbase_nitro_enclave_apr2026.pdf) | [fe2af8cbffefa44bbb1a3917507f8bd8ebec7a2](https://github.com/base/contracts/tree/ffe2af8cbffefa44bbb1a3917507f8bd8ebec7a2) |
| 04/10/26 | [Cantina](https://cantina.xyz/portfolio/b72c7078-f6da-4074-a3bd-4f938f469fb7) | AggregateVerifier Multiproof Contracts 2 | [Report](./cantina_coinbase_aggregateverifier_apr2026.pdf) | [ffe2af8cbffefa44bbb1a3917507f8bd8ebec7a2](https://github.com/base/contracts/tree/ffe2af8cbffefa44bbb1a3917507f8bd8ebec7a2) |
| 06/04/26 | [Cantina](https://cantina.xyz/portfolio/6ce647dc-3b2c-448c-9421-426087341ce8) | Proof Contracts Update | [Report](./cantina_coinbase_proof_contracts_update_jun2026.pdf) | [e225648a7ed538e7e28c041d44f3b7a606ba7743](https://github.com/base/contracts/tree/e225648a7ed538e7e28c041d44f3b7a606ba7743) |
Binary file not shown.
Binary file added audits/cantina_coinbase_multiproof_mar2026.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1 change: 1 addition & 0 deletions deploy-config/local-tee.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"multiproofGameType": 621,
"multiproofGenesisBlockNumber": 0,
"multiproofIntermediateBlockInterval": 10,
"multiproofMaxUpgradeId": 12,
"multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001",
"nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000",
"operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
Expand Down
1 change: 1 addition & 0 deletions deploy-config/local.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"multiproofGameType": 621,
"multiproofGenesisBlockNumber": 0,
"multiproofIntermediateBlockInterval": 10,
"multiproofMaxUpgradeId": 12,
"multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001",
"nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000",
"operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
Expand Down
4 changes: 4 additions & 0 deletions deploy-config/mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa",
"l1FeeVaultWithdrawalNetwork": 0,
"l2BlockTime": 2,
"l2ChainId": 10,
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockNumber": 0,
"l2GenesisTimestamp": 1686789347,
"l2OutputOracleStartingBlockNumber": 105235063,
"l2OutputOracleStartingTimestamp": 1686068903,
"multiproofBlockInterval": 600,
Expand All @@ -22,6 +25,7 @@
"multiproofGenesisBlockNumber": 0,
"multiproofGenesisOutputRoot": "0x0000000000000000000000000000000000000000000000000000000000000001",
"multiproofIntermediateBlockInterval": 30,
"multiproofMaxUpgradeId": 12,
"nitroEnclaveVerifier": "0x0000000000000000000000000000000000000000",
"operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"operatorFeeVaultRecipient": "0xa3d596EAfaB6B13Ab18D40FaE1A962700C84ADEa",
Expand Down
4 changes: 4 additions & 0 deletions deploy-config/sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"l1FeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"l1FeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301",
"l1FeeVaultWithdrawalNetwork": 0,
"l2BlockTime": 2,
"l2ChainId": 11155420,
"l2GenesisBlockGasLimit": "0x1c9c380",
"l2GenesisBlockNumber": 0,
"l2GenesisTimestamp": 1691802540,
"l2OutputOracleStartingBlockNumber": 0,
"l2OutputOracleStartingTimestamp": 1690493568,
"multiproofBlockInterval": 600,
Expand All @@ -22,6 +25,7 @@
"multiproofGenesisBlockNumber": 37223829,
"multiproofGenesisOutputRoot": "0xbc273d5876d1858ecd5aaf4ce4eaf16c73f0187ca4271b774ed5da7d2254ba79",
"multiproofIntermediateBlockInterval": 30,
"multiproofMaxUpgradeId": 12,
"nitroEnclaveVerifier": "0x77461a6434fFE3435206B19658F33274f3104e07",
"operatorFeeVaultMinimumWithdrawalAmount": "0x8ac7230489e80000",
"operatorFeeVaultRecipient": "0xfd1D2e729aE8eEe2E146c033bf4400fE75284301",
Expand Down
40 changes: 0 additions & 40 deletions interfaces/L1/IETHLockbox.sol

This file was deleted.

3 changes: 0 additions & 3 deletions interfaces/L1/IOptimismPortal2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ISystemConfig } from "interfaces/L1/ISystemConfig.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
import { IAnchorStateRegistry } from "interfaces/L1/proofs/IAnchorStateRegistry.sol";
import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol";
import { IETHLockbox } from "interfaces/L1/IETHLockbox.sol";

interface IOptimismPortal2 is IProxyAdminOwnedBase {
error ContentLengthMismatch();
Expand All @@ -34,7 +33,6 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase {
error OptimismPortal_ProofNotOldEnough();
error OptimismPortal_Unproven();
error OptimismPortal_ImmediateFinalityNotEnabled();
error OptimismPortal_InvalidLockboxState();
error OutOfGas();
error UnexpectedList();
error UnexpectedString();
Expand All @@ -48,7 +46,6 @@ interface IOptimismPortal2 is IProxyAdminOwnedBase {
receive() external payable;

function anchorStateRegistry() external view returns (IAnchorStateRegistry);
function ethLockbox() external view returns (IETHLockbox);
function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) external view;
function depositTransaction(
address _to,
Expand Down
48 changes: 48 additions & 0 deletions interfaces/L1/IProtocolVersions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { ISemver } from "interfaces/universal/ISemver.sol";
import { IProxyAdminOwnedBase } from "interfaces/L1/IProxyAdminOwnedBase.sol";
import { IReinitializableBase } from "interfaces/universal/IReinitializableBase.sol";

/// @title IProtocolVersions
/// @notice Interface for the ProtocolVersions upgrade schedule contract.
interface IProtocolVersions is IProxyAdminOwnedBase, ISemver, IReinitializableBase {
event UpgradeRegistered(uint256 indexed id);
event MinimumProtocolVersionUpdated(uint256 indexed protocolVersion);
event TimestampSet(uint256 indexed id, uint256 timestamp);
event ScheduleIdUpdated(bytes32 indexed newScheduleId);
event IncidentResponderUpdated(address indexed previousIncidentResponder, address indexed newIncidentResponder);
event Initialized(uint8 version);

error ProtocolVersions_UnknownUpgrade(uint256 id);
error ProtocolVersions_InvalidProtocolVersion();
error ProtocolVersions_ActivationAlreadyPassed(uint256 id, uint64 activationTimestamp);
error ProtocolVersions_NotIncidentResponder();
error ProtocolVersions_NotScheduled(uint256 id);
error ProtocolVersions_DelayMustBeLater(uint64 currentTimestamp, uint64 newTimestamp);
error ProtocolVersions_StaticScheduleHole(uint256 id, uint256 nextScheduledId);
error ProtocolVersions_TimestampNotAfterPrevious(
uint256 id, uint256 previousId, uint64 previousTimestamp, uint64 timestamp
);
error ProtocolVersions_TimestampNotBeforeNext(uint256 id, uint256 nextId, uint64 timestamp, uint64 nextTimestamp);
error ProtocolVersions_NotInitialized();
error ProtocolVersions_InsufficientNotice(uint64 timestamp);

function initialize(address _incidentResponder) external;
function registerUpgrade(uint64 timestamp, uint256 minProtocolVersion) external returns (uint256);
function setMinimumProtocolVersion(uint256 protocolVersion) external;
function setTimestamp(uint256 id, uint64 timestamp) external;
function setIncidentResponder(address newIncidentResponder) external;
function delayTimestamp(uint256 id, uint64 newTimestamp) external;

function MIN_NOTICE() external view returns (uint64);
function minimumProtocolVersion() external view returns (uint256);
function incidentResponder() external view returns (address);
function scheduleId() external view returns (bytes32);
function scheduleId(uint256 id) external view returns (bytes32);
function activatedScheduleId(uint64 l2Timestamp) external view returns (bytes32);
function getSchedule() external view returns (uint64[] memory);

function __constructor__() external;
}
Loading
Loading