Skip to content

feat(ev-dev): part 5 – add interactive TUI dashboard#184

Open
randygrok wants to merge 116 commits into
mainfrom
feat/ev-dev-part5-tui
Open

feat(ev-dev): part 5 – add interactive TUI dashboard#184
randygrok wants to merge 116 commits into
mainfrom
feat/ev-dev-part5-tui

Conversation

@randygrok
Copy link
Copy Markdown
Contributor

@randygrok randygrok commented Mar 20, 2026

Description

Adds an interactive terminal UI dashboard to ev-dev with the --tui flag. The TUI displays real-time blocks, logs, and accounts panels using ratatui, with a custom tracing layer that captures log events in real-time. Plain log output remains the default behavior.

Type of Change

  • New feature (non-breaking change which adds functionality)

Testing

  • TUI renders blocks, logs, and accounts panels
  • Keyboard navigation works (Tab, arrows, q to quit)
  • Terminal properly restored on exit or panic
  • Plain log output mode (--tui off) unchanged

Summary by CodeRabbit

Release Notes

New Features

  • Added TUI mode (--tui) to ev-dev for live chain monitoring displaying blocks, logs, and accounts with keyboard navigation
  • Introduced genesis-mode deployment workflow with separate Genesis and Deploy subcommands in ev-deployer
  • Added Permit2 canonical address support for deterministic contract deployment via CREATE2

Documentation

  • Updated ev-deployer README with genesis/deploy workflow steps and contract deployment pipeline documentation
  • Enhanced ev-dev README with TUI usage guide, keyboard shortcuts, and live CREATE2 deployment instructions

Tests

  • Added e2e test validating standard Viem transfers estimate and execute correctly

Chores

  • Updated Reth and Alloy dependency versions to align with v2.2.0
  • Upgraded Tokio runtime to 1.52

Review Change Stack

randygrok and others added 27 commits March 13, 2026 14:30
…e patching

Add Hyperlane monorepo as git submodule pinned to @hyperlane-xyz/core@11.0.3.
Implement bytecode patching utility for Solidity immutable variables and
MerkleTreeHook genesis alloc generation with mailbox/localDomain/deployedBlock
patching. This is the foundation for embedding Hyperlane contracts at genesis.
The MerkleTreeHook bytecode test needs OpenZeppelin dependencies
from the Hyperlane monorepo, which are managed by soldeer.
Verify bytecode, storage slots, and patched immutables (mailbox,
localDomain, deployedBlock) for the MerkleTreeHook contract via RPC.
Rustdoc interprets [644], [578], and [32] as intra-doc links,
causing the docs CI job to fail with -D warnings.
Add Uniswap Permit2 as a genesis-deployable contract with EIP-712
immutable patching (_CACHED_CHAIN_ID, _CACHED_DOMAIN_SEPARATOR).
…racts

Add three Hyperlane core contracts to ev-deployer so the full messaging
stack can be embedded at genesis without post-deploy transactions.

- Mailbox: core messaging hub with localDomain/deployedBlock immutables
- NoopIsm: stateless ISM that accepts all messages (for devnet)
- ProtocolFee: post-dispatch hook with MAX_PROTOCOL_FEE immutable
…lean ci build

The embedded bytecodes were compiled with --extra-output storageLayout
which subtly altered the output. Regenerated from a clean ci profile
build to match what forge produces without extra flags.
- Expand CI workflow path triggers to include Cargo.toml, Cargo.lock,
  and the workflow file itself
- Fix README merge behavior description (not in-place)
- Validate contract addresses are unique in config
- Use trim_start_matches("0x") instead of strip_prefix().unwrap() in
  bytecode verification tests
- Add curl timeouts to e2e test RPC calls to prevent CI hangs
…s validation

Merge ev-deployer-part1-core into ev-deployer-merkle-tree-hook, resolving
config.rs conflicts by keeping both the MerkleTreeHook/ProtocolFee
validations and the new duplicate-address check between AdminProxy and
FeeVault.
- Add ev-deployer dependency to ev-dev
- Accept optional --deploy-config argument in ev-dev CLI
- Load deploy config and override chain ID if needed
- Display deployed contract addresses in dev chain banner
- Add just recipes for installing ev-dev and ev-deployer binaries
- Integrate ratatui for terminal UI with blocks, logs, and accounts panels
- Implement custom tracing layer to capture real-time log events
- Add keyboard navigation (Tab for panel switch, arrows for scroll, q to quit)
- Support coexistence of TUI and plain log output modes
- Add crossterm for terminal event handling
@randygrok randygrok requested a review from a team as a code owner March 20, 2026 08:33
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

This PR implements Permit2 canonical CREATE2 deployment, introduces a dual-mode (genesis/deploy) configuration system for ev-deployer, adds a complete terminal UI for ev-dev chain monitoring with live balance polling, upgrades workspace dependencies to reth v2.2.0, and refactors ev-reth executor traits to use references instead of clones.

Changes

Deployment and Development Tooling Enhancement

Layer / File(s) Summary
Workspace dependency upgrades
Cargo.toml
Bumps reth v2.1.0 → v2.2.0 with aligned alloy versions and tokio 1.51 → 1.52.
Permit2 canonical salt constant and CREATE2 validation
bin/ev-deployer/src/contracts/permit2.rs, bin/ev-deployer/src/deploy/mod.rs, bin/ev-deployer/src/deploy/create2.rs
Defines PERMIT2_CANONICAL_SALT constant from Uniswap deploy script, adds deployment module documentation, and adds unit test confirming the canonical salt produces the expected canonical address via CREATE2.
Deployment pipeline integration of canonical Permit2 salt
bin/ev-deployer/src/deploy/pipeline.rs
Pipeline now uses PERMIT2_CANONICAL_SALT for Permit2 CREATE2 address computation instead of deriving a generic salt; removes shared pipeline salt derivation.
InitMode enum and template generation for genesis/deploy split
bin/ev-deployer/src/init.rs
Introduces InitMode::Genesis and InitMode::Deploy to conditionally emit address fields and deterministic-deployer sections, enabling genesis-only contract address injection and deploy-mode CREATE2 configuration.
Init subcommand CLI restructuring
bin/ev-deployer/src/main.rs
Refactors Command::Init from flat flags to nested InitSubcommand enum; branches dispatch on Genesis vs Deploy subcommands with mode-specific defaults.
Init test suite refactoring for genesis and deploy modes
bin/ev-deployer/src/init.rs
Introduces LEGACY_GENESIS_TEMPLATE; updates existing genesis tests to set InitMode::Genesis explicitly; adds deploy-mode tests validating absence of address fields and deterministic deployer section.
Documentation and configuration updates for ev-deployer and ev-dev
bin/ev-deployer/README.md, bin/ev-deployer/src/init_template.toml, bin/ev-dev/README.md
Documents genesis/deploy workflows, canonical salt behavior, deployment pipeline steps, and new --genesis-config flag; adds live CREATE2 deployment workflow section and TUI mode documentation.
ev-dev dependencies and TUI main integration
bin/ev-dev/Cargo.toml, bin/ev-dev/src/main.rs
Adds Alloy provider/RPC/network crates and TUI dependencies (ratatui, crossterm, tracing-subscriber, arboard, futures); adds tui module, new CLI arguments, and refactors main flow with helper functions branching into TUI vs non-TUI execution paths.
ev-dev TUI app state, data models, and async polling
bin/ev-dev/src/tui/app.rs
Implements App state container with block/log/account/transaction data structures, panel navigation, clipboard operations, async block-detail fetching, and spawn_balance_poller for periodic balance updates via mpsc.
ev-dev TUI event handling and terminal lifecycle
bin/ev-dev/src/tui/events.rs, bin/ev-dev/src/tui/mod.rs
Implements keyboard event handling for panel navigation/scrolling/clipboard actions; defines TUI module structure with terminal lifecycle management, event loop, and panic hook restoration.
ev-dev TUI tracing layer and field collection
bin/ev-dev/src/tui/tracing_layer.rs
Implements custom tracing subscriber layer (TuiTracingLayer) that intercepts events, collects field key/value pairs, extracts messages, and forwards log entries non-blockingly through mpsc channel.
ev-dev TUI rendering and UI layout
bin/ev-dev/src/tui/ui.rs
Implements complete TUI rendering using ratatui with styling helpers, top-level orchestration, header/blocks/accounts/logs/footer sub-renders, block-detail popup, and context-sensitive help.
Genesis allocation for deterministic deployer
bin/ev-dev/assets/devnet-genesis.json
Adds genesis alloc for deterministic CREATE2 deployer contract at 0x4e59... enabling on-chain deployment during dev-chain operation.
ev-reth executor trait and factory refactoring
crates/node/src/evm_executor.rs
Changes commit_transaction return type to bare GasOutput; adds Send + 'static constraint on transaction envelope; refactors factory to pass references (&'a Spec, &'a R) instead of clones.
Logging format and E2E test coverage
crates/node/src/builder.rs, clients/tests/e2e/flows.e2e.test.ts
Updates debug logging to use named ?gas_used field; adds e2e test for Viem public/wallet client transfers with intrinsic gas validation on a defined test chain.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant EventHandler as handle_key
  participant App
  participant BalancePoller
  participant RPC as HTTP/RPC
  participant UI as TUI Renderer
  User->>EventHandler: keyboard input
  EventHandler->>App: navigate/scroll/fetch block
  BalancePoller->>RPC: poll balances
  RPC-->>BalancePoller: balances
  BalancePoller->>App: send balance update
  App->>App: drain logs/balances
  App->>UI: draw (blocks/logs/accounts/footer)
  UI-->>User: terminal display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • evstack/ev-reth#167: Modifies the same ev-deployer modules (Cargo.toml, README, src/main.rs) with overlapping initialization and deployment logic.
  • evstack/ev-reth#182: Directly related through PERMIT2_CANONICAL_SALT constant, CREATE2 address test, and canonical salt usage in the deployment pipeline.
  • evstack/ev-reth#223: Modifies the same crates/node/src/evm_executor.rs file with identical commit_transaction return type and executor factory wiring changes.

Suggested reviewers

  • damiannolan
  • tac0turtle

Poem

🐰 A rabbit hops through deploy salt and mode,
Genesis paths split where addresses flow,
The TUI gleams with balance and block,
Executor refs dance and factory flock,
Reth v2.2 runs the ev-dev show! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature added: an interactive TUI dashboard for ev-dev.
Description check ✅ Passed The description covers the main feature (interactive TUI), key components (panels, ratatui, custom tracing layer), behavior (real-time display), and testing details, but lacks specific related issue links and some sections from the template.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ev-dev-part5-tui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented Mar 20, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

randygrok and others added 23 commits March 31, 2026 14:25
Add Nick's CREATE2 factory (0x4e59b44...956c) to the devnet genesis
so that ev-deployer deploy works against ev-dev out of the box.

On post-merge chains the canonical keyless deployment transaction
cannot be replayed, so the runtime bytecode is embedded directly
in the genesis alloc.

Document the live deployment workflow in both ev-dev and ev-deployer
READMEs.
Inject Nick's CREATE2 factory (0x4e59b44847b379578588920ca78fbf26c0b4956c)
into genesis state so ev-deployer deploy works on post-merge chains
where the canonical keyless transaction cannot land. Genesis-only —
the deploy pipeline already validates its existence on-chain.
…dy:evstack/ev-reth into feat/ev-dev-part5-tui

# Conflicts:
#	bin/ev-deployer/src/deploy/pipeline.rs
#	bin/ev-deployer/src/init.rs
Deploy mode now uses the original Uniswap salt so Permit2 lands at its
canonical address (0x000000000022D473030F116dDEE9F6B43aC78BA3) instead
of a random one.
`ev-deployer init genesis` generates config with address fields for
genesis injection. `ev-deployer init deploy` generates config without
addresses (computed via CREATE2) and auto-includes the deterministic
deployer since it is required for deploy mode.
…late

The deterministic deployer cannot be deployed via CREATE2 (circular
dependency). The deploy pipeline already verifies it exists on-chain,
so including it in the deploy config template was misleading.
Reflect the init genesis/deploy split, canonical Permit2 CREATE2 salt,
and removal of deterministic deployer from deploy config.
The flag passes a config for embedding contracts into genesis, not for
deploying to a live chain. The old name was misleading after the init
genesis/deploy split.
…tion' into feat/ev-dev-part5-tui

# Conflicts:
#	Cargo.lock
Base automatically changed from ev-deployer-part4-ev-dev-integration to ev-deployer-part3a May 12, 2026 09:02
Base automatically changed from ev-deployer-part3a to main May 12, 2026 09:49
tac0turtle and others added 3 commits May 12, 2026 15:48
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.51.1 to 1.52.3.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](tokio-rs/tokio@tokio-1.51.1...tokio-1.52.3)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.52.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
bin/ev-dev/src/tui/app.rs (2)

246-246: 💤 Low value

Consider graceful error handling instead of expect.

While the RPC URL is constructed from CLI arguments and should always be valid, using expect can cause a panic in unexpected edge cases. Consider using unwrap_or_else with a fallback or early return to avoid panicking.

♻️ Suggested improvement
-            let provider =
-                ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"));
+            let Ok(url) = rpc_url.parse() else {
+                return;
+            };
+            let provider = ProviderBuilder::new().connect_http(url);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/ev-dev/src/tui/app.rs` at line 246, The call
ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"))
uses expect and may panic; replace this with proper Result handling for
rpc_url.parse() (e.g., rpc_url.parse().unwrap_or_else(|e| { log the error and
return/propagate Err }) or a match/if let) and ensure connect_http is only
called when parsing succeeds—update the surrounding function (where
ProviderBuilder::new().connect_http is invoked) to return an error or
early-return on parse failure instead of panicking.

339-339: 💤 Low value

Consider graceful error handling instead of expect.

Similar to the issue in fetch_block_detail, using expect on URL parsing could cause a panic. Consider handling the error gracefully.

♻️ Suggested improvement
-            let provider =
-                ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"));
+            let Ok(url) = rpc_url.parse() else {
+                break;
+            };
+            let provider = ProviderBuilder::new().connect_http(url);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/ev-dev/src/tui/app.rs` at line 339, The call using
ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"))
panics on invalid URLs; replace the expect with graceful error handling: parse
rpc_url with rpc_url.parse() and handle the Result (via match, ? propagation, or
map_err) to return or log a descriptive error instead of panicking, then pass
the successfully parsed URL into ProviderBuilder::new().connect_http; update the
surrounding function signature to return a Result if necessary to propagate the
error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/ev-deployer/src/deploy/pipeline.rs`:
- Around line 71-74: Add a pre-deploy guard that verifies any existing saved
Permit2 address in state matches the canonical computed address
(compute_address(permit2_salt, &initcode)) and fail fast if they differ so we
don't resume with a legacy non-canonical address; specifically, before deciding
to skip deployment based on status, load the stored Permit2 address from state,
compute the canonical address using permit2_salt and PERMIT2_INITCODE
(initcode), compare it to the stored address, and return an explicit error
instructing migration if they mismatch (apply the same check around the second
occurrence at the line 83 codepath).

---

Nitpick comments:
In `@bin/ev-dev/src/tui/app.rs`:
- Line 246: The call
ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"))
uses expect and may panic; replace this with proper Result handling for
rpc_url.parse() (e.g., rpc_url.parse().unwrap_or_else(|e| { log the error and
return/propagate Err }) or a match/if let) and ensure connect_http is only
called when parsing succeeds—update the surrounding function (where
ProviderBuilder::new().connect_http is invoked) to return an error or
early-return on parse failure instead of panicking.
- Line 339: The call using
ProviderBuilder::new().connect_http(rpc_url.parse().expect("valid RPC URL"))
panics on invalid URLs; replace the expect with graceful error handling: parse
rpc_url with rpc_url.parse() and handle the Result (via match, ? propagation, or
map_err) to return or log a descriptive error instead of panicking, then pass
the successfully parsed URL into ProviderBuilder::new().connect_http; update the
surrounding function signature to return a Result if necessary to propagate the
error.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1735bea3-e0e5-45ff-876b-1bb56409d2e4

📥 Commits

Reviewing files that changed from the base of the PR and between c93670a and 429fdfa.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • Cargo.toml
  • bin/ev-deployer/README.md
  • bin/ev-deployer/src/contracts/permit2.rs
  • bin/ev-deployer/src/deploy/create2.rs
  • bin/ev-deployer/src/deploy/mod.rs
  • bin/ev-deployer/src/deploy/pipeline.rs
  • bin/ev-deployer/src/init.rs
  • bin/ev-deployer/src/init_template.toml
  • bin/ev-deployer/src/main.rs
  • bin/ev-dev/Cargo.toml
  • bin/ev-dev/README.md
  • bin/ev-dev/assets/devnet-genesis.json
  • bin/ev-dev/src/main.rs
  • bin/ev-dev/src/tui/app.rs
  • bin/ev-dev/src/tui/events.rs
  • bin/ev-dev/src/tui/mod.rs
  • bin/ev-dev/src/tui/tracing_layer.rs
  • bin/ev-dev/src/tui/ui.rs
  • clients/tests/e2e/flows.e2e.test.ts
  • crates/node/src/builder.rs
  • crates/node/src/evm_executor.rs

Comment on lines +71 to 74
let permit2_salt = contracts::permit2::PERMIT2_CANONICAL_SALT;
let initcode = contracts::permit2::PERMIT2_INITCODE.to_vec();
let address = compute_address(salt, &initcode);
let address = compute_address(permit2_salt, &initcode);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce canonical Permit2 address when resuming from existing state.

Line 71 makes Permit2 canonical, but a pre-existing state file can still skip deployment based only on status and retain a legacy non-canonical address. Add a mismatch guard before deploy to fail fast and force explicit migration.

Suggested fix
         let permit2_salt = contracts::permit2::PERMIT2_CANONICAL_SALT;
         let initcode = contracts::permit2::PERMIT2_INITCODE.to_vec();
         let address = compute_address(permit2_salt, &initcode);
+        if let Some(existing) = state.contracts.permit2.as_ref() {
+            if existing.status >= ContractStatus::Deployed && existing.address != address {
+                eyre::bail!(
+                    "state permit2 address mismatch: state={}, canonical={}. \
+                     migrate or delete the state file before resuming",
+                    existing.address,
+                    address
+                );
+            }
+        }

         let expected_runtime = contracts::permit2::expected_runtime_bytecode(chain_id, address);

Also applies to: 83-83

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/ev-deployer/src/deploy/pipeline.rs` around lines 71 - 74, Add a
pre-deploy guard that verifies any existing saved Permit2 address in state
matches the canonical computed address (compute_address(permit2_salt,
&initcode)) and fail fast if they differ so we don't resume with a legacy
non-canonical address; specifically, before deciding to skip deployment based on
status, load the stored Permit2 address from state, compute the canonical
address using permit2_salt and PERMIT2_INITCODE (initcode), compare it to the
stored address, and return an explicit error instructing migration if they
mismatch (apply the same check around the second occurrence at the line 83
codepath).

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.

3 participants