Skip to content

Espresso 1: Contracts#443

Draft
QuentinI wants to merge 20 commits into
celo-org:celo-rebase-17from
EspressoSystems:ag/contracts
Draft

Espresso 1: Contracts#443
QuentinI wants to merge 20 commits into
celo-org:celo-rebase-17from
EspressoSystems:ag/contracts

Conversation

@QuentinI
Copy link
Copy Markdown

@QuentinI QuentinI commented May 7, 2026

This PR pulls in the BatchAuthenticator part of Espresso integration at packages/contracts-bedrock/src/L1/BatchAuthenticator.sol. All other changes are deployment scripts, tests, and other wiring.

BatchAuthenticator is the contract that the batchers will have to authenticate their batches with; corresponding changes for the derivation pipeline and batchers forthcoming as subsequent PRs.

@QuentinI QuentinI force-pushed the ag/contracts branch 2 times, most recently from 4bc5939 to cbd97f9 Compare May 8, 2026 00:42
Adds the Espresso-introduced contracts and the minimum supporting changes
required for them to compile, test, and pass the contract checks.

New contracts and scripts:

- src/L1/BatchAuthenticator.sol and interfaces/L1/IBatchAuthenticator.sol
  (upgradeable contract that authenticates batch transactions, with switching
  between Espresso and fallback batchers)
- scripts/deploy/DeployBatchAuthenticator.s.sol and
  scripts/deploy/DeployEspresso.s.sol
- test/L1/BatchAuthenticator.t.sol and test/mocks/MockEspressoTEEVerifiers.sol
- snapshots/{abi,storageLayout}/BatchAuthenticator.json
- snapshots/semver-lock.json entry for BatchAuthenticator

New submodules:

- lib/espresso-tee-contracts (interfaces required by BatchAuthenticator)
- lib/openzeppelin-contracts-upgradeable-v5 (OZ v5 used by BatchAuthenticator
  via OwnableUpgradeable)

Supporting changes (Espresso-driven):

- foundry.toml: remappings for OZ v5 and espresso-tee-contracts; ignored
  warning codes for vendored libs; OOM-safe jobs settings; via-ir profile.
- justfile: fix-proxy-artifact recipe to handle OZ v5 shadowing Proxy/ProxyAdmin
  artifacts; build/coverage hooks.
- src/universal/Proxy.sol, src/universal/ProxyAdmin.sol: pin pragma to exact
  0.8.15 so they stay in their own compilation group and never emit PUSH0.
- src/universal/ReinitializableBase.sol: loosen pragma to ^0.8.15 so
  BatchAuthenticator (compiled with OZ v5) can import it.
- scripts/* and test/*: disambiguate Proxy artifact lookups to
  src/universal/Proxy.sol:Proxy (avoids OZ v5 proxy/Proxy.sol shadow).
- scripts/checks: bypass interface checks for artifacts originating from lib/;
  add Espresso-related contract names to exclude lists; pragma exclusions for
  Proxy/ProxyAdmin/BatchAuthenticator.
- test/vendor/Initializable.t.sol: exclude BatchAuthenticator (deployed by a
  separate Espresso script).

Co-authored-by: OpenCode <noreply@opencode.ai>
@QuentinI QuentinI changed the title [WIP] Espresso Contracts Espresso 1: Contracts May 8, 2026
@jcortejoso
Copy link
Copy Markdown
Member

@codex review

@jcortejoso jcortejoso requested a review from piersy May 11, 2026 14:12
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee2d984108

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol
Comment thread packages/contracts-bedrock/test/mocks/MockEspressoTEEVerifiers.sol Outdated
Comment thread packages/contracts-bedrock/test/L1/BatchAuthenticator.t.sol Outdated
Comment thread packages/contracts-bedrock/foundry.toml Outdated
Comment thread packages/contracts-bedrock/test/L1/BatchAuthenticator.t.sol
Comment thread packages/contracts-bedrock/src/L1/BatchAuthenticator.sol
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
Copy link
Copy Markdown

@Mc01 Mc01 May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to double check versioning in each file for consistency.

OP source contracts follow 0.8.15, but files like BatchAuthenticator.sol & contracts in espresso-tee-contracts follow ^0.8.0 (which is fine I guess).

OP scripts usually follow ^0.8.0, but here scripts follow 0.8.25 - could you tell more about the motivation for it?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relaxed to ^0.8.0 - I don't remember any specific reason for us doing this, going to assume there wasn't one since nothing seems to break doing this.

address payable _addr;
assembly { _addr := create(0, add(_initCode, 0x20), mload(_initCode)) }
require(_addr != address(0), "DeployBatchAuthenticator: ProxyAdmin deployment failed");
proxyAdmin = IProxyAdmin(_addr);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should separate ProxyAdmin be deployed just for BatchAuthenticator or would it be possible to re-use existing ProxyAdmin from our OP Stack deployment?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's a technical reason
cc @shenkeyao is there a governance reason for this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think technically we can reuse the existing ProxyAdmin, but that would require the OP stack to participate in every future batchAuthenticator upgrade, which is an undesirable governance dependency. Looks like this was introduced in this commit, so cc @jjeangal, who might have more context.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script was essentially built to deploy the contracts required for running solely the batch poster on the devnet. I believe it is technically possible to re-use the ProxyAdmin from your OP stack deployment. The decision to do so is more operational than architectural.
We can open a discussion as to whether this is something we want to do!

checkOutput(_output);
}

function deployBatchAuthenticator(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this is the second place that can deploy BatchAuthenticator, so the same question: should separate ProxyAdmin be freshly deployed just for this contract? Or is it possible to use already deployed ProxyAdmin for Celo's OP Stack?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeployEspresso.s.sol is the script invoked by the Espresso-specific stage of the op-deployer apply pipeline so it would never be used in production.

Comment thread packages/contracts-bedrock/scripts/checks/strict-pragma/main.go Outdated
Comment thread packages/contracts-bedrock/foundry.toml Outdated
Comment thread packages/contracts-bedrock/scripts/checks/interfaces/main.go Outdated
return nil, []error{fmt.Errorf("%s: Interface does not start with 'I'", contractName)}
}

// Espresso: skip interface artifacts that originate from lib/ (e.g. OZ v5 interfaces
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not the biggest fan of all this artifact handling.

I think there's a way around it: 0322873

Let me know what you think.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me
cc @shenkeyao I think you have more context here

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to cherry-pick 0322873! This would help clean up many artifact-related changes.

However, the lib/ skip in this file would still be necessary, because OZ v5 interfaces compiled transitively from espresso-tee-contracts would still land in forge-artifacts/.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cherry-picked!

address _owner
)
external
reinitializer(initVersion())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialize() is non-idempotent: activeIsEspresso = true on L73 re-runs on every reinitializer(initVersion()) bump and silently undoes any prior switchBatcher() (e.g. a guardian flip to Fallback).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0ba3285

Comment thread packages/contracts-bedrock/src/L1/BatchAuthenticator.sol Outdated
QuentinI and others added 4 commits May 20, 2026 21:14
- strict-pragma: remove unneeded exclusions for src/universal/Proxy.sol
  and src/universal/ProxyAdmin.sol — both already use strict
  'pragma solidity 0.8.15;', so the entries (and their misleading
  comment claiming '^') were dead.
- interfaces: move the Espresso excludeContracts block out of the
  upstream-shared area and down next to the Celo block, with one
  entry per line to match the surrounding style. Localizes future
  rebase deltas.

Co-authored-by: OpenCode <noreply@opencode.ai>
Comment thread packages/contracts-bedrock/src/L1/BatchAuthenticator.sol Outdated
Comment thread packages/contracts-bedrock/src/L1/BatchAuthenticator.sol Outdated
Comment thread packages/contracts-bedrock/src/L1/BatchAuthenticator.sol Outdated
QuentinI and others added 5 commits May 25, 2026 14:11
Inline the EspressoTEEVerifier deployment in DeployEspresso.s.sol so it
no longer imports lib/espresso-tee-contracts/scripts/DeployTEEVerifier.s.sol
or DeployNitroTEEVerifier.s.sol. The upstream scripts pulled OZ v5's
TransparentUpgradeableProxy (and its auto-deployed ProxyAdmin) into the
OP artifact tree, shadowing src/universal/ProxyAdmin.sol and forcing a
~90-line fix-proxy-artifact justfile recipe.

The TEEVerifier is now deployed behind src/universal/Proxy.sol +
src/universal/ProxyAdmin.sol, matching how BatchAuthenticator is
deployed in the same script. ERC-1967 slots are unchanged, so external
callers see no difference.

The raw vm.getCode("ProxyAdmin") lookups in the deploy scripts and
BatchAuthenticator tests are switched to the explicit artifact path
vm.getCode("forge-artifacts/ProxyAdmin.sol/ProxyAdmin.json") to
deterministically resolve the default compilation profile's bytecode
(the dispute profile transitively compiles ProxyAdmin at optimizer_runs=5000,
creating a second artifact that broke unqualified lookups).

The fix-proxy-artifact recipe and its 5 callsites are removed.
Cherry-picked from piersy's commit 5d0a803 on PR ethereum-optimism#443.

Walks the dual-batcher state machine: Espresso path → switchBatcher →
fallback path → switchBatcher → Espresso path. Asserts every transition
emits the expected event, that signer registration survives the
round-trip, and that re-issuing the same call after a mode flip changes
the outcome (the previously-valid Espresso signature is no longer
consulted on the fallback path).

Co-authored-by: Piers Powlesland <pierspowlesland@gmail.com>
Co-authored-by: OpenCode <noreply@opencode.ai>
}

function authenticateBatchInfo(bytes32 _commitment, bytes calldata _signature) external {
if (activeIsEspresso) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to check the tx sender here as well? The spec says:

Both keys must be present for a batch to be successfully posted.

Currently this only checks the ephemeral signature, not the espresso batcher address.

Something like:

    if (activeIsEspresso) {
        if (msg.sender != espressoBatcher) {
            revert UnauthorizedEspressoBatcher(msg.sender, espressoBatcher);
        }
        espressoTEEVerifier.verify(_signature, _commitment, IEspressoTEEVerifier.TeeType.NITRO);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if the check on the espressoBatcher address is actually included by it being part of the enclave hash? I.E. is the PCR0 dependent on the espressoBatcher EOA?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PCR0 is not dependent on EOA.

The reasoning behind this was that the batcher's behaviour is attested to by the PCR0, i.e. even if a random batcher started authorizing batches it wouldn't be able to post anything other than what it saw on Espresso anyways, otherwise it couldn't have registered because it wouldn't get the attestation. However, we have decided that a check here wouldn't hurt though since we're doing permissioned batching anyways. @shenkeyao implemented it here; as soon as the PR is merged I will cherry-pick it for this PR.

@jcortejoso
Copy link
Copy Markdown
Member

Hey @QuentinI!

I have authorized to run CircleCI tests from your fork. May you commit something to trigger execution?

Also I run manually the contract tests and found two are failing:

Ran 717 test suites in 349.71s (2868.40s CPU time): 2299 tests passed, 2 failed, 99 skipped (2400 total tests)
Failing tests:
Encountered 1 failing test in test/L1/BatchAuthenticator.t.sol:BatchAuthenticator_Uncategorized_Test
[FAIL: assertion failed: 3 != 2] test_setEspressoBatcher_sameBlockOverwrites() (gas: 725110)
Encountered 1 failing test in test/vendor/Initializable.t.sol:Initializer_Test
[FAIL: panic: array out-of-bounds access (0x32)] test_cannotReinitialize_succeeds() (gas: 3399) 


/// @notice Append-only history of authorized Espresso batcher addresses
/// and the L1 block at which each became active.
EspressoBatcherEntry[] internal _espressoBatcherHistory;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know about it! Done in 4a5d1b5.

Comment thread packages/contracts-bedrock/foundry.toml Outdated
{ paths = "src/L1/ProtocolVersions.sol", optimizer_runs = 0 },
{ paths = "src/universal/StorageSetter.sol", optimizer_runs = 0 }
{ paths = "src/universal/StorageSetter.sol", optimizer_runs = 0 },
{ paths = "src/L1/StandardValidator.sol", optimizer_runs = 5000 },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this addition for?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, this looks like a rebase artifact, removed in 4f91109.

Comment thread packages/contracts-bedrock/foundry.toml Outdated
Comment on lines +99 to +117
@@ -84,6 +108,13 @@ ffi = true
# you increase the gas limit above this value it must be a string.
gas_limit = 9223372036854775807

# Disable forge lint during build so 287+ linter warnings (e.g. unsafe-typecast) don't fail the build.
# Run `forge lint` separately when fixing style.
# Note: [lint] is a Foundry 1.5+ top-level section. Forge 1.2.x treats it as a deprecated profile
# notation and emits a harmless warning; lint_on_build has no effect there (feature didn't exist yet).
[lint]
lint_on_build = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @QuentinI I'm wondering if we could resolve these workarounds in the way outlined below?

A few of the foundry.toml workarounds in this PR look like symptoms of the same root cause — the deploy scripts are pulling the entire espresso-tee-contracts impl chain into OP's compile groups. Specifically:

  • ignored_error_codes adds 6321, 5667, 1878 — these are coming from compiling submodule mocks (5667 unused param), submodule scripts (1878 missing SPDX), and other submodule code (6321 unnamed return). The suppression is global, so any matching warning in OP's own src/ would now compile silently.
  • [lint] lint_on_build = false silences 287+ warnings (unsafe-typecast etc.) — again mostly from the submodule, and again globally, so new lint regressions in OP's own code slip through.
  • jobs = 1 in four profiles (ci, cicoverage, ciheavy, lite) plus threads = 4 — band-aid for OOM caused by the 211-file espresso compile group running alongside the 530-file OP group on 16 GB runners. Real CI wall-time cost on every PR.

All three could be mitigated by one structural change: switch DeployEspresso.s.sol to use vm.getCode against the submodule's own out/ for the implementation contracts, mirroring the pattern this PR already uses for ProxyAdmin at DeployBatchAuthenticator.s.sol:50-58. Concretely:

  1. Pre-build the submodule (it has its own foundry.toml pinning solc 0.8.25): forge build --root lib/espresso-tee-contracts as a CI/Makefile step before OP's main build.
  2. Drop the impl imports at DeployEspresso.s.sol:12-13 (EspressoTEEVerifier, EspressoNitroTEEVerifier). Keep the interface imports — they're tiny.
  3. Replace the two new EspressoTEEVerifier(...) / new EspressoNitroTEEVerifier(...) calls with the same vm.getCode + assembly { create(...) } pattern as ProxyAdmin, pointing at lib/espresso-tee-contracts/out/...json.
  4. Add { access = 'read', path = './lib/espresso-tee-contracts/out/' } to fs_permissions.

That eliminates the impl closure (TEEHelper, JournalValidation, and the chunk of aws-nitro-enclave-attestation reachable from it) from OP's Solc invocations — probably ~150 of the 211 files in the espresso group. What remains is just the inheritance closure from BatchAuthenticator → OwnableWithGuardiansUpgradeable → OZ, which can't be split this way but is ~30 files, not 200+.

Once the group is that small, jobs = 1 and threads = 4 should come off, and the three new ignored_error_codes + lint_on_build = false go with them since OP's build no longer compiles the submodule's mocks/scripts that emit those warnings.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds very reasonable, I will try it out!

Copy link
Copy Markdown
Author

@QuentinI QuentinI Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does seems to allow us to drop the new error codes and lint_on_build, but the compilation groups don't shrink perceptibly. I'll see if I can do anything to fix that part. Implemented everything else in in 348fed7

Replace the hand-rolled `EspressoBatcherEntry[]` history + binary search
with OpenZeppelin's `Checkpoints.Trace160` (`(uint96 key, uint160 value)`).
`uint160` is exactly an address with no waste, and `uint96` easily covers
L1 block numbers. `upperLookupRecent` replaces the custom binary search
and the same-block-overwrite branch is now handled inside `_insert`.

Co-authored-by: OpenCode <noreply@opencode.ai>
@QuentinI
Copy link
Copy Markdown
Author

QuentinI commented Jun 1, 2026

@jcortejoso I have fixed the two tests; thank you for approving the CI - it CI seems to pass except for the workflows that refuse to run altogether

QuentinI and others added 2 commits June 1, 2026 16:31
…trictions entries

src/L1/StandardValidator.sol was renamed to OPContractsManagerStandardValidator.sol
upstream in ethereum-optimism#16237; the old path was a rebase artifact with no corresponding file.

Co-authored-by: OpenCode <noreply@opencode.ai>
…sed warning codes

Drop the two impl imports (EspressoTEEVerifier, EspressoNitroTEEVerifier) from
DeployEspresso.s.sol and replace direct instantiation with vm.getCode + assembly
create, reading bytecode from the submodule's own out/ directory.

This removes the impl closure (TEEHelper, JournalValidation, and the
aws-nitro-enclave-attestation chain) from OP's solc invocations. The impls
are still parsed/ABI-checked by forge via libs=['lib'], but they no longer
require bytecode emission or the optimizer backend.

Since OP's build no longer compiles the submodule's impl files, the three
error codes those files triggered (6321 unnamed return, 5667 unused param,
1878 missing SPDX) can be removed from ignored_error_codes. OP's own code
does not trigger any of them. The lint_on_build=false workaround is also
removed for the same reason — with the impl closure gone, forge lint reports
283 warnings (all from OP's own code), none of which cause a build failure.

Adds fs_permissions read access for lib/espresso-tee-contracts/out/ so
vm.getCode can locate the pre-built artifacts. The submodule must be built
(forge build --root lib/espresso-tee-contracts) before OP's main build.

Co-authored-by: OpenCode <noreply@opencode.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants