Skip to content

Commit 96b3306

Browse files
feat: EIP-7843 — SLOTNUM opcode (Amsterdam) (#10)
* feat: EIP-7843 — SLOTNUM opcode (Amsterdam) Adds SLOTNUM (0x4B): pushes the beacon chain slot number from the block environment. Gas: G_BASE (2). Adds slot_number field to BlockEnv and BlockEnvBuilder (defaults to 0). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(eip-7843): make slot_number optional everywhere per review feedback - `BlockEnv.slot_number`: `u64` → `?u64`; default is `null` (absent on pre-Amsterdam / non-beacon chains) - `BlockEnvBuilder`: add `setSlotNumber(?u64)`; propagate `slot_number` through all existing setter methods (previously silently dropped) - `Host.slotNumber()`: returns `?u64` - `opSlotnum`: halt with `invalid_opcode` when slot number is absent - `spec_test/runner.zig`: initialize `slot_number` to `null` Addresses review comment from @garyschulte on PR #10. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 44810d3 commit 96b3306

File tree

7 files changed

+67
-0
lines changed

7 files changed

+67
-0
lines changed

src/bytecode/main.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ pub const SELFBALANCE: u8 = 0x47;
225225
pub const BASEFEE: u8 = 0x48;
226226
pub const BLOBHASH: u8 = 0x49;
227227
pub const BLOBBASEFEE: u8 = 0x4A;
228+
pub const SLOTNUM: u8 = 0x4B;
228229
pub const POP: u8 = 0x50;
229230
pub const MLOAD: u8 = 0x51;
230231
pub const MSTORE: u8 = 0x52;
@@ -387,6 +388,7 @@ pub const OPCODE_INFO: [256]?OpCodeInfo = blk: {
387388
map[BASEFEE] = OpCodeInfo{ .name = "BASEFEE", .inputs = 0, .outputs = 1, .immediate_size = 0, .terminating = false };
388389
map[BLOBHASH] = OpCodeInfo{ .name = "BLOBHASH", .inputs = 1, .outputs = 1, .immediate_size = 0, .terminating = false };
389390
map[BLOBBASEFEE] = OpCodeInfo{ .name = "BLOBBASEFEE", .inputs = 0, .outputs = 1, .immediate_size = 0, .terminating = false };
391+
map[SLOTNUM] = OpCodeInfo{ .name = "SLOTNUM", .inputs = 0, .outputs = 1, .immediate_size = 0, .terminating = false };
390392

391393
// Stack operations
392394
map[POP] = OpCodeInfo{ .name = "POP", .inputs = 1, .outputs = 0, .immediate_size = 0, .terminating = false };

src/context/block.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ pub const BlockEnv = struct {
2929
///
3030
/// Incorporated as part of the Cancun upgrade via EIP-4844.
3131
blob_excess_gas_and_price: ?BlobExcessGasAndPrice,
32+
/// Beacon chain slot number (EIP-7843, Amsterdam+).
33+
///
34+
/// `null` when the block header does not carry a slot number (pre-Amsterdam or
35+
/// non-beacon chains). `SLOTNUM` halts with `invalid_opcode` in that case.
36+
slot_number: ?u64,
3237

3338
pub fn default() BlockEnv {
3439
return .{
@@ -40,6 +45,7 @@ pub const BlockEnv = struct {
4045
.difficulty = @as(primitives.U256, 0),
4146
.prevrandao = null,
4247
.blob_excess_gas_and_price = BlobExcessGasAndPrice.new(0, primitives.BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE),
48+
.slot_number = null,
4349
};
4450
}
4551

@@ -143,6 +149,7 @@ pub const BlockEnvBuilder = struct {
143149
difficulty: ?primitives.U256,
144150
prevrandao: ?primitives.Hash,
145151
blob_excess_gas_and_price: ?BlobExcessGasAndPrice,
152+
slot_number: ?u64,
146153

147154
pub fn new() BlockEnvBuilder {
148155
return .{
@@ -154,6 +161,7 @@ pub const BlockEnvBuilder = struct {
154161
.difficulty = null,
155162
.prevrandao = null,
156163
.blob_excess_gas_and_price = null,
164+
.slot_number = null,
157165
};
158166
}
159167

@@ -167,6 +175,7 @@ pub const BlockEnvBuilder = struct {
167175
.difficulty = self.difficulty,
168176
.prevrandao = self.prevrandao,
169177
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
178+
.slot_number = self.slot_number,
170179
};
171180
}
172181

@@ -180,6 +189,7 @@ pub const BlockEnvBuilder = struct {
180189
.difficulty = self.difficulty,
181190
.prevrandao = self.prevrandao,
182191
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
192+
.slot_number = self.slot_number,
183193
};
184194
}
185195

@@ -193,6 +203,7 @@ pub const BlockEnvBuilder = struct {
193203
.difficulty = self.difficulty,
194204
.prevrandao = self.prevrandao,
195205
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
206+
.slot_number = self.slot_number,
196207
};
197208
}
198209

@@ -206,6 +217,7 @@ pub const BlockEnvBuilder = struct {
206217
.difficulty = self.difficulty,
207218
.prevrandao = self.prevrandao,
208219
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
220+
.slot_number = self.slot_number,
209221
};
210222
}
211223

@@ -219,6 +231,7 @@ pub const BlockEnvBuilder = struct {
219231
.difficulty = self.difficulty,
220232
.prevrandao = self.prevrandao,
221233
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
234+
.slot_number = self.slot_number,
222235
};
223236
}
224237

@@ -232,6 +245,7 @@ pub const BlockEnvBuilder = struct {
232245
.difficulty = difficulty,
233246
.prevrandao = self.prevrandao,
234247
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
248+
.slot_number = self.slot_number,
235249
};
236250
}
237251

@@ -245,6 +259,7 @@ pub const BlockEnvBuilder = struct {
245259
.difficulty = self.difficulty,
246260
.prevrandao = prevrandao,
247261
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
262+
.slot_number = self.slot_number,
248263
};
249264
}
250265

@@ -258,6 +273,21 @@ pub const BlockEnvBuilder = struct {
258273
.difficulty = self.difficulty,
259274
.prevrandao = self.prevrandao,
260275
.blob_excess_gas_and_price = blob_excess_gas_and_price,
276+
.slot_number = self.slot_number,
277+
};
278+
}
279+
280+
pub fn setSlotNumber(self: BlockEnvBuilder, slot_number: ?u64) BlockEnvBuilder {
281+
return .{
282+
.number = self.number,
283+
.beneficiary = self.beneficiary,
284+
.timestamp = self.timestamp,
285+
.gas_limit = self.gas_limit,
286+
.basefee = self.basefee,
287+
.difficulty = self.difficulty,
288+
.prevrandao = self.prevrandao,
289+
.blob_excess_gas_and_price = self.blob_excess_gas_and_price,
290+
.slot_number = slot_number,
261291
};
262292
}
263293

@@ -271,6 +301,7 @@ pub const BlockEnvBuilder = struct {
271301
.difficulty = self.difficulty orelse @as(primitives.U256, 0),
272302
.prevrandao = self.prevrandao,
273303
.blob_excess_gas_and_price = self.blob_excess_gas_and_price orelse BlobExcessGasAndPrice.new(0, primitives.BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE),
304+
.slot_number = self.slot_number,
274305
};
275306
}
276307
};

src/interpreter/host.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ pub const Host = struct {
163163
return self.ctx.blockHash(number);
164164
}
165165

166+
pub fn slotNumber(self: *Host) ?u64 {
167+
return self.ctx.block.slot_number;
168+
}
169+
166170
// -----------------------------------------------------------------------
167171
// Account state access (via journaled_state)
168172
// -----------------------------------------------------------------------

src/interpreter/opcodes/environment.zig

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,25 @@ pub fn opBlobbasefee(ctx: *InstructionContext) void {
512512
}
513513
stack.pushUnsafe(@as(primitives.U256, h.blobBasefee()));
514514
}
515+
516+
/// SLOTNUM (0x4B): Push the beacon chain slot number (EIP-7843, Amsterdam+).
517+
/// Stack: [] -> [slotNumber] Gas: 2 (G_BASE, dispatch)
518+
///
519+
/// Halts with `invalid_opcode` if no host is present or if the block environment
520+
/// does not carry a slot number (e.g. pre-Amsterdam or non-beacon execution).
521+
pub fn opSlotnum(ctx: *InstructionContext) void {
522+
const h = ctx.host orelse {
523+
ctx.interpreter.halt(.invalid_opcode);
524+
return;
525+
};
526+
const slot = h.slotNumber() orelse {
527+
ctx.interpreter.halt(.invalid_opcode);
528+
return;
529+
};
530+
const stack = &ctx.interpreter.stack;
531+
if (!stack.hasSpace(1)) {
532+
ctx.interpreter.halt(.stack_overflow);
533+
return;
534+
}
535+
stack.pushUnsafe(@as(primitives.U256, slot));
536+
}

src/interpreter/opcodes/main.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ pub const opChainid = environment.opChainid;
8585
pub const opBasefee = environment.opBasefee;
8686
pub const opBlobhash = environment.opBlobhash;
8787
pub const opBlobbasefee = environment.opBlobbasefee;
88+
pub const opSlotnum = environment.opSlotnum;
8889

8990
// Host-requiring opcodes (account state, storage, logs, selfdestruct)
9091
pub const host_ops = @import("host_ops.zig");

src/interpreter/protocol_schedule.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ pub fn makeInstructionTable(spec: primitives.SpecId) InstructionTable {
4949
if (primitives.isEnabledIn(spec, .shanghai)) applyShanghaiChanges(&table);
5050
if (primitives.isEnabledIn(spec, .cancun)) applyCancunChanges(&table);
5151
if (primitives.isEnabledIn(spec, .osaka)) applyOsakaChanges(&table);
52+
if (primitives.isEnabledIn(spec, .amsterdam)) applyAmsterdamChanges(&table);
5253

5354
return table;
5455
}
@@ -254,6 +255,11 @@ fn applyOsakaChanges(table: *InstructionTable) void {
254255
table[bytecode_mod.CLZ] = entry(opcodes.opClz, gas_costs.G_LOW);
255256
}
256257

258+
fn applyAmsterdamChanges(table: *InstructionTable) void {
259+
// EIP-7843: SLOTNUM opcode — push beacon chain slot number
260+
table[bytecode_mod.SLOTNUM] = entry(opcodes.opSlotnum, gas_costs.G_BASE);
261+
}
262+
257263
// ---------------------------------------------------------------------------
258264
// Precompile set construction
259265
// ---------------------------------------------------------------------------

src/spec_test/runner.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ pub fn runTestCase(tc: types.TestCase, allocator: std.mem.Allocator) TestOutcome
259259
.difficulty = u256FromBeBytes(tc.block_difficulty),
260260
.prevrandao = tc.prevrandao,
261261
.blob_excess_gas_and_price = blob_excess_gas_and_price,
262+
.slot_number = null,
262263
});
263264

264265
// Build access list for TxEnv

0 commit comments

Comments
 (0)