Skip to content

Commit

Permalink
fix: eth_getCode now uses blockNumber when address is an HTS token
Browse files Browse the repository at this point in the history
Signed-off-by: Simeon Nakov <simeon.nakov@limechain.tech>
  • Loading branch information
simzzz committed Jan 28, 2025
1 parent fe6c0cf commit e415dd6
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 9 deletions.
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 @@ export class EthImpl implements Eth {
]);
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);

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

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/lib/eth.ts#L1215-L1216

Added lines #L1215 - L1216 were not covered by tests

if (!blockInfo) {
throw predefined.UNKNOWN_BLOCK(`Block number ${blockNumber} does not exist`);

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L1219 was not covered by tests
}

const tokenId = this.common.addressToTokenId(address);
const tokenInfo = await this.mirrorNodeClient.getTokenById(tokenId, requestDetails);

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

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/lib/eth.ts#L1222-L1223

Added lines #L1222 - L1223 were not covered by tests

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;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L1230 was not covered by tests
}
}
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}`;

Check warning on line 395 in packages/relay/src/lib/services/ethService/ethCommonService/index.ts

View check run for this annotation

Codecov / codecov/patch

packages/relay/src/lib/services/ethService/ethCommonService/index.ts#L394-L395

Added lines #L394 - L395 were not covered by tests
}
}
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);
}
});
});
});

0 comments on commit e415dd6

Please sign in to comment.