Skip to content

Commit 88d1e80

Browse files
authored
feat: allowed eth_call to accept null/empty tx.to to simulate deploying smart contracts (#2647)
* feat: allowed eth_call to accept null/empty tx.to to simulate deploying smart contracts Signed-off-by: Logan Nguyen <[email protected]> * fix: added support for `to` being empty or undefined Signed-off-by: Logan Nguyen <[email protected]> * fix: removed unnecessary test cases Signed-off-by: Logan Nguyen <[email protected]> --------- Signed-off-by: Logan Nguyen <[email protected]>
1 parent eb3742a commit 88d1e80

File tree

6 files changed

+66
-52
lines changed

6 files changed

+66
-52
lines changed

packages/relay/src/lib/eth.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,11 +1616,11 @@ export class EthImpl implements Eth {
16161616
const callData = call.data ? call.data : call.input;
16171617
// log request
16181618
this.logger.trace(
1619-
`${requestIdPrefix} call({to=${call.to}, from=${call.from}, data=${callData}, gas=${call.gas}, ...}, blockParam=${blockParam})`,
1619+
`${requestIdPrefix} call({to=${call.to}, from=${call.from}, data=${callData}, gas=${call.gas}, gasPrice=${call.gasPrice} blockParam=${blockParam}, estimate=${call.estimate})`,
16201620
);
1621-
// log call data size and gas
1621+
// log call data size
16221622
const callDataSize = callData ? callData.length : 0;
1623-
this.logger.trace(`${requestIdPrefix} call data size: ${callDataSize}, gas: ${call.gas}`);
1623+
this.logger.trace(`${requestIdPrefix} call data size: ${callDataSize}`);
16241624
// metrics for selector
16251625
if (callDataSize >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) {
16261626
this.ethExecutionsCounter
@@ -1629,7 +1629,6 @@ export class EthImpl implements Eth {
16291629
}
16301630

16311631
const blockNumberOrTag = await this.extractBlockNumberOrTag(blockParam, requestIdPrefix);
1632-
16331632
await this.performCallChecks(call);
16341633

16351634
// Get a reasonable value for "gas" if it is not specified.
@@ -1891,8 +1890,8 @@ export class EthImpl implements Eth {
18911890
* @param call
18921891
*/
18931892
async performCallChecks(call: any): Promise<void> {
1894-
// The "to" address must always be 42 chars.
1895-
if (!call.to || call.to.length != 42) {
1893+
// after this PR https://github.com/hashgraph/hedera-mirror-node/pull/8100 in mirror-node, call.to is allowed to be empty or null
1894+
if (call.to && !isValidEthereumAddress(call.to)) {
18961895
throw predefined.INVALID_CONTRACT_ADDRESS(call.to);
18971896
}
18981897
}

packages/relay/tests/lib/eth/eth-config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,14 @@ export const CONTRACT_ADDRESS_2 = '0x000000000000000000000000000000000000055e';
128128
export const CONTRACT_ADDRESS_3 = '0x000000000000000000000000000000000000255c';
129129
export const HTS_TOKEN_ADDRESS = '0x0000000000000000000000000000000002dca431';
130130
export const ACCOUNT_ADDRESS_1 = '0x13212A14deaf2775a5b3bEcC857806D5c719d3f2';
131-
export const NON_EXISTENT_CONTRACT_ADDRESS = `'0x55555555555555555555555555555555555555'`;
131+
export const NON_EXISTENT_CONTRACT_ADDRESS = `0x5555555555555555555555555555555555555555`;
132132
export const DEFAULT_HTS_TOKEN = mockData.token;
133133
export const DEPLOYED_BYTECODE =
134134
'0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100329190';
135135
export const MIRROR_NODE_DEPLOYED_BYTECODE =
136136
'0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100321234';
137+
export const EXAMPLE_CONTRACT_BYTECODE =
138+
'0x6080604052348015600f57600080fd5b50609e8061001e6000396000f3fe608060405260043610602a5760003560e01c80635c36b18614603557806383197ef014605557600080fd5b36603057005b600080fd5b348015604057600080fd5b50600160405190815260200160405180910390f35b348015606057600080fd5b50606633ff5b00fea2646970667358221220886a6d6d6c88bcfc0063129ca2391a3d98aee75ad7fe3e870ec6679215456a3964736f6c63430008090033';
137139
export const TINYBAR_TO_WEIBAR_COEF_BIGINT = BigInt(constants.TINYBAR_TO_WEIBAR_COEF);
138140
export const ONE_TINYBAR_IN_WEI_HEX = toHex(TINYBAR_TO_WEIBAR_COEF_BIGINT);
139141

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

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import {
4141
NON_EXISTENT_CONTRACT_ADDRESS,
4242
WRONG_CONTRACT_ADDRESS,
4343
ONE_TINYBAR_IN_WEI_HEX,
44+
EXAMPLE_CONTRACT_BYTECODE,
4445
} from './eth-config';
4546
import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError';
4647
import RelayAssertions from '../../assertions';
@@ -117,21 +118,6 @@ describe('@ethCall Eth Call spec', async function () {
117118
process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true';
118119
});
119120

120-
it('eth_call with missing `to` field', async function () {
121-
await ethCallFailing(
122-
ethImpl,
123-
{
124-
from: CONTRACT_ADDRESS_1,
125-
data: CONTRACT_CALL_DATA,
126-
gas: MAX_GAS_LIMIT_HEX,
127-
},
128-
'latest',
129-
(error) => {
130-
expect(error.message).to.equal(`Invalid Contract Address: ${undefined}.`);
131-
},
132-
);
133-
});
134-
135121
it('eth_call with incorrect `to` field length', async function () {
136122
await ethCallFailing(
137123
ethImpl,
@@ -702,31 +688,12 @@ describe('@ethCall Eth Call spec', async function () {
702688
expect((result as JsonRpcError).data).to.equal(defaultErrorMessageHex);
703689
});
704690

705-
it('eth_call with missing `to` field', async function () {
706-
const args = [
707-
{
708-
...defaultCallData,
709-
from: CONTRACT_ADDRESS_1,
710-
data: CONTRACT_CALL_DATA,
711-
gas: MAX_GAS_LIMIT,
712-
},
713-
'latest',
714-
];
715-
716-
await RelayAssertions.assertRejection(
717-
predefined.INVALID_CONTRACT_ADDRESS(undefined),
718-
ethImpl.call,
719-
false,
720-
ethImpl,
721-
args,
722-
);
723-
});
724-
725691
it('eth_call with wrong `to` field', async function () {
726692
const args = [
727693
{
728694
...defaultCallData,
729695
from: CONTRACT_ADDRESS_1,
696+
to: WRONG_CONTRACT_ADDRESS,
730697
data: CONTRACT_CALL_DATA,
731698
gas: MAX_GAS_LIMIT,
732699
},
@@ -775,6 +742,33 @@ describe('@ethCall Eth Call spec', async function () {
775742
expect(result).to.be.not.null;
776743
expect(result).to.equal('0x');
777744
});
745+
746+
it('eth_call to simulate deploying a smart contract with `to` field being null', async function () {
747+
const callData = {
748+
data: EXAMPLE_CONTRACT_BYTECODE,
749+
to: null,
750+
from: ACCOUNT_ADDRESS_1,
751+
};
752+
753+
web3Mock
754+
.onPost('contracts/call', { ...callData, estimate: false, block: 'latest' })
755+
.reply(200, { result: EXAMPLE_CONTRACT_BYTECODE });
756+
const result = await ethImpl.call(callData, 'latest');
757+
expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE);
758+
});
759+
760+
it('eth_call to simulate deploying a smart contract with `to` field being empty/undefined', async function () {
761+
const callData = {
762+
data: EXAMPLE_CONTRACT_BYTECODE,
763+
from: ACCOUNT_ADDRESS_1,
764+
};
765+
766+
web3Mock
767+
.onPost('contracts/call', { ...callData, estimate: false, block: 'latest' })
768+
.reply(200, { result: EXAMPLE_CONTRACT_BYTECODE });
769+
const result = await ethImpl.call(callData, 'latest');
770+
expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE);
771+
});
778772
});
779773

780774
describe('contractCallFormat', () => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function ()
126126

127127
sdkClientStub.submitEthereumTransaction.returns({
128128
txResponse: {
129-
transactionId: TransactionId.fromString(transactionIdServicesFormat),
129+
transactionId: '',
130130
},
131131
fileId: null,
132132
});

packages/relay/tests/lib/formatters.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,11 @@ describe('Formatters', () => {
471471

472472
it('should return false for an address with an undefined value', () => {
473473
const address = undefined;
474-
expect(isValidEthereumAddress(address)).to.equal(false);
474+
expect(isValidEthereumAddress(address as any)).to.equal(false);
475+
});
476+
it('should return false for an address with a null value', () => {
477+
const address = null;
478+
expect(isValidEthereumAddress(address as any)).to.equal(false);
475479
});
476480

477481
it('should return false for an address with a null value', () => {

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

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,25 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
156156
expect(res).to.eq(BASIC_CONTRACT_PING_RESULT);
157157
});
158158

159+
it('@release should execute "eth_call" request to simulate deploying a contract with `to` field being null', async function () {
160+
const callData = {
161+
from: accounts[0].address,
162+
to: null,
163+
data: basicContractJson.bytecode,
164+
};
165+
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
166+
expect(res).to.eq(basicContractJson.deployedBytecode);
167+
});
168+
169+
it('@release should execute "eth_call" request to simulate deploying a contract with `to` field being empty/undefined', async function () {
170+
const callData = {
171+
from: accounts[0].address,
172+
data: basicContractJson.bytecode,
173+
};
174+
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
175+
expect(res).to.eq(basicContractJson.deployedBytecode);
176+
});
177+
159178
it('should fail "eth_call" request without data field', async function () {
160179
const callData = {
161180
from: accounts[0].address,
@@ -503,18 +522,14 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
503522
expect(res).to.eq('0x0000000000000000000000000000000000000000000000000000000000000004');
504523
});
505524

506-
it("009 should fail for missing 'to' field", async function () {
525+
it("009 should work for missing 'to' field", async function () {
507526
const callData = {
508527
from: accounts[0].address,
509-
data: '0x0ec1551d',
528+
data: basicContractJson.bytecode,
510529
};
511530

512-
await relay.callFailing(
513-
RelayCall.ETH_ENDPOINTS.ETH_CALL,
514-
[callData, 'latest'],
515-
predefined.INVALID_CONTRACT_ADDRESS(undefined),
516-
requestId,
517-
);
531+
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
532+
expect(res).to.eq(basicContractJson.deployedBytecode);
518533
});
519534

520535
// value is processed only when eth_call goes through the mirror node

0 commit comments

Comments
 (0)