Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions specs/runner.zig
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ pub fn runJsonTest(allocator: std.mem.Allocator, test_case: std.json.Value) !voi

// Parse environment
const env = test_case.object.get("env");

const excess_blob_gas = if (env != null and env.?.object.get("currentExcessBlobGas") != null)
try std.fmt.parseInt(u64, env.?.object.get("currentExcessBlobGas").?.string, 0)
else 0;

const block_info = evm.BlockInfo{
.number = if (env != null and env.?.object.get("currentNumber") != null)
try parseIntFromJson(env.?.object.get("currentNumber").?)
Expand All @@ -73,18 +78,10 @@ pub fn runJsonTest(allocator: std.mem.Allocator, test_case: std.json.Value) !voi
try std.fmt.parseInt(u256, env.?.object.get("currentBaseFee").?.string, 0)
else 10,

.blob_base_fee = if (env != null and env.?.object.get("currentBlobBaseFee") != null)
try std.fmt.parseInt(u256, env.?.object.get("currentBlobBaseFee").?.string, 0)
else 0,
.excess_blob_gas = excess_blob_gas,

.blob_base_fee = primitives.Blob.calculate_blob_gas_price(excess_blob_gas, primitives.Blob.BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE),

.blob_versioned_hashes = blk: {
if (env) |e| if (e.object.get("currentBlobVersionedHashes")) |hashes| {
const bytes = try primitives.Hex.hex_to_bytes(allocator, hashes.string);
break :blk std.mem.bytesAsSlice([32]u8, bytes);
};
break :blk &.{};
},

.prev_randao = blk: {
if (env) |e| if (e.object.get("currentRandom")) |rand| {
const bytes = try primitives.Hex.hex_to_bytes(allocator, rand.string);
Expand Down Expand Up @@ -281,6 +278,25 @@ pub fn runJsonTest(allocator: std.mem.Allocator, test_case: std.json.Value) !voi
const max_priority_fee_per_gas = if (tx.object.get("maxPriorityFeePerGas")) |m| blk: {
break :blk try parseIntFromJson(m);
} else 0;

// Determine max fee per blob gas
const max_fee_per_blob_gas = if (tx.object.get("maxFeePerBlobGas")) |m| blk: {
break :blk try parseIntFromJson(m);
} else 0;

const blob_versioned_hashes = blk: {
if (tx.object.get("blobVersionedHashes")) |hashes| if (hashes == .array) {
var list = try allocator.alloc([32]u8, hashes.array.items.len);
for (hashes.array.items, 0..) |item, i| {
const bytes = try primitives.Hex.hex_to_bytes(allocator, item.string);
defer allocator.free(bytes);
@memcpy(&list[i], bytes[0..32]);
}
break :blk list;
};
break :blk &.{};
};
defer if (blob_versioned_hashes.len > 0) allocator.free(blob_versioned_hashes);

// Create EVM with transaction context (create per transaction to set correct origin)
const tx_context = evm.TransactionContext{
Expand All @@ -289,6 +305,8 @@ pub fn runJsonTest(allocator: std.mem.Allocator, test_case: std.json.Value) !voi
.chain_id = 1,
.max_fee_per_gas = max_fee_per_gas,
.max_priority_fee_per_gas = max_priority_fee_per_gas,
.max_fee_per_blob_gas = max_fee_per_blob_gas,
.blob_versioned_hashes = blob_versioned_hashes,
};

var evm_instance = try evm.DefaultEvm.init(
Expand Down
4 changes: 4 additions & 0 deletions src/block/block_info.zig
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ pub fn BlockInfo(comptime config: BlockInfoConfig) type {
/// Empty slice for non-blob transactions
/// TODO: this is a transaction-level setting (should be in TransactionContext)
blob_versioned_hashes: []const [32]u8 = &.{},
/// Excess blob gas after this block (for next block's pricing)
excess_blob_gas: u64 = 0,
/// Blob gas used in this block
blob_gas_used: u64 = 0,
/// Beacon block root for EIP-4788 (Dencun)
/// Contains the parent beacon block root for trust-minimized access to consensus layer
beacon_root: ?[32]u8 = null,
Expand Down
3 changes: 3 additions & 0 deletions src/block/transaction_context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub const TransactionContext = struct {
/// Priority fee per gas / tip (EIP-1559 type 2+ transactions)
/// For legacy tx, this is 0
max_priority_fee_per_gas: u64 = 0,
/// Maximum fee per blob gas the transaction is willing to pay (EIP-4844)
/// Set to 0 for non-blob transactions
max_fee_per_blob_gas: u256 = 0,
};

test "TransactionContext creation and field access" {
Expand Down
86 changes: 79 additions & 7 deletions src/eips_and_hardforks/eips.zig
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ pub const Eips = struct {
return false;
}


/// EIP-170: Get maximum contract code size based on hardfork
pub fn eip_170_max_code_size(self: Self) u32 {
// EIP-170: Contract code size limit (Spurious Dragon)
Expand Down Expand Up @@ -448,7 +447,7 @@ pub const Eips = struct {

/// Get calldata gas cost for zero/non-zero bytes depending on hardfork
/// Introduced in genesis hardfork and non-zero bytes reduced from 68 to 16 gas in EIP-2028 (Istanbul)
///
///
/// The zero/non-zero bytes are counted in tokens with hardfork-dependent logic above so we can reuse
/// tokens for both calldata and floor gas
pub fn calldata_gas_cost(self: Self, calldata_tokens: u64, is_create: bool, input_len: usize) u64 {
Expand All @@ -459,7 +458,7 @@ pub const Eips = struct {
}
break :blk 0;
};

return base_calldata_cost + init_code_cost;
}

Expand Down Expand Up @@ -506,6 +505,80 @@ pub const Eips = struct {

return .{ .effective_gas_price = base_fee_per_gas + max_priority_fee, .miner_fee = max_priority_fee };
}

/// Get target blob gas per block for current hardfork
pub fn target_blob_gas(self: Self) u64 {
if (!self.is_eip_active(4844)) return 0;
// EIP-7691: Increased blob throughput in Prague
return if (self.hardfork.isAtLeast(.PRAGUE))
primitives.Blob.TARGET_BLOB_GAS_PER_BLOCK_PRAGUE
else
primitives.Blob.TARGET_BLOB_GAS_PER_BLOCK_CANCUN;
}

/// Get maximum blob gas per block for current hardfork
pub fn max_blob_gas(self: Self) u64 {
if (!self.is_eip_active(4844)) return 0;
// EIP-7691: Increased blob throughput in Prague
return if (self.hardfork.isAtLeast(.PRAGUE))
primitives.Blob.MAX_BLOB_GAS_PER_BLOCK_PRAGUE
else
primitives.Blob.MAX_BLOB_GAS_PER_BLOCK_CANCUN;
}

/// Get blob base fee update fraction for current hardfork
pub fn blob_base_fee_update_fraction(self: Self) u64 {
if (!self.is_eip_active(4844)) return 0;
// EIP-7691: Adjusted update fraction in Prague
return if (self.hardfork.isAtLeast(.PRAGUE))
primitives.Blob.BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE
else
primitives.Blob.BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN;
}

/// Calculate blob gas price using exponential formula (EIP-4844)
pub fn blob_gas_price(self: Self, excess_gas: u64) u128 {
if (!self.is_eip_active(4844)) return 0;
const update_fraction = self.blob_base_fee_update_fraction();
return @as(u128, primitives.Blob.calculate_blob_gas_price(excess_gas, update_fraction));
}

/// Validate blob gas parameters for a transaction
pub fn validate_blob_gas(self: Self, blob_count: usize, max_fee_per_blob_gas: u256, current_blob_base_fee: u256) bool {
if (!self.is_eip_active(4844)) return true;
if (blob_count == 0) return true;

if (blob_count > primitives.Blob.MAX_BLOBS_PER_TRANSACTION) return false;
if (max_fee_per_blob_gas == 0) return false;
if (max_fee_per_blob_gas < current_blob_base_fee) return false;

return true;
}

/// Calculate total blob gas cost for a transaction
pub fn blob_gas_cost(self: Self, base_fee: u256, blob_count: usize) u256 {
if (!self.is_eip_active(4844)) return 0;
if (blob_count == 0) return 0;

const blob_gas = @as(u64, blob_count) * primitives.Blob.BLOB_GAS_PER_BLOB;
return @as(u256, blob_gas) * base_fee;
}

/// Calculate maximum blob gas cost for balance checks
pub fn max_blob_gas_cost(self: Self, max_fee_per_blob_gas: u256, blob_count: usize) u256 {
if (!self.is_eip_active(4844)) return 0;
if (blob_count == 0) return 0;

const blob_gas = @as(u64, blob_count) * primitives.Blob.BLOB_GAS_PER_BLOB;
return @as(u256, blob_gas) * max_fee_per_blob_gas;
}

/// Calculate excess blob gas for next block (wrapper for EIP checking)
pub fn excess_blob_gas(self: Self, parent_excess: u64, parent_blob_gas_used: u64) u64 {
if (!self.is_eip_active(4844)) return 0;
const target = self.target_blob_gas();
return primitives.Blob.excess_blob_gas(parent_excess, parent_blob_gas_used, target);
}
};

const std = @import("std");
Expand Down Expand Up @@ -867,9 +940,9 @@ test "edge cases - large calldata" {
test "regression - hardfork ordering" {
// Ensure EIPs are activated in correct order
const hardforks = [_]Hardfork{
.FRONTIER, .HOMESTEAD, .DAO, .TANGERINE, .SPURIOUS, .BYZANTIUM,
.CONSTANTINOPLE, .PETERSBURG, .ISTANBUL, .BERLIN, .LONDON,
.MERGE, .SHANGHAI, .CANCUN, .PRAGUE,
.FRONTIER, .HOMESTEAD, .DAO, .TANGERINE, .SPURIOUS, .BYZANTIUM,
.CONSTANTINOPLE, .PETERSBURG, .ISTANBUL, .BERLIN, .LONDON, .MERGE,
.SHANGHAI, .CANCUN, .PRAGUE,
};

// EIP-2028 should be active from Istanbul onwards
Expand Down Expand Up @@ -1139,7 +1212,6 @@ test "initcode_size_boundaries" {
try std.testing.expectEqual(pre_shanghai.size_limit() * 2, post_shanghai.size_limit());
}


test "specific eip helper functions" {
const frontier = Eips{ .hardfork = Hardfork.FRONTIER };
const spurious = Eips{ .hardfork = Hardfork.SPURIOUS_DRAGON };
Expand Down
Loading
Loading