Skip to content

Commit f3aea74

Browse files
authored
Task: clean-up and de-duplicate code (#90)
* deduplicate and fix bug in one of the memory expansion gas formulas removed the now vestigial MemoryGas struct in gas.zig deduplicate runSystemCall, runSystemCallCapture, applying fix in runSystemCall from #81 to runSystemCallCapture also deduplicate signingHash, txHash * fix overflow potential in memory.expandWithGas Signed-off-by: garyschulte <garyschulte@gmail.com>
1 parent 3de426e commit f3aea74

12 files changed

Lines changed: 162 additions & 524 deletions

File tree

src/evm/interpreter/gas.zig

Lines changed: 0 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ pub const Gas = struct {
2828
/// amount, matching the reference (vm charge_state_gas / credit_state_gas_refund), so a
2929
/// spilled-then-refunded charge restores regular gas for subsequent 63/64 forwarding.
3030
state_gas_spilled: u64 = 0,
31-
/// Memoisation of values for memory expansion cost.
32-
memory: MemoryGas,
3331

3432
/// Creates a new `Gas` struct with the given gas limit.
3533
pub fn new(limit: u64) Gas {
@@ -41,7 +39,6 @@ pub const Gas = struct {
4139
.reservoir = 0,
4240
.state_gas_spent = 0,
4341
.state_gas_refunded = 0,
44-
.memory = MemoryGas.new(),
4542
};
4643
}
4744

@@ -55,7 +52,6 @@ pub const Gas = struct {
5552
.reservoir = 0,
5653
.state_gas_spent = 0,
5754
.state_gas_refunded = 0,
58-
.memory = MemoryGas.new(),
5955
};
6056
}
6157

@@ -64,16 +60,6 @@ pub const Gas = struct {
6460
return self.limit;
6561
}
6662

67-
/// Returns the memory gas.
68-
pub fn getMemory(self: Gas) MemoryGas {
69-
return self.memory;
70-
}
71-
72-
/// Returns the memory gas mutably.
73-
pub fn getMemoryMut(self: *Gas) *MemoryGas {
74-
return &self.memory;
75-
}
76-
7763
/// Returns the total amount of gas that was refunded.
7864
pub fn getRefunded(self: Gas) i64 {
7965
return self.refunded;
@@ -192,32 +178,6 @@ pub const Gas = struct {
192178
self.refunded = refund_amount;
193179
}
194180

195-
/// Load gas with memory
196-
pub fn loadGasWithMemory(self: *Gas, amount: u64, memory: MemoryGas) void {
197-
self.remaining = amount;
198-
self.memory = memory;
199-
}
200-
201-
/// Load gas with memory and refund
202-
pub fn loadGasWithMemoryAndRefund(self: *Gas, amount: u64, memory: MemoryGas, refund_amount: i64) void {
203-
self.remaining = amount;
204-
self.memory = memory;
205-
self.refunded = refund_amount;
206-
}
207-
208-
/// Load gas with limit and memory
209-
pub fn loadGasWithLimitAndMemory(self: *Gas, amount: u64, limit: u64, memory: MemoryGas) void {
210-
self.remaining = @min(amount, limit);
211-
self.memory = memory;
212-
}
213-
214-
/// Load gas with limit, memory and refund
215-
pub fn loadGasWithLimitMemoryAndRefund(self: *Gas, amount: u64, limit: u64, memory: MemoryGas, refund_amount: i64) void {
216-
self.remaining = @min(amount, limit);
217-
self.memory = memory;
218-
self.refunded = refund_amount;
219-
}
220-
221181
/// Apply EIP-3529 refund cap in-place.
222182
/// Pre-London: cap at gas_spent / 2. London+: cap at gas_spent / 5.
223183
pub fn setFinalRefund(self: *Gas, is_london: bool) void {
@@ -234,63 +194,3 @@ pub const Gas = struct {
234194
return if (spent > ref) spent - ref else 0;
235195
}
236196
};
237-
238-
/// Memory gas tracking for memory expansion costs
239-
pub const MemoryGas = struct {
240-
/// The current memory size in words
241-
size: u64,
242-
/// The maximum memory size reached during execution
243-
max_size: u64,
244-
245-
pub fn new() MemoryGas {
246-
return MemoryGas{
247-
.size = 0,
248-
.max_size = 0,
249-
};
250-
}
251-
252-
/// Get the current memory size in words
253-
pub fn getSize(self: MemoryGas) u64 {
254-
return self.size;
255-
}
256-
257-
/// Get the maximum memory size reached
258-
pub fn maxSize(self: MemoryGas) u64 {
259-
return self.max_size;
260-
}
261-
262-
/// Set the memory size
263-
pub fn setSize(self: *MemoryGas, size: u64) void {
264-
self.size = size;
265-
self.max_size = @max(self.max_size, size);
266-
}
267-
268-
/// Calculate memory expansion cost
269-
pub fn expansionCost(self: MemoryGas, new_size: u64) u64 {
270-
if (new_size <= self.size) {
271-
return 0;
272-
}
273-
274-
// std.math.divCeil(u64, n, 32) avoids (n + 31) overflow for large n.
275-
const new_words = std.math.divCeil(u64, new_size, 32) catch return std.math.maxInt(u64);
276-
const current_words = std.math.divCeil(u64, self.size, 32) catch return std.math.maxInt(u64);
277-
278-
if (new_words <= current_words) {
279-
return 0;
280-
}
281-
282-
const sq_new = std.math.mul(u64, new_words, new_words) catch return std.math.maxInt(u64);
283-
const cost = std.math.add(u64, sq_new / 512, 3 * new_words) catch return std.math.maxInt(u64);
284-
const sq_cur = std.math.mul(u64, current_words, current_words) catch return std.math.maxInt(u64);
285-
const current_cost = std.math.add(u64, sq_cur / 512, 3 * current_words) catch return std.math.maxInt(u64);
286-
287-
return cost - current_cost;
288-
}
289-
290-
/// Record memory expansion
291-
pub fn recordExpansion(self: *MemoryGas, new_size: u64) u64 {
292-
const cost = self.expansionCost(new_size);
293-
self.setSize(new_size);
294-
return cost;
295-
}
296-
};

src/evm/interpreter/gas_costs.zig

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,11 @@ fn memoryCost(num_words: usize) u64 {
134134
return std.math.add(u64, linear, quadratic) catch std.math.maxInt(u64);
135135
}
136136

137-
// Calculate memory size in words (rounded up)
137+
// Calculate memory size in words (rounded up).
138+
// Uses divCeil rather than (size + 31) / 32: a huge size must saturate, not
139+
// overflow-panic, on the intermediate +31.
138140
pub fn toWordSize(size: usize) usize {
139-
return (size + 31) / 32;
141+
return std.math.divCeil(usize, size, 32) catch std.math.maxInt(usize);
140142
}
141143

142144
// Get SLOAD gas cost based on spec and cold/warm access

src/evm/interpreter/instruction_context.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,10 @@ pub const InstructionContext = struct {
1616

1717
/// Function pointer type for all opcode handlers.
1818
pub const InstructionFn = *const fn (ctx: *InstructionContext) void;
19+
20+
/// Shared memory-expansion helper for opcode handlers: grows memory to `new_size`
21+
/// bytes, charging the expansion gas cost. Returns false (OOG) without mutating
22+
/// memory if gas or the resize fails.
23+
pub fn expandMemory(ctx: *InstructionContext, new_size: usize) bool {
24+
return ctx.interpreter.memory.expandWithGas(&ctx.interpreter.gas, new_size);
25+
}

src/evm/interpreter/memory.zig

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
33
const alloc_mod = @import("zesu_allocator");
4+
const gas_costs = @import("gas_costs.zig");
5+
const Gas = @import("gas.zig").Gas;
46

57
/// EVM memory implementation using a shared buffer
68
pub const Memory = struct {
@@ -278,28 +280,41 @@ pub const Memory = struct {
278280
try self.set(offset, &[_]u8{byte});
279281
}
280282

281-
/// Calculate memory expansion cost
283+
/// Calculate memory expansion cost. Single source of truth for the
284+
/// `3*words + words^2/512` formula lives in gas_costs.memoryExpansionCost;
285+
/// this just converts the byte sizes involved to word counts.
282286
pub fn expansionCost(self: Memory, new_size: usize) u64 {
283287
if (new_size <= self.buffer.items.len) {
284288
return 0;
285289
}
290+
return gas_costs.memoryExpansionCost(
291+
gas_costs.toWordSize(self.buffer.items.len),
292+
gas_costs.toWordSize(new_size),
293+
);
294+
}
286295

287-
// std.math.divCeil avoids (n + 31) overflow when n is near maxInt(usize).
288-
const new_words = std.math.divCeil(usize, new_size, 32) catch return std.math.maxInt(u64);
289-
const current_words = std.math.divCeil(usize, self.buffer.items.len, 32) catch return std.math.maxInt(u64);
290-
291-
if (new_words <= current_words) {
292-
return 0;
293-
}
294-
295-
const n: u64 = @intCast(new_words);
296-
const c: u64 = @intCast(current_words);
297-
const sq_n = std.math.mul(u64, n, n) catch return std.math.maxInt(u64);
298-
const cost = std.math.add(u64, sq_n / 512, 3 * n) catch return std.math.maxInt(u64);
299-
const sq_c = std.math.mul(u64, c, c) catch return std.math.maxInt(u64);
300-
const current_cost = std.math.add(u64, sq_c / 512, 3 * c) catch return std.math.maxInt(u64);
301-
302-
return cost - current_cost;
296+
/// Grow memory to at least `new_size` bytes, charging the expansion cost against
297+
/// `gas`. Returns false if `gas` can't cover the cost, if rounding `new_size` up to
298+
/// a 32-byte boundary would overflow, or if the resize fails. Note gas charged via
299+
/// `spend` is not refunded on a later failure — this is consistent with EVM
300+
/// out-of-gas semantics (a frame that fails to expand memory halts and forfeits its
301+
/// remaining gas regardless).
302+
/// New bytes are zero-initialized per EVM memory semantics.
303+
pub fn expandWithGas(self: *Memory, gas: *Gas, new_size: usize) bool {
304+
if (new_size == 0 or new_size <= self.buffer.items.len) return true;
305+
306+
const cost = self.expansionCost(new_size);
307+
if (!gas.spend(cost)) return false;
308+
309+
// Checked: toWordSize used in expansionCost saturates rather than overflow-panics
310+
// on huge new_size, so the round-trip back to an aligned byte size must be
311+
// checked separately — words*32 can still exceed usize
312+
const new_words = std.math.divCeil(usize, new_size, 32) catch return false;
313+
const aligned_size = std.math.mul(usize, new_words, 32) catch return false;
314+
const old_size = self.buffer.items.len;
315+
self.buffer.resize(alloc_mod.get(), aligned_size) catch return false;
316+
@memset(self.buffer.items[old_size..aligned_size], 0);
317+
return true;
303318
}
304319

305320
/// Resize memory to a new size

src/evm/interpreter/opcodes/call.zig

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
3-
const InstructionContext = @import("../instruction_context.zig").InstructionContext;
3+
const instruction_context = @import("../instruction_context.zig");
4+
const InstructionContext = instruction_context.InstructionContext;
5+
const expandMemory = instruction_context.expandMemory;
46
const gas_costs = @import("../gas_costs.zig");
57
const host_module = @import("../host.zig");
6-
const alloc_mod = @import("zesu_allocator");
78
const CallScheme = @import("../interpreter_action.zig").CallScheme;
89
const CreateScheme = @import("../interpreter_action.zig").CreateScheme;
910
const CreateInputs = @import("../interpreter_action.zig").CreateInputs;
@@ -12,34 +13,6 @@ const Interpreter = interp_mod.Interpreter;
1213
const PendingCallData = interp_mod.PendingCallData;
1314
const PendingCreateData = interp_mod.PendingCreateData;
1415

15-
// ---------------------------------------------------------------------------
16-
// Memory expansion helper
17-
// ---------------------------------------------------------------------------
18-
19-
fn memoryCostWords(num_words: usize) u64 {
20-
const n: u64 = @intCast(num_words);
21-
const linear = std.math.mul(u64, n, gas_costs.G_MEMORY) catch return std.math.maxInt(u64);
22-
const quadratic = (std.math.mul(u64, n, n) catch return std.math.maxInt(u64)) / 512;
23-
return std.math.add(u64, linear, quadratic) catch std.math.maxInt(u64);
24-
}
25-
26-
fn expandMemory(ctx: *InstructionContext, new_size: usize) bool {
27-
if (new_size == 0) return true;
28-
const current = ctx.interpreter.memory.size();
29-
if (new_size <= current) return true;
30-
const current_words = (current + 31) / 32;
31-
const new_words = (std.math.add(usize, new_size, 31) catch return false) / 32;
32-
if (new_words > current_words) {
33-
const cost = memoryCostWords(new_words) - memoryCostWords(current_words);
34-
if (!ctx.interpreter.gas.spend(cost)) return false;
35-
}
36-
const aligned_size = new_words * 32;
37-
const old_size = ctx.interpreter.memory.size();
38-
ctx.interpreter.memory.buffer.resize(alloc_mod.get(), aligned_size) catch return false;
39-
@memset(ctx.interpreter.memory.buffer.items[old_size..aligned_size], 0);
40-
return true;
41-
}
42-
4316
// ---------------------------------------------------------------------------
4417
// Common call dispatch helper
4518
// ---------------------------------------------------------------------------

src/evm/interpreter/opcodes/environment.zig

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,10 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
3-
const InstructionContext = @import("../instruction_context.zig").InstructionContext;
3+
const instruction_context = @import("../instruction_context.zig");
4+
const InstructionContext = instruction_context.InstructionContext;
5+
const expandMemory = instruction_context.expandMemory;
46
const gas_costs = @import("../gas_costs.zig");
57
const host_module = @import("../host.zig");
6-
const alloc_mod = @import("zesu_allocator");
7-
8-
// ---------------------------------------------------------------------------
9-
// Memory expansion helper (shared pattern from other opcode files)
10-
// ---------------------------------------------------------------------------
11-
12-
fn memoryCostWords(num_words: usize) u64 {
13-
const n: u64 = @intCast(num_words);
14-
const linear = std.math.mul(u64, n, gas_costs.G_MEMORY) catch return std.math.maxInt(u64);
15-
const quadratic = (std.math.mul(u64, n, n) catch return std.math.maxInt(u64)) / 512;
16-
return std.math.add(u64, linear, quadratic) catch std.math.maxInt(u64);
17-
}
18-
19-
fn expandMemory(ctx: *InstructionContext, new_size: usize) bool {
20-
if (new_size == 0) return true;
21-
const current = ctx.interpreter.memory.size();
22-
if (new_size <= current) return true;
23-
const current_words = (current + 31) / 32;
24-
const new_words = (std.math.add(usize, new_size, 31) catch return false) / 32;
25-
if (new_words > current_words) {
26-
const cost = memoryCostWords(new_words) - memoryCostWords(current_words);
27-
if (!ctx.interpreter.gas.spend(cost)) return false;
28-
}
29-
const aligned_size = new_words * 32;
30-
const old_size = ctx.interpreter.memory.size();
31-
ctx.interpreter.memory.buffer.resize(alloc_mod.get(), aligned_size) catch return false;
32-
@memset(ctx.interpreter.memory.buffer.items[old_size..aligned_size], 0);
33-
return true;
34-
}
358

369
// ---------------------------------------------------------------------------
3710
// Opcodes that read from interpreter.input (no Host required)

src/evm/interpreter/opcodes/host_ops.zig

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,12 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
3-
const InstructionContext = @import("../instruction_context.zig").InstructionContext;
3+
const instruction_context = @import("../instruction_context.zig");
4+
const InstructionContext = instruction_context.InstructionContext;
5+
const expandMemory = instruction_context.expandMemory;
46
const gas_costs = @import("../gas_costs.zig");
57
const host_module = @import("../host.zig");
68
const alloc_mod = @import("zesu_allocator");
79

8-
// ---------------------------------------------------------------------------
9-
// Memory expansion helper
10-
// ---------------------------------------------------------------------------
11-
12-
fn memoryCostWords(num_words: usize) u64 {
13-
const n: u64 = @intCast(num_words);
14-
// Use checked arithmetic: a huge offset must yield OOG, not a panic.
15-
const linear = std.math.mul(u64, n, gas_costs.G_MEMORY) catch return std.math.maxInt(u64);
16-
const quadratic = (std.math.mul(u64, n, n) catch return std.math.maxInt(u64)) / 512;
17-
return std.math.add(u64, linear, quadratic) catch std.math.maxInt(u64);
18-
}
19-
20-
fn expandMemory(ctx: *InstructionContext, new_size: usize) bool {
21-
if (new_size == 0) return true;
22-
const current = ctx.interpreter.memory.size();
23-
if (new_size <= current) return true;
24-
const current_words = (current + 31) / 32;
25-
const new_words = (std.math.add(usize, new_size, 31) catch return false) / 32;
26-
if (new_words > current_words) {
27-
const cost = memoryCostWords(new_words) - memoryCostWords(current_words);
28-
if (!ctx.interpreter.gas.spend(cost)) return false;
29-
}
30-
const aligned_size = new_words * 32;
31-
const old_size = ctx.interpreter.memory.size();
32-
ctx.interpreter.memory.buffer.resize(alloc_mod.get(), aligned_size) catch return false;
33-
@memset(ctx.interpreter.memory.buffer.items[old_size..aligned_size], 0);
34-
return true;
35-
}
36-
3710
// ---------------------------------------------------------------------------
3811
// Account balance / code opcodes
3912
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)