From 8a5215210b1b70ff08bc8710758727fea67e30a2 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 5 Feb 2025 15:13:45 +0200 Subject: [PATCH] we now include contract in the check as well + removed duplicate call to mirror node Signed-off-by: Simeon Nakov --- .../relay/src/lib/clients/mirrorNodeClient.ts | 9 +-- packages/relay/src/lib/eth.ts | 45 +++++++------- .../relay/tests/lib/eth/eth_getCode.spec.ts | 60 +++++++++++++++---- .../tests/acceptance/rpc_batch2.spec.ts | 11 +++- 4 files changed, 84 insertions(+), 41 deletions(-) diff --git a/packages/relay/src/lib/clients/mirrorNodeClient.ts b/packages/relay/src/lib/clients/mirrorNodeClient.ts index 0e8837973..c2d246fa9 100644 --- a/packages/relay/src/lib/clients/mirrorNodeClient.ts +++ b/packages/relay/src/lib/clients/mirrorNodeClient.ts @@ -1155,14 +1155,9 @@ export class MirrorNodeClient { ); } - public async getTokenById(tokenId: string, requestDetails: RequestDetails, retries?: number, timestamp?: string) { - const queryParamObject = {}; - if (timestamp) { - this.setQueryParam(queryParamObject, 'timestamp', timestamp); - } - const queryParams = this.getQueryParams(queryParamObject); + public async getTokenById(tokenId: string, requestDetails: RequestDetails, retries?: number) { return this.get( - `${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}${queryParams}`, + `${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`, MirrorNodeClient.GET_TOKENS_ENDPOINT, requestDetails, retries, diff --git a/packages/relay/src/lib/eth.ts b/packages/relay/src/lib/eth.ts index 76e8a5a43..5acbb63b1 100644 --- a/packages/relay/src/lib/eth.ts +++ b/packages/relay/src/lib/eth.ts @@ -19,7 +19,7 @@ */ import { ConfigService } from '@hashgraph/json-rpc-config-service/dist/services'; -import { FileId, Hbar, PrecheckStatusError } from '@hashgraph/sdk'; +import { ContractId, FileId, Hbar, PrecheckStatusError } from '@hashgraph/sdk'; import crypto from 'crypto'; import { Transaction as EthersTransaction } from 'ethers'; import { Logger } from 'pino'; @@ -1209,30 +1209,11 @@ export class EthImpl implements Eth { constants.TYPE_CONTRACT, constants.TYPE_TOKEN, ]); - if (result) { if (result?.type === constants.TYPE_TOKEN) { - if (blockNumber && !this.common.blockTagIsLatestOrPending(blockNumber)) { - let blockNumberInt; - if (blockNumber === EthImpl.blockEarliest) { - blockNumberInt = 0; - } else { - blockNumberInt = parseInt(blockNumber, 16); - } - const blockInfo = await this.mirrorNodeClient.getBlock(blockNumberInt, requestDetails); - - if (!blockInfo) { - throw predefined.UNKNOWN_BLOCK(`Block number ${blockNumber} does not exist`); - } - - const tokenId = Utils.addressToTokenId(address); - const tokenInfo = await this.mirrorNodeClient.getTokenById( - tokenId, - requestDetails, - undefined, - blockInfo.timestamp.to, - ); - if (!tokenInfo) { + const blockInfo = await this.getBlockInfo(blockNumber, requestDetails); + if (blockInfo) { + if (parseFloat(result.entity?.created_timestamp) > parseFloat(blockInfo.timestamp.to)) { return EthImpl.emptyHex; } } @@ -1241,6 +1222,12 @@ export class EthImpl implements Eth { } return EthImpl.redirectBytecodeAddressReplace(address); } else if (result?.type === constants.TYPE_CONTRACT) { + const blockInfo = await this.getBlockInfo(blockNumber, requestDetails); + if (blockInfo) { + if (parseFloat(result.entity?.created_timestamp) > parseFloat(blockInfo.timestamp.to)) { + return EthImpl.emptyHex; + } + } if (result?.entity.runtime_bytecode !== EthImpl.emptyHex) { const prohibitedOpcodes = ['CALLCODE', 'DELEGATECALL', 'SELFDESTRUCT', 'SUICIDE']; const opcodes = asm.disassemble(result?.entity.runtime_bytecode); @@ -2858,4 +2845,16 @@ export class EthImpl implements Eth { const exchangeRateInCents = currentNetworkExchangeRate.cent_equivalent / currentNetworkExchangeRate.hbar_equivalent; return exchangeRateInCents; } + + private async getBlockInfo(blockNumber: string | null, requestDetails: RequestDetails): Promise { + if (blockNumber && !this.common.blockTagIsLatestOrPending(blockNumber)) { + const blockNumberInt = blockNumber === EthImpl.blockEarliest ? 0 : parseInt(blockNumber, 16); + const blockInfo = await this.mirrorNodeClient.getBlock(blockNumberInt, requestDetails); + if (!blockInfo) { + throw predefined.UNKNOWN_BLOCK(`Block number ${blockNumber} does not exist`); + } + return blockInfo; + } + return null; + } } diff --git a/packages/relay/tests/lib/eth/eth_getCode.spec.ts b/packages/relay/tests/lib/eth/eth_getCode.spec.ts index 7d69ebd0e..dbb2aee73 100644 --- a/packages/relay/tests/lib/eth/eth_getCode.spec.ts +++ b/packages/relay/tests/lib/eth/eth_getCode.spec.ts @@ -18,6 +18,7 @@ * */ +import { ContractId } from '@hashgraph/sdk'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; import sinon from 'sinon'; @@ -48,8 +49,9 @@ let getSdkClientStub: sinon.SinonStub; describe('@ethGetCode using MirrorNode', async function () { this.timeout(10000); const { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv(); - const validBlockParam = [null, 'earliest', 'latest', 'pending', 'finalized', 'safe', '0x0', '0x369ABF']; - const invalidBlockParam = ['hedera', 'ethereum', '0xhbar', '0x369ABF369ABF369ABF369ABF']; + const earlyBlockParams = ['0x0', '0x369ABF', 'earliest']; + const otherValidBlockParams = [null, 'latest', 'pending', 'finalized', 'safe']; + const invalidBlockParam = ['hedera', 'ethereum', '0xhbar', '0x369ABF369ABF369ABF']; const requestDetails = new RequestDetails({ requestId: 'eth_getCodeTest', ipAddress: '0.0.0.0' }); @@ -131,8 +133,19 @@ describe('@ethGetCode using MirrorNode', async function () { expect(res).to.equal(EthImpl.invalidEVMInstruction); }); - validBlockParam.forEach((blockParam) => { - it(`should pass the validate param check with blockParam=${blockParam} and return the bytecode`, async () => { + earlyBlockParams.forEach((blockParam) => { + it(`should return empty bytecode for early block param ${blockParam}`, async () => { + const paramAsInt = blockParam === 'earliest' ? 0 : parseInt(blockParam, 16); + restMock.onGet(`blocks/${paramAsInt}`).reply(200, { + timestamp: { to: '1532175203.847228000' }, + }); + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, blockParam, requestDetails); + expect(res).to.equal(EthImpl.emptyHex); + }); + }); + + otherValidBlockParams.forEach((blockParam) => { + it(`should return deployed bytecode for block param ${blockParam}`, async () => { const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, blockParam, requestDetails); expect(res).to.equal(MIRROR_NODE_DEPLOYED_BYTECODE); }); @@ -168,11 +181,27 @@ describe('@ethGetCode using MirrorNode', async function () { timestamp: { to: blockToTimestamp }, }); - restMock.onGet(`tokens/0.0.${parseInt(HTS_TOKEN_ADDRESS, 16)}?timestamp=${blockToTimestamp}`).reply(404, null); const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, blockNumberBeforeCreation, requestDetails); expect(res).to.equal(EthImpl.emptyHex); }); + it('should return empty bytecode for contract before creation block', async () => { + const blockNumberBeforeCreation = '0x152a4aa'; + const blockToTimestamp = '1632175203.847228000'; + const contractId = ContractId.fromEvmAddress(0, 0, CONTRACT_ADDRESS_1); + + restMock.onGet(`contracts/${contractId.toString()}`).reply(200, { + ...DEFAULT_CONTRACT, + created_timestamp: '1632175205.855270000', + }); + restMock.onGet(`blocks/${parseInt(blockNumberBeforeCreation, 16)}`).reply(200, { + timestamp: { to: blockToTimestamp }, + }); + + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, blockNumberBeforeCreation, requestDetails); + expect(res).to.equal(EthImpl.emptyHex); + }); + it('should return redirect bytecode for HTS token after creation block', async () => { const blockNumberAfterCreation = '0x152a4ab'; const blockToTimestamp = '1632175206.000000000'; @@ -186,11 +215,6 @@ describe('@ethGetCode using MirrorNode', async function () { timestamp: { to: blockToTimestamp }, }); - restMock.onGet(`tokens/0.0.${parseInt(HTS_TOKEN_ADDRESS, 16)}?timestamp=${blockToTimestamp}`).reply(200, { - ...DEFAULT_HTS_TOKEN, - created_timestamp: '1632175205.855270000', - }); - const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, blockNumberAfterCreation, requestDetails); const expectedRedirectBytecode = `6080604052348015600f57600080fd5b506000610167905077618dc65e${HTS_TOKEN_ADDRESS.slice( 2, @@ -219,5 +243,21 @@ describe('@ethGetCode using MirrorNode', async function () { `Block number ${futureBlockNumber} does not exist`, ); }); + + it('should return empty bytecode for contract when earliest block is queried', async () => { + const blockToTimestamp = '1632175203.847228000'; + const contractId = ContractId.fromEvmAddress(0, 0, CONTRACT_ADDRESS_1); + + restMock.onGet(`contracts/${contractId.toString()}`).reply(200, { + ...DEFAULT_CONTRACT, + created_timestamp: '1632175205.855270000', + }); + restMock.onGet('blocks/0').reply(200, { + timestamp: { to: blockToTimestamp }, + }); + + const res = await ethImpl.getCode(CONTRACT_ADDRESS_1, 'earliest', requestDetails); + expect(res).to.equal(EthImpl.emptyHex); + }); }); }); diff --git a/packages/server/tests/acceptance/rpc_batch2.spec.ts b/packages/server/tests/acceptance/rpc_batch2.spec.ts index 49017e7fb..af3075138 100644 --- a/packages/server/tests/acceptance/rpc_batch2.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch2.spec.ts @@ -793,7 +793,16 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () { it('should return empty bytecode for HTS token when a block earlier than the token creation is passed', async function () { const res = await relay.call( RelayCalls.ETH_ENDPOINTS.ETH_GET_CODE, - [NftHTSTokenContractAddress, '0x123'], + [NftHTSTokenContractAddress, '0x123'], // a very early block number + requestId, + ); + expect(res).to.equal(EthImpl.emptyHex); + }); + + it('should return empty bytecode for contract when a block earlier than the contract creation is passed', async function () { + const res = await relay.call( + RelayCalls.ETH_ENDPOINTS.ETH_GET_CODE, + [mainContractAddress, '0x123'], // a very early block number requestId, ); expect(res).to.equal(EthImpl.emptyHex);