Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a70c9d6
add stack checks to most opcodes
Demuirgos Jan 7, 2026
d17ea15
remove bound checks from evmstack and delegate them to opcode level
Demuirgos Jan 7, 2026
706163f
merge master and fix typo
Demuirgos Jan 7, 2026
2140ca7
add missing normal opcodes
Demuirgos Jan 7, 2026
3dff561
apply changes to Eof instructions as well
Demuirgos Jan 7, 2026
bc0f8a5
fix debug build issues
Demuirgos Jan 7, 2026
d396bc8
fixed check for push; added check for blobhashfee
Demuirgos Jan 7, 2026
1dfad8c
add overflow check for Push0 and underflow check for extcodehash
Demuirgos Jan 7, 2026
3961a83
fix stackoverflow check
Demuirgos Jan 7, 2026
be5b795
merge master
Demuirgos Jan 7, 2026
f614f7f
fix overflow check
Demuirgos Jan 8, 2026
ffa25ea
fix Keccak underflow check
Demuirgos Jan 8, 2026
5b30940
fix checks for multiple opcodes
Demuirgos Jan 8, 2026
ba63cde
add check to Push2 special handling
Demuirgos Jan 8, 2026
8db6752
ws fix
Demuirgos Jan 8, 2026
033a6ce
merge master
Demuirgos Jan 8, 2026
bb3f435
Update src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Cod…
Demuirgos Jan 8, 2026
7b746db
remove unused method
Demuirgos Jan 8, 2026
b2019a5
fix check for codesize opcode
Demuirgos Jan 8, 2026
b570ab8
put meaniningful comments back
Demuirgos Jan 8, 2026
482abf7
Merge branch 'master' into feature/amortized-stack-checks
Demuirgos Jan 9, 2026
08cd2e9
remove empty line as copilot suggested
Demuirgos Jan 9, 2026
ada1246
merge master
Demuirgos Jan 15, 2026
c48df0c
throw on peephole opt of CODESIZE
Demuirgos Jan 15, 2026
76ef2b6
ws fix
Demuirgos Jan 15, 2026
8bb1ee5
JUMP opcodes test fix
Demuirgos Jan 15, 2026
3dee293
merge master
Demuirgos Jan 19, 2026
5aba275
ws fix
Demuirgos Jan 19, 2026
f6e8073
Merge branch 'master' into feature/amortized-stack-checks
Demuirgos Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<OffFlag>(0x00);

tracer.MoveNext();
Expand Down
113 changes: 21 additions & 92 deletions src/Nethermind/Nethermind.Evm/EvmStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ public EvmStack(scoped in int head, ITxTracer txTracer, scoped in Span<byte> 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);
}
Expand All @@ -70,7 +66,6 @@ public void PushBytes<TTracingInst>(scoped ReadOnlySpan<byte> value)
if (value.Length != WordSize)
{
ref byte bytes = ref PushBytesRef();
// Not full entry, clear first
Unsafe.As<byte, Word>(ref bytes) = default;
value.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref bytes, WordSize - value.Length), value.Length));
}
Expand Down Expand Up @@ -197,6 +192,7 @@ public void Push20Bytes<TTracingInst>(ref byte value)
ulong lane2 = Unsafe.As<byte, ulong>(ref Unsafe.Add(ref value, 4));
ulong lane3 = Unsafe.As<byte, ulong>(ref Unsafe.Add(ref value, 12));

// Single 32-byte store
head = Vector256.Create(default, lane1, lane2, lane3).AsByte();
}

Expand All @@ -212,7 +208,6 @@ public void Push32Bytes<TTracingInst>(in Word value)
if (TTracingInst.IsActive)
_tracer.TraceWord(in value);

// Single 32-byte store
PushedHead() = value;
}

Expand Down Expand Up @@ -262,7 +257,7 @@ public void PushZero<TTracingInst>()
if (TTracingInst.IsActive)
_tracer.ReportStackPush(Bytes.ZeroByteSpan);

// Single 32-byte store: Zero
// Single 32-byte store: Zero
PushedHead() = default;
}

Expand Down Expand Up @@ -302,7 +297,6 @@ public unsafe void PushUInt64<TTracingInst>(ulong value)
/// <remarks>
/// This method is a counterpart to <see cref="PopUInt256"/> and uses the same, raw data approach to write data back.
/// </remarks>

public void PushUInt256<TTracingInst>(in UInt256 value)
where TTracingInst : struct, IFlag
{
Expand Down Expand Up @@ -359,14 +353,9 @@ public void PushSignedInt256<TTracingInst>(in Int256.Int256 value)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool PopLimbo()
public void PopLimbo()
{
if (Head-- == 0)
{
return false;
}

return true;
Head--;
}

/// <summary>
Expand All @@ -377,11 +366,10 @@ public bool PopLimbo()
/// All it does is <see cref="Unsafe.ReadUnaligned{T}(ref byte)"/> and then reverse endianness if needed. Then it creates <paramref name="result"/>.
/// </remarks>
/// <param name="result">The returned value.</param>
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)
{
Expand Down Expand Up @@ -424,106 +412,65 @@ public bool PopUInt256(out UInt256 result)

result = new UInt256(u0, u1, u2, u3);
}

return true;
}

public readonly bool PeekUInt256IsZero()
{
int head = Head;
if (head-- == 0)
{
return false;
}

int head = Head - 1;
ref byte bytes = ref _bytes[head * WordSize];
return Unsafe.ReadUnaligned<UInt256>(ref bytes).IsZero;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly ref byte PeekBytesByRef()
{
int head = Head;
if (head-- == 0)
{
return ref Unsafe.NullRef<byte>();
}
int head = Head - 1;
return ref Unsafe.Add(ref MemoryMarshal.GetReference(_bytes), head * WordSize);
}

public readonly Span<byte> 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<byte>();
}
head--;
int head = Head - 1;
Head = head;
return ref Unsafe.Add(ref MemoryMarshal.GetReference(_bytes), head * WordSize);
}

public Span<byte> 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<byte> word)
public void PopWord256(out Span<byte> 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<TTracingInst>(int depth)
public void Dup<TTracingInst>(int depth)
where TTracingInst : struct, IFlag
{
int head = Head;
if (head < depth) goto StackUnderflow;

ref byte bytes = ref MemoryMarshal.GetReference(_bytes);

Expand All @@ -534,27 +481,17 @@ public EvmExceptionType Dup<TTracingInst>(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<TTracingInst>(int depth)
public readonly void Swap<TTracingInst>(int depth)
where TTracingInst : struct, IFlag
{
int head = Head;
if (head < depth) goto StackUnderflow;

ref byte bytes = ref MemoryMarshal.GetReference(_bytes);

Expand All @@ -566,18 +503,12 @@ public readonly EvmExceptionType Swap<TTracingInst>(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<TTracingInst>(int n, int m)
public readonly void Exchange<TTracingInst>(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);

Expand All @@ -589,8 +520,6 @@ public readonly bool Exchange<TTracingInst>(int n, int m)
Unsafe.WriteUnaligned(ref second, buffer);

if (TTracingInst.IsActive) Trace(maxDepth);

return true;
}

private readonly void Trace(int depth)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public interface IOpBitwise
/// <param name="b">The second operand vector.</param>
/// <returns>The result of the bitwise operation.</returns>
static abstract Word Operation(in Word a, in Word b);

static virtual bool CheckStackUnderflow(ref EvmStack stack)
{
return stack.Head < 2;
}
}

/// <summary>
Expand All @@ -47,18 +52,19 @@ public static EvmExceptionType InstructionBitwise<TGasPolicy, TOpBitwise>(Virtua
where TGasPolicy : struct, IGasPolicy<TGasPolicy>
where TOpBitwise : struct, IOpBitwise
{
if (TOpBitwise.CheckStackUnderflow(ref stack))
goto StackUnderflow;

// Deduct the operation's gas cost.
TGasPolicy.Consume(ref gas, TOpBitwise.GasCost);

// 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<Word>(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<Word>(ref bytesRef);

// Write the result directly into the memory of the top stack element.
Expand Down
Loading