Skip to content

Commit 76c45e1

Browse files
chore: Cherry pick #2377 on 0.46 release (#2385)
fix: wrong gas estimation due to invalid tx object (#2377) * fix: wrong gas estimation due to invalid tx object optimization chore: fix typo test: add unit test fix logic delete input if not used * add simple fix * remove unused import * revert changes * fix acceptance tests * add unit tests --------- Signed-off-by: georgi-l95 <[email protected]> Signed-off-by: Alfredo Gutierrez <[email protected]> Co-authored-by: Georgi Lazarov <[email protected]>
1 parent 353b62c commit 76c45e1

File tree

5 files changed

+172
-3
lines changed

5 files changed

+172
-3
lines changed

packages/relay/src/lib/eth.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ export class EthImpl implements Eth {
576576

577577
if (contractCallResponse?.result) {
578578
gas = prepend0x(trimPrecedingZeros(contractCallResponse.result));
579+
this.logger.info(`${requestIdPrefix} Returning gas: ${gas}`);
579580
}
580581
} catch (e: any) {
581582
this.logger.error(
@@ -624,9 +625,8 @@ export class EthImpl implements Eth {
624625
// Handle Contract Call or Contract Create
625626
gas = this.defaultGas;
626627
}
628+
this.logger.error(`${requestIdPrefix} Returning predefined gas: ${gas}`);
627629
}
628-
this.logger.error(`${requestIdPrefix} Returning predefined gas: ${gas}`);
629-
630630
return gas;
631631
}
632632

@@ -644,9 +644,19 @@ export class EthImpl implements Eth {
644644
if (transaction.gas) {
645645
transaction.gas = parseInt(transaction.gas);
646646
}
647+
648+
if (transaction.data && transaction.input) {
649+
throw predefined.INVALID_ARGUMENTS('Cannot accept both input and data fields. Use only one.');
650+
}
651+
647652
// Support either data or input. https://ethereum.github.io/execution-apis/api-documentation/ lists input but many EVM tools still use data.
653+
// We chose in the mirror node to use data field as the correct one, however for us to be able to support all tools,
654+
// we have to modify transaction object, so that it complies with the mirror node.
655+
// That means that, if input field is passed, but data is not, we have to copy one value to the other.
656+
// For optimization purposes, we can rid of the input property or replace it with empty string.
648657
if (transaction.input && transaction.data === undefined) {
649658
transaction.data = transaction.input;
659+
delete transaction.input;
650660
}
651661
}
652662

packages/relay/tests/lib/eth/eth_call.spec.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,4 +880,74 @@ describe('@ethCall Eth Call spec', async function () {
880880
expect(result).to.equal('0x');
881881
});
882882
});
883+
884+
describe('contractCallFormat', () => {
885+
it('should format transaction value to tiny bar integer', () => {
886+
const transaction = {
887+
value: '0x2540BE400',
888+
};
889+
890+
ethImpl.contractCallFormat(transaction);
891+
expect(transaction.value).to.equal(1);
892+
});
893+
894+
it('should parse gasPrice to integer', () => {
895+
const transaction = {
896+
gasPrice: '1000000000',
897+
};
898+
899+
ethImpl.contractCallFormat(transaction);
900+
901+
expect(transaction.gasPrice).to.equal(1000000000);
902+
});
903+
904+
it('should parse gas to integer', () => {
905+
const transaction = {
906+
gas: '50000',
907+
};
908+
909+
ethImpl.contractCallFormat(transaction);
910+
911+
expect(transaction.gas).to.equal(50000);
912+
});
913+
914+
it('should throw an error if both input and data fields are provided', () => {
915+
const transaction = {
916+
input: 'input data',
917+
data: 'some data',
918+
};
919+
try {
920+
ethImpl.contractCallFormat(transaction);
921+
} catch (error) {
922+
expect(error.code).eq(-32000);
923+
expect(error.message).eq('Invalid arguments: Cannot accept both input and data fields. Use only one.');
924+
}
925+
});
926+
927+
it('should copy input to data if input is provided but data is not', () => {
928+
const transaction = {
929+
input: 'input data',
930+
};
931+
932+
ethImpl.contractCallFormat(transaction);
933+
934+
// @ts-ignore
935+
expect(transaction.data).to.equal('input data');
936+
expect(transaction.input).to.be.undefined;
937+
});
938+
939+
it('should not modify transaction if input and data fields are not provided', () => {
940+
const transaction = {
941+
value: '0x2540BE400',
942+
gasPrice: '1000000000',
943+
gas: '50000',
944+
};
945+
946+
ethImpl.contractCallFormat(transaction);
947+
948+
expect(transaction.value).to.equal(1);
949+
expect(transaction.gasPrice).to.equal(1000000000);
950+
expect(transaction.gas).to.equal(50000);
951+
});
952+
});
883953
});

packages/relay/tests/lib/eth/eth_estimateGas.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import constants from '../../../src/lib/constants';
2929
import { SDKClient } from '../../../src/lib/clients';
3030
import { numberTo0x } from '../../../dist/formatters';
3131
import { DEFAULT_NETWORK_FEES, NO_TRANSACTIONS, ONE_TINYBAR_IN_WEI_HEX, RECEIVER_ADDRESS } from './eth-config';
32-
import { JsonRpcError } from '../../../src/lib/errors/JsonRpcError';
32+
import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError';
3333
import { generateEthTestEnv } from './eth-helpers';
3434

3535
dotenv.config({ path: path.resolve(__dirname, '../test.env') });
@@ -232,6 +232,21 @@ describe('@ethEstimateGas Estimate Gas spec', async function () {
232232
);
233233
});
234234

235+
it('should eth_estimateGas for contract create with input field and absent data field', async () => {
236+
const gasEstimation = 1357410;
237+
const callData = {
238+
input:
239+
'0x81cb089c285e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af85e5ee3a7353704fb114955037443af',
240+
from: '0x81cb089c285e5ee3a7353704fb114955037443af',
241+
to: null,
242+
value: '0x0',
243+
};
244+
web3Mock.onPost('contracts/call', { ...callData, estimate: true }).reply(200, { result: `0x14b662` });
245+
246+
const gas = await ethImpl.estimateGas({ ...callData }, null);
247+
expect((gas as string).toLowerCase()).to.equal(numberTo0x(gasEstimation).toLowerCase());
248+
});
249+
235250
it('should eth_estimateGas transfer with invalid value', async function () {
236251
const result = await ethImpl.estimateGas(
237252
{
@@ -370,4 +385,21 @@ describe('@ethEstimateGas Estimate Gas spec', async function () {
370385
expect(transaction.gasPrice).to.eq(1000000);
371386
expect(transaction.gas).to.eq(14250000);
372387
});
388+
389+
it('should throw on estimateGas precheck', async function () {
390+
const transaction = {
391+
from: '0x05fba803be258049a27b820088bab1cad2058871',
392+
data: '0x',
393+
input: '0x',
394+
value: '0xA186B8E9800',
395+
gasPrice: '0xF4240',
396+
gas: '0xd97010',
397+
};
398+
399+
try {
400+
ethImpl.contractCallFormat(transaction);
401+
} catch (error) {
402+
expect(error).to.equal(predefined.INVALID_ARGUMENTS('Cannot accept both input and data fields. Use only one.'));
403+
}
404+
});
373405
});

packages/server/tests/acceptance/rpc_batch2.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,46 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () {
356356
expect(res).to.not.be.equal('0x');
357357
expect(res).to.not.be.equal('0x0');
358358
});
359+
360+
it('should execute "eth_estimateGas" with input as 0x instead of data', async function () {
361+
const res = await relay.call(
362+
RelayCalls.ETH_ENDPOINTS.ETH_ESTIMATE_GAS,
363+
[
364+
{
365+
from: '0x114f60009ee6b84861c0cdae8829751e517bc4d7',
366+
to: '0xae410f34f7487e2cd03396499cebb09b79f45d6e',
367+
value: '0xa688906bd8b00000',
368+
gas: '0xd97010',
369+
input: '0x',
370+
},
371+
],
372+
requestId,
373+
);
374+
expect(res).to.contain('0x');
375+
expect(res).to.not.be.equal('0x');
376+
expect(res).to.not.be.equal('0x0');
377+
});
378+
379+
it('should throw on "eth_estimateGas" with both input and data fields present in the txObject', async function () {
380+
try {
381+
await relay.call(
382+
RelayCalls.ETH_ENDPOINTS.ETH_ESTIMATE_GAS,
383+
[
384+
{
385+
from: '0x114f60009ee6b84861c0cdae8829751e517bc4d7',
386+
to: '0xae410f34f7487e2cd03396499cebb09b79f45d6e',
387+
value: '0xa688906bd8b00000',
388+
gas: '0xd97010',
389+
input: '0x',
390+
data: '0x',
391+
},
392+
],
393+
requestId,
394+
);
395+
} catch (e) {
396+
expect(e).to.equal(predefined.INVALID_ARGUMENTS('Cannot accept both input and data fields. Use only one.'));
397+
}
398+
});
359399
});
360400

361401
describe('eth_gasPrice', async function () {

packages/server/tests/acceptance/rpc_batch3.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,23 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
324324
await Assertions.assertPredefinedRpcError(errorType, relay.call, false, relay, args);
325325
});
326326

327+
it('should fail to execute "eth_call" with both data and input fields', async function () {
328+
const callData = {
329+
from: accounts[0].address,
330+
to: evmAddress,
331+
data: BASIC_CONTRACT_PING_CALL_DATA,
332+
};
333+
334+
// deploymentBlockNumber to HEX
335+
const block = numberTo0x(deploymentBlockNumber);
336+
try {
337+
await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, { blockNumber: block }], requestId);
338+
} catch (error) {
339+
expect(error.code).eq(-32000);
340+
expect(error.message).eq('Invalid arguments: Cannot accept both input and data fields. Use only one.');
341+
}
342+
});
343+
327344
it('should fail to execute "eth_call" with wrong block number object', async function () {
328345
const callData = {
329346
from: accounts[0].address,

0 commit comments

Comments
 (0)