Skip to content
Open
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
c6f9110
Changes to the inbox contract. Test TestBatchInbox_SwitchActiveBatche…
philippecamacho Nov 14, 2025
8c1f2be
Fix logical error in the Inbox contract.
philippecamacho Nov 14, 2025
d6ae426
Fix golint errors
philippecamacho Nov 14, 2025
b5380cb
Remove unneeded changes.
philippecamacho Nov 14, 2025
e561f3a
Fix configuration
philippecamacho Nov 14, 2025
c4c9350
Inbox contract unit test.
philippecamacho Nov 14, 2025
d8b52fd
Remove integration test for Inbox contract that was confusing.
philippecamacho Nov 14, 2025
891fffd
Fix solidity formatting.
philippecamacho Nov 14, 2025
1589c51
Fix configuration
philippecamacho Nov 14, 2025
af0a88d
Run the L1 contracts tests in CI.
philippecamacho Nov 14, 2025
5b17934
Pinpoint forge version
philippecamacho Nov 14, 2025
4d79e02
Trying to fix configuration issue for e2e tests.
philippecamacho Nov 14, 2025
62d96d3
Small configuration change.
philippecamacho Nov 14, 2025
8e2b1b2
Swapping batchers in batch inbox contract constructor.
philippecamacho Nov 14, 2025
2e287eb
Remove redundant concept of preApprovedBatcherKey.
philippecamacho Nov 15, 2025
9c2ceb1
Document BatchInbox.sol contract.
philippecamacho Nov 15, 2025
d6d802a
Add a test to ensure the TEE and non TEE batchers addresses are diffe…
philippecamacho Nov 15, 2025
02d8b55
Check formatting before running the tests.
philippecamacho Nov 15, 2025
e9bd770
Ensure the devnet uses two different addresses for the TEE and non TE…
philippecamacho Nov 15, 2025
56aec0c
Improve handling of configuration variables.
philippecamacho Nov 16, 2025
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
49 changes: 49 additions & 0 deletions .github/workflows/contracts-l1-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: L1 Contracts Tests

on:
pull_request:
push:
branches:
- "celo-integration*"
- "main"
- "develop"
workflow_dispatch:

jobs:
contracts-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly-654c8f01721e43dbc8a53c7a3b022548cb82b2f9

- name: Install Just
uses: extractions/setup-just@v2

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Install dependencies
working-directory: packages/contracts-bedrock
run: just install

- name: Build go-ffi
working-directory: packages/contracts-bedrock
run: just build-go-ffi

- name: Check formatting
working-directory: packages/contracts-bedrock
run: forge fmt --check

- name: Run L1 contracts tests
working-directory: packages/contracts-bedrock
run: forge test --match-path "test/L1/*.t.sol" -vv

3 changes: 3 additions & 0 deletions espresso/.env
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ BATCH_AUTHENTICATOR_OWNER_PRIVATE_KEY=0x7c852118294e51e653712a81e05800f419141751
# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/1"
PROPOSER_ADDRESS=0x70997970C51812dc3A010C7d01b50e0d17dc79C8

# cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/5"
NON_TEE_BATCHER_ADDRESS=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc

L1_CHAIN_ID=11155111
L2_CHAIN_ID=22266222

Expand Down
5 changes: 4 additions & 1 deletion espresso/scripts/prepare-allocs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@ op-deployer init --l1-chain-id "${L1_CHAIN_ID}" \
--outdir ${DEPLOYER_DIR}

dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].espressoEnabled -t bool -v true
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].preApprovedBatcherKey -v "${OPERATOR_ADDRESS}"
# Use a distinct address for the non-TEE batcher to satisfy the BatchInbox constructor
# In devnet we reuse the proposer address for the non-TEE batcher role.
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].nonTeeBatcher -v "${OPERATOR_ADDRESS}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .chains.[0].teeBatcher -v "${OPERATOR_ADDRESS}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .l1ContractsLocator -v "${ARTIFACTS_DIR}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .l2ContractsLocator -v "${ARTIFACTS_DIR}"
dasel put -f "${DEPLOYER_DIR}/intent.toml" -s .opcmAddress -v `jq -r .opcmAddress < ${DEPLOYER_DIR}/bootstrap_implementations.json`
Expand Down
142 changes: 4 additions & 138 deletions op-batcher/bindings/batch_authenticator.go

Large diffs are not rendered by default.

153 changes: 149 additions & 4 deletions op-batcher/bindings/batch_inbox.go

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions op-deployer/pkg/deployer/opcm/espresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ type DeployAWSNitroVerifierOutput struct {
}

type DeployEspressoInput struct {
Salt common.Hash
PreApprovedBatcherKey common.Address
NitroTEEVerifier common.Address
Salt common.Hash
NitroTEEVerifier common.Address
TeeBatcher common.Address
NonTeeBatcher common.Address
}

type DeployEspressoOutput struct {
Expand Down
7 changes: 4 additions & 3 deletions op-deployer/pkg/deployer/pipeline/espresso.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ func DeployEspresso(env *Env, intent *state.Intent, st *state.State, chainID com
}

eo, err = opcm.DeployEspresso(env.L1ScriptHost, opcm.DeployEspressoInput{
Salt: st.Create2Salt,
PreApprovedBatcherKey: chainIntent.PreApprovedBatcherKey,
NitroTEEVerifier: nvo.NitroTEEVerifierAddress,
Salt: st.Create2Salt,
NitroTEEVerifier: nvo.NitroTEEVerifierAddress,
TeeBatcher: chainIntent.TeeBatcher,
NonTeeBatcher: chainIntent.NonTeeBatcher,
}, batchAuthenticatorOwnwerAddress)
if err != nil {
return fmt.Errorf("failed to deploy espresso contracts: %w", err)
Expand Down
5 changes: 3 additions & 2 deletions op-deployer/pkg/deployer/state/chain_intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ type ChainIntent struct {
L2DevGenesisParams *L2DevGenesisParams `json:"l2DevGenesisParams,omitempty" toml:"l2DevGenesisParams,omitempty"`

// Espresso-specific fields
EspressoEnabled bool `json:"espressoEnabled,omitzero" toml:"espressoEnabled,omitzero"`
PreApprovedBatcherKey common.Address `json:"preApprovedBatcherKey,omitzero" toml:"preApprovedBatcherKey,omitzero"`
EspressoEnabled bool `json:"espressoEnabled,omitzero" toml:"espressoEnabled,omitzero"`
NonTeeBatcher common.Address `json:"nonTeeBatcher,omitzero" toml:"nonTeeBatcher,omitzero"`
TeeBatcher common.Address `json:"teeBatcher,omitzero" toml:"teeBatcher,omitzero"`
}

type ChainRoles struct {
Expand Down
17 changes: 9 additions & 8 deletions op-e2e/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import (
_ "embed"
)

const ESPRESSO_PRE_APPROVED_BATCHER_PRIVATE_KEY = "5fede428b9506dee864b0d85aefb2409f4728313eb41da4121409299c487f816"
const ESPRESSO_NON_TEE_BATCHER_PRIVATE_KEY = "5fede428b9506dee864b0d85aefb2409f4728313eb41da4121409299c487f816"

// cast wallet address --mnemonic "test test ... junk" --hd-path "m/44'/60'/0'/0/5"
const TEE_BATCHER_ADDRESS = "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"

// legacy geth log levels - the geth command line --verbosity flag wasn't
// migrated to use slog's numerical levels.
Expand Down Expand Up @@ -287,17 +290,15 @@ func initAllocType(root string, allocType AllocType) {
}
}

if allocType == AllocTypeEspressoWithoutEnclave {
batcherPk, err := crypto.HexToECDSA(ESPRESSO_PRE_APPROVED_BATCHER_PRIVATE_KEY)
// Configure Espresso allocation types
if allocType == AllocTypeEspresso || allocType == AllocTypeEspressoWithoutEnclave || allocType == AllocTypeEspressoWithEnclave {
batcherPk, err := crypto.HexToECDSA(ESPRESSO_NON_TEE_BATCHER_PRIVATE_KEY)
if err != nil {
panic(fmt.Errorf("failed to parse batcher private key: %w", err))
}
intent.Chains[0].EspressoEnabled = true
intent.Chains[0].PreApprovedBatcherKey = crypto.PubkeyToAddress(batcherPk.PublicKey)
}

if allocType == AllocTypeEspressoWithEnclave {
intent.Chains[0].EspressoEnabled = true
intent.Chains[0].NonTeeBatcher = crypto.PubkeyToAddress(batcherPk.PublicKey)
intent.Chains[0].TeeBatcher = common.HexToAddress(TEE_BATCHER_ADDRESS)
}

baseUpgradeSchedule := map[string]any{
Expand Down
4 changes: 2 additions & 2 deletions op-e2e/system/e2esys/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -1016,12 +1016,12 @@ func (cfg SystemConfig) Start(t *testing.T, startOpts ...StartOption) (*System,
batcherTargetNumFrames = 1
}

testingBatcherPk, err := crypto.HexToECDSA(config.ESPRESSO_PRE_APPROVED_BATCHER_PRIVATE_KEY)
testingBatcherPk, err := crypto.HexToECDSA(config.ESPRESSO_NON_TEE_BATCHER_PRIVATE_KEY)
if err != nil {
return nil, fmt.Errorf("failed to parse pre-approved batcher private key: %w", err)
}
espressoCfg := espresso.CLIConfig{
Enabled: (cfg.AllocType == config.AllocTypeEspressoWithEnclave) || (cfg.AllocType == config.AllocTypeEspressoWithoutEnclave),
Enabled: (cfg.AllocType == config.AllocTypeEspresso) || (cfg.AllocType == config.AllocTypeEspressoWithEnclave) || (cfg.AllocType == config.AllocTypeEspressoWithoutEnclave),
PollInterval: 250 * time.Millisecond,
L1URL: sys.EthInstances[RoleL1].UserRPC().RPC(),
RollupL1URL: sys.EthInstances[RoleL1].UserRPC().RPC(),
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/interfaces/L1/IBatchInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ interface IBatchInbox {

function version() external view returns (string memory);

function __constructor__(address _batchAuthenticator) external;
function __constructor__(address _teeBatcher, address _nonTeeBatcher, address _batchAuthenticator) external;
}
27 changes: 18 additions & 9 deletions packages/contracts-bedrock/scripts/deploy/DeployEspresso.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,22 @@ import { EspressoTEEVerifier } from "@espresso-tee-contracts/EspressoTEEVerifier

contract DeployEspressoInput is BaseDeployIO {
bytes32 internal _salt;
address internal _preApprovedBatcherKey;
address internal _nitroTEEVerifier;
address internal _teeBatcher;
address internal _nonTeeBatcher;

function set(bytes4 _sel, bytes32 _val) public {
if (_sel == this.salt.selector) _salt = _val;
else revert("DeployEspressoInput: unknown selector");
}

function set(bytes4 _sel, address _val) public {
if (_sel == this.preApprovedBatcherKey.selector) {
_preApprovedBatcherKey = _val;
} else if (_sel == this.nitroTEEVerifier.selector) {
if (_sel == this.nitroTEEVerifier.selector) {
_nitroTEEVerifier = _val;
} else if (_sel == this.teeBatcher.selector) {
_teeBatcher = _val;
} else if (_sel == this.nonTeeBatcher.selector) {
_nonTeeBatcher = _val;
} else {
revert("DeployEspressoInput: unknown selector");
}
Expand All @@ -41,8 +44,12 @@ contract DeployEspressoInput is BaseDeployIO {
return _nitroTEEVerifier;
}

function preApprovedBatcherKey() public view returns (address) {
return _preApprovedBatcherKey;
function teeBatcher() public view returns (address) {
return _teeBatcher;
}

function nonTeeBatcher() public view returns (address) {
return _nonTeeBatcher;
}
}

Expand Down Expand Up @@ -90,15 +97,14 @@ contract DeployEspresso is Script {
returns (IBatchAuthenticator)
{
bytes32 salt = input.salt();
address preApprovedBatcherKey = input.preApprovedBatcherKey();
vm.broadcast(msg.sender);
IBatchAuthenticator impl = IBatchAuthenticator(
DeployUtils.create2({
_name: "BatchAuthenticator",
_salt: salt,
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IBatchAuthenticator.__constructor__, (address(teeVerifier), preApprovedBatcherKey, owner)
IBatchAuthenticator.__constructor__, (address(teeVerifier), input.nonTeeBatcher(), owner)
)
)
})
Expand Down Expand Up @@ -135,7 +141,10 @@ contract DeployEspresso is Script {
_name: "BatchInbox",
_salt: salt,
_args: DeployUtils.encodeConstructor(
abi.encodeCall(IBatchInbox.__constructor__, (address(batchAuthenticator)))
abi.encodeCall(
IBatchInbox.__constructor__,
(input.teeBatcher(), input.nonTeeBatcher(), address(batchAuthenticator))
)
)
})
);
Expand Down
86 changes: 70 additions & 16 deletions packages/contracts-bedrock/src/L1/BatchInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,84 @@ pragma solidity 0.8.28;

import { IBatchAuthenticator } from "interfaces/L1/IBatchAuthenticator.sol";

/// @title BatchInbox
/// @notice Receives batches from either a TEE batcher or a non-TEE batcher and enforces
/// that TEE batches are authenticated by the configured batch authenticator.
contract BatchInbox {
IBatchAuthenticator immutable batchAuthenticator;
/// @notice Address of the TEE-based batcher.
address public immutable teeBatcher;

constructor(IBatchAuthenticator _batchAuthenticator) {
/// @notice Address of the non-TEE (fallback) batcher.
address public immutable nonTeeBatcher;

/// @notice Contract responsible for authenticating TEE batch commitments.
IBatchAuthenticator public immutable batchAuthenticator;

/// @notice Flag indicating which batcher is currently active.
/// @dev When true the TEE batcher is active; when false the non-TEE batcher is active.
bool public activeIsTee;

/// @notice Initializes the contract with the TEE and non-TEE batcher addresses
/// and the batch authenticator.
/// @param _teeBatcher Address of the TEE batcher.
/// @param _nonTeeBatcher Address of the non-TEE batcher.
/// @param _batchAuthenticator Address of the batch authenticator contract.
constructor(address _teeBatcher, address _nonTeeBatcher, IBatchAuthenticator _batchAuthenticator) {
require(_teeBatcher != address(0) && _nonTeeBatcher != address(0), "BatchInbox: zero batcher");
//require(_teeBatcher != _nonTeeBatcher, "BatchInbox: identical batchers");
teeBatcher = _teeBatcher;
nonTeeBatcher = _nonTeeBatcher;
batchAuthenticator = _batchAuthenticator;
// By default, start with the TEE batcher active
activeIsTee = true;
}

/// @notice Toggles the active batcher between the TEE and non-TEE batcher.
function switchBatcher() external {
activeIsTee = !activeIsTee;
}

/// @notice Fallback entry point for batch submissions.
/// @dev Enforces that the caller matches the currently active batcher and, when
/// the TEE batcher is active, that the batch commitment is approved by
/// the batch authenticator. For non-TEE batches, only the caller check
/// is enforced.
fallback() external {
if (blobhash(0) != 0) {
bytes memory concatenatedHashes = new bytes(0);
uint256 currentBlob = 0;
while (blobhash(currentBlob) != 0) {
concatenatedHashes = bytes.concat(concatenatedHashes, blobhash(currentBlob));
currentBlob++;
}
bytes32 hash = keccak256(concatenatedHashes);
if (!batchAuthenticator.validBatchInfo(hash)) {
revert("Invalid blob batch");
address expectedBatcher = activeIsTee ? teeBatcher : nonTeeBatcher;
if (msg.sender != expectedBatcher) {
revert("BatchInbox: unauthorized batcher");
}

// Only TEE batchers require authentication
if (activeIsTee) {
if (blobhash(0) != 0) {
bytes memory concatenatedHashes = new bytes(0);
uint256 currentBlob = 0;
while (blobhash(currentBlob) != 0) {
concatenatedHashes = bytes.concat(concatenatedHashes, blobhash(currentBlob));
currentBlob++;
}
bytes32 hash = keccak256(concatenatedHashes);
if (!batchAuthenticator.validBatchInfo(hash)) {
revert("Invalid blob batch");
}
} else {
bytes32 hash = keccak256(msg.data);
if (!batchAuthenticator.validBatchInfo(hash)) {
revert("Invalid calldata batch");
}
}
}
}

/// @notice Returns the currently active batcher and whether it is the TEE batcher.
/// @return active Address of the currently active batcher.
/// @return isTee True if the active batcher is the TEE batcher, false if it is the non-TEE batcher.
function _activeBatcher() internal view returns (address active, bool isTee) {
if (activeIsTee) {
return (teeBatcher, true);
} else {
bytes32 hash = keccak256(msg.data);
if (!batchAuthenticator.validBatchInfo(hash)) {
revert("Invalid calldata batch");
}
return (nonTeeBatcher, false);
}
}
}
Loading
Loading