diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip4844Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip4844Tests.cs new file mode 100644 index 00000000000..01365eb989b --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/Eip4844Tests.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Specs; +using NUnit.Framework; +using System; +using Nethermind.Int256; +using System.Linq; + +namespace Nethermind.Evm.Test; + +[TestFixture] +public class Eip4844Tests : VirtualMachineTestsBase +{ + protected override long BlockNumber => MainnetSpecProvider.GrayGlacierBlockNumber; + protected override ulong Timestamp => MainnetSpecProvider.ShardingForkBlockTimestamp; + + [TestCase(0, 0, Description = "Should return 0 when no hashes")] + [TestCase(1, 1, Description = "Should return 0 when out of range")] + [TestCase(2, 1, Description = "Should return 0 when way out of range")] + [TestCase(0, 1, Description = "Should return hash, when exists")] + [TestCase(1, 3, Description = "Should return hash, when exists")] + public void Test_datahash_index_in_range(int index, int datahashesCount) + { + byte[][] hashes = new byte[datahashesCount][]; + for (int i = 0; i < datahashesCount; i++) + { + hashes[i] = new byte[32]; + for (int n = 0; n < datahashesCount; n++) + { + hashes[i][n] = (byte)((i * 3 + 10 * 7) % 256); + } + } + byte[] expectedOutput = datahashesCount > index ? hashes[index] : new byte[32]; + + // Cost of transaction call + PUSH1 x4 + MSTORE (entry cost + 1 memory cell used) + const long GasCostOfCallingWrapper = GasCostOf.Transaction + GasCostOf.VeryLow * 5 + GasCostOf.Memory; + + byte[] code = Prepare.EvmCode + .PushData(new UInt256((ulong)index)) + .DATAHASH() + .MSTORE(0) + .Return(32, 0) + .Done; + + TestAllTracerWithOutput result = Execute(BlockNumber, 50000, code, blobVersionedHashes: hashes, timestamp: Timestamp); + + result.StatusCode.Should().Be(StatusCode.Success); + result.ReturnValue.SequenceEqual(expectedOutput); + AssertGas(result, GasCostOfCallingWrapper + GasCostOf.DataHash); + } + + protected override TestAllTracerWithOutput CreateTracer() + { + TestAllTracerWithOutput tracer = base.CreateTracer(); + tracer.IsTracingAccess = false; + return tracer; + } +} diff --git a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs index 11d39fefc86..4b703a9ae09 100644 --- a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs @@ -104,7 +104,7 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase ShanghaiInstructions.Union( new Instruction[] { - // TODO: Add DATAHASH + Instruction.DATAHASH, } ).ToArray(); diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index 8e1d2fb8788..a2a65114d70 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -110,9 +110,9 @@ protected TestAllTracerWithOutput Execute(params byte[] code) return tracer; } - protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, long blockGasLimit = DefaultBlockGasLimit, ulong timestamp = 0) + protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, long blockGasLimit = DefaultBlockGasLimit, ulong timestamp = 0, byte[][] blobVersionedHashes = null) { - (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, blockGasLimit: blockGasLimit, timestamp: timestamp); + (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, blockGasLimit: blockGasLimit, timestamp: timestamp, blobVersionedHashes: blobVersionedHashes); TestAllTracerWithOutput tracer = CreateTracer(); _processor.Execute(transaction, block.Header, tracer); return tracer; @@ -125,7 +125,8 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ SenderRecipientAndMiner senderRecipientAndMiner = null, int value = 1, long blockGasLimit = DefaultBlockGasLimit, - ulong timestamp = 0) + ulong timestamp = 0, + byte[][] blobVersionedHashes = null) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; TestState.CreateAccount(senderRecipientAndMiner.Sender, 100.Ether()); @@ -144,6 +145,7 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[ .WithGasLimit(gasLimit) .WithGasPrice(1) .WithValue(value) + .WithBlobHashes(blobVersionedHashes) .To(senderRecipientAndMiner.Recipient) .SignedAndResolved(_ethereumEcdsa, senderRecipientAndMiner.SenderKey) .TestObject; diff --git a/src/Nethermind/Nethermind.Evm/ByteCodeBuilderExtensions.cs b/src/Nethermind/Nethermind.Evm/ByteCodeBuilderExtensions.cs index a8b8749e325..c7ac623fc7f 100644 --- a/src/Nethermind/Nethermind.Evm/ByteCodeBuilderExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/ByteCodeBuilderExtensions.cs @@ -1,15 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections; -using System.Collections.Generic; using Nethermind.Core; -using Nethermind.Core.Collections; -using Nethermind.Core.Extensions; using Nethermind.Int256; -using Nethermind.Serialization.Json; -using Org.BouncyCastle.Asn1.Mozilla; namespace Nethermind.Evm { @@ -92,6 +85,8 @@ public static Prepare SELFBALANCE(this Prepare @this) => @this.Op(Instruction.SELFBALANCE); public static Prepare BASEFEE(this Prepare @this) => @this.Op(Instruction.BASEFEE); + public static Prepare DATAHASH(this Prepare @this) + => @this.Op(Instruction.DATAHASH); public static Prepare POP(this Prepare @this) => @this.Op(Instruction.POP); public static Prepare PC(this Prepare @this) diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs index 59d33449635..6875a781100 100644 --- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs +++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs @@ -39,6 +39,7 @@ public static class GasCostOf public const long TxDataNonZero = 68; public const long TxDataNonZeroEip2028 = 16; public const long Transaction = 21000; + public const long DataHash = 3; public const long Log = 375; public const long LogTopic = 375; public const long LogData = 8; diff --git a/src/Nethermind/Nethermind.Evm/Instruction.cs b/src/Nethermind/Nethermind.Evm/Instruction.cs index 554ba378844..3d4c220e302 100644 --- a/src/Nethermind/Nethermind.Evm/Instruction.cs +++ b/src/Nethermind/Nethermind.Evm/Instruction.cs @@ -67,6 +67,7 @@ public enum Instruction : byte CHAINID = 0x46, SELFBALANCE = 0x47, BASEFEE = 0x48, + DATAHASH = 0x49, POP = 0x50, MLOAD = 0x51, diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 915c7a7e892..a3480d8d91a 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -321,7 +321,7 @@ senderBalance < (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + value recipientOrNull = recipient; ExecutionEnvironment env = new(); - env.TxExecutionContext = new TxExecutionContext(block, caller, effectiveGasPrice); + env.TxExecutionContext = new TxExecutionContext(block, caller, effectiveGasPrice, transaction.BlobVersionedHashes); env.Value = value; env.TransferValue = value; env.Caller = caller; diff --git a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs index 490d56deb4e..ecb81ac49c4 100644 --- a/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs +++ b/src/Nethermind/Nethermind.Evm/TxExecutionContext.cs @@ -11,12 +11,14 @@ namespace Nethermind.Evm public BlockHeader Header { get; } public Address Origin { get; } public UInt256 GasPrice { get; } + public byte[][]? BlobVersionedHashes { get; } - public TxExecutionContext(BlockHeader blockHeader, Address origin, in UInt256 gasPrice) + public TxExecutionContext(BlockHeader blockHeader, Address origin, in UInt256 gasPrice, byte[][] blobVersionedHashes) { Header = blockHeader; Origin = origin; GasPrice = gasPrice; + BlobVersionedHashes = blobVersionedHashes; } } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index 3fecc72ee5e..4ab0e6db8be 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -1720,6 +1720,32 @@ void UpdateMemoryCost(in UInt256 position, in UInt256 length) stack.PushUInt256(in baseFee); break; } + case Instruction.DATAHASH: + { + if (!spec.IsEip4844Enabled) + { + EndInstructionTraceError(EvmExceptionType.BadInstruction); + return CallResult.InvalidInstructionException; + } + + if (!UpdateGas(GasCostOf.DataHash, ref gasAvailable)) + { + EndInstructionTraceError(EvmExceptionType.OutOfGas); + return CallResult.OutOfGasException; + } + + stack.PopUInt256(out UInt256 blobIndex); + + if (txCtx.BlobVersionedHashes is not null && blobIndex < txCtx.BlobVersionedHashes.Length) + { + stack.PushBytes(txCtx.BlobVersionedHashes[blobIndex.u0]); + } + else + { + stack.PushZero(); + } + break; + } case Instruction.POP: { if (!UpdateGas(GasCostOf.Base, ref gasAvailable))