Skip to content

Add Celo CIP-64 acceptance test (sysgo)#440

Draft
palango wants to merge 18 commits into
celo-rebase-17from
palango/celo-cip64-acceptance-test
Draft

Add Celo CIP-64 acceptance test (sysgo)#440
palango wants to merge 18 commits into
celo-rebase-17from
palango/celo-cip64-acceptance-test

Conversation

@palango
Copy link
Copy Markdown

@palango palango commented Apr 29, 2026

TL;DR

Adds a sysgo.WithCelo() deployer option that etches the Celo predeploys into the L2 genesis, plus a single op-acceptance-tests/tests/celo/cip64_test.go that signs a CeloDynamicFeeTxV2 from a TEST-funded EOA (no native CELO) and asserts it lands. Also fixes a handful of upstream OP-Stack assumptions that previously prevented CIP-64 txs from being included on a sysgo-built devnet.

Run with just celo from op-acceptance-tests/.

What's in the diff

1. New deployCeloContracts deploy intent

A per-chain DeployCeloContracts bool on state.ChainIntent. When set, the L2Genesis foundry script's new setCeloPredeploys etches:

  • CeloRegistry, GoldToken, FeeHandler, MentoFeeHandlerSeller, UniswapFeeHandlerSeller, SortedOracles (initialized with a 1-hour report expiry so owner() is non-zero), AddressSortedLinkedListWithMedian (library), the testing FeeCurrency, and FeeCurrencyDirectory at the celo-mainnet address 0x15F344…6276 that op-geth's MainnetAddresses defaults to.
  • A test fee currency (StableTokenV2 named TEST) at 0x765DE816…1282a, registered with the FCD at 80 000 intrinsic gas, with 100k TEST minted to devAccounts[0].

2. sysgo.WithCelo() option

Plumbs the flag through intentbuilderstate.ChainIntentopcm.L2GenesisInput → forge script. Tests opt in with:

presets.DoMain(m,
    presets.WithMinimal(),
    stack.MakeCommon(sysgo.WithDeployerOptions(sysgo.WithCelo())),
)

3. Three upstream assumptions fixed so CIP-64 txs can land

These were all "OP Stack defaults that quietly drop CIP-64 txs" — diagnosed by instrumenting a local op-geth.

  • L1 rollup data fee in native CELO: even for fee-currency txs, op-geth's txpool charges the rollup data cost against the sender's native balance. Mirroring celo-mainnet, when DeployCeloContracts is set we zero BasefeeScalar and BlobBaseFeeScalar in the deployer (operator pays all DA cost). The zero-scalar require() in DeployOPChain.s.sol is gated on the same flag so non-Celo chains keep the safety net.
  • MultiGasPool per-currency budget = 0: the miner uses a per-fee-currency gas budget. miner.DefaultConfig.FeeCurrencyDefault is 0.5, but op-e2e/e2eutils/geth.InitL2's miner.Config{} literal omitted the field. CIP-64 txs were silently popped at block-building time. The fix lives in op-devstack/sysgo/l2_el_opgeth.go, gated on the L2 genesis containing FCD code, so it doesn't pollute generic L2 init.

4. The acceptance test

op-acceptance-tests/tests/celo/cip64_test.go::TestCIP64_PayGasInTestFeeCurrency:

  1. Skips if FCD is missing (so the gate runs cleanly on non-Celo devnets).
  2. Signs a CeloDynamicFeeTxV2 with foundry's devAccounts[0] (zero native, 100k TEST), FeeCurrency = TEST.
  3. Waits for inclusion.
  4. Asserts the sender's native balance didn't move and TEST balance dropped.

Registered as the celo gate in acceptance-tests.yaml.

Verification

  • go test ./op-acceptance-tests/tests/celo/... — passes (~3 s after warmup).
  • go test -run TestApplyDeployCeloContracts ./op-deployer/pkg/deployer/integration_test/... — passes; checks the predeploys actually end up in the L2 allocs.
  • go test -run TestApplyGenesisStrategy ./op-deployer/pkg/deployer/integration_test/... — passes; confirms no regression on the non-Celo path.

Test plan

  • CI runs the new celo gate.
  • Verify the existing CGT and base gates still pass on this branch.

Out of scope / follow-ups

  • WithCustomGasToken + WithCelo() interaction (Celo's scalar-zeroing currently overrides whatever scalar the user set).
  • The --libraries link for AddressSortedLinkedListWithMedian is only on build-no-tests; the other forge build recipes don't carry it.
  • The hardcoded mainnet FCD address would mismatch on a chain configured with params.CeloSepoliaChainID. No one hits this today.

palango added 12 commits April 29, 2026 12:39
Adds a per-chain DeployCeloContracts intent that, when set, etches the Celo
predeploys into the L2 genesis allocs: CeloRegistry, GoldToken, FeeHandler,
the FeeHandlerSellers, SortedOracles, AddressSortedLinkedListWithMedian,
the testing FeeCurrency, and FeeCurrencyDirectory at the mainnet address
0x15F344...6276 expected by celo-org/op-geth's MainnetAddresses default.

Also deploys a generic test fee currency (StableTokenV2) at the predeploy
address previously labelled cUSD, registers devAccounts[0] as its oracle,
reports a price, and registers it with the FeeCurrencyDirectory at 80k
intrinsic gas so CIP-64 transactions can pay gas in it.

Default behavior is unchanged. Wires the flag through ChainIntent and
opcm.L2GenesisInput; adds an integration smoke test verifying the allocs.
Exposes the new DeployCeloContracts intent through the intentbuilder
L2Configurator and a sysgo.WithCelo() deployer option. Tests can now
spin up an in-process devnet with the Celo predeploys deployed and a
test fee currency registered for CIP-64 coverage.
Two independent fixes are needed before CIP-64 (CeloDynamicFeeTxV2)
transactions actually land in a block on a sysgo chain with WithCelo()
deployed.

First, the L1 rollup data fee. Even for fee-currency txs, op-geth's
txpool charges the rollup data cost against the sender's native balance.
On the Celo test setup the sender has zero native, so every tx is
rejected with "insufficient funds". This mirrors what celo-mainnet does
in production: when DeployCeloContracts is set we hand both
GasPriceOracleBaseFeeScalar and BlobBaseFeeScalar to 0 so the chain
operator pays all DA cost. The zero-scalar guard in DeployOPChain.s.sol
is relaxed to allow this.

Second, op-geth's MultiGasPool. The miner uses a per-fee-currency gas
budget when building blocks. miner.DefaultConfig sets
FeeCurrencyDefault=0.5 (50% of the block gas limit available to any one
fee currency), but op-e2e/e2eutils/geth's L2 init constructs miner.Config
as a literal that omits the field, so it defaults to 0 and every CIP-64
tx is silently popped during block building. Set FeeCurrencyDefault to
the celo default and provide an explicit 0.9 entry for the test fee
currency, mirroring celo-mainnet's cUSD/USDT/USDC config.
Adds tests/celo/ with a single TestCIP64_PayGasInTestFeeCurrency that
spins up a sysgo chain via sysgo.WithCelo(), sends a CeloDynamicFeeTxV2
(CIP-64) signed by foundry's devAccounts[0] (pre-funded with 100k TEST
and zero native CELO), waits for inclusion, and asserts the sender's
native balance did not move while the TEST balance dropped.

The test gracefully skips on devnets without the FeeCurrencyDirectory
predeploy. Registered as the celo gate in acceptance-tests.yaml; just
celo runs it.
Calling SortedOracles.initialize gives the predeploy a non-zero owner
(the script deployer) and a non-zero reportExpirySeconds. Previously
the proxy was etched but never initialized, leaving owner = address(0)
and reportExpirySeconds = 0, which caused isOldestReportExpired() to
treat every report as expired and forced deployTestFeeCurrency to
prank as address(0) to satisfy the onlyOwner gate on addOracle.
The previous fix put FeeCurrencyDefault and a hardcoded cUSD entry
into the miner.Config{} literal in op-e2e/e2eutils/geth.InitL2, which
applies to every L2 EL the test suite spins up. That polluted the
generic L2 init code with Celo-specific defaults.

Move both settings to op-devstack/sysgo/l2_el_opgeth.go, gated on the
L2 genesis actually containing code at the FeeCurrencyDirectory
predeploy address. Only chains booted with sysgo.WithCelo() now
configure the MultiGasPool, and InitL2 returns to being Celo-agnostic.
…loContracts

Restore the require()s on basefeeScalar and blobBaseFeeScalar in
DeployOPChain.s.sol's checkInput, gated on a new deployCeloContracts
field on the Types.DeployOPChainInput struct. Non-Celo chains regain
the safety net against accidentally deploying with zero scalars while
Celo chains continue to bypass it (where the operator pays all DA cost).

The flag is propagated from the chain intent through opcm.DeployOPChainInput
into the forge struct, mirroring the existing UseCustomGasToken plumbing.
…eeCurrencyDirectory + TestFeeCurrency

Adds FeeCurrencyDirectory and TestFeeCurrency to op-core/predeploys
alongside the other Celo predeploys, with matching *Addr vars and
CeloPredeploys map entries. Removes the duplicate hardcoded addresses
from sysgo/l2_el_opgeth.go and op-acceptance-tests/tests/celo/helpers.go,
and references the predeploys package directly from cip64_test.go.

Now there's one canonical Go source for these addresses; the Solidity
constants in src/celo/CeloPredeploys.sol remain the matching on-chain
source.
Replace inlined hex addresses with the existing predeploys.*Addr
constants (now including the FeeCurrencyDirectory and TestFeeCurrency
entries added in the previous commit). A typo in any of these literals
would have produced a silent false-positive — the constants close that
gap.
The same helper exists in both pipeline/opchain.go and state/deploy_config.go.
They live in different packages and can't share the symbol, but the names
should match for grep-ability and to make the duplication explicit.
Aligns the comment text too.
L2Genesis already uses vm.prank for single-call prank scopes
elsewhere (e.g., activateEcotone). Bring the four startPrank/stopPrank
pairs in setFeeCurrencyDirectory and deployTestFeeCurrency in line.
The L2Configurator interface method took a bool, but the only caller
(sysgo.WithCelo) always passed true. Calling with false was a no-op and
confusing. Make the method nullary and unconditionally enable the flag,
matching how WithCustomGasToken signals enablement via its presence.
@palango palango marked this pull request as draft April 29, 2026 14:09
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: 5eeab6b6b5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread op-acceptance-tests/tests/celo/helpers.go Outdated
palango added 3 commits April 29, 2026 16:27
…inInput literals

The new DeployCeloContracts field on opcm.L2GenesisInput / DeployOPChainInput
broke two struct literals that exhaustruct flagged in CI: the L2 setup harness
in test/setup/Setup.sol and the interop genesis path in op-chain-ops/interopgen.
Setup.sol pipes the new field from DeployConfig (which already exposes it);
interopgen never deploys Celo contracts so it's hardwired to false.
…ndry.toml

CI runs forge build --deny-warnings --skip test (no --libraries) and produced
artifacts whose bytecode still had link placeholders, which then prevented
op-deployer from loading the L2Genesis script. Pin the library address in
foundry.toml so every forge build invocation links it automatically; drop
the now-redundant flag from the build-no-tests recipe.
…on-Celo devnets

eth_call against an address with no code returns empty bytes without an
RPC error, so the previous guard didn't trip on chains where the
FeeCurrencyDirectory predeploy is missing — the test would fall through
and fail on unrelated ABI decoding instead.

Skip when eth_getCode at the FeeCurrencyDirectory address returns empty,
and when getCurrencyConfig(testFeeCurrency) reports a zero oracle (the
default for unregistered tokens; the call returns a zero-valued struct
rather than reverting).

Caught by Codex on PR #440.
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 29, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.2%. Comparing base (954c2bf) to head (ae871db).

Additional details and impacted files
@@               Coverage Diff                @@
##           celo-rebase-17    #440     +/-   ##
================================================
- Coverage            76.2%   76.2%   -0.1%     
================================================
  Files                 591     591             
  Lines               74215   74215             
================================================
- Hits                56608   56585     -23     
- Misses              17463   17486     +23     
  Partials              144     144             
Flag Coverage Δ
cannon-go-tests-64 66.4% <ø> (ø)
unit 76.8% <ø> (-0.1%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

palango added 3 commits April 29, 2026 17:42
…ge-build recipe

Putting the library address in foundry.toml's libraries array also writes
it into artifact metadata in a format that breaks Go-side artifact loaders
(json: cannot unmarshal string into map[string]string). Set the link via
the --libraries CLI flag in the central forge-build justfile recipe instead,
so every CI invocation that goes through `just forge-build` picks it up
without affecting metadata.
forge test caught two more constructor mismatches: DeployOPChain.t.sol's
Types.DeployOPChainInput literal and L2Genesis.t.sol's L2Genesis.Input
literal. Both default the new field to false (these tests don't exercise
the Celo predeploy path).
The existing memory-all gateless job already discovers and runs the celo
gate alongside everything else, but adding a dedicated CI job makes celo
failures visible independently and lets us iterate on the gate without
sifting through the larger memory-all results.
@palango palango requested a review from piersy May 7, 2026 10:24
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.

2 participants