From 673c54061c5bb957023e5aa9d042ab857843dc6b Mon Sep 17 00:00:00 2001 From: William Morriss Date: Thu, 7 May 2026 19:41:54 -0500 Subject: [PATCH] feat(eth-rpc): implement eth_baseFee returning next block base fee Assisted-by: Claude:claude-sonnet-4-6 --- .../Modules/Eth/EthRpcModuleTests.GasPrice.cs | 46 +++++++++++++++++++ .../Modules/Eth/EthRpcModule.cs | 17 +++++++ .../Modules/Eth/IEthRpcModule.cs | 6 +++ 3 files changed, 69 insertions(+) diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs index a18bbf283ba..b15f468b183 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.GasPrice.cs @@ -52,6 +52,52 @@ public async Task Eth_blobBaseFee_ShouldGiveCorrectResult(ulong? excessB return await ctx.Test.TestEthRpc("eth_blobBaseFee"); } + [TestCaseSource(nameof(GetBaseFeeTestCases))] + public async Task Eth_baseFee_ShouldGiveCorrectResult(UInt256 baseFeePerGas, long gasLimit, long gasUsed, bool londonEnabled) + { + ISpecProvider specProvider = londonEnabled + ? new TestSpecProvider(London.Instance) + : GetSpecProviderWithEip1559EnabledAs(false); + using Context ctx = await Context.Create(specProvider); + Block[] blocks = [ + Build.A.Block.WithNumber(0).WithBaseFeePerGas(baseFeePerGas).WithGasLimit(gasLimit).WithGasUsed(gasUsed).TestObject, + ]; + BlockTree blockTree = Build.A.BlockTree(blocks[0]).WithBlocks(blocks).TestObject; + ctx.Test = await TestRpcBlockchain.ForTest(SealEngineType.NethDev).WithBlockFinder(blockTree).Build(specProvider); + + return await ctx.Test.TestEthRpc("eth_baseFee"); + } + + public static IEnumerable GetBaseFeeTestCases + { + get + { + static string Success(UInt256 result) => $"{{\"jsonrpc\":\"2.0\",\"result\":\"{result.ToHexString(true)}\",\"id\":67}}"; + const string NullResult = "{\"jsonrpc\":\"2.0\",\"result\":null,\"id\":67}"; + + yield return new TestCaseData(UInt256.Zero, 30_000_000L, 0L, false) + { + TestName = "Pre-London block returns null", + ExpectedResult = NullResult + }; + yield return new TestCaseData((UInt256)1_000_000_000, 30_000_000L, 15_000_000L, true) + { + TestName = "Block at gas target returns same base fee", + ExpectedResult = Success(1_000_000_000) + }; + yield return new TestCaseData((UInt256)1_000_000_000, 30_000_000L, 30_000_000L, true) + { + TestName = "Block over gas target increases base fee by 12.5%", + ExpectedResult = Success(1_125_000_000) + }; + yield return new TestCaseData((UInt256)1_000_000_000, 30_000_000L, 0L, true) + { + TestName = "Block under gas target decreases base fee by 12.5%", + ExpectedResult = Success(875_000_000) + }; + } + } + [TestCase(true, "0x3")] //Gas Prices: 1,2,3,3,4,5 | Max Index: 5 | 60th Percentile: 5 * (3/5) = 3 | Result: 3 (0x3) [TestCase(false, "0x2")] //Gas Prices: 0,1,1,2,2,3 | Max Index: 5 | 60th Percentile: 5 * (3/5) = 3 | Result: 2 (0x2) public async Task Eth_gasPrice_BlocksAvailableLessThanBlocksToCheckWith1559Tx_ShouldGiveCorrectResult(bool eip1559Enabled, string expected) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index d8e98d7959a..9e6d60732ff 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -121,6 +121,23 @@ public ResultWrapper eth_protocolVersion() return ResultWrapper.Success(feePerBlobGas); } + public ResultWrapper eth_baseFee() + { + BlockHeader? head = _blockFinder.Head?.Header; + if (head is null) + { + return ResultWrapper.Success(null); + } + + IEip1559Spec specFor1559 = _specProvider.GetSpecFor1559(head.Number + 1); + if (!specFor1559.IsEip1559Enabled) + { + return ResultWrapper.Success(null); + } + + return ResultWrapper.Success(BaseFeeCalculator.Calculate(head, specFor1559)); + } + public ResultWrapper eth_maxPriorityFeePerGas() { UInt256 gasPriceWithBaseFee = _gasPriceOracle.GetMaxPriorityGasFeeEstimate(); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index cf75526583c..6bbd60429c8 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -70,6 +70,12 @@ public interface IEthRpcModule : IRpcModule ExampleResponse = "0x1")] ResultWrapper eth_blobBaseFee(); + [JsonRpcMethod(IsImplemented = true, + Description = "Returns the base fee of the next block in wei", + IsSharable = true, + ExampleResponse = "0x3b9aca00")] + ResultWrapper eth_baseFee(); + [JsonRpcMethod(IsImplemented = false, Description = "Returns accounts", IsSharable = true,