Repository navigation
fix(contracts): free memory after each mutation in execute - #66
Merged
Merged
Conversation
execute copies the arguments of each mutation into memory, and Solidity never frees memory. Memory grew with each mutation in a batch. With 10,000 concurrent mutations, the harness benchmark settled a batch that went above the 8 MiB memory limit per transaction on Monad, and the transaction halted with OutOfGas(MemoryLimit). execute now saves the free memory pointer before each mutation and sets it back after the mutation. The harness benchmark now sends 500 concurrent mutations, not 10,000. The runtime settles all pending mutations in one transaction, and 10,000 Credit mutations need about 220M gas. Monad accepts a maximum of 30M gas per transaction. The benchmark also waits until all mutations are included before it closes, and it prints the mean settlement gas for each mutation. Regenerate the Token and OrderBook declarations for the new AST ids.
Comment on lines
+197
to
+199
| uint256 freeMemoryPointer = _getFreeMemoryPointer(); | ||
| _execute(batch.mutations[i], batch.mutationData[i], batch.authorizationData[i]); | ||
| _setFreeMemoryPointer(freeMemoryPointer); |
There was a problem hiding this comment.
Memory reuse lacks regression coverage
The new memory-pointer reset has no focused regression test. The Solidity tests execute one mutation at a time, and the benchmark now uses a smaller batch without asserting memory use. Removing the reset could therefore leave these checks passing.
Add a test that checks memory stays bounded across repeated mutations, including queued mutations and different argument sizes.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/typewriter/src/Typewriter.sol
Line: 197-199
Comment:
**Memory reuse lacks regression coverage**
The new memory-pointer reset has no focused regression test. The Solidity tests execute one mutation at a time, and the benchmark now uses a smaller batch without asserting memory use. Removing the reset could therefore leave these checks passing.
Add a test that checks memory stays bounded across repeated mutations, including queued mutations and different argument sizes.
---
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!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
executecopies the arguments of each mutation into memory, and Solidity never frees memory. Memory grew with each mutation in a batch. With 10,000 concurrent mutations, the harness benchmark settled a batch that went above the 8 MiB memory limit per transaction on Monad (MONAD_MEMORY_LIMIT), and the transaction halted withOutOfGas(MemoryLimit).executenow saves the free memory pointer before each mutation and sets it back after the mutation._executereturns nothing, so no memory that it allocates stays live after it returns. The pointer is saved before the call because the call copies the arguments into memory.MONAD_TX_GAS_LIMIT_CAP). The largest transaction in the benchmark now has about 550 mutations and uses about 12M gas.afterEachdropped the test database.Known issue (not fixed here)
The runtime has no limit on the number of mutations in one settlement transaction. A burst of more than about 1,300 Credit mutations between two submits will go above the 30M gas limit. The local simulation does not find this, because
relax_cfgsetstx_gas_limit_captou64::MAX.Results
Over 6 runs, throughput was 3,300–3,910 mutations/s, except one run at 2,034. The 500-mutation window is short, so throughput is noisy. The mean gas was 23,895–23,959. It changes a little with the number of settlement transactions, because each transaction adds a fixed cost.
Test plan
bun run benchmarkinpackages/typewriterpassesbun run testinpackages/typewriterpassesforge testfor the typewriter, token, and order-book contracts passesbun run typecheckpasses🤖 Generated with Claude Code
The PR appears safe to merge, with a non-blocking request for a focused memory-growth regression test.
Fix with agent prompt
Summary
The PR restores the free memory pointer after each mutation in
execute.Acknowledgment:
kyscott18explicitly identifies the missing settlement batch limit as known and deferred. The description states that large bursts can still exceed the transaction gas cap; this review does not report that existing issue.Reviews (1) · Last reviewed commit: "fix(contracts): free memory after each m..." · Reviewed by Greptile