Skip to content

Commit 69c781b

Browse files
authored
Handle empty state_changes array in contract details (#733)
* Handle empty state_change array in contract details Signed-off-by: lukelee-sl <[email protected]> * update errors to use predefined errors Signed-off-by: lukelee-sl <[email protected]> * remove redundant null check Signed-off-by: lukelee-sl <[email protected]> add utility function for brevity and clarity Signed-off-by: lukelee-sl <[email protected]> remove unused import Signed-off-by: lukelee-sl <[email protected]> Signed-off-by: lukelee-sl <[email protected]>
1 parent c952b75 commit 69c781b

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

packages/relay/src/lib/eth.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,7 @@ export class EthImpl implements Eth {
482482
*/
483483
async getStorageAt(address: string, slot: string, blockNumberOrTag?: string | null, requestId?: string) : Promise<string> {
484484
const requestIdPrefix = formatRequestIdMessage(requestId);
485+
this.logger.trace(`${requestIdPrefix} getStorageAt(address=${address}, slot=${slot}, blockNumberOrTag=${blockNumberOrTag})`);
485486
let result = EthImpl.zeroHex32Byte; // if contract or slot not found then return 32 byte 0
486487
const blockResponse = await this.getHistoricalBlockResponse(blockNumberOrTag, false);
487488

@@ -498,7 +499,7 @@ export class EthImpl implements Eth {
498499
// retrieve the contract result details
499500
await this.mirrorNodeClient.getContractResultsDetails(address, contractResult.results[0].timestamp)
500501
.then(contractResultDetails => {
501-
if (contractResultDetails?.state_changes != null) {
502+
if (EthImpl.isArrayNonEmpty(contractResultDetails?.state_changes)) {
502503
// filter the state changes to match slot and return value
503504
const stateChange = contractResultDetails.state_changes.find(stateChange => stateChange.slot === slot);
504505
result = stateChange.value_written;
@@ -1400,4 +1401,9 @@ export class EthImpl implements Eth {
14001401

14011402
return contractAddress;
14021403
}
1404+
1405+
static isArrayNonEmpty(input: any): boolean {
1406+
return Array.isArray(input) && input.length > 0;
1407+
}
1408+
14031409
}

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

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { Block, Transaction } from '../../src/lib/model';
4242
import constants from '../../src/lib/constants';
4343
import { SDKClient } from '../../src/lib/clients';
4444
import { SDKClientError } from '../../src/lib/errors/SDKClientError';
45+
4546
const LRU = require('lru-cache');
4647

4748
const logger = pino();
@@ -378,6 +379,18 @@ describe('Eth calls using MirrorNode', async function () {
378379
}
379380
};
380381

382+
const defaultDetailedContractResultsNullStateChange = {
383+
...defaultDetailedContractResults, ...{
384+
'state_changes' : null
385+
}
386+
};
387+
388+
const defaultDetailedContractResultsEmptyArrayStateChange = {
389+
...defaultDetailedContractResults, ...{
390+
'state_changes' : []
391+
}
392+
};
393+
381394
const detailedContractResultNotFound = { "_status": { "messages": [{ "message": "No correlating transaction" }] } };
382395
const timeoutError = { "type": "Error", "message": "timeout of 10000ms exceeded" };
383396

@@ -2213,8 +2226,40 @@ describe('Eth calls using MirrorNode', async function () {
22132226
await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber));
22142227
} catch (e: any) {
22152228
hasError = true;
2216-
expect(e.code).to.equal(-32001);
2217-
expect(e.name).to.equal('Resource not found');
2229+
expect(e.code).to.equal(predefined.RESOURCE_NOT_FOUND().code);
2230+
expect(e.name).to.equal(predefined.RESOURCE_NOT_FOUND().name);
2231+
}
2232+
expect(hasError).to.be.true;
2233+
});
2234+
2235+
it('eth_getStorageAt should throw a predefined RESOURCE_NOT_FOUND when state_changes is null', async function () {
2236+
defaultDetailedContractResultsNullStateChange
2237+
mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock);
2238+
mock.onGet(`contracts/${contractAddress1}/results?timestamp=lte:${defaultBlock.timestamp.to}&limit=1&order=desc`).reply(200, defaultContractResults);
2239+
mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResultsNullStateChange);
2240+
let hasError = false;
2241+
try {
2242+
await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber));
2243+
} catch (e: any) {
2244+
hasError = true;
2245+
expect(e.code).to.equal(predefined.RESOURCE_NOT_FOUND().code);
2246+
expect(e.name).to.equal(predefined.RESOURCE_NOT_FOUND().name);
2247+
}
2248+
expect(hasError).to.be.true;
2249+
});
2250+
2251+
it('eth_getStorageAt should throw a predefined RESOURCE_NOT_FOUND when state_changes is an empty array', async function () {
2252+
defaultDetailedContractResultsNullStateChange
2253+
mock.onGet(`blocks/${blockNumber}`).reply(200, defaultBlock);
2254+
mock.onGet(`contracts/${contractAddress1}/results?timestamp=lte:${defaultBlock.timestamp.to}&limit=1&order=desc`).reply(200, defaultContractResults);
2255+
mock.onGet(`contracts/${contractAddress1}/results/${contractTimestamp1}`).reply(200, defaultDetailedContractResultsEmptyArrayStateChange);
2256+
let hasError = false;
2257+
try {
2258+
await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber));
2259+
} catch (e: any) {
2260+
hasError = true;
2261+
expect(e.code).to.equal(predefined.RESOURCE_NOT_FOUND().code);
2262+
expect(e.name).to.equal(predefined.RESOURCE_NOT_FOUND().name);
22182263
}
22192264
expect(hasError).to.be.true;
22202265
});
@@ -2230,8 +2275,8 @@ describe('Eth calls using MirrorNode', async function () {
22302275
await ethImpl.getStorageAt(contractAddress1, defaultDetailedContractResults.state_changes[0].slot, EthImpl.numberTo0x(blockNumber));
22312276
} catch (e: any) {
22322277
hasError = true;
2233-
expect(e.code).to.equal(-32001);
2234-
expect(e.name).to.equal('Resource not found');
2278+
expect(e.code).to.equal(predefined.RESOURCE_NOT_FOUND().code);
2279+
expect(e.name).to.equal(predefined.RESOURCE_NOT_FOUND().name);
22352280
}
22362281
expect(hasError).to.be.true;
22372282
});

0 commit comments

Comments
 (0)