Skip to content

fix(eth): allow eth_call and eth_estimateGas from contract and non-existent addresses#13470

Open
nijoe1 wants to merge 15 commits intofilecoin-project:masterfrom
nijoe1:fix/eth_call_and_eth_estimate
Open

fix(eth): allow eth_call and eth_estimateGas from contract and non-existent addresses#13470
nijoe1 wants to merge 15 commits intofilecoin-project:masterfrom
nijoe1:fix/eth_call_and_eth_estimate

Conversation

@nijoe1
Copy link
Member

@nijoe1 nijoe1 commented Jan 13, 2026

Related Issues

Fixes compatibility with Geth's eth_call and eth_estimateGas behavior where callers can simulate transactions from contract addresses or non-existent addresses. This is required for tools like Hardhat, Foundry, and other Ethereum development frameworks that rely on this behavior for gas estimation and call simulation.

Proposed Changes

API Layer:

  • Add ErrSenderValidationFailed error type to distinguish sender validation failures from other errors

VM Layer:

  • Add ApplyMessageSkipSenderValidation method to VM interface
  • Implement skip-sender-validation for LegacyVM (bypasses actor existence, account type, nonce, and balance checks)
  • FVM delegates validation to state manager level

State Manager:

  • Add CallWithGasSkipSenderValidation for gas estimation from any address
  • Add ApplyOnStateWithGasSkipSenderValidation for eth_call simulation
  • Implement createSyntheticSenderActor to create ephemeral EthAccount actors for non-existent senders
  • Implement maybeModifySenderForSimulation to temporarily modify EVM/Placeholder actors to EthAccount for simulation

Eth/Gas API:

  • EthEstimateGas now falls back to skip-sender-validation when sender validation fails
  • EthCall uses skip-sender-validation for all calls
  • Preserve ErrExecutionReverted errors for proper JSON-RPC codec handling
  • Add telemetry metrics: eth_call_count, eth_estimate_gas_count, eth_estimate_gas_skip_sender

Key behaviors:

  • State modifications are isolated via buffered blockstore and discarded after simulation
  • Synthetic actors have zero balance (value transfers fail as expected)
  • Only EVM and Placeholder actors can be modified for simulation (system actors are rejected)

Additional Info

This matches Geth's SkipAccountChecks behavior in state_transition.go. The implementation ensures:

  • No state leakage to chain (all modifications are ephemeral)
  • Proper error propagation for reverts with full error data
  • Backwards compatibility for existing EOA-based calls

fixes: #12441 #12896

Checklist

Before you mark the PR ready for review, please make sure that:

nijoe1 and others added 9 commits January 7, 2026 14:49
…ailures

This error type enables callers to distinguish sender validation failures
from other errors and retry with skip-sender-validation when appropriate.
… simulation

Add new VM interface method that skips sender validation checks, enabling
simulation from contract addresses or non-existent addresses. This matches
Geth's behavior for eth_call and eth_estimateGas.

For LegacyVM, this bypasses actor existence, account type, nonce, and
balance checks. For FVM, validation is handled at the state manager level.
…dress

Implement skip sender validation for state manager calls:
- CallWithGasSkipSenderValidation: Skip sender validation during gas estimation
- ApplyOnStateWithGasSkipSenderValidation: Skip validation for eth_call
- createSyntheticSenderActor: Create ephemeral EthAccount for non-existent senders
- maybeModifySenderForSimulation: Modify EVM/Placeholder actors to EthAccount

State modifications are isolated via buffered blockstore and discarded
after simulation. This enables eth_call/eth_estimateGas from contracts
or non-existent addresses, matching Geth's behavior.
…stimateGas

Wire up skip sender validation throughout the gas estimation and eth_call paths:
- EthEstimateGas falls back to skip-sender when sender validation fails
- EthCall uses ApplyOnStateWithGasSkipSenderValidation for all calls
- Add metrics: eth_call_count, eth_estimate_gas_count, eth_estimate_gas_skip_sender

Error handling improved to preserve ErrExecutionReverted for JSON-RPC codec
and distinguish sender validation errors from other failures.
Add integration tests covering all skip-sender-validation code paths:
- TestEthCallSkipSender: 10 subtests (from contract, non-existent, EOA, nil,
  value without balance, and 5 revert types)
- TestEthEstimateGasSkipSender: 8 subtests (from contract, non-existent, EOA,
  and 5 revert types with gas bounds validation)
- TestSkipSenderStateIsolation: Verify synthetic actors don't persist

Tests use dynamic selector computation and table-driven design.
…n failure

The previous implementation always used ApplyOnStateWithGasSkipSenderValidation
for EthCall, which broke the eth_conformance test because it affected the
state handling for normal EOA accounts.

This fix changes EthCall to:
1. First try the normal ApplyOnStateWithGas path (works for valid EOAs)
2. Fall back to ApplyOnStateWithGasSkipSenderValidation only when:
   - The normal path returns ErrSenderValidationFailed
   - The normal path returns SysErrSenderInvalid exit code (contracts)

This maintains Geth compatibility for simulating from contracts/non-existent
addresses while preserving correct behavior for normal accounts.
* Refactor eth_call and eth_estimateGas for better accuracy and code reuse

- Unify gas estimation logic in gasutils to reduce duplication
- Update eth_estimateGas fallback path to use ethGasSearch for improved accuracy
- Ensure consistent handling of ErrSenderValidationFailed across API layers

* Further refine VM simulation and gas estimation accuracy

- Skip gas holder transfers and nonce increments in VM during simulations
- Use ethGasSearch in skip-sender fallback for consistent estimation accuracy
- Improve state isolation for synthetic actor simulations

* Simplify eth_call refinements and revert invasive VM changes

- Revert VM-level skip logic to maintain standard execution path
- Implement 'simulation balance' in StateManager for synthetic actors
- Simplify eth_estimateGas fallback to reduce complexity
- Retain gasutils refactoring for better code reuse

* Add regression tests for simulation balance and gas price scenarios

- Verify eth_call from non-existent address succeeds with non-zero GasPrice
- Verify eth_call succeeds with value transfers within simulation balance (100 FIL)
- Verify eth_call still fails for value transfers exceeding simulation balance

* Restore FromNonExistent test case and keep gas price regression cases

* fix(eth): keep synthetic sender balance at zero

* test(eth): add estimateGas non-existent sender gasPrice case

* fix(eth): reject estimateGas with gas price on missing sender

* revert(eth): keep estimateGas skip-sender fallback minimal

* test(eth): expand skip-sender estimateGas/call coverage

* test(eth): allow gasPrice eth_call for missing senders

* fix:improves eth_call with value tests

---------

Co-authored-by: nijoe1 <nick@fil.builders>
Copilot AI review requested due to automatic review settings January 13, 2026 20:01
@github-project-automation github-project-automation bot moved this to 📌 Triage in FilOz Jan 13, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements skip-sender-validation functionality for eth_call and eth_estimateGas to match Geth's behavior, enabling these calls to simulate transactions from contract addresses or non-existent addresses. This is essential for Ethereum development tools like Hardhat and Foundry.

Changes:

  • Added skip-sender-validation paths in VM layer and state manager to bypass actor existence and account type checks
  • Modified eth_call and eth_estimateGas to fallback to skip-sender-validation when normal validation fails
  • Added telemetry metrics to track skip-sender-validation usage

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
api/api_errors.go Added ErrSenderValidationFailed error type for distinguishing sender validation failures
chain/vm/vmi.go Added ApplyMessageSkipSenderValidation method to VM interface
chain/vm/vm.go Implemented skip-sender-validation in LegacyVM with synthetic actor creation
chain/vm/fvm.go FVM delegates skip-sender-validation to state manager level
chain/vm/execution.go Added executor wrapper for skip-sender-validation method
chain/stmgr/call.go Implemented CallWithGasSkipSenderValidation and ApplyOnStateWithGasSkipSenderValidation with proper state tree actor creation
node/impl/gasutils/gasutils.go Refactored gas estimation with unified internal implementation supporting skip-sender-validation
node/impl/eth/api.go Added interface methods for skip-sender-validation state manager methods
node/impl/eth/gas.go Modified EthEstimateGas and EthCall to fallback to skip-sender-validation on sender validation failures
metrics/metrics.go Added metrics for eth_call and eth_estimateGas operations
itests/eth_skip_sender_test.go Comprehensive tests for skip-sender-validation behavior with various sender types and error conditions
Comments suppressed due to low confidence (1)

chain/vm/vm.go:590

  • In the skipSenderValidation path, when a synthetic actor is created with zero balance (lines 510-513), the code still attempts to transfer gas cost from this actor at line 588. The transferToGasHolder method will fail with "not enough funds" error because it tries to deduct the gas cost from an actor with zero balance. This will cause all eth_call and eth_estimateGas operations from non-existent addresses to fail, defeating the purpose of this feature. The gas transfer should be skipped or handled differently when skipSenderValidation is true.
	gasHolder := &types.Actor{Balance: types.NewInt(0)}
	if err := vm.transferToGasHolder(msg.From, gasHolder, gascost); err != nil {
		return nil, xerrors.Errorf("failed to withdraw gas funds: %w", err)
	}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +514 to +515
}
gascost = types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The synthetic actor is created with zero balance, but gascost is calculated as msg.GasLimit * msg.GasFeeCap. When the subsequent code attempts to transfer this gas cost from the synthetic actor (which happens at line 588, outside this diff), it will fail because the actor has insufficient funds. Consider setting msg.GasFeeCap to zero when skipSenderValidation is true, or handle the gas transfer differently.

Suggested change
}
gascost = types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap)
}
// For simulations, do not pre-charge gas from the synthetic zero-balance actor.
// This avoids insufficient-funds failures when gas is later debited.
gascost = types.NewInt(0)

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +49
func setupSkipSenderTest(t *testing.T) *skipSenderEnv {
ctx, cancel, client := kit.SetupFEVMTest(t)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

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

The test uses SetupFEVMTest which only tests the FVM path (network version >= 16). The LegacyVM path (older network versions) is not tested, but the skip-sender-validation feature is implemented for both VM types. Consider adding tests that verify the behavior on older network versions using LegacyVM to ensure the feature works correctly in both paths.

Copilot uses AI. Check for mistakes.
@rvagg
Copy link
Member

rvagg commented Jan 14, 2026

Really quick skim-review to start with, will try and make time to go deeper. Overall this looks pretty good @nijoe1, great work!

  • I don't think this fully fixes Remove the restriction on the "from" field when calling from a contract or identical address #12896, there's a deeper account type check that's going to hold that one up. That one gets caught up here ultimately: https://github.com/filecoin-project/ref-fvm/blob/269c858c1162ecceb20e30c207ba4e99ad03e98b/fvm/src/executor/default.rs#L407-L428 - needs to be an account actor, so the sender can exist but if it's not an account actor then it'll bork. The failures that are resulting in that report are from patterns that exist in EVM-land where they construct messages from smart contracts and expect them to estimate correctly, that pattern doesn't work for us. So the plumbing to override that in ref-fvm is gonig to be more complex.
  • I'm not sure about the synthetic actor creation going on here to mutate state directly before using it. That probably works but I think we need to work through potential problems with that model, you might be opening up new edge cases. You're passing in a state root that's been mutated by manual means, no longer matches the original requested state root, and may not conform to all of the expected invariants of the state. The more "correct" way is to just insert an implicit message earlier than the message you're executing that creates the actor - so the original state is unmolested but gets mutated through the message system, then you don't have to worry about getting the state structure right, the account creation logic in actors will do that for you.

@BigLep BigLep moved this from 📌 Triage to ⌨️ In Progress in FilOz Jan 14, 2026
@BigLep BigLep requested a review from rvagg January 14, 2026 05:15
@nijoe1
Copy link
Member Author

nijoe1 commented Jan 16, 2026

This case is handled here where it modifies in memory the Contract.code to look like an EthAccount.code

To check out if the tests are not sufficient in real world scenarios I cloned today the foc-devnet and connected my local lotus repo pointing to this PR code changes. Then using foundry I implemented the same set of tests as the ones defined here and all passed using the local lotus rpc on devnet where on calibration failed

@rvagg
Copy link
Member

rvagg commented Jan 17, 2026

OK I think maybeModifySenderForSimulation is going to be the wrong approach for the same reason - you're modifying an existing actor, mutating state, which changes the system that you're running the transaction over. By changing the nature of the calling actor you change the outcome of the execution and that could cause either subtle or very obvious side effects that are entirely different to an actual execution. Part of the point of estimateGas is that you should be able to run a transaction and see its results on top of a particular state, but you no longer have that same state. The right answer for that case is to change the restrictiveness of the call to not perform the checks that are getting in the way.

@nijoe1
Copy link
Member Author

nijoe1 commented Jan 17, 2026

OK I think maybeModifySenderForSimulation is going to be the wrong approach for the same reason - you're modifying an existing actor, mutating state, which changes the system that you're running the transaction over. By changing the nature of the calling actor you change the outcome of the execution and that could cause either subtle or very obvious side effects that are entirely different to an actual execution. Part of the point of estimateGas is that you should be able to run a transaction and see its results on top of a particular state, but you no longer have that same state. The right answer for that case is to change the restrictiveness of the call to not perform the checks that are getting in the way.

That's true. I added locally one test where

ContractA -- calls --> contractB
contractB -- calls-a-getter-from -- contractA
returns the value got from contractA

And that fails when doing eth_call because we change contractA code

Which is the best approach to test local changes made on ref-fvm and filecoin-ffi? on lotus?

Seems that I found an ugly workaround to do that where skip-sender-validation tests pass while implementing your above improvements but many other tests fail. That's why I am asking if there is a standard pattern to connect lotus with filecoin-ffi - ref-fvm

@rvagg
Copy link
Member

rvagg commented Jan 19, 2026

Sorry this is where it gets complicated. I was going to point you to https://github.com/filecoin-project/lotus/blob/master/documentation/misc/Builtin-actors_Development.md but there's a TODO at the bottom to document the things we need here. You need to clone ref-fvm, filecoin-ffi and do some messing around with Cargo.toml in filecoin-ffi to point to your local copy, or a branch on GitHub that you've modified, then you need to rebuild the CGO bindings in filecoin-ffi to make updates if you're changing the API (I'm assuming you're going to pass a flag down into ref-fvm that tells it to skip validation checks), then you can either symlink ffi into lotus or update the submodule to point to your branch.

Then you'll need PRs against all 3 and we'll have to verify they work and review and merge them up the stack and back to here. It's a little messy but AI should be your friend in figuring out how to do the complicated bits like the horrible plumbing and CGO stuff in filecoin-ffi. Also look through git history to find examples of this. filecoin-project/filecoin-ffi#512 is pretty close and recent, plumbing a boolean option down into ref-fvm, see the linked ref-fvm PR there and also the corresponding change in lotus (somewhere).

@nijoe1
Copy link
Member Author

nijoe1 commented Jan 19, 2026

I made the workflow work and pass all the tests including fixes to all your above comments concerns!

I might need some time to figure out how to cross-reference make PRs to each repo as the one that you shared!
I will share more hopefully by EOW!

Thanks @rvagg

@rvagg
Copy link
Member

rvagg commented Jan 21, 2026

@nijoe1 you'll probably have trouble getting CI happy when you do the separate PRs, it's possible, with Cargo.toml changes, but we can also deal with them individually and I could review by checking out your work and trying it for myself. Do your best and we'll work with it.

@nijoe1 nijoe1 force-pushed the fix/eth_call_and_eth_estimate branch 2 times, most recently from eded1f7 to c0d0c97 Compare January 26, 2026 11:59
@nijoe1
Copy link
Member Author

nijoe1 commented Jan 26, 2026

@rvagg I made the requested changes around the synthetic-actor and maybe-modify-actor with only one additional function into filecoin-ffi.

@BigLep BigLep moved this from ⌨️ In Progress to 🔎 Awaiting Review in FilOz Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 🔎 Awaiting Review

Development

Successfully merging this pull request may close these issues.

eth_estimateGas responds with actor not found

3 participants