From 0451b700236d43a883b2b666de852c3c2608f9f4 Mon Sep 17 00:00:00 2001 From: Shunji Zhan Date: Mon, 4 Dec 2023 15:36:21 +0800 Subject: [PATCH] estimateGas supports blocktag (#894) * estimateGas supports blocktag * fix * fix * fix --- packages/eth-providers/src/base-provider.ts | 28 +++++++++++++------ .../src/__tests__/e2e/endpoint.test.ts | 3 ++ .../src/__tests__/e2e/utils.ts | 5 ++-- .../eth-rpc-adapter/src/eip1193-bridge.ts | 4 +-- packages/eth-rpc-adapter/src/validate.ts | 5 ++++ 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/packages/eth-providers/src/base-provider.ts b/packages/eth-providers/src/base-provider.ts index 8130aaeb2..490b269a6 100644 --- a/packages/eth-providers/src/base-provider.ts +++ b/packages/eth-providers/src/base-provider.ts @@ -928,8 +928,15 @@ export abstract class BaseProvider extends AbstractProvider { * @param transaction The transaction to estimate the gas of * @returns The estimated gas used by this transaction */ - estimateGas = async (transaction: Deferrable): Promise => { - const { usedGas, gasLimit, usedStorage } = await this.estimateResources(transaction); + estimateGas = async ( + transaction: Deferrable, + blockTag?: BlockTag | Promise + ): Promise => { + const blockHash = blockTag && blockTag !== 'latest' + ? await this._getBlockHash(blockTag) + : undefined; // if blockTag is latest, avoid explicit blockhash for better performance + + const { usedGas, gasLimit, usedStorage } = await this.estimateResources(transaction, blockHash); const tx = await resolveProperties(transaction); const data = tx.data?.toString() ?? '0x'; @@ -948,7 +955,7 @@ export abstract class BaseProvider extends AbstractProvider { ? this.api.tx.evm.call(...callParams) : this.api.tx.evm.create(...createParams); - let txFee = await this._estimateGasCost(extrinsic); + let txFee = await this._estimateGasCost(extrinsic, blockHash); txFee = txFee.mul(gasLimit).div(usedGas); // scale it to the same ratio when estimate passing gasLimit if (usedStorage.gt(0)) { @@ -965,9 +972,13 @@ export abstract class BaseProvider extends AbstractProvider { return encodeGasLimit(txFee, gasPrice, gasLimit, usedStorage, isTokenTransfer); }; - _estimateGasCost = async (extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>) => { + _estimateGasCost = async ( + extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>, + at?: string, + ) => { + const apiAt = await this.api.at(at ?? await this.bestBlockHash); + const u8a = extrinsic.toU8a(); - const apiAt = await this.api.at(await this.bestBlockHash); const lenIncreaseAfterSignature = 100; // approximate length increase after signature const feeDetails = await apiAt.call.transactionPaymentApi.queryFeeDetails( u8a, @@ -1098,7 +1109,8 @@ export abstract class BaseProvider extends AbstractProvider { * @returns The estimated resources used by this transaction */ estimateResources = async ( - transaction: Deferrable + transaction: Deferrable, + blockHash?: string, ): Promise<{ usedGas: BigNumber; gasLimit: BigNumber; @@ -1116,7 +1128,7 @@ export abstract class BaseProvider extends AbstractProvider { storageLimit: STORAGE_LIMIT, }; - const gasInfo = await this._ethCall(txRequest); + const gasInfo = await this._ethCall(txRequest, blockHash); const usedGas = BigNumber.from(gasInfo.used_gas).toNumber(); const usedStorage = gasInfo.used_storage; @@ -1523,7 +1535,7 @@ export abstract class BaseProvider extends AbstractProvider { switch (blockTag) { case 'pending': { - return logger.throwError('pending tag not implemented', Logger.errors.UNSUPPORTED_OPERATION); + return logger.throwError('pending tag not supported', Logger.errors.UNSUPPORTED_OPERATION); } case 'latest': { return this.safeMode ? this.finalizedBlockHash : this.bestBlockHash; diff --git a/packages/eth-rpc-adapter/src/__tests__/e2e/endpoint.test.ts b/packages/eth-rpc-adapter/src/__tests__/e2e/endpoint.test.ts index 48bff861d..5d0f74cea 100644 --- a/packages/eth-rpc-adapter/src/__tests__/e2e/endpoint.test.ts +++ b/packages/eth-rpc-adapter/src/__tests__/e2e/endpoint.test.ts @@ -1940,6 +1940,9 @@ describe('endpoint', () => { const { gasLimit } = await estimateGas(tx); const bbb = (gasLimit.toNumber() % 100000) / 100; + const { gasLimit: gasLimitWithBlockTag } = await estimateGas(tx, 'latest'); + expect(gasLimitWithBlockTag.toBigInt()).to.equal(gasLimit.toBigInt()); + // should be passing gasLimit instead of usedGas expect(bbb).to.gt(GAS_MONSTER_GAS_REQUIRED / 30000); diff --git a/packages/eth-rpc-adapter/src/__tests__/e2e/utils.ts b/packages/eth-rpc-adapter/src/__tests__/e2e/utils.ts index 3550f8142..b96a01990 100644 --- a/packages/eth-rpc-adapter/src/__tests__/e2e/utils.ts +++ b/packages/eth-rpc-adapter/src/__tests__/e2e/utils.ts @@ -1,4 +1,5 @@ import { BigNumber, ContractFactory, Signer } from 'ethers'; +import { BlockTagish } from '@acala-network/eth-providers'; import { Log, TransactionRequest } from '@ethersproject/abstract-provider'; import { expect } from 'vitest'; import { hexValue } from '@ethersproject/bytes'; @@ -71,9 +72,9 @@ export const eth_getTransactionByHash_karura = rpcGet('eth_getTransactionByHash' export const eth_getBlockByNumber_karura = rpcGet('eth_getBlockByNumber', KARURA_ETH_RPC_URL); export const eth_getStorageAt_karura = rpcGet('eth_getStorageAt', KARURA_ETH_RPC_URL); -export const estimateGas = async (tx: TransactionRequest) => { +export const estimateGas = async (tx: TransactionRequest, blockTag?: BlockTagish) => { const gasPrice = (await eth_gasPrice([])).data.result; - const res = await eth_estimateGas([{ ...tx, gasPrice }]); + const res = await eth_estimateGas([{ ...tx, gasPrice }, blockTag]); if (res.data.error) { throw new Error(res.data.error.message); } diff --git a/packages/eth-rpc-adapter/src/eip1193-bridge.ts b/packages/eth-rpc-adapter/src/eip1193-bridge.ts index a29c90a36..548328b21 100644 --- a/packages/eth-rpc-adapter/src/eip1193-bridge.ts +++ b/packages/eth-rpc-adapter/src/eip1193-bridge.ts @@ -293,8 +293,8 @@ class Eip1193BridgeImpl { * @returns GAS USED - the amount of gas used. */ async eth_estimateGas(params: any[]): Promise { - validate([{ type: 'transaction' }], params); - const val = await this.#provider.estimateGas(params[0]); + validate([{ type: 'transaction' }, { type: 'block?' }], params); + const val = await this.#provider.estimateGas(params[0], params[1]); return hexValue(val); } diff --git a/packages/eth-rpc-adapter/src/validate.ts b/packages/eth-rpc-adapter/src/validate.ts index a90b1d0d6..2e2d51054 100644 --- a/packages/eth-rpc-adapter/src/validate.ts +++ b/packages/eth-rpc-adapter/src/validate.ts @@ -4,6 +4,7 @@ export type Schema = { type: | 'address' | 'block' + | 'block?' | 'transaction' | 'blockHash' | 'trasactionHash' @@ -148,6 +149,10 @@ export const validate = (schema: Schema, data: unknown[]) => { validateBlock(data[i]); break; } + case 'block?': { + data[i] && validateBlock(data[i]); + break; + } case 'transaction': { validateTransaction(data[i]); break;