Skip to content

Comments

feat: Implement Forest.EthTraceCall#6412

Draft
akaladarshi wants to merge 21 commits intomainfrom
akaladarshi/eth-trace-call
Draft

feat: Implement Forest.EthTraceCall#6412
akaladarshi wants to merge 21 commits intomainfrom
akaladarshi/eth-trace-call

Conversation

@akaladarshi
Copy link
Collaborator

@akaladarshi akaladarshi commented Jan 13, 2026

Summary of changes

Changes introduced in this pull request:

Reference issue to close (if applicable)

Closes #6185

Other information and links

Change checklist

  • I have performed a self-review of my own code,
  • I have made corresponding changes to the documentation. All new code adheres to the team's documentation standards,
  • I have added tests that prove my fix is effective or that my feature works (if possible),
  • I have made sure the CHANGELOG is up-to-date. All user-facing changes should be reflected in this document.

Summary by CodeRabbit

  • New Features

    • Implemented EthTraceCall RPC method for detailed transaction tracing
    • Added state diff support to show account and storage changes during transaction execution
  • Documentation

    • Added developer guide for trace_call implementation and testing workflows
    • Added user documentation for trace_call RPC method with usage examples and troubleshooting
  • Tests

    • Added integration test harness for validating trace outputs
    • Added Tracer contract for EVM behavior testing

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR implements the trace_call RPC method for Forest with support for trace types and state diffs. It introduces new types for trace results and state diffs, adds state diff computation logic, includes an integration test suite with a test contract, and provides comprehensive documentation for users and developers.

Changes

Cohort / File(s) Summary
Core RPC Implementation
src/rpc/methods/eth.rs, src/rpc/mod.rs
Added public EthTraceCall RPC method with EthTraceType and EthTraceResults types; pre/post state computation, trace generation, and conditional state-diff output; updated call paths to use non-clone semantics for block_hash.
Type System & State Diffs
src/rpc/methods/eth/types.rs
Introduced Delta, ChangedType, AccountDiff, and StateDiff types for tracking account/storage changes; updated EthAddress and EthHash to derive PartialOrd, Ord, Copy; changed to_filecoin_address signature from &self to self.
State Diff Computation
src/rpc/methods/eth/trace.rs
Implemented build_state_diff with storage-diff integration using KAMT; added helpers for pre/post actor comparison, EVM storage diffing, and storage entry extraction; replaced env.caller.clone() with direct usage.
State Manager Updates
src/state_manager/mod.rs
Updated apply_on_state_with_gas and call_with_gas to accept flush: bool parameter; returns now include optional state root Cid via vm.flush() when requested.
EVM Accessors
src/shim/actors/builtin/evm/mod.rs
Added three new accessor methods: bytecode(), bytecode_hash(), contract_state() to expose EVM state data.
Clone Optimizations
src/rpc/methods/eth/filter/mod.rs, src/tool/subcommands/api_cmd/api_compare_tests.rs, src/tool/subcommands/api_cmd/stateful_tests.rs
Replaced block_hash and address clones with direct values; adjusted test setups to use moved values instead of clones.
Dependencies & Configuration
Cargo.toml
Added fvm_ipld_kamt = "0.4.5" dependency for KAMT-based storage diffing.
Gas Estimation Update
src/rpc/methods/gas.rs
Updated call_with_gas invocation to handle new return signature with two additional unused values and extra boolean argument.
Testing & Test Contracts
scripts/tests/trace_call_integration_test.sh, src/tool/subcommands/api_cmd/contracts/tracer/Tracer.sol, src/tool/subcommands/api_cmd/test_snapshots_ignored.txt
Added comprehensive bash integration test script with Forest/Anvil trace comparison; created Tracer contract with storage, ETH flow, and trace-testing functions; updated snapshot ignore list.
Documentation
docs/docs/developers/guides/trace_call_guide.md, docs/docs/users/knowledge_base/RPC/trace_call.md, docs/dictionary.txt
Added developer guide covering Tracer.sol usage and integration testing; added user guide detailing trace_call RPC method, request/response formats, and examples; expanded dictionary with blockchain terminology.

Sequence Diagram

sequenceDiagram
    participant Client
    participant RPC as RPC Handler<br/>(trace_call)
    participant StateManager
    participant VM
    participant Store as Blockstore
    
    Client->>RPC: call trace_call(message, traceTypes, blockTag)
    RPC->>StateManager: Get state for blockTag
    StateManager->>Store: Load pre-execution state
    Store-->>StateManager: pre_state
    
    RPC->>VM: Invoke transaction with gas
    VM->>VM: Execute message
    VM-->>RPC: execution result, gas usage
    
    RPC->>StateManager: Get post-execution state
    StateManager->>Store: Load post-execution state
    Store-->>StateManager: post_state
    
    alt if StateDiff requested
        RPC->>RPC: build_state_diff(pre_state, post_state, touched_addresses)
        RPC->>Store: Extract EVM storage entries
        Store-->>RPC: storage map
        RPC->>RPC: Compute Delta for each slot
        RPC->>RPC: Compare balance, nonce, code
    end
    
    RPC->>RPC: Format traces and state diffs
    RPC-->>Client: EthTraceResults (traces + optional stateDiff)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • LesnyRumcajs
  • sudo-shashank
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Out of Scope Changes check ❓ Inconclusive While most changes directly support trace_call implementation, several modifications appear tangential: Cargo.toml dependency (fvm_ipld_kamt), dictionary entries, and numerous clone-to-value refactorings lack clear justification. Clarify the necessity of the fvm_ipld_kamt dependency, dictionary additions, and whether clone removal changes are essential vs. optimization-only.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Implement Forest.EthTraceCall' clearly and specifically describes the main change—implementing the EthTraceCall RPC method.
Linked Issues check ✅ Passed The PR implements the trace_call RPC method, includes comprehensive tests (integration test script, extended test coverage in eth.rs and trace.rs), and adds extensive documentation (trace_call.md, trace_call_guide.md).

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch akaladarshi/eth-trace-call

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

@akaladarshi akaladarshi changed the title feat: Implement Forest.EthTraceCall feat: Implement Forest.EthTraceCall [Skip CI] Jan 13, 2026
@akaladarshi akaladarshi force-pushed the akaladarshi/eth-trace-call branch 2 times, most recently from 701d095 to a69d125 Compare January 29, 2026 15:28
@akaladarshi akaladarshi force-pushed the akaladarshi/eth-trace-call branch from a69d125 to 47b7aa3 Compare February 3, 2026 14:16
@LesnyRumcajs
Copy link
Member

As discussed, let's have a short guide in docs outlining its usage. This is important in light of various formats used across the Ethereum ecosystem for the output of this method.

Copy link
Member

Choose a reason for hiding this comment

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

The developers guides are intended for Forest maintainers. This should be in user space. Looking briefly, it's less of a guide more of a something to add to the knowledge base section (perhaps in a /RPC subsection). It's fine but let's rename the title to something along the lines of trace_call usage.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, going through it all I think it should be split into two parts:

  1. User guide/knowledge post with basic usage info.
  2. Dev guide with all of the Anvil stuff and comparing things.

All in all, solid piece of work, seems to work fine.

Copy link
Contributor

@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.

🧹 Nitpick comments (1)
src/rpc/methods/eth.rs (1)

3944-3952: Add documentation for EthTraceCall.

The public EthTraceCall enum lacks a doc comment. Per coding guidelines, all public types should have documentation describing their purpose and usage.

+/// Executes a call and returns traces for the transaction.
+/// 
+/// Supports both call traces (showing the hierarchy of calls) and state diffs
+/// (showing account state changes) based on the requested trace types.
 pub enum EthTraceCall {}

As per coding guidelines: "Document all public functions and structs with doc comments".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rpc/methods/eth.rs` around lines 3944 - 3952, Add a doc comment for the
public enum EthTraceCall explaining its purpose and usage: describe that
EthTraceCall implements RpcMethod for the "Forest.EthTraceCall"/"trace_call" RPC
to return transaction traces, summarize expected parameters (tx, traceTypes,
blockParam), note permissions (Permission::Read) and any important
behavior/return expectations; place the documentation immediately above the `pub
enum EthTraceCall {}` declaration so it appears in generated docs and IDE
tooltips.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/rpc/methods/eth.rs`:
- Around line 474-501: Add concise top-level documentation comments for the
public enum EthTraceType and the public struct EthTraceResults: place
triple-slash doc comments (///) immediately above the EthTraceType enum
describing its purpose (to select the trace output format returned by
eth_call/trace APIs, e.g., structured call traces vs state diffs) and
immediately above EthTraceResults describing that it encapsulates the returned
trace output (output bytes, optional state_diff, and trace vector) and when each
field is populated; keep wording brief and public-facing so crate docs surface
the intent of these types.
- Around line 3993-3996: The current code silently converts payload decode
failures into None by calling get_trace_output(&msg, &invoke_result) when
building EthTraceResults, which hides decode errors; change get_trace_output to
return a Result<Option<T>, E> (or otherwise surface the decode_payload error)
and update the EthTraceResults construction site to propagate that error instead
of swallowing it—i.e., call get_trace_output(...)? (or handle Err by returning
the error) so decode_payload failures are returned to the eth_call client rather
than converted into null output.
- Around line 4035-4049: The get_trace_output function currently swallows decode
errors by calling decode_payload(...).ok(); change get_trace_output (and its
callers) to return Result<Option<EthBytes>, RpcError> (or the project’s standard
error type) instead of Option<EthBytes>, replace the final .ok() with a
?-propagated decode_payload(...) call so failure returns an Err with the decode
error, and ensure callers of get_trace_output (and any upstream RPC response
builders) are updated to handle and surface that Err to clients; reference
get_trace_output, decode_payload, ApiInvocResult, and EthBytes when making these
changes.

---

Nitpick comments:
In `@src/rpc/methods/eth.rs`:
- Around line 3944-3952: Add a doc comment for the public enum EthTraceCall
explaining its purpose and usage: describe that EthTraceCall implements
RpcMethod for the "Forest.EthTraceCall"/"trace_call" RPC to return transaction
traces, summarize expected parameters (tx, traceTypes, blockParam), note
permissions (Permission::Read) and any important behavior/return expectations;
place the documentation immediately above the `pub enum EthTraceCall {}`
declaration so it appears in generated docs and IDE tooltips.

@akaladarshi akaladarshi changed the title feat: Implement Forest.EthTraceCall [Skip CI] feat: Implement Forest.EthTraceCall Feb 19, 2026
@codecov
Copy link

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 79.65180% with 187 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.41%. Comparing base (a9f8cf1) to head (ee300c5).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/rpc/methods/eth/trace.rs 81.19% 84 Missing and 23 partials ⚠️
src/rpc/methods/eth.rs 64.59% 73 Missing and 1 partial ⚠️
src/shim/actors/builtin/evm/mod.rs 66.66% 3 Missing ⚠️
src/state_manager/mod.rs 93.54% 0 Missing and 2 partials ⚠️
src/rpc/methods/eth/filter/mod.rs 92.30% 1 Missing ⚠️
Additional details and impacted files
Files with missing lines Coverage Δ
src/rpc/methods/eth/types.rs 75.56% <100.00%> (+7.42%) ⬆️
src/rpc/methods/gas.rs 86.87% <100.00%> (+0.03%) ⬆️
src/rpc/mod.rs 87.59% <ø> (-0.78%) ⬇️
src/rpc/methods/eth/filter/mod.rs 87.61% <92.30%> (-0.13%) ⬇️
src/state_manager/mod.rs 69.76% <93.54%> (+0.21%) ⬆️
src/shim/actors/builtin/evm/mod.rs 82.85% <66.66%> (+5.93%) ⬆️
src/rpc/methods/eth.rs 69.24% <64.59%> (-0.42%) ⬇️
src/rpc/methods/eth/trace.rs 75.87% <81.19%> (+8.63%) ⬆️

... and 11 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a9f8cf1...ee300c5. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@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.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/rpc/methods/eth.rs`:
- Around line 3988-4077: The output decoding in trace_call currently swallows
decode errors via decode_payload(...).ok(); locate get_trace_output (and the
other occurrence around the 4079–4093 area) and replace the .ok() usage so the
decode error is propagated instead: call decode_payload(...) and use ? to return
the error, adding context with .context("failed to decode return data for
trace_call") (or an equivalent descriptive message) so the error flows back
through the existing Result/ServerError handling rather than producing a null
output.

@sudo-shashank
Copy link
Contributor

sudo-shashank commented Feb 19, 2026

@akaladarshi enable RPC check on this PR and resolve all comments

@akaladarshi akaladarshi added the RPC requires calibnet RPC checks to run on CI label Feb 19, 2026
sudo-shashank
sudo-shashank previously approved these changes Feb 20, 2026
Copy link
Contributor

@sudo-shashank sudo-shashank left a comment

Choose a reason for hiding this comment

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

LGTM

Copy link
Member

Choose a reason for hiding this comment

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

I can imagine more methods being explained; let's put it into an knowledge base/RPC directory.

Copy link
Member

Choose a reason for hiding this comment

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

some of the entries don't look like something that should be added; if it's a variable name or a method name, it should be done inline within the doc.

}

impl EthAddress {
#[allow(clippy::wrong_self_convention)]
Copy link
Member

Choose a reason for hiding this comment

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

why?

Copy link
Collaborator Author

@akaladarshi akaladarshi Feb 20, 2026

Choose a reason for hiding this comment

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

The linter is because of this, not sure why now and not on main branch.

[lint:clippy] error: methods with the following characteristics: (to_* and self type is Copy) usually take self by value
[lint:clippy] --> src/rpc/methods/eth/types.rs:134:32
[lint:clippy] |
[lint:clippy] 134 | pub fn to_filecoin_address(&self) -> anyhow::Result {
[lint:clippy] | ^^^^^
[lint:clippy] |
[lint:clippy] = help: consider choosing a less ambiguous name
[lint:clippy] = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.93.0/index.html#wrong_self_convention
[lint:clippy] = note: -D clippy::wrong-self-convention implied by -D warnings
[lint:clippy] = help: to override -D warnings add #[allow(clippy::wrong_self_convention)]
[lint:clippy]

Some(ts.clone()),
trace_config,
StateLookupPolicy::Enabled,
false,
Copy link
Member

Choose a reason for hiding this comment

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

I recommend not using booleans as parameters unless it's obvious what they do. When you're reading call_with_gas (and don't recall what's the method signature), you probably can imagine what StateLookupPolicy::Enabled signifies; on the other hand, false doesn't tell you anything.

I'd move it to an enumeration.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I total agree here, that's why I suggested to go with the boolean for now as I am going to raise a subsequent PR to refactor the state_manager in that we will be having more structure to the methods and there i can add this as well, WDYT?

Copy link
Contributor

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/rpc/methods/eth/types.rs`:
- Around line 785-844: Add doc comments to all public helpers related to diffs:
document Delta::from_comparison (describe that it compares old/new Option<T> and
returns Added/Removed/Changed/Unchanged), Delta::is_unchanged (explain it
returns true only for Delta::Unchanged), AccountDiff::is_unchanged (explain it
tests all fields and empty storage), StateDiff::new (note it constructs an empty
StateDiff) and StateDiff::insert_if_changed (state that it inserts the
AccountDiff only when not unchanged). Use /// style comments placed immediately
above each function/struct definition, briefly describing inputs, behavior, and
any important side-effects (e.g., that insert_if_changed skips unchanged diffs).

---

Duplicate comments:
In `@src/rpc/methods/eth.rs`:
- Around line 474-501: Add top-level doc comments for the public trace types:
place concise /// doc comments above the EthTraceType enum describing it
represents the two supported trace output modes (structured call graph "Trace"
and state changes "StateDiff") and above the EthTraceResults struct describing
it represents the combined trace result (output bytes, optional state_diff, and
call trace Vec<EthTrace>) so consumers can discover purpose and fields; keep
existing variant/field docs and serde/lifecycle attributes
(lotus_json_with_self!, #[serde(...)] and derives) intact and ensure comments
follow crate style (brief summary sentence and any important usage notes).
- Around line 4078-4091: In get_trace_output, stop swallowing decode errors by
removing the final .ok(); change the function to propagate decode failures
(e.g., change the signature from fn get_trace_output(...) -> Option<EthBytes> to
return Result<Option<EthBytes>, E>), replace decode_payload(&return_data,
CBOR).ok() with a fallible propagation (e.g., decode_payload(&return_data,
CBOR)? wrapped to produce Ok(Some(...))), and convert the early default-return
branches to Ok(Some(EthBytes::default())) or Ok(None) as appropriate so callers
of get_trace_output (and types like ApiInvocResult and EthBytes) must handle
decoding errors instead of silently receiving None.

Comment on lines +785 to +844
impl<T: PartialEq> Delta<T> {
pub fn from_comparison(old: Option<T>, new: Option<T>) -> Self {
match (old, new) {
(None, None) => Delta::Unchanged,
(None, Some(new_val)) => Delta::Added(new_val),
(Some(old_val), None) => Delta::Removed(old_val),
(Some(old_val), Some(new_val)) => {
if old_val == new_val {
Delta::Unchanged
} else {
Delta::Changed(ChangedType {
from: old_val,
to: new_val,
})
}
}
}
}

pub fn is_unchanged(&self) -> bool {
matches!(self, Delta::Unchanged)
}
}

/// Account state diff after transaction execution.
/// Tracks changes to balance, nonce, code, and storage.
// Taken from https://github.com/alloy-rs/alloy/blob/v1.5.2/crates/rpc-types-trace/src/parity.rs#L156
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AccountDiff {
pub balance: Delta<EthBigInt>,
pub code: Delta<EthBytes>,
pub nonce: Delta<EthUint64>,
/// All touched/changed storage values (key -> delta)
pub storage: BTreeMap<EthHash, Delta<EthHash>>,
}

impl AccountDiff {
pub fn is_unchanged(&self) -> bool {
self.balance.is_unchanged()
&& self.code.is_unchanged()
&& self.nonce.is_unchanged()
&& self.storage.is_empty()
}
}

/// State diff containing all account changes from a transaction.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct StateDiff(pub BTreeMap<EthAddress, AccountDiff>);

impl StateDiff {
pub fn new() -> Self {
Self(BTreeMap::new())
}

pub fn insert_if_changed(&mut self, addr: EthAddress, diff: AccountDiff) {
if !diff.is_unchanged() {
self.0.insert(addr, diff);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add doc comments for public diff helpers.
Public helper methods were added without doc comments.

✍️ Suggested doc comments
 impl<T: PartialEq> Delta<T> {
+    /// Build a delta from optional old/new values.
     pub fn from_comparison(old: Option<T>, new: Option<T>) -> Self {
@@
-    pub fn is_unchanged(&self) -> bool {
+    /// Returns true when the delta represents no change.
+    pub fn is_unchanged(&self) -> bool {
         matches!(self, Delta::Unchanged)
     }
 }
@@
 impl AccountDiff {
+    /// Returns true when all tracked fields are unchanged and storage is empty.
     pub fn is_unchanged(&self) -> bool {
         self.balance.is_unchanged()
@@
 impl StateDiff {
+    /// Create an empty state diff.
     pub fn new() -> Self {
         Self(BTreeMap::new())
     }
 
+    /// Insert the account diff only when it contains changes.
     pub fn insert_if_changed(&mut self, addr: EthAddress, diff: AccountDiff) {
         if !diff.is_unchanged() {
             self.0.insert(addr, diff);
         }
     }
 }

As per coding guidelines: "Document all public functions and structs with doc comments".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
impl<T: PartialEq> Delta<T> {
pub fn from_comparison(old: Option<T>, new: Option<T>) -> Self {
match (old, new) {
(None, None) => Delta::Unchanged,
(None, Some(new_val)) => Delta::Added(new_val),
(Some(old_val), None) => Delta::Removed(old_val),
(Some(old_val), Some(new_val)) => {
if old_val == new_val {
Delta::Unchanged
} else {
Delta::Changed(ChangedType {
from: old_val,
to: new_val,
})
}
}
}
}
pub fn is_unchanged(&self) -> bool {
matches!(self, Delta::Unchanged)
}
}
/// Account state diff after transaction execution.
/// Tracks changes to balance, nonce, code, and storage.
// Taken from https://github.com/alloy-rs/alloy/blob/v1.5.2/crates/rpc-types-trace/src/parity.rs#L156
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AccountDiff {
pub balance: Delta<EthBigInt>,
pub code: Delta<EthBytes>,
pub nonce: Delta<EthUint64>,
/// All touched/changed storage values (key -> delta)
pub storage: BTreeMap<EthHash, Delta<EthHash>>,
}
impl AccountDiff {
pub fn is_unchanged(&self) -> bool {
self.balance.is_unchanged()
&& self.code.is_unchanged()
&& self.nonce.is_unchanged()
&& self.storage.is_empty()
}
}
/// State diff containing all account changes from a transaction.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct StateDiff(pub BTreeMap<EthAddress, AccountDiff>);
impl StateDiff {
pub fn new() -> Self {
Self(BTreeMap::new())
}
pub fn insert_if_changed(&mut self, addr: EthAddress, diff: AccountDiff) {
if !diff.is_unchanged() {
self.0.insert(addr, diff);
}
}
impl<T: PartialEq> Delta<T> {
/// Build a delta from optional old/new values.
pub fn from_comparison(old: Option<T>, new: Option<T>) -> Self {
match (old, new) {
(None, None) => Delta::Unchanged,
(None, Some(new_val)) => Delta::Added(new_val),
(Some(old_val), None) => Delta::Removed(old_val),
(Some(old_val), Some(new_val)) => {
if old_val == new_val {
Delta::Unchanged
} else {
Delta::Changed(ChangedType {
from: old_val,
to: new_val,
})
}
}
}
}
/// Returns true when the delta represents no change.
pub fn is_unchanged(&self) -> bool {
matches!(self, Delta::Unchanged)
}
}
/// Account state diff after transaction execution.
/// Tracks changes to balance, nonce, code, and storage.
// Taken from https://github.com/alloy-rs/alloy/blob/v1.5.2/crates/rpc-types-trace/src/parity.rs#L156
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct AccountDiff {
pub balance: Delta<EthBigInt>,
pub code: Delta<EthBytes>,
pub nonce: Delta<EthUint64>,
/// All touched/changed storage values (key -> delta)
pub storage: BTreeMap<EthHash, Delta<EthHash>>,
}
impl AccountDiff {
/// Returns true when all tracked fields are unchanged and storage is empty.
pub fn is_unchanged(&self) -> bool {
self.balance.is_unchanged()
&& self.code.is_unchanged()
&& self.nonce.is_unchanged()
&& self.storage.is_empty()
}
}
/// State diff containing all account changes from a transaction.
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct StateDiff(pub BTreeMap<EthAddress, AccountDiff>);
impl StateDiff {
/// Create an empty state diff.
pub fn new() -> Self {
Self(BTreeMap::new())
}
/// Insert the account diff only when it contains changes.
pub fn insert_if_changed(&mut self, addr: EthAddress, diff: AccountDiff) {
if !diff.is_unchanged() {
self.0.insert(addr, diff);
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rpc/methods/eth/types.rs` around lines 785 - 844, Add doc comments to all
public helpers related to diffs: document Delta::from_comparison (describe that
it compares old/new Option<T> and returns Added/Removed/Changed/Unchanged),
Delta::is_unchanged (explain it returns true only for Delta::Unchanged),
AccountDiff::is_unchanged (explain it tests all fields and empty storage),
StateDiff::new (note it constructs an empty StateDiff) and
StateDiff::insert_if_changed (state that it inserts the AccountDiff only when
not unchanged). Use /// style comments placed immediately above each
function/struct definition, briefly describing inputs, behavior, and any
important side-effects (e.g., that insert_if_changed skips unchanged diffs).

@akaladarshi akaladarshi force-pushed the akaladarshi/eth-trace-call branch from 8b47987 to ee300c5 Compare February 20, 2026 17:44
@akaladarshi akaladarshi marked this pull request as draft February 20, 2026 17:46
@akaladarshi akaladarshi marked this pull request as draft February 20, 2026 17:46
Copy link
Contributor

@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.

🧹 Nitpick comments (1)
src/rpc/methods/eth.rs (1)

4015-4031: Prefer .context() to wrap state/apply errors.
Keeps error chains consistent with project style.

♻️ Suggested tweak
-        let (pre_state_root, _) = ctx
-            .state_manager
-            .tipset_state(&ts, StateLookupPolicy::Enabled)
-            .await
-            .map_err(|e| anyhow::anyhow!("failed to get tipset state: {e}"))?;
+        let (pre_state_root, _) = ctx
+            .state_manager
+            .tipset_state(&ts, StateLookupPolicy::Enabled)
+            .await
+            .context("failed to get tipset state")?;
@@
-            .await
-            .map_err(|e| anyhow::anyhow!("failed to apply message: {e}"))?;
+            .await
+            .context("failed to apply message")?;
As per coding guidelines: "Use `anyhow::Result` for most operations, adding context with `.context()`".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rpc/methods/eth.rs` around lines 4015 - 4031, Wrap the error returns with
anyhow::Context instead of map_err(anyhow::anyhow!(...)): add use
anyhow::Context if missing, change the tipset_state call to use
.await.context("failed to get tipset state"), change
StateTree::new_from_root(...) to append .context("failed to create StateTree
from root"), and change the apply_on_state_with_gas call to
.await.context("failed to apply message"); keep the same messages but use
.context() for consistent error chaining around tipset_state,
StateTree::new_from_root, and apply_on_state_with_gas.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/docs/developers/guides/trace_call_guide.md`:
- Around line 72-75: Replace the unhyphenated compound adjectives "Geth style
tracing" and "Parity style tracing" with hyphenated forms "Geth-style tracing"
and "Parity-style tracing" in the document so the compounds are grammatically
correct; search for the exact phrases "Geth style" and "Parity style" in the
text and update them accordingly.

In `@src/rpc/methods/eth.rs`:
- Around line 474-501: Public API types EthTraceType and EthTraceResults lack
doc comments; add concise top-level Rust doc comments (/// ...) describing each
type's purpose and behavior, plus brief docs for each EthTraceType variant
(Trace, StateDiff) and for EthTraceResults fields (output, state_diff, trace) to
explain when they are populated and their semantics; ensure comments follow
camelCase/serde intent and appear above the enum, its variants, and the
struct/fields (retain existing serde attributes and lotus_json_with_self!
macros).
- Around line 4036-4038: The code currently swallows decoding failures by
calling decode_payload(...).ok() when building EthTraceResults (around the
get_trace_output/invoke_result usage); change that to propagate the decode error
instead of turning it into None — i.e., remove .ok() and use the fallible call
(e.g., decode_payload(...) ? or decode_payload(...).map_err(|e|
convert_to_rpc_error(e))?) so the enclosing function returns an Err on decode
failure; apply the same fix to the other occurrence that mirrors this behavior
later in the file (the second decode_payload(...) call used when constructing
trace output).

In `@src/rpc/methods/eth/types.rs`:
- Around line 785-845: Public API items lack doc comments; add concise doc
comments for Delta::from_comparison, Delta::is_unchanged, the AccountDiff struct
and AccountDiff::is_unchanged, and the StateDiff struct plus StateDiff::new and
StateDiff::insert_if_changed that explain purpose, parameters and return
behavior (e.g., what from_comparison does with old/new Options, what
is_unchanged checks, that StateDiff is a transparent BTreeMap of account ->
AccountDiff, new creates an empty StateDiff, and insert_if_changed only inserts
non-unchanged AccountDiffs). Keep comments short, use /// style and mention any
invariants or important behavior (like storage emptiness meaning unchanged).

---

Nitpick comments:
In `@src/rpc/methods/eth.rs`:
- Around line 4015-4031: Wrap the error returns with anyhow::Context instead of
map_err(anyhow::anyhow!(...)): add use anyhow::Context if missing, change the
tipset_state call to use .await.context("failed to get tipset state"), change
StateTree::new_from_root(...) to append .context("failed to create StateTree
from root"), and change the apply_on_state_with_gas call to
.await.context("failed to apply message"); keep the same messages but use
.context() for consistent error chaining around tipset_state,
StateTree::new_from_root, and apply_on_state_with_gas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RPC requires calibnet RPC checks to run on CI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement trace_call RPC method

4 participants