From d221d80f2b7d78ce2a00020ba1567416f017f2ec Mon Sep 17 00:00:00 2001 From: tgmichel Date: Wed, 29 Mar 2023 12:02:56 +0200 Subject: [PATCH 1/2] Add support for estimate gas historically --- client/rpc/src/eth/execute.rs | 32 ++++++++++--- ts-tests/tests/test-execute.ts | 86 +++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/client/rpc/src/eth/execute.rs b/client/rpc/src/eth/execute.rs index db73fe52f8..589b4b7fd9 100644 --- a/client/rpc/src/eth/execute.rs +++ b/client/rpc/src/eth/execute.rs @@ -313,15 +313,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 = EGA::adapt_request(request); @@ -333,8 +354,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() { @@ -367,8 +387,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 f26bc839d4..404a1e5204 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,90 @@ 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 () { From 4091d46d13cb5f1d1fc7ec6a83bead13d9743f23 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Thu, 30 Mar 2023 12:00:47 +0200 Subject: [PATCH 2/2] prettier --- ts-tests/tests/test-execute.ts | 51 +++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/ts-tests/tests/test-execute.ts b/ts-tests/tests/test-execute.ts index 404a1e5204..99074089b8 100644 --- a/ts-tests/tests/test-execute.ts +++ b/ts-tests/tests/test-execute.ts @@ -16,7 +16,6 @@ const FORCE_GAS_CONTRACT_BYTECODE = ForceGasLimit.bytecode; const FORCE_GAS_CONTRACT_ABI = ForceGasLimit.abi as AbiItem[]; describeWithFrontier("Frontier RPC (estimate gas historically)", (context) => { - const TEST_CONTRACT_BYTECODE = Storage.bytecode; const TEST_CONTRACT_ABI = Storage.abi as AbiItem[]; @@ -52,12 +51,16 @@ describeWithFrontier("Frontier RPC (estimate gas historically)", (context) => { ) .encodeABI(); - const ESTIMATE_AT_1 = context.web3.utils.hexToNumber((await customRequest(context.web3, "eth_estimateGas", [ - { - to: contractAddress, - data: SSTORE_SET_DATA - }, - ])).result); + 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( @@ -75,24 +78,32 @@ describeWithFrontier("Frontier RPC (estimate gas historically)", (context) => { 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); + 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); + 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);