Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allowed eth_call to accept null/empty tx.to to simulate deploying smart contracts #2647

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1616,11 +1616,11 @@ export class EthImpl implements Eth {
const callData = call.data ? call.data : call.input;
// log request
this.logger.trace(
`${requestIdPrefix} call({to=${call.to}, from=${call.from}, data=${callData}, gas=${call.gas}, ...}, blockParam=${blockParam})`,
`${requestIdPrefix} call({to=${call.to}, from=${call.from}, data=${callData}, gas=${call.gas}, gasPrice=${call.gasPrice} blockParam=${blockParam}, estimate=${call.estimate})`,
);
// log call data size and gas
// log call data size
const callDataSize = callData ? callData.length : 0;
this.logger.trace(`${requestIdPrefix} call data size: ${callDataSize}, gas: ${call.gas}`);
this.logger.trace(`${requestIdPrefix} call data size: ${callDataSize}`);
// metrics for selector
if (callDataSize >= constants.FUNCTION_SELECTOR_CHAR_LENGTH) {
this.ethExecutionsCounter
Expand All @@ -1629,7 +1629,6 @@ export class EthImpl implements Eth {
}

const blockNumberOrTag = await this.extractBlockNumberOrTag(blockParam, requestIdPrefix);

await this.performCallChecks(call);

// Get a reasonable value for "gas" if it is not specified.
Expand Down Expand Up @@ -1891,8 +1890,8 @@ export class EthImpl implements Eth {
* @param call
*/
async performCallChecks(call: any): Promise<void> {
// The "to" address must always be 42 chars.
if (!call.to || call.to.length != 42) {
// after this PR https://github.com/hashgraph/hedera-mirror-node/pull/8100 in mirror-node, call.to is allowed to be empty or null
if (call.to && !isValidEthereumAddress(call.to)) {
throw predefined.INVALID_CONTRACT_ADDRESS(call.to);
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/relay/tests/lib/eth/eth-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,14 @@ export const CONTRACT_ADDRESS_2 = '0x000000000000000000000000000000000000055e';
export const CONTRACT_ADDRESS_3 = '0x000000000000000000000000000000000000255c';
export const HTS_TOKEN_ADDRESS = '0x0000000000000000000000000000000002dca431';
export const ACCOUNT_ADDRESS_1 = '0x13212A14deaf2775a5b3bEcC857806D5c719d3f2';
export const NON_EXISTENT_CONTRACT_ADDRESS = `'0x55555555555555555555555555555555555555'`;
export const NON_EXISTENT_CONTRACT_ADDRESS = `0x5555555555555555555555555555555555555555`;
export const DEFAULT_HTS_TOKEN = mockData.token;
export const DEPLOYED_BYTECODE =
'0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100329190';
export const MIRROR_NODE_DEPLOYED_BYTECODE =
'0x608060405234801561001057600080fd5b5060405161078938038061078983398181016040528101906100321234';
export const EXAMPLE_CONTRACT_BYTECODE =
'0x6080604052348015600f57600080fd5b50609e8061001e6000396000f3fe608060405260043610602a5760003560e01c80635c36b18614603557806383197ef014605557600080fd5b36603057005b600080fd5b348015604057600080fd5b50600160405190815260200160405180910390f35b348015606057600080fd5b50606633ff5b00fea2646970667358221220886a6d6d6c88bcfc0063129ca2391a3d98aee75ad7fe3e870ec6679215456a3964736f6c63430008090033';
export const TINYBAR_TO_WEIBAR_COEF_BIGINT = BigInt(constants.TINYBAR_TO_WEIBAR_COEF);
export const ONE_TINYBAR_IN_WEI_HEX = toHex(TINYBAR_TO_WEIBAR_COEF_BIGINT);

Expand Down
64 changes: 29 additions & 35 deletions packages/relay/tests/lib/eth/eth_call.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
NON_EXISTENT_CONTRACT_ADDRESS,
WRONG_CONTRACT_ADDRESS,
ONE_TINYBAR_IN_WEI_HEX,
EXAMPLE_CONTRACT_BYTECODE,
} from './eth-config';
import { JsonRpcError, predefined } from '../../../src/lib/errors/JsonRpcError';
import RelayAssertions from '../../assertions';
Expand Down Expand Up @@ -117,21 +118,6 @@ describe('@ethCall Eth Call spec', async function () {
process.env.ETH_CALL_DEFAULT_TO_CONSENSUS_NODE = 'true';
});

it('eth_call with missing `to` field', async function () {
quiet-node marked this conversation as resolved.
Show resolved Hide resolved
await ethCallFailing(
ethImpl,
{
from: CONTRACT_ADDRESS_1,
data: CONTRACT_CALL_DATA,
gas: MAX_GAS_LIMIT_HEX,
},
'latest',
(error) => {
expect(error.message).to.equal(`Invalid Contract Address: ${undefined}.`);
},
);
});

it('eth_call with incorrect `to` field length', async function () {
await ethCallFailing(
ethImpl,
Expand Down Expand Up @@ -702,31 +688,12 @@ describe('@ethCall Eth Call spec', async function () {
expect((result as JsonRpcError).data).to.equal(defaultErrorMessageHex);
});

it('eth_call with missing `to` field', async function () {
const args = [
{
...defaultCallData,
from: CONTRACT_ADDRESS_1,
data: CONTRACT_CALL_DATA,
gas: MAX_GAS_LIMIT,
},
'latest',
];

await RelayAssertions.assertRejection(
predefined.INVALID_CONTRACT_ADDRESS(undefined),
ethImpl.call,
false,
ethImpl,
args,
);
});

it('eth_call with wrong `to` field', async function () {
const args = [
{
...defaultCallData,
from: CONTRACT_ADDRESS_1,
to: WRONG_CONTRACT_ADDRESS,
data: CONTRACT_CALL_DATA,
gas: MAX_GAS_LIMIT,
},
Expand Down Expand Up @@ -775,6 +742,33 @@ describe('@ethCall Eth Call spec', async function () {
expect(result).to.be.not.null;
expect(result).to.equal('0x');
});

it('eth_call to simulate deploying a smart contract with `to` field being null', async function () {
const callData = {
data: EXAMPLE_CONTRACT_BYTECODE,
to: null,
from: ACCOUNT_ADDRESS_1,
};

web3Mock
.onPost('contracts/call', { ...callData, estimate: false, block: 'latest' })
.reply(200, { result: EXAMPLE_CONTRACT_BYTECODE });
const result = await ethImpl.call(callData, 'latest');
expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE);
});

it('eth_call to simulate deploying a smart contract with `to` field being empty/undefined', async function () {
const callData = {
data: EXAMPLE_CONTRACT_BYTECODE,
from: ACCOUNT_ADDRESS_1,
};

web3Mock
.onPost('contracts/call', { ...callData, estimate: false, block: 'latest' })
.reply(200, { result: EXAMPLE_CONTRACT_BYTECODE });
const result = await ethImpl.call(callData, 'latest');
expect(result).to.eq(EXAMPLE_CONTRACT_BYTECODE);
});
});

describe('contractCallFormat', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function ()

sdkClientStub.submitEthereumTransaction.returns({
txResponse: {
transactionId: TransactionId.fromString(transactionIdServicesFormat),
transactionId: '',
},
fileId: null,
});
Expand Down
6 changes: 5 additions & 1 deletion packages/relay/tests/lib/formatters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,11 @@ describe('Formatters', () => {

it('should return false for an address with an undefined value', () => {
const address = undefined;
expect(isValidEthereumAddress(address)).to.equal(false);
expect(isValidEthereumAddress(address as any)).to.equal(false);
});
it('should return false for an address with a null value', () => {
const address = null;
expect(isValidEthereumAddress(address as any)).to.equal(false);
});

it('should return false for an address with a null value', () => {
Expand Down
31 changes: 23 additions & 8 deletions packages/server/tests/acceptance/rpc_batch3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,25 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
expect(res).to.eq(BASIC_CONTRACT_PING_RESULT);
});

it('@release should execute "eth_call" request to simulate deploying a contract with `to` field being null', async function () {
const callData = {
from: accounts[0].address,
to: null,
data: basicContractJson.bytecode,
};
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
expect(res).to.eq(basicContractJson.deployedBytecode);
});

it('@release should execute "eth_call" request to simulate deploying a contract with `to` field being empty/undefined', async function () {
const callData = {
from: accounts[0].address,
data: basicContractJson.bytecode,
};
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
expect(res).to.eq(basicContractJson.deployedBytecode);
});

it('should fail "eth_call" request without data field', async function () {
const callData = {
from: accounts[0].address,
Expand Down Expand Up @@ -503,18 +522,14 @@ describe('@api-batch-3 RPC Server Acceptance Tests', function () {
expect(res).to.eq('0x0000000000000000000000000000000000000000000000000000000000000004');
});

it("009 should fail for missing 'to' field", async function () {
it("009 should work for missing 'to' field", async function () {
const callData = {
from: accounts[0].address,
data: '0x0ec1551d',
data: basicContractJson.bytecode,
};

await relay.callFailing(
RelayCall.ETH_ENDPOINTS.ETH_CALL,
[callData, 'latest'],
predefined.INVALID_CONTRACT_ADDRESS(undefined),
requestId,
);
const res = await relay.call(RelayCall.ETH_ENDPOINTS.ETH_CALL, [callData, 'latest'], requestId);
expect(res).to.eq(basicContractJson.deployedBytecode);
});

// value is processed only when eth_call goes through the mirror node
Expand Down
Loading