diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs index 2fe35fd2f03..d84d84cad0c 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs @@ -68,6 +68,7 @@ public void Base_fee_opcode_should_return_expected_results(bool eip3198Enabled, if (send1559Tx) { transaction.DecodedMaxFeePerGas = (UInt256)baseFee; + transaction.GasPrice = (UInt256)baseFee; transaction.Type = TxType.EIP1559; } else diff --git a/src/Nethermind/Nethermind.Evm/Tracing/Proofs/ProofTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/Proofs/ProofTxTracer.cs index 8e9705d3591..8abc3f8de24 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/Proofs/ProofTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/Proofs/ProofTxTracer.cs @@ -51,7 +51,7 @@ public ProofTxTracer(bool treatSystemAccountDifferently) public bool IsTracingStack => false; public bool IsTracingState => true; public bool IsTracingStorage => true; - + public void ReportActionEnd(long gas, Address deploymentAddress, ReadOnlyMemory deployedCode) { throw new NotSupportedException(); diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs index fb75e77eda6..a76f43ea067 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/ITransactionProcessor.cs @@ -40,5 +40,10 @@ public interface ITransactionProcessor /// Call transaction, no validations, commit state /// void Trace(Transaction transaction, BlockHeader block, ITxTracer txTracer); + + /// + /// Call transaction, rollback state, do not check base fee when gas pricing is not passed + /// + void CallWithNoBaseFee(Transaction transaction, BlockHeader block, ITxTracer txTracer); } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/ReadOnlyTransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/ReadOnlyTransactionProcessor.cs index d6158cc2794..87d465e0046 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/ReadOnlyTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/ReadOnlyTransactionProcessor.cs @@ -51,6 +51,11 @@ public void BuildUp(Transaction transaction, BlockHeader block, ITxTracer txTrac public void Trace(Transaction transaction, BlockHeader block, ITxTracer txTracer) => _transactionProcessor.Trace(transaction, block, txTracer); + public void CallWithNoBaseFee(Transaction transaction, BlockHeader block, ITxTracer txTracer) + { + _transactionProcessor.CallWithNoBaseFee(transaction, block, txTracer); + } + public bool IsContractDeployed(Address address) => _stateProvider.IsContract(address); diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index a4ee12a276f..00efc4951c3 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -67,7 +67,12 @@ private enum ExecutionOptions /// /// Commit and later restore state also skip validation, use for CallAndRestore /// - CommitAndRestore = Commit | Restore | NoValidation + CommitAndRestore = Commit | Restore | NoValidation, + + /// + /// Commit and later restore state also skip base fee validation if gas pricing is not passed + /// + NoBaseFee = CommitAndRestore | 8 } public TransactionProcessor( @@ -116,6 +121,11 @@ public void Trace(Transaction transaction, BlockHeader block, ITxTracer txTracer Execute(transaction, block, txTracer, ExecutionOptions.NoValidation); } + public void CallWithNoBaseFee(Transaction transaction, BlockHeader block, ITxTracer txTracer) + { + Execute(transaction, block, txTracer, ExecutionOptions.NoBaseFee); + } + private void QuickFail(Transaction tx, BlockHeader block, ITxTracer txTracer, bool eip658NotEnabled, string? reason) { @@ -138,6 +148,19 @@ private void QuickFail(Transaction tx, BlockHeader block, ITxTracer txTracer, bo } } + private bool ShouldSkipGasPricing(IReleaseSpec spec, Transaction transaction) + { + if (!spec.IsEip1559Enabled) return transaction.GasPrice.IsZero; + + if (transaction.IsEip1559) + { + return transaction.MaxFeePerGas.IsZero && transaction.MaxPriorityFeePerGas.IsZero; + } + + return transaction.GasPrice.IsZero; + + } + private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTracer, ExecutionOptions executionOptions) { @@ -152,6 +175,8 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra //we commit only after all block is constructed bool notSystemTransaction = !transaction.IsSystem(); bool deleteCallerAccount = false; + bool noBaseFee = (executionOptions & ExecutionOptions.NoBaseFee) == ExecutionOptions.NoBaseFee; + bool validatePricing = !noValidation || noBaseFee; IReleaseSpec spec = _specProvider.GetSpec(block.Number); if (!notSystemTransaction) @@ -159,8 +184,29 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra spec = new SystemTransactionReleaseSpec(spec); } + bool skipGasPricing = ShouldSkipGasPricing(spec, transaction); + UInt256 value = transaction.Value; + if (!noBaseFee || transaction.MaxFeePerGas > 0 || transaction.MaxPriorityFeePerGas > 0) + { + if (validatePricing && transaction.MaxFeePerGas < block.BaseFeePerGas) + { + TraceLogInvalidTx(transaction, "MAX FEE PER GAS LESS THAN BLOCK BASE FEE"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, + $"max fee per gas less than block base fee: address {transaction.SenderAddress}, maxFeePerGas: {transaction.MaxFeePerGas}, baseFee {block.BaseFeePerGas}"); + return; + } + + if (validatePricing && transaction.MaxFeePerGas < transaction.MaxPriorityFeePerGas) + { + TraceLogInvalidTx(transaction, "MAX FEE PER GAS LESS THAN MAX PRIORITY FEE PER GAS"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, + $"max fee per gas less than max priority fee per gas: address {transaction.SenderAddress}, maxFeePerGas: {transaction.MaxFeePerGas}, maxPriorityFeePerGas: {transaction.MaxPriorityFeePerGas}"); + return; + } + } + if (!transaction.TryCalculatePremiumPerGas(block.BaseFeePerGas, out UInt256 premiumPerGas) && !noValidation) { TraceLogInvalidTx(transaction, "MINER_PREMIUM_IS_NEGATIVE"); @@ -200,7 +246,7 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra if (gasLimit < intrinsicGas) { TraceLogInvalidTx(transaction, $"GAS_LIMIT_BELOW_INTRINSIC_GAS {gasLimit} < {intrinsicGas}"); - QuickFail(transaction, block, txTracer, eip658NotEnabled, "gas limit below intrinsic gas"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, $"gas limit below intrinsic gas: have {gasLimit}, want {intrinsicGas}"); return; } @@ -208,7 +254,7 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra { TraceLogInvalidTx(transaction, $"BLOCK_GAS_LIMIT_EXCEEDED {gasLimit} > {block.GasLimit} - {block.GasUsed}"); - QuickFail(transaction, block, txTracer, eip658NotEnabled, "block gas limit exceeded"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, $"block gas limit exceeded: gasLimit {gasLimit} > block gasLimit {block.GasLimit} - block gasUsed {block.GasUsed}"); return; } } @@ -249,24 +295,26 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra if (notSystemTransaction) { - UInt256 senderBalance = _stateProvider.GetBalance(caller); - if (!noValidation && ((ulong)intrinsicGas * effectiveGasPrice + value > senderBalance || - senderReservedGasPayment + value > senderBalance)) + if (!noBaseFee || !skipGasPricing) { - TraceLogInvalidTx(transaction, - $"INSUFFICIENT_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}"); - QuickFail(transaction, block, txTracer, eip658NotEnabled, "insufficient sender balance"); - return; - } + UInt256 senderBalance = _stateProvider.GetBalance(caller); + if (validatePricing && ((ulong)intrinsicGas * effectiveGasPrice + value > senderBalance || + senderReservedGasPayment + value > senderBalance)) + { + TraceLogInvalidTx(transaction, + $"INSUFFICIENT_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, $"insufficient funds for gas * price + value: address {caller}, have {senderBalance}, want {UInt256.Max(senderReservedGasPayment + value, (ulong)intrinsicGas * effectiveGasPrice + value)}"); + return; + } - if (!noValidation && spec.IsEip1559Enabled && !transaction.IsFree() && - senderBalance < (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + value) - { - TraceLogInvalidTx(transaction, - $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {transaction.MaxFeePerGas}"); - QuickFail(transaction, block, txTracer, eip658NotEnabled, - "insufficient MaxFeePerGas for sender balance"); - return; + if (validatePricing && spec.IsEip1559Enabled && !transaction.IsFree() && + senderBalance < (UInt256)transaction.GasLimit * transaction.MaxFeePerGas + value) + { + TraceLogInvalidTx(transaction, + $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({caller})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {transaction.MaxFeePerGas}"); + QuickFail(transaction, block, txTracer, eip658NotEnabled, $"insufficient MaxFeePerGas for sender balance: address {{caller}}, sender_balance = {senderBalance}, maxFeePerGas: {transaction.MaxFeePerGas}"); + return; + } } if (transaction.Nonce != _stateProvider.GetNonce(caller)) diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index 9322239bec5..66b1d572b26 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -165,11 +165,11 @@ public CallOutput(byte[] outputData, long gasSpent, string error, bool inputErro public AccessList? AccessList { get; set; } } - public CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken) + public CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool noBaseFee = false) { CallOutputTracer callOutputTracer = new(); (bool Success, string Error) tryCallResult = TryCallAndRestore(header, header.Timestamp, tx, false, - callOutputTracer.WithCancellation(cancellationToken)); + callOutputTracer.WithCancellation(cancellationToken), noBaseFee); return new CallOutput { Error = tryCallResult.Success ? callOutputTracer.Error : tryCallResult.Error, @@ -226,11 +226,12 @@ public CallOutput CreateAccessList(BlockHeader header, Transaction tx, Cancellat in UInt256 timestamp, Transaction transaction, bool treatBlockHeaderAsParentBlock, - ITxTracer tracer) + ITxTracer tracer, + bool noBaseFee = false) { try { - CallAndRestore(blockHeader, timestamp, transaction, treatBlockHeaderAsParentBlock, tracer); + CallAndRestore(blockHeader, timestamp, transaction, treatBlockHeaderAsParentBlock, tracer, noBaseFee); return (true, string.Empty); } catch (InsufficientBalanceException ex) @@ -244,7 +245,8 @@ private void CallAndRestore( in UInt256 timestamp, Transaction transaction, bool treatBlockHeaderAsParentBlock, - ITxTracer tracer) + ITxTracer tracer, + bool noBaseFee = false) { if (transaction.SenderAddress == null) { @@ -274,7 +276,15 @@ private void CallAndRestore( : blockHeader.BaseFeePerGas; transaction.Hash = transaction.CalculateHash(); - _transactionProcessor.CallAndRestore(transaction, callHeader, tracer); + if (noBaseFee) + { + _transactionProcessor.CallWithNoBaseFee(transaction, callHeader, tracer); + } + else + { + _transactionProcessor.CallAndRestore(transaction, callHeader, tracer); + } + } finally { diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs index fec4f798313..614f7144fc4 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridgeContract.cs @@ -50,7 +50,7 @@ public ConstantBridgeContract(Contract contract, IBlockchainBridge blockchainBri public override object[] Call(CallInfo callInfo) { var transaction = GenerateTransaction(callInfo); - var result = _blockchainBridge.Call(callInfo.ParentHeader, transaction, CancellationToken.None); + var result = _blockchainBridge.Call(callInfo.ParentHeader, transaction, CancellationToken.None, false); if (!string.IsNullOrEmpty(result.Error)) { throw new AbiException(result.Error); diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index 31147c67786..e5a2d72b39d 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -35,7 +35,7 @@ public interface IBlockchainBridge : ILogFinder TxReceipt GetReceipt(Keccak txHash); (TxReceipt Receipt, UInt256? EffectiveGasPrice) GetReceiptAndEffectiveGasPrice(Keccak txHash); (TxReceipt Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Keccak txHash); - BlockchainBridge.CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken); + BlockchainBridge.CallOutput Call(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool noBaseFee); BlockchainBridge.CallOutput EstimateGas(BlockHeader header, Transaction tx, CancellationToken cancellationToken); BlockchainBridge.CallOutput CreateAccessList(BlockHeader header, Transaction tx, CancellationToken cancellationToken, bool optimize); ulong GetChainId(); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs index 382553fc325..27b510e7c22 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs @@ -36,13 +36,16 @@ namespace Nethermind.JsonRpc.Test.Modules.Eth public partial class EthRpcModuleTests { [Test] - public async Task Eth_estimateGas_web3_should_return_insufficient_balance_error() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Eth_estimateGas_web3_should_return_insufficient_balance_error(string type) { using Context ctx = await Context.Create(); Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"value\": 500}"); + "{"+type+"\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"value\": 500}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual( @@ -50,15 +53,36 @@ public async Task Eth_estimateGas_web3_should_return_insufficient_balance_error( serialized); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); } - + + [Test] + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Eth_estimateGas_web3_without_gas_pricing_and_value(string type) + { + using Context ctx = await Context.Create(); + Address someAccount = new("0x0001020304050607080910111213141516171819"); + ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); + TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( + "{" + type + "\"from\":\"0x0001020304050607080910111213141516171819\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + string serialized = + ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); + Assert.AreEqual( + "{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}", + serialized); + ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); + } [Test] - public async Task Eth_estimateGas_web3_sample_not_enough_gas_system_account() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Eth_estimateGas_web3_sample_not_enough_gas_system_account(string type) { using Context ctx = await Context.Create(); ctx._test.ReadOnlyState.AccountExists(Address.SystemUser).Should().BeFalse(); - TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize("{"+type+ + "\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}", serialized); @@ -66,13 +90,16 @@ public async Task Eth_estimateGas_web3_sample_not_enough_gas_system_account() } [Test] - public async Task Eth_estimateGas_web3_sample_not_enough_gas_other_account() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Eth_estimateGas_web3_sample_not_enough_gas_other_account(string type) { using Context ctx = await Context.Create(); Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{" + type + "\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}", serialized); @@ -80,13 +107,16 @@ public async Task Eth_estimateGas_web3_sample_not_enough_gas_other_account() } [Test] - public async Task Eth_estimateGas_web3_above_block_gas_limit() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Eth_estimateGas_web3_above_block_gas_limit(string type) { using Context ctx = await Context.Create(); Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gas\":\"0x100000000\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{"+type+"\"from\":\"0x0001020304050607080910111213141516171819\",\"gas\":\"0x100000000\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}", serialized); @@ -173,52 +203,63 @@ public async Task Eth_estimate_gas_is_lower_with_optimized_access_list() } [Test] - public async Task Estimate_gas_without_gas_pricing() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Estimate_gas_without_gas_pricing(string type) { using Context ctx = await Context.Create(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{" + type + "\"from\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}", serialized); } [Test] - public async Task Estimate_gas_with_gas_pricing() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + public async Task Estimate_gas_with_gas_pricing(string type) { using Context ctx = await Context.Create(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + "{" + type + "\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}", serialized); } [Test] - public async Task Estimate_gas_without_gas_pricing_after_1559_legacy() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x2\", ")] + public async Task Estimate_gas_without_gas_pricing_after_1559_legacy(string type) { using Context ctx = await Context.CreateWithLondonEnabled(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + "{" + type + "\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}", serialized); } - + [Test] - public async Task Estimate_gas_without_gas_pricing_after_1559_new_type_of_transaction() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x2\", ")] + public async Task Estimate_gas_without_gas_pricing_after_1559_new_type_of_transaction(string type) { using Context ctx = await Context.CreateWithLondonEnabled(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\"}"); + "{" + type + "\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\"}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}", serialized); - byte[] code = Prepare.EvmCode - .Op(Instruction.BASEFEE) - .PushData(0) - .Op(Instruction.SSTORE) - .Done; } [Test] - public async Task Estimate_gas_with_base_fee_opcode() + [TestCase("")] + [TestCase("\"type\":null, ")] + [TestCase("\"type\":\"0x0\", ")] + [TestCase("\"type\":\"0x2\", ")] + public async Task Estimate_gas_with_base_fee_opcode(string type) { using Context ctx = await Context.CreateWithLondonEnabled(); @@ -233,7 +274,7 @@ public async Task Estimate_gas_with_base_fee_opcode() string dataStr = code.ToHexString(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - $"{{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\", \"data\": \"{dataStr}\"}}"); + $"{{{type}\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\", \"data\": \"{dataStr}\"}}"); string serialized = ctx._test.TestEthRpc("eth_estimateGas", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual( "{\"jsonrpc\":\"2.0\",\"result\":\"0xe891\",\"id\":67}", diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs index 6cb72dfb156..389b112bde2 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs @@ -66,7 +66,7 @@ public async Task Eth_call_web3_should_return_insufficient_balance_error() "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\", \"value\": 500}"); string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual( - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32000,\"message\":\"insufficient funds for transfer: address 0x0001020304050607080910111213141516171819\"},\"id\":67}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"insufficient funds for gas * price + value: address 0x0001020304050607080910111213141516171819, have 0, want 22473081332\",\"data\":\"insufficient funds for gas * price + value: address 0x0001020304050607080910111213141516171819, have 0, want 22473081332\"},\"id\":67}", serialized); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); } @@ -78,7 +78,7 @@ public async Task Eth_call_web3_sample_not_enough_gas_other_account() Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx._test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"from\":\"0x0001020304050607080910111213141516171819\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction), "0x0"); Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}", serialized); @@ -108,7 +108,7 @@ public async Task Eth_call_no_recipient_should_work_as_init() string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction), "latest"); Assert.AreEqual( - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"VM execution error.\",\"data\":\"StackUnderflow\"},\"id\":67}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"StackUnderflow\",\"data\":\"StackUnderflow\"},\"id\":67}", serialized); } @@ -127,7 +127,7 @@ public async Task should_not_reject_transactions_with_deployed_code_when_eip3607 string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction), "latest"); - Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}", serialized); + Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"max fee per gas less than block base fee: address 0xb7705ae4c6f81b66cdb323c65f4e8133690fc099, maxFeePerGas: 1, baseFee 765625000\",\"data\":\"max fee per gas less than block base fee: address 0xb7705ae4c6f81b66cdb323c65f4e8133690fc099, maxFeePerGas: 1, baseFee 765625000\"},\"id\":67}", serialized); } [Test] @@ -203,7 +203,17 @@ public async Task Eth_call_with_gas_pricing() TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction)); - Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}", serialized); + Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"insufficient funds for gas * price + value: address 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24, have 0, want 336000\",\"data\":\"insufficient funds for gas * price + value: address 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24, have 0, want 336000\"},\"id\":67}", serialized); + } + + [Test] + public async Task Eth_call_gas_price() + { + using Context ctx = await Context.Create(); + TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( + "{\"from\": \"0xec37828d7e88b3cbb2cabffa353d7de556cadaff\", \"to\": \"0x343bdeb107d2b2babbe8f533693ddb043d23ea95\", \"gasPrice\": \"0x4D5CFC203B8C8\"}"); + string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction)); + Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"insufficient funds for gas * price + value: address 0xec37828d7e88b3cbb2cabffa353d7de556cadaff, have 0, want 28580752133073000000\",\"data\":\"insufficient funds for gas * price + value: address 0xec37828d7e88b3cbb2cabffa353d7de556cadaff, have 0, want 28580752133073000000\"},\"id\":67}", serialized); } [Test] @@ -213,7 +223,7 @@ public async Task Eth_call_without_gas_pricing_after_1559_legacy() TransactionForRpc transaction = ctx._test.JsonSerializer.Deserialize( "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction)); - Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}", serialized); + Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"max fee per gas less than block base fee: address 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24, maxFeePerGas: 16, baseFee 765625000\",\"data\":\"max fee per gas less than block base fee: address 0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24, maxFeePerGas: 16, baseFee 765625000\"},\"id\":67}", serialized); } [Test] @@ -270,7 +280,7 @@ public async Task Eth_call_with_revert() $"{{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"type\": \"0x2\", \"data\": \"{dataStr}\"}}"); string serialized = ctx._test.TestEthRpc("eth_call", ctx._test.JsonSerializer.Serialize(transaction)); Assert.AreEqual( - "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"VM execution error.\",\"data\":\"revert\"},\"id\":67}", + "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32015,\"message\":\"revert\",\"data\":\"revert\"},\"id\":67}", serialized); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs index 4d173a12ac9..c9fc0171e75 100644 --- a/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/IJsonRpcConfig.cs @@ -36,6 +36,11 @@ public interface IJsonRpcConfig : IConfig Description = "JSON RPC' timeout value given in milliseconds.", DefaultValue = "20000")] int Timeout { get; set; } + + [ConfigItem( + Description = "Defines whether the eth_call should be executed without taking base fee into consideration (in case when maxPriorityFeePerGas and maxFeePerGas are equal to zero).", + DefaultValue = "false")] + bool NoBaseFee { get; set; } [ConfigItem( Description = "Base file path for diagnostic JSON RPC recorder.", @@ -108,5 +113,7 @@ public interface IJsonRpcConfig : IConfig "If this limit is exceeded on Http calls 503 Service Unavailable will be returned along with Json RPC error. " + "Defaults to number of logical processes.")] int? EthModuleConcurrentInstances { get; set; } + + JsonRpcConfig Clone(); } } diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs index ec9046d7331..47a61ca8b14 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcConfig.cs @@ -49,5 +49,8 @@ public int WebSocketsPort public string CallsFilterFilePath { get; set; } = "Data/jsonrpc.filter"; public long? MaxRequestBodySize { get; set; } = 30000000; public int? EthModuleConcurrentInstances { get; set; } = null; + public bool NoBaseFee { get; set; } = false; + public JsonRpcConfig Clone() => (JsonRpcConfig)MemberwiseClone(); + } } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModule.TransactionExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModule.TransactionExecutor.cs index eca2e8d9410..b6411b40c71 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModule.TransactionExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthModule.TransactionExecutor.cs @@ -65,10 +65,10 @@ public ResultWrapper ExecuteTx( using CancellationTokenSource cancellationTokenSource = new(_rpcConfig.Timeout); Transaction tx = transactionCall.ToTransaction(_blockchainBridge.GetChainId()); - return ExecuteTx(header.Clone(), tx, cancellationTokenSource.Token); + return ExecuteTx(header.Clone(), tx, cancellationTokenSource.Token, _rpcConfig.NoBaseFee); } - protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token); + protected abstract ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token, bool noBaseFee = false); protected ResultWrapper GetInputError(BlockchainBridge.CallOutput result) => ResultWrapper.Fail(result.Error, ErrorCodes.InvalidInput); @@ -81,9 +81,9 @@ public CallTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder blockFind { } - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token, bool noBaseFee = false) { - BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx, token); + BlockchainBridge.CallOutput result = _blockchainBridge.Call(header, tx, token, noBaseFee); if (result.Error is null) { @@ -92,7 +92,7 @@ protected override ResultWrapper ExecuteTx(BlockHeader header, Transacti return result.InputError ? GetInputError(result) - : ResultWrapper.Fail("VM execution error.", ErrorCodes.ExecutionError, result.Error); + : ResultWrapper.Fail(result.Error, ErrorCodes.ExecutionError, result.Error); } } @@ -103,7 +103,7 @@ public EstimateGasTxExecutor(IBlockchainBridge blockchainBridge, IBlockFinder bl { } - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token, bool noBaseFee = false) { BlockchainBridge.CallOutput result = _blockchainBridge.EstimateGas(header, tx, token); @@ -128,7 +128,7 @@ public CreateAccessListTxExecutor(IBlockchainBridge blockchainBridge, IBlockFind _optimize = optimize; } - protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token) + protected override ResultWrapper ExecuteTx(BlockHeader header, Transaction tx, CancellationToken token, bool noBaseFee = false) { BlockchainBridge.CallOutput result = _blockchainBridge.CreateAccessList(header, tx, token, _optimize); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 60112751a49..f8d460e93a6 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -367,9 +367,13 @@ private async Task> SendTx(Transaction tx, } } - public ResultWrapper eth_call(TransactionForRpc transactionCall, BlockParameter? blockParameter = null) => - new CallTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig) + public ResultWrapper eth_call(TransactionForRpc transactionCall, BlockParameter? blockParameter = null) + { + JsonRpcConfig rpcConfig = _rpcConfig.Clone(); + rpcConfig.NoBaseFee = true; + return new CallTxExecutor(_blockchainBridge, _blockFinder, rpcConfig) .ExecuteTx(transactionCall, blockParameter); + } public ResultWrapper eth_estimateGas(TransactionForRpc transactionCall, BlockParameter blockParameter) => new EstimateGasTxExecutor(_blockchainBridge, _blockFinder, _rpcConfig)