diff --git a/client/rpc/src/eth/execute.rs b/client/rpc/src/eth/execute.rs index 73c3ba1ac2..58de621806 100644 --- a/client/rpc/src/eth/execute.rs +++ b/client/rpc/src/eth/execute.rs @@ -351,15 +351,36 @@ where } } - pub async fn estimate_gas(&self, request: CallRequest, _: Option) -> Result { + pub async fn estimate_gas( + &self, + request: CallRequest, + number: Option, + ) -> Result { let client = Arc::clone(&self.client); let block_data_cache = Arc::clone(&self.block_data_cache); // Define the lower bound of estimate const MIN_GAS_PER_TX: U256 = U256([21_000, 0, 0, 0]); - // Get best hash (TODO missing support for estimating gas historically) - let substrate_hash = client.info().best_hash; + // Get substrate hash and runtime api + let (substrate_hash, api) = match frontier_backend_client::native_block_id::( + self.client.as_ref(), + self.backend.as_ref(), + number, + )? { + Some(id) => { + let hash = client + .expect_block_hash_from_id(&id) + .map_err(|_| crate::err(JSON_RPC_ERROR_DEFAULT, "header not found", None))?; + (hash, client.runtime_api()) + } + None => { + // Not mapped in the db, assume pending. + let hash = client.info().best_hash; + let api = pending_runtime_api(client.as_ref(), self.graph.as_ref())?; + (hash, api) + } + }; // Adapt request for gas estimation. let request = EC::EstimateGasAdapter::adapt_request(request); @@ -371,8 +392,7 @@ where }; if is_simple_transfer { if let Some(to) = request.to { - let to_code = client - .runtime_api() + let to_code = api .account_code_at(substrate_hash, to) .map_err(|err| internal_err(format!("runtime error: {:?}", err)))?; if to_code.is_empty() { @@ -405,8 +425,6 @@ where let max_gas_limit = block_gas_limit * self.execute_gas_limit_multiplier; - let api = client.runtime_api(); - // Determine the highest possible gas limits let mut highest = match request.gas { Some(amount) => { diff --git a/ts-tests/tests/test-execute.ts b/ts-tests/tests/test-execute.ts index 18f9d981ef..f023adab30 100644 --- a/ts-tests/tests/test-execute.ts +++ b/ts-tests/tests/test-execute.ts @@ -6,6 +6,7 @@ import { describeWithFrontier, customRequest, createAndFinalizeBlock } from "./u import { AbiItem } from "web3-utils"; import Test from "../build/contracts/Test.json"; +import Storage from "../build/contracts/Storage.json"; import ForceGasLimit from "../build/contracts/ForceGasLimit.json"; const TEST_CONTRACT_BYTECODE = Test.bytecode; @@ -13,7 +14,101 @@ const TEST_CONTRACT_DEPLOYED_BYTECODE = Test.deployedBytecode; const FORCE_GAS_CONTRACT_BYTECODE = ForceGasLimit.bytecode; const FORCE_GAS_CONTRACT_ABI = ForceGasLimit.abi as AbiItem[]; -const FORCE_GAS_CONTRACT_DEPLOYED_BYTECODE = ForceGasLimit.deployedBytecode; + +describeWithFrontier("Frontier RPC (estimate gas historically)", (context) => { + const TEST_CONTRACT_BYTECODE = Storage.bytecode; + const TEST_CONTRACT_ABI = Storage.abi as AbiItem[]; + + it("estimate gas historically should work", async function () { + const contract = new context.web3.eth.Contract(TEST_CONTRACT_ABI); + + this.timeout(15000); + const tx = await context.web3.eth.accounts.signTransaction( + { + from: GENESIS_ACCOUNT, + data: TEST_CONTRACT_BYTECODE, + value: "0x00", + gasPrice: "0x3B9ACA00", + gas: "0x100000", + }, + GENESIS_ACCOUNT_PRIVATE_KEY + ); + + expect(await customRequest(context.web3, "eth_sendRawTransaction", [tx.rawTransaction])).to.include({ + id: 1, + jsonrpc: "2.0", + }); + + await createAndFinalizeBlock(context.web3); + let receipt0 = await context.web3.eth.getTransactionReceipt(tx.transactionHash); + let contractAddress = receipt0.contractAddress; + + // Estimate what a sstore set costs at block number 1 + const SSTORE_SET_DATA = contract.methods + .setStorage( + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ) + .encodeABI(); + + const ESTIMATE_AT_1 = context.web3.utils.hexToNumber( + ( + await customRequest(context.web3, "eth_estimateGas", [ + { + to: contractAddress, + data: SSTORE_SET_DATA, + }, + ]) + ).result + ); + + // Set the storage and create a block + const tx1 = await context.web3.eth.accounts.signTransaction( + { + from: GENESIS_ACCOUNT, + to: contractAddress, + data: SSTORE_SET_DATA, + value: "0x00", + gasPrice: "0x3B9ACA00", + gas: "0x500000", + }, + GENESIS_ACCOUNT_PRIVATE_KEY + ); + await customRequest(context.web3, "eth_sendRawTransaction", [tx1.rawTransaction]); + await createAndFinalizeBlock(context.web3); + + // Estimate what a sstore reset costs at block number 2 + const ESTIMATE_AT_2 = context.web3.utils.hexToNumber( + ( + await customRequest(context.web3, "eth_estimateGas", [ + { + to: contractAddress, + data: SSTORE_SET_DATA, + }, + ]) + ).result + ); + + // SSTORE over an existing storage is cheaper + expect(ESTIMATE_AT_2).to.be.lt(ESTIMATE_AT_1); + + // Estimate what a sstore reset costed at block number 1, queried historically + const ESTIMATE_AT_1_QUERY = context.web3.utils.hexToNumber( + ( + await customRequest(context.web3, "eth_estimateGas", [ + { + to: contractAddress, + data: SSTORE_SET_DATA, + }, + 1, + ]) + ).result + ); + + // Expect to get the original estimated gas at block 1 + expect(ESTIMATE_AT_1_QUERY).to.be.eq(ESTIMATE_AT_1); + }); +}); describeWithFrontier("Frontier RPC (RPC execution)", (context) => { step("should call with gas limit under block gas limit", async function () {