diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
index ad8ff933da1..b24bf603112 100644
--- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs
@@ -250,6 +250,11 @@ public interface IReleaseSpec
///
bool IsEip3607Enabled { get; }
+ ///
+ /// Transient storage
+ ///
+ bool IsEip1153Enabled { get; }
+
///
/// Should transactions be validated against chainId.
///
@@ -327,5 +332,7 @@ public interface IReleaseSpec
public Address? Eip1559FeeCollector => null;
public UInt256? Eip1559BaseFeeMinValue => null;
+
+ public bool TransientStorageEnabled => IsEip1153Enabled;
}
}
diff --git a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs
index c8eb7b2321d..e937b0b5d43 100644
--- a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -28,10 +28,10 @@ public class CoinbaseTests : VirtualMachineTestsBase
private bool _setAuthor;
- protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction)
+ protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit)
{
senderRecipientAndMiner ??= new SenderRecipientAndMiner();
- Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction);
+ Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit);
if(_setAuthor) block.Header.Author = TestItem.AddressC;
block.Header.Beneficiary = senderRecipientAndMiner.Recipient;
return block;
diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs
new file mode 100644
index 00000000000..988fc1af92a
--- /dev/null
+++ b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs
@@ -0,0 +1,850 @@
+// Copyright (c) 2021 Demerzel Solutions Limited
+// This file is part of the Nethermind library.
+//
+// The Nethermind library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The Nethermind library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the Nethermind. If not, see .
+
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Core.Specs;
+using Nethermind.Specs;
+using Nethermind.Core.Test.Builders;
+using NUnit.Framework;
+using System.Diagnostics;
+
+namespace Nethermind.Evm.Test
+{
+ ///
+ /// Tests functionality of Transient Storage
+ ///
+ internal class Eip1153Tests : VirtualMachineTestsBase
+ {
+ protected override long BlockNumber => MainnetSpecProvider.ShanghaiBlockNumber;
+ protected override ISpecProvider SpecProvider => MainnetSpecProvider.Instance;
+
+ ///
+ /// Transient storage should be activated after Shanghai hardfork
+ ///
+ [Test]
+ public void after_shanghai_can_call_tstore_tload()
+ {
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .LoadDataFromTransientStorage(1)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(StatusCode.Success, result.StatusCode);
+ }
+
+ ///
+ /// Transient storage should not be activated until after Shanghai hardfork
+ ///
+ [Test]
+ public void before_shanghai_can_not_call_tstore_tload()
+ {
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber - 1, 100000, code);
+ Assert.AreEqual(StatusCode.Failure, result.StatusCode);
+
+ code = Prepare.EvmCode
+ .LoadDataFromTransientStorage(1)
+ .Done;
+
+ result = Execute(MainnetSpecProvider.ShanghaiBlockNumber - 1, 100000, code);
+ Assert.AreEqual(StatusCode.Failure, result.StatusCode);
+ }
+
+ ///
+ /// Uninitialized transient storage is zero
+ ///
+ [Test]
+ public void tload_uninitialized_returns_zero()
+ {
+ byte[] code = Prepare.EvmCode
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .Return(32, 0)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(StatusCode.Success, result.StatusCode);
+
+ // Should be 0 since it's not yet set
+ Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Simple performance test
+ ///
+ [Explicit("Depends on hardware")]
+ [Test]
+ public void transient_storage_performance_test()
+ {
+ Stopwatch stopwatch = new Stopwatch();
+ long blockGasLimit = 30000000;
+ long numOfOps = (long)(blockGasLimit * .95) / (GasCostOf.TLoad + GasCostOf.TStore + GasCostOf.VeryLow * 4);
+ Prepare prepare = Prepare.EvmCode;
+ for (long i = 0; i < numOfOps; i++)
+ {
+ prepare.StoreDataInTransientStorage(1, 8);
+ prepare.LoadDataFromTransientStorage(1);
+ prepare.Op(Instruction.POP);
+ }
+
+ byte[] code = prepare.Done;
+
+ stopwatch.Start();
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, blockGasLimit, code, blockGasLimit);
+ Assert.AreEqual(StatusCode.Success, result.StatusCode);
+ stopwatch.Stop();
+ Assert.IsTrue(stopwatch.ElapsedMilliseconds < 5000);
+ }
+
+ ///
+ /// Simple functionality test
+ ///
+ [Test]
+ public void tload_after_tstore()
+ {
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .Return(32, 0)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(StatusCode.Success, result.StatusCode);
+
+ Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Testing transient data store/load from different locations
+ ///
+ /// Location
+ [TestCase(2)]
+ [TestCase(3)]
+ [TestCase(4)]
+ [TestCase(5)]
+ [TestCase(6)]
+ public void tload_after_tstore_from_different_locations(int loadLocation)
+ {
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .LoadDataFromTransientStorage(loadLocation)
+ .DataOnStackToMemory(0)
+ .Return(32, 0)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(StatusCode.Success, result.StatusCode);
+
+ Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Contracts have separate transient storage
+ ///
+ [Test]
+ public void tload_from_different_contract()
+ {
+ // TLOAD and RETURN the resulting value
+ byte[] contractCode = Prepare.EvmCode
+ .PushData(1)
+ .Op(Instruction.TLOAD)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Store 8 at index 1 and call contract from above
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ // If transient state was not isolated, the return value would be 8
+ Assert.AreEqual(0, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Reentrant calls access the same transient storage
+ ///
+ [Test]
+ public void tload_from_reentrant_call()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(78)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ // Return the response
+ .ReturnInnerCallResult()
+
+ // Reentrant, TLOAD and return value
+ .Op(Instruction.JUMPDEST)
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Reentrant calls can manipulate the same transient storage
+ ///
+ [Test]
+ public void tstore_from_reentrant_call()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(78)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ // Return the response
+ .ReturnInnerCallResult()
+
+ // Reentrant, TLOAD and return value
+ .Op(Instruction.JUMPDEST) // PC = 78
+ .StoreDataInTransientStorage(1, 9)
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(9, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Successfully returned calls do not revert transient storage writes
+ ///
+ [Test]
+ public void tstore_from_reentrant_call_read_by_caller()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(77)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ // TLOAD and return value (should be 9)
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Reentrant, TSTORE 9
+ .Op(Instruction.JUMPDEST) // PC = 77
+ .StoreDataInTransientStorage(1, 9)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(9, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Revert undoes the transient storage write from the failed call
+ ///
+ [Test]
+ public void revert_resets_transient_state()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(77)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Reentrant, TSTORE 9 but REVERT
+ .Op(Instruction.JUMPDEST) // PC = 77
+ .StoreDataInTransientStorage(1, 9)
+ .Op(Instruction.REVERT)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ // Should be original TSTORE value
+ Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Revert undoes all the transient storage writes to the same key from the failed call
+ ///
+ [Test]
+ public void revert_resets_all_transient_state()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(77)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE
+ .StoreDataInTransientStorage(1, 8)
+ .Call(TestItem.AddressD, 50000)
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Reentrant, TSTORE 9 but REVERT
+ .Op(Instruction.JUMPDEST) // PC = 77
+ .StoreDataInTransientStorage(1, 9)
+ .StoreDataInTransientStorage(1, 10)
+ .Op(Instruction.REVERT)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ // Should be original TSTORE value
+ Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Revert undoes transient storage writes from inner calls that successfully returned
+ ///
+ [Test]
+ public void revert_resets_transient_state_from_succesful_calls()
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check call depth
+ .PushData(0)
+ .Op(Instruction.CALLDATALOAD)
+ // Store input in mem and reload it to stack
+ .DataOnStackToMemory(5)
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+
+ // See if we're at call depth 1
+ .PushData(1)
+ .Op(Instruction.EQ)
+ .PushData(84)
+ .Op(Instruction.JUMPI)
+
+ // See if we're at call depth 2
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(2)
+ .Op(Instruction.EQ)
+ .PushData(135)
+ .Op(Instruction.JUMPI)
+
+ // Call depth = 0, call self after TSTORE 8
+ .StoreDataInTransientStorage(1, 8)
+
+ // Recursive call with input
+ // Depth++
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(1)
+ .Op(Instruction.ADD)
+
+ .CallWithInput(TestItem.AddressD, 50000)
+
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Call depth 1, TSTORE 9 but REVERT after recursion
+ .Op(Instruction.JUMPDEST) // PC = 84
+ .StoreDataInTransientStorage(1, 9)
+
+ // Recursive call with input
+ // Depth++
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(1)
+ .Op(Instruction.ADD)
+
+ .CallWithInput(TestItem.AddressD, 50000)
+
+ .Op(Instruction.REVERT)
+
+ // Call depth 2, TSTORE 10 and complete
+ .Op(Instruction.JUMPDEST) // PC = 135
+ .StoreDataInTransientStorage(1, 10)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .CallWithInput(TestItem.AddressD, 50000, new byte[32])
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ // Should be original TSTORE value
+ Assert.AreEqual(8, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Transient storage cannot be manipulated in a static context
+ ///
+ [TestCase(Instruction.CALL, 1)]
+ [TestCase(Instruction.STATICCALL, 0)]
+ public void tstore_in_staticcall(Instruction callType, int expectedResult)
+ {
+ byte[] contractCode = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .PushData(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract (1 if successful)
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 7)
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32])
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Transient storage cannot be manipulated in a static context when calling self
+ ///
+ [TestCase(Instruction.CALL, 9)]
+ [TestCase(Instruction.STATICCALL, 8)]
+ public void tstore_from_static_reentrant_call(Instruction callType, int expectedResult)
+ {
+ // If caller is self, TSTORE 9 and break recursion
+ // Else, TSTORE 8 and call self, return the result of TLOAD
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(113)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE 8
+ .StoreDataInTransientStorage(1, 8)
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32])
+ // Return the TLOAD value
+ // Should be 8 if call fails, 9 if success
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Reentrant, TSTORE 9
+ .Op(Instruction.JUMPDEST) // PC = 113
+ .StoreDataInTransientStorage(1, 9)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Transient storage cannot be manipulated in a nested static context
+ ///
+ [TestCase(Instruction.CALL, 10)]
+ [TestCase(Instruction.STATICCALL, 8)]
+ public void tstore_from_nonstatic_reentrant_call_with_static_intermediary(Instruction callType, int expectedResult)
+ {
+ // If caller is self, TLOAD and return value (break recursion)
+ // Else, TSTORE and call self, return the response
+ byte[] contractCode = Prepare.EvmCode
+ // Check call depth
+ .PushData(0)
+ .Op(Instruction.CALLDATALOAD)
+ // Store input in mem and reload it to stack
+ .DataOnStackToMemory(5)
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+
+ // See if we're at call depth 1
+ .PushData(1)
+ .Op(Instruction.EQ)
+ .PushData(84)
+ .Op(Instruction.JUMPI)
+
+ // See if we're at call depth 2
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(2)
+ .Op(Instruction.EQ)
+ .PushData(140)
+ .Op(Instruction.JUMPI)
+
+ // Call depth = 0, call self after TSTORE 8
+ .StoreDataInTransientStorage(1, 8)
+
+ // Recursive call with input
+ // Depth++
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(1)
+ .Op(Instruction.ADD)
+
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000)
+
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Call depth 1, TSTORE 9 but REVERT after recursion
+ .Op(Instruction.JUMPDEST) // PC = 84
+
+ // Recursive call with input
+ // Depth++
+ .PushData(5)
+ .Op(Instruction.MLOAD)
+ .PushData(1)
+ .Op(Instruction.ADD)
+ .CallWithInput(TestItem.AddressD, 50000)
+
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Call depth 2, TSTORE 10 and complete
+ .Op(Instruction.JUMPDEST) // PC = 140
+ .StoreDataInTransientStorage(1, 10) // This will fail
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .CallWithInput(TestItem.AddressD, 50000, new byte[32])
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ // Should be original TSTORE value
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Delegatecall manipulates transient storage in the context of the current address
+ ///
+ [TestCase(Instruction.CALL, 7)]
+ [TestCase(Instruction.DELEGATECALL, 8)]
+ public void tstore_in_delegatecall(Instruction callType, int expectedResult)
+ {
+ byte[] contractCode = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 8)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 7)
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32])
+ // TLOAD and return value
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Delegatecall reads transient storage in the context of the current address
+ ///
+ [TestCase(Instruction.CALL, 0)]
+ [TestCase(Instruction.DELEGATECALL, 7)]
+ public void tload_in_delegatecall(Instruction callType, int expectedResult)
+ {
+ byte[] contractCode = Prepare.EvmCode
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 7)
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32])
+ // Return response from nested call
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+
+ ///
+ /// Zeroing out a transient storage slot does not result in gas refund
+ ///
+ [Test]
+ public void tstore_does_not_result_in_gasrefund()
+ {
+ byte[] code = Prepare.EvmCode
+ .StoreDataInTransientStorage(1, 7)
+ .StoreDataInTransientStorage(1, 0)
+ .Done;
+
+ TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 4 + GasCostOf.TStore * 2, receipt.GasSpent, "gas");
+ }
+
+ ///
+ /// Transient storage does not persist beyond a single transaction
+ ///
+ [Test]
+ public void transient_state_not_persisted_across_txs()
+ {
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .LoadDataFromTransientStorage(1)
+ // See if we're at call depth 1
+ .PushData(1)
+ .Op(Instruction.EQ)
+ .PushData(24)
+ .Op(Instruction.JUMPI)
+
+ // TSTORE 1 and Return 1
+ .StoreDataInTransientStorage(1, 1)
+ .PushData(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+
+ // Return 0
+ .Op(Instruction.JUMPDEST) // PC = 24
+ .PushData(0)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(1, (int)result.ReturnValue.ToUInt256());
+
+ // If transient state persisted across txs, calling again would return 0
+ result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(1, (int)result.ReturnValue.ToUInt256());
+ }
+
+
+ ///
+ /// Transient storage can be accessed in a static context when calling self
+ ///
+ [TestCase(Instruction.CALL, 8)]
+ [TestCase(Instruction.STATICCALL, 8)]
+ public void tload_from_static_reentrant_call(Instruction callType, int expectedResult)
+ {
+ // If caller is self, TLOAD and break recursion
+ // Else, TSTORE 8 and call self, return the result of the inner call
+ byte[] contractCode = Prepare.EvmCode
+ // Check if caller is self
+ .Op(Instruction.CALLER)
+ .PushData(TestItem.AddressD)
+ .Op(Instruction.EQ)
+ .PushData(114)
+ .Op(Instruction.JUMPI)
+
+ // Non-reentrant, call self after TSTORE 8
+ .StoreDataInTransientStorage(1, 8)
+ .DynamicCallWithInput(callType, TestItem.AddressD, 50000, new byte[32])
+ .ReturnInnerCallResult()
+
+ // Reentrant, TLOAD and return
+ .Op(Instruction.JUMPDEST) // PC = 114
+ .LoadDataFromTransientStorage(1)
+ .DataOnStackToMemory(0)
+ .PushData(32)
+ .PushData(0)
+ .Op(Instruction.RETURN)
+ .Done;
+
+ TestState.CreateAccount(TestItem.AddressD, 1.Ether());
+ Keccak contractCodeHash = TestState.UpdateCode(contractCode);
+ TestState.UpdateCodeHash(TestItem.AddressD, contractCodeHash, Spec);
+
+ // Return the result received from the contract
+ byte[] code = Prepare.EvmCode
+ .Call(TestItem.AddressD, 50000)
+ .ReturnInnerCallResult()
+ .Done;
+
+ TestAllTracerWithOutput result = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+
+ Assert.AreEqual(expectedResult, (int)result.ReturnValue.ToUInt256());
+ }
+ }
+}
diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs b/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs
index 9f47b465921..71c5cd6a8ee 100644
--- a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs
@@ -250,13 +250,13 @@ parentEvmState is null
new ExecutionEnvironment(),
ExecutionType.Call,
true,
- new Snapshot(Snapshot.EmptyPosition, Snapshot.EmptyPosition),
+ Snapshot.Empty,
isContinuation)
: new EvmState(10000,
new ExecutionEnvironment(),
ExecutionType.Call,
false,
- new Snapshot(Snapshot.EmptyPosition, Snapshot.EmptyPosition),
+ Snapshot.Empty,
0,
0,
false,
diff --git a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs
index 22f1a93affb..f95ef1b4417 100644
--- a/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/InvalidOpcodeTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -120,6 +120,22 @@ public class InvalidOpcodeTests : VirtualMachineTestsBase
)
))))).ToArray();
+ private static readonly Instruction[] ShanghaiInstructions =
+ FrontierInstructions.Union(
+ HomesteadInstructions.Union(
+ ByzantiumInstructions.Union(
+ ConstantinopleFixInstructions.Union(
+ IstanbulInstructions.Union(
+ BerlinInstructions.Union(
+ LondonInstructions.Union(
+ new Instruction[]
+ {
+ Instruction.TLOAD,
+ Instruction.TSTORE
+ }
+ )
+ )))))).ToArray();
+
private Dictionary _validOpcodes
= new()
{
@@ -133,7 +149,8 @@ private Dictionary _validOpcodes
{MainnetSpecProvider.MuirGlacierBlockNumber, IstanbulInstructions},
{MainnetSpecProvider.BerlinBlockNumber, BerlinInstructions},
{MainnetSpecProvider.LondonBlockNumber, LondonInstructions},
- {long.MaxValue, LondonInstructions}
+ {MainnetSpecProvider.ShanghaiBlockNumber, ShanghaiInstructions},
+ {long.MaxValue, ShanghaiInstructions}
};
private const string InvalidOpCodeErrorMessage = "BadInstruction";
@@ -157,6 +174,7 @@ protected override ILogManager GetLogManager()
[TestCase(MainnetSpecProvider.BerlinBlockNumber)]
[TestCase(MainnetSpecProvider.BerlinBlockNumber)]
[TestCase(MainnetSpecProvider.LondonBlockNumber)]
+ [TestCase(MainnetSpecProvider.ShanghaiBlockNumber)]
[TestCase(long.MaxValue)]
public void Test(long blockNumber)
{
diff --git a/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs
index 1b78f3b639c..2cb2500b901 100644
--- a/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/Sha3Tests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -29,9 +29,9 @@ public class Sha3Tests : VirtualMachineTestsBase
private bool _setAuthor;
- protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction)
+ protected override Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit)
{
- Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction);
+ Block block = base.BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit);
if(_setAuthor) block.Header.Author = TestItem.AddressC;
block.Header.Beneficiary = TestItem.AddressB;
return block;
diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs
index f1998e937bf..5e1654cbbb7 100644
--- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -19,9 +19,9 @@
using Nethermind.Core;
using Nethermind.Core.Extensions;
using Nethermind.Evm.Tracing.GethStyle;
-using Nethermind.State;
using Nethermind.Int256;
using NUnit.Framework;
+using Nethermind.Specs;
namespace Nethermind.Evm.Test
{
@@ -425,6 +425,37 @@ public void Sstore_twice_0_same_storage_should_refund_only_once()
Assert.AreEqual(BigInteger.Zero.ToBigEndianByteArray(), Storage.Get(new StorageCell(Recipient, 0)), "storage");
}
+ ///
+ /// TLoad gas cost check
+ ///
+ [Test]
+ public void Tload()
+ {
+ byte[] code = Prepare.EvmCode
+ .PushData(96)
+ .Op(Instruction.TLOAD)
+ .Done;
+
+ TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 1 + GasCostOf.TLoad, receipt.GasSpent, "gas");
+ }
+
+ ///
+ /// TStore gas cost check
+ ///
+ [Test]
+ public void Tstore()
+ {
+ byte[] code = Prepare.EvmCode
+ .PushData(96)
+ .PushData(64)
+ .Op(Instruction.TSTORE)
+ .Done;
+
+ TestAllTracerWithOutput receipt = Execute(MainnetSpecProvider.ShanghaiBlockNumber, 100000, code);
+ Assert.AreEqual(GasCostOf.Transaction + GasCostOf.VeryLow * 2 + GasCostOf.TStore, receipt.GasSpent, "gas");
+ }
+
[Test]
[Ignore("Not yet implemented")]
public void Ropsten_attack_contract_test()
diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs
index 5b27f00a280..60761ab726f 100644
--- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs
+++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs
@@ -40,6 +40,7 @@ public class VirtualMachineTestsBase
protected const string SampleHexData1 = "a01234";
protected const string SampleHexData2 = "b15678";
protected const string HexZero = "00";
+ protected const long DefaultBlockGasLimit = 8000000;
private IEthereumEcdsa _ethereumEcdsa;
protected ITransactionProcessor _processor;
@@ -116,9 +117,9 @@ protected T Execute(T tracer, params byte[] code) where T : ITxTracer
return tracer;
}
- protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code)
+ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[] code, long blockGasLimit = DefaultBlockGasLimit)
{
- (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code);
+ (Block block, Transaction transaction) = PrepareTx(blockNumber, gasLimit, code, blockGasLimit: blockGasLimit);
TestAllTracerWithOutput tracer = CreateTracer();
_processor.Execute(transaction, block.Header, tracer);
return tracer;
@@ -129,7 +130,8 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[
long gasLimit,
byte[] code,
SenderRecipientAndMiner senderRecipientAndMiner = null,
- int value = 1)
+ int value = 1,
+ long blockGasLimit = DefaultBlockGasLimit)
{
senderRecipientAndMiner ??= SenderRecipientAndMiner.Default;
TestState.CreateAccount(senderRecipientAndMiner.Sender, 100.Ether());
@@ -152,7 +154,7 @@ protected TestAllTracerWithOutput Execute(long blockNumber, long gasLimit, byte[
.SignedAndResolved(_ethereumEcdsa, senderRecipientAndMiner.SenderKey)
.TestObject;
- Block block = BuildBlock(blockNumber, senderRecipientAndMiner, transaction);
+ Block block = BuildBlock(blockNumber, senderRecipientAndMiner, transaction, blockGasLimit);
return (block, transaction);
}
@@ -202,10 +204,10 @@ protected Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecip
return BuildBlock(blockNumber, senderRecipientAndMiner, null);
}
- protected virtual Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction tx)
+ protected virtual Block BuildBlock(long blockNumber, SenderRecipientAndMiner senderRecipientAndMiner, Transaction tx, long blockGasLimit = DefaultBlockGasLimit)
{
senderRecipientAndMiner ??= SenderRecipientAndMiner.Default;
- return Build.A.Block.WithNumber(blockNumber).WithTransactions(tx == null ? new Transaction[0] : new[] {tx}).WithGasLimit(8000000).WithBeneficiary(senderRecipientAndMiner.Miner).TestObject;
+ return Build.A.Block.WithNumber(blockNumber).WithTransactions(tx == null ? new Transaction[0] : new[] {tx}).WithGasLimit(blockGasLimit).WithBeneficiary(senderRecipientAndMiner.Miner).TestObject;
}
protected void AssertGas(TestAllTracerWithOutput receipt, long gas)
diff --git a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs
index 6db7c50e6af..17520644947 100644
--- a/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs
+++ b/src/Nethermind/Nethermind.Evm/ByteCodeBuilder.cs
@@ -22,6 +22,9 @@
namespace Nethermind.Evm
{
+ ///
+ /// A utility class to easily construct common patterns of EVM byte code
+ ///
public class Prepare
{
private readonly List _byteCode = new();
@@ -115,12 +118,20 @@ public Prepare CallWithInput(Address address, long gasLimit, string input)
return CallWithInput(address, gasLimit, Bytes.FromHexString(input));
}
- public Prepare CallWithInput(Address address, long gasLimit, byte[] input)
+ public Prepare CallWithInput(Address address, long gasLimit, byte[]? input = null)
{
- StoreDataInMemory(0, input);
+ if (input != null)
+ {
+ StoreDataInMemory(0, input);
+ }
+ else
+ {
+ // Use top of stack as input
+ DataOnStackToMemory(0);
+ }
PushData(0);
PushData(0);
- PushData(input.Length);
+ PushData(input != null ? input.Length : 32);
PushData(0);
PushData(0);
PushData(address);
@@ -179,6 +190,43 @@ public Prepare StaticCall(Address address, long gasLimit)
return this;
}
+ ///
+ /// Call the address with the specified callType
+ ///
+ /// CALL, STATICCALL, DELEGATECALL
+ /// Address of the contract
+ /// Gas limit of the call
+ /// Optional 32 byte input
+ /// Prepare with call bytecode
+ /// Throws exception if callType is incorrect
+ public Prepare DynamicCallWithInput(Instruction callType, Address address, long gasLimit, byte[]? input = null)
+ {
+ if (callType != Instruction.CALL &&
+ callType != Instruction.STATICCALL &&
+ callType != Instruction.DELEGATECALL)
+ {
+ throw new Exception($"Unexpected call type {callType}");
+ }
+ if (input != null)
+ {
+ StoreDataInMemory(0, input);
+ }
+ else
+ {
+ // Use top of stack as input
+ DataOnStackToMemory(0);
+ }
+ PushData(0);
+ PushData(0);
+ PushData(input != null ? input.Length : 32);
+ PushData(0);
+ PushData(0);
+ PushData(address);
+ PushData(gasLimit);
+ Op(callType);
+ return this;
+ }
+
public Prepare PushData(Address address)
{
PushData(address.Bytes);
@@ -271,5 +319,74 @@ private Prepare StoreDataInMemory(int position, byte[] data)
return this;
}
+
+ ///
+ /// Take the data already on stack and store it in memory
+ /// at specified position
+ ///
+ /// Memory position
+ /// Prepare with requested bytecode
+ public Prepare DataOnStackToMemory(int position)
+ {
+ PushData(position);
+ Op(Instruction.MSTORE);
+ return this;
+ }
+
+ ///
+ /// Store input value at specified key in transient storage
+ ///
+ /// Storage key
+ /// Value to store
+ /// Prepare with requested bytecode
+ public Prepare StoreDataInTransientStorage(int key, int value)
+ {
+ PushData(value);
+ PushData(key);
+ Op(Instruction.TSTORE);
+ return this;
+ }
+
+ ///
+ /// Load value from specified key in transient storage
+ ///
+ /// Storage key
+ /// Prepare with requested bytecode
+ public Prepare LoadDataFromTransientStorage(int key)
+ {
+ PushData(key);
+ Op(Instruction.TLOAD);
+ return this;
+ }
+
+ ///
+ /// Return the data in memory at position
+ ///
+ /// Data size
+ /// Memory position
+ /// Prepare with requested bytecode
+ public Prepare Return(int size, int position)
+ {
+ PushData(size);
+ PushData(position);
+ Op(Instruction.RETURN);
+ return this;
+ }
+
+ ///
+ /// Returns the result from a call made immediately prior
+ ///
+ /// Prepare with requested bytecode
+ public Prepare ReturnInnerCallResult()
+ {
+ PushData(32);
+ PushData(0);
+ PushData(0);
+ Op(Instruction.RETURNDATACOPY);
+ PushData(32);
+ PushData(0);
+ Op(Instruction.RETURN);
+ return this;
+ }
}
}
diff --git a/src/Nethermind/Nethermind.Evm/GasCostOf.cs b/src/Nethermind/Nethermind.Evm/GasCostOf.cs
index fa9576eb9e5..c43d4469ae7 100644
--- a/src/Nethermind/Nethermind.Evm/GasCostOf.cs
+++ b/src/Nethermind/Nethermind.Evm/GasCostOf.cs
@@ -70,5 +70,7 @@ public static class GasCostOf
public const long AccessAccountListEntry = 2400; // eip-2930
public const long AccessStorageListEntry = 1900; // eip-2930
+ public const long TLoad = WarmStateRead; // eip-1153
+ public const long TStore = WarmStateRead; // eip-1153
}
}
diff --git a/src/Nethermind/Nethermind.Evm/Instruction.cs b/src/Nethermind/Nethermind.Evm/Instruction.cs
index 959327bfaef..d162919ffb3 100644
--- a/src/Nethermind/Nethermind.Evm/Instruction.cs
+++ b/src/Nethermind/Nethermind.Evm/Instruction.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -167,6 +167,10 @@ public enum Instruction : byte
LOG3 = 0xa3,
LOG4 = 0xa4,
+ // EIP-1153
+ TLOAD = 0xb3,
+ TSTORE = 0xb4,
+
CREATE = 0xf0,
CALL = 0xf1,
CALLCODE = 0xf2,
diff --git a/src/Nethermind/Nethermind.Evm/Metrics.cs b/src/Nethermind/Nethermind.Evm/Metrics.cs
index b5514c7a54c..e60bdafe38e 100644
--- a/src/Nethermind/Nethermind.Evm/Metrics.cs
+++ b/src/Nethermind/Nethermind.Evm/Metrics.cs
@@ -34,7 +34,13 @@ public class Metrics
[Description("Number of SSTORE opcodes executed.")]
public static long SstoreOpcode { get; set; }
-
+
+ [Description("Number of TLOAD opcodes executed.")]
+ public static long TloadOpcode { get; set; }
+
+ [Description("Number of TSTORE opcodes executed.")]
+ public static long TstoreOpcode { get; set; }
+
[Description("Number of MODEXP precompiles executed.")]
public static long ModExpOpcode { get; set; }
diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs
index 09b29a81abe..58e8933e5be 100644
--- a/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs
+++ b/src/Nethermind/Nethermind.Evm/Tracing/ITxTracer.cs
@@ -127,8 +127,10 @@ void ReportMemoryChange(long offset, in ZeroPaddedMemory data)
void ReportStorageChange(in ReadOnlySpan key, in ReadOnlySpan value);
void SetOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan newValue, ReadOnlySpan currentValue);
+ void SetOperationTransientStorage(Address storageCellAddress, UInt256 storageIndex, Span newValue, byte[] currentValue) { }
void LoadOperationStorage(Address address, UInt256 storageIndex, ReadOnlySpan value);
+ void LoadOperationTransientStorage(Address storageCellAddress, UInt256 storageIndex, byte[] value) { }
void ReportSelfDestruct(Address address, UInt256 balance, Address refundAddress);
diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs
index 9f03c1f6fbf..7c1f805f2c4 100644
--- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs
+++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs
@@ -2017,6 +2017,80 @@ void UpdateMemoryCost(in UInt256 position, in UInt256 length)
break;
}
+ case Instruction.TLOAD:
+ {
+ Metrics.TloadOpcode++;
+ if (!spec.TransientStorageEnabled)
+ {
+ EndInstructionTraceError(EvmExceptionType.BadInstruction);
+ return CallResult.InvalidInstructionException;
+ }
+ var gasCost = GasCostOf.TLoad;
+
+ if (!UpdateGas(gasCost, ref gasAvailable))
+ {
+ EndInstructionTraceError(EvmExceptionType.OutOfGas);
+ return CallResult.OutOfGasException;
+ }
+
+ stack.PopUInt256(out UInt256 storageIndex);
+ StorageCell storageCell = new(env.ExecutingAccount, storageIndex);
+
+ byte[] value = _storage.GetTransientState(storageCell);
+ stack.PushBytes(value);
+
+ if (_txTracer.IsTracingOpLevelStorage)
+ {
+ _txTracer.LoadOperationTransientStorage(storageCell.Address, storageIndex, value);
+ }
+
+ break;
+ }
+ case Instruction.TSTORE:
+ {
+ Metrics.TstoreOpcode++;
+ if (!spec.TransientStorageEnabled)
+ {
+ EndInstructionTraceError(EvmExceptionType.BadInstruction);
+ return CallResult.InvalidInstructionException;
+ }
+
+ if (vmState.IsStatic)
+ {
+ EndInstructionTraceError(EvmExceptionType.StaticCallViolation);
+ return CallResult.StaticCallViolationException;
+ }
+
+ long gasCost = GasCostOf.TStore;
+ if (!UpdateGas(gasCost, ref gasAvailable))
+ {
+ EndInstructionTraceError(EvmExceptionType.OutOfGas);
+ return CallResult.OutOfGasException;
+ }
+
+ stack.PopUInt256(out UInt256 storageIndex);
+ Span newValue = stack.PopBytes();
+ bool newIsZero = newValue.IsZero();
+ if (!newIsZero)
+ {
+ newValue = newValue.WithoutLeadingZeros().ToArray();
+ }
+ else
+ {
+ newValue = BytesZero;
+ }
+
+ StorageCell storageCell = new(env.ExecutingAccount, storageIndex);
+ byte[] currentValue = newValue.ToArray();
+ _storage.SetTransientState(storageCell, currentValue);
+
+ if (_txTracer.IsTracingOpLevelStorage)
+ {
+ _txTracer.SetOperationTransientStorage(storageCell.Address, storageIndex, newValue, currentValue);
+ }
+
+ break;
+ }
case Instruction.JUMP:
{
if (!UpdateGas(GasCostOf.Mid, ref gasAvailable))
diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs
index 4dd2c5c76f3..00f877adc10 100644
--- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs
index 0c7cc4b9a56..d141b7d3339 100644
--- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecLoaderTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
index 52fd930b8e2..3bbc6b9b32b 100644
--- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -147,5 +147,6 @@ public long Eip1559TransitionBlock
}
public Address? Eip1559FeeCollector => _spec.Eip1559FeeCollector;
+ public bool IsEip1153Enabled => _spec.IsEip1153Enabled;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
index 92fb77df2eb..af04c641235 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainParameters.cs
@@ -113,11 +113,13 @@ public class ChainParameters
/// Optional, minimal value of EIP1559 base fee
///
public UInt256? Eip1559BaseFeeMinValue { get; set; }
-
+
public long? MergeForkIdTransition { get; set; }
-
+
public long? TerminalPowBlockNumber { get; set; }
-
+
public UInt256? TerminalTotalDifficulty { get; set; }
+
+ public long? Eip1153Transition { get; set; }
}
}
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs
index 1858cf23bed..bffe8b3f35c 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpec.cs
@@ -87,5 +87,7 @@ public class ChainSpec
public long? TerminalPoWBlockNumber { get; set; }
public UInt256? TerminalTotalDifficulty { get; set; }
+
+ public long? ShanghaiBlockNumber { get; set; }
}
}
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
index 386dded0717..36dc2f8d614 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecBasedSpecProvider.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -139,6 +139,7 @@ private void BuildTransitions()
releaseSpec.IsEip3541Enabled = (_chainSpec.Parameters.Eip3541Transition ?? long.MaxValue) <= releaseStartBlock;
releaseSpec.IsEip3529Enabled = (_chainSpec.Parameters.Eip3529Transition ?? long.MaxValue) <= releaseStartBlock;
releaseSpec.IsEip3607Enabled = (_chainSpec.Parameters.Eip3607Transition ?? long.MaxValue) <= releaseStartBlock;
+ releaseSpec.IsEip1153Enabled = (_chainSpec.Parameters.Eip1153Transition ?? long.MaxValue) <= releaseStartBlock;
releaseSpec.ValidateChainId = (_chainSpec.Parameters.ValidateChainIdTransition ?? 0) <= releaseStartBlock;
releaseSpec.ValidateReceipts = ((_chainSpec.Parameters.ValidateReceiptsTransition > 0) ? Math.Max(_chainSpec.Parameters.ValidateReceiptsTransition ?? 0, _chainSpec.Parameters.Eip658Transition ?? 0) : 0) <= releaseStartBlock;
releaseSpec.Eip1559FeeCollector = releaseSpec.IsEip1559Enabled && (_chainSpec.Parameters.Eip1559FeeCollectorTransition ?? long.MaxValue) <= releaseStartBlock ? _chainSpec.Parameters.Eip1559FeeCollector : null;
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
index a6d10c9d30e..8be3765f451 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/ChainSpecLoader.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -146,6 +146,7 @@ private void LoadParameters(ChainSpecJson chainSpecJson, ChainSpec chainSpec)
Eip3541Transition = chainSpecJson.Params.Eip3541Transition,
Eip3529Transition = chainSpecJson.Params.Eip3529Transition,
Eip3607Transition = chainSpecJson.Params.Eip3607Transition,
+ Eip1153Transition = chainSpecJson.Params.Eip1153Transition,
TransactionPermissionContract = chainSpecJson.Params.TransactionPermissionContract,
TransactionPermissionContractTransition = chainSpecJson.Params.TransactionPermissionContractTransition,
ValidateChainIdTransition = chainSpecJson.Params.ValidateChainIdTransition,
@@ -222,7 +223,8 @@ private static void LoadTransitions(ChainSpecJson chainSpecJson, ChainSpec chain
chainSpec.GrayGlacierBlockNumber = chainSpec.Ethash?.DifficultyBombDelays.Count > 5 ?
chainSpec.Ethash?.DifficultyBombDelays.Keys.ToArray()[5]
: null;
-
+ chainSpec.ShanghaiBlockNumber = chainSpec.Parameters.Eip1153Transition ?? (long.MaxValue - 1);
+
// TheMerge parameters
chainSpec.MergeForkIdBlockNumber = chainSpec.Parameters.MergeForkIdTransition;
chainSpec.TerminalPoWBlockNumber = chainSpec.Parameters.TerminalPowBlockNumber;
diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
index f989111f5d6..07b306ef789 100644
--- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
+++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/Json/ChainSpecParamsJson.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -138,11 +138,13 @@ internal class ChainSpecParamsJson
public long? Eip1559BaseFeeMinValueTransition { get; set; }
public UInt256? Eip1559BaseFeeMinValue { get; set; }
-
+
public long? MergeForkIdTransition { get; set; }
-
+
public UInt256? TerminalTotalDifficulty { get; set; }
-
+
public long? TerminalPoWBlockNumber { get; set; }
+
+ public long? Eip1153Transition { get; set; }
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs
index c9a6870e473..f9bc8cf4b34 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs
@@ -83,5 +83,6 @@ private Olympic()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs b/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs
index 12f3a70139d..8ea42d2f0f1 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/01_Frontier.cs
@@ -81,5 +81,6 @@ private Frontier()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs b/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs
index 910b627cff3..9ed0a4435a4 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/02_Homestead.cs
@@ -79,5 +79,6 @@ private Homestead() { }
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs b/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs
index 88586f050bf..04765ccbd7e 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/03_Dao.cs
@@ -81,5 +81,6 @@ private Dao()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs b/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs
index 33bb970a7aa..ecf3f7fd058 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/04_TangerineWhistle.cs
@@ -81,5 +81,6 @@ private TangerineWhistle()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs b/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs
index 34105adc895..a50e7c7ba13 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/05_SpuriousDragon.cs
@@ -81,5 +81,6 @@ private SpuriousDragon()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs
index 1427783cf2a..c66c133a242 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs
@@ -81,5 +81,6 @@ private Byzantium()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs
index ef0d4bfe894..673d294897a 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs
@@ -81,5 +81,6 @@ private Constantinople()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs b/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs
index a1a9f2c7a83..141f3422422 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/08_ConstantinopleFix.cs
@@ -83,5 +83,6 @@ private ConstantinopleFix()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs b/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs
index 88d6a734d1f..86af18f96f8 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/09_Istanbul.cs
@@ -81,5 +81,6 @@ private Istanbul()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs
index 310792cba08..aac60115e00 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/10_MuirGlacier.cs
@@ -79,5 +79,6 @@ private MuirGlacier() { }
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs b/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs
index 3bdb331966f..0b9eceeeda8 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/11_Berlin.cs
@@ -81,5 +81,6 @@ private Berlin()
public bool IsEip3541Enabled => false;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => long.MaxValue;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/12_London.cs b/src/Nethermind/Nethermind.Specs/Forks/12_London.cs
index 273d646ae4c..a60a6437330 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/12_London.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/12_London.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -82,5 +82,6 @@ private London()
public bool IsEip3541Enabled => true;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => 12965000;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs
index e1d20cc86ff..04c041529c0 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/13_ArrowGlacier.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -82,5 +82,6 @@ private ArrowGlacier()
public bool IsEip3541Enabled => true;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => 12965000;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs b/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs
index 2bff0d592f2..5bfdb3a38d1 100644
--- a/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs
+++ b/src/Nethermind/Nethermind.Specs/Forks/14_GrayGlacier.cs
@@ -80,5 +80,6 @@ private GrayGlacier() { }
public bool IsEip3541Enabled => true;
public bool IsEip3607Enabled => true;
public long Eip1559TransitionBlock => 12965000;
+ public bool IsEip1153Enabled => false;
}
}
diff --git a/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs
new file mode 100644
index 00000000000..27982049251
--- /dev/null
+++ b/src/Nethermind/Nethermind.Specs/Forks/15_Shanghai.cs
@@ -0,0 +1,88 @@
+// Copyright (c) 2021 Demerzel Solutions Limited
+// This file is part of the Nethermind library.
+//
+// The Nethermind library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The Nethermind library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the Nethermind. If not, see .
+//
+
+using System.Threading;
+using Nethermind.Core;
+using Nethermind.Core.Specs;
+using Nethermind.Int256;
+
+namespace Nethermind.Specs.Forks
+{
+ public class Shanghai : IReleaseSpec
+ {
+ private static IReleaseSpec _instance;
+
+ private Shanghai()
+ {
+ }
+
+ public static IReleaseSpec Instance => LazyInitializer.EnsureInitialized(ref _instance, () => new Shanghai());
+
+ public string Name => "Shanghai";
+ public long MaximumExtraDataSize => 32;
+ public long MaxCodeSize => 24576;
+ public long MinGasLimit => 5000;
+ public long GasLimitBoundDivisor => 0x0400;
+ public UInt256 BlockReward { get; } = UInt256.Parse("2000000000000000000");
+ public long DifficultyBombDelay => 10700000L;
+ public long DifficultyBoundDivisor => 0x0800;
+ public long? FixedDifficulty => null;
+ public int MaximumUncleCount => 2;
+ public bool IsTimeAdjustmentPostOlympic => true;
+ public bool IsEip2Enabled => true;
+ public bool IsEip7Enabled => true;
+ public bool IsEip100Enabled => true;
+ public bool IsEip140Enabled => true;
+ public bool IsEip150Enabled => true;
+ public bool IsEip155Enabled => true;
+ public bool IsEip158Enabled => true;
+ public bool IsEip160Enabled => true;
+ public bool IsEip170Enabled => true;
+ public bool IsEip196Enabled => true;
+ public bool IsEip197Enabled => true;
+ public bool IsEip198Enabled => true;
+ public bool IsEip211Enabled => true;
+ public bool IsEip214Enabled => true;
+ public bool IsEip649Enabled => true;
+ public bool IsEip658Enabled => true;
+ public bool IsEip145Enabled => true;
+ public bool IsEip1014Enabled => true;
+ public bool IsEip1052Enabled => true;
+ public bool IsEip1283Enabled => false;
+ public bool IsEip1234Enabled => true;
+ public bool IsEip1344Enabled => true;
+ public bool IsEip2028Enabled => true;
+ public bool IsEip152Enabled => true;
+ public bool IsEip1108Enabled => true;
+ public bool IsEip1884Enabled => true;
+ public bool IsEip2200Enabled => true;
+ public bool IsEip2315Enabled => false;
+ public bool IsEip2537Enabled => false;
+ public bool IsEip2565Enabled => true;
+ public bool IsEip2929Enabled => true;
+ public bool IsEip2930Enabled => true;
+ public bool IsEip158IgnoredAccount(Address address) => false;
+ public bool IsEip1559Enabled => true;
+ public bool IsEip3198Enabled => true;
+ public bool IsEip3529Enabled => true;
+ public bool IsEip3541Enabled => true;
+ public bool IsEip3607Enabled => true;
+ public bool IsEip3675Enabled => false;
+ public long Eip1559TransitionBlock => 12965000;
+ public bool IsEip1153Enabled => true;
+ }
+}
diff --git a/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs
index c9af9c904c6..bc1752d4107 100644
--- a/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs
+++ b/src/Nethermind/Nethermind.Specs/MainNetSpecProvider.cs
@@ -51,7 +51,8 @@ public IReleaseSpec GetSpec(long blockNumber) =>
< LondonBlockNumber => Berlin.Instance,
< ArrowGlacierBlockNumber => London.Instance,
< GrayGlacierBlockNumber => ArrowGlacier.Instance,
- _ => GrayGlacier.Instance
+ < ShanghaiBlockNumber => GrayGlacier.Instance,
+ _ => Shanghai.Instance
};
public const long HomesteadBlockNumber = 1_150_000;
@@ -78,7 +79,7 @@ public IReleaseSpec GetSpec(long blockNumber) =>
{
HomesteadBlockNumber, DaoBlockNumberConst, TangerineWhistleBlockNumber, SpuriousDragonBlockNumber,
ByzantiumBlockNumber, ConstantinopleFixBlockNumber, IstanbulBlockNumber, MuirGlacierBlockNumber,
- BerlinBlockNumber, LondonBlockNumber, ArrowGlacierBlockNumber, GrayGlacierBlockNumber
+ BerlinBlockNumber, LondonBlockNumber, ArrowGlacierBlockNumber, GrayGlacierBlockNumber, ShanghaiBlockNumber
};
private MainnetSpecProvider() { }
diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
index 1945ecd5fd2..1e59143ceac 100644
--- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs
@@ -76,5 +76,6 @@ public class ReleaseSpec : IReleaseSpec
public long Eip1559TransitionBlock { get; set; }
public Address Eip1559FeeCollector { get; set; }
public UInt256? Eip1559BaseFeeMinValue { get; set; }
+ public bool IsEip1153Enabled { get; set; }
}
}
diff --git a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
index 7cf3120c27f..6cc2cce2d82 100644
--- a/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
+++ b/src/Nethermind/Nethermind.Specs/SystemTransactionReleaseSpec.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -130,5 +130,6 @@ public bool IsEip158IgnoredAccount(Address address)
public long Eip1559TransitionBlock => _spec.Eip1559TransitionBlock;
public Address Eip1559FeeCollector => _spec.Eip1559FeeCollector;
+ public bool IsEip1153Enabled => _spec.IsEip1153Enabled;
}
}
diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs
index 6569adfafc5..2769c197740 100644
--- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs
+++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -15,6 +15,7 @@
// along with the Nethermind. If not, see .
using System;
+using FluentAssertions;
using Nethermind.Core;
using Nethermind.Core.Crypto;
using Nethermind.Core.Extensions;
@@ -57,7 +58,7 @@ public void Empty_commit_restore()
Context ctx = new();
StorageProvider provider = BuildStorageProvider(ctx);
provider.Commit();
- provider.Restore(-1);
+ provider.Restore(Snapshot.Storage.Empty);
}
private StorageProvider BuildStorageProvider(Context ctx)
@@ -236,6 +237,193 @@ public void Can_commit_when_exactly_at_capacity_regression()
Assert.AreEqual(_values[(Resettable.StartCapacity + 1) % 2], valueAfter);
}
+ ///
+ /// Transient storage should be zero if uninitialized
+ ///
+ [Test]
+ public void Can_tload_uninitialized_locations()
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+ // Should be 0 if not set
+ Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 1)).IsZero());
+
+ // Should be 0 if loading from the same contract but different index
+ provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]);
+ Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 1)).IsZero());
+
+ // Should be 0 if loading from the same index but different contract
+ Assert.True(provider.GetTransientState(new StorageCell(ctx.Address2, 1)).IsZero());
+ }
+
+ ///
+ /// Simple transient storage test
+ ///
+ [Test]
+ public void Can_tload_after_tstore()
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+
+ provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]);
+ Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2)));
+ }
+
+ ///
+ /// Transient storage can be updated and restored
+ ///
+ /// Snapshot to restore to
+ [TestCase(-1)]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void Tload_same_address_same_index_different_values_restore(int snapshot)
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+ Snapshot.Storage[] snapshots = new Snapshot.Storage[4];
+ snapshots[0] = ((IStorageProvider)provider).TakeSnapshot();
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[1]);
+ snapshots[1] = ((IStorageProvider)provider).TakeSnapshot();
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[2]);
+ snapshots[2] = ((IStorageProvider)provider).TakeSnapshot();
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[3]);
+ snapshots[3] = ((IStorageProvider)provider).TakeSnapshot();
+
+ Assert.AreEqual(snapshots[snapshot + 1].TransientStorageSnapshot, snapshot);
+ // Persistent storage is unimpacted by transient storage
+ Assert.AreEqual(snapshots[snapshot + 1].PersistentStorageSnapshot, -1);
+ provider.Restore(snapshots[snapshot + 1]);
+
+ Assert.AreEqual(_values[snapshot + 1], provider.GetTransientState(new StorageCell(ctx.Address1, 1)));
+ }
+
+ ///
+ /// Commit will reset transient state
+ ///
+ [Test]
+ public void Commit_resets_transient_state()
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+
+ provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]);
+ Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2)));
+
+ provider.Commit();
+ Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 2)).IsZero());
+ }
+
+ ///
+ /// Reset will reset transient state
+ ///
+ [Test]
+ public void Reset_resets_transient_state()
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+
+ provider.SetTransientState(new StorageCell(ctx.Address1, 2), _values[1]);
+ Assert.AreEqual(_values[1], provider.GetTransientState(new StorageCell(ctx.Address1, 2)));
+
+ provider.Reset();
+ Assert.True(provider.GetTransientState(new StorageCell(ctx.Address1, 2)).IsZero());
+ }
+
+ ///
+ /// Transient state does not impact persistent state
+ ///
+ /// Snapshot to restore to
+ [TestCase(-1)]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void Transient_state_restores_independent_of_persistent_state(int snapshot)
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+ Snapshot.Storage[] snapshots = new Snapshot.Storage[4];
+
+ // No updates
+ snapshots[0] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Only update transient
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[1]);
+ snapshots[1] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Update both
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[2]);
+ provider.Set(new StorageCell(ctx.Address1, 1), _values[9]);
+ snapshots[2] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Only update persistent
+ provider.Set(new StorageCell(ctx.Address1, 1), _values[8]);
+ snapshots[3] = ((IStorageProvider)provider).TakeSnapshot();
+
+ provider.Restore(snapshots[snapshot + 1]);
+
+ // Since we didn't update transient on the 3rd snapshot
+ if (snapshot == 2)
+ {
+ snapshot--;
+ }
+
+ snapshots.Should().Equal(
+ Snapshot.Storage.Empty,
+ new Snapshot.Storage(Snapshot.EmptyPosition, 0),
+ new Snapshot.Storage(0, 1),
+ new Snapshot.Storage(1, 1));
+
+ _values[snapshot + 1].Should().BeEquivalentTo(provider.GetTransientState(new StorageCell(ctx.Address1, 1)));
+ }
+
+ ///
+ /// Persistent state does not impact transient state
+ ///
+ /// Snapshot to restore to
+ [TestCase(-1)]
+ [TestCase(0)]
+ [TestCase(1)]
+ [TestCase(2)]
+ public void Persistent_state_restores_independent_of_transient_state(int snapshot)
+ {
+ Context ctx = new();
+ StorageProvider provider = BuildStorageProvider(ctx);
+ Snapshot.Storage[] snapshots = new Snapshot.Storage[4];
+
+ // No updates
+ snapshots[0] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Only update persistent
+ provider.Set(new StorageCell(ctx.Address1, 1), _values[1]);
+ snapshots[1] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Update both
+ provider.Set(new StorageCell(ctx.Address1, 1), _values[2]);
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[9]);
+ snapshots[2] = ((IStorageProvider)provider).TakeSnapshot();
+
+ // Only update transient
+ provider.SetTransientState(new StorageCell(ctx.Address1, 1), _values[8]);
+ snapshots[3] = ((IStorageProvider)provider).TakeSnapshot();
+
+ provider.Restore(snapshots[snapshot + 1]);
+
+ // Since we didn't update persistent on the 3rd snapshot
+ if (snapshot == 2)
+ {
+ snapshot--;
+ }
+
+ snapshots.Should().Equal(
+ Snapshot.Storage.Empty,
+ new Snapshot.Storage(0, Snapshot.EmptyPosition),
+ new Snapshot.Storage(1, 0),
+ new Snapshot.Storage(1, 1));
+
+ _values[snapshot + 1].Should().BeEquivalentTo(provider.Get(new StorageCell(ctx.Address1, 1)));
+ }
+
private class Context
{
public IStateProvider StateProvider { get; }
diff --git a/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs b/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs
index 0d44082d6eb..a68a70703f8 100644
--- a/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs
+++ b/src/Nethermind/Nethermind.State.Test/WorldStateTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -48,7 +48,7 @@ public void When_taking_a_snapshot_return_the_same_value_as_both()
Snapshot snapshot = worldState.TakeSnapshot();
snapshot.StateSnapshot.Should().Be(0);
- snapshot.StorageSnapshot.Should().Be(0);
+ snapshot.StorageSnapshot.PersistentStorageSnapshot.Should().Be(0);
}
[Test]
@@ -60,11 +60,12 @@ public void When_taking_a_snapshot_can_return_non_zero_snapshot_value()
WorldState worldState = new(stateProvider, storageProvider);
stateProvider.TakeSnapshot().Returns(1);
- storageProvider.TakeSnapshot().Returns(2);
+ storageProvider.TakeSnapshot().Returns(new Snapshot.Storage(2, 3));
Snapshot snapshot = worldState.TakeSnapshot();
snapshot.StateSnapshot.Should().Be(1);
- snapshot.StorageSnapshot.Should().Be(2);
+ snapshot.StorageSnapshot.PersistentStorageSnapshot.Should().Be(2);
+ snapshot.StorageSnapshot.TransientStorageSnapshot.Should().Be(3);
}
[Test]
@@ -85,9 +86,9 @@ public void Can_restore_snapshot()
IStorageProvider storageProvider = Substitute.For();
WorldState worldState = new(stateProvider, storageProvider);
- worldState.Restore(new Snapshot(1, 2));
+ worldState.Restore(new Snapshot(1, new Snapshot.Storage(2, 1)));
stateProvider.Received().Restore(1);
- storageProvider.Received().Restore(2);
+ storageProvider.Received().Restore(new Snapshot.Storage(2, 1));
}
}
}
diff --git a/src/Nethermind/Nethermind.State/IStorageProvider.cs b/src/Nethermind/Nethermind.State/IStorageProvider.cs
index 0c0d1ab281f..eb2f45cb5f1 100644
--- a/src/Nethermind/Nethermind.State/IStorageProvider.cs
+++ b/src/Nethermind/Nethermind.State/IStorageProvider.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -18,20 +18,67 @@
namespace Nethermind.State
{
- public interface IStorageProvider : IJournal
+ ///
+ /// Interface for the StorageProvider
+ /// Includes both persistent and transient storage
+ ///
+ public interface IStorageProvider : IJournal
{
+ ///
+ /// Return the original persistent storage value from the storage cell
+ ///
+ ///
+ ///
byte[] GetOriginal(StorageCell storageCell);
+ ///
+ /// Get the persistent storage value at the specified storage cell
+ ///
+ /// Storage location
+ /// Value at cell
byte[] Get(StorageCell storageCell);
+ ///
+ /// Set the provided value to persistent storage at the specified storage cell
+ ///
+ /// Storage location
+ /// Value to store
void Set(StorageCell storageCell, byte[] newValue);
+ ///
+ /// Get the transient storage value at the specified storage cell
+ ///
+ /// Storage location
+ /// Value at cell
+ byte[] GetTransientState(StorageCell storageCell);
+
+ ///
+ /// Set the provided value to transient storage at the specified storage cell
+ ///
+ /// Storage location
+ /// Value to store
+ void SetTransientState(StorageCell storageCell, byte[] newValue);
+
+ ///
+ /// Reset all storage
+ ///
void Reset();
+ ///
+ /// Commit persisent storage trees
+ ///
+ /// Current block number
void CommitTrees(long blockNumber);
+ ///
+ /// Commit persistent storage
+ ///
void Commit();
+ ///
+ /// Commit persistent storage
+ ///
+ /// State tracer
void Commit(IStorageTracer stateTracer);
///
@@ -43,10 +90,14 @@ public interface IStorageProvider : IJournal
/// If is true and there are already changes in then next call to
/// will use changes before this snapshot as original values for this new transaction.
///
- int TakeSnapshot(bool newTransactionStart = false);
+ Snapshot.Storage TakeSnapshot(bool newTransactionStart = false);
- int IJournal.TakeSnapshot() => TakeSnapshot();
+ Snapshot.Storage IJournal.TakeSnapshot() => TakeSnapshot();
+ ///
+ /// Clear all storage at specified address
+ ///
+ /// Contract address
void ClearStorage(Address address);
}
}
diff --git a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs
new file mode 100644
index 00000000000..823472d1436
--- /dev/null
+++ b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs
@@ -0,0 +1,328 @@
+// Copyright (c) 2021 Demerzel Solutions Limited
+// This file is part of the Nethermind library.
+//
+// The Nethermind library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The Nethermind library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the Nethermind. If not, see .
+
+using System;
+using System.Collections.Generic;
+using Nethermind.Core;
+using Nethermind.Core.Collections;
+using Nethermind.Core.Resettables;
+using Nethermind.Logging;
+
+namespace Nethermind.State
+{
+ ///
+ /// Contains common code for both Persistent and Transient storage providers
+ ///
+ public abstract class PartialStorageProviderBase
+ {
+ protected readonly ResettableDictionary> _intraBlockCache = new();
+
+ protected readonly ILogger _logger;
+
+ private const int StartCapacity = Resettable.StartCapacity;
+ private int _capacity = StartCapacity;
+ protected Change?[] _changes = new Change[StartCapacity];
+ protected int _currentPosition = Resettable.EmptyPosition;
+
+ // stack of snapshot indexes on changes for start of each transaction
+ // this is needed for OriginalValues for new transactions
+ protected readonly Stack _transactionChangesSnapshots = new();
+
+ protected static readonly byte[] _zeroValue = {0};
+
+ protected PartialStorageProviderBase(ILogManager? logManager)
+ {
+ _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager));
+ }
+
+ ///
+ /// Get the storage value at the specified storage cell
+ ///
+ /// Storage location
+ /// Value at cell
+ public byte[] Get(StorageCell storageCell)
+ {
+ return GetCurrentValue(storageCell);
+ }
+
+ ///
+ /// Set the provided value to storage at the specified storage cell
+ ///
+ /// Storage location
+ /// Value to store
+ public void Set(StorageCell storageCell, byte[] newValue)
+ {
+ PushUpdate(storageCell, newValue);
+ }
+
+ ///
+ /// Creates a restartable snapshot.
+ ///
+ /// Indicates new transaction will start here.
+ /// Snapshot index
+ public int TakeSnapshot(bool newTransactionStart)
+ {
+ if (_logger.IsTrace) _logger.Trace($"Storage snapshot {_currentPosition}");
+ if (newTransactionStart && _currentPosition != Resettable.EmptyPosition)
+ {
+ _transactionChangesSnapshots.Push(_currentPosition);
+ }
+
+ return _currentPosition;
+ }
+
+ ///
+ /// Restore the state to the provided snapshot
+ ///
+ /// Snapshot index
+ /// Throws exception if snapshot is invalid
+ public void Restore(int snapshot)
+ {
+ if (_logger.IsTrace) _logger.Trace($"Restoring storage snapshot {snapshot}");
+
+ if (snapshot > _currentPosition)
+ {
+ throw new InvalidOperationException($"{GetType().Name} tried to restore snapshot {snapshot} beyond current position {_currentPosition}");
+ }
+
+ if (snapshot == _currentPosition)
+ {
+ return;
+ }
+
+ List keptInCache = new();
+
+ for (int i = 0; i < _currentPosition - snapshot; i++)
+ {
+ Change change = _changes[_currentPosition - i];
+ if (_intraBlockCache[change!.StorageCell].Count == 1)
+ {
+ if (_changes[_intraBlockCache[change.StorageCell].Peek()]!.ChangeType == ChangeType.JustCache)
+ {
+ int actualPosition = _intraBlockCache[change.StorageCell].Pop();
+ if (actualPosition != _currentPosition - i)
+ {
+ throw new InvalidOperationException($"Expected actual position {actualPosition} to be equal to {_currentPosition} - {i}");
+ }
+
+ keptInCache.Add(change);
+ _changes[actualPosition] = null;
+ continue;
+ }
+ }
+
+ int forAssertion = _intraBlockCache[change.StorageCell].Pop();
+ if (forAssertion != _currentPosition - i)
+ {
+ throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}");
+ }
+
+ _changes[_currentPosition - i] = null;
+
+ if (_intraBlockCache[change.StorageCell].Count == 0)
+ {
+ _intraBlockCache.Remove(change.StorageCell);
+ }
+ }
+
+ _currentPosition = snapshot;
+ foreach (Change kept in keptInCache)
+ {
+ _currentPosition++;
+ _changes[_currentPosition] = kept;
+ _intraBlockCache[kept.StorageCell].Push(_currentPosition);
+ }
+
+ while (_transactionChangesSnapshots.TryPeek(out int lastOriginalSnapshot) && lastOriginalSnapshot > snapshot)
+ {
+ _transactionChangesSnapshots.Pop();
+ }
+
+ }
+
+ ///
+ /// Commit persistent storage
+ ///
+ public void Commit()
+ {
+ Commit(NullStorageTracer.Instance);
+ }
+
+ protected readonly struct ChangeTrace
+ {
+ public ChangeTrace(byte[]? before, byte[]? after)
+ {
+ After = after ?? _zeroValue;
+ Before = before ?? _zeroValue;
+ }
+
+ public ChangeTrace(byte[]? after)
+ {
+ After = after ?? _zeroValue;
+ Before = _zeroValue;
+ }
+
+ public byte[] Before { get; }
+ public byte[] After { get; }
+ }
+
+ ///
+ /// Commit persistent storage
+ ///
+ /// State tracer
+ public void Commit(IStorageTracer tracer)
+ {
+ if (_currentPosition == Snapshot.EmptyPosition)
+ {
+ if (_logger.IsTrace) _logger.Trace("No storage changes to commit");
+ }
+ else
+ {
+ CommitCore(tracer);
+ }
+ }
+
+ ///
+ /// Called by Commit
+ /// Used for storage-specific logic
+ ///
+ /// Storage tracer
+ protected virtual void CommitCore(IStorageTracer tracer)
+ {
+ Resettable.Reset(ref _changes, ref _capacity, ref _currentPosition);
+ _intraBlockCache.Reset();
+ _transactionChangesSnapshots.Clear();
+ }
+
+ ///
+ /// Reset the storage state
+ ///
+ public virtual void Reset()
+ {
+ if (_logger.IsTrace) _logger.Trace("Resetting storage");
+
+ _intraBlockCache.Clear();
+ _transactionChangesSnapshots.Clear();
+ _currentPosition = -1;
+ Array.Clear(_changes, 0, _changes.Length);
+ }
+
+ ///
+ /// Attempt to get the current value at the storage cell
+ ///
+ /// Storage location
+ /// Resulting value
+ /// True if value has been set
+ protected bool TryGetCachedValue(StorageCell storageCell, out byte[]? bytes)
+ {
+ if (_intraBlockCache.TryGetValue(storageCell, out StackList stack))
+ {
+ int lastChangeIndex = stack.Peek();
+ {
+ bytes = _changes[lastChangeIndex]!.Value;
+ return true;
+ }
+ }
+
+ bytes = null;
+ return false;
+ }
+
+ ///
+ /// Get the current value at the specified location
+ ///
+ /// Storage location
+ /// Value at location
+ protected abstract byte[] GetCurrentValue(StorageCell storageCell);
+
+ ///
+ /// Update the storage cell with provided value
+ ///
+ /// Storage location
+ /// Value to set
+ private void PushUpdate(StorageCell cell, byte[] value)
+ {
+ SetupRegistry(cell);
+ IncrementChangePosition();
+ _intraBlockCache[cell].Push(_currentPosition);
+ _changes[_currentPosition] = new Change(ChangeType.Update, cell, value);
+ }
+
+ ///
+ /// Increment position and size (if needed) of _changes
+ ///
+ protected void IncrementChangePosition()
+ {
+ Resettable.IncrementPosition(ref _changes, ref _capacity, ref _currentPosition);
+ }
+
+ ///
+ /// Initialize the StackList at the storage cell position if needed
+ ///
+ ///
+ protected void SetupRegistry(StorageCell cell)
+ {
+ if (!_intraBlockCache.ContainsKey(cell))
+ {
+ _intraBlockCache[cell] = new StackList();
+ }
+ }
+
+ ///
+ /// Clear all storage at specified address
+ ///
+ /// Contract address
+ public virtual void ClearStorage(Address address)
+ {
+ // We are setting cached values to zero so we do not use previously set values
+ // when the contract is revived with CREATE2 inside the same block
+ foreach (KeyValuePair> cellByAddress in _intraBlockCache)
+ {
+ if (cellByAddress.Key.Address == address)
+ {
+ Set(cellByAddress.Key, _zeroValue);
+ }
+ }
+ }
+
+ ///
+ /// Used for tracking each change to storage
+ ///
+ protected class Change
+ {
+ public Change(ChangeType changeType, StorageCell storageCell, byte[] value)
+ {
+ StorageCell = storageCell;
+ Value = value;
+ ChangeType = changeType;
+ }
+
+ public ChangeType ChangeType { get; }
+ public StorageCell StorageCell { get; }
+ public byte[] Value { get; }
+ }
+
+ ///
+ /// Type of change to track
+ ///
+ protected enum ChangeType
+ {
+ JustCache,
+ Update,
+ Destroy,
+ }
+ }
+}
diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs
new file mode 100644
index 00000000000..1a61a142a77
--- /dev/null
+++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs
@@ -0,0 +1,297 @@
+// Copyright (c) 2021 Demerzel Solutions Limited
+// This file is part of the Nethermind library.
+//
+// The Nethermind library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The Nethermind library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the Nethermind. If not, see .
+
+using System;
+using System.Collections.Generic;
+using Nethermind.Core;
+using Nethermind.Core.Collections;
+using Nethermind.Core.Crypto;
+using Nethermind.Core.Extensions;
+using Nethermind.Core.Resettables;
+using Nethermind.Logging;
+using Nethermind.Trie.Pruning;
+
+namespace Nethermind.State
+{
+ ///
+ /// Manages persistent storage allowing for snapshotting and restoring
+ /// Persists data to ITrieStore
+ ///
+ public class PersistentStorageProvider : PartialStorageProviderBase
+ {
+ private readonly ITrieStore _trieStore;
+ private readonly IStateProvider _stateProvider;
+ private readonly ILogManager? _logManager;
+ private readonly ResettableDictionary _storages = new();
+ ///
+ /// EIP-1283
+ ///
+ private readonly ResettableDictionary _originalValues = new();
+ private readonly ResettableHashSet _committedThisRound = new();
+
+ public PersistentStorageProvider(ITrieStore? trieStore, IStateProvider? stateProvider, ILogManager? logManager)
+ : base(logManager)
+ {
+ _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore));
+ _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider));
+ _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager));
+ }
+
+ ///
+ /// Reset the storage state
+ ///
+ public override void Reset()
+ {
+ base.Reset();
+ _storages.Reset();
+ _originalValues.Clear();
+ _committedThisRound.Clear();
+ }
+
+ ///
+ /// Get the current value at the specified location
+ ///
+ /// Storage location
+ /// Value at location
+ protected override byte[] GetCurrentValue(StorageCell storageCell) =>
+ TryGetCachedValue(storageCell, out byte[]? bytes) ? bytes! : LoadFromTree(storageCell);
+
+ ///
+ /// Return the original persistent storage value from the storage cell
+ ///
+ ///
+ ///
+ public byte[] GetOriginal(StorageCell storageCell)
+ {
+ if (!_originalValues.ContainsKey(storageCell))
+ {
+ throw new InvalidOperationException("Get original should only be called after get within the same caching round");
+ }
+
+ if (_transactionChangesSnapshots.TryPeek(out int snapshot))
+ {
+ if (_intraBlockCache.TryGetValue(storageCell, out StackList stack))
+ {
+ if (stack.TryGetSearchedItem(snapshot, out int lastChangeIndexBeforeOriginalSnapshot))
+ {
+ return _changes[lastChangeIndexBeforeOriginalSnapshot]!.Value;
+ }
+ }
+ }
+
+ return _originalValues[storageCell];
+ }
+
+
+ ///
+ /// Called by Commit
+ /// Used for persistent storage specific logic
+ ///
+ /// Storage tracer
+ protected override void CommitCore(IStorageTracer tracer)
+ {
+ if (_logger.IsTrace) _logger.Trace("Committing storage changes");
+
+ if (_changes[_currentPosition] is null)
+ {
+ throw new InvalidOperationException($"Change at current position {_currentPosition} was null when commiting {nameof(PartialStorageProviderBase)}");
+ }
+
+ if (_changes[_currentPosition + 1] != null)
+ {
+ throw new InvalidOperationException($"Change after current position ({_currentPosition} + 1) was not null when commiting {nameof(PartialStorageProviderBase)}");
+ }
+
+ HashSet toUpdateRoots = new();
+
+ bool isTracing = tracer.IsTracingStorage;
+ Dictionary? trace = null;
+ if (isTracing)
+ {
+ trace = new Dictionary();
+ }
+
+ for (int i = 0; i <= _currentPosition; i++)
+ {
+ Change change = _changes[_currentPosition - i];
+ if (!isTracing && change!.ChangeType == ChangeType.JustCache)
+ {
+ continue;
+ }
+
+ if (_committedThisRound.Contains(change!.StorageCell))
+ {
+ if (isTracing && change.ChangeType == ChangeType.JustCache)
+ {
+ trace![change.StorageCell] = new ChangeTrace(change.Value, trace[change.StorageCell].After);
+ }
+
+ continue;
+ }
+
+ if (isTracing && change.ChangeType == ChangeType.JustCache)
+ {
+ tracer!.ReportStorageRead(change.StorageCell);
+ }
+
+ _committedThisRound.Add(change.StorageCell);
+
+ if (change.ChangeType == ChangeType.Destroy)
+ {
+ continue;
+ }
+
+ int forAssertion = _intraBlockCache[change.StorageCell].Pop();
+ if (forAssertion != _currentPosition - i)
+ {
+ throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}");
+ }
+
+ switch (change.ChangeType)
+ {
+ case ChangeType.Destroy:
+ break;
+ case ChangeType.JustCache:
+ break;
+ case ChangeType.Update:
+ if (_logger.IsTrace)
+ {
+ _logger.Trace($" Update {change.StorageCell.Address}_{change.StorageCell.Index} V = {change.Value.ToHexString(true)}");
+ }
+
+ StorageTree tree = GetOrCreateStorage(change.StorageCell.Address);
+ Db.Metrics.StorageTreeWrites++;
+ toUpdateRoots.Add(change.StorageCell.Address);
+ tree.Set(change.StorageCell.Index, change.Value);
+ if (isTracing)
+ {
+ trace![change.StorageCell] = new ChangeTrace(change.Value);
+ }
+
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ // TODO: it seems that we are unnecessarily recalculating root hashes all the time in storage?
+ foreach (Address address in toUpdateRoots)
+ {
+ // since the accounts could be empty accounts that are removing (EIP-158)
+ if (_stateProvider.AccountExists(address))
+ {
+ Keccak root = RecalculateRootHash(address);
+
+ // _logger.Warn($"Recalculating storage root {address}->{root} ({toUpdateRoots.Count})");
+ _stateProvider.UpdateStorageRoot(address, root);
+ }
+ }
+
+ base.CommitCore(tracer);
+ _originalValues.Reset();
+ _committedThisRound.Reset();
+
+ if (isTracing)
+ {
+ ReportChanges(tracer!, trace!);
+ }
+ }
+
+ ///
+ /// Commit persisent storage trees
+ ///
+ /// Current block number
+ public void CommitTrees(long blockNumber)
+ {
+ // _logger.Warn($"Storage block commit {blockNumber}");
+ foreach (KeyValuePair storage in _storages)
+ {
+ storage.Value.Commit(blockNumber);
+ }
+
+ // TODO: maybe I could update storage roots only now?
+
+ // only needed here as there is no control over cached storage size otherwise
+ _storages.Reset();
+ }
+
+ private StorageTree GetOrCreateStorage(Address address)
+ {
+ if (!_storages.ContainsKey(address))
+ {
+ StorageTree storageTree = new(_trieStore, _stateProvider.GetStorageRoot(address), _logManager);
+ return _storages[address] = storageTree;
+ }
+
+ return _storages[address];
+ }
+
+ private byte[] LoadFromTree(StorageCell storageCell)
+ {
+ StorageTree tree = GetOrCreateStorage(storageCell.Address);
+
+ Db.Metrics.StorageTreeReads++;
+ byte[] value = tree.Get(storageCell.Index);
+ PushToRegistryOnly(storageCell, value);
+ return value;
+ }
+
+ private void PushToRegistryOnly(StorageCell cell, byte[] value)
+ {
+ SetupRegistry(cell);
+ IncrementChangePosition();
+ _intraBlockCache[cell].Push(_currentPosition);
+ _originalValues[cell] = value;
+ _changes[_currentPosition] = new Change(ChangeType.JustCache, cell, value);
+ }
+
+ private static void ReportChanges(IStorageTracer tracer, Dictionary trace)
+ {
+ foreach ((StorageCell address, ChangeTrace change) in trace)
+ {
+ byte[] before = change.Before;
+ byte[] after = change.After;
+
+ if (!Bytes.AreEqual(before, after))
+ {
+ tracer.ReportStorageChange(address, before, after);
+ }
+ }
+ }
+
+ private Keccak RecalculateRootHash(Address address)
+ {
+ StorageTree storageTree = GetOrCreateStorage(address);
+ storageTree.UpdateRootHash();
+ return storageTree.RootHash;
+ }
+
+ ///
+ /// Clear all storage at specified address
+ ///
+ /// Contract address
+ public override void ClearStorage(Address address)
+ {
+ base.ClearStorage(address);
+
+ // here it is important to make sure that we will not reuse the same tree when the contract is revived
+ // by means of CREATE 2 - notice that the cached trie may carry information about items that were not
+ // touched in this block, hence were not zeroed above
+ // TODO: how does it work with pruning?
+ _storages[address] = new StorageTree(_trieStore, Keccak.EmptyTreeHash, _logManager);
+ }
+ }
+}
diff --git a/src/Nethermind/Nethermind.State/Snapshot.cs b/src/Nethermind/Nethermind.State/Snapshot.cs
index 11945148fa2..a6429daef46 100644
--- a/src/Nethermind/Nethermind.State/Snapshot.cs
+++ b/src/Nethermind/Nethermind.State/Snapshot.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -26,15 +26,33 @@ namespace Nethermind.State
///
public readonly struct Snapshot
{
- public Snapshot(int stateSnapshot, int storageSnapshot)
+ public static readonly Snapshot Empty = new(EmptyPosition, Storage.Empty);
+
+ public Snapshot(int stateSnapshot, Storage storageSnapshot)
{
StateSnapshot = stateSnapshot;
StorageSnapshot = storageSnapshot;
}
+
+ ///
+ /// Tracks snapshot positions for Persistent and Transient storage
+ ///
+ public readonly struct Storage
+ {
+ public static readonly Storage Empty = new(EmptyPosition, EmptyPosition);
+
+ public int PersistentStorageSnapshot { get; }
+ public int TransientStorageSnapshot { get; }
+
+ public Storage(int storageSnapshot, int transientStorageSnapshot)
+ {
+ PersistentStorageSnapshot = storageSnapshot;
+ TransientStorageSnapshot = transientStorageSnapshot;
+ }
+ }
public int StateSnapshot { get; }
-
- public int StorageSnapshot { get; }
+ public Storage StorageSnapshot { get; }
public const int EmptyPosition = -1;
}
diff --git a/src/Nethermind/Nethermind.State/StorageProvider.cs b/src/Nethermind/Nethermind.State/StorageProvider.cs
index 387ca0f6bed..ace96841820 100644
--- a/src/Nethermind/Nethermind.State/StorageProvider.cs
+++ b/src/Nethermind/Nethermind.State/StorageProvider.cs
@@ -14,13 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the Nethermind. If not, see .
-using System;
-using System.Collections.Generic;
using Nethermind.Core;
-using Nethermind.Core.Collections;
-using Nethermind.Core.Crypto;
-using Nethermind.Core.Extensions;
-using Nethermind.Core.Resettables;
using Nethermind.Logging;
using Nethermind.Trie.Pruning;
@@ -28,436 +22,90 @@ namespace Nethermind.State
{
public class StorageProvider : IStorageProvider
{
- private readonly ResettableDictionary> _intraBlockCache = new();
-
- ///
- /// EIP-1283
- ///
- private readonly ResettableDictionary _originalValues = new();
-
- private readonly ResettableHashSet _committedThisRound = new();
-
- private readonly ILogger _logger;
-
- private readonly ITrieStore _trieStore;
-
- private readonly IStateProvider _stateProvider;
- private readonly ILogManager _logManager;
-
- private readonly ResettableDictionary _storages = new();
-
- private const int StartCapacity = Resettable.StartCapacity;
- private int _capacity = StartCapacity;
- private Change?[] _changes = new Change[StartCapacity];
- private int _currentPosition = Resettable.EmptyPosition;
-
- // stack of snapshot indexes on changes for start of each transaction
- // this is needed for OriginalValues for new transactions
- private readonly Stack _transactionChangesSnapshots = new();
+ PersistentStorageProvider _persistentStorageProvider;
+ TransientStorageProvider _transientStorageProvider;
public StorageProvider(ITrieStore? trieStore, IStateProvider? stateProvider, ILogManager? logManager)
{
- _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager));
- _logger = logManager.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager));
- _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore));
- _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider));
- }
-
- public byte[] GetOriginal(StorageCell storageCell)
- {
- if (!_originalValues.ContainsKey(storageCell))
- {
- throw new InvalidOperationException("Get original should only be called after get within the same caching round");
- }
-
- if (_transactionChangesSnapshots.TryPeek(out int snapshot))
- {
- if (_intraBlockCache.TryGetValue(storageCell, out StackList stack))
- {
- if (stack.TryGetSearchedItem(snapshot, out int lastChangeIndexBeforeOriginalSnapshot))
- {
- return _changes[lastChangeIndexBeforeOriginalSnapshot]!.Value;
- }
- }
- }
-
- return _originalValues[storageCell];
- }
-
- public byte[] Get(StorageCell storageCell)
- {
- return GetCurrentValue(storageCell);
- }
-
- public void Set(StorageCell storageCell, byte[] newValue)
- {
- PushUpdate(storageCell, newValue);
- }
-
- private Keccak RecalculateRootHash(Address address)
- {
- StorageTree storageTree = GetOrCreateStorage(address);
- storageTree.UpdateRootHash();
- return storageTree.RootHash;
+ _persistentStorageProvider = new PersistentStorageProvider(trieStore, stateProvider, logManager);
+ _transientStorageProvider = new TransientStorageProvider(logManager);
}
- int IStorageProvider.TakeSnapshot(bool newTransactionStart)
- {
- if (_logger.IsTrace) _logger.Trace($"Storage snapshot {_currentPosition}");
- if (newTransactionStart && _currentPosition != Resettable.EmptyPosition)
- {
- _transactionChangesSnapshots.Push(_currentPosition);
- }
- return _currentPosition;
- }
-
- public void Restore(int snapshot)
+ public void ClearStorage(Address address)
{
- if (_logger.IsTrace) _logger.Trace($"Restoring storage snapshot {snapshot}");
-
- if (snapshot > _currentPosition)
- {
- throw new InvalidOperationException($"{nameof(StorageProvider)} tried to restore snapshot {snapshot} beyond current position {_currentPosition}");
- }
-
- if (snapshot == _currentPosition)
- {
- return;
- }
-
- List keptInCache = new();
-
- for (int i = 0; i < _currentPosition - snapshot; i++)
- {
- Change change = _changes[_currentPosition - i];
- if (_intraBlockCache[change!.StorageCell].Count == 1)
- {
- if (_changes[_intraBlockCache[change.StorageCell].Peek()]!.ChangeType == ChangeType.JustCache)
- {
- int actualPosition = _intraBlockCache[change.StorageCell].Pop();
- if (actualPosition != _currentPosition - i)
- {
- throw new InvalidOperationException($"Expected actual position {actualPosition} to be equal to {_currentPosition} - {i}");
- }
-
- keptInCache.Add(change);
- _changes[actualPosition] = null;
- continue;
- }
- }
-
- int forAssertion = _intraBlockCache[change.StorageCell].Pop();
- if (forAssertion != _currentPosition - i)
- {
- throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}");
- }
-
- _changes[_currentPosition - i] = null;
-
- if (_intraBlockCache[change.StorageCell].Count == 0)
- {
- _intraBlockCache.Remove(change.StorageCell);
- }
- }
-
- _currentPosition = snapshot;
- foreach (Change kept in keptInCache)
- {
- _currentPosition++;
- _changes[_currentPosition] = kept;
- _intraBlockCache[kept.StorageCell].Push(_currentPosition);
- }
-
- while (_transactionChangesSnapshots.TryPeek(out int lastOriginalSnapshot) && lastOriginalSnapshot > snapshot)
- {
- _transactionChangesSnapshots.Pop();
- }
-
+ _persistentStorageProvider.ClearStorage(address);
+ _transientStorageProvider.ClearStorage(address);
}
public void Commit()
{
- Commit(NullStorageTracer.Instance);
+ _persistentStorageProvider.Commit();
+ _transientStorageProvider.Commit();
}
- private static readonly byte[] _zeroValue = {0};
-
- private readonly struct ChangeTrace
+ public void Commit(IStorageTracer stateTracer)
{
- public ChangeTrace(byte[]? before, byte[]? after)
- {
- After = after ?? _zeroValue;
- Before = before ?? _zeroValue;
- }
-
- public ChangeTrace(byte[]? after)
- {
- After = after ?? _zeroValue;
- Before = _zeroValue;
- }
-
- public byte[] Before { get; }
- public byte[] After { get; }
- }
-
- public void Commit(IStorageTracer tracer)
- {
- if (_currentPosition == -1)
- {
- if (_logger.IsTrace) _logger.Trace("No storage changes to commit");
- return;
- }
-
- if (_logger.IsTrace) _logger.Trace("Committing storage changes");
-
- if (_changes[_currentPosition] is null)
- {
- throw new InvalidOperationException($"Change at current position {_currentPosition} was null when commiting {nameof(StorageProvider)}");
- }
-
- if (_changes[_currentPosition + 1] != null)
- {
- throw new InvalidOperationException($"Change after current position ({_currentPosition} + 1) was not null when commiting {nameof(StorageProvider)}");
- }
-
- HashSet toUpdateRoots = new();
-
- bool isTracing = tracer.IsTracingStorage;
- Dictionary? trace = null;
- if (isTracing)
- {
- trace = new Dictionary();
- }
-
- for (int i = 0; i <= _currentPosition; i++)
- {
- Change change = _changes[_currentPosition - i];
- if (!isTracing && change!.ChangeType == ChangeType.JustCache)
- {
- continue;
- }
-
- if (_committedThisRound.Contains(change!.StorageCell))
- {
- if (isTracing && change.ChangeType == ChangeType.JustCache)
- {
- trace![change.StorageCell] = new ChangeTrace(change.Value, trace[change.StorageCell].After);
- }
-
- continue;
- }
-
- if (isTracing && change.ChangeType == ChangeType.JustCache)
- {
- tracer!.ReportStorageRead(change.StorageCell);
- }
-
- _committedThisRound.Add(change.StorageCell);
-
- if (change.ChangeType == ChangeType.Destroy)
- {
- continue;
- }
-
- int forAssertion = _intraBlockCache[change.StorageCell].Pop();
- if (forAssertion != _currentPosition - i)
- {
- throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {_currentPosition} - {i}");
- }
-
- switch (change.ChangeType)
- {
- case ChangeType.Destroy:
- break;
- case ChangeType.JustCache:
- break;
- case ChangeType.Update:
- if (_logger.IsTrace)
- {
- _logger.Trace($" Update {change.StorageCell.Address}_{change.StorageCell.Index} V = {change.Value.ToHexString(true)}");
- }
-
- StorageTree tree = GetOrCreateStorage(change.StorageCell.Address);
- Db.Metrics.StorageTreeWrites++;
- toUpdateRoots.Add(change.StorageCell.Address);
- tree.Set(change.StorageCell.Index, change.Value);
- if (isTracing)
- {
- trace![change.StorageCell] = new ChangeTrace(change.Value);
- }
-
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-
- // TODO: it seems that we are unnecessarily recalculating root hashes all the time in storage?
- foreach (Address address in toUpdateRoots)
- {
- // since the accounts could be empty accounts that are removing (EIP-158)
- if (_stateProvider.AccountExists(address))
- {
- Keccak root = RecalculateRootHash(address);
-
- // _logger.Warn($"Recalculating storage root {address}->{root} ({toUpdateRoots.Count})");
- _stateProvider.UpdateStorageRoot(address, root);
- }
- }
-
- Resettable.Reset(ref _changes, ref _capacity, ref _currentPosition, StartCapacity);
- _committedThisRound.Reset();
- _intraBlockCache.Reset();
- _originalValues.Reset();
- _transactionChangesSnapshots.Clear();
-
- if (isTracing)
- {
- ReportChanges(tracer!, trace!);
- }
- }
-
- private static void ReportChanges(IStorageTracer tracer, Dictionary trace)
- {
- foreach ((StorageCell address, ChangeTrace change) in trace)
- {
- byte[] before = change.Before;
- byte[] after = change.After;
-
- if (!Bytes.AreEqual(before, after))
- {
- tracer.ReportStorageChange(address, before, after);
- }
- }
- }
-
- public void Reset()
- {
- if (_logger.IsTrace) _logger.Trace("Resetting storage");
-
- _intraBlockCache.Clear();
- _originalValues.Clear();
- _transactionChangesSnapshots.Clear();
- _currentPosition = -1;
- _committedThisRound.Clear();
- Array.Clear(_changes, 0, _changes.Length);
- _storages.Reset();
+ _persistentStorageProvider.Commit(stateTracer);
+ _transientStorageProvider.Commit(stateTracer);
}
public void CommitTrees(long blockNumber)
{
- // _logger.Warn($"Storage block commit {blockNumber}");
- foreach (KeyValuePair storage in _storages)
- {
- storage.Value.Commit(blockNumber);
- }
-
- // TODO: maybe I could update storage roots only now?
-
- // only needed here as there is no control over cached storage size otherwise
- _storages.Reset();
+ _persistentStorageProvider.CommitTrees(blockNumber);
}
- private StorageTree GetOrCreateStorage(Address address)
+ public byte[] Get(StorageCell storageCell)
{
- if (!_storages.ContainsKey(address))
- {
- StorageTree storageTree = new(_trieStore, _stateProvider.GetStorageRoot(address), _logManager);
- return _storages[address] = storageTree;
- }
-
- return _storages[address];
+ return _persistentStorageProvider.Get(storageCell);
}
- private byte[] GetCurrentValue(StorageCell storageCell)
+ public byte[] GetOriginal(StorageCell storageCell)
{
- if (_intraBlockCache.TryGetValue(storageCell, out StackList stack))
- {
- int lastChangeIndex = stack.Peek();
- return _changes[lastChangeIndex]!.Value;
- }
-
- return LoadFromTree(storageCell);
+ return _persistentStorageProvider.GetOriginal(storageCell);
}
- private byte[] LoadFromTree(StorageCell storageCell)
+ public byte[] GetTransientState(StorageCell storageCell)
{
- StorageTree tree = GetOrCreateStorage(storageCell.Address);
-
- Db.Metrics.StorageTreeReads++;
- byte[] value = tree.Get(storageCell.Index);
- PushToRegistryOnly(storageCell, value);
- return value;
+ return _transientStorageProvider.Get(storageCell);
}
- private void PushToRegistryOnly(StorageCell cell, byte[] value)
+ public void Reset()
{
- SetupRegistry(cell);
- IncrementChangePosition();
- _intraBlockCache[cell].Push(_currentPosition);
- _originalValues[cell] = value;
- _changes[_currentPosition] = new Change(ChangeType.JustCache, cell, value);
+ _persistentStorageProvider.Reset();
+ _transientStorageProvider.Reset();
}
- private void PushUpdate(StorageCell cell, byte[] value)
+ ///
+ /// Convenience for test cases
+ ///
+ ///
+ internal void Restore(int snapshot)
{
- SetupRegistry(cell);
- IncrementChangePosition();
- _intraBlockCache[cell].Push(_currentPosition);
- _changes[_currentPosition] = new Change(ChangeType.Update, cell, value);
+ Restore(new Snapshot.Storage(snapshot, Snapshot.EmptyPosition));
}
- private void IncrementChangePosition()
+ public void Restore(Snapshot.Storage snapshot)
{
- Resettable.IncrementPosition(ref _changes, ref _capacity, ref _currentPosition);
+ _persistentStorageProvider.Restore(snapshot.PersistentStorageSnapshot);
+ _transientStorageProvider.Restore(snapshot.TransientStorageSnapshot);
}
- private void SetupRegistry(StorageCell cell)
+ public void Set(StorageCell storageCell, byte[] newValue)
{
- if (!_intraBlockCache.ContainsKey(cell))
- {
- _intraBlockCache[cell] = new StackList();
- }
+ _persistentStorageProvider.Set(storageCell, newValue);
}
- private class Change
+ public void SetTransientState(StorageCell storageCell, byte[] newValue)
{
- public Change(ChangeType changeType, StorageCell storageCell, byte[] value)
- {
- StorageCell = storageCell;
- Value = value;
- ChangeType = changeType;
- }
-
- public ChangeType ChangeType { get; }
- public StorageCell StorageCell { get; }
- public byte[] Value { get; }
+ _transientStorageProvider.Set(storageCell, newValue);
}
- public void ClearStorage(Address address)
+ Snapshot.Storage IStorageProvider.TakeSnapshot(bool newTransactionStart)
{
- /* we are setting cached values to zero so we do not use previously set values
- when the contract is revived with CREATE2 inside the same block */
- foreach (var cellByAddress in _intraBlockCache)
- {
- if (cellByAddress.Key.Address == address)
- {
- Set(cellByAddress.Key, _zeroValue);
- }
- }
+ int persistentSnapshot = _persistentStorageProvider.TakeSnapshot(newTransactionStart);
+ int transientSnapshot = _transientStorageProvider.TakeSnapshot(newTransactionStart);
- /* here it is important to make sure that we will not reuse the same tree when the contract is revived
- by means of CREATE 2 - notice that the cached trie may carry information about items that were not
- touched in this block, hence were not zeroed above */
- // TODO: how does it work with pruning?
- _storages[address] = new StorageTree(_trieStore, Keccak.EmptyTreeHash, _logManager);
- }
-
- private enum ChangeType
- {
- JustCache,
- Update,
- Destroy,
+ return new Snapshot.Storage(persistentSnapshot, transientSnapshot);
}
}
}
diff --git a/src/Nethermind/Nethermind.State/TransientStorageProvider.cs b/src/Nethermind/Nethermind.State/TransientStorageProvider.cs
new file mode 100644
index 00000000000..73cdd945f7f
--- /dev/null
+++ b/src/Nethermind/Nethermind.State/TransientStorageProvider.cs
@@ -0,0 +1,39 @@
+// Copyright (c) 2021 Demerzel Solutions Limited
+// This file is part of the Nethermind library.
+//
+// The Nethermind library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The Nethermind library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the Nethermind. If not, see .
+
+using Nethermind.Core;
+using Nethermind.Logging;
+
+namespace Nethermind.State
+{
+ ///
+ /// EIP-1153 provides a transient store for contracts that doesn't persist
+ /// storage across calls. Reverts will rollback any transient state changes.
+ ///
+ public class TransientStorageProvider : PartialStorageProviderBase
+ {
+ public TransientStorageProvider(ILogManager? logManager)
+ : base(logManager) { }
+
+ ///
+ /// Get the storage value at the specified storage cell
+ ///
+ /// Storage location
+ /// Value at cell
+ protected override byte[] GetCurrentValue(StorageCell storageCell) =>
+ TryGetCachedValue(storageCell, out byte[]? bytes) ? bytes! : _zeroValue;
+ }
+}
diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs
index 628a25c9f36..37ab5b1d0e2 100644
--- a/src/Nethermind/Nethermind.State/WorldState.cs
+++ b/src/Nethermind/Nethermind.State/WorldState.cs
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Demerzel Solutions Limited
+// Copyright (c) 2021 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
@@ -25,7 +25,8 @@ public class WorldState : IWorldState
public Snapshot TakeSnapshot(bool newTransactionStart = false)
{
- return new (StateProvider.TakeSnapshot(), StorageProvider.TakeSnapshot(newTransactionStart));
+ Snapshot.Storage storageSnapshot = StorageProvider.TakeSnapshot(newTransactionStart);
+ return new (StateProvider.TakeSnapshot(), storageSnapshot);
}
public void Restore(Snapshot snapshot)