Skip to content

feat(exit-certificate): subtract genesis preload from EOA balances in Step B#1654

Open
joanestebanr wants to merge 1 commit into
feature/exit-certificate-toolfrom
feature/exit_certificate_tool-genesis_preload-pm345
Open

feat(exit-certificate): subtract genesis preload from EOA balances in Step B#1654
joanestebanr wants to merge 1 commit into
feature/exit-certificate-toolfrom
feature/exit_certificate_tool-genesis_preload-pm345

Conversation

@joanestebanr

Copy link
Copy Markdown
Collaborator

🔄 Changes Summary

  • Step B1 now always subtracts each EOA's genesis (block 0) ETH balance from its live balance, so genesis-preloaded ETH (never bridged in) no longer inflates the certificate.
  • checkGenesisBalances returns the set of EOAs preloaded at genesis (no longer aborts on any non-zero genesis balance); keeps the diagnostic breakdown, reserves its error for RPC failures.
  • New subtractGenesisBalances removes the genesis amount per EOA, drops EOAs reduced to 0, and caps to 0 + flags any EOA whose genesis balance exceeds its live balance.
  • Subtraction runs before building EOA balances/accumulations, so all outputs are genesis-adjusted.

⚠️ Breaking Changes

  • 🔌 Behavior: ignoreGenesisBalance semantics changed. It no longer gates "any genesis preload aborts"; the preload is always subtracted, and the flag now only governs the case where the genesis balance exceeds the live balance (abort by default vs cap-to-0-and-continue).

📋 Config Updates

  • 🧾 No new config keys. options.ignoreGenesisBalance (default false) meaning updated as above.

✅ Testing

  • 🤖 Automatic: go test ./tools/exit_certificate/... (added TestSubtractGenesisBalances, TestRunStepB1GenesisPreloadSubtracted, TestRunStepB1GenesisPreloadExceedsBalanceAborts). golangci-lint run ./tools/exit_certificate/... → 0 issues.

🐞 Issues

  • Related: agglayer/pm#345

📝 Notes

  • Targets the integration branch feature/exit-certificate-tool (the tool is not yet in develop).

🤖 Generated with Claude Code

… Step B

Step B1 now always subtracts each EOA's genesis (block 0) ETH balance from
its live balance, since genesis-preloaded ETH was never bridged in and would
otherwise inflate the exit certificate totals.

- checkGenesisBalances now returns the set of EOAs preloaded at genesis
  (instead of aborting on any non-zero genesis balance); the diagnostic
  breakdown is kept and the error is reserved for RPC failures.
- New subtractGenesisBalances removes the genesis amount from each live
  balance, dropping EOAs reduced to 0. When the genesis balance exceeds the
  live balance the EOA is capped to 0 and flagged.
- ignoreGenesisBalance now only governs the negative case: abort by default,
  or cap-to-0-and-continue with a warning when true.
- Subtraction runs before building EOA balances/accumulations so outputs are
  genesis-adjusted.

Updated unit tests (subtractGenesisBalances + Step B1 subtract/abort paths)
and aligned CLAUDE.md/README.md docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude

claude Bot commented Jun 15, 2026

Copy link
Copy Markdown

Claude finished @joanestebanr's task in 2m 9s —— View job


📋 Review Complete ✅


🚀 What's New

This PR changes the genesis preload handling logic from an abort-on-detect guard to an always-subtract adjustment. Previously, any non-zero genesis (block 0) ETH balance would abort Step B1. Now the genesis balance is always subtracted from each EOA's live balance (since preloaded ETH was never bridged in), and ignoreGenesisBalance only controls what happens when the subtraction would go negative.

🐛 Bug Fixes

None - this is a feature change, not a bug fix.

📋 Config Updates

options.ignoreGenesisBalance (default false) - semantics changed:

  • Before: Controls whether any genesis preload aborts (false = abort, true = warn and continue)
  • After: Controls behavior when genesis balance exceeds live balance (false = abort, true = cap to 0 and continue)
  • The subtraction now always happens regardless of this flag's value

⚠️ Breaking Changes

🔴 CRITICAL - Behavioral change: ignoreGenesisBalance semantics have fundamentally changed. Any deployment relying on the old "abort on any genesis preload" behavior will now silently subtract genesis balances instead. This is a breaking change that could affect certificate accuracy if not understood.


🔍 Detailed Code Review

🟢 Strengths

  1. Well-designed separation of concerns: checkGenesisBalances fetches and logs, subtractGenesisBalances mutates - clean functional split
  2. Excellent test coverage: Three new tests cover the happy path (subtraction), edge case (genesis exceeds live), and unit test for the subtraction logic itself
  3. Proper in-place mutation: subtractGenesisBalances correctly removes EOAs dropped to 0, maintaining the fetchETHBalances invariant (only non-zero balances in map)
  4. Clear documentation: Both README and CLAUDE.md accurately describe the new behavior with examples
  5. Good error handling: Distinguishes between RPC failures (errors returned) and negative balances (list of errored EOAs returned for caller to decide policy)

🔴 Critical Issues

Logging mismatch in diagnostic output (tools/exit_certificate/step_b.go:185-191)

The summary log says "Total genesis subtraction" but actually shows the remaining balance after subtraction, not the amount being subtracted:

diffStr := new(big.Int).Sub(totalBalance, sumBalances(genesisBalances)).String()
// ...
log.Infof("Total genesis subtraction: %s wei (%d accounts)", padLeft(diffStr, maxLen), len(eoaEthBalances))

Where:

  • totalBalance = EOA ETH + Contract ETH at target block
  • genesisBalances = Genesis preload amounts
  • diffStr = totalBalance - genesisBalances = Result after subtraction (not the amount subtracted)

Problem: The label "Total genesis subtraction" implies it's showing how much was subtracted (genesisSumStr), but it's actually showing what remains.

Recommended fix: Either:

  1. Change the label to match the value: "Total after subtraction" or "Net balance after genesis removal"
  2. Change the value to match the label: Show genesisSumStr (the actual amount subtracted)

Preferred: Option 2 is clearer and more useful for operators - show the amount being removed:

log.Infof("Total genesis subtracted : %s wei (%d accounts)", padLeft(genesisSumStr, maxLen), len(genesisBalances))

This would give operators a clear breakdown:

Genesis ETH preload total: X wei
Total EOA ETH            : Y wei
Total contract ETH       : Z wei
Total genesis subtracted : X wei  (= amount removed from EOA balances)

🟡 Suggestions for Improvement

1. Edge case documentation (tools/exit_certificate/step_b.go:210-213)

When an EOA has a genesis balance but no live balance, the code correctly handles it but could be clearer:

live := eoaEthBalances[addr]
if live == nil {
    live = new(big.Int)  // Implicitly 0, will result in negative subtraction
}

Suggestion: Add a brief comment explaining this represents the "genesis preload but spent everything" case:

live := eoaEthBalances[addr]
if live == nil {
    // EOA had genesis preload but has 0 balance at target block (spent or never bridged)
    live = new(big.Int)
}

2. Breaking change documentation (documentation files)

The breaking change is mentioned in the PR description but could be more prominent in the documentation for existing users upgrading to this version.

Suggestion: Consider adding a note in the config documentation or a migration guide section for users who were relying on the old abort-on-any-genesis behavior.

3. Minor: Emoji usage (tools/exit_certificate/step_b.go:179)

log.Infof("🚨🚨🚨 Genesis ETH preload detected for %s: %s wei", addr.Hex(), bal.String())

The triple emoji is attention-grabbing but may be excessive. Consider a single emoji or standard logging format for consistency with the rest of the codebase.

🟢 Security Review

No security vulnerabilities identified

  • ✅ Integer overflow: Uses big.Int throughout, safe from overflow
  • ✅ Nil pointer derefs: Properly handles nil live balances (line 211-213)
  • ✅ Map manipulation: Correctly deletes zero-balance entries, no memory leaks
  • ✅ RPC injection: No user input flows to RPC calls, all addresses are from prior steps
  • ✅ Authorization: Config flag controls behavior appropriately

🟢 Performance Review

Performance impact is acceptable

  • One additional RPC batch call to fetch genesis balances - acceptable overhead for correctness
  • In-place map mutation avoids allocations
  • No algorithmic complexity changes (still O(n))
  • Batch RPC infrastructure handles concurrent requests efficiently

✅ Test Coverage

Unit tests added (tools/exit_certificate/step_b_helpers_test.go):

  • TestSubtractGenesisBalances: Tests the core subtraction logic with 4 scenarios
  • TestRunStepB1GenesisPreloadSubtracted: Integration test for normal subtraction (100 - 40 = 60)
  • TestRunStepB1GenesisPreloadExceedsBalanceAborts: Tests both abort (default) and cap-to-0 (with flag) behaviors

Coverage assessment: ✅ Excellent - all critical paths and edge cases are tested


📝 Summary

Overall assessment: This is a well-implemented feature with solid logic, good test coverage, and clear documentation. The core algorithm correctly handles all edge cases including EOAs with no live balance, exact matches, and negative results.

Action items:

🔴 Required: Fix the logging mismatch at step_b.go:185-191

  • The "Total genesis subtraction" log line should show genesisSumStr (amount subtracted), not diffStr (remaining balance)

🟡 Recommended:

  • Add clarifying comment for the nil live balance case at line 211
  • Emphasize the breaking change more clearly for existing users
  • Consider toning down the emoji usage

Approval recommendation: This PR can be merged after fixing the logging issue. The breaking change is intentional and well-documented in the PR description. The implementation is correct and the test coverage is comprehensive.


@joanestebanr joanestebanr self-assigned this Jun 15, 2026

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

Copy link
Copy Markdown

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: 22a92f2fff

ℹ️ 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".

if live == nil {
live = new(big.Int)
}
remaining := new(big.Int).Sub(live, genesisBal)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve bridged ETH recipients when subtracting preloads

When a prefunded EOA has moved any of its block-0 ETH to another EOA and later receives a real L1→L2 native deposit, this subtracts the entire genesis balance from that EOA's current balance based only on the address. In a case like genesis A=100, A sends 100 to B, then A receives a 100 native bridge deposit, remaining is non-negative so Step B does not abort, but the certificate drops A's legitimate bridged funds while B still receives the genesis-funded balance; Step C can also reclassify the missing native total as SC-locked to exitAddress. Keep aborting on genesis preloads or account for value flows before doing per-address subtraction.

Useful? React with 👍 / 👎.

@sonarqubecloud

Copy link
Copy Markdown

joanestebanr added a commit that referenced this pull request Jun 25, 2026
…and CI workflow (#1655)

## 🔄 Changes Summary
- Add the **exit-certificate E2E test harness** under
`test/e2e-exit_certificate/`: spins up a Kurtosis CDK network
(`run_network.sh`), prepares a settled certificate
(`prepare_network.sh`), and runs the `exit_certificate` tool
(`run_exit_tool.sh`), orchestrated by `run_e2e_test.sh` (+ `helper.sh`,
`single_op_pessimistic_args.json`, `README.md`).
- Add supporting scripts under `tools/exit_certificate/scripts/`:
  - `bridge_l2_to_l1.sh` — L2→L1 bridge to produce a certificate.
- `agglayer_certificate_status.sh` — poll/wait for certificate
settlement.
  - `export_kurtosis_env.sh` — export Kurtosis env vars for the scripts.
  - `agglayer_status/main.go` — agglayer status helper.
  - Update `bridge_l1_to_l2.sh`.
- Add CI workflow
`.github/workflows/test-e2e-exit_cenrtificate_tool.yml` that runs the
E2E test on PRs touching `tools/exit_certificate/**` (also
`workflow_dispatch`).

## ⚠️ Breaking Changes
- None.

## 📋 Config Updates
- None.

## ✅ Testing
- 🤖 **Automatic**: New GitHub Actions workflow runs
`test/e2e-exit_certificate/run_e2e_test.sh` on PRs that modify
`tools/exit_certificate/**` (Kurtosis CDK network → settled certificate
→ exit_certificate tool).
- 🖱️ **Manual**: `./test/e2e-exit_certificate/run_e2e_test.sh` locally
(requires Docker, Kurtosis CLI, Foundry `cast`, Go).

## 🐞 Issues
- N/A

## 🔗 Related PRs
- #1654 (feat: subtract genesis preload from EOA balances in Step B)

## 📝 Notes
- The E2E harness clones a pinned Kurtosis CDK commit and manages its
own `aggkit` enclave; the workflow stops the enclave on completion.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant