Skip to content

Commit

Permalink
Add MCopy to Evm (#5791)
Browse files Browse the repository at this point in the history
* initial draft implementation of MCOPY

* added a couple of tests

* put flag activation for mcopy

* updated tests

* ws fix

* reverted unused change

* Minor fixes and refactors :
- fixed gas miscalculation
- refactored expansion gas calculations

* update tests

* Add Eip5656 configs glue to chainspec loader

* - Updated opcode value
- Added one extra test
- Update implementation to match opcode change

* - update name tests

* - update GetName

* - Aligned MCOPY implementation with Ben's Stack optimizations

* Add hive tests for Eip5656-MCOPY

* - turn off Ethereum tests untill tests are merged
  • Loading branch information
Demuirgos authored Jun 23, 2023
1 parent c83f1f1 commit 1bfc733
Show file tree
Hide file tree
Showing 21 changed files with 276 additions and 20 deletions.
27 changes: 27 additions & 0 deletions src/Nethermind/Ethereum.Blockchain.Test/Eip5656Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections.Generic;
using Ethereum.Test.Base;
using NUnit.Framework;

namespace Ethereum.Blockchain.Test;

[TestFixture]
[Parallelizable(ParallelScope.All)]
public class Eip5656Tests : GeneralStateTestBase
{
// wait untill Eip5656 tests are merged to de-comment this

// [TestCaseSource(nameof(LoadTests))]
// public void Test(GeneralStateTest test)
// {
// Assert.True(RunTest(test).Pass);
// }

public static IEnumerable<GeneralStateTest> LoadTests()
{
var loader = new TestsSourceLoader(new LoadEipTestsStrategy(), "stEIP5656-MCOPY");
return (IEnumerable<GeneralStateTest>)loader.LoadTests();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="..\..\tests\EIPTests\StateTests\st*">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ethereum.Test.Base\Ethereum.Test.Base.csproj" />
Expand Down
72 changes: 72 additions & 0 deletions src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Collections.Generic;
using System.IO;
using Ethereum.Test.Base.Interfaces;

namespace Ethereum.Test.Base
{
public class LoadEipTestsStrategy : ITestLoadStrategy
{
public IEnumerable<IEthereumTest> Load(string testsDirectoryName, string wildcard = null)
{
IEnumerable<string> testDirs;
if (!Path.IsPathRooted(testsDirectoryName))
{
string testsDirectory = GetGeneralStateTestsDirectory();


testDirs = Directory.EnumerateDirectories(testsDirectory, testsDirectoryName, new EnumerationOptions { RecurseSubdirectories = true });
}
else
{
testDirs = new[] { testsDirectoryName };
}

List<GeneralStateTest> testJsons = new();
foreach (string testDir in testDirs)
{
testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard));
}

return testJsons;
}

private string GetGeneralStateTestsDirectory()
{
char pathSeparator = Path.AltDirectorySeparatorChar;
string currentDirectory = AppDomain.CurrentDomain.BaseDirectory;

return currentDirectory.Remove(currentDirectory.LastIndexOf("src")) + $"src{pathSeparator}tests{pathSeparator}EipTests{pathSeparator}StateTests";
}

private IEnumerable<GeneralStateTest> LoadTestsFromDirectory(string testDir, string wildcard)
{
List<GeneralStateTest> testsByName = new();
IEnumerable<string> testFiles = Directory.EnumerateFiles(testDir);

foreach (string testFile in testFiles)
{
FileTestsSource fileTestsSource = new(testFile, wildcard);
try
{
var tests = fileTestsSource.LoadGeneralStateTests();
foreach (GeneralStateTest blockchainTest in tests)
{
blockchainTest.Category = testDir;
}

testsByName.AddRange(tests);
}
catch (Exception e)
{
testsByName.Add(new GeneralStateTest { Name = testFile, LoadFailure = $"Failed to load: {e}" });
}
}

return testsByName;
}
}
}
7 changes: 7 additions & 0 deletions src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
/// </summary>
bool IsEip3855Enabled { get; }

/// <summary>
/// MCOPY instruction
/// </summary>
bool IsEip5656Enabled { get; }

/// <summary>
/// EIP-3860: Limit and meter initcode
/// </summary>
Expand Down Expand Up @@ -340,5 +345,7 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec
public bool TransientStorageEnabled => IsEip1153Enabled;

public bool WithdrawalsEnabled => IsEip4895Enabled;

bool MCopyIncluded => IsEip5656Enabled;
}
}
14 changes: 14 additions & 0 deletions src/Nethermind/Nethermind.Evm.Test/InstructionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using FluentAssertions;
using Nethermind.Specs.Forks;
using NUnit.Framework;

namespace Nethermind.Evm.Test
Expand All @@ -19,5 +20,18 @@ public void Return_prevrandao_name_for_prevrandao_opcode_for_post_merge()
{
Instruction.PREVRANDAO.GetName(true).Should().Be("PREVRANDAO");
}


[Test]
public void Return_mcopy_name_for_mcopy_opcode_post_eip_5656()
{
Instruction.MCOPY.GetName(true, Cancun.Instance).Should().Be("MCOPY");
}

[Test]
public void Return_jumpsub_name_for_mcopy_opcode_pre_eip_5656()
{
Instruction.MCOPY.GetName(true, Shanghai.Instance).Should().Be("JUMPSUB");
}
}
}
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase
{
Instruction.TSTORE,
Instruction.TLOAD,
Instruction.MCOPY,
Instruction.BLOBHASH,
}
).ToArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,11 @@ public void Debugger_stops_at_correct_breakpoint_depth(string bytecodeHex)
{
if (tracer.CanReadState)
{
if(tracer.CurrentState.Env.CallDepth == TARGET_OPCODE_PTR_BREAK_POINT.depth && tracer.CurrentState.ProgramCounter == TARGET_OPCODE_PTR_BREAK_POINT.pc)
if (tracer.CurrentState.Env.CallDepth == TARGET_OPCODE_PTR_BREAK_POINT.depth && tracer.CurrentState.ProgramCounter == TARGET_OPCODE_PTR_BREAK_POINT.pc)
{
stoppedAtCorrectBreakpoint = true;
} else
}
else
{
stoppedAtCorrectBreakpoint = false;
}
Expand Down
90 changes: 90 additions & 0 deletions src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
using Nethermind.Int256;
using NUnit.Framework;
using Nethermind.Specs;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System;

namespace Nethermind.Evm.Test
{
Expand Down Expand Up @@ -427,6 +430,93 @@ public void Tload()
Assert.That(receipt.GasSpent, Is.EqualTo(GasCostOf.Transaction + GasCostOf.VeryLow * 1 + GasCostOf.TLoad), "gas");
}

/// <summary>
/// MCOPY gas cost check
/// </summary>
[Test]
public void MCopy()
{
byte[] data = new byte[] { 0x60, 0x17, 0x60, 0x03, 0x02, 0x00 };
byte[] code = Prepare.EvmCode
.MSTORE(0, data.PadRight(32))
.MCOPY(6, 0, 6)
.STOP()
.Done;
GethLikeTxTrace traces = Execute(new GethLikeTxTracer(GethTraceOptions.Default), code, MainnetSpecProvider.CancunActivation).BuildResult();

Assert.That(traces.Entries[^2].GasCost, Is.EqualTo(GasCostOf.VeryLow + GasCostOf.VeryLow * ((data.Length + 31) / 32) + GasCostOf.Memory * 0), "gas");
}

[Test]
public void MCopy_exclusive_areas()
{
byte[] data = Bytes.FromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] bytecode = Prepare.EvmCode
.MSTORE(0, data)
.MCOPY(32, 0, 32)
.STOP()
.Done;
GethLikeTxTrace traces = Execute(new GethLikeTxTracer(GethTraceOptions.Default), bytecode, MainnetSpecProvider.CancunActivation).BuildResult();

var copied = traces.Entries.Last().Memory[0];
var origin = traces.Entries.Last().Memory[1];

Assert.That(traces.Entries[^2].GasCost, Is.EqualTo(GasCostOf.VeryLow + GasCostOf.VeryLow * ((data.Length + 31) / 32) + GasCostOf.Memory * 1), "gas");
Assert.That(origin, Is.EqualTo(copied));
}


[Test]
public void MCopy_Overwrite_areas_copy_right()
{
int SLICE_SIZE = 8;
byte[] data = Bytes.FromHexString("0102030405060708000000000000000000000000000000000000000000000000");
byte[] bytecode = Prepare.EvmCode
.MSTORE(0, data)
.MCOPY(1, 0, (UInt256)SLICE_SIZE)
.STOP()
.Done;
GethLikeTxTrace traces = Execute(new GethLikeTxTracer(GethTraceOptions.Default), bytecode, MainnetSpecProvider.CancunActivation).BuildResult();

var result = traces.Entries.Last().Memory[0];

Assert.That(traces.Entries[^2].GasCost, Is.EqualTo(GasCostOf.VeryLow + GasCostOf.VeryLow * (SLICE_SIZE + 31) / 32), "gas");
Assert.That(result, Is.EqualTo("0101020304050607080000000000000000000000000000000000000000000000"), "memory state");
}

[Test]
public void MCopy_twice_same_location()
{
byte[] data = Bytes.FromHexString("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
byte[] bytecode = Prepare.EvmCode
.MSTORE(0, data)
.MCOPY(0, 0, 32)
.STOP()
.Done;
GethLikeTxTrace traces = Execute(new GethLikeTxTracer(GethTraceOptions.Default), bytecode, MainnetSpecProvider.CancunActivation).BuildResult();

Assert.That(traces.Entries[^2].GasCost, Is.EqualTo(GasCostOf.VeryLow + GasCostOf.VeryLow * ((data.Length + 31) / 32)), "gas");
Assert.That(traces.Entries.Last().Memory.Count, Is.EqualTo(1));
}

[Test]
public void MCopy_Overwrite_areas_copy_left()
{
int SLICE_SIZE = 8;
byte[] data = Bytes.FromHexString("0001020304050607080000000000000000000000000000000000000000000000");
byte[] bytecode = Prepare.EvmCode
.MSTORE(0, data)
.MCOPY(0, 1, (UInt256)SLICE_SIZE)
.STOP()
.Done;
GethLikeTxTrace traces = Execute(new GethLikeTxTracer(GethTraceOptions.Default), bytecode, MainnetSpecProvider.CancunActivation).BuildResult();

var result = traces.Entries.Last().Memory[0];

Assert.That(traces.Entries[^2].GasCost, Is.EqualTo(GasCostOf.VeryLow + GasCostOf.VeryLow * (SLICE_SIZE + 31) / 32), "gas");
Assert.That(result, Is.EqualTo("0102030405060708080000000000000000000000000000000000000000000000"), "memory state");
}

/// <summary>
/// TStore gas cost check
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class VirtualMachineTestsBase
protected static PrivateKey RecipientKey { get; } = TestItem.PrivateKeyB;
protected static PrivateKey MinerKey { get; } = TestItem.PrivateKeyD;

protected virtual long BlockNumber => MainnetSpecProvider.ByzantiumBlockNumber;
protected virtual long BlockNumber { get; } = MainnetSpecProvider.ByzantiumBlockNumber;
protected virtual ulong Timestamp => 0UL;
protected virtual ISpecProvider SpecProvider => MainnetSpecProvider.Instance;
protected IReleaseSpec Spec => SpecProvider.GetSpec(BlockNumber, Timestamp);
Expand Down Expand Up @@ -100,9 +100,9 @@ protected TestAllTracerWithOutput Execute(params byte[] code)

protected virtual TestAllTracerWithOutput CreateTracer() => new();

protected T Execute<T>(T tracer, params byte[] code) where T : ITxTracer
protected T Execute<T>(T tracer, byte[] code, ForkActivation? forkActivation = null) where T : ITxTracer
{
(Block block, Transaction transaction) = PrepareTx(BlockNumber, 100000, code, timestamp: Timestamp);
(Block block, Transaction transaction) = PrepareTx(forkActivation?.BlockNumber ?? BlockNumber, 100000, code, timestamp: forkActivation?.Timestamp ?? Timestamp);
_processor.Execute(transaction, block.Header, tracer);
return tracer;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Nethermind/Nethermind.Evm/ByteCodeBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ public static Prepare REVERT(this Prepare @this, UInt256? pos = null, UInt256? l
#endregion

#region opcodes_with_3_args
public static Prepare MCOPY(this Prepare @this, UInt256? dst = null, UInt256? src = null, UInt256? len = null)
=> @this.PushSequence(len, src, dst)
.Op(Instruction.MCOPY);
public static Prepare ADDMOD(this Prepare @this, UInt256? lhs = null, UInt256? rhs = null, UInt256? mod = null)
=> @this.PushSequence(mod, rhs, lhs)
.Op(Instruction.ADDMOD);
Expand Down
15 changes: 9 additions & 6 deletions src/Nethermind/Nethermind.Evm/Instruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using FastEnumUtility;
using Nethermind.Core.Specs;

namespace Nethermind.Evm
{
Expand Down Expand Up @@ -84,6 +85,7 @@ public enum Instruction : byte
BEGINSUB = 0x5c,
RETURNSUB = 0x5d,
JUMPSUB = 0x5e,
MCOPY = 0x5e,

PUSH0 = 0x5f, // EIP-3855
PUSH1 = 0x60,
Expand Down Expand Up @@ -177,12 +179,13 @@ public enum Instruction : byte

public static class InstructionExtensions
{
public static string? GetName(this Instruction instruction, bool isPostMerge = false) =>
(instruction == Instruction.PREVRANDAO && !isPostMerge)
? "DIFFICULTY"
: FastEnum.IsDefined(instruction)
? FastEnum.GetName(instruction)
: null;
public static string? GetName(this Instruction instruction, bool isPostMerge = false, IReleaseSpec? spec = null) =>
instruction switch
{
Instruction.PREVRANDAO when !isPostMerge => "DIFFICULTY",
Instruction.JUMPSUB or Instruction.MCOPY => spec?.IsEip5656Enabled == true ? "MCOPY" : "JUMPSUB",
_ => FastEnum.IsDefined(instruction) ? FastEnum.GetName(instruction) : null
};
}
}

4 changes: 4 additions & 0 deletions src/Nethermind/Nethermind.Evm/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public class Metrics
[Description("Number of TSTORE opcodes executed.")]
public static long TstoreOpcode { get; set; }

[CounterMetric]
[Description("Number of MCOPY opcodes executed.")]
public static long MCopyOpcode { get; set; }

[CounterMetric]
[Description("Number of MODEXP precompiles executed.")]
public static long ModExpOpcode { get; set; }
Expand Down
Loading

0 comments on commit 1bfc733

Please sign in to comment.