Skip to content

Commit d581a6b

Browse files
gakonstampcode-com
andcommitted
fix(invariant): remove unused FuzzCase.calldata field to prevent OOM
The calldata field in FuzzCase was stored but never read after construction. Removing it entirely eliminates memory accumulation during long invariant runs. Changes: - Remove FuzzCase.calldata field (unused after construction) - Remove prune_calldata() methods (no longer needed) - Restore vyper test files that were incorrectly deleted Fixes #12397 Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp <[email protected]> Amp-Thread-ID: https://ampcode.com/threads/T-019c17c9-d969-7370-bf0d-495e473e8e30 Co-authored-by: Amp <[email protected]>
1 parent 68b258a commit d581a6b

File tree

6 files changed

+44
-31
lines changed

6 files changed

+44
-31
lines changed

crates/evm/evm/src/executors/fuzz/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ impl FuzzedExecutor {
372372

373373
if success {
374374
Ok(FuzzOutcome::Case(CaseOutcome {
375-
case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend },
375+
case: FuzzCase { gas: call.gas_used, stipend: call.stipend },
376376
traces: call.traces,
377377
coverage: call.line_coverage,
378378
breakpoints,

crates/evm/evm/src/executors/invariant/mod.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,6 @@ impl InvariantTest {
241241
}
242242
self.test_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
243243

244-
// Prune older cases if we have too many.
245-
// We keep a rolling window of full traces for the last 4096 runs to aid debugging,
246-
// but prune older ones to avoid unbounded memory usage.
247-
const MAX_KEPT_CALLDATA: usize = 4096;
248-
if self.test_data.fuzz_cases.len() > MAX_KEPT_CALLDATA {
249-
let prune_index = self.test_data.fuzz_cases.len() - MAX_KEPT_CALLDATA - 1;
250-
self.test_data.fuzz_cases[prune_index].prune_calldata();
251-
}
252-
253244
// Revert state to not persist values between runs.
254245
self.fuzz_state.revert();
255246
}
@@ -461,11 +452,9 @@ impl<'a> InvariantExecutor<'a> {
461452
{
462453
warn!(target: "forge::test", "{error}");
463454
}
464-
current_run.fuzz_runs.push(FuzzCase {
465-
calldata: tx.call_details.calldata.clone(),
466-
gas: call_result.gas_used,
467-
stipend: call_result.stipend,
468-
});
455+
current_run
456+
.fuzz_runs
457+
.push(FuzzCase { gas: call_result.gas_used, stipend: call_result.stipend });
469458

470459
// Determine if test can continue or should exit.
471460
let result = can_continue(

crates/evm/fuzz/src/lib.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -307,21 +307,12 @@ impl FuzzTestResult {
307307
/// Data of a single fuzz test case
308308
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
309309
pub struct FuzzCase {
310-
/// The calldata used for this fuzz test
311-
pub calldata: Bytes,
312310
/// Consumed gas
313311
pub gas: u64,
314312
/// The initial gas stipend for the transaction
315313
pub stipend: u64,
316314
}
317315

318-
impl FuzzCase {
319-
/// Removes the calldata from the fuzz case to save memory.
320-
pub fn prune_calldata(&mut self) {
321-
self.calldata = Bytes::new();
322-
}
323-
}
324-
325316
/// Container type for all successful test cases
326317
#[derive(Clone, Debug, Serialize, Deserialize)]
327318
#[serde(transparent)]
@@ -390,13 +381,6 @@ impl FuzzedCases {
390381
pub fn lowest_gas(&self) -> u64 {
391382
self.lowest().map(|c| c.gas).unwrap_or_default()
392383
}
393-
394-
/// Prunes calldata from all fuzz cases.
395-
pub fn prune_calldata(&mut self) {
396-
for case in &mut self.cases {
397-
case.prune_calldata();
398-
}
399-
}
400384
}
401385

402386
/// Fixtures to be used for fuzz tests.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from src import ICounter
2+
3+
interface Vm:
4+
def deployCode(artifact_name: String[1024], args: Bytes[1024] = b"") -> address: nonpayable
5+
6+
vm: constant(Vm) = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)
7+
counter: ICounter
8+
9+
@external
10+
def setUp():
11+
self.counter = ICounter(extcall vm.deployCode("src/Counter.vy"))
12+
13+
@external
14+
def test_increment():
15+
extcall self.counter.increment()
16+
assert staticcall self.counter.number() == 1

testdata/src/Counter.vy

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from . import ICounter
2+
implements: ICounter
3+
4+
number: public(uint256)
5+
6+
@external
7+
def set_number(new_number: uint256):
8+
self.number = new_number
9+
10+
@external
11+
def increment():
12+
self.number += 1

testdata/src/ICounter.vyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@view
2+
@external
3+
def number() -> uint256:
4+
...
5+
6+
@external
7+
def set_number(new_number: uint256):
8+
...
9+
10+
@external
11+
def increment():
12+
...

0 commit comments

Comments
 (0)