diff --git a/packages/relay/src/formatters.ts b/packages/relay/src/formatters.ts index f0c0e75736..4fe338f9e7 100644 --- a/packages/relay/src/formatters.ts +++ b/packages/relay/src/formatters.ts @@ -132,7 +132,7 @@ const parseNumericEnvVar = (envVarName: string, fallbackConstantKey: string): nu * @param value * @returns tinybarValue */ -const weibarHexToTinyBarInt = (value: string): number | null => { +const weibarHexToTinyBarInt = (value: bigint | boolean | number | string | null | undefined): number | null => { if (value && value !== '0x') { const weiBigInt = BigInt(value); const coefBigInt = BigInt(constants.TINYBAR_TO_WEIBAR_COEF); @@ -224,11 +224,11 @@ const numberTo0x = (input: number | BigNumber | bigint): string => { return EMPTY_HEX + input.toString(16); }; -const nullableNumberTo0x = (input: number | BigNumber): string | null => { +const nullableNumberTo0x = (input: number | BigNumber | bigint | null): string | null => { return input == null ? null : numberTo0x(input); }; -const nanOrNumberTo0x = (input: number | BigNumber): string => { +const nanOrNumberTo0x = (input: number | BigNumber | bigint | null): string => { return input == null || Number.isNaN(input) ? numberTo0x(0) : numberTo0x(input); }; @@ -236,7 +236,7 @@ const toHash32 = (value: string): string => { return value.substring(0, 66); }; -const toNullableBigNumber = (value: string): string | null => { +const toNullableBigNumber = (value: string | null): string | null => { if (typeof value === 'string') { return new BN(value).toString(); } @@ -269,7 +269,10 @@ const toHexString = (byteArray) => { return encoded; }; -const isValidEthereumAddress = (address: string): boolean => { +const isValidEthereumAddress = (address: string | null | undefined): boolean => { + if (!address) { + return false; + } return new RegExp(constants.BASE_HEX_REGEX + '{40}$').test(address); }; diff --git a/packages/relay/src/index.ts b/packages/relay/src/index.ts index b6b6c8150e..4ed56c85b0 100644 --- a/packages/relay/src/index.ts +++ b/packages/relay/src/index.ts @@ -22,7 +22,7 @@ import { Block, Log, Receipt, Transaction } from './lib/model'; import { JsonRpcError, predefined } from './lib/errors/JsonRpcError'; import WebSocketError from './lib/errors/WebSocketError'; import { MirrorNodeClientError } from './lib/errors/MirrorNodeClientError'; -import { MirrorNodeClient } from './lib/clients'; +import { IContractCallRequest, MirrorNodeClient } from './lib/clients'; import { IFilterService } from './lib/services/ethService/ethFilterService/IFilterService'; import { IDebugService } from './lib/services/debugService/IDebugService'; @@ -67,7 +67,11 @@ export interface Eth { coinbase(requestId?: string): JsonRpcError; - estimateGas(transaction: any, blockParam: string | null, requestId?: string): Promise; + estimateGas( + transaction: IContractCallRequest, + blockParam: string | null, + requestId?: string, + ): Promise; gasPrice(requestId?: string): Promise; diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 9fc505a7d9..78c5c97580 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -59,6 +59,27 @@ export interface IContractLogsResultsParams { topic3?: string | string[]; } +export interface IContractCallRequest { + block?: string; + estimate?: boolean; + from?: string; + to?: string | null; + gas?: number | string; + gasPrice?: number | string; + value?: number | string | null; + data?: string | null; + input?: string; +} + +export interface IContractCallResponse { + result?: string; + errorMessage?: string; + statusCode?: number; + _status?: { + messages: Array<{ message: string; detail: string; data: string }>; + }; +} + export class MirrorNodeClient { private static GET_ACCOUNTS_BY_ID_ENDPOINT = 'accounts/'; private static GET_BALANCE_ENDPOINT = 'balances'; @@ -1017,7 +1038,15 @@ export class MirrorNodeClient { return this.get(`${apiEndpoint}${queryParams}`, MirrorNodeClient.CONTRACT_ADDRESS_STATE_ENDPOINT, requestIdPrefix); } - public async postContractCall(callData: string, requestIdPrefix?: string) { + /** + * Send a contract call request to mirror node + * @param callData {IContractCallRequest} contract call request data + * @param requestIdPrefix {string} optional request id prefix + */ + public async postContractCall( + callData: IContractCallRequest, + requestIdPrefix?: string, + ): Promise { return this.post( MirrorNodeClient.CONTRACT_CALL_ENDPOINT, callData, diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index 2a8cd43858..e19bee539e 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -1,4 +1,4 @@ -/*- +/* - * * Hedera JSON RPC Relay * @@ -92,6 +92,7 @@ export default { ISTANBUL_TX_DATA_NON_ZERO_COST: 16, TX_BASE_COST: 21_000, TX_HOLLOW_ACCOUNT_CREATION_GAS: 587_000, + TX_CONTRACT_CALL_AVERAGE_GAS: 500_000, TX_DEFAULT_GAS_DEFAULT: 400_000, TX_CREATE_EXTRA: 32_000, TX_DATA_ZERO_COST: 4, diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 3329e97b3f..0ecb9a05ad 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -1,4 +1,4 @@ -/*- +/* - * * Hedera JSON RPC Relay * @@ -21,26 +21,27 @@ import { Eth } from '../index'; import { FileId, Hbar, PrecheckStatusError } from '@hashgraph/sdk'; import { Logger } from 'pino'; -import { Block, Transaction, Log, Transaction1559 } from './model'; -import { MirrorNodeClient } from './clients'; +import { Block, Log, Transaction, Transaction1559 } from './model'; +import { IContractCallRequest, IContractCallResponse, MirrorNodeClient } from './clients'; import { JsonRpcError, predefined } from './errors/JsonRpcError'; import { SDKClientError } from './errors/SDKClientError'; import { MirrorNodeClientError } from './errors/MirrorNodeClientError'; import constants from './constants'; import { Precheck } from './precheck'; import { + ASCIIToHex, + formatContractResult, formatTransactionIdWithoutQueryParams, + isHex, + isValidEthereumAddress, + nanOrNumberTo0x, + nullableNumberTo0x, + numberTo0x, parseNumericEnvVar, - formatContractResult, prepend0x, - numberTo0x, - nullableNumberTo0x, - nanOrNumberTo0x, toHash32, - weibarHexToTinyBarInt, trimPrecedingZeros, - ASCIIToHex, - isHex, + weibarHexToTinyBarInt, } from '../formatters'; import crypto from 'crypto'; import HAPIService from './services/hapiService/hapiService'; @@ -51,7 +52,6 @@ import { IFilterService } from './services/ethService/ethFilterService/IFilterSe import { CacheService } from './services/cacheService/cacheService'; import { IDebugService } from './services/debugService/IDebugService'; import { DebugService } from './services/debugService'; -import { isValidEthereumAddress } from '../formatters'; import { IFeeHistory } from './types/IFeeHistory'; import { ITransactionReceipt } from './types/ITransactionReceipt'; @@ -143,6 +143,7 @@ export class EthImpl implements Eth { * @private */ private readonly defaultGas = numberTo0x(parseNumericEnvVar('TX_DEFAULT_GAS', 'TX_DEFAULT_GAS_DEFAULT')); + private readonly contractCallAverageGas = numberTo0x(constants.TX_CONTRACT_CALL_AVERAGE_GAS); private readonly ethCallCacheTtl = parseNumericEnvVar('ETH_CALL_CACHE_TTL', 'ETH_CALL_CACHE_TTL_DEFAULT'); private readonly ethBlockNumberCacheTtlMs = parseNumericEnvVar( 'ETH_BLOCK_NUMBER_CACHE_TTL_MS', @@ -235,17 +236,17 @@ export class EthImpl implements Eth { * The ethExecutionsCounter used to track the number of active contract execution requests. * @private */ - private ethExecutionsCounter: Counter; + private readonly ethExecutionsCounter: Counter; /** * The Common Service implemntation that contains logic shared by other services. */ - private common: CommonService; + private readonly common: CommonService; /** * The Filter Service implemntation that takes care of all filter API operations. */ - private filterServiceImpl: FilterService; + private readonly filterServiceImpl: FilterService; /** * The Debug Service implemntation that takes care of all filter API operations. @@ -254,10 +255,12 @@ export class EthImpl implements Eth { /** * Create a new Eth implementation. - * @param nodeClient + * @param hapiService * @param mirrorNodeClient * @param logger * @param chain + * @param registry + * @param cacheService */ constructor( hapiService: HAPIService, @@ -557,7 +560,7 @@ export class EthImpl implements Eth { */ // eslint-disable-next-line @typescript-eslint/no-unused-vars async estimateGas( - transaction: any, + transaction: IContractCallRequest, _blockParam: string | null, requestIdPrefix?: string, ): Promise { @@ -566,7 +569,7 @@ export class EthImpl implements Eth { if (callDataSize >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) { this.ethExecutionsCounter - .labels(EthImpl.ethEstimateGas, callData.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) + .labels(EthImpl.ethEstimateGas, callData!.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) .inc(); } @@ -574,86 +577,130 @@ export class EthImpl implements Eth { `${requestIdPrefix} estimateGas(transaction=${JSON.stringify(transaction)}, _blockParam=${_blockParam})`, ); - this.contractCallFormat(transaction); - let gas = EthImpl.gasTxBaseCost; try { - const contractCallResponse = await this.mirrorNodeClient.postContractCall( - { - ...transaction, - estimate: true, - }, - requestIdPrefix, - ); - - if (contractCallResponse?.result) { - gas = prepend0x(trimPrecedingZeros(contractCallResponse.result)); - this.logger.info(`${requestIdPrefix} Returning gas: ${gas}`); + const response = await this.estimateGasFromMirrorNode(transaction, requestIdPrefix); + if (response?.result) { + this.logger.info(`${requestIdPrefix} Returning gas: ${response.result}`); + return prepend0x(trimPrecedingZeros(response.result)); + } else { + return this.predefinedGasForTransaction(transaction, requestIdPrefix); } } catch (e: any) { this.logger.error( `${requestIdPrefix} Error raised while fetching estimateGas from mirror-node: ${JSON.stringify(e)}`, ); + return this.predefinedGasForTransaction(transaction, requestIdPrefix, e); + } + } - // Handle Simple Transaction and Hollow account creation - if (transaction && transaction.to && (!transaction.data || transaction.data === '0x')) { - const value = Number(transaction.value); - if (value > 0) { - const accountCacheKey = `${constants.CACHE_KEY.ACCOUNT}_${transaction.to}`; - let toAccount: object | null = this.cacheService.get( - accountCacheKey, - EthImpl.ethEstimateGas, - requestIdPrefix, - ); - if (!toAccount) { - toAccount = await this.mirrorNodeClient.getAccount(transaction.to, requestIdPrefix); - } - - // when account exists return default base gas, otherwise return the minimum amount of gas to create an account entity - if (toAccount) { - this.cacheService.set(accountCacheKey, toAccount, EthImpl.ethEstimateGas, undefined, requestIdPrefix); + /** + * Executes an estimate contract call gas request in the mirror node. + * + * @param {IContractCallRequest} transaction The transaction data for the contract call. + * @param requestIdPrefix The prefix for the request ID. + * @returns {Promise} the response from the mirror node + */ + private async estimateGasFromMirrorNode( + transaction: IContractCallRequest, + requestIdPrefix?: string, + ): Promise { + this.contractCallFormat(transaction); + const callData = { ...transaction, estimate: true }; + return this.mirrorNodeClient.postContractCall(callData, requestIdPrefix); + } - gas = EthImpl.gasTxBaseCost; - } else { - gas = EthImpl.gasTxHollowAccountCreation; - } - } else { - return predefined.INVALID_PARAMETER( - 0, - `Invalid 'value' field in transaction param. Value must be greater than 0`, - ); - } - } else { - // The size limit of the encoded contract posted to the mirror node can cause contract deployment transactions to fail with a 400 response code. - // The contract is actually deployed on the consensus node, so the contract will work. In these cases, we don't want to return a - // CONTRTACT_REVERT error. - if ( - this.estimateGasThrows && - e.isContractReverted() && - e.message !== MirrorNodeClientError.messages.INVALID_HEX - ) { - return predefined.CONTRACT_REVERT(e.detail, e.data); - } - // Handle Contract Call or Contract Create - gas = this.defaultGas; + /** + * Fallback calculations for the amount of gas to be used for a transaction. + * This method is used when the mirror node fails to return a gas estimate. + * + * @param {IContractCallRequest} transaction The transaction data for the contract call. + * @param {string} requestIdPrefix The prefix for the request ID. + * @param error (Optional) received error from the mirror-node contract call request. + * @returns {Promise} the calculated gas cost for the transaction + */ + private async predefinedGasForTransaction( + transaction: IContractCallRequest, + requestIdPrefix?: string, + error?: any, + ): Promise { + const isSimpleTransfer = !!transaction?.to && (!transaction.data || transaction.data === '0x'); + const isContractCall = + !!transaction?.to && transaction?.data && transaction.data.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH; + const isContractCreate = !transaction?.to && transaction?.data && transaction.data !== '0x'; + + if (isSimpleTransfer) { + // Handle Simple Transaction and Hollow Account creation + const isNonZeroValue = Number(transaction.value) > 0; + if (!isNonZeroValue) { + return predefined.INVALID_PARAMETER( + 0, + `Invalid 'value' field in transaction param. Value must be greater than 0`, + ); + } + // when account exists return default base gas + if (await this.getAccount(transaction.to!, requestIdPrefix)) { + this.logger.warn(`${requestIdPrefix} Returning predefined gas for simple transfer: ${EthImpl.gasTxBaseCost}`); + return EthImpl.gasTxBaseCost; } - this.logger.error(`${requestIdPrefix} Returning predefined gas: ${gas}`); + // otherwise, return the minimum amount of gas for hollow account creation + this.logger.warn( + `${requestIdPrefix} Returning predefined gas for hollow account creation: ${EthImpl.gasTxHollowAccountCreation}`, + ); + return EthImpl.gasTxHollowAccountCreation; + } else if (isContractCreate) { + // The size limit of the encoded contract posted to the mirror node can + // cause contract deployment transactions to fail with a 400 response code. + // The contract is actually deployed on the consensus node, so the contract will work. + // In these cases, we don't want to return a CONTRACT_REVERT error. + if ( + this.estimateGasThrows && + error?.isContractReverted() && + error?.message !== MirrorNodeClientError.messages.INVALID_HEX + ) { + return predefined.CONTRACT_REVERT(error.detail, error.data); + } + this.logger.warn(`${requestIdPrefix} Returning predefined gas for contract creation: ${EthImpl.gasTxBaseCost}`); + return numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!)); + } else if (isContractCall) { + this.logger.warn(`${requestIdPrefix} Returning predefined gas for contract call: ${this.contractCallAverageGas}`); + return this.contractCallAverageGas; + } else { + this.logger.warn(`${requestIdPrefix} Returning predefined gas for unknown transaction: ${this.defaultGas}`); + return this.defaultGas; } - return gas; + } + + /** + * Tries to get the account with the given address from the cache, + * if not found, it fetches it from the mirror node. + * + * @param {string} address the address of the account + * @param {string} requestIdPrefix the prefix for the request ID + * @returns the account (if such exists for the given address) + */ + private async getAccount(address: string, requestIdPrefix?: string) { + const key = `${constants.CACHE_KEY.ACCOUNT}_${address}`; + let account = this.cacheService.get(key, EthImpl.ethEstimateGas, requestIdPrefix); + if (!account) { + account = await this.mirrorNodeClient.getAccount(address, requestIdPrefix); + this.cacheService.set(key, account, EthImpl.ethEstimateGas, undefined, requestIdPrefix); + } + return account; } /** * Perform value format precheck before making contract call towards the mirror node * @param transaction */ - contractCallFormat(transaction: any): void { + contractCallFormat(transaction: IContractCallRequest): void { if (transaction.value) { transaction.value = weibarHexToTinyBarInt(transaction.value); } if (transaction.gasPrice) { - transaction.gasPrice = parseInt(transaction.gasPrice); + transaction.gasPrice = parseInt(transaction.gasPrice.toString()); } if (transaction.gas) { - transaction.gas = parseInt(transaction.gas); + transaction.gas = parseInt(transaction.gas.toString()); } // Support either data or input. https://ethereum.github.io/execution-apis/api-documentation/ lists input but many EVM tools still use data. @@ -808,7 +855,8 @@ export class EthImpl implements Eth { * * @param address * @param slot - * @param blockNumberOrTag + * @param blockNumberOrTagOrHash + * @param requestIdPrefix */ async getStorageAt( address: string, @@ -862,7 +910,8 @@ export class EthImpl implements Eth { * Current implementation does not yet utilize blockNumber * * @param account - * @param blockNumberOrTag + * @param blockNumberOrTagOrHash + * @param requestIdPrefix */ async getBalance(account: string, blockNumberOrTagOrHash: string | null, requestIdPrefix?: string): Promise { const latestBlockTolerance = 1; @@ -1026,6 +1075,7 @@ export class EthImpl implements Eth { * * @param address * @param blockNumber + * @param requestIdPrefix */ async getCode(address: string, blockNumber: string | null, requestIdPrefix?: string): Promise { if (!EthImpl.isBlockParamValid(blockNumber)) { @@ -1129,6 +1179,7 @@ export class EthImpl implements Eth { * * @param hash * @param showDetails + * @param requestIdPrefix */ async getBlockByHash(hash: string, showDetails: boolean, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getBlockByHash(hash=${hash}, showDetails=${showDetails})`); @@ -1149,6 +1200,7 @@ export class EthImpl implements Eth { * Gets the block by its block number. * @param blockNumOrTag Possible values are earliest/pending/latest or hex, and can't be null (validator check). * @param showDetails + * @param requestIdPrefix */ async getBlockByNumber(blockNumOrTag: string, showDetails: boolean, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getBlockByNumber(blockNum=${blockNumOrTag}, showDetails=${showDetails})`); @@ -1175,6 +1227,7 @@ export class EthImpl implements Eth { * Gets the number of transaction in a block by its block hash. * * @param hash + * @param requestIdPrefix */ async getBlockTransactionCountByHash(hash: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByHash(hash=${hash}, showDetails=%o)`); @@ -1202,6 +1255,7 @@ export class EthImpl implements Eth { /** * Gets the number of transaction in a block by its block number. * @param blockNumOrTag + * @param requestIdPrefix */ async getBlockTransactionCountByNumber(blockNumOrTag: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getBlockTransactionCountByNumber(blockNum=${blockNumOrTag}, showDetails=%o)`); @@ -1241,6 +1295,7 @@ export class EthImpl implements Eth { * * @param blockHash * @param transactionIndex + * @param requestIdPrefix */ async getTransactionByBlockHashAndIndex( blockHash: string, @@ -1270,6 +1325,7 @@ export class EthImpl implements Eth { * * @param blockNumOrTag * @param transactionIndex + * @param requestIdPrefix */ async getTransactionByBlockNumberAndIndex( blockNumOrTag: string, @@ -1303,6 +1359,7 @@ export class EthImpl implements Eth { * * @param address * @param blockNumOrTag + * @param requestIdPrefix */ async getTransactionCount( address: string, @@ -1411,6 +1468,7 @@ export class EthImpl implements Eth { * Submits a transaction to the network for execution. * * @param transaction + * @param requestIdPrefix */ async sendRawTransaction(transaction: string, requestIdPrefix?: string): Promise { if (transaction?.length >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) @@ -1515,7 +1573,7 @@ export class EthImpl implements Eth { * @returns {Promise} A promise resolving to the result of the transaction, or undefined if all retry attempts fail. */ private async sendRawTransactionWithRetry( - sendRawTransaction: () => T, + sendRawTransaction: () => Promise, { maxAttempts = 2, backOff = 500, @@ -1528,7 +1586,7 @@ export class EthImpl implements Eth { onError?: (error: SDKClientError) => void; }, ): Promise { - const delay = (backOff) => new Promise((resolve) => setTimeout(resolve, backOff)); + const delay = async (backOff: number) => new Promise((resolve) => setTimeout(resolve, backOff)); for (let count = 0; count < maxAttempts; count++) { try { return await sendRawTransaction(); @@ -1546,10 +1604,15 @@ export class EthImpl implements Eth { /** * Execute a free contract call query. * - * @param call - * @param blockParam + * @param call {IContractCallRequest} The contract call request data. + * @param blockParam either a string (blockNumber or blockTag) or an object (blockHash or blockNumber) + * @param requestIdPrefix optional request ID prefix for logging. */ - async call(call: any, blockParam: string | object | null, requestIdPrefix?: string): Promise { + async call( + call: IContractCallRequest, + blockParam: string | object | null, + requestIdPrefix?: string, + ): Promise { const callData = call.data ? call.data : call.input; // log request this.logger.trace( @@ -1559,17 +1622,18 @@ export class EthImpl implements Eth { const callDataSize = callData ? callData.length : 0; this.logger.trace(`${requestIdPrefix} call data size: ${callDataSize}, gas: ${call.gas}`); // metrics for selector - if (callDataSize >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) + if (callDataSize >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) { this.ethExecutionsCounter - .labels(EthImpl.ethCall, callData.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) + .labels(EthImpl.ethCall, callData!.substring(0, constants.FUNCTION_SELECTOR_CHAR_LENGTH)) .inc(); + } const blockNumberOrTag = await this.extractBlockNumberOrTag(blockParam, requestIdPrefix); await this.performCallChecks(call); // Get a reasonable value for "gas" if it is not specified. - const gas = this.getCappedBlockGasLimit(call.gas, requestIdPrefix); + const gas = this.getCappedBlockGasLimit(call.gas?.toString(), requestIdPrefix); this.contractCallFormat(call); @@ -1680,13 +1744,13 @@ export class EthImpl implements Eth { } async callMirrorNode( - call: any, + call: IContractCallRequest, gas: number | null, - value: string | null, + value: number | string | null | undefined, block: string | null, requestIdPrefix?: string, ): Promise { - let callData: any = {}; + let callData: IContractCallRequest = {}; try { this.logger.debug( `${requestIdPrefix} Making eth_call on contract ${call.to} with gas ${gas} and call data "${call.data}" from "${call.from}" at blockBlockNumberOrTag: "${block}" using mirror-node.`, @@ -1825,7 +1889,6 @@ export class EthImpl implements Eth { * Perform neccecery checks for the passed call object * * @param call - * @param requestIdPrefix */ async performCallChecks(call: any): Promise { // The "to" address must always be 42 chars. @@ -1864,6 +1927,7 @@ export class EthImpl implements Eth { * Gets a transaction by the provided hash * * @param hash + * @param requestIdPrefix */ async getTransactionByHash(hash: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash); @@ -1909,6 +1973,7 @@ export class EthImpl implements Eth { * Gets a receipt for a transaction that has already executed. * * @param hash + * @param requestIdPrefix */ async getTransactionReceipt(hash: string, requestIdPrefix?: string): Promise { this.logger.trace(`${requestIdPrefix} getTransactionReceipt(${hash})`); @@ -2075,6 +2140,7 @@ export class EthImpl implements Eth { * most recent block, 'earliest' is 0, numbers become numbers. * * @param tag null, a number, or 'latest', 'pending', or 'earliest' + * @param requestIdPrefix * @private */ private async translateBlockTag(tag: string | null, requestIdPrefix?: string): Promise { @@ -2087,7 +2153,7 @@ export class EthImpl implements Eth { } } - private getCappedBlockGasLimit(gasString: string, requestIdPrefix?: string): number | null { + private getCappedBlockGasLimit(gasString: string | undefined, requestIdPrefix?: string): number | null { if (!gasString) { // Return null and don't include in the mirror node call, as mirror is doing this estimation on the go. return null; @@ -2114,6 +2180,7 @@ export class EthImpl implements Eth { * * @param blockHashOrNumber * @param showDetails + * @param requestIdPrefix */ private async getBlock( blockHashOrNumber: string, @@ -2247,7 +2314,6 @@ export class EthImpl implements Eth { /** * Creates a new instance of transaction object using the information in the log, all unavailable information will be null * @param log - * @param requestIdPrefix * @returns Transaction Object */ private createTransactionFromLog(log: Log): Transaction1559 { @@ -2298,8 +2364,8 @@ export class EthImpl implements Eth { * Remove when https://github.com/hashgraph/hedera-mirror-node/issues/5862 is implemented * * @param address string - * @param blockNum string - * @param requestId string + * @param blockNumOrHash + * @param requestIdPrefix * @returns string */ private async getAcccountNonceFromContractResult( diff --git a/packages/relay/src/lib/precheck.ts b/packages/relay/src/lib/precheck.ts index a4daa33137..01a1cd58a9 100644 --- a/packages/relay/src/lib/precheck.ts +++ b/packages/relay/src/lib/precheck.ts @@ -215,7 +215,6 @@ export class Precheck { }; const txGas = tx.gasPrice || tx.maxFeePerGas! + tx.maxPriorityFeePerGas!; const txTotalValue = tx.value + txGas * tx.gasLimit; - let tinybars: BigInt; if (account == null) { this.logger.trace( @@ -226,6 +225,7 @@ export class Precheck { throw predefined.RESOURCE_NOT_FOUND(`tx.from '${tx.from}'.`); } + let tinybars: bigint; try { tinybars = BigInt(account.balance.balance.toString()) * BigInt(constants.TINYBAR_TO_WEIBAR_COEF); result.passes = tinybars >= txTotalValue; @@ -294,7 +294,7 @@ export class Precheck { * @returns {number} The intrinsic gas cost. * @private */ - private static transactionIntrinsicGasCost(data: string): number { + public static transactionIntrinsicGasCost(data: string): number { const trimmedData = data.replace('0x', ''); let zeros = 0; diff --git a/packages/relay/tests/lib/eth/eth_call.spec.ts b/packages/relay/tests/lib/eth/eth_call.spec.ts index e0fd495d67..cc60f1ff96 100644 --- a/packages/relay/tests/lib/eth/eth_call.spec.ts +++ b/packages/relay/tests/lib/eth/eth_call.spec.ts @@ -24,11 +24,9 @@ import chaiAsPromised from 'chai-as-promised'; import sinon from 'sinon'; import { EthImpl } from '../../../src/lib/eth'; -import constants from '../../../src/lib/constants'; import { SDKClient } from '../../../src/lib/clients'; import { ACCOUNT_ADDRESS_1, - BLOCK_HASH_TRIMMED, CONTRACT_ADDRESS_1, CONTRACT_ADDRESS_2, CONTRACT_CALL_DATA, diff --git a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts index d213bf525b..974aa5c3d4 100644 --- a/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts +++ b/packages/relay/tests/lib/eth/eth_estimateGas.spec.ts @@ -20,23 +20,25 @@ import path from 'path'; import dotenv from 'dotenv'; import { expect, use } from 'chai'; -import sinon from 'sinon'; import chaiAsPromised from 'chai-as-promised'; import { v4 as uuid } from 'uuid'; import { EthImpl } from '../../../src/lib/eth'; import constants from '../../../src/lib/constants'; -import { SDKClient } from '../../../src/lib/clients'; +import { IContractCallResponse, IContractCallRequest, SDKClient } from '../../../src/lib/clients'; import { numberTo0x } from '../../../dist/formatters'; import { DEFAULT_NETWORK_FEES, NO_TRANSACTIONS, ONE_TINYBAR_IN_WEI_HEX, RECEIVER_ADDRESS } from './eth-config'; -import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError'; +import { Eth, JsonRpcError } from '../../../src'; import { generateEthTestEnv } from './eth-helpers'; +import { createStubInstance, stub, SinonStub, SinonStubbedInstance } from 'sinon'; +import { Precheck } from '../../../src/lib/precheck'; dotenv.config({ path: path.resolve(__dirname, '../test.env') }); use(chaiAsPromised); -let sdkClientStub; -let getSdkClientStub, ethImplOverridden; +let sdkClientStub: SinonStubbedInstance; +let getSdkClientStub: SinonStub<[], SDKClient>; +let ethImplOverridden: Eth; let currentMaxBlockRange: number; describe('@ethEstimateGas Estimate Gas spec', async function () { @@ -44,10 +46,19 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { let { restMock, web3Mock, hapiServiceInstance, ethImpl, cacheService, mirrorNodeInstance, logger, registry } = generateEthTestEnv(); - function mockContractCall(callData, estimate = true, statusCode = 501, result) { + function mockContractCall( + callData: IContractCallRequest, + estimate: boolean, + statusCode: number, + result: IContractCallResponse, + ) { return web3Mock.onPost('contracts/call', { ...callData, estimate }).reply(statusCode, result); } + function mockGetAccount(idOrAliasOrEvmAddress: string, statusCode: number, result: any) { + return restMock.onGet(`accounts/${idOrAliasOrEvmAddress}?transactions=false`).reply(statusCode, result); + } + const transaction = { from: '0x05fba803be258049a27b820088bab1cad2058871', data: '0x60806040523480156200001157600080fd5b50604051620019f4380380620019f48339818101604052810190620000379190620001fa565b818181600390816200004a9190620004ca565b5080600490816200005c9190620004ca565b5050505050620005b1565b6000604051905090565b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b620000d08262000085565b810181811067ffffffffffffffff82111715620000f257620000f162000096565b5b80604052505', @@ -61,9 +72,9 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { cacheService.clear(); restMock.reset(); - sdkClientStub = sinon.createStubInstance(SDKClient); - getSdkClientStub = sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); - ethImplOverridden = new EthImpl(sdkClientStub, mirrorNodeInstance, logger, '0x12a', registry, cacheService); + sdkClientStub = createStubInstance(SDKClient); + getSdkClientStub = stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); + ethImplOverridden = new EthImpl(hapiServiceInstance, mirrorNodeInstance, logger, '0x12a', registry, cacheService); restMock.onGet('network/fees').reply(200, DEFAULT_NETWORK_FEES); currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE); process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1'; @@ -79,7 +90,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { describe('eth_estimateGas with contract call', async function () {}); it('should eth_estimateGas with transaction.data null does not fail', async function () { - const callData = { + const callData: IContractCallRequest = { from: '0x05fba803be258049a27b820088bab1cad2058871', value: '0x0', gasPrice: '0x0', @@ -92,18 +103,19 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas to mirror node for contract call returns 501', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x608060405234801561001057600080fd5b506040516107893803806107898339818101604052810190610032919061015a565b806000908051906020019061004892919061004f565b50506102f6565b82805461005b90610224565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101c0565b61019b565b90508281526020810184848401111561011d57600080fd5b6101288482856101f1565b509392505050565b600082601f83011261014157600080fd5b81516101518482602086016100f2565b91505092915050565b60006020828403121561016c57600080fd5b600082015167ffffffffffffffff81111561018657600080fd5b61019284828501610130565b91505092915050565b60006101a56101b6565b90506101b18282610256565b919050565b6000604051905090565b600067ffffffffffffffff8211156101db576101da6102b6565b5b6101e4826102e5565b9050602081019050919050565b60005b8381101561020f5780820151818401526020810190506101f4565b8381111561021e576000848401525b50505050565b6000600282049050600182168061023c57607f821691505b602082108114156102505761024f610287565b5b50919050565b61025f826102e5565b810181811067ffffffffffffffff8211171561027e5761027d6102b6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610484806103056000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061022c565b610075565b005b61005f61008f565b60405161006c91906102a6565b60405180910390f35b806000908051906020019061008b929190610121565b5050565b60606000805461009e9061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca9061037c565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b5050505050905090565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea264697066735822122070d157c4efbb3fba4a1bde43cbba5b92b69f2fc455a650c0dfb61e9ed3d4bd6364736f6c634300080400330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b696e697469616c5f6d7367000000000000000000000000000000000000000000', from: '0x81cb089c285e5ee3a7353704fb114955037443af', + to: RECEIVER_ADDRESS, }; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); const gas = await ethImpl.estimateGas(callData, null); - expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); + expect(gas).to.equal(numberTo0x(constants.TX_CONTRACT_CALL_AVERAGE_GAS)); }); it('should eth_estimateGas contract call returns workaround response from mirror-node', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x608060405234801561001057600080fd5b506040516107893803806107898339818101604052810190610032919061015a565b806000908051906020019061004892919061004f565b50506102f6565b82805461005b90610224565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101c0565b61019b565b90508281526020810184848401111561011d57600080fd5b6101288482856101f1565b509392505050565b600082601f83011261014157600080fd5b81516101518482602086016100f2565b91505092915050565b60006020828403121561016c57600080fd5b600082015167ffffffffffffffff81111561018657600080fd5b61019284828501610130565b91505092915050565b60006101a56101b6565b90506101b18282610256565b919050565b6000604051905090565b600067ffffffffffffffff8211156101db576101da6102b6565b5b6101e4826102e5565b9050602081019050919050565b60005b8381101561020f5780820151818401526020810190506101f4565b8381111561021e576000848401525b50505050565b6000600282049050600182168061023c57607f821691505b602082108114156102505761024f610287565b5b50919050565b61025f826102e5565b810181811067ffffffffffffffff8211171561027e5761027d6102b6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610484806103056000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061022c565b610075565b005b61005f61008f565b60405161006c91906102a6565b60405180910390f35b806000908051906020019061008b929190610121565b5050565b60606000805461009e9061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca9061037c565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b5050505050905090565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea264697066735822122070d157c4efbb3fba4a1bde43cbba5b92b69f2fc455a650c0dfb61e9ed3d4bd6364736f6c634300080400330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b696e697469616c5f6d7367000000000000000000000000000000000000000000', from: '0x81cb089c285e5ee3a7353704fb114955037443af', }; @@ -114,7 +126,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas contract call with value is converted to tinybars before it is sent to mirror node', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x608060405234801561001057600080fd5b506040516107893803806107898339818101604052810190610032919061015a565b806000908051906020019061004892919061004f565b50506102f6565b82805461005b90610224565b90600052602060002090601f01602090048101928261007d57600085556100c4565b82601f1061009657805160ff19168380011785556100c4565b828001600101855582156100c4579182015b828111156100c35782518255916020019190600101906100a8565b5b5090506100d191906100d5565b5090565b5b808211156100ee5760008160009055506001016100d6565b5090565b6000610105610100846101c0565b61019b565b90508281526020810184848401111561011d57600080fd5b6101288482856101f1565b509392505050565b600082601f83011261014157600080fd5b81516101518482602086016100f2565b91505092915050565b60006020828403121561016c57600080fd5b600082015167ffffffffffffffff81111561018657600080fd5b61019284828501610130565b91505092915050565b60006101a56101b6565b90506101b18282610256565b919050565b6000604051905090565b600067ffffffffffffffff8211156101db576101da6102b6565b5b6101e4826102e5565b9050602081019050919050565b60005b8381101561020f5780820151818401526020810190506101f4565b8381111561021e576000848401525b50505050565b6000600282049050600182168061023c57607f821691505b602082108114156102505761024f610287565b5b50919050565b61025f826102e5565b810181811067ffffffffffffffff8211171561027e5761027d6102b6565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b610484806103056000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063a41368621461003b578063cfae321714610057575b600080fd5b6100556004803603810190610050919061022c565b610075565b005b61005f61008f565b60405161006c91906102a6565b60405180910390f35b806000908051906020019061008b929190610121565b5050565b60606000805461009e9061037c565b80601f01602080910402602001604051908101604052809291908181526020018280546100ca9061037c565b80156101175780601f106100ec57610100808354040283529160200191610117565b820191906000526020600020905b8154815290600101906020018083116100fa57829003601f168201915b5050505050905090565b82805461012d9061037c565b90600052602060002090601f01602090048101928261014f5760008555610196565b82601f1061016857805160ff1916838001178555610196565b82800160010185558215610196579182015b8281111561019557825182559160200191906001019061017a565b5b5090506101a391906101a7565b5090565b5b808211156101c05760008160009055506001016101a8565b5090565b60006101d76101d2846102ed565b6102c8565b9050828152602081018484840111156101ef57600080fd5b6101fa84828561033a565b509392505050565b600082601f83011261021357600080fd5b81356102238482602086016101c4565b91505092915050565b60006020828403121561023e57600080fd5b600082013567ffffffffffffffff81111561025857600080fd5b61026484828501610202565b91505092915050565b60006102788261031e565b6102828185610329565b9350610292818560208601610349565b61029b8161043d565b840191505092915050565b600060208201905081810360008301526102c0818461026d565b905092915050565b60006102d26102e3565b90506102de82826103ae565b919050565b6000604051905090565b600067ffffffffffffffff8211156103085761030761040e565b5b6103118261043d565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b82818337600083830152505050565b60005b8381101561036757808201518184015260208101905061034c565b83811115610376576000848401525b50505050565b6000600282049050600182168061039457607f821691505b602082108114156103a8576103a76103df565b5b50919050565b6103b78261043d565b810181811067ffffffffffffffff821117156103d6576103d561040e565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f830116905091905056fea264697066735822122070d157c4efbb3fba4a1bde43cbba5b92b69f2fc455a650c0dfb61e9ed3d4bd6364736f6c634300080400330000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b696e697469616c5f6d7367000000000000000000000000000000000000000000', from: '0x81cb089c285e5ee3a7353704fb114955037443af', value: 1, @@ -125,24 +137,24 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { expect((gas as string).toLowerCase()).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT).toLowerCase()); }); - it('should eth_estimateGas contract call returns default', async function () { - const callData = { + it('should eth_estimateGas for contract deploy returns default', async function () { + const callData: IContractCallRequest = { data: '0x01', }; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); const gas = await ethImpl.estimateGas({ data: '0x01' }, null); - expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); + expect(gas).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(callData.data!))); }); it('should eth_estimateGas to mirror node for transfer returns 501', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x', from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: RECEIVER_ADDRESS, value: '0x2540BE400', }; - mockContractCall({ ...callData, value: 1 }, true, 501, { errorMessage: '', statusCode: 501 }); + mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); restMock.onGet(`accounts/${RECEIVER_ADDRESS}${NO_TRANSACTIONS}`).reply(200, { address: RECEIVER_ADDRESS }); const gas = await ethImpl.estimateGas(callData, null); @@ -150,7 +162,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas to mirror node for transfer without value returns 501', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x', from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: RECEIVER_ADDRESS, @@ -164,7 +176,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas transfer to existing account', async function () { - const callData = { + const callData: IContractCallRequest = { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; @@ -182,7 +194,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas transfer to existing cached account', async function () { - const callData = { + const callData: IContractCallRequest = { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; @@ -211,7 +223,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas transfer to non existing account', async function () { - const callData = { + const callData: IContractCallRequest = { to: RECEIVER_ADDRESS, value: 10, //in tinybars }; @@ -230,7 +242,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas transfer with 0 value', async function () { - const callData = { + const callData: IContractCallRequest = { to: RECEIVER_ADDRESS, value: 0, //in tinybars }; @@ -253,19 +265,19 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { it('should eth_estimateGas for contract create with input field and absent data field', async () => { const gasEstimation = 1357410; - const mockedCallData = { + const mockedCallData: IContractCallRequest = { from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: null, value: 0, data: '0x81cb089c285e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af', }; - const callData = { - input: - '0x81cb089c285e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af', + const callData: IContractCallRequest = { from: '0x81cb089c285e5ee3a7353704fb114955037443af', to: null, value: '0x0', + input: + '0x81cb089c285e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af', }; mockContractCall(mockedCallData, true, 200, { result: `0x14b662` }); @@ -276,7 +288,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas transfer with invalid value', async function () { - const callData = { + const callData: IContractCallRequest = { to: RECEIVER_ADDRESS, value: null, //in tinybars }; @@ -297,7 +309,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas empty call returns transfer cost', async function () { - const callData = {}; + const callData: IContractCallRequest = {}; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); const gas = await ethImpl.estimateGas({}, null); @@ -305,7 +317,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas empty call returns transfer cost with overridden default gas', async function () { - const callData = {}; + const callData: IContractCallRequest = {}; mockContractCall(callData, true, 200, { result: numberTo0x(defaultGasOverride) }); const gas = await ethImplOverridden.estimateGas({}, null); @@ -313,19 +325,20 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas empty input transfer cost', async function () { - const callData = { + const callData: IContractCallRequest = { data: '', + estimate: true, }; - web3Mock - .onPost('contracts/call', { ...callData, estimate: true }) - .reply(501, { errorMessage: '', statusCode: 501 }); + const contractsCallResponse: IContractCallResponse = { errorMessage: '', statusCode: 501 }; + + web3Mock.onPost('contracts/call', callData).reply(501, contractsCallResponse); const gas = await ethImpl.estimateGas({ data: '' }, null); expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); it('should eth_estimateGas empty input transfer cost with overridden default gas', async function () { - const callData = { + const callData: IContractCallRequest = { data: '', }; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); @@ -335,17 +348,20 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas zero input returns transfer cost', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x', + to: RECEIVER_ADDRESS, + value: '0x1', }; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); + mockGetAccount(RECEIVER_ADDRESS, 200, { account: '0.0.1234', evm_address: RECEIVER_ADDRESS }); - const gas = await ethImpl.estimateGas({ data: '0x' }, null); - expect(gas).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); + const gas = await ethImpl.estimateGas(callData, null); + expect(gas).to.equal(numberTo0x(constants.TX_BASE_COST)); }); it('should eth_estimateGas zero input returns transfer cost with overridden default gas', async function () { - const callData = { + const callData: IContractCallRequest = { data: '0x0', }; mockContractCall(callData, true, 501, { errorMessage: '', statusCode: 501 }); @@ -355,7 +371,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }); it('should eth_estimateGas with contract revert and message does not equal executionReverted', async function () { - web3Mock.onPost('contracts/call', { ...transaction, estimate: true }).reply(400, { + const contractCallResult: IContractCallResponse = { _status: { messages: [ { @@ -365,11 +381,12 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }, ], }, - }); + }; + web3Mock.onPost('contracts/call', { ...transaction, estimate: true }).reply(400, contractCallResult); - const result: any = await ethImpl.estimateGas(transaction, id); + const estimatedGas = await ethImpl.estimateGas(transaction, id); - expect(result).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); + expect(estimatedGas).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); }); it('should eth_estimateGas with contract revert and message does not equal executionReverted and ESTIMATE_GAS_THROWS is set to false', async function () { @@ -389,7 +406,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { const result: any = await ethImpl.estimateGas(transaction, id); - expect(result).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); + expect(result).to.equal(numberTo0x(Precheck.transactionIntrinsicGasCost(transaction.data!))); process.env.ESTIMATE_GAS_THROWS = estimateGasThrows; }); @@ -427,7 +444,7 @@ describe('@ethEstimateGas Estimate Gas spec', async function () { }, }); - const result: any = await ethImpl.estimateGas(transaction, id); + const result: any = await ethImpl.estimateGas({ ...transaction, data: '0x', value: '0x1' }, id); expect(result).to.equal(numberTo0x(constants.TX_DEFAULT_GAS_DEFAULT)); }); diff --git a/packages/relay/tests/lib/eth/eth_getCode.spec.ts b/packages/relay/tests/lib/eth/eth_getCode.spec.ts index c1ff93bb89..74c8eec6b8 100644 --- a/packages/relay/tests/lib/eth/eth_getCode.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getCode.spec.ts @@ -143,7 +143,7 @@ describe('@ethGetCode using MirrorNode', async function () { try { await ethImpl.getCode(EthImpl.iHTSAddress, blockParam); expect(true).to.eq(false); - } catch (error) { + } catch (error: any) { const expectedError = predefined.UNKNOWN_BLOCK( `The value passed is not a valid blockHash/blockNumber/blockTag value: ${blockParam}`, ); diff --git a/packages/relay/tests/lib/formatters.spec.ts b/packages/relay/tests/lib/formatters.spec.ts index 5621ee8c1f..8c50818552 100644 --- a/packages/relay/tests/lib/formatters.spec.ts +++ b/packages/relay/tests/lib/formatters.spec.ts @@ -428,6 +428,11 @@ describe('Formatters', () => { const address = undefined; expect(isValidEthereumAddress(address)).to.equal(false); }); + + it('should return false for an address with a null value', () => { + const address = null; + expect(isValidEthereumAddress(address)).to.equal(false); + }); }); describe('isHex Function', () => { it('should return true for valid lowercase hexadecimal string', () => { diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index 4806cd9358..6ddc788762 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -111,7 +111,7 @@ describe('Precheck', async function () { let hasError = false; try { precheck.value(parsedTxWithValueLessThanOneTinybar); - } catch (e) { + } catch (e: any) { expect(e).to.exist; expect(e.code).to.eq(-32602); expect(e.message).to.eq('Value below 10_000_000_000 wei which is 1 tinybar'); diff --git a/packages/relay/tests/lib/services/debugService/debug.spec.ts b/packages/relay/tests/lib/services/debugService/debug.spec.ts index b888e5306e..cb720ff390 100644 --- a/packages/relay/tests/lib/services/debugService/debug.spec.ts +++ b/packages/relay/tests/lib/services/debugService/debug.spec.ts @@ -19,6 +19,7 @@ */ import chai, { expect } from 'chai'; +import chaiAsPromised from 'chai-as-promised'; import path from 'path'; import dotenv from 'dotenv'; import MockAdapter from 'axios-mock-adapter'; @@ -32,7 +33,6 @@ import RelayAssertions from '../../../assertions'; import { predefined } from '../../../../src'; import { CacheService } from '../../../../src/lib/services/cacheService/cacheService'; import { CommonService } from '../../../../src/lib/services/ethService'; -import chaiAsPromised from 'chai-as-promised'; import { IOpcodesResponse } from '../../../../src/lib/clients/models/IOpcodesResponse'; import { strip0x } from '../../../../src/formatters'; diff --git a/packages/ws-server/src/metrics/wsMetricRegistry.ts b/packages/ws-server/src/metrics/wsMetricRegistry.ts index dc72d4dd7b..9e638ccca5 100644 --- a/packages/ws-server/src/metrics/wsMetricRegistry.ts +++ b/packages/ws-server/src/metrics/wsMetricRegistry.ts @@ -115,7 +115,7 @@ export default class WsMetricRegistry { registers: [register], async collect() { switch (mode) { - case 'CPU': + case 'CPU': { let lastCpuUsage = process.cpuUsage(); let lastTime = process.hrtime(); const currentCpuUsage = process.cpuUsage(); @@ -136,10 +136,12 @@ export default class WsMetricRegistry { this.set({ cpu: 'CPU' }, cpuUsagePercentage); break; - case 'Memory Usage': + } + case 'Memory Usage': { const memoryUsage = process.memoryUsage(); this.set({ memory: 'Memory Usage' }, memoryUsage.heapUsed); break; + } } }, });