diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index 4f83760c05..8b5a92ee45 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -55,6 +55,7 @@ export default { BLOCK_GAS_LIMIT: 15_000_000, ISTANBUL_TX_DATA_NON_ZERO_COST: 16, TX_BASE_COST: 21_000, + TX_HOLLOW_ACCOUNT_CREATION_GAS: 587_000, TX_DEFAULT_GAS: 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 434fc078af..5472ad8ff7 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -53,6 +53,7 @@ export class EthImpl implements Eth { static emptyBloom = "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; static defaultGas = EthImpl.numberTo0x(constants.TX_DEFAULT_GAS); static gasTxBaseCost = EthImpl.numberTo0x(constants.TX_BASE_COST); + static gasTxHollowAccountCreation = EthImpl.numberTo0x(constants.TX_HOLLOW_ACCOUNT_CREATION_GAS); static ethTxType = 'EthereumTransaction'; static ethEmptyTrie = '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'; static defaultGasUsedRatio = 0.5; @@ -325,8 +326,11 @@ export class EthImpl implements Eth { async estimateGas(transaction: any, _blockParam: string | null, requestId?: string) { const requestIdPrefix = formatRequestIdMessage(requestId); this.logger.trace(`${requestIdPrefix} estimateGas(transaction=${JSON.stringify(transaction)}, _blockParam=${_blockParam})`); + //this checks whether this is a transfer transaction and not a contract function execution if (!transaction || !transaction.data || transaction.data === '0x') { - return EthImpl.gasTxBaseCost; + const toAccount = await this.mirrorNodeClient.getAccount(transaction.to); + // when account exists return default base gas, otherwise return the minimum amount of gas to create an account entity + return toAccount ? EthImpl.gasTxBaseCost : EthImpl.gasTxHollowAccountCreation; } else { return EthImpl.defaultGas; } diff --git a/packages/relay/tests/lib/eth.spec.ts b/packages/relay/tests/lib/eth.spec.ts index 7221d7b623..12a4784366 100644 --- a/packages/relay/tests/lib/eth.spec.ts +++ b/packages/relay/tests/lib/eth.spec.ts @@ -2218,19 +2218,33 @@ describe('Eth calls using MirrorNode', async function () { expect(gas).to.equal(EthImpl.defaultGas); }); + it('eth_estimateGas transfer to existing account', async function() { + const receiverAddress = '0x5b98Ce3a4D1e1AC55F15Da174D5CeFcc5b8FB994'; + mock.onGet(`accounts/${receiverAddress}`).reply(200, { address: receiverAddress }); + + const gas = await ethImpl.estimateGas({ + to: receiverAddress, + value: 100_000_000_000 + }, null); + expect(gas).to.equal(EthImpl.gasTxBaseCost); + }); + it('eth_estimateGas empty call returns transfer cost', async function () { + mock.onGet(`accounts/undefined`).reply(404); const gas = await ethImpl.estimateGas({}, null); - expect(gas).to.equal(EthImpl.gasTxBaseCost); + expect(gas).to.equal(EthImpl.gasTxHollowAccountCreation); }); it('eth_estimateGas empty input transfer cost', async function () { + mock.onGet(`accounts/undefined`).reply(404); const gas = await ethImpl.estimateGas({ data: "" }, null); - expect(gas).to.equal(EthImpl.gasTxBaseCost); + expect(gas).to.equal(EthImpl.gasTxHollowAccountCreation); }); it('eth_estimateGas zero input returns transfer cost', async function () { + mock.onGet(`accounts/undefined`).reply(404); const gas = await ethImpl.estimateGas({ data: "0x" }, null); - expect(gas).to.equal(EthImpl.gasTxBaseCost); + expect(gas).to.equal(EthImpl.gasTxHollowAccountCreation); }); it('eth_gasPrice', async function () { diff --git a/packages/relay/tests/lib/openrpc.spec.ts b/packages/relay/tests/lib/openrpc.spec.ts index be77993d19..09d382e3e9 100644 --- a/packages/relay/tests/lib/openrpc.spec.ts +++ b/packages/relay/tests/lib/openrpc.spec.ts @@ -196,6 +196,7 @@ describe("Open RPC Specification", function () { }); it('should execute "eth_estimateGas"', async function () { + mock.onGet(`accounts/undefined`).reply(404); const response = await ethImpl.estimateGas({}, null); validateResponseSchema(methodsResponseSchema.eth_estimateGas, response); diff --git a/packages/server/tests/acceptance/rpc_batch2.spec.ts b/packages/server/tests/acceptance/rpc_batch2.spec.ts index e7e164888b..9e389e26b1 100644 --- a/packages/server/tests/acceptance/rpc_batch2.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch2.spec.ts @@ -49,6 +49,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { let contractExecuteTimestamp; let mirrorContract; let mirrorContractDetails; + let mirrorSecondaryAccount; let requestId; const CHAIN_ID = process.env.CHAIN_ID || 0; @@ -99,6 +100,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { // get contract result details mirrorContractDetails = await mirrorNode.get(`/contracts/${contractId}/results/${contractExecuteTimestamp}`, requestId); + mirrorSecondaryAccount = (await mirrorNode.get(`accounts?account.id=${accounts[1].accountId}`, requestId)).accounts[0]; const latestBlock = (await mirrorNode.get(`/blocks?limit=1&order=desc`, requestId)).blocks[0]; blockNumberAtStartOfTests = latestBlock.number; @@ -117,6 +119,32 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { expect(res).to.not.be.equal('0x0'); }); + it('@release should execute "eth_estimateGas" for contract call', async function() { + const res = await relay.call('eth_estimateGas', [{ + to: mirrorContract.evm_address, + data: BASIC_CONTRACT_PING_CALL_DATA + }], requestId); + expect(res).to.contain('0x'); + expect(res).to.equal(EthImpl.defaultGas); + }); + + it('@release should execute "eth_estimateGas" for existing account', async function() { + const res = await relay.call('eth_estimateGas', [{ + to: mirrorSecondaryAccount.evm_address + }], requestId); + expect(res).to.contain('0x'); + expect(res).to.equal(EthImpl.gasTxBaseCost); + }); + + it('@release should execute "eth_estimateGas" hollow account creation', async function() { + const hollowAccount = ethers.Wallet.createRandom(); + const res = await relay.call('eth_estimateGas', [{ + to: hollowAccount.address + }], requestId); + expect(res).to.contain('0x'); + expect(res).to.equal(EthImpl.gasTxHollowAccountCreation); + }); + it('should execute "eth_estimateGas" with to, from, value and gas filed', async function () { const res = await relay.call('eth_estimateGas', [{ from: '0x114f60009ee6b84861c0cdae8829751e517bc4d7',