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

fix: fixed eth_getCode to now use blockNumber when address is an HTS token #3433

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,25 @@
]);
if (result) {
if (result?.type === constants.TYPE_TOKEN) {
if (blockNumber && !this.common.blockTagIsLatestOrPending(blockNumber)) {
const 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 = this.common.addressToTokenId(address);
const tokenInfo = await this.mirrorNodeClient.getTokenById(tokenId, requestDetails);

if (!tokenInfo) {
return EthImpl.emptyHex;

Check warning on line 1226 in packages/relay/src/lib/eth.ts

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/lib/eth.ts#L1226

Added line #L1226 was not covered by tests
}

if (parseFloat(tokenInfo.created_timestamp) > parseFloat(blockInfo.timestamp.to)) {
return EthImpl.emptyHex;
}
}
if (this.logger.isLevelEnabled('trace')) {
this.logger.trace(`${requestIdPrefix} Token redirect case, return redirectBytecode`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,9 @@ export class CommonService implements ICommonService {

return this.getLogsWithParams(address, params, requestDetails);
}

public addressToTokenId(address: string): string {
const tokenNum = parseInt(address.slice(-8), 16);
return `0.0.${tokenNum}`;
}
}
82 changes: 73 additions & 9 deletions packages/relay/tests/lib/eth/eth_getCode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
*/

import { expect, use } from 'chai';
import sinon from 'sinon';
import chaiAsPromised from 'chai-as-promised';
import sinon from 'sinon';

import { EthImpl } from '../../../src/lib/eth';
import { SDKClientError } from '../../../src/lib/errors/SDKClientError';
import { JsonRpcError, predefined } from '../../../src';
import { SDKClient } from '../../../src/lib/clients';
import { SDKClientError } from '../../../src/lib/errors/SDKClientError';
import { EthImpl } from '../../../src/lib/eth';
import { RequestDetails } from '../../../src/lib/types';
import { overrideEnvsInMochaDescribe } from '../../helpers';
import {
CONTRACT_ADDRESS_1,
DEFAULT_CONTRACT,
Expand All @@ -36,9 +39,6 @@ import {
NO_TRANSACTIONS,
} from './eth-config';
import { generateEthTestEnv } from './eth-helpers';
import { JsonRpcError, predefined } from '../../../src';
import { overrideEnvsInMochaDescribe } from '../../helpers';
import { RequestDetails } from '../../../src/lib/types';

use(chaiAsPromised);

Expand All @@ -47,9 +47,9 @@ let getSdkClientStub: sinon.SinonStub;

describe('@ethGetCode using MirrorNode', async function () {
this.timeout(10000);
let { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv();
let validBlockParam = [null, 'earliest', 'latest', 'pending', 'finalized', 'safe', '0x0', '0x369ABF'];
let invalidBlockParam = ['hedera', 'ethereum', '0xhbar', '0x369ABF369ABF369ABF369ABF'];
const { restMock, hapiServiceInstance, ethImpl, cacheService } = generateEthTestEnv();
const validBlockParam = [null, 'earliest', 'latest', 'pending', 'finalized', 'safe', '0x0', '0x369ABF'];
const invalidBlockParam = ['hedera', 'ethereum', '0xhbar', '0x369ABF369ABF369ABF369ABF'];

const requestDetails = new RequestDetails({ requestId: 'eth_getCodeTest', ipAddress: '0.0.0.0' });

Expand Down Expand Up @@ -155,5 +155,69 @@ describe('@ethGetCode using MirrorNode', async function () {
}
});
});

it('should return empty bytecode for HTS token before creation block', async () => {
const blockNumberBeforeCreation = '0x152a4aa'; // Example block number before creation
restMock.onGet(`tokens/0.0.${parseInt(HTS_TOKEN_ADDRESS, 16)}`).reply(200, {
...DEFAULT_HTS_TOKEN,
created_timestamp: '1632175205.855270000',
});
restMock.onGet(`blocks/${parseInt(blockNumberBeforeCreation, 16)}`).reply(200, {
timestamp: { to: '1632175203.847228000' },
});
const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, blockNumberBeforeCreation, requestDetails);
expect(res).to.equal(EthImpl.emptyHex);
});

it('should return redirect bytecode for HTS token after creation block', async () => {
const blockNumberAfterCreation = '0x152a4ab'; // Example block number after creation
restMock.onGet(`tokens/0.0.${parseInt(HTS_TOKEN_ADDRESS, 16)}`).reply(200, {
...DEFAULT_HTS_TOKEN,
created_timestamp: '1632175205.855270000',
});
restMock.onGet(`blocks/${parseInt(blockNumberAfterCreation, 16)}`).reply(200, {
timestamp: { to: '1632175206.000000000' },
});
const res = await ethImpl.getCode(HTS_TOKEN_ADDRESS, blockNumberAfterCreation, requestDetails);
const expectedRedirectBytecode = `6080604052348015600f57600080fd5b506000610167905077618dc65e${HTS_TOKEN_ADDRESS.slice(
2,
)}600052366000602037600080366018016008845af43d806000803e8160008114605857816000f35b816000fdfea2646970667358221220d8378feed472ba49a0005514ef7087017f707b45fb9bf56bb81bb93ff19a238b64736f6c634300080b0033`;
expect(res).to.equal(expectedRedirectBytecode);
});

it('should throw error for invalid block number', async () => {
const invalidBlockNumber = '0xinvalid';
try {
await ethImpl.getCode(HTS_TOKEN_ADDRESS, invalidBlockNumber, requestDetails);
expect(true).to.eq(false);
} catch (error: any) {
const expectedError = predefined.UNKNOWN_BLOCK(
`The value passed is not a valid blockHash/blockNumber/blockTag value: ${invalidBlockNumber}`,
);
expect(error).to.exist;
expect(error instanceof JsonRpcError);
expect(error.code).to.eq(expectedError.code);
expect(error.message).to.eq(expectedError.message);
}
});

it('should throw error when block does not exist', async () => {
const futureBlockNumber = '0x1000000';
restMock.onGet(`contracts/${HTS_TOKEN_ADDRESS}`).reply(404, null);
restMock.onGet(`accounts/${HTS_TOKEN_ADDRESS}?limit=100`).reply(404, null);
restMock.onGet(`tokens/0.0.${parseInt(HTS_TOKEN_ADDRESS, 16)}`).reply(200, DEFAULT_HTS_TOKEN);
restMock.onGet(`blocks/${parseInt(futureBlockNumber, 16)}`).reply(404, null);

try {
await ethImpl.getCode(HTS_TOKEN_ADDRESS, futureBlockNumber, requestDetails);
expect(true).to.eq(false);
} catch (error: any) {
const expectedError = predefined.UNKNOWN_BLOCK(`Block number ${futureBlockNumber} does not exist`);
expect(error).to.exist;
expect(error instanceof JsonRpcError);
expect(error.code).to.eq(expectedError.code);
expect(error.message).to.eq(expectedError.message);
}
});
});
});
Loading