diff --git a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs index 4d1b4cfea7d..1da14760c07 100644 --- a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Numerics; -using System.Text.Json.Serialization; using Ethereum.Test.Base; using Nethermind.Core; diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/LoadPyspecTestsStrategy.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/LoadPyspecTestsStrategy.cs index ee879258d11..f363f20e2dc 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/LoadPyspecTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/LoadPyspecTestsStrategy.cs @@ -15,6 +15,12 @@ namespace Ethereum.Blockchain.Pyspec.Test; public class LoadPyspecTestsStrategy : ITestLoadStrategy { + private enum TestType + { + Blockchain, + GeneralState, + Eof + } public string ArchiveVersion { get; init; } = Constants.DEFAULT_ARCHIVE_VERSION; public string ArchiveName { get; init; } = Constants.DEFAULT_ARCHIVE_NAME; @@ -23,11 +29,16 @@ public IEnumerable Load(string testsDir, string wildcard = null) string testsDirectoryName = Path.Combine(AppContext.BaseDirectory, "PyTests", ArchiveVersion, ArchiveName.Split('.')[0]); if (!Directory.Exists(testsDirectoryName)) // Prevent redownloading the fixtures if they already exists with this version and archive name DownloadAndExtract(ArchiveVersion, ArchiveName, testsDirectoryName); - bool isStateTest = testsDir.Contains("state_tests", StringComparison.InvariantCultureIgnoreCase); + TestType testType = testsDir.Contains("state_tests", StringComparison.InvariantCultureIgnoreCase) + ? TestType.GeneralState + : testsDir.Contains("eof_tests", StringComparison.InvariantCultureIgnoreCase) + ? TestType.Eof + : TestType.Blockchain; + IEnumerable testDirs = !string.IsNullOrEmpty(testsDir) ? Directory.EnumerateDirectories(Path.Combine(testsDirectoryName, testsDir), "*", new EnumerationOptions { RecurseSubdirectories = true }) : Directory.EnumerateDirectories(testsDirectoryName, "*", new EnumerationOptions { RecurseSubdirectories = true }); - return testDirs.SelectMany(td => LoadTestsFromDirectory(td, wildcard, isStateTest)); + return testDirs.SelectMany(td => LoadTestsFromDirectory(td, wildcard, testType)); } private void DownloadAndExtract(string archiveVersion, string archiveName, string testsDirectoryName) @@ -44,7 +55,7 @@ private void DownloadAndExtract(string archiveVersion, string archiveName, strin TarFile.ExtractToDirectory(gzStream, testsDirectoryName, true); } - private IEnumerable LoadTestsFromDirectory(string testDir, string wildcard, bool isStateTest) + private IEnumerable LoadTestsFromDirectory(string testDir, string wildcard, TestType testType) { List testsByName = new(); IEnumerable testFiles = Directory.EnumerateFiles(testDir); @@ -54,20 +65,28 @@ private IEnumerable LoadTestsFromDirectory(string testDir, string FileTestsSource fileTestsSource = new(testFile, wildcard); try { - IEnumerable tests = isStateTest - ? fileTestsSource.LoadGeneralStateTests() - : fileTestsSource.LoadBlockchainTests(); + IEnumerable tests = testType switch + { + TestType.Eof => fileTestsSource.LoadEofTests(), + TestType.GeneralState => fileTestsSource.LoadGeneralStateTests(), + _ => fileTestsSource.LoadBlockchainTests() + }; + foreach (IEthereumTest test in tests) { - test.Category = testDir; + test.Category ??= testDir; } testsByName.AddRange(tests); } catch (Exception e) { - IEthereumTest failedTest = isStateTest - ? new GeneralStateTest() - : new BlockchainTest(); + IEthereumTest failedTest = testType switch + { + TestType.Eof => new EofTest(), + TestType.GeneralState => new GeneralStateTest(), + _ => new BlockchainTest() + }; + failedTest.Name = testDir; failedTest.LoadFailure = $"Failed to load: {e}"; testsByName.Add(failedTest); diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs new file mode 100644 index 00000000000..4c56759ae0e --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaEofTests.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +public class OsakaEofTests : EofTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public void Test(EofTest test) => RunCITest(test); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy() + { + ArchiveName = "fixtures_eip7692-osaka.tar.gz", + ArchiveVersion = "eip7692@v2.0.0" + }, $"fixtures/eof_tests/osaka"); + return loader.LoadTests().Cast().Select(t => new TestCaseData(t) + .SetName(t.Name) + .SetCategory(t.Category)); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs similarity index 65% rename from src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs rename to src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs index 107b4e249f1..6e9f00834d0 100644 --- a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueStateTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/OsakaStateTests.cs @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + using System.Collections.Generic; using System.Linq; using Ethereum.Test.Base; @@ -9,14 +12,18 @@ namespace Ethereum.Blockchain.Pyspec.Test; [TestFixture] [Parallelizable(ParallelScope.All)] [Explicit("These tests are not ready yet")] -public class PragueStateTests : GeneralStateTestBase +public class OsakaStateTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) => RunTest(test).Pass.Should().BeTrue(); private static IEnumerable LoadTests() { - TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/state_tests/prague"); + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy() + { + ArchiveName = "fixtures_eip7692-osaka.tar.gz", + ArchiveVersion = "eip7692@v2.0.0" + }, $"fixtures/state_tests/osaka"); return loader.LoadTests().Cast(); } } diff --git a/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockchainTests.cs b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockchainTests.cs new file mode 100644 index 00000000000..2474c0cfa2b --- /dev/null +++ b/src/Nethermind/Ethereum.Blockchain.Pyspec.Test/PragueBlockchainTests.cs @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ethereum.Test.Base; +using NUnit.Framework; + +namespace Ethereum.Blockchain.Pyspec.Test; + +[TestFixture] +[Parallelizable(ParallelScope.All)] +[Explicit("These tests are not ready yet")] +public class PragueBlockChainTests : BlockchainTestBase +{ + [TestCaseSource(nameof(LoadTests))] + public async Task Test(BlockchainTest test) => await RunTest(test); + + private static IEnumerable LoadTests() + { + TestsSourceLoader loader = new(new LoadPyspecTestsStrategy(), $"fixtures/blockchain_tests/prague"); + return loader.LoadTests().OfType(); + } +} diff --git a/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs index 54ee33d93b9..238b44b61c6 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Test/BadOpcodeTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using Ethereum.Test.Base; using NUnit.Framework; diff --git a/src/Nethermind/Ethereum.Blockchain.Test/EofTests.cs b/src/Nethermind/Ethereum.Blockchain.Test/EofTests.cs index 251ec083de0..c23f2e75f58 100644 --- a/src/Nethermind/Ethereum.Blockchain.Test/EofTests.cs +++ b/src/Nethermind/Ethereum.Blockchain.Test/EofTests.cs @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; +using System.Linq; using Ethereum.Test.Base; using NUnit.Framework; @@ -11,17 +13,21 @@ namespace Ethereum.Blockchain.Test; [Parallelizable(ParallelScope.All)] public class EOFTests : GeneralStateTestBase { - // Uncomment when EOF tests are merged - - // [TestCaseSource(nameof(LoadTests))] - // public void Test(GeneralStateTest test) - // { - // Assert.That(RunTest(test).Pass, Is.True); - // } + [TestCaseSource(nameof(LoadTests))] + public void Test(GeneralStateTest test) + { + // Legacy ethereum/tests EOF tests currently are based on a Prague spec, which lacks EOF. + // All EOF tests are now a part of EEST + //Assert.That(RunTest(test).Pass, Is.True); + } public static IEnumerable LoadTests() { - var loader = new TestsSourceLoader(new LoadEipTestsStrategy(), "stEOF"); - return (IEnumerable)loader.LoadTests(); + var eip3540Loader = (IEnumerable)(new TestsSourceLoader(new LoadEofTestsStrategy(), "stEIP3540").LoadTests()); + var eip3670Loader = (IEnumerable)(new TestsSourceLoader(new LoadEofTestsStrategy(), "stEIP3670").LoadTests()); + var eip4200Loader = (IEnumerable)(new TestsSourceLoader(new LoadEofTestsStrategy(), "stEIP4200").LoadTests()); + var eip4750Loader = (IEnumerable)(new TestsSourceLoader(new LoadEofTestsStrategy(), "stEIP4750").LoadTests()); + var eip5450Loader = (IEnumerable)(new TestsSourceLoader(new LoadEofTestsStrategy(), "stEIP5450").LoadTests()); + return eip3540Loader.Concat(eip3670Loader).Concat(eip4200Loader).Concat(eip4750Loader).Concat(eip5450Loader); } } diff --git a/src/Nethermind/Ethereum.Test.Base/EofTest.cs b/src/Nethermind/Ethereum.Test.Base/EofTest.cs new file mode 100644 index 00000000000..a773a6b9468 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/EofTest.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Ethereum.Test.Base.Interfaces; +using Nethermind.Evm.EvmObjectFormat; + +namespace Ethereum.Test.Base; +public class Result +{ + public string Fork { get; set; } + public bool Success { get; set; } + public string? Error { get; set; } +} + +public class VectorTest +{ + public byte[] Code { get; set; } + public ValidationStrategy ContainerKind { get; set; } +} + +public class EofTest : IEthereumTest +{ + public string Name { get; set; } + public VectorTest Vector { get; set; } + public string? Category { get; set; } + public string? LoadFailure { get; set; } + public Result Result { get; internal set; } + public string? Description { get; set; } + public string? Url { get; set; } + public string? Spec { get; set; } +} diff --git a/src/Nethermind/Ethereum.Test.Base/EofTestBase.cs b/src/Nethermind/Ethereum.Test.Base/EofTestBase.cs new file mode 100644 index 00000000000..3f0d38ee79a --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/EofTestBase.cs @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Logging; +using NUnit.Framework; +using Nethermind.Evm.EvmObjectFormat; + +namespace Ethereum.Test.Base +{ + public abstract class EofTestBase + { + private static ILogger _logger = new(TextContextLogger.Instance); + private static ILogManager _logManager = new TestLogManager(LogLevel.Warn); + + [SetUp] + public void Setup() + { + EofValidator.Logger = _logger; + } + + protected static void Setup(ILogManager logManager) + { + _logManager = logManager ?? LimboLogs.Instance; + _logger = _logManager.GetClassLogger(); + } + + protected void RunCITest(EofTest test) + { + var result = RunTest(test, NullTxTracer.Instance); + + if (result != test.Result.Success) + { + _logger.Info($"Spec: {test.Spec}"); + _logger.Info(test.Description); + _logger.Info($"Url: {test.Url}"); + } + + Assert.That(result, Is.EqualTo(test.Result.Success)); + } + + protected bool RunTest(EofTest test) + { + return RunTest(test, NullTxTracer.Instance) == test.Result.Success; + } + + protected bool RunTest(EofTest test, ITxTracer txTracer) + { + TestContext.Out.WriteLine($"Running {test.Name} at {DateTime.UtcNow:HH:mm:ss.ffffff}"); + Assert.That(test.LoadFailure, Is.Null, "test data loading failure"); + + var vector = test.Vector; + var code = vector.Code; + var strategy = vector.ContainerKind; + var fork = test.Result.Fork switch + { + "Osaka" => Nethermind.Specs.Forks.Osaka.Instance, + "Prague" => Nethermind.Specs.Forks.Prague.Instance, + "Berlin" => Nethermind.Specs.Forks.Berlin.Instance, + "London" => Nethermind.Specs.Forks.London.Instance, + "Shanghai" => Nethermind.Specs.Forks.Shanghai.Instance, + "Constantinople" => Nethermind.Specs.Forks.Constantinople.Instance, + "Byzantium" => Nethermind.Specs.Forks.Byzantium.Instance, + "SpuriousDragon" => Nethermind.Specs.Forks.SpuriousDragon.Instance, + "TangerineWhistle" => Nethermind.Specs.Forks.TangerineWhistle.Instance, + "Homestead" => Nethermind.Specs.Forks.Homestead.Instance, + "Frontier" => Nethermind.Specs.Forks.Frontier.Instance, + _ => throw new NotSupportedException($"Fork {test.Result.Fork} is not supported") + }; + + bool result = CodeDepositHandler.IsValidWithEofRules(fork, code, 1, strategy); + return result; + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/EofTestJson.cs b/src/Nethermind/Ethereum.Test.Base/EofTestJson.cs new file mode 100644 index 00000000000..0e2a4a6077c --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/EofTestJson.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Ethereum.Test.Base +{ + public class EofTestJson + { + [JsonPropertyName("_info")] + public GeneralStateTestInfoJson? Info { get; set; } + + public Dictionary Vectors { get; set; } + + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs b/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs index eecd49c13e9..0cb2f1adf3c 100644 --- a/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs +++ b/src/Nethermind/Ethereum.Test.Base/EthereumTestResult.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Text.Json.Serialization; - using Nethermind.Core.Crypto; namespace Ethereum.Test.Base diff --git a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs index 2e0baece45e..c72b53f7429 100644 --- a/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs +++ b/src/Nethermind/Ethereum.Test.Base/FileTestsSource.cs @@ -20,6 +20,29 @@ public FileTestsSource(string fileName, string? wildcard = null) _wildcard = wildcard; } + public IEnumerable LoadEofTests() + { + try + { + if (Path.GetFileName(_fileName).StartsWith(".")) + { + return Enumerable.Empty(); + } + + if (_wildcard is not null && !_fileName.Contains(_wildcard)) + { + return Enumerable.Empty(); + } + + string json = File.ReadAllText(_fileName); + return JsonToEthereumTest.ConvertToEofTests(json); + } + catch (Exception e) + { + return Enumerable.Repeat(new EofTest { Name = _fileName, LoadFailure = $"Failed to load: {e}" }, 1); + } + } + public IEnumerable LoadGeneralStateTests() { try @@ -35,7 +58,7 @@ public IEnumerable LoadGeneralStateTests() } string json = File.ReadAllText(_fileName); - return JsonToEthereumTest.Convert(json); + return JsonToEthereumTest.ConvertStateTest(json); } catch (Exception e) { diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestInfoJson.cs b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestInfoJson.cs index 17a520aa6d6..122d940fb5a 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestInfoJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestInfoJson.cs @@ -11,6 +11,10 @@ namespace Ethereum.Test.Base [JsonConverter(typeof(GeneralStateTestInfoConverter))] public class GeneralStateTestInfoJson { + public string? Description { get; set; } + public string? Url { get; set; } + public string? Spec { get; set; } + public Dictionary? Labels { get; set; } } @@ -22,6 +26,9 @@ public class GeneralStateTestInfoConverter : JsonConverter? labels = null; + string? description = null; + string? url = null; + string? spec = null; if (reader.TokenType == JsonTokenType.StartObject) { var depth = reader.CurrentDepth; @@ -31,10 +38,32 @@ public class GeneralStateTestInfoConverter : JsonConverter>(ref reader, options); + if (reader.ValueTextEquals("labels"u8)) + { + reader.Read(); + labels = JsonSerializer.Deserialize>(ref reader, options); + } + else if (reader.ValueTextEquals("description"u8)) + { + reader.Read(); + description = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("url"u8)) + { + reader.Read(); + url = JsonSerializer.Deserialize(ref reader, options); + } + else if (reader.ValueTextEquals("reference-spec"u8)) + { + reader.Read(); + spec = JsonSerializer.Deserialize(ref reader, options); + } + else + { + reader.Skip(); + } } else { @@ -43,7 +72,13 @@ public class GeneralStateTestInfoConverter : JsonConverter 0) + { + TestContext.Out.WriteLine(); + TestContext.Out.WriteLine("Differences from expected"); + TestContext.Out.WriteLine(); + } + foreach (string difference in differences) + { + TestContext.Out.WriteLine(difference); + } + // Assert.Zero(differences.Count, "differences"); return testResult; } diff --git a/src/Nethermind/Ethereum.Test.Base/Interfaces/IEofTestRunner.cs b/src/Nethermind/Ethereum.Test.Base/Interfaces/IEofTestRunner.cs new file mode 100644 index 00000000000..ae82bd923eb --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/Interfaces/IEofTestRunner.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; + +namespace Ethereum.Test.Base.Interfaces +{ + public interface IEofTestRunner + { + IEnumerable RunTests(); + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 7d41065cd52..dc436ce6a5c 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Crypto; +using Nethermind.Evm.EvmObjectFormat; using Nethermind.Int256; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; @@ -60,6 +61,7 @@ private static IReleaseSpec ParseSpec(string network) "Cancun" => Cancun.Instance, "Paris" => Paris.Instance, "Prague" => Prague.Instance, + "Osaka" => Osaka.Instance, _ => throw new NotSupportedException() }; } @@ -301,7 +303,62 @@ public static BlockchainTest Convert(string name, BlockchainTestJson testJson) private static readonly EthereumJsonSerializer _serializer = new(); - public static IEnumerable Convert(string json) + public static IEnumerable ConvertToEofTests(string json) + { + Dictionary testsInFile = _serializer.Deserialize>(json); + List tests = new(); + foreach (KeyValuePair namedTest in testsInFile) + { + var index = namedTest.Key.IndexOf(".py::"); + var name = namedTest.Key.Substring(index + 5); + string category = namedTest.Key.Substring(0, index).Replace("tests/osaka/eip7692_eof_v1/", ""); + + string? description = null; + string? url = null; + string? spec = null; + var info = namedTest.Value?.Info; + if (info is not null) + { + description = info.Description; + url = info.Url; + spec = info.Spec; + } + + foreach (KeyValuePair pair in namedTest.Value.Vectors) + { + VectorTestJson vectorJson = pair.Value; + VectorTest vector = new(); + vector.Code = Bytes.FromHexString(vectorJson.Code); + vector.ContainerKind = + ("INITCODE".Equals(vectorJson.ContainerKind) + ? ValidationStrategy.ValidateInitcodeMode + : ValidationStrategy.ValidateRuntimeMode) + | ValidationStrategy.ValidateFullBody; + + foreach (var result in vectorJson.Results) + { + EofTest test = new() + { + Name = $"{name}", + Category = $"{category} [{result.Key}]", + Url = url, + Description = description, + Spec = spec + }; + test.Vector = vector; + + test.Result = result.Value.Result + ? new Result { Fork = result.Key, Success = true } + : new Result { Fork = result.Key, Success = false, Error = result.Value.Exception }; + tests.Add(test); + } + } + } + + return tests; + } + + public static IEnumerable ConvertStateTest(string json) { Dictionary testsInFile = _serializer.Deserialize>(json); @@ -309,7 +366,6 @@ public static IEnumerable Convert(string json) List tests = new(); foreach (KeyValuePair namedTest in testsInFile) { - Console.WriteLine($"Loading {namedTest.Key}\n {namedTest.Value.Post}"); tests.AddRange(Convert(namedTest.Key, namedTest.Value)); } diff --git a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs index e629f80b277..c8a2f6c0d7b 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestFileStrategy.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using Ethereum.Test.Base.Interfaces; namespace Ethereum.Test.Base diff --git a/src/Nethermind/Ethereum.Test.Base/LoadEofTestsFileStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsFileStrategy.cs new file mode 100644 index 00000000000..1bf9ac0d1b0 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsFileStrategy.cs @@ -0,0 +1,56 @@ +// 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 LoadEofTestFileStrategy : ITestLoadStrategy + { + public IEnumerable Load(string testName, string? wildcard = null) + { + //in case user wants to give a test file other than the ones in ethereum tests submodule + if (File.Exists(testName)) + { + FileTestsSource fileTestsSource = new(testName, wildcard); + IEnumerable tests = fileTestsSource.LoadEofTests(); + + return tests; + } + + string testsDirectory = GetEofTestsDirectory(); + + IEnumerable testFiles = Directory.EnumerateFiles(testsDirectory, testName, SearchOption.AllDirectories); + + List eofTests = new(); + + //load all tests from found test files in ethereum tests submodule + foreach (string testFile in testFiles) + { + FileTestsSource fileTestsSource = new(testFile, wildcard); + try + { + IEnumerable tests = fileTestsSource.LoadEofTests(); + + eofTests.AddRange(tests); + } + catch (Exception e) + { + eofTests.Add(new EofTest() { Name = testFile, LoadFailure = $"Failed to load: {e}" }); + } + } + + return eofTests; + } + + private string GetEofTestsDirectory() + { + string currentDirectory = AppDomain.CurrentDomain.BaseDirectory; + + return Path.Combine(currentDirectory.Remove(currentDirectory.LastIndexOf("src")), "src", "tests", "EOFTests"); + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs new file mode 100644 index 00000000000..bffc302979a --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs @@ -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 LoadEofTestsStrategy : ITestLoadStrategy + { + public IEnumerable Load(string testsDirectoryName, string wildcard = null) + { + IEnumerable testDirs; + if (!Path.IsPathRooted(testsDirectoryName)) + { + string testsDirectory = GetGeneralStateTestsDirectory(); + + + testDirs = Directory.EnumerateDirectories(testsDirectory, testsDirectoryName, new EnumerationOptions { RecurseSubdirectories = true }); + } + else + { + testDirs = new[] { testsDirectoryName }; + } + + List 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{pathSeparator}stEOF"; + } + + private IEnumerable LoadTestsFromDirectory(string testDir, string wildcard) + { + List testsByName = new(); + IEnumerable 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; + } + } +} diff --git a/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs b/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs new file mode 100644 index 00000000000..3255830ae71 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TestResultJson.cs @@ -0,0 +1,9 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Ethereum.Test.Base; +public class TestResultJson +{ + public string? Exception { get; set; } + public bool Result { get; set; } +} diff --git a/src/Nethermind/Ethereum.Test.Base/TextContextLogger.cs b/src/Nethermind/Ethereum.Test.Base/TextContextLogger.cs new file mode 100644 index 00000000000..9a880575501 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/TextContextLogger.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Logging; +using NUnit.Framework; + +namespace Ethereum.Test.Base; + +public class TextContextLogger : InterfaceLogger +{ + private TextContextLogger() { } + + public static TextContextLogger Instance { get; } = new TextContextLogger(); + + public void Info(string text) => WriteEntry(text); + + public void Warn(string text) => WriteEntry(text); + + public void Debug(string text) => WriteEntry(text); + + public void Trace(string text) => WriteEntry(text); + + public void Error(string text, Exception ex = null) => WriteEntry(text + " " + ex); + + private static void WriteEntry(string text) => TestContext.Out.WriteLine(text); + + public bool IsInfo => true; + public bool IsWarn => true; + public bool IsDebug => true; + public bool IsTrace => true; + public bool IsError => true; +} diff --git a/src/Nethermind/Ethereum.Test.Base/VectorTestJson.cs b/src/Nethermind/Ethereum.Test.Base/VectorTestJson.cs new file mode 100644 index 00000000000..5466b7eb379 --- /dev/null +++ b/src/Nethermind/Ethereum.Test.Base/VectorTestJson.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; + +namespace Ethereum.Test.Base; + +public class VectorTestJson +{ + public string Code { get; set; } + public string ContainerKind { get; set; } + public Dictionary Results { get; set; } +} diff --git a/src/Nethermind/EthereumTests.sln b/src/Nethermind/EthereumTests.sln index 10f3a923f34..57ed3cdbc96 100644 --- a/src/Nethermind/EthereumTests.sln +++ b/src/Nethermind/EthereumTests.sln @@ -115,6 +115,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Synchronization" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.Sockets", "Nethermind.Sockets\Nethermind.Sockets.csproj", "{E9D67F92-D848-4DB3-A586-2AC6DE2A3933}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nethermind.EOFParse.Runner", "Nethermind.EOFParse.Runner\Nethermind.EOFParse.Runner.csproj", "{47773DCF-2892-4F87-993F-1A0912CC6420}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -341,6 +343,10 @@ Global {E9D67F92-D848-4DB3-A586-2AC6DE2A3933}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9D67F92-D848-4DB3-A586-2AC6DE2A3933}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9D67F92-D848-4DB3-A586-2AC6DE2A3933}.Release|Any CPU.Build.0 = Release|Any CPU + {47773DCF-2892-4F87-993F-1A0912CC6420}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47773DCF-2892-4F87-993F-1A0912CC6420}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47773DCF-2892-4F87-993F-1A0912CC6420}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47773DCF-2892-4F87-993F-1A0912CC6420}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs index a1f854f0471..f2e92cf6fab 100644 --- a/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs +++ b/src/Nethermind/Nethermind.Consensus/Messages/TxErrorMessages.cs @@ -81,4 +81,16 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public const string InvalidBlobData = "InvalidTxBlobData: Number of blobs, hashes, commitments and proofs must match."; + + public const string InvalidCreateTxData + = "InvalidCreateTxData: Legacy createTx cannot create Eof code"; + + public const string TooManyEofInitcodes + = $"TooManyEofInitcodes: Eof initcodes count exceeded limit"; + + public const string EmptyEofInitcodesField + = $"EmptyEofInitcodesField: Eof initcodes count must be greater than 0"; + + public const string EofContractSizeInvalid + = "EofContractSizeInvalid: Eof initcode size is invalid (either 0 or too big)"; } diff --git a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs index 9dfa54cf9a0..6658f55edfd 100644 --- a/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs +++ b/src/Nethermind/Nethermind.Core/Buffers/CappedArray.cs @@ -4,6 +4,8 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; +using Nethermind.Core.Extensions; namespace Nethermind.Core.Buffers; @@ -14,6 +16,7 @@ namespace Nethermind.Core.Buffers; /// if it represent null. /// public readonly struct CappedArray + where T : struct { private readonly static CappedArray _null = default; private readonly static CappedArray _empty = new CappedArray(Array.Empty()); @@ -111,6 +114,16 @@ public readonly Span AsSpan(int start, int length) return AsSpan().ToArray(); } + public override string? ToString() + { + if (typeof(T) == typeof(byte)) + { + return SpanExtensions.ToHexString(MemoryMarshal.AsBytes(AsSpan()), withZeroX: true); + } + + return base.ToString(); + } + public readonly ArraySegment AsArraySegment() { return AsArraySegment(0, _length); diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index 9957bd1e912..36331999e16 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -29,6 +29,7 @@ public static unsafe partial class Bytes public static readonly BytesComparer Comparer = new(); public static readonly ReadOnlyMemory ZeroByte = new byte[] { 0 }; public static readonly ReadOnlyMemory OneByte = new byte[] { 1 }; + public static readonly ReadOnlyMemory TwoByte = new byte[] { 2 }; private class BytesEqualityComparer : EqualityComparer { @@ -367,29 +368,57 @@ public static BigInteger ToUnsignedBigInteger(this ReadOnlySpan bytes) return new(bytes, true, true); } - public static uint ReadEthUInt32(this Span bytes) + public static short ReadEthInt16(this ReadOnlySpan bytes) { - return ReadEthUInt32((ReadOnlySpan)bytes); + if (bytes.Length > 2) + { + bytes = bytes.Slice(bytes.Length - 2, 2); + } + + return bytes.Length switch + { + 2 => BinaryPrimitives.ReadInt16BigEndian(bytes), + 1 => bytes[0], + _ => 0 + }; } - public static uint ReadEthUInt32(this ReadOnlySpan bytes) + public static ushort ReadEthUInt16(this ReadOnlySpan bytes) { - if (bytes.Length > 4) + if (bytes.Length > 2) { - bytes = bytes.Slice(bytes.Length - 4, 4); + bytes = bytes.Slice(bytes.Length - 2, 2); } - if (bytes.Length == 4) + return bytes.Length switch { - return BinaryPrimitives.ReadUInt32BigEndian(bytes); + 2 => BinaryPrimitives.ReadUInt16BigEndian(bytes), + 1 => bytes[0], + _ => 0 + }; + } + + public static ushort ReadEthUInt16LittleEndian(this Span bytes) + { + if (bytes.Length > 2) + { + bytes = bytes.Slice(bytes.Length - 2, 2); } - Span fourBytes = stackalloc byte[4]; - bytes.CopyTo(fourBytes[(4 - bytes.Length)..]); - return BinaryPrimitives.ReadUInt32BigEndian(fourBytes); + return bytes.Length switch + { + 2 => BinaryPrimitives.ReadUInt16LittleEndian(bytes), + 1 => bytes[0], + _ => 0 + }; } - public static uint ReadEthUInt32LittleEndian(this Span bytes) + public static uint ReadEthUInt32(this Span bytes) + { + return ReadEthUInt32((ReadOnlySpan)bytes); + } + + public static uint ReadEthUInt32(this ReadOnlySpan bytes) { if (bytes.Length > 4) { @@ -398,12 +427,12 @@ public static uint ReadEthUInt32LittleEndian(this Span bytes) if (bytes.Length == 4) { - return BinaryPrimitives.ReadUInt32LittleEndian(bytes); + return BinaryPrimitives.ReadUInt32BigEndian(bytes); } Span fourBytes = stackalloc byte[4]; bytes.CopyTo(fourBytes[(4 - bytes.Length)..]); - return BinaryPrimitives.ReadUInt32LittleEndian(fourBytes); + return BinaryPrimitives.ReadUInt32BigEndian(fourBytes); } public static int ReadEthInt32(this Span bytes) diff --git a/src/Nethermind/Nethermind.Core/Extensions/Int16Extensions.cs b/src/Nethermind/Nethermind.Core/Extensions/Int16Extensions.cs new file mode 100644 index 00000000000..67cc0acd737 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Extensions/Int16Extensions.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; + +namespace Nethermind.Core.Extensions; + +public static class short16Extensions +{ + public static byte[] ToByteArray(this short value) + { + byte[] bytes = new byte[sizeof(short)]; + BinaryPrimitives.WriteInt16BigEndian(bytes, value); + return bytes; + } + + public static byte[] ToBigEndianByteArray(this ushort value) + { + byte[] bytes = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + return bytes; + } + + public static byte[] ToBigEndianByteArray(this short value) + => ToBigEndianByteArray((ushort)value); + +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs b/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs index 7417a66bfb7..d95a7f69b0c 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Int64Extensions.cs @@ -26,7 +26,7 @@ public static ReadOnlySpan ToBigEndianSpanWithoutLeadingZeros(this long va public static byte[] ToBigEndianByteArrayWithoutLeadingZeros(this long value) => value.ToBigEndianSpanWithoutLeadingZeros(out _).ToArray(); - public static byte[] ToBigEndianByteArray(this long value) + public static byte[] ToBigEndianByteArray(this ulong value) { byte[] bytes = BitConverter.GetBytes(value); if (BitConverter.IsLittleEndian) @@ -36,6 +36,10 @@ public static byte[] ToBigEndianByteArray(this long value) return bytes; } + + public static byte[] ToBigEndianByteArray(this long value) + => ToBigEndianByteArray((ulong)value); + public static void WriteBigEndian(this long value, Span output) { BinaryPrimitives.WriteInt64BigEndian(output, value); diff --git a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs index 71505417f89..1ecd2adc0d8 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/IntExtensions.cs @@ -36,7 +36,7 @@ public static byte[] ToByteArray(this int value) return bytes; } - public static byte[] ToBigEndianByteArray(this int value) + public static byte[] ToBigEndianByteArray(this uint value) { byte[] bytes = BitConverter.GetBytes(value); if (BitConverter.IsLittleEndian) @@ -46,4 +46,7 @@ public static byte[] ToBigEndianByteArray(this int value) return bytes; } + + public static byte[] ToBigEndianByteArray(this int value) + => ToBigEndianByteArray((uint)value); } diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index 0189f27f4fa..bb72d244aed 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -308,6 +308,12 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// bool IsEip6780Enabled { get; } + /// + /// Eof execution env in EVM + /// + bool IsEofEnabled { get; } + + /// /// /// Transactions that allows code delegation for EOA /// diff --git a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs index 01c65e977f9..56b98cb3c09 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -82,6 +82,9 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec public virtual bool IsOpGraniteEnabled => spec.IsOpGraniteEnabled; public virtual ulong WithdrawalTimestamp => spec.WithdrawalTimestamp; public virtual ulong Eip4844TransitionTimestamp => spec.Eip4844TransitionTimestamp; + + public virtual bool IsEofEnabled => spec.IsEofEnabled; + public virtual bool IsEip158IgnoredAccount(Address address) => spec.IsEip158IgnoredAccount(address); public bool IsEip4844FeeCollectorEnabled => spec.IsEip4844FeeCollectorEnabled; } diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index 987af864482..3b0a97470c1 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -21,6 +21,7 @@ namespace Nethermind.Core [DebuggerDisplay("{Hash}, Value: {Value}, To: {To}, Gas: {GasLimit}")] public class Transaction { + public byte[] EofMagic = [0xEF, 0x00]; public const byte MaxTxType = 0x7F; public const int BaseTxGasCost = 21000; @@ -57,6 +58,8 @@ public class Transaction public Signature? Signature { get; set; } public bool IsSigned => Signature is not null; public bool IsContractCreation => To is null; + public bool IsEofContractCreation => IsContractCreation && (Data?.Span.StartsWith(EofMagic) ?? false); + public bool IsLegacyContractCreation => IsContractCreation && !IsEofContractCreation; public bool IsMessageCall => To is not null; [MemberNotNullWhen(true, nameof(AuthorizationList))] diff --git a/src/Nethermind/Nethermind.EOFParse.Runner/Nethermind.EOFParse.Runner.csproj b/src/Nethermind/Nethermind.EOFParse.Runner/Nethermind.EOFParse.Runner.csproj new file mode 100644 index 00000000000..973322a044a --- /dev/null +++ b/src/Nethermind/Nethermind.EOFParse.Runner/Nethermind.EOFParse.Runner.csproj @@ -0,0 +1,24 @@ + + + + Exe + netheofparse + annotations + true + true + false + false + true + true + + + + + + + + + + + + diff --git a/src/Nethermind/Nethermind.EOFParse.Runner/Program.cs b/src/Nethermind/Nethermind.EOFParse.Runner/Program.cs new file mode 100644 index 00000000000..09b3ca89e87 --- /dev/null +++ b/src/Nethermind/Nethermind.EOFParse.Runner/Program.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using CommandLine; +using Nethermind.Core.Extensions; +using Nethermind.Evm.EvmObjectFormat; + +namespace Nethermind.EOFParse.Runner +{ + internal class Program + { + public class Options + { + [Option('i', "input", Required = false, + HelpText = "Raw eof input")] + public string Input { get; set; } + + [Option('x', "stdin", Required = false, + HelpText = + "Interactive testing mode.")] + public bool Stdin { get; set; } + } + + public static void Main(params string[] args) + { + ParserResult result = Parser.Default.ParseArguments(args); + if (result is Parsed options) + Run(options.Value); + } + + private static void Run(Options options) + { + string input = options.Input; + if (options.Stdin || input?.Length == 0) + { + input = Console.ReadLine(); + } + + while (!string.IsNullOrWhiteSpace(input)) + { + if (!input.StartsWith('#')) + { + input = new string(input.Where(c => char.IsLetterOrDigit(c)).ToArray()); + + var bytecode = Bytes.FromHexString(input); + try + { + var validationResult = EofValidator.IsValidEof(bytecode, ValidationStrategy.ValidateRuntimeMode, + out EofContainer? header); + if (validationResult) + { + var sectionCount = header.Value.CodeSections.Length; + var subcontainerCount = header.Value.ContainerSections?.Length ?? 0; + var dataCount = header.Value.DataSection.Length; + Console.WriteLine($"OK {sectionCount}/{subcontainerCount}/{dataCount}"); + } + else + { + Console.WriteLine($"err: unknown"); + } + } + catch (Exception e) + { + Console.WriteLine($"err: {e.Message}"); + } + + + if (!options.Stdin) + break; + } + + input = Console.ReadLine(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.EOFParse.Runner/Properties/launchSettings.json b/src/Nethermind/Nethermind.EOFParse.Runner/Properties/launchSettings.json new file mode 100644 index 00000000000..e60316dc652 --- /dev/null +++ b/src/Nethermind/Nethermind.EOFParse.Runner/Properties/launchSettings.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "Valid": { + "commandName": "Project", + "commandLineArgs": "-i ef00010100040200010006040000000080000260006000fe00" + }, + "Invalid": { + "commandName": "Project", + "commandLineArgs": "-i ef000101000402000100010400000000800000c0" + }, + "Console": { + "commandName": "Project", + "commandLineArgs": "-x" + }, + "no args": { + "commandName": "Project", + "commandLineArgs": "" + } + } +} diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index be98ea39aab..93a0c4548cf 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -55,7 +55,7 @@ public void GlobalSetup() codeInfo: new CodeInfo(ByteCode), value: 0, transferValue: 0, - txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, null, codeInfoRepository), + txExecutionContext: new TxExecutionContext(_header, Address.Zero, 0, [], codeInfoRepository), inputData: default ); diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index f83b9b9846d..b7dec4af47d 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -72,7 +72,7 @@ public void TryGetDelegation_CodeIsNotDelegation_ReturnsFalse(byte[] code) stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); CodeInfoRepository sut = new(); - sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(false); + sut.TryGetDelegation(stateProvider, TestItem.AddressA, Substitute.For(), out _).Should().Be(false); } @@ -102,7 +102,7 @@ public void TryGetDelegation_CodeTryGetDelegation_ReturnsTrue(byte[] code) stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); CodeInfoRepository sut = new(); - sut.TryGetDelegation(stateProvider, TestItem.AddressA, out _).Should().Be(true); + sut.TryGetDelegation(stateProvider, TestItem.AddressA, Substitute.For(), out _).Should().Be(true); } [TestCaseSource(nameof(DelegationCodeCases))] @@ -117,7 +117,7 @@ public void TryGetDelegation_CodeTryGetDelegation_CorrectDelegationAddressIsSet( CodeInfoRepository sut = new(); Address result; - sut.TryGetDelegation(stateProvider, TestItem.AddressA, out result); + sut.TryGetDelegation(stateProvider, TestItem.AddressA, Substitute.For(), out result); result.Should().Be(new Address(code.Slice(3, Address.Size))); } @@ -138,7 +138,7 @@ public void GetExecutableCodeHash_CodeTryGetDelegation_ReturnsHashOfDelegated(by CodeInfoRepository sut = new(); - sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(delegationCode).ValueHash256); + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA, Substitute.For()).Should().Be(Keccak.Compute(delegationCode).ValueHash256); } [TestCaseSource(nameof(NotDelegationCodeCases))] @@ -153,7 +153,7 @@ public void GetExecutableCodeHash_CodeIsNotDelegation_ReturnsCodeHashOfAddress(b CodeInfoRepository sut = new(); - sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA).Should().Be(Keccak.Compute(code).ValueHash256); + sut.GetExecutableCodeHash(stateProvider, TestItem.AddressA, Substitute.For()).Should().Be(Keccak.Compute(code).ValueHash256); } [TestCaseSource(nameof(DelegationCodeCases))] @@ -171,7 +171,7 @@ public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[ stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); CodeInfoRepository sut = new(); - CodeInfo result = sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()); + ICodeInfo result = sut.GetCachedCodeInfo(stateProvider, TestItem.AddressA, Substitute.For()); result.MachineCode.ToArray().Should().BeEquivalentTo(delegationCode); } diff --git a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs index 59844dba4c0..abf2c78e3cd 100644 --- a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs @@ -1,13 +1,16 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Linq; using FluentAssertions; +using Nethermind.Core.Collections; using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Logging; using Nethermind.Specs; +using Nethermind.Specs.Forks; using NUnit.Framework; namespace Nethermind.Evm.Test @@ -73,19 +76,8 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase ConstantinopleFixInstructions.Union( new[] { Instruction.SELFBALANCE, Instruction.CHAINID }).ToArray(); - private static readonly Instruction[] BerlinInstructions = - IstanbulInstructions.Union( - // new[] - // { - // Instruction.BEGINSUB, - // Instruction.JUMPSUB, - // Instruction.RETURNSUB - // } - new Instruction[] { } - ).ToArray(); - private static readonly Instruction[] LondonInstructions = - BerlinInstructions.Union( + IstanbulInstructions.Union( new Instruction[] { Instruction.BASEFEE @@ -112,6 +104,31 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase } ).ToArray(); + private static readonly Instruction[] OsakaInstructions = + CancunInstructions.Union( + new Instruction[] + { + Instruction.RJUMP, + Instruction.RJUMPI, + Instruction.RJUMPV, + Instruction.CALLF, + Instruction.RETF, + Instruction.JUMPF, + Instruction.EOFCREATE, + Instruction.RETURNCONTRACT, + Instruction.DATASIZE, + Instruction.DATACOPY, + Instruction.DATALOAD, + Instruction.DATALOADN, + Instruction.SWAPN, + Instruction.DUPN, + Instruction.EXCHANGE, + Instruction.EXTCALL, + Instruction.EXTDELEGATECALL, + Instruction.EXTSTATICCALL, + } + ).ToArray(); + private readonly Dictionary _validOpcodes = new() { @@ -123,11 +140,13 @@ private readonly Dictionary _validOpcodes {(ForkActivation)MainnetSpecProvider.ConstantinopleFixBlockNumber, ConstantinopleFixInstructions}, {(ForkActivation)MainnetSpecProvider.IstanbulBlockNumber, IstanbulInstructions}, {(ForkActivation)MainnetSpecProvider.MuirGlacierBlockNumber, IstanbulInstructions}, - {(ForkActivation)MainnetSpecProvider.BerlinBlockNumber, BerlinInstructions}, + {(ForkActivation)MainnetSpecProvider.BerlinBlockNumber, IstanbulInstructions}, {(ForkActivation)MainnetSpecProvider.LondonBlockNumber, LondonInstructions}, {MainnetSpecProvider.ShanghaiActivation, ShanghaiInstructions}, {MainnetSpecProvider.CancunActivation, CancunInstructions}, - {(long.MaxValue, ulong.MaxValue), CancunInstructions} + {MainnetSpecProvider.PragueActivation, CancunInstructions}, + {MainnetSpecProvider.OsakaActivation, OsakaInstructions}, + {(long.MaxValue, ulong.MaxValue), OsakaInstructions} }; private const string InvalidOpCodeErrorMessage = "BadInstruction"; @@ -140,6 +159,7 @@ protected override ILogManager GetLogManager() return _logManager; } + [Ignore("Test Incorrect")] [TestCase(0)] [TestCase(MainnetSpecProvider.HomesteadBlockNumber)] [TestCase(MainnetSpecProvider.SpuriousDragonBlockNumber)] @@ -159,14 +179,125 @@ public void Test(long blockNumber, ulong? timestamp = null) Instruction[] validOpcodes = _validOpcodes[(blockNumber, timestamp)]; for (int i = 0; i <= byte.MaxValue; i++) { - logger.Info($"============ Testing opcode {i}=================="); + bool isEofContext = timestamp >= MainnetSpecProvider.PragueActivation.Timestamp; + bool isValidOpcode = false; + + Instruction opcode = (Instruction)i; + + if (opcode == Instruction.RETF) continue; + byte[] code = Prepare.EvmCode - .Op((byte)i) - .Done; + .Op((byte)i) + .Done; - bool isValidOpcode = ((Instruction)i != Instruction.INVALID) && validOpcodes.Contains((Instruction)i); - TestAllTracerWithOutput result = Execute((blockNumber, timestamp ?? 0), 1_000_000, code); + // hexString to array + + if (InstructionExtensions.IsValid(opcode, IsEofContext: true) && !InstructionExtensions.IsValid(opcode, IsEofContext: false)) + { + // will be tested in EOFCREATE container validations + if (opcode is Instruction.RETURNCONTRACT) continue; + var opcodeMetadata = InstructionExtensions.StackRequirements(opcode); + opcodeMetadata.InputCount ??= 1; + opcodeMetadata.OutputCount ??= (opcode is Instruction.DUPN ? (ushort)2 : (ushort)1); + + bool isFunCall = opcode is Instruction.CALLF; + + bool isCreateCall = opcode is Instruction.EOFCREATE; + byte[] runtimeContainer = Nethermind.Core.Extensions.Bytes.FromHexString("EF00010100040200010001040000000080000000"); + byte[] initcodeContainer = Nethermind.Core.Extensions.Bytes.FromHexString("EF00010100040200010004030001001404000000008000025F5FEE00") + .Concat(runtimeContainer) + .ToArray(); + + byte[] stackHeighExpected = BitConverter.GetBytes(Math.Max(opcodeMetadata.InputCount.Value, opcodeMetadata.OutputCount.Value)); + + List codesection = new(); + + for (var j = 0; j < opcodeMetadata.InputCount; j++) + { + codesection.AddRange( + Prepare.EvmCode + .PushSingle(0) + .Done + ); + } + + codesection.Add((byte)i); + + for (var j = 0; j < (opcodeMetadata.immediates ?? 3); j++) + { + if (isFunCall && j == 1) + { + codesection.Add(1); + continue; + } + codesection.Add(0); + } + + for (var j = 0; j < opcodeMetadata.OutputCount; j++) + { + codesection.AddRange( + Prepare.EvmCode + .Op(Instruction.POP) + .Done + ); + } + + if (opcode is not Instruction.JUMPF) + { + codesection.Add((byte)Instruction.STOP); + } + + + byte[] codeSectionSize = BitConverter.GetBytes((ushort)(codesection.Count)); + byte[] containerSectionSize = BitConverter.GetBytes((ushort)(initcodeContainer.Length)); + code = [ + // start header + 0xef, + 0x00, + 0x01, + 0x01, + 0x00, + (isFunCall ? (byte)0x08 : (byte)0x04), + 0x02, + 0x00, + (isFunCall ? (byte)0x02 : (byte)0x01), + codeSectionSize[1], + codeSectionSize[0], + .. (isFunCall ? [0x00, 0x01] : Array.Empty()), + .. (isCreateCall ? [0x03, 0x00, 0x01, containerSectionSize[1], containerSectionSize[0]] : Array.Empty()), + 0x04, + 0x00, + 0x20, + 0x00, + // end header + // start typesection + 0x00, + 0x80, + stackHeighExpected[1], + stackHeighExpected[0], + .. (isFunCall ? [0x00, 0x00, 0x00, 0x00] : Array.Empty()), + // end typesection + // start codesection + // start codesection 0 + .. codesection, + // end codesection 0 + // start codesection 1 + .. (isFunCall ? [(byte)Instruction.RETF] : Array.Empty()), + // end codesection 1 + // end codesection + // start container section + .. (isCreateCall ? initcodeContainer : Array.Empty()), + // end container section + // start data section + .. Enumerable.Range(0, 32).Select(b => (byte)b).ToArray() + // end data section + ]; + } + + logger.Info($"============ Testing opcode {i}=================="); + isValidOpcode = (opcode != Instruction.INVALID) && validOpcodes.Contains((Instruction)i); + TestAllTracerWithOutput result = Execute((blockNumber, timestamp ?? 0), 1_000_000, code); if (isValidOpcode) { result.Error.Should().NotBe(InvalidOpCodeErrorMessage, ((Instruction)i).ToString()); diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs index b2fd2017894..3a0a3997167 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs @@ -277,7 +277,7 @@ 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.DataStack, tracer.CurrentState.DataStackHead, tracer); - stack.PopLimbo(); + if (!stack.PopLimbo()) throw new EvmStackUnderflowException(); stack.PushByte(0x00); tracer.MoveNext(); diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionSubstateTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionSubstateTests.cs index 3b44d847768..db8fcc82c0c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionSubstateTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionSubstateTests.cs @@ -7,6 +7,7 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Extensions; +using Nethermind.Evm.CodeAnalysis; using NUnit.Framework; namespace Nethermind.Evm.Test @@ -26,7 +27,7 @@ public void should_return_proper_revert_error_when_there_is_no_exception() 0x05, 0x06, 0x07, 0x08, 0x09 }; ReadOnlyMemory readOnlyMemory = new(data); - TransactionSubstate transactionSubstate = new(readOnlyMemory, + TransactionSubstate transactionSubstate = new((CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, @@ -40,7 +41,7 @@ public void should_return_proper_revert_error_when_there_is_exception() { byte[] data = { 0x05, 0x06, 0x07, 0x08, 0x09 }; ReadOnlyMemory readOnlyMemory = new(data); - TransactionSubstate transactionSubstate = new(readOnlyMemory, + TransactionSubstate transactionSubstate = new((CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, @@ -54,7 +55,7 @@ public void should_return_weird_revert_error_when_there_is_exception() { byte[] data = TransactionSubstate.ErrorFunctionSelector.Concat(Bytes.FromHexString("0x00000001000000000000000000000000000000000000000012a9d65e7d180cfcf3601b6d00000000000000000000000000000000000000000000000000000001000276a400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000006a000000000300000000000115859c410282f6600012efb47fcfcad4f96c83d4ca676842fb03ef20a4770000000015f762bdaa80f6d9dc5518ff64cb7ba5717a10dabc4be3a41acd2c2f95ee22000012a9d65e7d180cfcf3601b6df0000000000000185594dac7eb0828ff000000000000000000000000")).ToArray(); ReadOnlyMemory readOnlyMemory = new(data); - TransactionSubstate transactionSubstate = new(readOnlyMemory, + TransactionSubstate transactionSubstate = new((CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, @@ -74,7 +75,7 @@ public void should_return_proper_revert_error_when_revert_custom_error_badly_imp byte[] data = Bytes.FromHexString(hex); ReadOnlyMemory readOnlyMemory = new(data); TransactionSubstate transactionSubstate = new( - readOnlyMemory, + (CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, @@ -147,7 +148,7 @@ public void should_return_proper_revert_error_when_using_special_functions((byte // See: https://docs.soliditylang.org/en/latest/control-structures.html#revert ReadOnlyMemory readOnlyMemory = new(tc.data); TransactionSubstate transactionSubstate = new( - readOnlyMemory, + (CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, @@ -171,7 +172,7 @@ public void should_return_proper_revert_error_when_revert_custom_error() }; ReadOnlyMemory readOnlyMemory = new(data); TransactionSubstate transactionSubstate = new( - readOnlyMemory, + (CodeInfo.Empty, readOnlyMemory), 0, new ArraySegment
(), new LogEntry[] { }, diff --git a/src/Nethermind/Nethermind.Evm/AddressExtensions.cs b/src/Nethermind/Nethermind.Evm/AddressExtensions.cs index d19f03056a5..ed886a60b5f 100644 --- a/src/Nethermind/Nethermind.Evm/AddressExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/AddressExtensions.cs @@ -25,15 +25,14 @@ public static Address From(Address? deployingAddress, in UInt256 nonce) return new Address(in contractAddressKeccak); } - public static Address From(Address deployingAddress, ReadOnlySpan salt, ReadOnlySpan initCode) { - // sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))) - Span bytes = new byte[1 + Address.Size + 32 + salt.Length]; + // sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code) ++ sha3(aux_data)) + Span bytes = new byte[1 + Address.Size + Keccak.Size + salt.Length]; bytes[0] = 0xff; - deployingAddress.Bytes.CopyTo(bytes.Slice(1, 20)); - salt.CopyTo(bytes.Slice(21, salt.Length)); - ValueKeccak.Compute(initCode).BytesAsSpan.CopyTo(bytes.Slice(21 + salt.Length, 32)); + deployingAddress.Bytes.CopyTo(bytes.Slice(1, Address.Size)); + salt.CopyTo(bytes.Slice(1 + Address.Size, salt.Length)); + ValueKeccak.Compute(initCode).BytesAsSpan.CopyTo(bytes.Slice(1 + Address.Size + salt.Length, Keccak.Size)); ValueHash256 contractAddressKeccak = ValueKeccak.Compute(bytes); return new Address(in contractAddressKeccak); diff --git a/src/Nethermind/Nethermind.Evm/BitmapHelper.cs b/src/Nethermind/Nethermind.Evm/BitmapHelper.cs new file mode 100644 index 00000000000..0facb32c3b4 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/BitmapHelper.cs @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Numerics; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace Nethermind.Evm; +public static class BitmapHelper +{ + private static readonly byte[] _lookup = + { + 0b0000_0000, + 0b0000_0001, + 0b0000_0011, + 0b0000_0111, + 0b0000_1111, + 0b0001_1111, + 0b0011_1111, + 0b0111_1111 + }; + + /// + /// Collects data locations in code. + /// An unset bit means the byte is an opcode, a set bit means it's data. + /// + public static byte[] CreateCodeBitmap(ReadOnlySpan code, bool isEof = false) + { + // The bitmap is 4 bytes longer than necessary, in case the code + // ends with a PUSH32, the algorithm will push zeroes onto the + // bitvector outside the bounds of the actual code. + byte[] bitvec = new byte[(code.Length / 8) + 1 + 4]; + + for (int pc = 0; pc < code.Length;) + { + var opMetadaata = ((Instruction)code[pc]).StackRequirements(); + + pc++; + + int numbits = + code[pc] == (byte)Instruction.RJUMPV + ? Instruction.RJUMPV.GetImmediateCount(isEof, code[pc]) + : opMetadaata.immediates.Value; + + if (numbits == 0) continue; + + HandleNumbits(numbits, bitvec, ref pc); + } + return bitvec; + } + + public static void HandleNumbits(int numbits, Span bitvec, scoped ref int pc) + { + if (numbits == 0) return; + + if (numbits >= 8) + { + for (; numbits >= 16; numbits -= 16) + { + bitvec.Set16(pc); + pc += 16; + } + + for (; numbits >= 8; numbits -= 8) + { + bitvec.Set8(pc); + pc += 8; + } + } + + if (numbits > 1) + { + bitvec.SetN(pc, _lookup[numbits]); + pc += numbits; + } + else + { + bitvec.Set1(pc); + pc += numbits; + } + } + /// + /// Checks if the position is in a code segment. + /// + public static bool IsCodeSegment(Span bitvec, int pos) + { + return (bitvec[pos / 8] & (0x80 >> (pos % 8))) == 0; + } + + private static void Set1(this Span bitvec, int pos) + { + bitvec[pos / 8] |= (byte)(1 << (pos % 8)); + } + + private static void SetN(this Span bitvec, int pos, ushort flag) + { + ushort a = (ushort)(flag << (pos % 8)); + bitvec[pos / 8] |= (byte)a; + byte b = (byte)(a >> 8); + if (b != 0) + { + // If the bit-setting affects the neighbouring byte, we can assign - no need to OR it, + // since it's the first write to that byte + bitvec[pos / 8 + 1] = b; + } + } + + private static void Set8(this Span bitvec, int pos) + { + byte a = (byte)(0xFF << (pos % 8)); + bitvec[pos / 8] |= a; + bitvec[pos / 8 + 1] = (byte)~a; + } + + private static void Set16(this Span bitvec, int pos) + { + byte a = (byte)(0xFF << (pos % 8)); + bitvec[pos / 8] |= a; + bitvec[pos / 8 + 1] = 0xFF; + bitvec[pos / 8 + 2] = (byte)~a; + } + + public static bool CheckCollision(ReadOnlySpan codeSegments, ReadOnlySpan jumpmask) + { + int count = Math.Min(codeSegments.Length, jumpmask.Length); + + int i = 0; + + ref byte left = ref MemoryMarshal.GetReference(codeSegments); + ref byte right = ref MemoryMarshal.GetReference(jumpmask); + + if (Vector256.IsHardwareAccelerated && count >= Vector256.Count) + { + for (; (uint)(i + Vector256.Count) <= (uint)count; i += Vector256.Count) + { + Vector256 result = Vector256.LoadUnsafe(ref left, (uint)i) & Vector256.LoadUnsafe(ref right, (uint)i); + if (result != default) + { + return true; + } + } + } + else if (Vector128.IsHardwareAccelerated && count >= Vector128.Count) + { + for (; (i + Vector128.Count) <= (uint)count; i += Vector128.Count) + { + Vector128 result = Vector128.LoadUnsafe(ref left, (uint)i) & Vector128.LoadUnsafe(ref right, (uint)i); + if (result != default) + { + return true; + } + } + } + + for (; i < count; i++) + { + if ((codeSegments[i] & jumpmask[i]) != 0) + { + return true; + } + } + + return false; + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs index e1bb762bceb..9efaa83028d 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/CodeInfo.cs @@ -5,10 +5,12 @@ using System.Threading; using Nethermind.Evm.Precompiles; +using System.Diagnostics; +using Nethermind.Evm.EvmObjectFormat; namespace Nethermind.Evm.CodeAnalysis { - public class CodeInfo : IThreadPoolWorkItem + public class CodeInfo : ICodeInfo, IThreadPoolWorkItem { public ReadOnlyMemory MachineCode { get; } public IPrecompile? Precompile { get; set; } @@ -55,5 +57,13 @@ public void AnalyseInBackgroundIfRequired() ThreadPool.UnsafeQueueUserWorkItem(this, preferLocal: false); } } + + public SectionHeader CodeSectionOffset(int idx) + => throw new UnreachableException(); + + public SectionHeader? ContainerSectionOffset(int idx) + => throw new UnreachableException(); + + public int PcOffset() => 0; } } diff --git a/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs b/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs index 08fd7440f06..44d507d7690 100644 --- a/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs +++ b/src/Nethermind/Nethermind.Evm/CodeDepositHandler.cs @@ -3,13 +3,14 @@ using System; using Nethermind.Core.Specs; +using Nethermind.Evm.EvmObjectFormat; namespace Nethermind.Evm { public static class CodeDepositHandler { private const byte InvalidStartingCodeByte = 0xEF; - public static long CalculateCost(int byteCodeLength, IReleaseSpec spec) + public static long CalculateCost(IReleaseSpec spec, int byteCodeLength) { if (spec.LimitCodeSize && byteCodeLength > spec.MaxCodeSize) return long.MaxValue; @@ -17,14 +18,24 @@ public static long CalculateCost(int byteCodeLength, IReleaseSpec spec) return GasCostOf.CodeDeposit * byteCodeLength; } - public static bool CodeIsInvalid(IReleaseSpec spec, byte[] output) - { - return spec.IsEip3541Enabled && output.Length >= 1 && output[0] == InvalidStartingCodeByte; - } + public static bool CodeIsInvalid(IReleaseSpec spec, ReadOnlyMemory code, int fromVersion) + => !CodeIsValid(spec, code, fromVersion); + + public static bool CodeIsValid(IReleaseSpec spec, ReadOnlyMemory code, int fromVersion) + => spec.IsEofEnabled ? IsValidWithEofRules(spec, code, fromVersion) : IsValidWithLegacyRules(spec, code); + + public static bool IsValidWithLegacyRules(IReleaseSpec spec, ReadOnlyMemory code) + => !spec.IsEip3541Enabled || !code.StartsWith(InvalidStartingCodeByte); - public static bool CodeIsInvalid(IReleaseSpec spec, ReadOnlyMemory output) + public static bool IsValidWithEofRules(IReleaseSpec spec, ReadOnlyMemory code, int fromVersion, EvmObjectFormat.ValidationStrategy strategy = EvmObjectFormat.ValidationStrategy.Validate) { - return spec.IsEip3541Enabled && output.Length >= 1 && output.StartsWith(InvalidStartingCodeByte); + bool isCodeEof = EofValidator.IsEof(code, out byte codeVersion); + bool valid = code.Length >= 1 + && codeVersion >= fromVersion + && (isCodeEof + ? EofValidator.IsValidEof(code, strategy, out _) + : (fromVersion > 0 ? false : IsValidWithLegacyRules(spec, code))); + return valid; } } } diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs new file mode 100644 index 00000000000..aaa8d0066ca --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/CodeInfoFactory.cs @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Specs; +using Nethermind.Evm.EvmObjectFormat; +using System; + +namespace Nethermind.Evm.CodeAnalysis; + +public static class CodeInfoFactory +{ + public static ICodeInfo CreateCodeInfo(ReadOnlyMemory code, IReleaseSpec spec, EvmObjectFormat.ValidationStrategy validationRules = EvmObjectFormat.ValidationStrategy.ExractHeader) + { + CodeInfo codeInfo = new CodeInfo(code); + if (spec.IsEofEnabled && code.Span.StartsWith(EofValidator.MAGIC)) + { + if (EofValidator.IsValidEof(code, validationRules, out EofContainer? container)) + { + return new EofCodeInfo(container.Value); + } + } + codeInfo.AnalyseInBackgroundIfRequired(); + return codeInfo; + } + + public static bool CreateInitCodeInfo(Memory data, IReleaseSpec spec, out ICodeInfo codeInfo, out Memory extraCalldata) + { + extraCalldata = default; + if (spec.IsEofEnabled && data.Span.StartsWith(EofValidator.MAGIC)) + { + if (EofValidator.IsValidEof(data, EvmObjectFormat.ValidationStrategy.ValidateInitcodeMode | EvmObjectFormat.ValidationStrategy.ValidateFullBody | EvmObjectFormat.ValidationStrategy.AllowTrailingBytes, out EofContainer? eofContainer)) + { + int containerSize = eofContainer.Value.Header.DataSection.EndOffset; + extraCalldata = data.Slice(containerSize); + ICodeInfo innerCodeInfo = new CodeInfo(data.Slice(0, containerSize)); + codeInfo = new EofCodeInfo(eofContainer.Value); + return true; + } + codeInfo = null; + return false; + } + codeInfo = new CodeInfo(data); + codeInfo.AnalyseInBackgroundIfRequired(); + return true; + } +} diff --git a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs index fe631b3fb10..9e71ca38c3e 100644 --- a/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/CodeInfoRepository.cs @@ -16,45 +16,46 @@ using Nethermind.Evm.Precompiles.Bls; using Nethermind.Evm.Precompiles.Snarks; using Nethermind.State; +using Nethermind.Evm.EvmObjectFormat; using Nethermind.Crypto; namespace Nethermind.Evm; public class CodeInfoRepository : ICodeInfoRepository { - private static readonly FrozenDictionary _precompiles = InitializePrecompiledContracts(); + private static readonly FrozenDictionary _precompiles = InitializePrecompiledContracts(); private static readonly CodeLruCache _codeCache = new(); - private readonly FrozenDictionary _localPrecompiles; + private readonly FrozenDictionary _localPrecompiles; - private static FrozenDictionary InitializePrecompiledContracts() + private static FrozenDictionary InitializePrecompiledContracts() { - return new Dictionary + return new Dictionary { - [EcRecoverPrecompile.Address] = new(EcRecoverPrecompile.Instance), - [Sha256Precompile.Address] = new(Sha256Precompile.Instance), - [Ripemd160Precompile.Address] = new(Ripemd160Precompile.Instance), - [IdentityPrecompile.Address] = new(IdentityPrecompile.Instance), - - [Bn254AddPrecompile.Address] = new(Bn254AddPrecompile.Instance), - [Bn254MulPrecompile.Address] = new(Bn254MulPrecompile.Instance), - [Bn254PairingPrecompile.Address] = new(Bn254PairingPrecompile.Instance), - [ModExpPrecompile.Address] = new(ModExpPrecompile.Instance), - - [Blake2FPrecompile.Address] = new(Blake2FPrecompile.Instance), - - [G1AddPrecompile.Address] = new(G1AddPrecompile.Instance), - [G1MulPrecompile.Address] = new(G1MulPrecompile.Instance), - [G1MSMPrecompile.Address] = new(G1MSMPrecompile.Instance), - [G2AddPrecompile.Address] = new(G2AddPrecompile.Instance), - [G2MulPrecompile.Address] = new(G2MulPrecompile.Instance), - [G2MSMPrecompile.Address] = new(G2MSMPrecompile.Instance), - [PairingCheckPrecompile.Address] = new(PairingCheckPrecompile.Instance), - [MapFpToG1Precompile.Address] = new(MapFpToG1Precompile.Instance), - [MapFp2ToG2Precompile.Address] = new(MapFp2ToG2Precompile.Instance), - - [PointEvaluationPrecompile.Address] = new(PointEvaluationPrecompile.Instance), - - [Secp256r1Precompile.Address] = new(Secp256r1Precompile.Instance), + [EcRecoverPrecompile.Address] = new CodeInfo(EcRecoverPrecompile.Instance), + [Sha256Precompile.Address] = new CodeInfo(Sha256Precompile.Instance), + [Ripemd160Precompile.Address] = new CodeInfo(Ripemd160Precompile.Instance), + [IdentityPrecompile.Address] = new CodeInfo(IdentityPrecompile.Instance), + + [Bn254AddPrecompile.Address] = new CodeInfo(Bn254AddPrecompile.Instance), + [Bn254MulPrecompile.Address] = new CodeInfo(Bn254MulPrecompile.Instance), + [Bn254PairingPrecompile.Address] = new CodeInfo(Bn254PairingPrecompile.Instance), + [ModExpPrecompile.Address] = new CodeInfo(ModExpPrecompile.Instance), + + [Blake2FPrecompile.Address] = new CodeInfo(Blake2FPrecompile.Instance), + + [G1AddPrecompile.Address] = new CodeInfo(G1AddPrecompile.Instance), + [G1MulPrecompile.Address] = new CodeInfo(G1MulPrecompile.Instance), + [G1MSMPrecompile.Address] = new CodeInfo(G1MSMPrecompile.Instance), + [G2AddPrecompile.Address] = new CodeInfo(G2AddPrecompile.Instance), + [G2MulPrecompile.Address] = new CodeInfo(G2MulPrecompile.Instance), + [G2MSMPrecompile.Address] = new CodeInfo(G2MSMPrecompile.Instance), + [PairingCheckPrecompile.Address] = new CodeInfo(PairingCheckPrecompile.Instance), + [MapFpToG1Precompile.Address] = new CodeInfo(MapFpToG1Precompile.Instance), + [MapFp2ToG2Precompile.Address] = new CodeInfo(MapFp2ToG2Precompile.Instance), + + [PointEvaluationPrecompile.Address] = new CodeInfo(PointEvaluationPrecompile.Instance), + + [Secp256r1Precompile.Address] = new CodeInfo(Secp256r1Precompile.Instance), }.ToFrozenDictionary(); } @@ -65,7 +66,7 @@ public CodeInfoRepository(ConcurrentDictionary kvp.Key, kvp => CreateCachedPrecompile(kvp, precompileCache)); } - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public ICodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; if (codeSource.IsPrecompile(vmSpec)) @@ -73,19 +74,19 @@ public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IR return _localPrecompiles[codeSource]; } - CodeInfo cachedCodeInfo = InternalGetCachedCode(worldState, codeSource); + ICodeInfo cachedCodeInfo = InternalGetCachedCode(worldState, codeSource, vmSpec); if (TryGetDelegatedAddress(cachedCodeInfo.MachineCode.Span, out delegationAddress)) { - cachedCodeInfo = InternalGetCachedCode(worldState, delegationAddress); + cachedCodeInfo = InternalGetCachedCode(worldState, delegationAddress, vmSpec); } return cachedCodeInfo; } - private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource) + private static ICodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, Address codeSource, IReleaseSpec vmSpec) { - CodeInfo? cachedCodeInfo = null; + ICodeInfo? cachedCodeInfo = null; ValueHash256 codeHash = worldState.GetCodeHash(codeSource); if (codeHash == Keccak.OfAnEmptyString.ValueHash256) { @@ -102,8 +103,7 @@ private static CodeInfo InternalGetCachedCode(IReadOnlyStateProvider worldState, MissingCode(codeSource, codeHash); } - cachedCodeInfo = new CodeInfo(code); - cachedCodeInfo.AnalyseInBackgroundIfRequired(); + cachedCodeInfo = CodeInfoFactory.CreateCodeInfo(code, vmSpec, Nethermind.Evm.EvmObjectFormat.ValidationStrategy.ExractHeader); _codeCache.Set(codeHash, cachedCodeInfo); } else @@ -123,7 +123,7 @@ static void MissingCode(Address codeSource, in ValueHash256 codeHash) public void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec) { - CodeInfo codeInfo = new(code); + ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(code, spec, ValidationStrategy.ExractHeader); codeInfo.AnalyseInBackgroundIfRequired(); ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); @@ -146,7 +146,7 @@ public void SetDelegation(IWorldState state, Address codeSource, Address authori ///
/// /// - public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address, IReleaseSpec spec) { ValueHash256 codeHash = worldState.GetCodeHash(address); if (codeHash == Keccak.OfAnEmptyString.ValueHash256) @@ -154,7 +154,7 @@ public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address addres return Keccak.OfAnEmptyString.ValueHash256; } - CodeInfo codeInfo = InternalGetCachedCode(worldState, address); + ICodeInfo codeInfo = InternalGetCachedCode(worldState, address, spec); return codeInfo.IsEmpty ? Keccak.OfAnEmptyString.ValueHash256 : TryGetDelegatedAddress(codeInfo.MachineCode.Span, out Address? delegationAddress) @@ -178,13 +178,13 @@ private static bool TryGetDelegatedAddress(ReadOnlySpan code, [NotNullWhen return false; } - private CodeInfo CreateCachedPrecompile( - in KeyValuePair originalPrecompile, + private ICodeInfo CreateCachedPrecompile( + in KeyValuePair originalPrecompile, ConcurrentDictionary, bool)> cache) => - new(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache)); + new CodeInfo(new CachedPrecompile(originalPrecompile.Key.Value, originalPrecompile.Value.Precompile!, cache)); - public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => - TryGetDelegatedAddress(InternalGetCachedCode(worldState, address).MachineCode.Span, out delegatedAddress); + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress) => + TryGetDelegatedAddress(InternalGetCachedCode(worldState, address, spec).MachineCode.Span, out delegatedAddress); private class CachedPrecompile( Address address, @@ -216,33 +216,33 @@ private sealed class CodeLruCache { private const int CacheCount = 16; private const int CacheMax = CacheCount - 1; - private readonly ClockCache[] _caches; + private readonly ClockCache[] _caches; public CodeLruCache() { - _caches = new ClockCache[CacheCount]; + _caches = new ClockCache[CacheCount]; for (int i = 0; i < _caches.Length; i++) { // Cache per nibble to reduce contention as TxPool is very parallel - _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); + _caches[i] = new ClockCache(MemoryAllowance.CodeCacheSize / CacheCount); } } - public CodeInfo? Get(in ValueHash256 codeHash) + public ICodeInfo? Get(in ValueHash256 codeHash) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Get(codeHash); } - public bool Set(in ValueHash256 codeHash, CodeInfo codeInfo) + public bool Set(in ValueHash256 codeHash, ICodeInfo codeInfo) { - ClockCache cache = _caches[GetCacheIndex(codeHash)]; + ClockCache cache = _caches[GetCacheIndex(codeHash)]; return cache.Set(codeHash, codeInfo); } private static int GetCacheIndex(in ValueHash256 codeHash) => codeHash.Bytes[^1] & CacheMax; - public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out CodeInfo? codeInfo) + public bool TryGet(in ValueHash256 codeHash, [NotNullWhen(true)] out ICodeInfo? codeInfo) { codeInfo = Get(codeHash); return codeInfo is not null; diff --git a/src/Nethermind/Nethermind.Evm/EvmException.cs b/src/Nethermind/Nethermind.Evm/EvmException.cs index 7bc74c52c79..2849a6dd796 100644 --- a/src/Nethermind/Nethermind.Evm/EvmException.cs +++ b/src/Nethermind/Nethermind.Evm/EvmException.cs @@ -22,12 +22,13 @@ public enum EvmExceptionType InvalidSubroutineReturn, InvalidJumpDestination, AccessViolation, + AddressOutOfRange, StaticCallViolation, PrecompileFailure, TransactionCollision, NotEnoughBalance, Other, Revert, - InvalidCode + InvalidCode, } } diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs new file mode 100644 index 00000000000..cf3e51e0802 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeInfo.cs @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core.Extensions; +using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.EvmObjectFormat.Handlers; +using Nethermind.Evm.Precompiles; + +namespace Nethermind.Evm.CodeAnalysis; + +public class EofCodeInfo : ICodeInfo +{ + public EofContainer EofContainer { get; private set; } + public ReadOnlyMemory MachineCode => EofContainer.Container; + public IPrecompile? Precompile => null; + public int Version => EofContainer.Header.Version; + public bool IsEmpty => EofContainer.IsEmpty; + public ReadOnlyMemory TypeSection => EofContainer.TypeSection; + public ReadOnlyMemory CodeSection => EofContainer.CodeSection; + public ReadOnlyMemory DataSection => EofContainer.DataSection; + public ReadOnlyMemory ContainerSection => EofContainer.ContainerSection; + + public SectionHeader CodeSectionOffset(int sectionId) => EofContainer.Header.CodeSections[sectionId]; + public SectionHeader? ContainerSectionOffset(int sectionId) => EofContainer.Header.ContainerSections.Value[sectionId]; + public int PcOffset() => EofContainer.Header.CodeSections.Start; + + public (byte inputCount, byte outputCount, ushort maxStackHeight) GetSectionMetadata(int index) + { + ReadOnlySpan typesectionSpan = EofContainer.TypeSections[index].Span; + return + ( + typesectionSpan[Eof1.INPUTS_OFFSET], + typesectionSpan[Eof1.OUTPUTS_OFFSET], + typesectionSpan.Slice(Eof1.MAX_STACK_HEIGHT_OFFSET, Eof1.MAX_STACK_HEIGHT_LENGTH).ReadEthUInt16() + ); + } + + public EofCodeInfo(in EofContainer container) + { + EofContainer = container; + } +} diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeValidator.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeValidator.cs new file mode 100644 index 00000000000..17ebd80b37b --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofCodeValidator.cs @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Nethermind.Evm.EvmObjectFormat.Handlers; +using Nethermind.Logging; + +[assembly: InternalsVisibleTo("Nethermind.EofParser")] +[assembly: InternalsVisibleTo("Ethereum.Test.Base")] + +namespace Nethermind.Evm.EvmObjectFormat; + +public static class EofValidator +{ + // magic prefix : EofFormatByte is the first byte, EofFormatDiff is chosen to diff from previously rejected contract according to EIP3541 + public static byte[] MAGIC = { 0xEF, 0x00 }; + public const byte ONE_BYTE_LENGTH = 1; + public const byte TWO_BYTE_LENGTH = 2; + public const byte VERSION_OFFSET = TWO_BYTE_LENGTH; // magic lenght + + private static readonly Dictionary _eofVersionHandlers = new(); + internal static ILogger Logger { get; set; } = NullLogger.Instance; + + static EofValidator() + { + _eofVersionHandlers.Add(Eof1.VERSION, new Eof1()); + } + + /// + /// returns whether the code passed is supposed to be treated as Eof regardless of its validity. + /// + /// Machine code to be checked + /// + public static bool IsEof(ReadOnlyMemory container, [NotNullWhen(true)] out byte version) + { + if (container.Length >= MAGIC.Length + 1) + { + version = container.ByteAt(MAGIC.Length); + return container.StartsWith(MAGIC); + } + else + { + version = 0; + return false; + } + + } + + public static bool IsValidEofHeader(ReadOnlyMemory code, [NotNullWhen(true)] out EofHeader? header) + { + if (IsEof(code, out byte version) && _eofVersionHandlers.TryGetValue(version, out IEofVersionHandler handler)) + { + return handler.TryParseEofHeader(code, ValidationStrategy.Validate, out header); + } + + if (Logger.IsTrace) Logger.Trace($"EOF: Eof not recognized"); + header = null; + return false; + } + + public static bool IsValidEof(ReadOnlyMemory code, ValidationStrategy strategy, [NotNullWhen(true)] out EofContainer? eofContainer) + { + if (strategy == ValidationStrategy.None) + { + if (Logger.IsTrace) Logger.Trace($"EOF: No validation"); + eofContainer = null; + return true; + } + + if (strategy.HasFlag(ValidationStrategy.HasEofMagic) && !code.StartsWith(MAGIC)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: No MAGIC as start of code"); + eofContainer = null; + return false; + } + + if (IsEof(code, out byte version) && _eofVersionHandlers.TryGetValue(version, out IEofVersionHandler handler)) + { + return handler.TryGetEofContainer(code, strategy, out eofContainer); + } + + if (Logger.IsTrace) Logger.Trace($"EOF: Not EOF"); + eofContainer = null; + return false; + } +} diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofHeader.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofHeader.cs new file mode 100644 index 00000000000..4211222949d --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofHeader.cs @@ -0,0 +1,113 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using Nethermind.Evm.EvmObjectFormat.Handlers; + +namespace Nethermind.Evm.EvmObjectFormat; + +public readonly struct EofContainer +{ + public readonly ReadOnlyMemory Container; + public bool IsEmpty => Container.IsEmpty; + + public EofContainer(ReadOnlyMemory container, EofHeader eofHeader) + { + Container = container; + Header = eofHeader; + Prefix = container.Slice(0, eofHeader.PrefixSize); + TypeSection = container[(Range)eofHeader.TypeSection]; + CodeSection = container[(Range)eofHeader.CodeSections]; + ContainerSection = eofHeader.ContainerSections.HasValue ? container[(Range)eofHeader.ContainerSections.Value] : ReadOnlyMemory.Empty; + + TypeSections = new ReadOnlyMemory[eofHeader.CodeSections.Count]; + for (var i = 0; i < eofHeader.CodeSections.Count; i++) + { + TypeSections[i] = TypeSection.Slice(i * Eof1.MINIMUM_TYPESECTION_SIZE, Eof1.MINIMUM_TYPESECTION_SIZE); + } + + CodeSections = new ReadOnlyMemory[eofHeader.CodeSections.Count]; + for (var i = 0; i < eofHeader.CodeSections.Count; i++) + { + CodeSections[i] = CodeSection[(Range)Header.CodeSections[i]]; + } + + if (eofHeader.ContainerSections.HasValue) + { + ContainerSections = new ReadOnlyMemory[eofHeader.ContainerSections.Value.Count]; + for (var i = 0; i < eofHeader.ContainerSections.Value.Count; i++) + { + ContainerSections[i] = ContainerSection[(Range)Header.ContainerSections.Value[i]]; + } + } + else + { + ContainerSections = Array.Empty>(); + } + + DataSection = container.Slice(eofHeader.DataSection.Start); + } + + public readonly EofHeader Header; + public readonly ReadOnlyMemory Prefix; + + public readonly ReadOnlyMemory TypeSection; + public readonly ReadOnlyMemory[] TypeSections; + + public readonly ReadOnlyMemory CodeSection; + public readonly ReadOnlyMemory[] CodeSections; + + + public readonly ReadOnlyMemory ContainerSection; + public readonly ReadOnlyMemory[] ContainerSections; + public readonly ReadOnlyMemory DataSection; +} +public struct EofHeader() +{ + public required byte Version; + public required int PrefixSize; + public required SectionHeader TypeSection; + public required CompoundSectionHeader CodeSections; + public required CompoundSectionHeader? ContainerSections; + public required SectionHeader DataSection; +} + +public readonly struct SectionHeader(int start, ushort size) +{ + public readonly int Start => start; + public readonly int Size => size; + public readonly int EndOffset => Start + Size; + + public static implicit operator Range(SectionHeader section) => new(section.Start, section.EndOffset); +} + +public readonly struct CompoundSectionHeader(int start, int[] subSectionsSizes) +{ + public readonly int Start => start; + + public readonly int[] SubSectionsSizes = subSectionsSizes; + + public readonly int EndOffset => Start + SubSectionsSizes.Sum(); + public readonly int Size => EndOffset - Start; + public readonly int Count => SubSectionsSizes.Length; + + private static int[] CreateSubSectionsSizes(int[] subSectionsSizes) + { + var subSectionsSizesAcc = new int[subSectionsSizes.Length]; + subSectionsSizesAcc[0] = 0; + for (var i = 1; i < subSectionsSizes.Length; i++) + { + subSectionsSizesAcc[i] = subSectionsSizesAcc[i - 1] + subSectionsSizes[i - 1]; + } + + return subSectionsSizesAcc; + } + + private int[] SubSectionsSizesAcc { get; } = CreateSubSectionsSizes(subSectionsSizes); + + public SectionHeader this[int i] => new SectionHeader(SubSectionsSizesAcc[i], (ushort)SubSectionsSizes[i]); + + public static implicit operator Range(CompoundSectionHeader section) => new(section.Start, section.EndOffset); +} + diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofValidationStrategy.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofValidationStrategy.cs new file mode 100644 index 00000000000..4531eea20d7 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/EofValidationStrategy.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Evm.EvmObjectFormat; + +public enum ValidationStrategy : byte +{ + None = 0, + Validate = 1, + ValidateFullBody = Validate | 2, + ValidateInitcodeMode = Validate | 4, + ValidateRuntimeMode = Validate | 8, + AllowTrailingBytes = Validate | 16, + ExractHeader = 32, + HasEofMagic = 64, + +} diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs new file mode 100644 index 00000000000..e2efd6b8422 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs @@ -0,0 +1,1152 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using DotNetty.Common.Utilities; +using FastEnumUtility; +using Nethermind.Core.Extensions; +using Nethermind.Evm; + +using static Nethermind.Evm.EvmObjectFormat.EofValidator; + +namespace Nethermind.Evm.EvmObjectFormat.Handlers; + +internal class Eof1 : IEofVersionHandler +{ + private readonly struct QueueManager + { + public readonly Queue<(int index, ValidationStrategy strategy)> ContainerQueue; + public readonly ValidationStrategy[] VisitedContainers; + + public QueueManager(int containerCount) + { + ContainerQueue = new(); + VisitedContainers = new ValidationStrategy[containerCount]; + } + + public void Enqueue(int index, ValidationStrategy strategy) + { + ContainerQueue.Enqueue((index, strategy)); + } + + public void MarkVisited(int index, ValidationStrategy strategy) + { + VisitedContainers[index] = strategy; + } + + public bool TryDequeue(out (int Index, ValidationStrategy Strategy) worklet) => ContainerQueue.TryDequeue(out worklet); + + public bool IsAllVisited() => VisitedContainers.All(x => x != 0); + } + + [StructLayout(LayoutKind.Sequential)] + private struct StackBounds() + { + public short Max = -1; + public short Min = 1023; + + public void Combine(StackBounds other) + { + this.Max = Math.Max(this.Max, other.Max); + this.Min = Math.Min(this.Min, other.Min); + } + + public bool BoundsEqual() => Max == Min; + + public static bool operator ==(StackBounds left, StackBounds right) => left.Max == right.Max && right.Min == left.Min; + public static bool operator !=(StackBounds left, StackBounds right) => !(left == right); + public override bool Equals(object obj) => obj is StackBounds && this == (StackBounds)obj; + public override int GetHashCode() => Max ^ Min; + } + + private ref struct Sizes + { + public ushort? TypeSectionSize; + public ushort? CodeSectionSize; + public ushort? DataSectionSize; + public ushort? ContainerSectionSize; + } + + public const byte VERSION = 0x01; + internal enum Separator : byte + { + KIND_TYPE = 0x01, + KIND_CODE = 0x02, + KIND_CONTAINER = 0x03, + KIND_DATA = 0x04, + TERMINATOR = 0x00 + } + + internal const byte MINIMUM_HEADER_SECTION_SIZE = 3; + internal const byte MINIMUM_TYPESECTION_SIZE = 4; + internal const byte MINIMUM_CODESECTION_SIZE = 1; + internal const byte MINIMUM_DATASECTION_SIZE = 0; + internal const byte MINIMUM_CONTAINERSECTION_SIZE = 0; + internal const byte MINIMUM_HEADER_SIZE = EofValidator.VERSION_OFFSET + + MINIMUM_HEADER_SECTION_SIZE + + MINIMUM_HEADER_SECTION_SIZE + EofValidator.TWO_BYTE_LENGTH + + MINIMUM_HEADER_SECTION_SIZE + + EofValidator.ONE_BYTE_LENGTH; + + internal const byte BYTE_BIT_COUNT = 8; // indicates the length of the count immediate of jumpv + internal const byte MINIMUMS_ACCEPTABLE_JUMPV_JUMPTABLE_LENGTH = 1; // indicates the length of the count immediate of jumpv + + internal const byte INPUTS_OFFSET = 0; + internal const byte INPUTS_MAX = 0x7F; + + internal const byte OUTPUTS_OFFSET = INPUTS_OFFSET + 1; + internal const byte OUTPUTS_MAX = 0x7F; + internal const byte NON_RETURNING = 0x80; + + internal const byte MAX_STACK_HEIGHT_OFFSET = OUTPUTS_OFFSET + 1; + internal const int MAX_STACK_HEIGHT_LENGTH = 2; + internal const ushort MAX_STACK_HEIGHT = 0x400; + + internal const ushort MINIMUM_NUM_CODE_SECTIONS = 1; + internal const ushort MAXIMUM_NUM_CODE_SECTIONS = 1024; + internal const ushort MAXIMUM_NUM_CONTAINER_SECTIONS = 0x00FF; + internal const ushort RETURN_STACK_MAX_HEIGHT = MAXIMUM_NUM_CODE_SECTIONS; // the size in the type sectionn allocated to each function section + + internal const ushort MINIMUM_SIZE = MINIMUM_HEADER_SIZE + + MINIMUM_TYPESECTION_SIZE // minimum type section body size + + MINIMUM_CODESECTION_SIZE // minimum code section body size + + MINIMUM_DATASECTION_SIZE; // minimum data section body size + + // EIP-3540 ties this to MAX_INIT_CODE_SIZE from EIP-3860, but we need a constant here + internal const ushort MAXIMUM_SIZE = 0xc000; + + public bool TryParseEofHeader(ReadOnlyMemory containerMemory, ValidationStrategy validationStrategy, out EofHeader? header) + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static ushort GetUInt16(ReadOnlySpan container, int offset) => + container.Slice(offset, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + ReadOnlySpan container = containerMemory.Span; + + header = null; + // we need to be able to parse header + minimum section lengths + if (container.Length < MINIMUM_SIZE) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + if (container.Length > MAXIMUM_SIZE) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is larger than allowed maximum size of {MAXIMUM_SIZE}"); + return false; + } + + if (!container.StartsWith(EofValidator.MAGIC)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code doesn't start with magic byte sequence expected {EofValidator.MAGIC.ToHexString(true)} "); + return false; + } + + if (container[EofValidator.VERSION_OFFSET] != VERSION) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is not Eof version {VERSION}"); + return false; + } + + Sizes sectionSizes = new(); + int[] codeSections = null; + int[] containerSections = null; + int pos = EofValidator.VERSION_OFFSET + 1; + + var continueParsing = true; + while (continueParsing && pos < container.Length) + { + var separator = (Separator)container[pos++]; + + switch (separator) + { + case Separator.KIND_TYPE: + if (sectionSizes.TypeSectionSize != null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Multiple type sections"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + sectionSizes.TypeSectionSize = GetUInt16(container, pos); + if (sectionSizes.TypeSectionSize < MINIMUM_TYPESECTION_SIZE) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, TypeSection Size must be at least 3, but found {sectionSizes.TypeSectionSize}"); + return false; + } + + pos += EofValidator.TWO_BYTE_LENGTH; + break; + case Separator.KIND_CODE: + if (sectionSizes.CodeSectionSize != null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Multiple code sections"); + return false; + } + + if (sectionSizes.TypeSectionSize is null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is not well formatted"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + var numberOfCodeSections = GetUInt16(container, pos); + sectionSizes.CodeSectionSize = (ushort)(numberOfCodeSections * EofValidator.TWO_BYTE_LENGTH); + if (numberOfCodeSections > MAXIMUM_NUM_CODE_SECTIONS) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, code sections count must not exceed {MAXIMUM_NUM_CODE_SECTIONS}"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH + EofValidator.TWO_BYTE_LENGTH * numberOfCodeSections) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + codeSections = new int[numberOfCodeSections]; + int CODESECTION_HEADER_PREFIX_SIZE = pos + EofValidator.TWO_BYTE_LENGTH; + for (ushort i = 0; i < codeSections.Length; i++) + { + int currentCodeSizeOffset = CODESECTION_HEADER_PREFIX_SIZE + i * EofValidator.TWO_BYTE_LENGTH; // offset of pos'th code size + int codeSectionSize = GetUInt16(container, currentCodeSizeOffset); + + if (codeSectionSize == 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Empty Code Section are not allowed, CodeSectionSize must be > 0 but found {codeSectionSize}"); + return false; + } + + codeSections[i] = codeSectionSize; + } + + pos += EofValidator.TWO_BYTE_LENGTH + EofValidator.TWO_BYTE_LENGTH * codeSections.Length; + break; + case Separator.KIND_CONTAINER: + if (sectionSizes.ContainerSectionSize != null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Multiple container sections"); + return false; + } + + if (sectionSizes.CodeSectionSize is null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is not well formatted"); + return false; + } + + if (sectionSizes.DataSectionSize is not null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Container section is out of order"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + var numberOfContainerSections = GetUInt16(container, pos); + sectionSizes.ContainerSectionSize = (ushort)(numberOfContainerSections * EofValidator.TWO_BYTE_LENGTH); + if (numberOfContainerSections is > (MAXIMUM_NUM_CONTAINER_SECTIONS + 1) or 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, container sections count must not exceed {MAXIMUM_NUM_CONTAINER_SECTIONS}"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH + EofValidator.TWO_BYTE_LENGTH * numberOfContainerSections) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + containerSections = new int[numberOfContainerSections]; + int CONTAINER_SECTION_HEADER_PREFIX_SIZE = pos + EofValidator.TWO_BYTE_LENGTH; + for (ushort i = 0; i < containerSections.Length; i++) + { + int currentContainerSizeOffset = CONTAINER_SECTION_HEADER_PREFIX_SIZE + i * EofValidator.TWO_BYTE_LENGTH; // offset of pos'th code size + int containerSectionSize = GetUInt16(container, currentContainerSizeOffset); + + if (containerSectionSize == 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Empty Container Section are not allowed, containerSectionSize must be > 0 but found {containerSectionSize}"); + return false; + } + + containerSections[i] = containerSectionSize; + } + + pos += EofValidator.TWO_BYTE_LENGTH + EofValidator.TWO_BYTE_LENGTH * containerSections.Length; + break; + case Separator.KIND_DATA: + if (sectionSizes.DataSectionSize != null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Multiple data sections"); + return false; + } + + if (sectionSizes.CodeSectionSize is null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is not well fromated"); + return false; + } + + if (container.Length < pos + EofValidator.TWO_BYTE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + sectionSizes.DataSectionSize = GetUInt16(container, pos); + + pos += EofValidator.TWO_BYTE_LENGTH; + break; + case Separator.TERMINATOR: + if (container.Length < pos + EofValidator.ONE_BYTE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is too small to be valid code"); + return false; + } + + continueParsing = false; + break; + default: + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code header is not well formatted"); + return false; + } + } + + if (sectionSizes.TypeSectionSize is null || sectionSizes.CodeSectionSize is null || sectionSizes.DataSectionSize is null) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code is not well formatted"); + return false; + } + + var typeSectionSubHeader = new SectionHeader(pos, sectionSizes.TypeSectionSize.Value); + var codeSectionSubHeader = new CompoundSectionHeader(typeSectionSubHeader.EndOffset, codeSections); + CompoundSectionHeader? containerSectionSubHeader = containerSections is null ? null + : new CompoundSectionHeader(codeSectionSubHeader.EndOffset, containerSections); + var dataSectionSubHeader = new SectionHeader(containerSectionSubHeader?.EndOffset ?? codeSectionSubHeader.EndOffset, sectionSizes.DataSectionSize.Value); + + if (dataSectionSubHeader.EndOffset < containerMemory.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Extra data after end of container, starting at {dataSectionSubHeader.EndOffset}"); + return false; + } + if ((validationStrategy.HasFlag(ValidationStrategy.Validate) && !validationStrategy.HasFlag(ValidationStrategy.ValidateRuntimeMode)) + && dataSectionSubHeader.EndOffset > container.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Container has truncated data where full data is required"); + return false; + } + + header = new EofHeader + { + Version = VERSION, + PrefixSize = pos, + TypeSection = typeSectionSubHeader, + CodeSections = codeSectionSubHeader, + ContainerSections = containerSectionSubHeader, + DataSection = dataSectionSubHeader, + }; + return true; + } + + public bool TryGetEofContainer(ReadOnlyMemory code, ValidationStrategy validationStrategy, [NotNullWhen(true)] out EofContainer? eofContainer) + { + if (!TryParseEofHeader(code, validationStrategy, out EofHeader? header)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Header not parsed"); + eofContainer = null; + return false; + } + + if (!ValidateBody(code.Span, header.Value, validationStrategy)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Body not valid"); + eofContainer = null; + return false; + } + + eofContainer = new EofContainer(code, header.Value); + + if (validationStrategy.HasFlag(ValidationStrategy.Validate)) + { + if (!ValidateContainer(eofContainer.Value, validationStrategy)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Container not valid"); + eofContainer = null; + return false; + } + } + + return true; + } + + private bool ValidateContainer(EofContainer eofContainer, ValidationStrategy validationStrategy) + { + Queue<(EofContainer container, ValidationStrategy strategy)> containers = new(); + containers.Enqueue((eofContainer, validationStrategy)); + while (containers.TryDequeue(out var target)) + { + EofContainer targetContainer = target.container; + validationStrategy = target.strategy; + + QueueManager containerQueue = new(1 + (targetContainer.Header.ContainerSections?.Count ?? 0)); + containerQueue.Enqueue(0, validationStrategy); + + containerQueue.VisitedContainers[0] = GetValidation(validationStrategy); + + while (containerQueue.TryDequeue(out var worklet)) + { + if (worklet.Index != 0) + { + if (containerQueue.VisitedContainers[worklet.Index] != 0) + continue; + + if (targetContainer.ContainerSections.Length < worklet.Index) + continue; + + var section = worklet.Index - 1; + ReadOnlyMemory subsection = targetContainer.ContainerSections[section]; + if (!TryParseEofHeader(subsection, validationStrategy, out EofHeader? header)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Header invalid: section {section}"); + return false; + } + if (!ValidateBody(subsection.Span, header.Value, validationStrategy)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Body invalid: section {section}"); + return false; + } + + if (validationStrategy.HasFlag(ValidationStrategy.Validate)) + { + containers.Enqueue((new EofContainer(subsection, header.Value), worklet.Strategy)); + } + } + else + { + if (!ValidateCodeSections(targetContainer, worklet.Strategy, in containerQueue)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code sections invalid"); + return false; + } + } + containerQueue.MarkVisited(worklet.Index, GetVisited(worklet.Strategy)); + } + if (!containerQueue.IsAllVisited()) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Not all containers visited"); + return false; + } + } + + return true; + } + + private static ValidationStrategy GetVisited(ValidationStrategy validationStrategy) + { + return validationStrategy.HasFlag(ValidationStrategy.ValidateInitcodeMode) + ? ValidationStrategy.ValidateInitcodeMode + : ValidationStrategy.ValidateRuntimeMode; + } + + private static ValidationStrategy GetValidation(ValidationStrategy validationStrategy) + { + return validationStrategy.HasFlag(ValidationStrategy.ValidateInitcodeMode) + ? ValidationStrategy.ValidateInitcodeMode + : validationStrategy.HasFlag(ValidationStrategy.ValidateRuntimeMode) + ? ValidationStrategy.ValidateRuntimeMode + : ValidationStrategy.None; + } + + private bool ValidateBody(ReadOnlySpan container, EofHeader header, ValidationStrategy strategy) + { + int startOffset = header.TypeSection.Start; + int endOffset = header.DataSection.Start; + int calculatedCodeLength = + header.TypeSection.Size + + header.CodeSections.Size + + (header.ContainerSections?.Size ?? 0); + CompoundSectionHeader codeSections = header.CodeSections; + + if (endOffset > container.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, DataSectionSize indicated in bundled header are incorrect, or DataSection is wrong"); + return false; + } + + ReadOnlySpan contractBody = container[startOffset..endOffset]; + ReadOnlySpan dataBody = container[endOffset..]; + var typeSection = header.TypeSection; + (int typeSectionStart, int typeSectionSize) = (typeSection.Start, typeSection.Size); + + if (header.ContainerSections?.Count > MAXIMUM_NUM_CONTAINER_SECTIONS + 1) + { + // move this check where `header.ExtraContainers.Count` is parsed + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, initcode Containers count must be less than {MAXIMUM_NUM_CONTAINER_SECTIONS} but found {header.ContainerSections?.Count}"); + return false; + } + + if (contractBody.Length != calculatedCodeLength) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, SectionSizes indicated in bundled header are incorrect, or ContainerCode is incomplete"); + return false; + } + + if (strategy.HasFlag(ValidationStrategy.ValidateFullBody) && header.DataSection.Size > dataBody.Length) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, DataSectionSize indicated in bundled header are incorrect, or DataSection is wrong"); + return false; + } + + if (!strategy.HasFlag(ValidationStrategy.AllowTrailingBytes) && strategy.HasFlag(ValidationStrategy.ValidateFullBody) && header.DataSection.Size != dataBody.Length) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, DataSectionSize indicated in bundled header are incorrect, or DataSection is wrong"); + return false; + } + + if (codeSections.Count == 0 || codeSections.SubSectionsSizes.Any(size => size == 0)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection size must follow a CodeSection, CodeSection length was {codeSections.Count}"); + return false; + } + + if (codeSections.Count != typeSectionSize / MINIMUM_TYPESECTION_SIZE) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code Sections count must match TypeSection count, CodeSection count was {codeSections.Count}, expected {typeSectionSize / MINIMUM_TYPESECTION_SIZE}"); + return false; + } + + ReadOnlySpan typesection = container.Slice(typeSectionStart, typeSectionSize); + if (!ValidateTypeSection(typesection)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, invalid typesection found"); + return false; + } + + return true; + } + + private bool ValidateCodeSections(EofContainer eofContainer, ValidationStrategy strategy, in QueueManager containerQueue) + { + QueueManager sectionQueue = new(eofContainer.Header.CodeSections.Count); + + sectionQueue.Enqueue(0, strategy); + + while (sectionQueue.TryDequeue(out var sectionIdx)) + { + if (sectionQueue.VisitedContainers[sectionIdx.Index] != 0) + continue; + + if (!ValidateInstructions(eofContainer, sectionIdx.Index, strategy, in sectionQueue, in containerQueue)) + return false; + + sectionQueue.MarkVisited(sectionIdx.Index, ValidationStrategy.Validate); + } + + return sectionQueue.IsAllVisited(); + } + + private bool ValidateTypeSection(ReadOnlySpan types) + { + if (types[INPUTS_OFFSET] != 0 || types[OUTPUTS_OFFSET] != NON_RETURNING) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, first 2 bytes of type section must be 0s"); + return false; + } + + if (types.Length % MINIMUM_TYPESECTION_SIZE != 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, type section length must be a product of {MINIMUM_TYPESECTION_SIZE}"); + return false; + } + + for (var offset = 0; offset < types.Length; offset += MINIMUM_TYPESECTION_SIZE) + { + var inputCount = types[offset + INPUTS_OFFSET]; + var outputCount = types[offset + OUTPUTS_OFFSET]; + ushort maxStackHeight = types.Slice(offset + MAX_STACK_HEIGHT_OFFSET, MAX_STACK_HEIGHT_LENGTH).ReadEthUInt16(); + + if (inputCount > INPUTS_MAX) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, Too many inputs"); + return false; + } + + if (outputCount > OUTPUTS_MAX && outputCount != NON_RETURNING) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, Too many outputs"); + return false; + } + + if (maxStackHeight > MAX_STACK_HEIGHT) + { + if (Logger.IsTrace) Logger.Trace("EOF: Eof{VERSION}, Stack depth too high"); + return false; + } + } + return true; + } + + private bool ValidateInstructions(EofContainer eofContainer, int sectionId, ValidationStrategy strategy, in QueueManager sectionsWorklist, in QueueManager containersWorklist) + { + ReadOnlySpan code = eofContainer.CodeSections[sectionId].Span; + + if (code.Length < 1) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection {sectionId} is too short to be valid"); + return false; + } + + var length = code.Length / BYTE_BIT_COUNT + 1; + byte[] invalidJmpLocationArray = ArrayPool.Shared.Rent(length); + byte[] jumpDestinationsArray = ArrayPool.Shared.Rent(length); + + try + { + // ArrayPool may return a larger array than requested, so we need to slice it to the actual length + Span invalidJumpDestinations = invalidJmpLocationArray.AsSpan(0, length); + Span jumpDestinations = jumpDestinationsArray.AsSpan(0, length); + // ArrayPool may return a larger array than requested, so we need to slice it to the actual length + invalidJumpDestinations.Clear(); + jumpDestinations.Clear(); + + ReadOnlySpan currentTypeSection = eofContainer.TypeSections[sectionId].Span; + var isCurrentSectionNonReturning = currentTypeSection[OUTPUTS_OFFSET] == 0x80; + bool hasRequiredSectionExit = isCurrentSectionNonReturning; + + int position; + Instruction opcode = Instruction.STOP; + for (position = 0; position < code.Length;) + { + opcode = (Instruction)code[position]; + int nextPosition = position + 1; + + if (!opcode.IsValid(IsEofContext: true)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection contains undefined opcode {opcode}"); + return false; + } + else if (opcode is Instruction.RETURN or Instruction.STOP) + { + if (strategy.HasFlag(ValidationStrategy.ValidateInitcodeMode)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection contains {opcode} opcode"); + return false; + } + else + { + if (containersWorklist.VisitedContainers[0] == ValidationStrategy.ValidateInitcodeMode) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection cannot contain {opcode} opcode"); + return false; + } + else + { + containersWorklist.VisitedContainers[0] = ValidationStrategy.ValidateRuntimeMode; + } + } + } + else if (opcode is Instruction.RETURNCONTRACT) + { + if (strategy.HasFlag(ValidationStrategy.ValidateRuntimeMode)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection contains {opcode} opcode"); + return false; + } + else + { + if (containersWorklist.VisitedContainers[0] == ValidationStrategy.ValidateRuntimeMode) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, CodeSection cannot contain {opcode} opcode"); + return false; + } + else + { + containersWorklist.VisitedContainers[0] = ValidationStrategy.ValidateInitcodeMode; + } + } + + if (nextPosition + EofValidator.ONE_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RETURNCONTRACT} Argument underflow"); + return false; + } + + ushort runtimeContainerId = code[nextPosition]; + if (eofContainer.Header.ContainerSections is null || runtimeContainerId >= eofContainer.Header.ContainerSections?.Count) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RETURNCONTRACT}'s immediate argument must be less than containerSection.Count i.e: {eofContainer.Header.ContainerSections?.Count}"); + return false; + } + + if (containersWorklist.VisitedContainers[runtimeContainerId + 1] != 0 + && containersWorklist.VisitedContainers[runtimeContainerId + 1] != ValidationStrategy.ValidateRuntimeMode) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RETURNCONTRACT}'s target container can only be a runtime mode bytecode"); + return false; + } + + containersWorklist.Enqueue(runtimeContainerId + 1, ValidationStrategy.ValidateRuntimeMode | ValidationStrategy.ValidateFullBody); + + BitmapHelper.HandleNumbits(EofValidator.ONE_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.RJUMP or Instruction.RJUMPI) + { + if (nextPosition + EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {opcode.FastToString()} Argument underflow"); + return false; + } + + short offset = code.Slice(nextPosition, EofValidator.TWO_BYTE_LENGTH).ReadEthInt16(); + int rjumpDest = offset + EofValidator.TWO_BYTE_LENGTH + nextPosition; + + if (rjumpDest < 0 || rjumpDest >= code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {opcode.FastToString()} Destination outside of Code bounds"); + return false; + } + + BitmapHelper.HandleNumbits(EofValidator.ONE_BYTE_LENGTH, jumpDestinations, ref rjumpDest); + BitmapHelper.HandleNumbits(EofValidator.TWO_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.JUMPF) + { + hasRequiredSectionExit = true; + if (nextPosition + EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.JUMPF} Argument underflow"); + return false; + } + + var targetSectionId = code.Slice(nextPosition, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + if (targetSectionId >= eofContainer.Header.CodeSections.Count) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.JUMPF} to unknown code section"); + return false; + } + + ReadOnlySpan targetTypeSection = eofContainer.TypeSections[targetSectionId].Span; + + var targetSectionOutputCount = targetTypeSection[OUTPUTS_OFFSET]; + var isTargetSectionNonReturning = targetTypeSection[OUTPUTS_OFFSET] == 0x80; + var currentSectionOutputCount = currentTypeSection[OUTPUTS_OFFSET]; + + if (!isTargetSectionNonReturning && currentSectionOutputCount < targetSectionOutputCount) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.JUMPF} to code section with more outputs"); + return false; + } + + if (isCurrentSectionNonReturning && !isTargetSectionNonReturning) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.JUMPF} from non-returning must target non-returning"); + return false; + } + + sectionsWorklist.Enqueue(targetSectionId, strategy); + BitmapHelper.HandleNumbits(EofValidator.TWO_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.DUPN or Instruction.SWAPN or Instruction.EXCHANGE) + { + if (nextPosition + EofValidator.ONE_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {opcode.FastToString()} Argument underflow"); + return false; + } + BitmapHelper.HandleNumbits(EofValidator.ONE_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.RJUMPV) + { + if (nextPosition + EofValidator.ONE_BYTE_LENGTH + EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RJUMPV} Argument underflow"); + return false; + } + + var count = (ushort)(code[nextPosition] + 1); + if (count < MINIMUMS_ACCEPTABLE_JUMPV_JUMPTABLE_LENGTH) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RJUMPV} jumpTable must have at least 1 entry"); + return false; + } + + if (nextPosition + EofValidator.ONE_BYTE_LENGTH + count * EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RJUMPV} jumpTable underflow"); + return false; + } + + int immediateValueSize = EofValidator.ONE_BYTE_LENGTH + count * EofValidator.TWO_BYTE_LENGTH; + for (var j = 0; j < count; j++) + { + var offset = code.Slice(nextPosition + EofValidator.ONE_BYTE_LENGTH + j * EofValidator.TWO_BYTE_LENGTH, EofValidator.TWO_BYTE_LENGTH).ReadEthInt16(); + var rjumpDest = offset + immediateValueSize + nextPosition; + if (rjumpDest < 0 || rjumpDest >= code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.RJUMPV} Destination outside of Code bounds"); + return false; + } + BitmapHelper.HandleNumbits(EofValidator.ONE_BYTE_LENGTH, jumpDestinations, ref rjumpDest); + } + + BitmapHelper.HandleNumbits(immediateValueSize, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.CALLF) + { + if (nextPosition + EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.CALLF} Argument underflow"); + return false; + } + + ushort targetSectionId = code.Slice(nextPosition, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + if (targetSectionId >= eofContainer.Header.CodeSections.Count) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.CALLF} Invalid Section Id"); + return false; + } + + ReadOnlySpan targetTypeSection = eofContainer.TypeSections[targetSectionId].Span; + + var targetSectionOutputCount = targetTypeSection[OUTPUTS_OFFSET]; + + if (targetSectionOutputCount == 0x80) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.CALLF} into non-returning function"); + return false; + } + + sectionsWorklist.Enqueue(targetSectionId, strategy); + BitmapHelper.HandleNumbits(EofValidator.TWO_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.RETF) + { + hasRequiredSectionExit = true; + if (isCurrentSectionNonReturning) + { + if (Logger.IsTrace) + Logger.Trace($"EOF: Eof{VERSION}, non returning sections are not allowed to use opcode {Instruction.RETF}"); + return false; + } + } + else if (opcode is Instruction.DATALOADN) + { + if (nextPosition + EofValidator.TWO_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.DATALOADN} Argument underflow"); + return false; + } + + ushort dataSectionOffset = code.Slice(nextPosition, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + if (dataSectionOffset + 32 > eofContainer.Header.DataSection.Size) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.DATALOADN}'s immediate argument must be less than dataSection.Length / 32 i.e: {eofContainer.Header.DataSection.Size / 32}"); + return false; + } + BitmapHelper.HandleNumbits(EofValidator.TWO_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is Instruction.EOFCREATE) + { + if (nextPosition + EofValidator.ONE_BYTE_LENGTH > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.EOFCREATE} Argument underflow"); + return false; + } + + int initCodeSectionId = code[nextPosition]; + + if (eofContainer.Header.ContainerSections is null || initCodeSectionId >= eofContainer.Header.ContainerSections?.Count) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.EOFCREATE}'s immediate must falls within the Containers' range available, i.e: {eofContainer.Header.CodeSections.Count}"); + return false; + } + + if (containersWorklist.VisitedContainers[initCodeSectionId + 1] != 0 + && containersWorklist.VisitedContainers[initCodeSectionId + 1] != ValidationStrategy.ValidateInitcodeMode) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {Instruction.EOFCREATE}'s target container can only be a initCode mode bytecode"); + return false; + } + + containersWorklist.Enqueue(initCodeSectionId + 1, ValidationStrategy.ValidateInitcodeMode | ValidationStrategy.ValidateFullBody); + + BitmapHelper.HandleNumbits(EofValidator.ONE_BYTE_LENGTH, invalidJumpDestinations, ref nextPosition); + } + else if (opcode is >= Instruction.PUSH0 and <= Instruction.PUSH32) + { + int pushDataLength = opcode - Instruction.PUSH0; + if (nextPosition + pushDataLength > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, {opcode.FastToString()} PC Reached out of bounds"); + return false; + } + BitmapHelper.HandleNumbits(pushDataLength, invalidJumpDestinations, ref nextPosition); + } + position = nextPosition; + } + + if (position > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, PC Reached out of bounds"); + return false; + } + + if (!opcode.IsTerminating()) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code section {sectionId} ends with a non-terminating opcode"); + return false; + } + + if (!hasRequiredSectionExit) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Code section {sectionId} is returning and does not have a RETF or JUMPF"); + return false; + } + + var result = BitmapHelper.CheckCollision(invalidJumpDestinations, jumpDestinations); + if (result) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Invalid Jump destination {result}"); + return false; + } + + if (!ValidateStackState(sectionId, code, eofContainer.TypeSection.Span)) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Invalid Stack state"); + return false; + } + return true; + } + finally + { + ArrayPool.Shared.Return(invalidJmpLocationArray); + ArrayPool.Shared.Return(jumpDestinationsArray); + } + } + public bool ValidateStackState(int sectionId, in ReadOnlySpan code, in ReadOnlySpan typesection) + { + StackBounds[] recordedStackHeight = ArrayPool.Shared.Rent(code.Length); + recordedStackHeight.Fill(new StackBounds()); + + try + { + ushort suggestedMaxHeight = typesection.Slice(sectionId * MINIMUM_TYPESECTION_SIZE + EofValidator.TWO_BYTE_LENGTH, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + var currrentSectionOutputs = typesection[sectionId * MINIMUM_TYPESECTION_SIZE + OUTPUTS_OFFSET] == 0x80 ? (ushort)0 : typesection[sectionId * MINIMUM_TYPESECTION_SIZE + OUTPUTS_OFFSET]; + short peakStackHeight = typesection[sectionId * MINIMUM_TYPESECTION_SIZE + INPUTS_OFFSET]; + + var unreachedBytes = code.Length; + var isTargetSectionNonReturning = false; + + var targetMaxStackHeight = 0; + var programCounter = 0; + recordedStackHeight[0].Max = peakStackHeight; + recordedStackHeight[0].Min = peakStackHeight; + StackBounds currentStackBounds = recordedStackHeight[0]; + + while (programCounter < code.Length) + { + var opcode = (Instruction)code[programCounter]; + (var inputs, var outputs, var immediates) = opcode.StackRequirements(); + + var posPostInstruction = (ushort)(programCounter + 1); + if (posPostInstruction > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, PC Reached out of bounds"); + return false; + } + + switch (opcode) + { + case Instruction.CALLF or Instruction.JUMPF: + ushort targetSectionId = code.Slice(posPostInstruction, immediates.Value).ReadEthUInt16(); + inputs = typesection[targetSectionId * MINIMUM_TYPESECTION_SIZE + INPUTS_OFFSET]; + + outputs = typesection[targetSectionId * MINIMUM_TYPESECTION_SIZE + OUTPUTS_OFFSET]; + isTargetSectionNonReturning = typesection[targetSectionId * MINIMUM_TYPESECTION_SIZE + OUTPUTS_OFFSET] == 0x80; + outputs = (ushort)(isTargetSectionNonReturning ? 0 : outputs); + targetMaxStackHeight = typesection.Slice(targetSectionId * MINIMUM_TYPESECTION_SIZE + MAX_STACK_HEIGHT_OFFSET, EofValidator.TWO_BYTE_LENGTH).ReadEthUInt16(); + + if (MAX_STACK_HEIGHT - targetMaxStackHeight + inputs < currentStackBounds.Max) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, stack head during callf must not exceed {MAX_STACK_HEIGHT}"); + return false; + } + + if (opcode is Instruction.JUMPF && !isTargetSectionNonReturning && !(currrentSectionOutputs + inputs - outputs == currentStackBounds.Min && currentStackBounds.BoundsEqual())) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Stack State invalid, required height {currrentSectionOutputs + inputs - outputs} but found {currentStackBounds.Max}"); + return false; + } + break; + case Instruction.DUPN: + var imm_n = 1 + code[posPostInstruction]; + inputs = (ushort)imm_n; + outputs = (ushort)(inputs + 1); + break; + case Instruction.SWAPN: + imm_n = 1 + code[posPostInstruction]; + outputs = inputs = (ushort)(1 + imm_n); + break; + case Instruction.EXCHANGE: + imm_n = 1 + (byte)(code[posPostInstruction] >> 4); + var imm_m = 1 + (byte)(code[posPostInstruction] & 0x0F); + outputs = inputs = (ushort)(imm_n + imm_m + 1); + break; + } + + if ((isTargetSectionNonReturning || opcode is not Instruction.JUMPF) && currentStackBounds.Min < inputs) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Stack Underflow required {inputs} but found {currentStackBounds.Min}"); + return false; + } + + if (!opcode.IsTerminating()) + { + var delta = (short)(outputs - inputs); + currentStackBounds.Max += delta; + currentStackBounds.Min += delta; + } + peakStackHeight = Math.Max(peakStackHeight, currentStackBounds.Max); + + switch (opcode) + { + case Instruction.RETF: + { + var expectedHeight = typesection[sectionId * MINIMUM_TYPESECTION_SIZE + OUTPUTS_OFFSET]; + if (expectedHeight != currentStackBounds.Min || !currentStackBounds.BoundsEqual()) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Stack state invalid required height {expectedHeight} but found {currentStackBounds.Min}"); + return false; + } + break; + } + case Instruction.RJUMP or Instruction.RJUMPI: + { + short offset = code.Slice(programCounter + 1, immediates.Value).ReadEthInt16(); + var jumpDestination = posPostInstruction + immediates.Value + offset; + + if (opcode is Instruction.RJUMPI && (posPostInstruction + immediates.Value < recordedStackHeight.Length)) + recordedStackHeight[posPostInstruction + immediates.Value].Combine(currentStackBounds); + + if (jumpDestination > programCounter) + recordedStackHeight[jumpDestination].Combine(currentStackBounds); + else + { + if (recordedStackHeight[jumpDestination] != currentStackBounds) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Stack state invalid at {jumpDestination}"); + return false; + } + } + + break; + } + case Instruction.RJUMPV: + { + var count = code[posPostInstruction] + 1; + immediates = (ushort)(count * EofValidator.TWO_BYTE_LENGTH + EofValidator.ONE_BYTE_LENGTH); + for (short j = 0; j < count; j++) + { + int case_v = posPostInstruction + EofValidator.ONE_BYTE_LENGTH + j * EofValidator.TWO_BYTE_LENGTH; + int offset = code.Slice(case_v, EofValidator.TWO_BYTE_LENGTH).ReadEthInt16(); + var jumpDestination = posPostInstruction + immediates.Value + offset; + if (jumpDestination > programCounter) + recordedStackHeight[jumpDestination].Combine(currentStackBounds); + else + { + if (recordedStackHeight[jumpDestination] != currentStackBounds) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Stack state invalid at {jumpDestination}"); + return false; + } + } + } + + posPostInstruction += immediates.Value; + if (posPostInstruction > code.Length) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, PC Reached out of bounds"); + return false; + } + break; + } + } + + unreachedBytes -= 1 + immediates.Value; + programCounter += 1 + immediates.Value; + + if (opcode.IsTerminating()) + { + if (programCounter < code.Length) + { + if (recordedStackHeight[programCounter].Max < 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, opcode not forward referenced, section {sectionId} pc {programCounter}"); + return false; + } + currentStackBounds = recordedStackHeight[programCounter]; + } + } + else + { + if (programCounter < code.Length) + { + recordedStackHeight[programCounter].Combine(currentStackBounds); + currentStackBounds = recordedStackHeight[programCounter]; + } + } + } + + if (unreachedBytes != 0) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, bytecode has unreachable segments"); + return false; + } + + if (peakStackHeight != suggestedMaxHeight) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, Suggested Max Stack height mismatches with actual Max, expected {suggestedMaxHeight} but found {peakStackHeight}"); + return false; + } + + var result = peakStackHeight < MAX_STACK_HEIGHT; + if (!result) + { + if (Logger.IsTrace) Logger.Trace($"EOF: Eof{VERSION}, stack overflow exceeded max stack height of {MAX_STACK_HEIGHT} but found {peakStackHeight}"); + return false; + } + return result; + } + finally + { + ArrayPool.Shared.Return(recordedStackHeight); + } + } +} + diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs new file mode 100644 index 00000000000..7254ced54ea --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/IEofVersionHandler.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Nethermind.Evm.EvmObjectFormat; +interface IEofVersionHandler +{ + bool TryParseEofHeader(ReadOnlyMemory code, ValidationStrategy strategy, [NotNullWhen(true)] out EofHeader? header); + bool TryGetEofContainer(ReadOnlyMemory code, ValidationStrategy strategy, [NotNullWhen(true)] out EofContainer? header); +} diff --git a/src/Nethermind/Nethermind.Evm/EvmStack.cs b/src/Nethermind/Nethermind.Evm/EvmStack.cs index ef6dc068bde..04cb8ed8cbc 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -219,12 +219,14 @@ public void PushSignedInt256(in Int256.Int256 value) PushUInt256(Unsafe.As(ref Unsafe.AsRef(in value))); } - public void PopLimbo() + public bool PopLimbo() { if (Head-- == 0) { - EvmStack.ThrowEvmStackUnderflowException(); + return false; } + + return true; } /// @@ -312,6 +314,18 @@ public readonly Span PeekWord256() public Address? PopAddress() => Head-- == 0 ? null : new Address(_bytes.Slice(Head * WordSize + WordSize - AddressSize, AddressSize).ToArray()); + public bool PopAddress(out Address address) + { + if (Head-- == 0) + { + address = null; + return false; + } + + address = new Address(_bytes.Slice(Head * WordSize + WordSize - AddressSize, AddressSize).ToArray()); + return true; + } + public ref byte PopBytesByRef() { if (Head-- == 0) @@ -332,6 +346,18 @@ public Span PopWord256() return _bytes.Slice(Head * WordSize, WordSize); } + public bool PopWord256(out Span word) + { + if (Head-- == 0) + { + word = default; + return false; + } + + word = _bytes.Slice(Head * WordSize, WordSize); + return true; + } + public byte PopByte() { if (Head-- == 0) @@ -417,6 +443,28 @@ public readonly bool Swap(int depth) return true; } + public readonly bool Exchange(int n, int m) + { + int maxDepth = Math.Max(n, m); + if (!EnsureDepth(maxDepth)) return false; + + ref byte bytes = ref MemoryMarshal.GetReference(_bytes); + + ref byte first = ref Unsafe.Add(ref bytes, (Head - n) * WordSize); + ref byte second = ref Unsafe.Add(ref bytes, (Head - m) * WordSize); + + Word buffer = Unsafe.ReadUnaligned(ref first); + Unsafe.WriteUnaligned(ref first, Unsafe.ReadUnaligned(ref second)); + Unsafe.WriteUnaligned(ref second, buffer); + + if (typeof(TTracing) == typeof(IsTracing)) + { + Trace(maxDepth); + } + + return true; + } + private readonly void Trace(int depth) { for (int i = depth; i > 0; i--) @@ -436,7 +484,7 @@ public static class EvmStack { public const int RegisterLength = 1; public const int MaxStackSize = 1025; - public const int ReturnStackSize = 1023; + public const int ReturnStackSize = 1025; public const int WordSize = 32; public const int AddressSize = 20; diff --git a/src/Nethermind/Nethermind.Evm/EvmState.cs b/src/Nethermind/Nethermind.Evm/EvmState.cs index b2c7cb93022..de20045dddc 100644 --- a/src/Nethermind/Nethermind.Evm/EvmState.cs +++ b/src/Nethermind/Nethermind.Evm/EvmState.cs @@ -19,13 +19,20 @@ namespace Nethermind.Evm [DebuggerDisplay("{ExecutionType} to {Env.ExecutingAccount}, G {GasAvailable} R {Refund} PC {ProgramCounter} OUT {OutputDestination}:{OutputLength}")] public class EvmState : IDisposable // TODO: rename to CallState { + public struct ReturnState + { + public int Index; + public int Offset; + public int Height; + } + private class StackPool { private readonly int _maxCallStackDepth; - private readonly struct StackItem(byte[] dataStack, int[] returnStack) + private readonly struct StackItem(byte[] dataStack, ReturnState[] returnStack) { public readonly byte[] DataStack = dataStack; - public readonly int[] ReturnStack = returnStack; + public readonly ReturnState[] ReturnStack = returnStack; } // TODO: we have wrong call depth calculation somewhere @@ -44,12 +51,12 @@ public StackPool(int maxCallStackDepth = VirtualMachine.MaxCallDepth * 2) /// /// /// - public void ReturnStacks(byte[] dataStack, int[] returnStack) + public void ReturnStacks(byte[] dataStack, ReturnState[] returnStack) { _stackPool.Push(new(dataStack, returnStack)); } - public (byte[], int[]) RentStacks() + public (byte[], ReturnState[]) RentStacks() { if (_stackPool.TryPop(out StackItem result)) { @@ -65,7 +72,7 @@ public void ReturnStacks(byte[] dataStack, int[] returnStack) return ( new byte[(EvmStack.MaxStackSize + EvmStack.RegisterLength) * 32], - new int[EvmStack.ReturnStackSize] + new ReturnState[EvmStack.ReturnStackSize] ); } } @@ -73,7 +80,7 @@ public void ReturnStacks(byte[] dataStack, int[] returnStack) public byte[]? DataStack; - public int[]? ReturnStack; + public ReturnState[]? ReturnStack; /// /// EIP-2929 accessed addresses @@ -200,6 +207,8 @@ public Address From case ExecutionType.CALLCODE: case ExecutionType.CREATE: case ExecutionType.CREATE2: + case ExecutionType.EOFCREATE: + case ExecutionType.TXCREATE: case ExecutionType.TRANSACTION: return Env.Caller; case ExecutionType.DELEGATECALL: @@ -212,6 +221,7 @@ public Address From public long GasAvailable { get; set; } public int ProgramCounter { get; set; } + public int FunctionIndex { get; set; } public long Refund { get; set; } public Address To => Env.CodeSource ?? Env.ExecutingAccount; diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index ec6600fef8e..613e0ef25fc 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -12,7 +12,7 @@ public readonly struct ExecutionEnvironment { public ExecutionEnvironment ( - CodeInfo codeInfo, + ICodeInfo codeInfo, Address executingAccount, Address caller, Address? codeSource, @@ -36,7 +36,7 @@ public ExecutionEnvironment /// /// Parsed bytecode for the current call. /// - public readonly CodeInfo CodeInfo; + public readonly ICodeInfo CodeInfo; /// /// Currently executing account (in DELEGATECALL this will be equal to caller). diff --git a/src/Nethermind/Nethermind.Evm/ExecutionType.cs b/src/Nethermind/Nethermind.Evm/ExecutionType.cs index f0ce0c67737..2f6498b06a9 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionType.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionType.cs @@ -8,10 +8,28 @@ namespace Nethermind.Evm { public static class ExecutionTypeExtensions { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnyCreateLegacy(this ExecutionType executionType) => + executionType is ExecutionType.CREATE or ExecutionType.CREATE2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnyCreateEof(this ExecutionType executionType) => + executionType is ExecutionType.EOFCREATE or ExecutionType.TXCREATE; // did not want to use flags here specifically [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsAnyCreate(this ExecutionType executionType) => - executionType is ExecutionType.CREATE or ExecutionType.CREATE2; + IsAnyCreateLegacy(executionType) || IsAnyCreateEof(executionType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnyCall(this ExecutionType executionType) => + IsAnyCallLegacy(executionType) || IsAnyCallEof(executionType); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnyCallLegacy(this ExecutionType executionType) => + executionType is ExecutionType.CALL or ExecutionType.STATICCALL or ExecutionType.DELEGATECALL or ExecutionType.CALLCODE; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsAnyCallEof(this ExecutionType executionType) => + executionType is ExecutionType.EOFCALL or ExecutionType.EOFSTATICCALL or ExecutionType.EOFDELEGATECALL; public static Instruction ToInstruction(this ExecutionType executionType) => executionType switch @@ -23,6 +41,10 @@ public static Instruction ToInstruction(this ExecutionType executionType) => ExecutionType.DELEGATECALL => Instruction.DELEGATECALL, ExecutionType.CREATE => Instruction.CREATE, ExecutionType.CREATE2 => Instruction.CREATE2, + ExecutionType.EOFCREATE => Instruction.EOFCREATE, + ExecutionType.EOFCALL => Instruction.EXTCALL, + ExecutionType.EOFSTATICCALL => Instruction.EXTSTATICCALL, + ExecutionType.EOFDELEGATECALL => Instruction.EXTDELEGATECALL, _ => throw new NotSupportedException($"Execution type {executionType} is not supported.") }; } @@ -33,10 +55,15 @@ public enum ExecutionType TRANSACTION, CALL, STATICCALL, - CALLCODE, DELEGATECALL, + CALLCODE, CREATE, - CREATE2 + CREATE2, + EOFCREATE, + TXCREATE, + EOFCALL, + EOFSTATICCALL, + EOFDELEGATECALL, } // ReSharper restore IdentifierTypo InconsistentNaming } diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs index e67770b53e8..d2e02e57aff 100644 --- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs +++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs @@ -62,6 +62,25 @@ public static class GasCostOf public const long AccessStorageListEntry = 1900; // eip-2930 public const long TLoad = WarmStateRead; // eip-1153 public const long TStore = WarmStateRead; // eip-1153 + + public const long DataLoad = 4; + public const long DataLoadN = 3; + public const long DataCopy = 3; + public const long DataSize = 2; + public const long ReturnContract = 0; + public const long EofCreate = 32000; + public const long ReturnDataLoad = 3; + + + public const long RJump = 2; + public const long RJumpi = 4; + public const long RJumpv = 4; + public const long Exchange = 3; + public const long Swapn = 3; + public const long Dupn = 3; + public const long Callf = 5; + public const long Jumpf = 5; + public const long Retf = 3; public const long PerAuthBaseCost = 2500; // eip-7702 } } diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs new file mode 100644 index 00000000000..bf7f9b24224 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.Precompiles; + +namespace Nethermind.Evm.CodeAnalysis; + +public interface ICodeInfo +{ + int Version => 0; + bool IsEmpty { get; } + ReadOnlyMemory MachineCode { get; } + IPrecompile? Precompile { get; } + bool IsPrecompile => Precompile is not null; + ReadOnlyMemory TypeSection => Memory.Empty; + ReadOnlyMemory CodeSection => MachineCode; + ReadOnlyMemory DataSection => Memory.Empty; + ReadOnlyMemory ContainerSection => Memory.Empty; + SectionHeader CodeSectionOffset(int idx); + SectionHeader? ContainerSectionOffset(int idx); + int PcOffset(); + (byte inputCount, byte outputCount, ushort maxStackHeight) GetSectionMetadata(int index) => (0, 0, 1024); + void AnalyseInBackgroundIfRequired() { } +} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs index b2f0a4a176d..a4c9b88af83 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfoRepository.cs @@ -14,15 +14,15 @@ namespace Nethermind.Evm; public interface ICodeInfoRepository { - CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress); - ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address); + ICodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress); + ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address, IReleaseSpec spec); void InsertCode(IWorldState state, ReadOnlyMemory code, Address codeOwner, IReleaseSpec spec); void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec); - bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress); + bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, IReleaseSpec spec, [NotNullWhen(true)] out Address? delegatedAddress); } public static class CodeInfoRepositoryExtensions { - public static CodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) + public static ICodeInfo GetCachedCodeInfo(this ICodeInfoRepository codeInfoRepository, IWorldState worldState, Address codeSource, IReleaseSpec vmSpec) => codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec, out _); } diff --git a/src/Nethermind/Nethermind.Evm/Instruction.cs b/src/Nethermind/Nethermind.Evm/Instruction.cs index 96fcf3f3595..3380e00e2ce 100644 --- a/src/Nethermind/Nethermind.Evm/Instruction.cs +++ b/src/Nethermind/Nethermind.Evm/Instruction.cs @@ -1,9 +1,14 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System.Diagnostics.CodeAnalysis; using FastEnumUtility; using Nethermind.Core.Specs; +using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Specs.Forks; +using System; +using System.Diagnostics.CodeAnalysis; + +using static Nethermind.Evm.EvmObjectFormat.EofValidator; namespace Nethermind.Evm { @@ -171,15 +176,214 @@ public enum Instruction : byte REVERT = 0xfd, INVALID = 0xfe, SELFDESTRUCT = 0xff, - } + RJUMP = 0xe0, + RJUMPI = 0xe1, + RJUMPV = 0xe2, + CALLF = 0xe3, + RETF = 0xe4, + JUMPF = 0xe5, + EOFCREATE = 0xec, + RETURNCONTRACT = 0xee, + DATALOAD = 0xd0, + DATALOADN = 0xd1, + DATASIZE = 0xd2, + DATACOPY = 0xd3, + + DUPN = 0xe6, + SWAPN = 0xe7, + EXCHANGE = 0xe8, // random value opcode spec has collision + RETURNDATALOAD = 0xf7, + + // opcode value not spec-ed + EXTCALL = 0xf8, + EXTDELEGATECALL = 0xf9, // DelegateCallEnabled + EXTSTATICCALL = 0xfb, // StaticCallEnabled + + } public static class InstructionExtensions { - public static string? GetName(this Instruction instruction, bool isPostMerge = false, IReleaseSpec? spec = null) => + + public static int GetImmediateCount(this Instruction instruction, bool IsEofContext, byte jumpvCount = 0) + => instruction switch { + Instruction.RJUMPV => IsEofContext ? jumpvCount * TWO_BYTE_LENGTH + ONE_BYTE_LENGTH : 0, + >= Instruction.PUSH0 and <= Instruction.PUSH32 => instruction - Instruction.PUSH0, + _ => IsEofContext ? instruction.StackRequirements().immediates.Value : 0 + }; + public static bool IsTerminating(this Instruction instruction) => instruction switch + { + Instruction.RETF or Instruction.INVALID or Instruction.STOP or Instruction.RETURN or Instruction.REVERT => true, + Instruction.JUMPF or Instruction.RETURNCONTRACT => true, + Instruction.RJUMP => true, + // Instruction.SELFDESTRUCT => true + _ => false + }; + + public static bool IsValid(this Instruction instruction, bool IsEofContext) + { + if (!Enum.IsDefined(instruction)) + { + return false; + } + + return instruction switch + { + Instruction.CALLF or Instruction.RETF or Instruction.JUMPF => IsEofContext, + Instruction.DUPN or Instruction.SWAPN or Instruction.EXCHANGE => IsEofContext, + Instruction.RJUMP or Instruction.RJUMPI or Instruction.RJUMPV => IsEofContext, + Instruction.RETURNCONTRACT or Instruction.EOFCREATE => IsEofContext, + Instruction.DATACOPY or Instruction.DATASIZE or Instruction.DATALOAD or Instruction.DATALOADN => IsEofContext, + Instruction.EXTSTATICCALL or Instruction.EXTDELEGATECALL or Instruction.EXTCALL => IsEofContext, + Instruction.RETURNDATALOAD => IsEofContext, + Instruction.CALL => !IsEofContext, + Instruction.CALLCODE => !IsEofContext, + Instruction.DELEGATECALL => !IsEofContext, + Instruction.STATICCALL => !IsEofContext, + Instruction.SELFDESTRUCT => !IsEofContext, + Instruction.JUMP => !IsEofContext, + Instruction.JUMPI => !IsEofContext, + Instruction.PC => !IsEofContext, + Instruction.CREATE2 or Instruction.CREATE => !IsEofContext, + Instruction.CODECOPY => !IsEofContext, + Instruction.CODESIZE => !IsEofContext, + Instruction.EXTCODEHASH => !IsEofContext, + Instruction.EXTCODECOPY => !IsEofContext, + Instruction.EXTCODESIZE => !IsEofContext, + Instruction.GAS => !IsEofContext, + _ => true + }; + } + + //Note() : Extensively test this, refactor it, + public static (ushort? InputCount, ushort? OutputCount, ushort? immediates) StackRequirements(this Instruction instruction) => instruction switch + { + Instruction.STOP => (0, 0, 0), + Instruction.ADD => (2, 1, 0), + Instruction.MUL => (2, 1, 0), + Instruction.SUB => (2, 1, 0), + Instruction.DIV => (2, 1, 0), + Instruction.SDIV => (2, 1, 0), + Instruction.MOD => (2, 1, 0), + Instruction.SMOD => (2, 1, 0), + Instruction.ADDMOD => (3, 1, 0), + Instruction.MULMOD => (3, 1, 0), + Instruction.EXP => (2, 1, 0), + Instruction.SIGNEXTEND => (2, 1, 0), + Instruction.LT => (2, 1, 0), + Instruction.GT => (2, 1, 0), + Instruction.SLT => (2, 1, 0), + Instruction.SGT => (2, 1, 0), + Instruction.EQ => (2, 1, 0), + Instruction.ISZERO => (1, 1, 0), + Instruction.AND => (2, 1, 0), + Instruction.OR => (2, 1, 0), + Instruction.XOR => (2, 1, 0), + Instruction.NOT => (1, 1, 0), + Instruction.BYTE => (2, 1, 0), + Instruction.SHL => (2, 1, 0), + Instruction.SHR => (2, 1, 0), + Instruction.SAR => (2, 1, 0), + Instruction.KECCAK256 => (2, 1, 0), + Instruction.ADDRESS => (0, 1, 0), + Instruction.BALANCE => (1, 1, 0), + Instruction.ORIGIN => (0, 1, 0), + Instruction.CALLER => (0, 1, 0), + Instruction.CALLVALUE => (0, 1, 0), + Instruction.CALLDATALOAD => (1, 1, 0), + Instruction.CALLDATASIZE => (0, 1, 0), + Instruction.CALLDATACOPY => (3, 0, 0), + Instruction.CODESIZE => (0, 1, 0), + Instruction.CODECOPY => (3, 0, 0), + Instruction.GASPRICE => (0, 1, 0), + Instruction.EXTCODESIZE => (1, 1, 0), + Instruction.EXTCODECOPY => (4, 0, 0), + Instruction.RETURNDATASIZE => (0, 1, 0), + Instruction.RETURNDATACOPY => (3, 0, 0), + Instruction.RETURNDATALOAD => (1, 1, 0), + Instruction.EXTCODEHASH => (1, 1, 0), + Instruction.BLOCKHASH => (1, 1, 0), + Instruction.COINBASE => (0, 1, 0), + Instruction.TIMESTAMP => (0, 1, 0), + Instruction.NUMBER => (0, 1, 0), + Instruction.PREVRANDAO => (0, 1, 0), + Instruction.GASLIMIT => (0, 1, 0), + Instruction.CHAINID => (0, 1, 0), + Instruction.SELFBALANCE => (0, 1, 0), + Instruction.BASEFEE => (0, 1, 0), + Instruction.POP => (1, 0, 0), + Instruction.MLOAD => (1, 1, 0), + Instruction.MSTORE => (2, 0, 0), + Instruction.MSTORE8 => (2, 0, 0), + Instruction.SLOAD => (1, 1, 0), + Instruction.SSTORE => (2, 0, 0), + Instruction.MSIZE => (0, 1, 0), + Instruction.GAS => (0, 1, 0), + Instruction.JUMPDEST => (0, 0, 0), + Instruction.RJUMP => (0, 0, 2), + Instruction.RJUMPI => (1, 0, 2), + Instruction.BLOBHASH => (1, 1, 0), + >= Instruction.PUSH0 and <= Instruction.PUSH32 => (0, 1, instruction - Instruction.PUSH0), + >= Instruction.DUP1 and <= Instruction.DUP16 => ((ushort)(instruction - Instruction.DUP1 + 1), (ushort)(instruction - Instruction.DUP1 + 2), 0), + >= Instruction.SWAP1 and <= Instruction.SWAP16 => ((ushort)(instruction - Instruction.SWAP1 + 2), (ushort)(instruction - Instruction.SWAP1 + 2), 0), + Instruction.LOG0 => (2, 0, 0), + Instruction.LOG1 => (3, 0, 0), + Instruction.LOG2 => (4, 0, 0), + Instruction.LOG3 => (5, 0, 0), + Instruction.LOG4 => (6, 0, 0), + Instruction.CALLF => (0, 0, 2), + Instruction.JUMPF => (0, 0, 2), + Instruction.RETF => (0, 0, 0), + Instruction.CREATE => (3, 1, 0), + Instruction.CALL => (6, 1, 0), + Instruction.RETURN => (2, 0, 0), + Instruction.DELEGATECALL => (6, 1, 0), + Instruction.CREATE2 => (4, 1, 0), + Instruction.STATICCALL => (6, 1, 0), + Instruction.REVERT => (2, 0, 0), + Instruction.INVALID => (0, 0, 0), + + Instruction.TLOAD => (1, 1, 0), + Instruction.TSTORE => (2, 0, 0), + Instruction.MCOPY => (3, 0, 0), + Instruction.CALLCODE => (6, 1, 0), + Instruction.SELFDESTRUCT => (1, 0, 0), + Instruction.JUMP => (1, 0, 0), + Instruction.JUMPI => (2, 0, 0), + Instruction.PC => (0, 1, 0), + Instruction.BLOBBASEFEE => (0, 1, 0), + + Instruction.EOFCREATE => (4, 1, 1), + Instruction.RETURNCONTRACT => (2, 2, 1), + Instruction.DATALOAD => (1, 1, 0), + Instruction.DATALOADN => (0, 1, 2), + Instruction.DATASIZE => (0, 1, 0), + Instruction.DATACOPY => (3, 0, 0), + + Instruction.RJUMPV => (1, 0, null), // null indicates this is a dynamic multi-bytes opcode + Instruction.SWAPN => (null, null, 1), + Instruction.DUPN => (null, null, 1), + Instruction.EXCHANGE => (null, null, 1), + + Instruction.EXTCALL => (4, 1, 0), + Instruction.EXTSTATICCALL => (3, 1, 0), + Instruction.EXTDELEGATECALL => (3, 1, 0), + _ => throw new NotImplementedException($"opcode {instruction} not implemented yet"), + }; + + public static string? GetName(this Instruction instruction, bool isPostMerge = false, IReleaseSpec? spec = null) + { + spec ??= Frontier.Instance; + return instruction switch + { + Instruction.EXTCALL => "EXTCALL", + Instruction.EXTSTATICCALL => "EXTSTATICCALL", // StaticCallEnabled + Instruction.EXTDELEGATECALL => "EXTDELEGATECALL", Instruction.PREVRANDAO when !isPostMerge => "DIFFICULTY", - _ => FastEnum.IsDefined(instruction) ? FastEnum.GetName(instruction) : null + Instruction.JUMPDEST => spec.IsEofEnabled ? "NOP" : "JUMPDEST", + _ => FastEnum.IsDefined(instruction) ? FastEnum.GetName(instruction) : null, }; + } } } diff --git a/src/Nethermind/Nethermind.Evm/ReadOnlyMemoryExtensions.cs b/src/Nethermind/Nethermind.Evm/ReadOnlyMemoryExtensions.cs index f555a6b732e..79a9ab6b584 100644 --- a/src/Nethermind/Nethermind.Evm/ReadOnlyMemoryExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/ReadOnlyMemoryExtensions.cs @@ -9,7 +9,18 @@ public static class ReadOnlyMemoryExtensions { public static bool StartsWith(this ReadOnlyMemory inputData, byte startingByte) { - return inputData.Span[0] == startingByte; + ReadOnlySpan span = inputData.Span; + return span.Length > 0 && span[0] == startingByte; + } + + public static bool StartsWith(this ReadOnlyMemory inputData, Span startingBytes) + { + return inputData.Span.StartsWith(startingBytes); + } + + public static byte ByteAt(this ReadOnlyMemory inputData, int index) + { + return inputData.Length > index ? inputData.Span[index] : (byte)0; } } } diff --git a/src/Nethermind/Nethermind.Evm/StatusCode.cs b/src/Nethermind/Nethermind.Evm/StatusCode.cs index 67d23340f69..fd17b3b7e96 100644 --- a/src/Nethermind/Nethermind.Evm/StatusCode.cs +++ b/src/Nethermind/Nethermind.Evm/StatusCode.cs @@ -13,4 +13,13 @@ public static class StatusCode public const byte Success = 1; public static readonly ReadOnlyMemory SuccessBytes = Bytes.OneByte; } + public static class EofStatusCode + { + public const byte Success = 0; + public static readonly ReadOnlyMemory SuccessBytes = Bytes.ZeroByte; + public const byte Revert = 1; + public static readonly ReadOnlyMemory RevertBytes = Bytes.OneByte; + public const byte Failure = 2; + public static readonly ReadOnlyMemory FailureBytes = Bytes.TwoByte; + } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs index dde143ac7e3..9d90e6c2654 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/GethStyle/Custom/JavaScript/GethLikeJavaScriptTxTracer.cs @@ -101,7 +101,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address public override void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnvironment env) { - _log.pc = pc; + _log.pc = pc + env.CodeInfo.PcOffset(); _log.op = new Log.Opcode(opcode); _log.gas = gas; _log.depth = env.GetGethTraceDepth(); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs index 0d4e90daad0..3ca76729d2a 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -73,8 +73,9 @@ private static string GetCallType(ExecutionType executionType) case ExecutionType.TRANSACTION: return "call"; case ExecutionType.CREATE: - return "create"; case ExecutionType.CREATE2: + case ExecutionType.EOFCREATE: + case ExecutionType.TXCREATE: return "create"; case ExecutionType.CALL: return "call"; @@ -96,8 +97,9 @@ private static string GetActionType(ExecutionType executionType) case ExecutionType.TRANSACTION: return "call"; case ExecutionType.CREATE: - return "create"; case ExecutionType.CREATE2: + case ExecutionType.EOFCREATE: + case ExecutionType.TXCREATE: return "create"; case ExecutionType.CALL: return "call"; @@ -259,7 +261,7 @@ public override void StartOperation(int pc, Instruction opcode, long gas, in Exe { ParityVmOperationTrace operationTrace = new(); _gasAlreadySetForCurrentOp = false; - operationTrace.Pc = pc; + operationTrace.Pc = pc + env.CodeInfo.PcOffset(); operationTrace.Cost = gas; _currentOperation = operationTrace; _currentPushList.Clear(); @@ -421,6 +423,10 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address return "create"; case ExecutionType.CREATE2: return "create2"; + case ExecutionType.EOFCREATE: + return "create3"; + case ExecutionType.TXCREATE: + return "create4"; default: return null; } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index a348e41c4bd..df948511c4b 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -13,12 +13,14 @@ using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.EvmObjectFormat.Handlers; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.State; using Nethermind.State.Tracing; - +using static Nethermind.Core.Extensions.MemoryExtensions; +using static Nethermind.Evm.EvmObjectFormat.EofValidator; using static Nethermind.Evm.VirtualMachine; namespace Nethermind.Evm.TransactionProcessing @@ -161,10 +163,13 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon _accessedAddresses.Clear(); int delegationRefunds = ProcessDelegations(tx, spec, _accessedAddresses); - ExecutionEnvironment env = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice, _codeInfoRepository, _accessedAddresses); - long gasAvailable = tx.GasLimit - intrinsicGas; - ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, _accessedAddresses, gasAvailable, env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); + if (!(result = BuildExecutionEnvironment(tx, in blCtx, spec, effectiveGasPrice, _codeInfoRepository, _accessedAddresses, out ExecutionEnvironment env))) + { + return result; + } + + ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, _accessedAddresses, gasAvailable, in env, out TransactionSubstate? substate, out long spentGas, out byte statusCode); PayFees(tx, header, spec, tracer, substate, spentGas, premiumPerGas, blobBaseFee, statusCode); // Finalize @@ -204,13 +209,13 @@ protected virtual TransactionResult Execute(Transaction tx, in BlockExecutionCon if (statusCode == StatusCode.Failure) { - byte[] output = (substate?.ShouldRevert ?? false) ? substate.Output.ToArray() : Array.Empty(); + byte[] output = (substate?.ShouldRevert ?? false) ? substate.Output.Bytes.ToArray() : Array.Empty(); tracer.MarkAsFailed(env.ExecutingAccount, spentGas, output, substate?.Error, stateRoot); } else { LogEntry[] logs = substate.Logs.Count != 0 ? substate.Logs.ToArray() : Array.Empty(); - tracer.MarkAsSuccess(env.ExecutingAccount, spentGas, substate.Output.ToArray(), logs, stateRoot); + tracer.MarkAsSuccess(env.ExecutingAccount, spentGas, substate.Output.Bytes.ToArray(), logs, stateRoot); } } @@ -268,7 +273,7 @@ bool IsValidForExecution( accessedAddresses.Add(authorizationTuple.Authority); - if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(WorldState, authorizationTuple.Authority, out _)) + if (WorldState.HasCode(authorizationTuple.Authority) && !_codeInfoRepository.TryGetDelegation(WorldState, authorizationTuple.Authority, spec, out _)) { error = $"Authority ({authorizationTuple.Authority}) has code deployed."; return false; @@ -513,13 +518,14 @@ protected virtual void DecrementNonce(Transaction tx) WorldState.DecrementNonce(tx.SenderAddress!); } - private ExecutionEnvironment BuildExecutionEnvironment( + private TransactionResult BuildExecutionEnvironment( Transaction tx, in BlockExecutionContext blCtx, IReleaseSpec spec, in UInt256 effectiveGasPrice, ICodeInfoRepository codeInfoRepository, - HashSet
accessedAddresses) + HashSet
accessedAddresses, + out ExecutionEnvironment env) { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); @@ -528,19 +534,29 @@ private ExecutionEnvironment BuildExecutionEnvironment( accessedAddresses.Add(tx.SenderAddress!); TxExecutionContext executionContext = new(in blCtx, tx.SenderAddress, effectiveGasPrice, tx.BlobVersionedHashes, codeInfoRepository); - Address? delegationAddress = null; - CodeInfo codeInfo = tx.IsContractCreation - ? new(tx.Data ?? Memory.Empty) - : codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec, out delegationAddress); - - if (delegationAddress is not null) - accessedAddresses.Add(delegationAddress); + ICodeInfo codeInfo = null; + ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data ?? default : default; + if (tx.IsContractCreation) + { + if (CodeInfoFactory.CreateInitCodeInfo(tx.Data ?? default, spec, out codeInfo, out Memory trailingData)) + { + inputData = trailingData; + } + } + else + { + Address? delegationAddress = null; + codeInfo = tx.IsContractCreation + ? new CodeInfo(tx.Data ?? Memory.Empty) + : codeInfoRepository.GetCachedCodeInfo(WorldState, recipient, spec, out delegationAddress); - codeInfo.AnalyseInBackgroundIfRequired(); + if (delegationAddress is not null) + accessedAddresses.Add(delegationAddress); - ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data ?? default : default; + codeInfo.AnalyseInBackgroundIfRequired(); + } - return new ExecutionEnvironment + env = new ExecutionEnvironment ( txExecutionContext: in executionContext, value: tx.Value, @@ -551,6 +567,8 @@ private ExecutionEnvironment BuildExecutionEnvironment( inputData: inputData, codeInfo: codeInfo ); + + return TransactionResult.Ok; } protected virtual bool ShouldValidate(ExecutionOptions opts) => !opts.HasFlag(ExecutionOptions.NoValidation); @@ -583,21 +601,38 @@ protected virtual void ExecuteEvmCall( try { - if (tx.IsContractCreation) + if (env.CodeInfo is not null) { - // if transaction is a contract creation then recipient address is the contract deployment address - if (!PrepareAccountForContractDeployment(env.ExecutingAccount, _codeInfoRepository, spec)) + if (tx.IsContractCreation) { - goto Fail; + // if transaction is a contract creation then recipient address is the contract deployment address + if (!PrepareAccountForContractDeployment(env.ExecutingAccount, _codeInfoRepository, spec)) + { + goto Fail; + } } } - ExecutionType executionType = tx.IsContractCreation ? ExecutionType.CREATE : ExecutionType.TRANSACTION; + ExecutionType executionType = tx.IsContractCreation ? (tx.IsEofContractCreation ? ExecutionType.TXCREATE : ExecutionType.CREATE) : ExecutionType.TRANSACTION; using (EvmState state = new(unspentGas, env, executionType, true, snapshot, false)) { - WarmUp(tx, header, spec, state, accessedAddresses); + if (spec.UseTxAccessLists) + { + state.WarmUp(tx.AccessList); // eip-2930 + } + + if (spec.UseHotAndColdStorage) + { + state.WarmUp(tx.SenderAddress); // eip-2929 + state.WarmUp(env.ExecutingAccount); // eip-2929 + } + if (spec.AddCoinbaseToTxAccessList) + { + state.WarmUp(header.GasBeneficiary); + } + WarmUp(tx, header, spec, state, accessedAddresses); substate = !tracer.IsTracingActions ? VirtualMachine.Run(state, WorldState, tracer) : VirtualMachine.Run(state, WorldState, tracer); @@ -619,28 +654,76 @@ protected virtual void ExecuteEvmCall( { // tks: there is similar code fo contract creation from init and from CREATE // this may lead to inconsistencies (however it is tested extensively in blockchain tests) - if (tx.IsContractCreation) + if (tx.IsLegacyContractCreation) { - long codeDepositGasCost = CodeDepositHandler.CalculateCost(substate.Output.Length, spec); + long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, substate.Output.Bytes.Length); if (unspentGas < codeDepositGasCost && spec.ChargeForTopLevelCreate) { goto Fail; } - if (CodeDepositHandler.CodeIsInvalid(spec, substate.Output)) + if (CodeDepositHandler.CodeIsInvalid(spec, substate.Output.Bytes, 0)) { goto Fail; } if (unspentGas >= codeDepositGasCost) { - var code = substate.Output.ToArray(); + var code = substate.Output.Bytes.ToArray(); _codeInfoRepository.InsertCode(WorldState, code, env.ExecutingAccount, spec); unspentGas -= codeDepositGasCost; } } + if (tx.IsEofContractCreation) + { + // 1 - load deploy EOF subcontainer at deploy_container_index in the container from which RETURNCONTRACT is executed + ReadOnlySpan auxExtraData = substate.Output.Bytes.Span; + EofCodeInfo deployCodeInfo = (EofCodeInfo)substate.Output.DeployCode; + + long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, deployCodeInfo.MachineCode.Length + auxExtraData.Length); + if (unspentGas < codeDepositGasCost && spec.ChargeForTopLevelCreate) + { + goto Fail; + } + + byte[] bytecodeResultArray = null; + + // 2 - concatenate data section with (aux_data_offset, aux_data_offset + aux_data_size) memory segment and update data size in the header + Span bytecodeResult = new byte[deployCodeInfo.MachineCode.Length + auxExtraData.Length]; + // 2 - 1 - 1 - copy old container + deployCodeInfo.MachineCode.Span.CopyTo(bytecodeResult); + // 2 - 1 - 2 - copy aux data to dataSection + auxExtraData.CopyTo(bytecodeResult[deployCodeInfo.MachineCode.Length..]); + + // 2 - 2 - update data section size in the header u16 + int dataSubheaderSectionStart = + VERSION_OFFSET // magic + version + + Eof1.MINIMUM_HEADER_SECTION_SIZE // type section : (1 byte of separator + 2 bytes for size) + + ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.CodeSections.Count // code section : (1 byte of separator + (CodeSections count) * 2 bytes for size) + + (deployCodeInfo.EofContainer.Header.ContainerSections is null + ? 0 // container section : (0 bytes if no container section is available) + : ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.ContainerSections.Value.Count) // container section : (1 byte of separator + (ContainerSections count) * 2 bytes for size) + + ONE_BYTE_LENGTH; // data section seperator + + ushort dataSize = (ushort)(deployCodeInfo.DataSection.Length + auxExtraData.Length); + bytecodeResult[dataSubheaderSectionStart + 1] = (byte)(dataSize >> 8); + bytecodeResult[dataSubheaderSectionStart + 2] = (byte)(dataSize & 0xFF); + + bytecodeResultArray = bytecodeResult.ToArray(); + + // 3 - if updated deploy container size exceeds MAX_CODE_SIZE instruction exceptionally aborts + bool invalidCode = !(bytecodeResultArray.Length < spec.MaxCodeSize); + if (unspentGas >= codeDepositGasCost && !invalidCode) + { + // 4 - set state[new_address].code to the updated deploy container + // push new_address onto the stack (already done before the ifs) + _codeInfoRepository.InsertCode(WorldState, bytecodeResultArray, env.ExecutingAccount, spec); + unspentGas -= codeDepositGasCost; + } + } + foreach (Address toBeDestroyed in substate.DestroyList) { if (Logger.IsTrace) diff --git a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs index bfc253e6844..a54777f6d70 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionSubstate.cs @@ -9,6 +9,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Evm.CodeAnalysis; using Nethermind.Int256; using Nethermind.Logging; @@ -46,7 +47,7 @@ public class TransactionSubstate public bool IsError => Error is not null && !ShouldRevert; public string? Error { get; } - public ReadOnlyMemory Output { get; } + public (ICodeInfo DeployCode, ReadOnlyMemory Bytes) Output { get; } public bool ShouldRevert { get; } public long Refund { get; } public IReadOnlyCollection Logs { get; } @@ -61,7 +62,18 @@ public TransactionSubstate(EvmExceptionType exceptionType, bool isTracerConnecte ShouldRevert = false; } - public TransactionSubstate(ReadOnlyMemory output, + public static TransactionSubstate FailedInitCode { get; } = new TransactionSubstate(); + + private TransactionSubstate() + { + Error = "Eip 7698: Invalid CreateTx InitCode"; + Refund = 0; + DestroyList = _emptyDestroyList; + Logs = _emptyLogs; + ShouldRevert = true; + } + + public TransactionSubstate((ICodeInfo eofDeployCode, ReadOnlyMemory bytes) output, long refund, IReadOnlyCollection
destroyList, IReadOnlyCollection logs, @@ -87,10 +99,10 @@ public TransactionSubstate(ReadOnlyMemory output, if (!isTracerConnected) return; - if (Output.Length <= 0) + if (Output.Bytes.Length <= 0) return; - ReadOnlySpan span = Output.Span; + ReadOnlySpan span = Output.Bytes.Span; Error = string.Concat( RevertedErrorMessagePrefix, TryGetErrorMessage(span) ?? EncodeErrorMessage(span) diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index b5ca0aa4a7d..82ec271e5bd 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -4,7 +4,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -14,10 +18,9 @@ using Nethermind.Evm.Tracing; using Nethermind.Logging; using Nethermind.State; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.Intrinsics; using static Nethermind.Evm.VirtualMachine; using static System.Runtime.CompilerServices.Unsafe; +using static Nethermind.Evm.EvmObjectFormat.EofValidator; #if DEBUG using Nethermind.Evm.Tracing.Debugger; @@ -28,11 +31,15 @@ namespace Nethermind.Evm; using Int256; +using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.EvmObjectFormat.Handlers; + public class VirtualMachine : IVirtualMachine { - public const int MaxCallDepth = 1024; - private static readonly UInt256 P255Int = (UInt256)System.Numerics.BigInteger.Pow(2, 255); + public const int MaxCallDepth = Eof1.RETURN_STACK_MAX_HEIGHT; + private readonly static UInt256 P255Int = (UInt256)System.Numerics.BigInteger.Pow(2, 255); + internal readonly static byte[] EofHash256 = KeccakHash.ComputeHashBytes(EofValidator.MAGIC); internal static ref readonly UInt256 P255 => ref P255Int; internal static readonly UInt256 BigInt256 = 256; internal static readonly UInt256 BigInt32 = 32; @@ -88,43 +95,57 @@ internal readonly ref struct CallResult public static CallResult StackOverflowException => new(EvmExceptionType.StackOverflow); // TODO: use these to avoid CALL POP attacks public static CallResult StackUnderflowException => new(EvmExceptionType.StackUnderflow); // TODO: use these to avoid CALL POP attacks public static CallResult InvalidCodeException => new(EvmExceptionType.InvalidCode); - public static CallResult Empty => new(default, null); + public static CallResult InvalidAddressRange => new(EvmExceptionType.AddressOutOfRange); public static object BoxedEmpty { get; } = new object(); + public static CallResult Empty(int fromVersion) => new(default, null, fromVersion); public CallResult(EvmState stateToExecute) { StateToExecute = stateToExecute; - Output = Array.Empty(); + Output = (null, Array.Empty()); PrecompileSuccess = null; ShouldRevert = false; ExceptionType = EvmExceptionType.None; } - private CallResult(EvmExceptionType exceptionType) + public CallResult(ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; - Output = StatusCode.FailureBytes; - PrecompileSuccess = null; - ShouldRevert = false; + Output = (null, output); + PrecompileSuccess = precompileSuccess; + ShouldRevert = shouldRevert; ExceptionType = exceptionType; + FromVersion = fromVersion; } - public CallResult(ReadOnlyMemory output, bool? precompileSuccess, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) + public CallResult(ICodeInfo container, ReadOnlyMemory output, bool? precompileSuccess, int fromVersion, bool shouldRevert = false, EvmExceptionType exceptionType = EvmExceptionType.None) { StateToExecute = null; - Output = output; + Output = (container, output); PrecompileSuccess = precompileSuccess; ShouldRevert = shouldRevert; ExceptionType = exceptionType; + FromVersion = fromVersion; + } + + private CallResult(EvmExceptionType exceptionType) + { + StateToExecute = null; + Output = (null, StatusCode.FailureBytes); + PrecompileSuccess = null; + ShouldRevert = false; + ExceptionType = exceptionType; } public EvmState? StateToExecute { get; } - public ReadOnlyMemory Output { get; } + public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } public bool? PrecompileSuccess { get; } // TODO: check this behaviour as it seems it is required and previously that was not the case public bool IsReturn => StateToExecute is null; public bool IsException => ExceptionType != EvmExceptionType.None; + + public int FromVersion { get; } } public interface IIsTracing { } @@ -251,7 +272,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl return new TransactionSubstate(callResult.ExceptionType, isTracing); } - previousCallResult = StatusCode.FailureBytes; + previousCallResult = currentState.ExecutionType.IsAnyCallEof() ? EofStatusCode.FailureBytes : StatusCode.FailureBytes; previousCallOutputDestination = UInt256.Zero; _returnDataBuffer = Array.Empty(); previousCallOutput = ZeroPaddedSpan.Empty; @@ -267,7 +288,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl { if (typeof(TTracingActions) == typeof(IsTracing)) { - long codeDepositGasCost = CodeDepositHandler.CalculateCost(callResult.Output.Length, spec); + long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, callResult.Output.Bytes.Length); if (callResult.IsException) { @@ -278,11 +299,11 @@ public TransactionSubstate Run(EvmState state, IWorldState worl _txTracer.ReportActionRevert(currentState.ExecutionType.IsAnyCreate() ? currentState.GasAvailable - codeDepositGasCost : currentState.GasAvailable, - callResult.Output); + callResult.Output.Bytes); } - else + else if (currentState.ExecutionType.IsAnyCreate()) { - if (currentState.ExecutionType.IsAnyCreate() && currentState.GasAvailable < codeDepositGasCost) + if (currentState.GasAvailable < codeDepositGasCost) { if (spec.ChargeForTopLevelCreate) { @@ -290,26 +311,23 @@ public TransactionSubstate Run(EvmState state, IWorldState worl } else { - _txTracer.ReportActionEnd(currentState.GasAvailable, currentState.To, callResult.Output); + _txTracer.ReportActionEnd(currentState.GasAvailable, currentState.To, callResult.Output.Bytes); } } // Reject code starting with 0xEF if EIP-3541 is enabled. - else if (currentState.ExecutionType.IsAnyCreate() && CodeDepositHandler.CodeIsInvalid(spec, callResult.Output)) + else if (CodeDepositHandler.CodeIsInvalid(spec, callResult.Output.Bytes, callResult.FromVersion)) { _txTracer.ReportActionError(EvmExceptionType.InvalidCode); } else { - if (currentState.ExecutionType.IsAnyCreate()) - { - _txTracer.ReportActionEnd(currentState.GasAvailable - codeDepositGasCost, currentState.To, callResult.Output); - } - else - { - _txTracer.ReportActionEnd(currentState.GasAvailable, _returnDataBuffer); - } + _txTracer.ReportActionEnd(currentState.GasAvailable - codeDepositGasCost, currentState.To, callResult.Output.Bytes); } } + else + { + _txTracer.ReportActionEnd(currentState.GasAvailable, _returnDataBuffer); + } } return new TransactionSubstate( @@ -339,48 +357,120 @@ public TransactionSubstate Run(EvmState state, IWorldState worl previousCallOutputDestination = UInt256.Zero; _returnDataBuffer = Array.Empty(); previousCallOutput = ZeroPaddedSpan.Empty; - - long codeDepositGasCost = CodeDepositHandler.CalculateCost(callResult.Output.Length, spec); - bool invalidCode = CodeDepositHandler.CodeIsInvalid(spec, callResult.Output); - if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) + if (previousState.ExecutionType.IsAnyCreateLegacy()) { - ReadOnlyMemory code = callResult.Output; - codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); - - currentState.GasAvailable -= codeDepositGasCost; - - if (typeof(TTracingActions) == typeof(IsTracing)) + long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, callResult.Output.Bytes.Length); + bool invalidCode = !CodeDepositHandler.IsValidWithLegacyRules(spec, callResult.Output.Bytes); + if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) { - _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output); + ReadOnlyMemory code = callResult.Output.Bytes; + codeInfoRepository.InsertCode(_state, code, callCodeOwner, spec); + currentState.GasAvailable -= codeDepositGasCost; + + if (_txTracer.IsTracingActions) + { + _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); + } } - } - else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) - { - currentState.GasAvailable -= gasAvailableForCodeDeposit; - worldState.Restore(previousState.Snapshot); - if (!previousState.IsCreateOnPreExistingAccount) + else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) { - _state.DeleteAccount(callCodeOwner); + currentState.GasAvailable -= gasAvailableForCodeDeposit; + worldState.Restore(previousState.Snapshot); + if (!previousState.IsCreateOnPreExistingAccount) + { + _state.DeleteAccount(callCodeOwner); + } + + previousCallResult = BytesZero; + previousStateSucceeded = false; + + if (_txTracer.IsTracingActions) + { + _txTracer.ReportActionError(invalidCode ? EvmExceptionType.InvalidCode : EvmExceptionType.OutOfGas); + } } - - previousCallResult = BytesZero; - previousStateSucceeded = false; - - if (typeof(TTracingActions) == typeof(IsTracing)) + else if (_txTracer.IsTracingActions) { - _txTracer.ReportActionError(invalidCode ? EvmExceptionType.InvalidCode : EvmExceptionType.OutOfGas); + _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); } } - else if (typeof(TTracingActions) == typeof(IsTracing)) + else if (previousState.ExecutionType.IsAnyCreateEof()) { - _txTracer.ReportActionEnd(0L, callCodeOwner, callResult.Output); + // ReturnContract was called with a container index and auxdata + // 1 - load deploy EOF subcontainer at deploy_container_index in the container from which RETURNCONTRACT is executed + ReadOnlySpan auxExtraData = callResult.Output.Bytes.Span; + EofCodeInfo deployCodeInfo = (EofCodeInfo)callResult.Output.Container; + byte[] bytecodeResultArray = null; + + // 2 - concatenate data section with (aux_data_offset, aux_data_offset + aux_data_size) memory segment and update data size in the header + Span bytecodeResult = new byte[deployCodeInfo.MachineCode.Length + auxExtraData.Length]; + // 2 - 1 - 1 - copy old container + deployCodeInfo.MachineCode.Span.CopyTo(bytecodeResult); + // 2 - 1 - 2 - copy aux data to dataSection + auxExtraData.CopyTo(bytecodeResult[deployCodeInfo.MachineCode.Length..]); + + // 2 - 2 - update data section size in the header u16 + int dataSubheaderSectionStart = + EofValidator.VERSION_OFFSET // magic + version + + Eof1.MINIMUM_HEADER_SECTION_SIZE // type section : (1 byte of separator + 2 bytes for size) + + ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.CodeSections.Count // code section : (1 byte of separator + (CodeSections count) * 2 bytes for size) + + (deployCodeInfo.EofContainer.Header.ContainerSections is null + ? 0 // container section : (0 bytes if no container section is available) + : ONE_BYTE_LENGTH + TWO_BYTE_LENGTH + TWO_BYTE_LENGTH * deployCodeInfo.EofContainer.Header.ContainerSections.Value.Count) // container section : (1 byte of separator + (ContainerSections count) * 2 bytes for size) + + ONE_BYTE_LENGTH; // data section seperator + + ushort dataSize = (ushort)(deployCodeInfo.DataSection.Length + auxExtraData.Length); + bytecodeResult[dataSubheaderSectionStart + 1] = (byte)(dataSize >> 8); + bytecodeResult[dataSubheaderSectionStart + 2] = (byte)(dataSize & 0xFF); + + bytecodeResultArray = bytecodeResult.ToArray(); + + // 3 - if updated deploy container size exceeds MAX_CODE_SIZE instruction exceptionally aborts + bool invalidCode = bytecodeResultArray.Length > spec.MaxCodeSize; + long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, bytecodeResultArray?.Length ?? 0); + if (gasAvailableForCodeDeposit >= codeDepositGasCost && !invalidCode) + { + // 4 - set state[new_address].code to the updated deploy container + // push new_address onto the stack (already done before the ifs) + codeInfoRepository.InsertCode(_state, bytecodeResultArray, callCodeOwner, spec); + currentState.GasAvailable -= codeDepositGasCost; + + if (_txTracer.IsTracingActions) + { + _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, bytecodeResultArray); + } + } + else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) + { + currentState.GasAvailable -= gasAvailableForCodeDeposit; + worldState.Restore(previousState.Snapshot); + if (!previousState.IsCreateOnPreExistingAccount) + { + _state.DeleteAccount(callCodeOwner); + } + + previousCallResult = BytesZero; + previousStateSucceeded = false; + + if (_txTracer.IsTracingActions) + { + _txTracer.ReportActionError(invalidCode ? EvmExceptionType.InvalidCode : EvmExceptionType.OutOfGas); + } + } + else if (_txTracer.IsTracingActions) + { + _txTracer.ReportActionEnd(0L, callCodeOwner, bytecodeResultArray); + } } } else { - _returnDataBuffer = callResult.Output; - previousCallResult = callResult.PrecompileSuccess.HasValue ? (callResult.PrecompileSuccess.Value ? StatusCode.SuccessBytes : StatusCode.FailureBytes) : StatusCode.SuccessBytes; - previousCallOutput = callResult.Output.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Length, (int)previousState.OutputLength)); + _returnDataBuffer = callResult.Output.Bytes; + previousCallResult = previousState.ExecutionType.IsAnyCallEof() ? EofStatusCode.SuccessBytes : + callResult.PrecompileSuccess.HasValue + ? (callResult.PrecompileSuccess.Value ? StatusCode.SuccessBytes : StatusCode.FailureBytes) + : StatusCode.SuccessBytes; + previousCallOutput = callResult.Output.Bytes.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Bytes.Length, (int)previousState.OutputLength)); previousCallOutputDestination = (ulong)previousState.OutputDestination; if (previousState.IsPrecompile) { @@ -405,15 +495,17 @@ public TransactionSubstate Run(EvmState state, IWorldState worl else { worldState.Restore(previousState.Snapshot); - _returnDataBuffer = callResult.Output; - previousCallResult = StatusCode.FailureBytes; - previousCallOutput = callResult.Output.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Length, (int)previousState.OutputLength)); + _returnDataBuffer = callResult.Output.Bytes; + previousCallResult = previousState.ExecutionType.IsAnyCallEof() + ? (callResult.PrecompileSuccess is not null ? EofStatusCode.FailureBytes : EofStatusCode.RevertBytes) + : StatusCode.FailureBytes; + previousCallOutput = callResult.Output.Bytes.Span.SliceWithZeroPadding(0, Math.Min(callResult.Output.Bytes.Length, (int)previousState.OutputLength)); previousCallOutputDestination = (ulong)previousState.OutputDestination; if (typeof(TTracingActions) == typeof(IsTracing)) { - _txTracer.ReportActionRevert(previousState.GasAvailable, callResult.Output); + _txTracer.ReportActionRevert(previousState.GasAvailable, callResult.Output.Bytes); } } } @@ -451,7 +543,7 @@ public TransactionSubstate Run(EvmState state, IWorldState worl return new TransactionSubstate(failure is OverflowException ? EvmExceptionType.Other : (failure as EvmException).ExceptionType, isTracing); } - previousCallResult = StatusCode.FailureBytes; + previousCallResult = currentState.ExecutionType.IsAnyCallEof() ? EofStatusCode.FailureBytes : StatusCode.FailureBytes; previousCallOutputDestination = UInt256.Zero; _returnDataBuffer = Array.Empty(); previousCallOutput = ZeroPaddedSpan.Empty; @@ -501,7 +593,7 @@ private bool ChargeAccountAccessGas(ref long gasAvailable, EvmState vmState, Add bool notOutOfGas = ChargeAccountGas(ref gasAvailable, vmState, address, spec); return notOutOfGas && chargeForDelegation - && vmState.Env.TxExecutionContext.CodeInfoRepository.TryGetDelegation(_state, address, out Address delegated) + && vmState.Env.TxExecutionContext.CodeInfoRepository.TryGetDelegation(_state, address, spec, out Address delegated) ? ChargeAccountGas(ref gasAvailable, vmState, delegated, spec) : notOutOfGas; @@ -603,7 +695,7 @@ private CallResult ExecutePrecompile(EvmState state, IReleaseSpec spec) if (!UpdateGas(checked(baseGasCost + blobGasCost), ref gasAvailable)) { - return new(default, false, true, EvmExceptionType.OutOfGas); + return new(default, false, 0, true, EvmExceptionType.OutOfGas); } state.GasAvailable = gasAvailable; @@ -611,7 +703,7 @@ private CallResult ExecutePrecompile(EvmState state, IReleaseSpec spec) try { (ReadOnlyMemory output, bool success) = precompile.Run(callData, spec); - CallResult callResult = new(output, success, !success); + CallResult callResult = new(output, success, 0, !success); return callResult; } catch (DllNotFoundException exception) @@ -622,7 +714,7 @@ private CallResult ExecutePrecompile(EvmState state, IReleaseSpec spec) catch (Exception exception) { if (_logger.IsError) _logger.Error($"Precompiled contract ({precompile.GetType()}) execution exception", exception); - CallResult callResult = new(default, false, true); + CallResult callResult = new(default, false, 0, true); return callResult; } } @@ -702,7 +794,7 @@ private CallResult ExecuteCall(EvmState vmState, ReadOnlyM ExecuteCode(vmState, ref stack, gasAvailable, spec); } Empty: - return CallResult.Empty; + return CallResult.Empty(vmState.Env.CodeInfo.Version); OutOfGas: return CallResult.OutOfGasException; } @@ -713,11 +805,17 @@ private CallResult ExecuteCode code = env.CodeInfo.MachineCode.Span; + + int programCounter = vmState.ProgramCounter; + int sectionIndex = vmState.FunctionIndex; + + ReadOnlySpan codeSection = env.CodeInfo.CodeSection.Span; + ReadOnlySpan dataSection = env.CodeInfo.DataSection.Span; + EvmExceptionType exceptionType = EvmExceptionType.None; bool isRevert = false; #if DEBUG @@ -731,13 +829,13 @@ private CallResult ExecuteCode externalCode = txCtx.CodeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; - slice = externalCode.SliceWithZeroPadding(b, (int)result); + + if (spec.IsEofEnabled && IsEof(externalCode, out _)) + { + slice = EofValidator.MAGIC.SliceWithZeroPadding(b, (int)result); + } + else + { + slice = externalCode.SliceWithZeroPadding(b, (int)result); + } + + if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, result)) goto OutOfGas; vmState.Memory.Save(in a, in slice); + if (typeof(TTracingInstructions) == typeof(IsTracing)) { _txTracer.ReportMemoryChange(a, in slice); @@ -1390,27 +1501,40 @@ private CallResult ExecuteCode _returnDataBuffer.Length) + + if (env.CodeInfo.Version == 0 && (UInt256.AddOverflow(c, b, out result) || result > _returnDataBuffer.Length)) { goto AccessViolation; } - if (!result.IsZero) + if (!c.IsZero) { - if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, result)) goto OutOfGas; + if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, c)) goto OutOfGas; - slice = _returnDataBuffer.Span.SliceWithZeroPadding(b, (int)result); + slice = _returnDataBuffer.Span.SliceWithZeroPadding(b, (int)c); vmState.Memory.Save(in a, in slice); if (typeof(TTracingInstructions) == typeof(IsTracing)) { _txTracer.ReportMemoryChange(a, in slice); } } + break; + } + case Instruction.RETURNDATALOAD: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + goto InvalidInstruction; + + gasAvailable -= GasCostOf.VeryLow; + if (!stack.PopUInt256(out a)) goto StackUnderflow; + + slice = _returnDataBuffer.Span.SliceWithZeroPadding(a, 32); + stack.PushBytes(slice); break; } case Instruction.BLOCKHASH: @@ -1543,7 +1667,7 @@ private CallResult ExecuteCode= code.Length) + if (programCounter >= codeSection.Length) { stack.PushZero(); } else { - stack.PushByte(code[programCounterInt]); + stack.PushByte(codeSection[programCounter]); } programCounter++; @@ -1709,8 +1832,8 @@ private CallResult ExecuteCode> 0x04); + int m = 1 + (int)(codeSection[programCounter] & 0x0f); + + stack.Exchange(n + 1, m + n + 1); + + programCounter += 1; + break; + } case Instruction.LOG0: case Instruction.LOG1: case Instruction.LOG2: @@ -1784,10 +1951,31 @@ private CallResult ExecuteCode= 256UL) { - stack.PopLimbo(); + if (!stack.PopLimbo()) goto StackUnderflow; stack.PushZero(); } else @@ -1873,7 +2061,7 @@ private CallResult ExecuteCode= 256) { - stack.PopLimbo(); + if (!stack.PopLimbo()) goto StackUnderflow; stack.PushZero(); } else @@ -1922,15 +2110,48 @@ private CallResult ExecuteCode code = _state.GetCode(address); + if (IsEof(code, out _)) + { + stack.PushBytes(EofHash256); + break; + } + } + + stack.PushBytes(_state.GetCodeHash(address).Bytes); + } } break; @@ -2005,6 +2226,229 @@ private CallResult ExecuteCode 0) + { + if (!UpdateGas(GasCostOf.RJump, ref gasAvailable)) goto OutOfGas; + short offset = codeSection.Slice(programCounter, TWO_BYTE_LENGTH).ReadEthInt16(); + programCounter += TWO_BYTE_LENGTH + offset; + break; + } + goto InvalidInstruction; + } + case Instruction.RJUMPI: + { + if (spec.IsEofEnabled && env.CodeInfo.Version > 0) + { + if (!UpdateGas(GasCostOf.RJumpi, ref gasAvailable)) goto OutOfGas; + Span condition = stack.PopWord256(); + short offset = codeSection.Slice(programCounter, TWO_BYTE_LENGTH).ReadEthInt16(); + if (!condition.SequenceEqual(BytesZero32)) + { + programCounter += offset; + } + programCounter += TWO_BYTE_LENGTH; + break; + } + goto InvalidInstruction; + } + case Instruction.RJUMPV: + { + if (spec.IsEofEnabled && env.CodeInfo.Version > 0) + { + if (!UpdateGas(GasCostOf.RJumpv, ref gasAvailable)) goto OutOfGas; + + stack.PopUInt256(out a); + var count = codeSection[programCounter] + 1; + var immediates = (ushort)(count * TWO_BYTE_LENGTH + ONE_BYTE_LENGTH); + if (a < count) + { + int case_v = programCounter + ONE_BYTE_LENGTH + (int)a * TWO_BYTE_LENGTH; + int offset = codeSection.Slice(case_v, TWO_BYTE_LENGTH).ReadEthInt16(); + programCounter += offset; + } + programCounter += immediates; + break; + } + goto InvalidInstruction; + } + case Instruction.CALLF: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + { + goto InvalidInstruction; + } + + if (!UpdateGas(GasCostOf.Callf, ref gasAvailable)) goto OutOfGas; + var index = (int)codeSection.Slice(programCounter, TWO_BYTE_LENGTH).ReadEthUInt16(); + (int inputCount, _, int maxStackHeight) = env.CodeInfo.GetSectionMetadata(index); + + if (Eof1.MAX_STACK_HEIGHT - maxStackHeight + inputCount < stack.Head) + { + goto StackOverflow; + } + + if (vmState.ReturnStackHead == Eof1.RETURN_STACK_MAX_HEIGHT) + goto InvalidSubroutineEntry; + + vmState.ReturnStack[vmState.ReturnStackHead++] = new EvmState.ReturnState + { + Index = sectionIndex, + Height = stack.Head - inputCount, + Offset = programCounter + TWO_BYTE_LENGTH + }; + + sectionIndex = index; + programCounter = env.CodeInfo.CodeSectionOffset(index).Start; + break; + } + + case Instruction.JUMPF: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + { + goto InvalidInstruction; + } + + if (!UpdateGas(GasCostOf.Jumpf, ref gasAvailable)) goto OutOfGas; + var index = (int)codeSection.Slice(programCounter, TWO_BYTE_LENGTH).ReadEthUInt16(); + (int inputCount, _, int maxStackHeight) = env.CodeInfo.GetSectionMetadata(index); + + if (Eof1.MAX_STACK_HEIGHT - maxStackHeight + inputCount < stack.Head) + { + goto StackOverflow; + } + + sectionIndex = index; + programCounter = env.CodeInfo.CodeSectionOffset(index).Start; + break; + } + case Instruction.RETF: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + { + goto InvalidInstruction; + } + + if (!UpdateGas(GasCostOf.Retf, ref gasAvailable)) goto OutOfGas; + (_, int outputCount, _) = env.CodeInfo.GetSectionMetadata(sectionIndex); + + var stackFrame = vmState.ReturnStack[--vmState.ReturnStackHead]; + sectionIndex = stackFrame.Index; + programCounter = stackFrame.Offset; + break; + } + case Instruction.EXTCALL: + case Instruction.EXTDELEGATECALL: + case Instruction.EXTSTATICCALL: + { + exceptionType = InstructionEofCall(vmState, ref stack, ref gasAvailable, spec, instruction, out returnData); + if (exceptionType != EvmExceptionType.None) goto ReturnFailure; + + if (returnData is null) + { + break; + } + if (ReferenceEquals(returnData, CallResult.BoxedEmpty)) + { + // Result pushed to stack + continue; + } + + goto DataReturn; + } + case Instruction.RETURNCONTRACT: + { + if (!spec.IsEofEnabled || !vmState.ExecutionType.IsAnyCreateEof()) + goto InvalidInstruction; + + if (!UpdateGas(GasCostOf.ReturnContract, ref gasAvailable)) goto OutOfGas; + + byte sectionIdx = codeSection[programCounter++]; + ReadOnlyMemory deployCode = env.CodeInfo.ContainerSection[(Range)env.CodeInfo.ContainerSectionOffset(sectionIdx)]; + EofCodeInfo deploycodeInfo = (EofCodeInfo)CodeInfoFactory.CreateCodeInfo(deployCode, spec, EvmObjectFormat.ValidationStrategy.ExractHeader); + + stack.PopUInt256(out a); + stack.PopUInt256(out b); + ReadOnlyMemory auxData = ReadOnlyMemory.Empty; + + if (!UpdateMemoryCost(vmState, ref gasAvailable, in a, b)) goto OutOfGas; + + int projectedNewSize = (int)b + deploycodeInfo.DataSection.Length; + if (projectedNewSize < deploycodeInfo.EofContainer.Header.DataSection.Size || projectedNewSize > UInt16.MaxValue) + { + goto AccessViolation; + } + + auxData = vmState.Memory.Load(a, b); + + UpdateCurrentState(vmState, programCounter, gasAvailable, stack.Head, sectionIndex); + return new CallResult(deploycodeInfo, auxData, null, env.CodeInfo.Version); + } + case Instruction.DATASIZE: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + goto InvalidInstruction; + + if (!UpdateGas(GasCostOf.DataSize, ref gasAvailable)) goto OutOfGas; + + stack.PushUInt32(dataSection.Length); + break; + } + case Instruction.DATALOAD: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + goto InvalidInstruction; + + if (!UpdateGas(GasCostOf.DataLoad, ref gasAvailable)) goto OutOfGas; + + stack.PopUInt256(out a); + ZeroPaddedSpan zpbytes = dataSection.SliceWithZeroPadding(a, 32); + stack.PushBytes(zpbytes); + break; + } + case Instruction.DATALOADN: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + goto InvalidInstruction; + + if (!UpdateGas(GasCostOf.DataLoadN, ref gasAvailable)) goto OutOfGas; + + var offset = codeSection.Slice(programCounter, TWO_BYTE_LENGTH).ReadEthUInt16(); + ZeroPaddedSpan zpbytes = dataSection.SliceWithZeroPadding(offset, 32); + stack.PushBytes(zpbytes); + + programCounter += TWO_BYTE_LENGTH; + break; + } + case Instruction.DATACOPY: + { + if (!spec.IsEofEnabled || env.CodeInfo.Version == 0) + goto InvalidInstruction; + + stack.PopUInt256(out UInt256 memOffset); + stack.PopUInt256(out UInt256 offset); + stack.PopUInt256(out UInt256 size); + + if (!UpdateGas(GasCostOf.DataCopy + GasCostOf.Memory * EvmPooledMemory.Div32Ceiling(in size), ref gasAvailable)) + goto OutOfGas; + + if (size > UInt256.Zero) + { + if (!UpdateMemoryCost(vmState, ref gasAvailable, in memOffset, size)) + goto OutOfGas; + + ZeroPaddedSpan dataSectionSlice = dataSection.SliceWithZeroPadding(offset, (int)size); + vmState.Memory.Save(in memOffset, dataSectionSlice); + if (_txTracer.IsTracingInstructions) + { + _txTracer.ReportMemoryChange((long)memOffset, dataSectionSlice.ToArray().AsSpan()); + } + } + + break; + } default: { goto InvalidInstruction; @@ -2030,23 +2474,23 @@ private CallResult ExecuteCode(gasAvailable, exceptionType); - + InvalidSubroutineEntry: + exceptionType = EvmExceptionType.InvalidSubroutineEntry; + goto ReturnFailure; + StackOverflow: + exceptionType = EvmExceptionType.StackOverflow; + goto ReturnFailure; [DoesNotReturn] static void ThrowOperationCanceledException() => throw new OperationCanceledException("Cancellation Requested"); @@ -2078,8 +2527,15 @@ static void ThrowOperationCanceledException() => private void InstructionExtCodeSize(Address address, ref EvmStack stack, ICodeInfoRepository codeInfoRepository, IReleaseSpec spec) where TTracingInstructions : struct, IIsTracing { ReadOnlyMemory accountCode = codeInfoRepository.GetCachedCodeInfo(_state, address, spec).MachineCode; - UInt256 result = (UInt256)accountCode.Span.Length; - stack.PushUInt256(in result); + if (spec.IsEofEnabled && IsEof(accountCode, out _)) + { + stack.PushUInt256(2); + } + else + { + UInt256 result = (UInt256)accountCode.Length; + stack.PushUInt256(in result); + } } [SkipLocalsInit] @@ -2088,11 +2544,11 @@ private EvmExceptionType InstructionCall( where TTracingInstructions : struct, IIsTracing where TTracingRefunds : struct, IIsTracing { + Metrics.IncrementCalls(); + returnData = null; ref readonly ExecutionEnvironment env = ref vmState.Env; - Metrics.IncrementCalls(); - if (instruction == Instruction.DELEGATECALL && !spec.DelegateCallEnabled || instruction == Instruction.STATICCALL && !spec.StaticCallEnabled) return EvmExceptionType.BadInstruction; @@ -2156,7 +2612,7 @@ private EvmExceptionType InstructionCall( !UpdateMemoryCost(vmState, ref gasAvailable, in outputOffset, outputLength) || !UpdateGas(gasExtra, ref gasAvailable)) return EvmExceptionType.OutOfGas; - CodeInfo codeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); + ICodeInfo codeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); codeInfo.AnalyseInBackgroundIfRequired(); if (spec.Use63Over64Rule) @@ -2272,6 +2728,176 @@ void TraceCallDetails(Address codeSource, ref UInt256 callValue, ref UInt256 tra } } + [SkipLocalsInit] + private EvmExceptionType InstructionEofCall(EvmState vmState, ref EvmStack stack, ref long gasAvailable, IReleaseSpec spec, + Instruction instruction, out object returnData) + where TTracingInstructions : struct, IIsTracing + where TTracingRefunds : struct, IIsTracing + { + Metrics.IncrementCalls(); + + const int MIN_RETAINED_GAS = 5000; + + returnData = null; + ref readonly ExecutionEnvironment env = ref vmState.Env; + + // Instruction is undefined in legacy code and only available in EOF + if (!spec.IsEofEnabled || + env.CodeInfo.Version == 0 || + (instruction == Instruction.EXTDELEGATECALL && !spec.DelegateCallEnabled) || + (instruction == Instruction.EXTSTATICCALL && !spec.StaticCallEnabled)) + return EvmExceptionType.BadInstruction; + + // 1. Pop required arguments from stack, halt with exceptional failure on stack underflow. + stack.PopWord256(out Span targetBytes); + stack.PopUInt256(out UInt256 dataOffset); + stack.PopUInt256(out UInt256 dataLength); + + UInt256 transferValue; + UInt256 callValue; + switch (instruction) + { + case Instruction.EXTSTATICCALL: + transferValue = UInt256.Zero; + callValue = UInt256.Zero; + break; + case Instruction.EXTDELEGATECALL: + transferValue = UInt256.Zero; + callValue = env.Value; + break; + default: // Instruction.EXTCALL + stack.PopUInt256(out transferValue); + callValue = transferValue; + break; + } + + // 3. If value is non-zero: + // a: Halt with exceptional failure if the current frame is in static-mode. + if (vmState.IsStatic && !transferValue.IsZero) return EvmExceptionType.StaticCallViolation; + // b. Charge CALL_VALUE_COST gas. + if (instruction == Instruction.EXTCALL && !transferValue.IsZero && !UpdateGas(GasCostOf.CallValue, ref gasAvailable)) return EvmExceptionType.OutOfGas; + + // 4. If target_address has any of the high 12 bytes set to a non-zero value + // (i.e. it does not contain a 20-byte address) + if (!targetBytes[0..12].IsZero()) + { + // then halt with an exceptional failure. + return EvmExceptionType.AddressOutOfRange; + } + + Address caller = instruction == Instruction.EXTDELEGATECALL ? env.Caller : env.ExecutingAccount; + Address codeSource = new Address(targetBytes[12..].ToArray()); + Address target = instruction == Instruction.EXTDELEGATECALL + ? env.ExecutingAccount + : codeSource; + + // 5. Perform (and charge for) memory expansion using [input_offset, input_size]. + if (!UpdateMemoryCost(vmState, ref gasAvailable, in dataOffset, in dataLength)) return EvmExceptionType.OutOfGas; + // 1. Charge WARM_STORAGE_READ_COST (100) gas. + // 6. If target_address is not in the warm_account_list, charge COLD_ACCOUNT_ACCESS - WARM_STORAGE_READ_COST (2500) gas. + if (!ChargeAccountAccessGas(ref gasAvailable, vmState, codeSource, false, spec)) return EvmExceptionType.OutOfGas; + + if ((!spec.ClearEmptyAccountWhenTouched && !_state.AccountExists(codeSource)) + || (spec.ClearEmptyAccountWhenTouched && transferValue != 0 && _state.IsDeadAccount(codeSource))) + { + // 7. If target_address is not in the state and the call configuration would result in account creation, + // charge ACCOUNT_CREATION_COST (25000) gas. (The only such case in this EIP is if value is non-zero.) + if (!UpdateGas(GasCostOf.NewAccount, ref gasAvailable)) return EvmExceptionType.OutOfGas; + } + + // 8. Calculate the gas available to callee as caller’s remaining gas reduced by max(floor(gas/64), MIN_RETAINED_GAS). + long callGas = gasAvailable - Math.Max(gasAvailable / 64, MIN_RETAINED_GAS); + + // 9. Fail with status code 1 returned on stack if any of the following is true (only gas charged until this point is consumed): + // a: Gas available to callee at this point is less than MIN_CALLEE_GAS. + // b: Balance of the current account is less than value. + // c: Current call stack depth equals 1024. + if (callGas < GasCostOf.CallStipend || + (!transferValue.IsZero && _state.GetBalance(env.ExecutingAccount) < transferValue) || + env.CallDepth >= MaxCallDepth) + { + returnData = CallResult.BoxedEmpty; + _returnDataBuffer = Array.Empty(); + stack.PushOne(); + + if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace("FAIL - call depth"); + if (typeof(TTracingInstructions) == typeof(IsTracing)) + { + // very specific for Parity trace, need to find generalization - very peculiar 32 length... + ReadOnlyMemory memoryTrace = vmState.Memory.Inspect(in dataOffset, 32); + _txTracer.ReportMemoryChange(dataOffset, memoryTrace.Span); + _txTracer.ReportOperationRemainingGas(gasAvailable); + _txTracer.ReportOperationError(EvmExceptionType.NotEnoughBalance); + _txTracer.ReportGasUpdateForVmTrace(callGas, gasAvailable); + } + + return EvmExceptionType.None; + } + + if (typeof(TLogger) == typeof(IsTracing)) + { + _logger.Trace($"caller {caller}"); + _logger.Trace($"target {codeSource}"); + _logger.Trace($"transferValue {transferValue}"); + _logger.Trace($"callValue {callValue}"); + } + + ICodeInfo targetCodeInfo = vmState.Env.TxExecutionContext.CodeInfoRepository.GetCachedCodeInfo(_state, codeSource, spec); + targetCodeInfo.AnalyseInBackgroundIfRequired(); + + if (instruction is Instruction.EXTDELEGATECALL + && targetCodeInfo.Version == 0) + { + // https://github.com/ipsilon/eof/blob/main/spec/eof.md#new-behavior + // EXTDELEGATECALL to a non-EOF contract (legacy contract, EOA, empty account) is disallowed, + // and it returns 1 (same as when the callee frame reverts) to signal failure. + // Only initial gas cost of EXTDELEGATECALL is consumed (similarly to the call depth check) + // and the target address still becomes warm. + returnData = CallResult.BoxedEmpty; + _returnDataBuffer = Array.Empty(); + stack.PushOne(); + return EvmExceptionType.None; + } + + // 10. Perform the call with the available gas and configuration. + if (!UpdateGas(callGas, ref gasAvailable)) return EvmExceptionType.OutOfGas; + + ReadOnlyMemory callData = vmState.Memory.Load(in dataOffset, dataLength); + + Snapshot snapshot = _state.TakeSnapshot(); + _state.SubtractFromBalance(caller, transferValue, spec); + + ExecutionEnvironment callEnv = new + ( + txExecutionContext: in env.TxExecutionContext, + callDepth: env.CallDepth + 1, + caller: caller, + codeSource: codeSource, + executingAccount: target, + transferValue: transferValue, + value: callValue, + inputData: callData, + codeInfo: targetCodeInfo + ); + if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"Tx call gas {callGas}"); + + ExecutionType executionType = GetCallExecutionType(instruction, env.IsPostMerge()); + returnData = new EvmState( + callGas, + callEnv, + executionType, + isTopLevel: false, + snapshot, + (long)0, + (long)0, + isStatic: instruction == Instruction.EXTSTATICCALL || vmState.IsStatic, + vmState, + isContinuation: false, + isCreateOnPreExistingAccount: false); + + return EvmExceptionType.None; + } + [SkipLocalsInit] private static EvmExceptionType InstructionRevert(EvmState vmState, ref EvmStack stack, ref long gasAvailable, out object returnData) where TTracing : struct, IIsTracing @@ -2355,6 +2981,140 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref return EvmExceptionType.None; } + [SkipLocalsInit] + private (EvmExceptionType exceptionType, EvmState? callState) InstructionEofCreate(EvmState vmState, ref int pc, ref ReadOnlySpan codeSection, ref EvmStack stack, ref long gasAvailable, IReleaseSpec spec, Instruction instruction) + where TTracing : struct, IIsTracing + { + ref readonly ExecutionEnvironment env = ref vmState.Env; + EofCodeInfo container = env.CodeInfo as EofCodeInfo; + var currentContext = ExecutionType.EOFCREATE; + + // 1 - deduct TX_CREATE_COST gas + if (!UpdateGas(GasCostOf.TxCreate, ref gasAvailable)) + return (EvmExceptionType.OutOfGas, null); + + // 2 - read immediate operand initcontainer_index, encoded as 8-bit unsigned value + int initcontainerIndex = codeSection[pc++]; + + // 3 - pop value, salt, input_offset, input_size from the operand stack + // no stack checks becaue EOF guarantees no stack undeflows + stack.PopUInt256(out UInt256 value); + stack.PopWord256(out Span salt); + stack.PopUInt256(out UInt256 dataOffset); + stack.PopUInt256(out UInt256 dataSize); + + // 4 - perform (and charge for) memory expansion using [input_offset, input_size] + if (!UpdateMemoryCost(vmState, ref gasAvailable, in dataOffset, dataSize)) return (EvmExceptionType.OutOfGas, null); + + // 5 - load initcode EOF subcontainer at initcontainer_index in the container from which EOFCREATE is executed + // let initcontainer be that EOF container, and initcontainer_size its length in bytes declared in its parent container header + ReadOnlySpan initContainer = container.ContainerSection.Span[(Range)container.ContainerSectionOffset(initcontainerIndex).Value]; + // Eip3860 + if (spec.IsEip3860Enabled) + { + //if (!UpdateGas(GasCostOf.InitCodeWord * numberOfWordInInitcode, ref gasAvailable)) + // return (EvmExceptionType.OutOfGas, null); + if (initContainer.Length > spec.MaxInitCodeSize) return (EvmExceptionType.OutOfGas, null); + } + + // 6 - deduct GAS_KECCAK256_WORD * ((initcontainer_size + 31) // 32) gas (hashing charge) + long numberOfWordsInInitCode = EvmPooledMemory.Div32Ceiling((UInt256)initContainer.Length); + long hashCost = GasCostOf.Sha3Word * numberOfWordsInInitCode; + if (!UpdateGas(hashCost, ref gasAvailable)) + return (EvmExceptionType.OutOfGas, null); + + // 7 - check that current call depth is below STACK_DEPTH_LIMIT and that caller balance is enough to transfer value + // in case of failure return 0 on the stack, caller’s nonce is not updated and gas for initcode execution is not consumed. + UInt256 balance = _state.GetBalance(env.ExecutingAccount); + if (env.CallDepth >= MaxCallDepth || value > balance) + { + // TODO: need a test for this + _returnDataBuffer = Array.Empty(); + stack.PushZero(); + return (EvmExceptionType.None, null); + } + + // 8 - caller’s memory slice [input_offset:input_size] is used as calldata + Span calldata = vmState.Memory.LoadSpan(dataOffset, dataSize); + + // 9 - execute the container and deduct gas for execution. The 63/64th rule from EIP-150 applies. + long callGas = spec.Use63Over64Rule ? gasAvailable - gasAvailable / 64L : gasAvailable; + if (!UpdateGas(callGas, ref gasAvailable)) return (EvmExceptionType.OutOfGas, null); + + // 10 - increment sender account’s nonce + UInt256 accountNonce = _state.GetNonce(env.ExecutingAccount); + UInt256 maxNonce = ulong.MaxValue; + if (accountNonce >= maxNonce) + { + _returnDataBuffer = Array.Empty(); + stack.PushZero(); + return (EvmExceptionType.None, null); + } + _state.IncrementNonce(env.ExecutingAccount); + + // 11 - calculate new_address as keccak256(0xff || sender || salt || keccak256(initcontainer))[12:] + Address contractAddress = ContractAddress.From(env.ExecutingAccount, salt, initContainer); + if (spec.UseHotAndColdStorage) + { + // EIP-2929 assumes that warm-up cost is included in the costs of CREATE and CREATE2 + vmState.WarmUp(contractAddress); + } + + + if (typeof(TTracing) == typeof(IsTracing)) EndInstructionTrace(gasAvailable, vmState?.Memory.Size ?? 0); + // todo: === below is a new call - refactor / move + + Snapshot snapshot = _state.TakeSnapshot(); + + bool accountExists = _state.AccountExists(contractAddress); + + if (accountExists && contractAddress.IsNonZeroAccount(spec, vmState.Env.TxExecutionContext.CodeInfoRepository, _state)) + { + /* we get the snapshot before this as there is a possibility with that we will touch an empty account and remove it even if the REVERT operation follows */ + if (typeof(TLogger) == typeof(IsTracing)) _logger.Trace($"Contract collision at {contractAddress}"); + _returnDataBuffer = Array.Empty(); + stack.PushZero(); + return (EvmExceptionType.None, null); + } + + if (_state.IsDeadAccount(contractAddress)) + { + _state.ClearStorage(contractAddress); + } + + _state.SubtractFromBalance(env.ExecutingAccount, value, spec); + + + ICodeInfo codeinfo = CodeInfoFactory.CreateCodeInfo(initContainer.ToArray(), spec, EvmObjectFormat.ValidationStrategy.ExractHeader); + + ExecutionEnvironment callEnv = new + ( + txExecutionContext: in env.TxExecutionContext, + callDepth: env.CallDepth + 1, + caller: env.ExecutingAccount, + executingAccount: contractAddress, + codeSource: null, + codeInfo: codeinfo, + inputData: calldata.ToArray(), + transferValue: value, + value: value + ); + EvmState callState = new( + callGas, + callEnv, + currentContext, + false, + snapshot, + 0L, + 0L, + vmState.IsStatic, + vmState, + false, + accountExists); + + return (EvmExceptionType.None, callState); + } + [SkipLocalsInit] private (EvmExceptionType exceptionType, EvmState? callState) InstructionCreate(EvmState vmState, ref EvmStack stack, ref long gasAvailable, IReleaseSpec spec, Instruction instruction) where TTracing : struct, IIsTracing @@ -2438,8 +3198,22 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref vmState.WarmUp(contractAddress); } + // Do not add the initCode to the cache as it is + // pointing to data in this tx and will become invalid + // for another tx as returned to pool. + if (spec.IsEofEnabled && initCode.Span.StartsWith(EofValidator.MAGIC)) + { + _returnDataBuffer = Array.Empty(); + stack.PushZero(); + UpdateGasUp(callGas, ref gasAvailable); + return (EvmExceptionType.None, null); + } + _state.IncrementNonce(env.ExecutingAccount); + CodeInfoFactory.CreateInitCodeInfo(initCode.ToArray(), spec, out ICodeInfo codeinfo, out _); + codeinfo.AnalyseInBackgroundIfRequired(); + Snapshot snapshot = _state.TakeSnapshot(); bool accountExists = _state.AccountExists(contractAddress); @@ -2460,12 +3234,6 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref _state.SubtractFromBalance(env.ExecutingAccount, value, spec); - // Do not add the initCode to the cache as it is - // pointing to data in this tx and will become invalid - // for another tx as returned to pool. - CodeInfo codeInfo = new(initCode); - codeInfo.AnalyseInBackgroundIfRequired(); - ExecutionEnvironment callEnv = new ( txExecutionContext: in env.TxExecutionContext, @@ -2473,7 +3241,7 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref caller: env.ExecutingAccount, executingAccount: contractAddress, codeSource: null, - codeInfo: codeInfo, + codeInfo: codeinfo, inputData: default, transferValue: value, value: value @@ -2481,7 +3249,12 @@ private EvmExceptionType InstructionSelfDestruct(EvmState vmState, ref EvmState callState = new( callGas, callEnv, - instruction == Instruction.CREATE2 ? ExecutionType.CREATE2 : ExecutionType.CREATE, + instruction switch + { + Instruction.CREATE => ExecutionType.CREATE, + Instruction.CREATE2 => ExecutionType.CREATE2, + _ => throw new UnreachableException() + }, false, snapshot, 0L, @@ -2715,15 +3488,17 @@ private CallResult GetFailureReturn(long gasAvailable, Evm EvmExceptionType.StackUnderflow => CallResult.StackUnderflowException, EvmExceptionType.InvalidJumpDestination => CallResult.InvalidJumpDestination, EvmExceptionType.AccessViolation => CallResult.AccessViolationException, + EvmExceptionType.AddressOutOfRange => CallResult.InvalidAddressRange, _ => throw new ArgumentOutOfRangeException(nameof(exceptionType), exceptionType, "") }; } - private static void UpdateCurrentState(EvmState state, int pc, long gas, int stackHead) + private static void UpdateCurrentState(EvmState state, int pc, long gas, int stackHead, int sectionId) { state.ProgramCounter = pc; state.GasAvailable = gas; state.DataStackHead = stackHead; + state.FunctionIndex = sectionId; } private static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in UInt256 position, in UInt256 length) @@ -2733,7 +3508,7 @@ private static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in return memoryCost == 0L || UpdateGas(memoryCost, ref gasAvailable); } - private static bool Jump(in UInt256 jumpDest, ref int programCounter, in ExecutionEnvironment env) + private static bool Jump(CodeInfo codeinfo, in UInt256 jumpDest, ref int programCounter, in ExecutionEnvironment env) { if (jumpDest > int.MaxValue) { @@ -2743,7 +3518,7 @@ private static bool Jump(in UInt256 jumpDest, ref int programCounter, in Executi } int jumpDestInt = (int)jumpDest; - if (!env.CodeInfo.ValidateJump(jumpDestInt)) + if (!codeinfo.ValidateJump(jumpDestInt)) { // https://github.com/NethermindEth/nethermind/issues/140 // TODO: add a test, validating inside the condition was not covered by existing tests and fails on 61363 Ropsten @@ -2785,13 +3560,16 @@ private void EndInstructionTraceError(long gasAvailable, EvmExceptionType evmExc _txTracer.ReportOperationError(evmExceptionType); } - private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) => - instruction switch + private static ExecutionType GetCallExecutionType(Instruction instruction, bool isPostMerge = false) + => instruction switch { Instruction.CALL => ExecutionType.CALL, Instruction.DELEGATECALL => ExecutionType.DELEGATECALL, Instruction.STATICCALL => ExecutionType.STATICCALL, Instruction.CALLCODE => ExecutionType.CALLCODE, + Instruction.EXTCALL => ExecutionType.EOFCALL, + Instruction.EXTDELEGATECALL => ExecutionType.EOFDELEGATECALL, + Instruction.EXTSTATICCALL => ExecutionType.EOFSTATICCALL, _ => throw new NotSupportedException($"Execution type is undefined for {instruction.GetName(isPostMerge)}") }; } diff --git a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs index 082d5ad57fe..89cfa170ffe 100644 --- a/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Facade/OverridableCodeInfoRepository.cs @@ -15,12 +15,12 @@ namespace Nethermind.Facade; public class OverridableCodeInfoRepository(ICodeInfoRepository codeInfoRepository) : ICodeInfoRepository { - private readonly Dictionary _codeOverwrites = new(); + private readonly Dictionary _codeOverwrites = new(); - public CodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) + public ICodeInfo GetCachedCodeInfo(IWorldState worldState, Address codeSource, IReleaseSpec vmSpec, out Address? delegationAddress) { delegationAddress = null; - return _codeOverwrites.TryGetValue(codeSource, out CodeInfo result) + return _codeOverwrites.TryGetValue(codeSource, out ICodeInfo result) ? result : codeInfoRepository.GetCachedCodeInfo(worldState, codeSource, vmSpec); } @@ -32,7 +32,7 @@ public void SetCodeOverwrite( IWorldState worldState, IReleaseSpec vmSpec, Address key, - CodeInfo value, + ICodeInfo value, Address? redirectAddress = null) { if (redirectAddress is not null) @@ -46,9 +46,9 @@ public void SetCodeOverwrite( public void SetDelegation(IWorldState state, Address codeSource, Address authority, IReleaseSpec spec) => codeInfoRepository.SetDelegation(state, codeSource, authority, spec); - public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, [NotNullWhen(true)] out Address? delegatedAddress) => - codeInfoRepository.TryGetDelegation(worldState, address, out delegatedAddress); + public bool TryGetDelegation(IReadOnlyStateProvider worldState, Address address, IReleaseSpec vmSpec, [NotNullWhen(true)] out Address? delegatedAddress) => + codeInfoRepository.TryGetDelegation(worldState, address, vmSpec, out delegatedAddress); - public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address) => - codeInfoRepository.GetExecutableCodeHash(worldState, address); + public ValueHash256 GetExecutableCodeHash(IWorldState worldState, Address address, IReleaseSpec spec) => + codeInfoRepository.GetExecutableCodeHash(worldState, address, spec); } diff --git a/src/Nethermind/Nethermind.Specs.Test/MainnetSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/MainnetSpecProviderTests.cs index 20b6fbb1f51..184c3381eba 100644 --- a/src/Nethermind/Nethermind.Specs.Test/MainnetSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/MainnetSpecProviderTests.cs @@ -69,6 +69,15 @@ public void Prague_eips(long blockNumber, ulong timestamp, bool isEnabled) { _specProvider.GetSpec(new ForkActivation(blockNumber, timestamp)).Eip2935ContractAddress.Should().BeNull(); } + // EOF was once in Prague but moved to Osaka, so let's verify it's not enabled. + _specProvider.GetSpec(new ForkActivation(blockNumber, timestamp)).IsEofEnabled.Should().Be(false); + } + + [TestCase(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.PragueBlockTimestamp, false)] + [TestCase(MainnetSpecProvider.ParisBlockNumber, MainnetSpecProvider.OsakaBlockTimestamp, true)] + public void Osaka_eips(long blockNumber, ulong timestamp, bool isEnabled) + { + _specProvider.GetSpec(new ForkActivation(blockNumber, timestamp)).IsEofEnabled.Should().Be(isEnabled); } [Test] diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index 5394d1c475d..3a8a6d12050 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -166,6 +166,8 @@ public ulong Eip4844TransitionTimestamp public UInt256 ForkBaseFee => _spec.ForkBaseFee; public UInt256 BaseFeeMaxChangeDenominator => _spec.BaseFeeMaxChangeDenominator; public long ElasticityMultiplier => _spec.ElasticityMultiplier; + + public bool IsEofEnabled => _spec.IsEofEnabled; public bool IsEip6110Enabled => _spec.IsEip6110Enabled; public Address DepositContractAddress => _spec.DepositContractAddress; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs index 4252264aab1..34b80f63b40 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs @@ -127,6 +127,7 @@ public class ChainParameters public ulong? Eip2935TransitionTimestamp { get; set; } public Address Eip2935ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7692TransitionTimestamp { get; set; } public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs index bb7c9a7011e..ccc21fe79a5 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs @@ -257,6 +257,7 @@ private static ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long releaseSt releaseSpec.IsEip4788Enabled = (chainSpec.Parameters.Eip4788TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip4788ContractAddress = chainSpec.Parameters.Eip4788ContractAddress; releaseSpec.IsEip2935Enabled = (chainSpec.Parameters.Eip2935TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; + releaseSpec.IsEofEnabled = (chainSpec.Parameters.Eip7692TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.Eip2935ContractAddress = chainSpec.Parameters.Eip2935ContractAddress; releaseSpec.IsEip7702Enabled = (chainSpec.Parameters.Eip7702TransitionTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs index cc72eae71e9..305cc9c0677 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs @@ -143,6 +143,7 @@ bool GetForInnerPathExistence(KeyValuePair o) => Eip5656TransitionTimestamp = chainSpecJson.Params.Eip5656TransitionTimestamp, Eip6780TransitionTimestamp = chainSpecJson.Params.Eip6780TransitionTimestamp, Rip7212TransitionTimestamp = chainSpecJson.Params.Rip7212TransitionTimestamp, + Eip7692TransitionTimestamp = chainSpecJson.Params.Eip7692TransitionTimestamp, OpGraniteTransitionTimestamp = chainSpecJson.Params.OpGraniteTransitionTimestamp, Eip4788TransitionTimestamp = chainSpecJson.Params.Eip4788TransitionTimestamp, Eip7702TransitionTimestamp = chainSpecJson.Params.Eip7702TransitionTimestamp, diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs index f905249c4e8..a93b916bbed 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs @@ -156,6 +156,7 @@ internal class ChainSpecParamsJson public ulong? Eip7251TransitionTimestamp { get; set; } public Address Eip7251ContractAddress { get; set; } public ulong? Rip7212TransitionTimestamp { get; set; } + public ulong? Eip7692TransitionTimestamp { get; set; } public ulong? Eip7702TransitionTimestamp { get; set; } public ulong? OpGraniteTransitionTimestamp { get; set; } } diff --git a/src/Nethermind/Nethermind.Specs/Forks/19_Osaka.cs b/src/Nethermind/Nethermind.Specs/Forks/19_Osaka.cs new file mode 100644 index 00000000000..1061948e90f --- /dev/null +++ b/src/Nethermind/Nethermind.Specs/Forks/19_Osaka.cs @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Specs; + +namespace Nethermind.Specs.Forks; + +public class Osaka : Prague +{ + private static IReleaseSpec _instance; + + protected Osaka() + { + Name = "Osaka"; + IsEofEnabled = true; + } + + public new static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, () => new Osaka()); +} diff --git a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs index 4aa6c8d8c63..d9c519e05d7 100644 --- a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs @@ -49,7 +49,8 @@ public IReleaseSpec GetSpec(ForkActivation forkActivation) => { Timestamp: null } or { Timestamp: < ShanghaiBlockTimestamp } => Paris.Instance, { Timestamp: < CancunBlockTimestamp } => Shanghai.Instance, { Timestamp: < PragueBlockTimestamp } => Cancun.Instance, - _ => Prague.Instance + { Timestamp: < OsakaBlockTimestamp } => Prague.Instance, + _ => Osaka.Instance }; public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalDifficulty = null) @@ -90,7 +91,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD ShanghaiActivation, CancunActivation, PragueActivation, - //OsakaActivation + OsakaActivation, }; public static MainnetSpecProvider Instance { get; } = new(); diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index f138b747d3b..e1f94630c68 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -111,6 +111,8 @@ public Address Eip4788ContractAddress set => _eip4788ContractAddress = value; } + public bool IsEofEnabled { get; set; } + public bool IsEip6110Enabled { get; set; } private Address _depositContractAddress; diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index e8afceafeb8..5539d66fe54 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Caching; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Resettables; using Nethermind.Core.Specs; using Nethermind.Int256; @@ -211,7 +212,7 @@ Account GetThroughCacheCheckExists() UInt256 newBalance = isSubtracting ? account.Balance - balanceChange : account.Balance + balanceChange; Account changedAccount = account.WithChangedBalance(newBalance); - if (_logger.IsTrace) _logger.Trace($" Update {address} B {account.Balance} -> {newBalance} ({(isSubtracting ? "-" : "+")}{balanceChange})"); + if (_logger.IsTrace) _logger.Trace($" Update {address} B {account.Balance.ToHexString(skipLeadingZeros: true)} -> {newBalance.ToHexString(skipLeadingZeros: true)} ({(isSubtracting ? "-" : "+")}{balanceChange})"); PushUpdate(address, changedAccount); } @@ -260,7 +261,7 @@ public void IncrementNonce(Address address, UInt256 delta) } Account changedAccount = account.WithChangedNonce(account.Nonce + delta); - if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce} -> {changedAccount.Nonce}"); + if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce.ToHexString(skipLeadingZeros: true)} -> {changedAccount.Nonce.ToHexString(skipLeadingZeros: true)}"); PushUpdate(address, changedAccount); } @@ -274,7 +275,7 @@ public void DecrementNonce(Address address, UInt256 delta) } Account changedAccount = account.WithChangedNonce(account.Nonce - delta); - if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce} -> {changedAccount.Nonce}"); + if (_logger.IsTrace) _logger.Trace($" Update {address} N {account.Nonce.ToHexString(skipLeadingZeros: true)} -> {changedAccount.Nonce.ToHexString(skipLeadingZeros: true)}"); PushUpdate(address, changedAccount); } @@ -382,7 +383,7 @@ public void Restore(int snapshot) public void CreateAccount(Address address, in UInt256 balance, in UInt256 nonce = default) { _needsStateRootUpdate = true; - if (_logger.IsTrace) _logger.Trace($"Creating account: {address} with balance {balance} and nonce {nonce}"); + if (_logger.IsTrace) _logger.Trace($"Creating account: {address} with balance {balance.ToHexString(skipLeadingZeros: true)} and nonce {nonce.ToHexString(skipLeadingZeros: true)}"); Account account = (balance.IsZero && nonce.IsZero) ? Account.TotallyEmpty : new Account(nonce, balance); PushNew(address, account); } @@ -497,7 +498,7 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool { if (releaseSpec.IsEip158Enabled && change.Account.IsEmpty && !isGenesis) { - if (_logger.IsTrace) _logger.Trace($" Commit remove empty {change.Address} B = {change.Account.Balance} N = {change.Account.Nonce}"); + if (_logger.IsTrace) _logger.Trace($" Commit remove empty {change.Address} B = {change.Account.Balance.ToHexString(skipLeadingZeros: true)} N = {change.Account.Nonce.ToHexString(skipLeadingZeros: true)}"); SetState(change.Address, null); if (isTracing) { @@ -506,7 +507,7 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool } else { - if (_logger.IsTrace) _logger.Trace($" Commit update {change.Address} B = {change.Account.Balance} N = {change.Account.Nonce} C = {change.Account.CodeHash}"); + if (_logger.IsTrace) _logger.Trace($" Commit update {change.Address} B = {change.Account.Balance.ToHexString(skipLeadingZeros: true)} N = {change.Account.Nonce.ToHexString(skipLeadingZeros: true)} C = {change.Account.CodeHash}"); SetState(change.Address, change.Account); if (isTracing) { @@ -520,7 +521,7 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool { if (!releaseSpec.IsEip158Enabled || !change.Account.IsEmpty || isGenesis) { - if (_logger.IsTrace) _logger.Trace($" Commit create {change.Address} B = {change.Account.Balance} N = {change.Account.Nonce}"); + if (_logger.IsTrace) _logger.Trace($" Commit create {change.Address} B = {change.Account.Balance.ToHexString(skipLeadingZeros: true)} N = {change.Account.Nonce.ToHexString(skipLeadingZeros: true)}"); SetState(change.Address, change.Account); if (isTracing) { diff --git a/src/Nethermind/Nethermind.Test.Runner/EofTestsRunner.cs b/src/Nethermind/Nethermind.Test.Runner/EofTestsRunner.cs new file mode 100644 index 00000000000..b17fe4ba9d0 --- /dev/null +++ b/src/Nethermind/Nethermind.Test.Runner/EofTestsRunner.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Ethereum.Test.Base; +using Ethereum.Test.Base.Interfaces; + +namespace Nethermind.Test.Runner; + +public class EofTestsRunner(ITestSourceLoader testsSource, string? filter) : EofTestBase, IEofTestRunner +{ + private readonly ConsoleColor _defaultColour = Console.ForegroundColor; + private readonly ITestSourceLoader _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); + + public IEnumerable RunTests() + { + List testResults = new(); + var tests = (IEnumerable)_testsSource.LoadTests(); + foreach (EofTest test in tests) + { + if (filter is not null && !Regex.Match(test.Name, $"^({filter})").Success) + continue; + Setup(); + + Console.Write($"{test.Name,-120} "); + if (test.LoadFailure is not null) + { + WriteRed(test.LoadFailure); + testResults.Add(new EthereumTestResult(test.Name, test.LoadFailure)); + } + else + { + var result = new EthereumTestResult(test.Name, "Osaka", RunTest(test)); + testResults.Add(result); + if (result.Pass) + WriteGreen("PASS"); + else + WriteRed("FAIL"); + } + } + + return testResults; + } + + private void WriteRed(string text) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(text); + Console.ForegroundColor = _defaultColour; + } + + private void WriteGreen(string text) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine(text); + Console.ForegroundColor = _defaultColour; + } +} diff --git a/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj b/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj index fd96da1e534..0bf19eb1df5 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj +++ b/src/Nethermind/Nethermind.Test.Runner/Nethermind.Test.Runner.csproj @@ -26,6 +26,9 @@ PreserveNewest + + PreserveNewest + Always diff --git a/src/Nethermind/Nethermind.Test.Runner/Program.cs b/src/Nethermind/Nethermind.Test.Runner/Program.cs index ccb60229cb5..ec1ed824372 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Test.Runner/Program.cs @@ -23,6 +23,9 @@ public class Options [Option('b', "blockTest", Required = false, HelpText = "Set test as blockTest. if not, it will be by default assumed a state test.")] public bool BlockTest { get; set; } + [Option('e', "eofTest", Required = false, HelpText = "Set test as eofTest. if not, it will be by default assumed a state test.")] + public bool EofTest { get; set; } + [Option('t', "trace", Required = false, HelpText = "Set to always trace (by default traces are only generated for failing tests). [Only for State Test]")] public bool TraceAlways { get; set; } @@ -64,8 +67,11 @@ private static async Task Run(Options options) while (!string.IsNullOrWhiteSpace(input)) { + if (options.BlockTest) await RunBlockTest(input, source => new BlockchainTestsRunner(source, options.Filter)); + else if (options.EofTest) + RunEofTest(input, source => new EofTestsRunner(source, options.Filter)); else RunStateTest(input, source => new StateTestsRunner(source, whenTrace, !options.ExcludeMemory, !options.ExcludeStack, options.Filter)); if (!options.Stdin) @@ -86,6 +92,14 @@ private static async Task RunBlockTest(string path, Func testRunnerBuilder) + { + ITestSourceLoader source = Path.HasExtension(path) + ? new TestsSourceLoader(new LoadEofTestFileStrategy(), path) + : new TestsSourceLoader(new LoadEofTestsStrategy(), path); + testRunnerBuilder(source).RunTests(); + } + private static void RunStateTest(string path, Func testRunnerBuilder) { ITestSourceLoader source = Path.HasExtension(path) diff --git a/src/Nethermind/Nethermind.Test.Runner/Properties/launchSettings.json b/src/Nethermind/Nethermind.Test.Runner/Properties/launchSettings.json index c2bffc4ce24..14ada1706b6 100644 --- a/src/Nethermind/Nethermind.Test.Runner/Properties/launchSettings.json +++ b/src/Nethermind/Nethermind.Test.Runner/Properties/launchSettings.json @@ -8,6 +8,10 @@ "commandName": "Project", "commandLineArgs": "-b -i blockchainTest1.json" }, + "EOF Test": { + "commandName": "Project", + "commandLineArgs": "-e -i eoftest1.json" + }, "Regex State Test": { "commandName": "Project", "commandLineArgs": "-i statetest1.json -f \"randomStatetestmartin-Wed_10_02_29-14338-([0-9]+)\"" diff --git a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs index 2d00f475f49..505d822302b 100644 --- a/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs +++ b/src/Nethermind/Nethermind.Test.Runner/StateTestTxTracer.cs @@ -54,7 +54,7 @@ public void StartOperation(int pc, Instruction opcode, long gas, in ExecutionEnv bool isPostMerge = env.IsPostMerge(); _gasAlreadySetForCurrentOp = false; _traceEntry = new StateTestTxTraceEntry(); - _traceEntry.Pc = pc; + _traceEntry.Pc = pc + env.CodeInfo.PcOffset(); _traceEntry.Operation = (byte)opcode; _traceEntry.OperationName = opcode.GetName(isPostMerge); _traceEntry.Gas = gas; diff --git a/src/Nethermind/Nethermind.Test.Runner/eoftest1.json b/src/Nethermind/Nethermind.Test.Runner/eoftest1.json new file mode 100644 index 00000000000..b9688e00a41 --- /dev/null +++ b/src/Nethermind/Nethermind.Test.Runner/eoftest1.json @@ -0,0 +1,23 @@ +{ + "tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_eof_example.py::test_eof_example[fork_CancunEIP7692-eof_test]": { + "vectors": { + "0": { + "code": "0xef0001010010020004000500060008000204000100008000010100000100010003020300035fe300010050e3000250e43080e300035050e480e4ef", + "results": { + "Prague": { + "result": true + } + } + } + }, + "_info": { + "hash": "0xf91c0d32c0e59772417a3e223b57590c91593cb17369253549b946f971c5fcbf", + "comment": "`execution-spec-tests` generated test", + "filling-transition-tool": "evmone-t8n 0.12.0-6+commit.2d20cc63.dirty", + "description": "Test function documentation:\n\n Example of python EOF classes", + "url": "https://github.com/ethereum/execution-spec-tests/blob/99d4c17de5888bb5343c767ef34b2735d2469770/tests/prague/eip7692_eof_v1/eip3540_eof_v1/test_eof_example.py#L19", + "reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3540.md", + "reference-spec-version": "8dcb0a8c1c0102c87224308028632cc986a61183" + } + } +} diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 2ff2a442f5b..67b86c5e30d 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -473,7 +473,7 @@ public virtual void Set(ReadOnlySpan rawKey, in CappedArray value) void Trace(in ReadOnlySpan rawKey, in CappedArray value) { - _logger.Trace($"{(value.Length == 0 ? $"Deleting {rawKey.ToHexString()}" : $"Setting {rawKey.ToHexString()} = {value.AsSpan().ToHexString()}")}"); + _logger.Trace($"{(value.Length == 0 ? $"Deleting {rawKey.ToHexString(withZeroX: true)}" : $"Setting {rawKey.ToHexString(withZeroX: true)} = {value.AsSpan().ToHexString(withZeroX: true)}")}"); } [DoesNotReturn] diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs index 1eb06f8dcc8..09293813006 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DeployedCodeFilter.cs @@ -15,9 +15,10 @@ internal sealed class DeployedCodeFilter(IReadOnlyStateProvider worldState, ICod { public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { - return worldState.IsInvalidContractSender(specProvider.GetCurrentHeadSpec(), + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + return worldState.IsInvalidContractSender(spec, tx.SenderAddress!, - () => codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) + () => codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, spec, out _)) ? AcceptTxResult.SenderIsContract : AcceptTxResult.Accepted; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs index f9077d6c966..1a7dc9e37f1 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/IsValidTxSender.cs @@ -11,8 +11,9 @@ public class IsValidTxSender(IWorldState worldState, ICodeInfoRepository codeInf { public bool IsValid(Address sender) { - return specProvider.GetCurrentHeadSpec().IsEip3607Enabled + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + return spec.IsEip3607Enabled && worldState.HasCode(sender) - && (!specProvider.GetCurrentHeadSpec().IsEip7702Enabled || !codeInfoRepository.TryGetDelegation(worldState, sender, out _)); + && (!spec.IsEip7702Enabled || !codeInfoRepository.TryGetDelegation(worldState, sender, spec, out _)); } } diff --git a/src/tests b/src/tests index 8c215d6b56f..27a008d13ec 160000 --- a/src/tests +++ b/src/tests @@ -1 +1 @@ -Subproject commit 8c215d6b56fed36501d04c165093357f102de2ac +Subproject commit 27a008d13ecb0718b300e8f055cc73357a11e96a