Repository navigation
Conversation
Execute ran pass 1, pass 2, a gas-limit binary search (~15 runs), and a final pass: about 18 EVM runs per accepted mutation. Only simulate needs a gas limit, because it builds the onchain transaction. Execute now runs pass 1 and pass 2 only, and its result has no gas_limit. Simulate returns the new SimulateResult type, which keeps gas_limit. Counter benchmark: evm execute 1.13ms -> 0.20ms per mutation. Harness benchmark: 2,236 -> 3,026 mutations/s.
Execute ran pass 1 to discover the access list, then pass 2 with that access list pre-warmed. Both passes run with the maximum gas limit, so they produce the same state. Pass 2 only changed gas_used, which the runtime does not read from execute. Execute now runs once, keeps that post-state, and records the touched accounts and slots as the journal's access list. Simulate keeps both passes and the gas-limit search. typewriter-evm execute benchmark: erc20 transfer 15.2us -> 12.1us. Counter benchmark: 32 in flight 2,827 -> 3,264-3,493 mutations/s; open loop at 4,000/s now keeps up (3,970/s, p50 ~280ms -> 10-21ms).
Simulate now computes the smallest gas limit that keeps the max-gas path in one traced pass, instead of a binary search and a final rerun. The tracer applies the EIP-150 63/64 rule to each call or create and the EIP-2200 SSTORE sentry. Monad has no refunds. A call that halts uses all of its gas, so simulate now returns an error for it. - Split the execute and simulate result types in Rust and TS. - Parse the tx fields once per request. - Filter precompiles from the access list with the spec's precompile set, so P-256 at 0x100 is no longer included. - Add Rust tests that check each traced limit is the minimum, and run them in the package test script. - Add tests for simulate with a cached journal and for the P-256 access list.
Typewriter apps do not make external calls, and the ERC20 journal test already covers simulate with a cached journal.
| if !result.result.is_ok_or_revert() { | ||
| self.halted = true; |
There was a problem hiding this comment.
frame_end marks every halted child as impossible to run at any gas limit. A contract can call a child with a fixed gas budget, catch its INVALID or out-of-gas failure, and still succeed. The child consumes only that budget, but finish returns an error. This makes simulate reject the successful transaction and stops submission.
Track the child's gas budget instead of rejecting every halt.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/typewriter-evm/src/harness.rs
Line: 435-436
Comment:
**Caught calls block submission**
`frame_end` marks every halted child as impossible to run at any gas limit. A contract can call a child with a fixed gas budget, catch its `INVALID` or out-of-gas failure, and still succeed. The child consumes only that budget, but `finish` returns an error. This makes `simulate` reject the successful transaction and stops submission.
Track the child's gas budget instead of rejecting every halt.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Simulate now sets the gas limit to gas_used plus a reserve. Each frame reserves 2,301 gas for the SSTORE sentry and enough that 63/64 of its remaining gas covers each child's gas plus the child's reserve. This removes the per-opcode gas tracking. A call that halts is still an error. Also simplify harness.rs: one Journal type writes pre or post images, access lists merge through ordered sets, and dispatch uses one response helper. The wire format does not change.
| fn reserve_covers_nested_calls() { | ||
| let outer = require_success(call(CALL, CALLEE, GAS)); | ||
| let middle = require_success(call(CALL, INNER, GAS)); | ||
| assert_reserve_needed(&[(CONTRACT, &outer), (CALLEE, &middle), (INNER, COUNTER)]); |
There was a problem hiding this comment.
The gas tracker was replaced, but the tests for CREATE and calls that send value were deleted without replacements. The remaining call tests all send zero value, so mistakes in those gas rules could pass unnoticed. Keep tests that run these transactions at the returned gas_limit and check that they succeed. This is non-blocking test feedback.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/typewriter-evm/src/harness.rs
Line: 897-900
Comment:
**Gas tests lose coverage**
The gas tracker was replaced, but the tests for `CREATE` and calls that send value were deleted without replacements. The remaining call tests all send zero value, so mistakes in those gas rules could pass unnoticed. Keep tests that run these transactions at the returned `gas_limit` and check that they succeed. This is non-blocking test feedback.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
Execute
executeruns once, keeps that post-state, and records the touched accounts and slots as the journal's access list.execute'sgas_usedis the gas with cold access. No runtime code reads it.Simulate
simulateruns the discovery pass (skipped when the journals give a cached access list), then one pass at max gas with the access list warm. The binary search and the final pass are removed.gas_limit = gas_used + reserve. Each frame reserves 2,301 gas for the SSTORE sentry (EIP-2200), and enough that 63/64 of its remaining gas covers each child's gas plus the child's reserve (EIP-150). Precompiles get no SSTORE reserve. Monad has no refunds, sogas_usedis the gas spent.simulatereturns an error. A halted call uses all the gas it gets, so no gas limit that the chain accepts can run the tx.Other
ExecuteResultandSimulateResultare separate types in Rust and TS.SimulateResultno longer hasjournal_idorslot_writes.harness.rsis simpler: oneJournaltype writes pre or post images, access lists merge through ordered sets, the tx fields are parsed once per request, and dispatch uses one response helper. The wire format does not change.typewriter-evmtest script now also runscargo test --lib.Results
Same machine, PR head before these changes (
9008e45) against4bc8675:packages/typewriter/test/harness.benchmark.ts(3 runs)typewriter-evmsimulate, discovered, burn 5000typewriter-evmsimulate, cached, burn 5000The harness benchmark shows no measurable change: simulate does not limit it.
typewriter-evmexecute throughput varied from 99k to 133k calls/s between runs, so it gives no clear result.Test plan
bun run lint,bun run typecheck,bun run testgas_usedand succeeds atgas_limitfor an SSTORE sentry, precompile calls (0x01, 0x100), and nested calls. A halted call returns an error.eth_createAccessList.🤖 Generated with Claude Code
Fix the outstanding rejection of successful transactions with caught child failures before merging.
Fix with agent prompt
Summary
executenow keeps the state from one cold-access run and saves the touched accounts and slots in its journal.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD E[execute] --> D[Run without an access list] D --> A[Collect touched accounts and slots] A --> J[Save journal and commit successful state] S[simulate] --> U[Undo selected journals] U --> C{Cached access list?} C -->|No| F[Discover access list and discard state] C -->|Yes| W[Run with warm access list] F --> W W --> G[Return gas used plus frame reserve] G --> R[Discard simulated state and replay journals]Reviews (3) · Last reviewed commit: "refactor(evm): replace the gas tracker w..." · Reviewed by Greptile