From a70c9d618fcef041fb61cd212b141ca59abe61e6 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Wed, 7 Jan 2026 16:11:39 +0100 Subject: [PATCH 01/21] add stack checks to most opcodes --- .../Instructions/EvmInstructions.Bitwise.cs | 8 +++ .../Instructions/EvmInstructions.Call.cs | 30 +++++++++++ .../Instructions/EvmInstructions.CodeCopy.cs | 9 ++++ .../Instructions/EvmInstructions.Create.cs | 15 ++++++ .../EvmInstructions.Math2Param.cs | 13 +++++ .../EvmInstructions.Math3Param.cs | 9 ++++ .../Instructions/EvmInstructions.Shifts.cs | 10 ++++ .../Instructions/EvmInstructions.Storage.cs | 50 +++++++++++++++++++ 8 files changed, 144 insertions(+) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs index aaeec10e0bf..11ff19b1cc6 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs @@ -28,6 +28,11 @@ public interface IOpBitwise /// The second operand vector. /// The result of the bitwise operation. static abstract Word Operation(in Word a, in Word b); + + static virtual bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 2; + } } /// @@ -44,6 +49,9 @@ public interface IOpBitwise public static EvmExceptionType InstructionBitwise(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TOpBitwise : struct, IOpBitwise { + if(TOpBitwise.CheckStackUndeflow(ref stack)) + goto StackUnderflow; + // Deduct the operation's gas cost. gasAvailable -= TOpBitwise.GasCost; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index a3db55e61c0..794fadb339a 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -31,6 +31,11 @@ public interface IOpCall /// Returns the specific execution type of the call. /// abstract static ExecutionType ExecutionType { get; } + + /// + /// Checks the stack for underflow conditions specific to call operations. + /// + abstract static bool CheckStackUndeflow(ref EvmStack stack); } /// @@ -39,6 +44,11 @@ public interface IOpCall public struct OpCall : IOpCall { public static ExecutionType ExecutionType => ExecutionType.CALL; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 7; + } } /// @@ -47,6 +57,11 @@ public struct OpCall : IOpCall public struct OpCallCode : IOpCall { public static ExecutionType ExecutionType => ExecutionType.CALLCODE; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 7; + } } /// @@ -55,6 +70,11 @@ public struct OpCallCode : IOpCall public struct OpDelegateCall : IOpCall { public static ExecutionType ExecutionType => ExecutionType.DELEGATECALL; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 6; + } } /// @@ -64,6 +84,11 @@ public struct OpStaticCall : IOpCall { public static bool IsStatic => true; public static ExecutionType ExecutionType => ExecutionType.STATICCALL; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 6; + } } /// @@ -103,6 +128,11 @@ public static EvmExceptionType InstructionCall( // Clear previous return data. vm.ReturnData = null; + if(TOpCall.CheckStackUndeflow(ref stack)) + { + goto StackUnderflow; + } + // Pop the gas limit for the call. if (!stack.PopUInt256(out UInt256 gasLimit)) goto StackUnderflow; // Pop the code source address from the stack. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 04602858408..9a90d996024 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -26,6 +26,15 @@ public interface IOpCodeCopy /// The virtual machine instance providing execution context. /// A read-only span of bytes containing the code. abstract static ReadOnlySpan GetCode(VirtualMachine vm); + + + /// + /// Checks the stack for underflow conditions specific to call operations. + /// + virtual static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 3; + } } /// diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index 06618582ea9..a73bc6b8be6 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -30,6 +30,11 @@ public interface IOpCreate /// Gets the execution type corresponding to the create operation. /// abstract static ExecutionType ExecutionType { get; } + + /// + /// Checks the stack for underflow conditions specific to call operations. + /// + abstract static bool CheckStackUndeflow(ref EvmStack stack); } /// @@ -41,6 +46,11 @@ public struct OpCreate : IOpCreate /// Gets the execution type for the CREATE opcode. /// public static ExecutionType ExecutionType => ExecutionType.CREATE; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 3; + } } /// @@ -52,6 +62,11 @@ public struct OpCreate2 : IOpCreate /// Gets the execution type for the CREATE2 opcode. /// public static ExecutionType ExecutionType => ExecutionType.CREATE2; + + public static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 4; + } } /// diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs index c5254acff00..aa9b93137eb 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -31,6 +31,14 @@ public interface IOpMath2Param /// The second operand. /// The result of the operation. abstract static void Operation(in UInt256 a, in UInt256 b, out UInt256 result); + + /// + /// Checks the stack for underflow conditions specific to call operations. + /// + virtual static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 2; + } } /// @@ -52,6 +60,11 @@ public static EvmExceptionType InstructionMath2Param(Virt where TOpMath : struct, IOpMath2Param where TTracingInst : struct, IFlag { + if(TOpMath.CheckStackUndeflow(ref stack)) + { + goto StackUnderflow; + } + // Deduct the gas cost for the specific math operation. gasAvailable -= TOpMath.GasCost; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs index e6708f6e850..ad91ef3a095 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs @@ -14,6 +14,10 @@ public interface IOpMath3Param { virtual static long GasCost => GasCostOf.Mid; abstract static void Operation(in UInt256 a, in UInt256 b, in UInt256 c, out UInt256 result); + virtual static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 3; + } } [SkipLocalsInit] @@ -21,6 +25,11 @@ public static EvmExceptionType InstructionMath3Param(Virt where TOpMath : struct, IOpMath3Param where TTracingInst : struct, IFlag { + if(TOpMath.CheckStackUndeflow(ref stack)) + { + goto StackUnderflow; + } + gasAvailable -= TOpMath.GasCost; if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs index 8c5bf453f80..ffc8109471d 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs @@ -31,6 +31,14 @@ public interface IOpShift /// The value to be shifted. /// The resulting shifted value. abstract static void Operation(in UInt256 a, in UInt256 b, out UInt256 result); + + /// + /// Checks the stack for underflow conditions specific to call operations. + /// + virtual static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 2; + } } /// @@ -52,6 +60,8 @@ public static EvmExceptionType InstructionShift(VirtualM where TOpShift : struct, IOpShift where TTracingInst : struct, IFlag { + if(TOpShift.CheckStackUndeflow(ref stack)) goto StackUnderflow; + // Deduct gas cost specific to the shift operation. gasAvailable -= TOpShift.GasCost; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs index 9d174c9053c..c64e0d7c4c8 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs @@ -17,6 +17,25 @@ namespace Nethermind.Evm; /// internal static partial class EvmInstructions { + const int TStoreStackRequiredItems = 2; + const int TLoadStackRequiredItems = 1; + + const int SStoreStackRequiredItems = 2; + const int SLoadStackRequiredItems = 1; + + const int MStoreStackRequiredItems = 2; + const int MStore8StackRequiredItems = 2; + const int MLoadStackRequiredItems = 1; + const int MCopyStackRequiredItems = 3; + + const int CallDataLoadStackRequiredItems = 1; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckStackUnderflow(ref EvmStack stack, int itemsNeeded) + { + return stack.Head < itemsNeeded; + } /// /// Executes the transient load (TLOAD) instruction. @@ -34,9 +53,13 @@ internal static partial class EvmInstructions public static EvmExceptionType InstructionTLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + // Increment the opcode metric for TLOAD. Metrics.TloadOpcode++; + if(CheckStackUnderflow(ref stack, TLoadStackRequiredItems)) + goto StackUnderflow; + // Deduct the fixed gas cost for TLOAD. gasAvailable -= GasCostOf.TLoad; @@ -85,6 +108,9 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack // Increment the opcode metric for TSTORE. Metrics.TstoreOpcode++; + if(CheckStackUnderflow(ref stack, TStoreStackRequiredItems)) + goto StackUnderflow; + EvmState vmState = vm.EvmState; // Disallow storage modification during static calls. @@ -140,6 +166,9 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack public static EvmExceptionType InstructionMStore(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, MStoreStackRequiredItems)) + goto StackUnderflow; + gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset; if not available, signal a stack underflow. @@ -185,6 +214,9 @@ public static EvmExceptionType InstructionMStore(VirtualMachine vm public static EvmExceptionType InstructionMStore8(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, MStore8StackRequiredItems)) + goto StackUnderflow; + gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset from the stack; if missing, signal a stack underflow. @@ -230,6 +262,9 @@ public static EvmExceptionType InstructionMStore8(VirtualMachine v public static EvmExceptionType InstructionMLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, MLoadStackRequiredItems)) + goto StackUnderflow; + gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset; if missing, signal a stack underflow. @@ -278,6 +313,9 @@ public static EvmExceptionType InstructionMCopy(VirtualMachine vm, // Increment the opcode metric for MCOPY. Metrics.MCopyOpcode++; + if (CheckStackUnderflow(ref stack, MCopyStackRequiredItems)) + goto StackUnderflow; + // Pop destination, source, and length values; if any are missing, signal a stack underflow. if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; @@ -332,6 +370,9 @@ internal static EvmExceptionType InstructionSStoreUnmetered(Virtua // Increment the SSTORE opcode metric. Metrics.IncrementSStoreOpcode(); + if (CheckStackUnderflow(ref stack, SStoreStackRequiredItems)) + goto StackUnderflow; + EvmState vmState = vm.EvmState; // Disallow storage modifications in static calls. if (vmState.IsStatic) goto StaticCallViolation; @@ -433,6 +474,9 @@ internal static EvmExceptionType InstructionSStoreMetered(VirtualMachine v // Increment the SLOAD opcode metric. Metrics.IncrementSLoadOpcode(); + if (CheckStackUnderflow(ref stack, SLoadStackRequiredItems)) + goto StackUnderflow; + // Deduct the gas cost for performing an SLOAD. gasAvailable -= spec.GetSLoadCost(); @@ -646,6 +693,9 @@ public static EvmExceptionType InstructionCallDataLoad(VirtualMach { gasAvailable -= GasCostOf.VeryLow; + if (CheckStackUnderflow(ref stack, CallDataLoadStackRequiredItems)) + goto StackUnderflow; + // Pop the offset from which to load call data. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; From d17ea155d71a8560bc1855b579ee8b67dcb1b273 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Wed, 7 Jan 2026 19:28:54 +0100 Subject: [PATCH 02/21] remove bound checks from evmstack and delegate them to opcode level --- src/Nethermind/Nethermind.Evm/EvmStack.cs | 211 ++++++------------ .../Instructions/EvmInstructions.Bitwise.cs | 2 - .../Instructions/EvmInstructions.Call.cs | 27 ++- .../Instructions/EvmInstructions.CodeCopy.cs | 29 +-- .../EvmInstructions.ControlFlow.cs | 24 +- .../Instructions/EvmInstructions.Create.cs | 10 +- .../Instructions/EvmInstructions.Crypto.cs | 6 +- .../EvmInstructions.Environment.cs | 30 ++- .../EvmInstructions.Math1Param.cs | 8 + .../EvmInstructions.Math2Param.cs | 11 +- .../EvmInstructions.Math3Param.cs | 4 +- .../Instructions/EvmInstructions.Shifts.cs | 16 +- .../Instructions/EvmInstructions.Stack.cs | 52 ++++- .../Instructions/EvmInstructions.Storage.cs | 30 +-- 14 files changed, 241 insertions(+), 219 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/EvmStack.cs b/src/Nethermind/Nethermind.Evm/EvmStack.cs index a86286434ca..cd9e60850f5 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -20,6 +20,10 @@ namespace Nethermind.Evm; using Word = Vector256; using HalfWord = Vector128; +/// +/// Generic EVM stack implementation with configurable bounds checking. +/// +/// Flag type controlling whether stack overflow/underflow checks are performed. [StructLayout(LayoutKind.Auto)] public ref struct EvmStack { @@ -43,12 +47,8 @@ public EvmStack(scoped in int head, ITxTracer txTracer, scoped in Span byt [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref byte PushBytesRef() { - // Workhorse method int head = Head; - if ((Head = head + 1) >= MaxStackSize) - { - ThrowEvmStackOverflowException(); - } + Head = head + 1; return ref Unsafe.Add(ref MemoryMarshal.GetReference(_bytes), head * WordSize); } @@ -70,7 +70,6 @@ public void PushBytes(scoped ReadOnlySpan value) if (value.Length != WordSize) { ref byte bytes = ref PushBytesRef(); - // Not full entry, clear first Unsafe.As(ref bytes) = default; value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - value.Length), value.Length)); } @@ -90,7 +89,6 @@ public void PushBytes(scoped in ZeroPaddedSpan value) if (valueSpan.Length != WordSize) { ref byte bytes = ref PushBytesRef(); - // Not full entry, clear first Unsafe.As(ref bytes) = default; valueSpan.CopyTo(MemoryMarshal.CreateSpan(ref bytes, value.Length)); } @@ -107,10 +105,7 @@ public void PushByte(byte value) if (TTracingInst.IsActive) _tracer.ReportStackPush(value); - // Build a 256-bit vector: [ 0, 0, 0, (value << 56) ] - // - when viewed as bytes: all zeros except byte[31] == value ref Word head = ref PushedHead(); - // Single 32-byte store: last byte as value head = CreateWordFromUInt64((ulong)value << 56); } @@ -118,16 +113,11 @@ public void PushByte(byte value) public unsafe void Push2Bytes(ref byte value) where TTracingInst : struct, IFlag { - // ushort size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(ushort)); ref Word head = ref PushedHead(); - // Load 2-byte source into the top 16 bits of the last 64-bit lane: - // lane3 covers bytes [24..31], so shifting by 48 bits ulong lane3 = (ulong)Unsafe.As(ref value) << 48; - - // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -135,16 +125,11 @@ public unsafe void Push2Bytes(ref byte value) public unsafe void Push4Bytes(ref byte value) where TTracingInst : struct, IFlag { - // uint size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(uint)); ref Word head = ref PushedHead(); - // Load 4-byte source into the top 32 bits of the last 64-bit lane: - // lane3 covers bytes [24..31], so shifting by 32 bits ulong lane3 = ((ulong)Unsafe.As(ref value)) << 32; - - // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -152,15 +137,11 @@ public unsafe void Push4Bytes(ref byte value) public unsafe void Push8Bytes(ref byte value) where TTracingInst : struct, IFlag { - // ulong size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(ulong)); ref Word head = ref PushedHead(); - // Load 8-byte source into last 64-bit lane ulong lane3 = Unsafe.As(ref value); - - // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -168,14 +149,11 @@ public unsafe void Push8Bytes(ref byte value) public unsafe void Push16Bytes(ref byte value) where TTracingInst : struct, IFlag { - // UInt128 size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(HalfWord)); ref Word head = ref PushedHead(); - // Load 16-byte source into 16-byte source as a Vector128 HalfWord src = Unsafe.As(ref value); - // Single 32-byte store head = Vector256.Create(default, src); } @@ -183,16 +161,10 @@ public unsafe void Push16Bytes(ref byte value) public void Push20Bytes(ref byte value) where TTracingInst : struct, IFlag { - // Address size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, 20); ref Word head = ref PushedHead(); - // build the 4×8-byte lanes: - // - lane0 = 0UL - // - lane1 = first 4 bytes of 'value', shifted up into the high half - // - lane2 = bytes [4..11] of 'value' - // - lane3 = bytes [12..19] of 'value' ulong lane1 = ((ulong)Unsafe.As(ref value)) << 32; ulong lane2 = Unsafe.As(ref Unsafe.Add(ref value, 4)); ulong lane3 = Unsafe.As(ref Unsafe.Add(ref value, 12)); @@ -212,7 +184,6 @@ public void Push32Bytes(in Word value) if (TTracingInst.IsActive) _tracer.TraceWord(in value); - // Single 32-byte store PushedHead() = value; } @@ -231,7 +202,6 @@ public void PushLeftPaddedBytes(ReadOnlySpan value, int padd if (value.Length != WordSize) { ref byte bytes = ref PushBytesRef(); - // Not full entry, clear first Unsafe.As(ref bytes) = default; value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - paddingLength), value.Length)); } @@ -248,10 +218,6 @@ public void PushOne() if (TTracingInst.IsActive) _tracer.ReportStackPush(Bytes.OneByteSpan); - // Build a 256-bit vector: [ 0, 0, 0, (1UL << 56) ] - // - when viewed as bytes: all zeros except byte[31] == 1 - - // Single 32-byte store PushedHead() = CreateWordFromUInt64(1UL << 56); } @@ -262,7 +228,6 @@ public void PushZero() if (TTracingInst.IsActive) _tracer.ReportStackPush(Bytes.ZeroByteSpan); - // Single 32-byte store: Zero PushedHead() = default; } @@ -273,11 +238,10 @@ public unsafe void PushUInt32(uint value) { value = BinaryPrimitives.ReverseEndianness(value); } - // uint size + if (TTracingInst.IsActive) _tracer.TraceBytes(in Unsafe.As(ref value), sizeof(uint)); - // Single 32-byte store PushedHead() = Vector256.Create(0U, 0U, 0U, 0U, 0U, 0U, 0U, value).AsByte(); } @@ -288,21 +252,13 @@ public unsafe void PushUInt64(ulong value) { value = BinaryPrimitives.ReverseEndianness(value); } - // ulong size + if (TTracingInst.IsActive) _tracer.TraceBytes(in Unsafe.As(ref value), sizeof(ulong)); - // Single 32-byte store PushedHead() = CreateWordFromUInt64(value); } - /// - /// Pushes an Uint256 written in big endian. - /// - /// - /// This method is a counterpart to and uses the same, raw data approach to write data back. - /// - public void PushUInt256(in UInt256 value) where TTracingInst : struct, IFlag { @@ -354,34 +310,19 @@ public void PushUInt256(in UInt256 value) public void PushSignedInt256(in Int256.Int256 value) where TTracingInst : struct, IFlag { - // tail call into UInt256 PushUInt256(in Unsafe.As(ref Unsafe.AsRef(in value))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool PopLimbo() + public void PopLimbo() { - if (Head-- == 0) - { - return false; - } - - return true; + Head--; } - /// - /// Pops an Uint256 written in big endian. - /// - /// - /// This method does its own calculations to create the . It knows that 32 bytes were popped with . It doesn't have to check the size of span or slice it. - /// All it does is and then reverse endianness if needed. Then it creates . - /// - /// The returned value. - public bool PopUInt256(out UInt256 result) + public void PopUInt256(out UInt256 result) { Unsafe.SkipInit(out result); ref byte bytes = ref PopBytesByRef(); - if (Unsafe.IsNullRef(ref bytes)) return false; if (Avx2.IsSupported) { @@ -408,7 +349,6 @@ public bool PopUInt256(out UInt256 result) ulong u3, u2, u1, u0; if (BitConverter.IsLittleEndian) { - // Combine read and switch endianness to movbe reg, mem u3 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref bytes)); u2 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong)))); u1 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong)))); @@ -424,18 +364,57 @@ public bool PopUInt256(out UInt256 result) result = new UInt256(u0, u1, u2, u3); } - - return true; } - public readonly bool PeekUInt256IsZero() + public void PopUInt256Unchecked(out UInt256 result) { - int head = Head; - if (head-- == 0) + Unsafe.SkipInit(out result); + ref byte bytes = ref PopBytesByRef(); + if (Avx2.IsSupported) { - return false; + Word data = Unsafe.ReadUnaligned(ref bytes); + Word shuffle = Vector256.Create( + 0x18191a1b1c1d1e1ful, + 0x1011121314151617ul, + 0x08090a0b0c0d0e0ful, + 0x0001020304050607ul).AsByte(); + if (Avx512Vbmi.VL.IsSupported) + { + Word convert = Avx512Vbmi.VL.PermuteVar32x8(data, shuffle); + result = Unsafe.As(ref convert); + } + else + { + Word convert = Avx2.Shuffle(data, shuffle); + Vector256 permute = Avx2.Permute4x64(Unsafe.As>(ref convert), 0b_01_00_11_10); + result = Unsafe.As, UInt256>(ref permute); + } } + else + { + ulong u3, u2, u1, u0; + if (BitConverter.IsLittleEndian) + { + u3 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref bytes)); + u2 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong)))); + u1 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong)))); + u0 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 3 * sizeof(ulong)))); + } + else + { + u3 = Unsafe.ReadUnaligned(ref bytes); + u2 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong))); + u1 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong))); + u0 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 3 * sizeof(ulong))); + } + result = new UInt256(u0, u1, u2, u3); + } + } + + public readonly bool PeekUInt256IsZero() + { + int head = Head - 1; ref byte bytes = ref _bytes[head * WordSize]; return Unsafe.ReadUnaligned(ref bytes).IsZero; } @@ -443,87 +422,53 @@ public readonly bool PeekUInt256IsZero() [MethodImpl(MethodImplOptions.AggressiveInlining)] public readonly ref byte PeekBytesByRef() { - int head = Head; - if (head-- == 0) - { - return ref Unsafe.NullRef(); - } + int head = Head - 1; return ref Unsafe.Add(ref MemoryMarshal.GetReference(_bytes), head * WordSize); } public readonly Span PeekWord256() { - int head = Head; - if (head-- == 0) - { - ThrowEvmStackUnderflowException(); - } - + int head = Head - 1; return _bytes.Slice(head * WordSize, WordSize); } - public Address? PopAddress() => Head-- == 0 ? null : new Address(_bytes.Slice(Head * WordSize + WordSize - AddressSize, AddressSize).ToArray()); + public Address? PopAddress() + => new Address(_bytes.Slice((--Head) * WordSize + WordSize - AddressSize, AddressSize).ToArray()); - public bool PopAddress(out Address address) + public void PopAddress(out Address address) { - if (Head-- == 0) - { - address = null; - return false; - } - + Head--; address = new Address(_bytes.Slice(Head * WordSize + WordSize - AddressSize, AddressSize).ToArray()); - return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref byte PopBytesByRef() { - int head = Head; - if (head == 0) - { - return ref Unsafe.NullRef(); - } - head--; + int head = Head - 1; Head = head; return ref Unsafe.Add(ref MemoryMarshal.GetReference(_bytes), head * WordSize); } public Span PopWord256() - { - ref byte bytes = ref PopBytesByRef(); - if (Unsafe.IsNullRef(ref bytes)) ThrowEvmStackUnderflowException(); - - return MemoryMarshal.CreateSpan(ref bytes, WordSize); - } + => MemoryMarshal.CreateSpan(ref PopBytesByRef(), WordSize); - public bool PopWord256(out Span word) + public void PopWord256(out Span word) { - if (Head-- == 0) - { - word = default; - return false; - } - + Head--; word = _bytes.Slice(Head * WordSize, WordSize); - return true; } public byte PopByte() { ref byte bytes = ref PopBytesByRef(); - - if (Unsafe.IsNullRef(ref bytes)) ThrowEvmStackUnderflowException(); - return Unsafe.Add(ref bytes, WordSize - sizeof(byte)); } [SkipLocalsInit] - public EvmExceptionType Dup(int depth) + public void Dup(int depth) where TTracingInst : struct, IFlag { int head = Head; - if (head < depth) goto StackUnderflow; ref byte bytes = ref MemoryMarshal.GetReference(_bytes); @@ -534,27 +479,17 @@ public EvmExceptionType Dup(int depth) if (TTracingInst.IsActive) Trace(depth); - if (++head >= MaxStackSize) goto StackOverflow; - - Head = head; - - return EvmExceptionType.None; - // Jump forward to be unpredicted by the branch predictor. - StackUnderflow: - return EvmExceptionType.StackUnderflow; - StackOverflow: - return EvmExceptionType.StackOverflow; + Head = head + 1; } public readonly bool EnsureDepth(int depth) => Head >= depth; [SkipLocalsInit] - public readonly EvmExceptionType Swap(int depth) + public readonly void Swap(int depth) where TTracingInst : struct, IFlag { int head = Head; - if (head < depth) goto StackUnderflow; ref byte bytes = ref MemoryMarshal.GetReference(_bytes); @@ -566,18 +501,12 @@ public readonly EvmExceptionType Swap(int depth) Unsafe.WriteUnaligned(ref top, buffer); if (TTracingInst.IsActive) Trace(depth); - - return EvmExceptionType.None; - // Jump forward to be unpredicted by the branch predictor. - StackUnderflow: - return EvmExceptionType.StackUnderflow; } - public readonly bool Exchange(int n, int m) + public readonly void Exchange(int n, int m) where TTracingInst : struct, IFlag { int maxDepth = Math.Max(n, m); - if (!EnsureDepth(maxDepth)) return false; ref byte bytes = ref MemoryMarshal.GetReference(_bytes); @@ -589,8 +518,6 @@ public readonly bool Exchange(int n, int m) Unsafe.WriteUnaligned(ref second, buffer); if (TTracingInst.IsActive) Trace(maxDepth); - - return true; } private readonly void Trace(int depth) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs index 11ff19b1cc6..1d2df184810 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs @@ -57,13 +57,11 @@ public static EvmExceptionType InstructionBitwise(VirtualMachine _, // Pop the first operand from the stack by reference to minimize copying. ref byte bytesRef = ref stack.PopBytesByRef(); - if (IsNullRef(ref bytesRef)) goto StackUnderflow; // Read the 256-bit vector from unaligned memory. Word aVec = ReadUnaligned(ref bytesRef); // Peek at the top of the stack for the second operand without removing it. bytesRef = ref stack.PeekBytesByRef(); - if (IsNullRef(ref bytesRef)) goto StackUnderflow; Word bVec = ReadUnaligned(ref bytesRef); // Write the result directly into the memory of the top stack element. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index 794fadb339a..0e8116fc752 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -134,10 +134,9 @@ public static EvmExceptionType InstructionCall( } // Pop the gas limit for the call. - if (!stack.PopUInt256(out UInt256 gasLimit)) goto StackUnderflow; + stack.PopUInt256(out UInt256 gasLimit); // Pop the code source address from the stack. Address codeSource = stack.PopAddress(); - if (codeSource is null) goto StackUnderflow; ref readonly ExecutionEnvironment env = ref vm.EvmState.Env; // Determine the call value based on the call type. @@ -152,17 +151,13 @@ public static EvmExceptionType InstructionCall( // Delegate calls use the value from the current execution context. callValue = env.Value; } - else if (!stack.PopUInt256(out callValue)) - { - goto StackUnderflow; - } + else stack.PopUInt256(out callValue); // Pop additional parameters: data offset, data length, output offset, and output length. - if (!stack.PopUInt256(out UInt256 dataOffset) || - !stack.PopUInt256(out UInt256 dataLength) || - !stack.PopUInt256(out UInt256 outputOffset) || - !stack.PopUInt256(out UInt256 outputLength)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 dataOffset); + stack.PopUInt256(out UInt256 dataLength); + stack.PopUInt256(out UInt256 outputOffset); + stack.PopUInt256(out UInt256 outputLength); // Charge gas for accessing the account's code (including delegation logic if applicable). if (!EvmCalculations.ChargeAccountAccessGasWithDelegation(ref gasAvailable, vm, codeSource)) goto OutOfGas; @@ -371,10 +366,14 @@ public static EvmExceptionType InstructionReturn( goto BadInstruction; } - // Pop memory position and length for the return data. - if (!stack.PopUInt256(out UInt256 position) || - !stack.PopUInt256(out UInt256 length)) + if(CheckStackUnderflow(ref stack, 2)) + { goto StackUnderflow; + } + + // Pop memory position and length for the return data. + stack.PopUInt256(out UInt256 position); + stack.PopUInt256(out UInt256 length); // Update the memory cost for the region being returned. if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in position, in length)) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 9a90d996024..dc0c9cf0afa 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -64,11 +64,12 @@ public static EvmExceptionType InstructionCodeCopy( where TOpCodeCopy : struct, IOpCodeCopy where TTracingInst : struct, IFlag { + if (TOpCodeCopy.CheckStackUndeflow(ref stack)) goto StackUnderflow; + // Pop destination offset, source offset, and copy length. - if (!stack.PopUInt256(out UInt256 a) || - !stack.PopUInt256(out UInt256 b) || - !stack.PopUInt256(out UInt256 result)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); + stack.PopUInt256(out UInt256 result); // Deduct gas for the operation plus the cost for memory expansion. // Gas cost is calculated as a fixed "VeryLow" cost plus a per-32-bytes cost. @@ -144,14 +145,15 @@ public static EvmExceptionType InstructionExtCodeCopy( where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; + + if (CheckStackUnderflow(ref stack, 4)) goto StackUnderflow; + // Retrieve the target account address. Address address = stack.PopAddress(); // Pop destination offset, source offset, and length from the stack. - if (address is null || - !stack.PopUInt256(out UInt256 a) || - !stack.PopUInt256(out UInt256 b) || - !stack.PopUInt256(out UInt256 result)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); + stack.PopUInt256(out UInt256 result); // Deduct gas cost: cost for external code access plus memory expansion cost. gasAvailable -= spec.GetExtCodeCost() + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in result, out bool outOfGas); @@ -229,13 +231,14 @@ public static EvmExceptionType InstructionExtCodeSize( ref int programCounter) where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + IReleaseSpec spec = vm.Spec; // Deduct the gas cost for external code access. gasAvailable -= spec.GetExtCodeCost(); // Pop the account address from the stack. Address address = stack.PopAddress(); - if (address is null) goto StackUnderflow; // Charge gas for accessing the account's state. if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) @@ -255,12 +258,12 @@ public static EvmExceptionType InstructionExtCodeSize( } // If the next instruction is GT or EQ and the top stack element is zero, // then this pattern likely corresponds to a contract existence check. - else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ) && - stack.PeekUInt256IsZero()) + else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ) + && CheckStackUnderflow(ref stack, 1) && stack.PeekUInt256IsZero()) { optimizeAccess = true; // Remove the zero from the stack since we will have consumed it. - if (!stack.PopLimbo()) goto StackUnderflow; + stack.PopLimbo(); } if (optimizeAccess) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs index 1bd918970b9..8a2a1aeae95 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs @@ -29,6 +29,8 @@ internal static partial class EvmInstructions public static EvmExceptionType InstructionProgramCounter(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Deduct the base gas cost for reading the program counter. gasAvailable -= GasCostOf.Base; // The program counter pushed is adjusted by -1 to reflect the correct opcode location. @@ -73,10 +75,13 @@ public static EvmExceptionType InstructionJumpDest(VirtualMachine vm, ref EvmSta [SkipLocalsInit] public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) { + + if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + // Deduct the gas cost for performing a jump. gasAvailable -= GasCostOf.Jump; // Pop the jump destination from the stack. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Validate the jump destination and update the program counter if valid. if (!Jump(result, ref programCounter, in vm.EvmState.Env)) goto InvalidJumpDestination; @@ -105,10 +110,12 @@ public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack s [MethodImpl(MethodImplOptions.NoInlining)] public static EvmExceptionType InstructionJumpIf(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) { + if(CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; + // Deduct the high gas cost for a conditional jump. gasAvailable -= GasCostOf.JumpI; // Pop the jump destination. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); bool shouldJump = TestJumpCondition(ref stack, out bool isOverflow); if (isOverflow) goto StackUnderflow; @@ -165,12 +172,11 @@ public static EvmExceptionType InstructionStop(VirtualMachine vm, ref EvmStack s [SkipLocalsInit] public static EvmExceptionType InstructionRevert(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) { + if (CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; + // Attempt to pop memory offset and length; if either fails, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 position) || - !stack.PopUInt256(out UInt256 length)) - { - goto StackUnderflow; - } + stack.PopUInt256(out UInt256 position); + stack.PopUInt256(out UInt256 length); // Ensure sufficient gas for any required memory expansion. if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in position, in length)) @@ -200,6 +206,8 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E // Increment metrics for self-destruct operations. Metrics.IncrementSelfDestructs(); + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + EvmState vmState = vm.EvmState; IReleaseSpec spec = vm.Spec; IWorldState state = vm.WorldState; @@ -216,8 +224,6 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E // Pop the inheritor address from the stack; signal underflow if missing. Address inheritor = stack.PopAddress(); - if (inheritor is null) - goto StackUnderflow; // Charge gas for account access; if insufficient, signal out-of-gas. if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, inheritor, chargeForWarm: false)) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index a73bc6b8be6..4277da5f11c 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -93,6 +93,9 @@ public static EvmExceptionType InstructionCreate( // Increment metrics counter for contract creation operations. Metrics.IncrementCreates(); + if(TOpCreate.CheckStackUndeflow(ref stack)) + goto StackUnderflow; + // Obtain the current EVM specification and check if the call is static (static calls cannot create contracts). IReleaseSpec spec = vm.Spec; if (vm.EvmState.IsStatic) @@ -105,10 +108,9 @@ public static EvmExceptionType InstructionCreate( // Pop parameters off the stack: value to transfer, memory position for the initialization code, // and the length of the initialization code. - if (!stack.PopUInt256(out UInt256 value) || - !stack.PopUInt256(out UInt256 memoryPositionOfInitCode) || - !stack.PopUInt256(out UInt256 initCodeLength)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 value); + stack.PopUInt256(out UInt256 memoryPositionOfInitCode); + stack.PopUInt256(out UInt256 initCodeLength); Span salt = default; // For CREATE2, an extra salt value is required. Use type check to differentiate. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs index 374a6914851..e34aa2678f6 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs @@ -21,9 +21,11 @@ internal static partial class EvmInstructions public static EvmExceptionType InstructionKeccak256(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + // Ensure two 256-bit words are available (memory offset and length). - if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); // Deduct gas: base cost plus additional cost per 32-byte word. gasAvailable -= GasCostOf.Sha3 + GasCostOf.Sha3Word * EvmCalculations.Div32Ceiling(in b, out bool outOfGas); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index e8679333e2d..1a1c8ecc450 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -150,6 +150,8 @@ public static EvmExceptionType InstructionEnvAddress(Virtu where TOpEnv : struct, IOpEnvAddress where TTracingInst : struct, IFlag { + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Deduct the gas cost as defined by the operation implementation. gasAvailable -= TOpEnv.GasCost; @@ -177,6 +179,8 @@ public static EvmExceptionType InstructionBlkAddress(Virtu where TOpEnv : struct, IOpBlkAddress where TTracingInst : struct, IFlag { + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Deduct the gas cost as defined by the operation implementation. gasAvailable -= TOpEnv.GasCost; @@ -203,6 +207,8 @@ public static EvmExceptionType InstructionEnvUInt256(Virtu where TOpEnv : struct, IOpEnvUInt256 where TTracingInst : struct, IFlag { + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; ref readonly UInt256 result = ref TOpEnv.Operation(vm.EvmState); @@ -226,6 +232,8 @@ public static EvmExceptionType InstructionBlkUInt256(Virtu where TOpEnv : struct, IOpBlkUInt256 where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; ref readonly UInt256 result = ref TOpEnv.Operation(vm); @@ -249,6 +257,8 @@ public static EvmExceptionType InstructionEnvUInt32(Virtua where TOpEnv : struct, IOpEnvUInt32 where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; uint result = TOpEnv.Operation(vm.EvmState); @@ -272,6 +282,8 @@ public static EvmExceptionType InstructionEnvUInt64(Virtua where TOpEnv : struct, IOpEnvUInt64 where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; ulong result = TOpEnv.Operation(vm.EvmState); @@ -295,6 +307,8 @@ public static EvmExceptionType InstructionBlkUInt64(Virtua where TOpEnv : struct, IOpBlkUInt64 where TTracingInst : struct, IFlag { + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; ulong result = TOpEnv.Operation(vm); @@ -318,6 +332,8 @@ public static EvmExceptionType InstructionEnv32Bytes(Virtu where TOpEnv : struct, IOpEnv32Bytes where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= TOpEnv.GasCost; ref readonly ValueHash256 result = ref TOpEnv.Operation(vm); @@ -498,12 +514,13 @@ public static ref readonly ValueHash256 Operation(VirtualMachine vm) public static EvmExceptionType InstructionBalance(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + IReleaseSpec spec = vm.Spec; // Deduct gas cost for balance operation as per specification. gasAvailable -= spec.GetBalanceCost(); Address address = stack.PopAddress(); - if (address is null) goto StackUnderflow; // Charge gas for account access. If insufficient gas remains, abort. if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; @@ -533,6 +550,8 @@ public static EvmExceptionType InstructionBalance(VirtualMachine v public static EvmExceptionType InstructionSelfBalance(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + gasAvailable -= GasCostOf.SelfBalance; // Get balance for currently executing account. @@ -655,6 +674,8 @@ public static EvmExceptionType InstructionExtCodeHashEof(VirtualMa public static EvmExceptionType InstructionPrevRandao(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Charge the base gas cost for this opcode. gasAvailable -= GasCostOf.Base; stack.Push32Bytes(in vm.BlockExecutionContext.PrevRandao); @@ -676,6 +697,7 @@ public static EvmExceptionType InstructionPrevRandao(VirtualMachin public static EvmExceptionType InstructionGas(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Deduct the base gas cost for reading gas. gasAvailable -= GasCostOf.Base; @@ -708,11 +730,12 @@ public static EvmExceptionType InstructionGas(VirtualMachine vm, r public static EvmExceptionType InstructionBlobHash(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; // Deduct the gas cost for blob hash operation. gasAvailable -= GasCostOf.BlobHash; // Pop the blob index from the stack. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Retrieve the array of versioned blob hashes from the execution context. byte[][] versionedHashes = vm.TxExecutionContext.BlobVersionedHashes; @@ -752,11 +775,12 @@ public static EvmExceptionType InstructionBlobHash(VirtualMachine public static EvmExceptionType InstructionBlockHash(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; // Deduct the gas cost for block hash operation. gasAvailable -= GasCostOf.BlockHash; // Pop the block number from the stack. - if (!stack.PopUInt256(out UInt256 a)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); // Convert the block number to a long. Clamp the value to long.MaxValue if it exceeds it. long number = a > long.MaxValue ? long.MaxValue : (long)a.u0; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs index 115e1090fdd..8b45219004b 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs @@ -33,6 +33,11 @@ public interface IOpMath1Param /// The input 256‐bit vector. /// The result of the operation as a 256‐bit vector. abstract static Word Operation(Word value); + + virtual static bool CheckStackUndeflow(ref EvmStack stack) + { + return stack.Head < 1; + } } /// @@ -53,6 +58,9 @@ public interface IOpMath1Param public static EvmExceptionType InstructionMath1Param(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TOpMath : struct, IOpMath1Param { + if(TOpMath.CheckStackUndeflow(ref stack)) + goto StackUnderflow; + // Deduct the gas cost associated with the math operation. gasAvailable -= TOpMath.GasCost; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs index aa9b93137eb..4efd09bbe7e 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -69,7 +69,8 @@ public static EvmExceptionType InstructionMath2Param(Virt gasAvailable -= TOpMath.GasCost; // Pop two operands from the stack. If either pop fails, jump to the underflow handler. - if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); // Execute the math operation defined by TOpMath. TOpMath.Operation(in a, in b, out UInt256 result); @@ -275,12 +276,16 @@ public static void Operation(in UInt256 a, in UInt256 b, out UInt256 result) public static EvmExceptionType InstructionExp(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + if(CheckStackUnderflow(ref stack, 2)) + { + goto StackUnderflow; + } + // Charge the fixed gas cost for exponentiation. gasAvailable -= GasCostOf.Exp; // Pop the base value from the stack. - if (!stack.PopUInt256(out UInt256 a)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); // Pop the exponent as a 256-bit word. ReadOnlySpan bytes = stack.PopWord256(); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs index ad91ef3a095..7959a07c1ba 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs @@ -32,7 +32,9 @@ public static EvmExceptionType InstructionMath3Param(Virt gasAvailable -= TOpMath.GasCost; - if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); + stack.PopUInt256(out UInt256 c); if (c.IsZero) { diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs index ffc8109471d..a1f0bbee049 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs @@ -66,19 +66,19 @@ public static EvmExceptionType InstructionShift(VirtualM gasAvailable -= TOpShift.GasCost; // Pop the shift amount from the stack. - if (!stack.PopUInt256(out UInt256 a)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); // If the shift amount is 256 or more, per EVM semantics, discard the second operand and push zero. if (a >= 256) { // Pop the second operand without using its value. - if (!stack.PopLimbo()) goto StackUnderflow; + stack.PopLimbo(); stack.PushZero(); } else { // Otherwise, pop the value to be shifted. - if (!stack.PopUInt256(out UInt256 b)) goto StackUnderflow; + stack.PopUInt256(out UInt256 b); // Perform the shift operation using the specific implementation. TOpShift.Operation(in a, in b, out UInt256 result); stack.PushUInt256(in result); @@ -107,11 +107,19 @@ public static EvmExceptionType InstructionShift(VirtualM public static EvmExceptionType InstructionSar(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) where TTracingInst : struct, IFlag { + static bool CheckStackUnderflow(ref EvmStack stack) => stack.Head < 2; + + if(CheckStackUnderflow(ref stack)) + { + goto StackUnderflow; + } + // Deduct the gas cost for the arithmetic shift operation. gasAvailable -= GasCostOf.VeryLow; // Pop the shift amount and the value to be shifted. - if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); // If the shift amount is 256 or more, the result depends solely on the sign of the value. if (a >= 256) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index b24a08cd8bc..808ba6ba616 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -17,6 +17,22 @@ namespace Nethermind.Evm; internal static partial class EvmInstructions { + const int PopStackRequiredItemsCount = 1; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckStackUnderflow(ref EvmStack stack, int itemsNeeded) + { + return stack.Head < itemsNeeded; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool CheckStackOverflow(ref EvmStack stack, int itemsNeeded) + { + return stack.Head > EvmStack.MaxStackSize; + } + + /// /// Pops a value from the EVM stack. /// Deducts the base gas cost and returns an exception if the stack is underflowed. @@ -30,10 +46,15 @@ internal static partial class EvmInstructions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EvmExceptionType InstructionPop(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) { + if (CheckStackUndeflow(ref stack, PopStackRequiredItemsCount)) + return EvmExceptionType.StackUnderflow; + // Deduct the minimal gas cost for a POP operation. gasAvailable -= GasCostOf.Base; // Pop from the stack; if nothing to pop, signal a stack underflow. - return stack.PopLimbo() ? EvmExceptionType.None : EvmExceptionType.StackUnderflow; + stack.PopLimbo(); + + return EvmExceptionType.None; } /// @@ -509,6 +530,10 @@ public static EvmExceptionType InstructionPush(VirtualMa where TOpCount : IOpCount where TTracingInst : struct, IFlag { + if(CheckStackOverflow(ref stack, TOpCount.Count)) { + return EvmExceptionType.StackOverflow; + } + // Deduct a very low gas cost for the push operation. gasAvailable -= GasCostOf.VeryLow; // Retrieve the code segment containing immediate data. @@ -534,9 +559,17 @@ public static EvmExceptionType InstructionDup(VirtualMac where TOpCount : IOpCount where TTracingInst : struct, IFlag { + if(CheckStackUndeflow(ref stack, TOpCount.Count) + || CheckStackOverflow(ref stack, 1)) + { + return EvmExceptionType.StackUnderflow; + } + gasAvailable -= GasCostOf.VeryLow; - return stack.Dup(TOpCount.Count); + stack.Dup(TOpCount.Count); + + return EvmExceptionType.None; } /// @@ -553,9 +586,16 @@ public static EvmExceptionType InstructionSwap(VirtualMa where TOpCount : IOpCount where TTracingInst : struct, IFlag { + if(CheckStackUndeflow(ref stack, TOpCount.Count + 1)) + { + return EvmExceptionType.StackUnderflow; + } + gasAvailable -= GasCostOf.VeryLow; // Swap the top element with the (n+1)th element; ensure adequate stack depth. - return stack.Swap(TOpCount.Count + 1); + stack.Swap(TOpCount.Count + 1); + + return EvmExceptionType.None; } /// @@ -580,8 +620,12 @@ public static EvmExceptionType InstructionLog(VirtualMachine vm, ref E // Logging is not permitted in static call contexts. if (vmState.IsStatic) goto StaticCallViolation; + if(CheckStackUndeflow(ref stack, TOpCount.Count)) + goto StackUnderflow; + // Pop memory offset and length for the log data. - if (!stack.PopUInt256(out UInt256 position) || !stack.PopUInt256(out UInt256 length)) goto StackUnderflow; + stack.PopUInt256(out UInt256 position); + stack.PopUInt256(out UInt256 length); // The number of topics is defined by the generic parameter. long topicsCount = TOpCount.Count; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs index c64e0d7c4c8..09ae7824429 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs @@ -30,13 +30,6 @@ internal static partial class EvmInstructions const int CallDataLoadStackRequiredItems = 1; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckStackUnderflow(ref EvmStack stack, int itemsNeeded) - { - return stack.Head < itemsNeeded; - } - /// /// Executes the transient load (TLOAD) instruction. /// @@ -64,7 +57,7 @@ public static EvmExceptionType InstructionTLoad(VirtualMachine vm, gasAvailable -= GasCostOf.TLoad; // Attempt to pop the key (offset) from the stack; if unavailable, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Construct a transient storage cell using the executing account and the provided offset. StorageCell storageCell = new(vm.EvmState.Env.ExecutingAccount, in result); @@ -120,7 +113,7 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack gasAvailable -= GasCostOf.TStore; // Pop the key (offset) from the stack; if unavailable, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Construct a transient storage cell for the executing account at the specified key. StorageCell storageCell = new(vmState.Env.ExecutingAccount, in result); @@ -172,7 +165,7 @@ public static EvmExceptionType InstructionMStore(VirtualMachine vm gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset; if not available, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Retrieve the 32-byte word to be stored. Span bytes = stack.PopWord256(); @@ -220,7 +213,7 @@ public static EvmExceptionType InstructionMStore8(VirtualMachine v gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset from the stack; if missing, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Pop a single byte from the stack. byte data = stack.PopByte(); @@ -268,7 +261,7 @@ public static EvmExceptionType InstructionMLoad(VirtualMachine vm, gasAvailable -= GasCostOf.VeryLow; // Pop the memory offset; if missing, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); EvmState vmState = vm.EvmState; @@ -317,7 +310,9 @@ public static EvmExceptionType InstructionMCopy(VirtualMachine vm, goto StackUnderflow; // Pop destination, source, and length values; if any are missing, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; + stack.PopUInt256(out UInt256 a); + stack.PopUInt256(out UInt256 b); + stack.PopUInt256(out UInt256 c); // Calculate additional gas cost based on the length (using a division rounding-up method) and deduct the total cost. gasAvailable -= GasCostOf.VeryLow + GasCostOf.VeryLow * EvmCalculations.Div32Ceiling(c, out bool outOfGas); @@ -384,7 +379,7 @@ internal static EvmExceptionType InstructionSStoreUnmetered(Virtua goto OutOfGas; // Pop the key and then the new value for storage; signal underflow if unavailable. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); ReadOnlySpan bytes = stack.PopWord256(); // Determine if the new value is effectively zero and normalize non-zero values by stripping leading zeros. @@ -493,7 +488,7 @@ internal static EvmExceptionType InstructionSStoreMetered bytes = stack.PopWord256(); // Determine if the new value is effectively zero and normalize non-zero values by stripping leading zeros. @@ -652,7 +647,7 @@ internal static EvmExceptionType InstructionSLoad(VirtualMachine v gasAvailable -= spec.GetSLoadCost(); // Pop the key from the stack; if unavailable, signal a stack underflow. - if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Construct the storage cell for the executing account. Address executingAccount = vm.EvmState.Env.ExecutingAccount; @@ -697,8 +692,7 @@ public static EvmExceptionType InstructionCallDataLoad(VirtualMach goto StackUnderflow; // Pop the offset from which to load call data. - if (!stack.PopUInt256(out UInt256 result)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 result); // Load 32 bytes from input data, applying zero padding as needed. stack.PushBytes(vm.EvmState.Env.InputData.SliceWithZeroPadding(result, 32)); From 2140ca7e81d765c0a48b2560f12641dad0701536 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Wed, 7 Jan 2026 20:00:24 +0100 Subject: [PATCH 03/21] add missing normal opcodes --- .../Instructions/EvmInstructions.CodeCopy.cs | 1 - .../Instructions/EvmInstructions.Math1Param.cs | 15 ++++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index dbcac02ab37..0416fe44d2d 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -39,7 +39,6 @@ virtual static bool CheckStackUnderflow(ref EvmStack stack) return stack.Head < 3; } } - } /// /// Copies a portion of code (or call data) into memory. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs index 168dd5c69c3..8112e41e5e8 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs @@ -125,11 +125,13 @@ public static EvmExceptionType InstructionByte(Virtual where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, 2)) + goto StackUnderflow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the byte position and the 256-bit word. - if (!stack.PopUInt256(out UInt256 a)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); Span bytes = stack.PopWord256(); // If the position is out-of-range, push zero. @@ -165,16 +167,15 @@ public static EvmExceptionType InstructionByte(Virtual public static EvmExceptionType InstructionSignExtend(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { + if(CheckStackUnderflow(ref stack, 2)) + goto StackUnderflow; + TGasPolicy.Consume(ref gas, GasCostOf.Low); // Pop the index to determine which byte to use for sign extension. - if (!stack.PopUInt256(out UInt256 a)) - goto StackUnderflow; + stack.PopUInt256(out UInt256 a); if (a >= BigInt32) { - // If the index is out-of-range, no extension is needed. - if (!stack.EnsureDepth(1)) - goto StackUnderflow; return EvmExceptionType.None; } From 3dff561cc568d4e79349acf2d9583517bfffc98e Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Wed, 7 Jan 2026 20:15:54 +0100 Subject: [PATCH 04/21] apply changes to Eof instructions as well --- .../Instructions/EvmInstructions.Eof.cs | 99 ++++++++++++------- 1 file changed, 61 insertions(+), 38 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs index 0141d37ba68..fd35f073b2e 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs @@ -29,6 +29,7 @@ public interface IOpEofCall virtual static bool IsStatic => false; // Specifies the execution type of the call. abstract static ExecutionType ExecutionType { get; } + abstract static bool CheckStackUnderflow(ref EvmStack stack); } /// @@ -37,6 +38,11 @@ public interface IOpEofCall public struct OpEofCall : IOpEofCall { public static ExecutionType ExecutionType => ExecutionType.EOFCALL; + + public static bool CheckStackUnderflow(ref EvmStack stack) + { + return stack.Head < 4; + } } /// @@ -45,6 +51,11 @@ public struct OpEofCall : IOpEofCall public struct OpEofDelegateCall : IOpEofCall { public static ExecutionType ExecutionType => ExecutionType.EOFDELEGATECALL; + + public static bool CheckStackUnderflow(ref EvmStack stack) + { + return stack.Head < 3; + } } /// @@ -54,6 +65,11 @@ public struct OpEofStaticCall : IOpEofCall { public static bool IsStatic => true; public static ExecutionType ExecutionType => ExecutionType.EOFSTATICCALL; + + public static bool CheckStackUnderflow(ref EvmStack stack) + { + return stack.Head < 3; + } } /// @@ -72,6 +88,8 @@ public static EvmExceptionType InstructionReturnDataSize where TTracingInst : struct, IFlag { + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Deduct base gas cost for this instruction. TGasPolicy.Consume(ref gas, GasCostOf.Base); @@ -99,13 +117,11 @@ public static EvmExceptionType InstructionReturnDataCopy where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, 3)) goto StackUnderflow; // Pop the required parameters: destination memory offset, source offset in return data, and number of bytes to copy. - if (!stack.PopUInt256(out UInt256 destOffset) || - !stack.PopUInt256(out UInt256 sourceOffset) || - !stack.PopUInt256(out UInt256 size)) - { - goto StackUnderflow; - } + stack.PopUInt256(out UInt256 destOffset); + stack.PopUInt256(out UInt256 sourceOffset); + stack.PopUInt256(out UInt256 size); // Deduct the fixed gas cost and the memory cost based on the size (rounded up to 32-byte words). TGasPolicy.Consume(ref gas, GasCostOf.VeryLow + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas)); @@ -161,11 +177,13 @@ public static EvmExceptionType InstructionDataLoad(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; // Ensure the instruction is only valid for non-legacy (EOF) code. if (codeInfo.Version == 0) goto BadInstruction; + if (CheckStackUnderflow(ref stack, 1)) return EvmExceptionType.StackUnderflow; // Deduct gas required for data loading. if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoad)) goto OutOfGas; @@ -193,10 +211,13 @@ public static EvmExceptionType InstructionDataLoadN(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) goto OutOfGas; @@ -255,13 +276,12 @@ public static EvmExceptionType InstructionDataCopy(Vir if (codeInfo.Version == 0) goto BadInstruction; + if (CheckStackUnderflow(ref stack, 3)) goto StackUnderflow; + // Pop destination memory offset, data section offset, and size. - if (!stack.PopUInt256(out UInt256 memOffset) || - !stack.PopUInt256(out UInt256 offset) || - !stack.PopUInt256(out UInt256 size)) - { - goto StackUnderflow; - } + stack.PopUInt256(out UInt256 memOffset); + stack.PopUInt256(out UInt256 offset); + stack.PopUInt256(out UInt256 size); // Calculate memory expansion gas cost and deduct overall gas for data copy. if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataCopy + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas)) @@ -539,12 +559,14 @@ public static EvmExceptionType InstructionDupN(Virtual // Read the immediate operand. int imm = codeInfo.CodeSection.Span[programCounter]; - // Duplicate the (imm+1)th stack element. - EvmExceptionType result = stack.Dup(imm + 1); + if(CheckStackUnderflow(ref stack, imm + 1)) return EvmExceptionType.StackUnderflow; + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + // Duplicate the (imm+1)th stack element. + stack.Dup(imm + 1); programCounter += 1; - return result; + return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. OutOfGas: return EvmExceptionType.OutOfGas; @@ -570,11 +592,14 @@ public static EvmExceptionType InstructionSwapN(Virtua // Immediate operand determines the swap index. int n = 1 + (int)codeInfo.CodeSection.Span[programCounter]; - EvmExceptionType result = stack.Swap(n + 1); + + if (CheckStackUnderflow(ref stack, n + 1)) return EvmExceptionType.StackUnderflow; + + stack.Swap(n + 1); programCounter += 1; - return result; + return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. OutOfGas: return EvmExceptionType.OutOfGas; @@ -640,6 +665,8 @@ public static EvmExceptionType InstructionEofCreate(Vi if (vm.VmState.IsStatic) goto StaticCallViolation; + if(CheckStackUnderflow(ref stack, 4)) return EvmExceptionType.StackUnderflow; + // Cast the current code info to EOF-specific container type. EofCodeInfo container = env.CodeInfo as EofCodeInfo; ExecutionType currentContext = ExecutionType.EOFCREATE; @@ -653,13 +680,10 @@ public static EvmExceptionType InstructionEofCreate(Vi int initContainerIndex = codeSection[programCounter++]; // 3. Pop contract creation parameters from the stack. - if (!stack.PopUInt256(out UInt256 value) || - !stack.PopWord256(out Span salt) || - !stack.PopUInt256(out UInt256 dataOffset) || - !stack.PopUInt256(out UInt256 dataSize)) - { - goto OutOfGas; - } + stack.PopUInt256(out UInt256 value); + stack.PopWord256(out Span salt); + stack.PopUInt256(out UInt256 dataOffset); + stack.PopUInt256(out UInt256 dataSize); // 4. Charge for memory expansion for the input data. if (!TGasPolicy.UpdateMemoryCost(ref gas, in dataOffset, dataSize, vm.VmState)) @@ -795,6 +819,8 @@ public static EvmExceptionType InstructionReturnCode(VirtualMachine< if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.ReturnCode)) goto OutOfGas; + if(CheckStackUnderflow(ref stack, 2)) return EvmExceptionType.StackUnderflow; + IReleaseSpec spec = vm.Spec; EofCodeInfo codeInfo = (EofCodeInfo)vm.VmState.Env.CodeInfo; @@ -847,10 +873,11 @@ public static EvmExceptionType InstructionReturnDataLoad(slice); @@ -893,13 +920,13 @@ public static EvmExceptionType InstructionEofCall targetBytes) || - !stack.PopUInt256(out UInt256 dataOffset) || - !stack.PopUInt256(out UInt256 dataLength)) - { + if(TOpEofCall.CheckStackUnderflow(ref stack)) goto StackUnderflow; - } + + // 1. Pop the target address (as 32 bytes) and memory offsets/length for the call data. + stack.PopWord256(out Span targetBytes); + stack.PopUInt256(out UInt256 dataOffset); + stack.PopUInt256(out UInt256 dataLength); UInt256 transferValue; UInt256 callValue; @@ -914,14 +941,10 @@ public static EvmExceptionType InstructionEofCall Date: Wed, 7 Jan 2026 20:24:09 +0100 Subject: [PATCH 05/21] fix debug build issues --- src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs index c4bfadd34fa..ba71b881ae1 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs @@ -303,7 +303,8 @@ public void Debugger_Can_Alter_Data_Stack(string bytecodeHex) { // we pop the condition and overwrite it with a false to force breaking out of the loop EvmStack stack = new(tracer.CurrentState.DataStackHead, tracer, tracer.CurrentState.DataStack); - if (!stack.PopLimbo()) throw new EvmStackUnderflowException(); + if (stack.Head == 0) throw new EvmStackUnderflowException(); + stack.PopLimbo(); stack.PushByte(0x00); tracer.MoveNext(); From d396bc81c4cea6993b00473ab274f809882d23cc Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 00:32:01 +0100 Subject: [PATCH 06/21] fixed check for push; added check for blobhashfee --- .../Nethermind.Evm/Instructions/EvmInstructions.Environment.cs | 1 + .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 29199dd02bd..7403f37e0f9 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -466,6 +466,7 @@ public static EvmExceptionType InstructionBlobBaseFee( // If the blob base fee is missing (no ExcessBlobGas set), this opcode is invalid. if (!context.Header.ExcessBlobGas.HasValue) goto BadInstruction; + if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Charge the base gas cost for this opcode. TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.Push32Bytes(in context.BlobBaseFee); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index 1541de05b5b..00c7cbb450a 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -537,7 +537,7 @@ public static EvmExceptionType InstructionPush Date: Thu, 8 Jan 2026 00:53:55 +0100 Subject: [PATCH 07/21] add overflow check for Push0 and underflow check for extcodehash --- .../Instructions/EvmInstructions.Environment.cs | 6 ++++-- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 7403f37e0f9..51fe0f4fbf2 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -634,11 +634,12 @@ public static EvmExceptionType InstructionExtCodeHash( where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + IReleaseSpec spec = vm.Spec; TGasPolicy.Consume(ref gas, spec.GetExtCodeHashCost()); Address address = stack.PopAddress(); - if (address is null) goto StackUnderflow; // Check if enough gas for account access and charge accordingly. if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; @@ -683,10 +684,11 @@ public static EvmExceptionType InstructionExtCodeHashEof(Virtua where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { + + if (CheckStackOverflow(ref stack, 1)) + { + return EvmExceptionType.StackOverflow; + } + TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.PushZero(); return EvmExceptionType.None; From 3961a83d3c99834515fc5f7a29bae56382198984 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 00:58:22 +0100 Subject: [PATCH 08/21] fix stackoverflow check --- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index 1dcfa226dfe..fa61d6f13c2 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -22,15 +22,15 @@ internal static partial class EvmInstructions [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckStackUnderflow(ref EvmStack stack, int itemsNeeded) + public static bool CheckStackUnderflow(ref EvmStack stack, int itemsPoppedCount) { - return stack.Head < itemsNeeded; + return stack.Head < itemsPoppedCount; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool CheckStackOverflow(ref EvmStack stack, int itemsNeeded) + public static bool CheckStackOverflow(ref EvmStack stack, int itemsPushedCount) { - return stack.Head > EvmStack.MaxStackSize; + return stack.Head + itemsPushedCount > EvmStack.MaxStackSize; } From f614f7f69492d547638db7890ef22cb97b492289 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 01:14:35 +0100 Subject: [PATCH 09/21] fix overflow check --- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index fa61d6f13c2..f4360a1d44a 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -30,7 +30,7 @@ public static bool CheckStackUnderflow(ref EvmStack stack, int itemsPoppedCount) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CheckStackOverflow(ref EvmStack stack, int itemsPushedCount) { - return stack.Head + itemsPushedCount > EvmStack.MaxStackSize; + return stack.Head + itemsPushedCount >= EvmStack.MaxStackSize; } From ffa25eaf4ab96e89d4d7d3e596bd92c019434c02 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 11:43:41 +0100 Subject: [PATCH 10/21] fix Keccak underflow check --- .../Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs index d44ceececb6..b1de9caa0d5 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs @@ -23,7 +23,7 @@ public static EvmExceptionType InstructionKeccak256(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; // Ensure two 256-bit words are available (memory offset and length). stack.PopUInt256(out UInt256 a); From 5b3094097fef579662df397177e6bc280bf0d394 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 12:00:49 +0100 Subject: [PATCH 11/21] fix checks for multiple opcodes --- .../Instructions/EvmInstructions.Stack.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index f4360a1d44a..0a2cca587e3 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -575,11 +575,11 @@ public static EvmExceptionType InstructionDup(VirtualMachi // Logging is not permitted in static call contexts. if (vmState.IsStatic) goto StaticCallViolation; - if(CheckStackUnderflow(ref stack, TOpCount.Count)) + if(CheckStackUnderflow(ref stack, TOpCount.Count + 2)) goto StackUnderflow; // Pop memory offset and length for the log data. From ba63cde1cfeb177a9723d854a26ee3c70cdd82c3 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 12:33:03 +0100 Subject: [PATCH 12/21] add check to Push2 special handling --- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index 0a2cca587e3..9b0199e11ab 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -142,6 +142,12 @@ public static EvmExceptionType InstructionPush2(Virtua where TTracingInst : struct, IFlag { const int Size = sizeof(ushort); + + if (CheckStackOverflow(ref stack, 1)) + { + return EvmExceptionType.StackOverflow; + } + // Deduct a very low gas cost for the push operation. TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Retrieve the code segment containing immediate data. From 8db675264c5921875a8dca1784ab966ff9f603c9 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 12:44:33 +0100 Subject: [PATCH 13/21] ws fix --- .../Instructions/EvmInstructions.Bitwise.cs | 2 +- .../Instructions/EvmInstructions.Call.cs | 4 ++-- .../EvmInstructions.ControlFlow.cs | 6 ++--- .../Instructions/EvmInstructions.Create.cs | 2 +- .../EvmInstructions.Environment.cs | 22 +++++++++---------- .../Instructions/EvmInstructions.Eof.cs | 15 +++++++------ .../EvmInstructions.Math1Param.cs | 4 ++-- .../EvmInstructions.Math2Param.cs | 4 ++-- .../EvmInstructions.Math3Param.cs | 2 +- .../Instructions/EvmInstructions.Shifts.cs | 4 ++-- .../Instructions/EvmInstructions.Stack.cs | 11 +++++----- .../Instructions/EvmInstructions.Storage.cs | 4 ++-- 12 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs index 969c57c490f..80e3fad7a7c 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs @@ -52,7 +52,7 @@ public static EvmExceptionType InstructionBitwise(Virtua where TGasPolicy : struct, IGasPolicy where TOpBitwise : struct, IOpBitwise { - if(TOpBitwise.CheckStackUnderflow(ref stack)) + if (TOpBitwise.CheckStackUnderflow(ref stack)) goto StackUnderflow; // Deduct the operation's gas cost. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index 51dde612251..afd2ce56899 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -128,7 +128,7 @@ public static EvmExceptionType InstructionCall(VirtualMachine where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Deduct the base gas cost for reading the program counter. TGasPolicy.Consume(ref gas, GasCostOf.Base); @@ -80,7 +80,7 @@ public static EvmExceptionType InstructionJump(VirtualMachine { - if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; // Deduct the gas cost for performing a jump. TGasPolicy.Consume(ref gas, GasCostOf.Jump); @@ -115,7 +115,7 @@ public static EvmExceptionType InstructionJump(VirtualMachine(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - if(CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; // Deduct the high gas cost for a conditional jump. TGasPolicy.Consume(ref gas, GasCostOf.JumpI); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index 236910bb8af..0523b167cf1 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -95,7 +95,7 @@ public static EvmExceptionType InstructionCreate where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; TGasPolicy.Consume(ref gas, TOpEnv.GasCost); @@ -283,7 +283,7 @@ public static EvmExceptionType InstructionEnvUInt32 where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; TGasPolicy.Consume(ref gas, TOpEnv.GasCost); @@ -310,7 +310,7 @@ public static EvmExceptionType InstructionEnvUInt64 where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; TGasPolicy.Consume(ref gas, TOpEnv.GasCost); @@ -364,7 +364,7 @@ public static EvmExceptionType InstructionEnv32Bytes where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; TGasPolicy.Consume(ref gas, TOpEnv.GasCost); @@ -466,7 +466,7 @@ public static EvmExceptionType InstructionBlobBaseFee( // If the blob base fee is missing (no ExcessBlobGas set), this opcode is invalid. if (!context.Header.ExcessBlobGas.HasValue) goto BadInstruction; - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Charge the base gas cost for this opcode. TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.Push32Bytes(in context.BlobBaseFee); @@ -566,7 +566,7 @@ public static EvmExceptionType InstructionBalance(Virt where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; IReleaseSpec spec = vm.Spec; // Deduct gas cost for balance operation as per specification. @@ -604,7 +604,7 @@ public static EvmExceptionType InstructionSelfBalance( where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; TGasPolicy.Consume(ref gas, GasCostOf.SelfBalance); @@ -736,7 +736,7 @@ public static EvmExceptionType InstructionPrevRandao(V where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Charge the base gas cost for this opcode. TGasPolicy.Consume(ref gas, GasCostOf.Base); @@ -761,7 +761,7 @@ public static EvmExceptionType InstructionGas(VirtualM where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Deduct the base gas cost for reading gas. TGasPolicy.Consume(ref gas, GasCostOf.Base); @@ -796,7 +796,7 @@ public static EvmExceptionType InstructionBlobHash(Vir where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; // Deduct the gas cost for blob hash operation. TGasPolicy.Consume(ref gas, GasCostOf.BlobHash); @@ -843,7 +843,7 @@ public static EvmExceptionType InstructionBlockHash(Vi where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; + if (CheckStackUnderflow(ref stack, 1)) goto StackUnderflow; // Deduct the gas cost for block hash operation. TGasPolicy.Consume(ref gas, GasCostOf.BlockHash); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs index fd35f073b2e..f3178519484 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs @@ -216,7 +216,7 @@ public static EvmExceptionType InstructionDataLoadN(Vi if (codeInfo.Version == 0) goto BadInstruction; - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) goto OutOfGas; @@ -560,8 +560,8 @@ public static EvmExceptionType InstructionDupN(Virtual // Read the immediate operand. int imm = codeInfo.CodeSection.Span[programCounter]; - if(CheckStackUnderflow(ref stack, imm + 1)) return EvmExceptionType.StackUnderflow; - if(CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; + if (CheckStackUnderflow(ref stack, imm + 1)) return EvmExceptionType.StackUnderflow; + if (CheckStackOverflow(ref stack, 1)) return EvmExceptionType.StackOverflow; // Duplicate the (imm+1)th stack element. stack.Dup(imm + 1); programCounter += 1; @@ -665,7 +665,7 @@ public static EvmExceptionType InstructionEofCreate(Vi if (vm.VmState.IsStatic) goto StaticCallViolation; - if(CheckStackUnderflow(ref stack, 4)) return EvmExceptionType.StackUnderflow; + if (CheckStackUnderflow(ref stack, 4)) return EvmExceptionType.StackUnderflow; // Cast the current code info to EOF-specific container type. EofCodeInfo container = env.CodeInfo as EofCodeInfo; @@ -819,7 +819,7 @@ public static EvmExceptionType InstructionReturnCode(VirtualMachine< if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.ReturnCode)) goto OutOfGas; - if(CheckStackUnderflow(ref stack, 2)) return EvmExceptionType.StackUnderflow; + if (CheckStackUnderflow(ref stack, 2)) return EvmExceptionType.StackUnderflow; IReleaseSpec spec = vm.Spec; EofCodeInfo codeInfo = (EofCodeInfo)vm.VmState.Env.CodeInfo; @@ -920,7 +920,7 @@ public static EvmExceptionType InstructionEofCall(Virtua where TGasPolicy : struct, IGasPolicy where TOpMath : struct, IOpMath1Param { - if(TOpMath.CheckStackUnderflow(ref stack)) + if (TOpMath.CheckStackUnderflow(ref stack)) goto StackUnderflow; // Deduct the gas cost associated with the math operation. @@ -167,7 +167,7 @@ public static EvmExceptionType InstructionByte(Virtual public static EvmExceptionType InstructionSignExtend(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) where TGasPolicy : struct, IGasPolicy { - if(CheckStackUnderflow(ref stack, 2)) + if (CheckStackUnderflow(ref stack, 2)) goto StackUnderflow; TGasPolicy.Consume(ref gas, GasCostOf.Low); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs index 932bcd466a2..594ef77aab9 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -63,7 +63,7 @@ public static EvmExceptionType InstructionMath2Param(VirtualM where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - if(CheckStackUnderflow(ref stack, 2)) + if (CheckStackUnderflow(ref stack, 2)) { goto StackUnderflow; } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs index 939924fe126..c86363e539b 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs @@ -27,7 +27,7 @@ public static EvmExceptionType InstructionMath3Param(VirtualM { static bool CheckStackUnderflow(ref EvmStack stack) => stack.Head < 2; - if(CheckStackUnderflow(ref stack)) + if (CheckStackUnderflow(ref stack)) { goto StackUnderflow; } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index 9b0199e11ab..d86db59b1c9 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -549,7 +549,8 @@ public static EvmExceptionType InstructionPush(VirtualMachi // Logging is not permitted in static call contexts. if (vmState.IsStatic) goto StaticCallViolation; - if(CheckStackUnderflow(ref stack, TOpCount.Count + 2)) + if (CheckStackUnderflow(ref stack, TOpCount.Count + 2)) goto StackUnderflow; // Pop memory offset and length for the log data. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs index c2ac307c16d..3631bd03f62 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs @@ -52,7 +52,7 @@ public static EvmExceptionType InstructionTLoad(Virtua // Increment the opcode metric for TLOAD. Metrics.TloadOpcode++; - if(CheckStackUnderflow(ref stack, TLoadStackRequiredItems)) + if (CheckStackUnderflow(ref stack, TLoadStackRequiredItems)) goto StackUnderflow; // Deduct the fixed gas cost for TLOAD. @@ -104,7 +104,7 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vmState = vm.VmState; From bb3f4351af4b34bd9b93f9b7b42cdffe7111750c Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 16:23:47 +0100 Subject: [PATCH 14/21] Update src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 0416fe44d2d..2a76543a9db 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -268,7 +268,7 @@ public static EvmExceptionType InstructionExtCodeSize( // If the next instruction is GT or EQ and the top stack element is zero, // then this pattern likely corresponds to a contract existence check. else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ) - && CheckStackUnderflow(ref stack, 1) && stack.PeekUInt256IsZero()) + && !CheckStackUnderflow(ref stack, 1) && stack.PeekUInt256IsZero()) { optimizeAccess = true; // Remove the zero from the stack since we will have consumed it. From 7b746dbb874e13df92ea6641ee73e11a239a15af Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 16:24:48 +0100 Subject: [PATCH 15/21] remove unused method --- src/Nethermind/Nethermind.Evm/EvmStack.cs | 46 ----------------------- 1 file changed, 46 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/EvmStack.cs b/src/Nethermind/Nethermind.Evm/EvmStack.cs index 4a1ebb112c6..70c58b0c0c6 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -366,52 +366,6 @@ public void PopUInt256(out UInt256 result) } } - public void PopUInt256Unchecked(out UInt256 result) - { - Unsafe.SkipInit(out result); - ref byte bytes = ref PopBytesByRef(); - if (Avx2.IsSupported) - { - Word data = Unsafe.ReadUnaligned(ref bytes); - Word shuffle = Vector256.Create( - 0x18191a1b1c1d1e1ful, - 0x1011121314151617ul, - 0x08090a0b0c0d0e0ful, - 0x0001020304050607ul).AsByte(); - if (Avx512Vbmi.VL.IsSupported) - { - Word convert = Avx512Vbmi.VL.PermuteVar32x8(data, shuffle); - result = Unsafe.As(ref convert); - } - else - { - Word convert = Avx2.Shuffle(data, shuffle); - Vector256 permute = Avx2.Permute4x64(Unsafe.As>(ref convert), 0b_01_00_11_10); - result = Unsafe.As, UInt256>(ref permute); - } - } - else - { - ulong u3, u2, u1, u0; - if (BitConverter.IsLittleEndian) - { - u3 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref bytes)); - u2 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong)))); - u1 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong)))); - u0 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 3 * sizeof(ulong)))); - } - else - { - u3 = Unsafe.ReadUnaligned(ref bytes); - u2 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong))); - u1 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong))); - u0 = Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 3 * sizeof(ulong))); - } - - result = new UInt256(u0, u1, u2, u3); - } - } - public readonly bool PeekUInt256IsZero() { int head = Head - 1; From b570ab872c53a64c852c9c25c6bd5c088cc125c8 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 8 Jan 2026 16:40:05 +0100 Subject: [PATCH 16/21] put meaniningful comments back --- src/Nethermind/Nethermind.Evm/EvmStack.cs | 60 ++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/EvmStack.cs b/src/Nethermind/Nethermind.Evm/EvmStack.cs index 70c58b0c0c6..c23776fc909 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -20,10 +20,6 @@ namespace Nethermind.Evm; using Word = Vector256; using HalfWord = Vector128; -/// -/// Generic EVM stack implementation with configurable bounds checking. -/// -/// Flag type controlling whether stack overflow/underflow checks are performed. [StructLayout(LayoutKind.Auto)] public ref struct EvmStack { @@ -89,6 +85,7 @@ public void PushBytes(scoped in ZeroPaddedSpan value) if (valueSpan.Length != WordSize) { ref byte bytes = ref PushBytesRef(); + // Not full entry, clear first Unsafe.As(ref bytes) = default; valueSpan.CopyTo(MemoryMarshal.CreateSpan(ref bytes, value.Length)); } @@ -105,7 +102,10 @@ public void PushByte(byte value) if (TTracingInst.IsActive) _tracer.ReportStackPush(value); + // Build a 256-bit vector: [ 0, 0, 0, (value << 56) ] + // - when viewed as bytes: all zeros except byte[31] == value ref Word head = ref PushedHead(); + // Single 32-byte store: last byte as value head = CreateWordFromUInt64((ulong)value << 56); } @@ -113,11 +113,16 @@ public void PushByte(byte value) public unsafe void Push2Bytes(ref byte value) where TTracingInst : struct, IFlag { + // ushort size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(ushort)); ref Word head = ref PushedHead(); + // Load 2-byte source into the top 16 bits of the last 64-bit lane: + // lane3 covers bytes [24..31], so shifting by 48 bits ulong lane3 = (ulong)Unsafe.As(ref value) << 48; + + // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -125,11 +130,16 @@ public unsafe void Push2Bytes(ref byte value) public unsafe void Push4Bytes(ref byte value) where TTracingInst : struct, IFlag { + // uint size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(uint)); ref Word head = ref PushedHead(); + // Load 4-byte source into the top 32 bits of the last 64-bit lane: + // lane3 covers bytes [24..31], so shifting by 32 bits ulong lane3 = ((ulong)Unsafe.As(ref value)) << 32; + + // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -137,11 +147,15 @@ public unsafe void Push4Bytes(ref byte value) public unsafe void Push8Bytes(ref byte value) where TTracingInst : struct, IFlag { + // ulong size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(ulong)); ref Word head = ref PushedHead(); + // Load 8-byte source into last 64-bit lane ulong lane3 = Unsafe.As(ref value); + + // Single 32-byte store head = CreateWordFromUInt64(lane3); } @@ -149,11 +163,14 @@ public unsafe void Push8Bytes(ref byte value) public unsafe void Push16Bytes(ref byte value) where TTracingInst : struct, IFlag { + // UInt128 size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, sizeof(HalfWord)); ref Word head = ref PushedHead(); + // Load 16-byte source into 16-byte source as a Vector128 HalfWord src = Unsafe.As(ref value); + // Single 32-byte store head = Vector256.Create(default, src); } @@ -161,14 +178,21 @@ public unsafe void Push16Bytes(ref byte value) public void Push20Bytes(ref byte value) where TTracingInst : struct, IFlag { + // Address size if (TTracingInst.IsActive) _tracer.TraceBytes(in value, 20); ref Word head = ref PushedHead(); + // build the 4×8-byte lanes: + // - lane0 = 0UL + // - lane1 = first 4 bytes of 'value', shifted up into the high half + // - lane2 = bytes [4..11] of 'value' + // - lane3 = bytes [12..19] of 'value' ulong lane1 = ((ulong)Unsafe.As(ref value)) << 32; ulong lane2 = Unsafe.As(ref Unsafe.Add(ref value, 4)); ulong lane3 = Unsafe.As(ref Unsafe.Add(ref value, 12)); + // Single 32-byte store head = Vector256.Create(default, lane1, lane2, lane3).AsByte(); } @@ -202,6 +226,7 @@ public void PushLeftPaddedBytes(ReadOnlySpan value, int padd if (value.Length != WordSize) { ref byte bytes = ref PushBytesRef(); + // Not full entry, clear first Unsafe.As(ref bytes) = default; value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - paddingLength), value.Length)); } @@ -218,6 +243,10 @@ public void PushOne() if (TTracingInst.IsActive) _tracer.ReportStackPush(Bytes.OneByteSpan); + // Build a 256-bit vector: [ 0, 0, 0, (1UL << 56) ] + // - when viewed as bytes: all zeros except byte[31] == 1 + + // Single 32-byte store PushedHead() = CreateWordFromUInt64(1UL << 56); } @@ -228,6 +257,7 @@ public void PushZero() if (TTracingInst.IsActive) _tracer.ReportStackPush(Bytes.ZeroByteSpan); + // Single 32-byte store: Zero PushedHead() = default; } @@ -238,10 +268,11 @@ public unsafe void PushUInt32(uint value) { value = BinaryPrimitives.ReverseEndianness(value); } - + // uint size if (TTracingInst.IsActive) _tracer.TraceBytes(in Unsafe.As(ref value), sizeof(uint)); + // Single 32-byte store PushedHead() = Vector256.Create(0U, 0U, 0U, 0U, 0U, 0U, 0U, value).AsByte(); } @@ -252,13 +283,20 @@ public unsafe void PushUInt64(ulong value) { value = BinaryPrimitives.ReverseEndianness(value); } - + // ulong size if (TTracingInst.IsActive) _tracer.TraceBytes(in Unsafe.As(ref value), sizeof(ulong)); + // Single 32-byte store PushedHead() = CreateWordFromUInt64(value); } + /// + /// Pushes an Uint256 written in big endian. + /// + /// + /// This method is a counterpart to and uses the same, raw data approach to write data back. + /// public void PushUInt256(in UInt256 value) where TTracingInst : struct, IFlag { @@ -310,6 +348,7 @@ public void PushUInt256(in UInt256 value) public void PushSignedInt256(in Int256.Int256 value) where TTracingInst : struct, IFlag { + // tail call into UInt256 PushUInt256(in Unsafe.As(ref Unsafe.AsRef(in value))); } @@ -319,6 +358,14 @@ public void PopLimbo() Head--; } + /// + /// Pops an Uint256 written in big endian. + /// + /// + /// This method does its own calculations to create the . It knows that 32 bytes were popped with . It doesn't have to check the size of span or slice it. + /// All it does is and then reverse endianness if needed. Then it creates . + /// + /// The returned value. public void PopUInt256(out UInt256 result) { Unsafe.SkipInit(out result); @@ -349,6 +396,7 @@ public void PopUInt256(out UInt256 result) ulong u3, u2, u1, u0; if (BitConverter.IsLittleEndian) { + // Combine read and switch endianness to movbe reg, mem u3 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref bytes)); u2 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, sizeof(ulong)))); u1 = BinaryPrimitives.ReverseEndianness(Unsafe.ReadUnaligned(ref Unsafe.Add(ref bytes, 2 * sizeof(ulong)))); From 08cd2e92b51081795e33bbd431ca55cb6dc29cd0 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Fri, 9 Jan 2026 12:29:29 +0100 Subject: [PATCH 17/21] remove empty line as copilot suggested Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index d86db59b1c9..e1323accad4 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -20,7 +20,6 @@ internal static partial class EvmInstructions { const int PopStackRequiredItemsCount = 1; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CheckStackUnderflow(ref EvmStack stack, int itemsPoppedCount) { From c48df0c786c80f519d1f79eb95b30ddf1ff86ba6 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 15 Jan 2026 14:40:40 +0100 Subject: [PATCH 18/21] throw on peephole opt of CODESIZE --- .../Instructions/EvmInstructions.CodeCopy.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 2a76543a9db..9f8d4bcb3f2 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -267,12 +267,19 @@ public static EvmExceptionType InstructionExtCodeSize( } // If the next instruction is GT or EQ and the top stack element is zero, // then this pattern likely corresponds to a contract existence check. - else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ) - && !CheckStackUnderflow(ref stack, 1) && stack.PeekUInt256IsZero()) + else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ)) { - optimizeAccess = true; - // Remove the zero from the stack since we will have consumed it. - stack.PopLimbo(); + if(CheckStackUnderflow(ref stack, 1)) + { + goto StackUnderflow; + } + + if (stack.PeekUInt256IsZero()) + { + optimizeAccess = true; + // Remove the zero from the stack since we will have consumed it. + stack.PopLimbo(); + } } if (optimizeAccess) From 76ef2b6bf13065e8794f41c34b365bb113df8f7e Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 15 Jan 2026 14:43:35 +0100 Subject: [PATCH 19/21] ws fix --- .../Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 9f8d4bcb3f2..0ec44ca54a4 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -269,7 +269,7 @@ public static EvmExceptionType InstructionExtCodeSize( // then this pattern likely corresponds to a contract existence check. else if ((nextInstruction == Instruction.GT || nextInstruction == Instruction.EQ)) { - if(CheckStackUnderflow(ref stack, 1)) + if (CheckStackUnderflow(ref stack, 1)) { goto StackUnderflow; } From 8bb1ee54fc4ca491c573f401f91ec35eb9ac34eb Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Thu, 15 Jan 2026 16:57:15 +0100 Subject: [PATCH 20/21] JUMP opcodes test fix --- .../Instructions/EvmInstructions.ControlFlow.cs | 11 ++--------- .../Instructions/EvmInstructions.Stack.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs index ad656022fa5..4ac7ab40b3c 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs @@ -122,8 +122,7 @@ public static EvmExceptionType InstructionJumpIf(VirtualMachine(VirtualMachine>(ref condition) != default); } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index e1323accad4..2136b1fb51a 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -176,9 +176,13 @@ public static EvmExceptionType InstructionPush2(Virtua else { TGasPolicy.Consume(ref gas, GasCostOf.JumpI); + if(CheckStackUnderflow(ref stack, 1)) + { + goto StackUnderflow; + } + vm.OpCodeCount++; - bool shouldJump = TestJumpCondition(ref stack, out bool isOverflow); - if (isOverflow) goto StackUnderflow; + bool shouldJump = TestJumpCondition(ref stack); if (!shouldJump) { // Move forward by 2 bytes + JUMPI From 5aba275216ddd27e35d19982e55f430e359d5190 Mon Sep 17 00:00:00 2001 From: Ayman Bouchareb Date: Mon, 19 Jan 2026 16:09:47 +0100 Subject: [PATCH 21/21] ws fix --- .../Nethermind.Evm/Instructions/EvmInstructions.Stack.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index 2136b1fb51a..2018ec4eb41 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -176,7 +176,7 @@ public static EvmExceptionType InstructionPush2(Virtua else { TGasPolicy.Consume(ref gas, GasCostOf.JumpI); - if(CheckStackUnderflow(ref stack, 1)) + if (CheckStackUnderflow(ref stack, 1)) { goto StackUnderflow; }