Skip to content

Commit 57c9fa1

Browse files
authored
fix: skipping calling resolveEntityType if action is CREATE (#4372)
Signed-off-by: Simeon Nakov <[email protected]>
1 parent 256d146 commit 57c9fa1

File tree

3 files changed

+111
-61
lines changed

3 files changed

+111
-61
lines changed

packages/relay/src/lib/debug.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ export class DebugImpl implements Debug {
291291
// The actions endpoint does not return input and output for the calls so we get them from another endpoint
292292
// The first one is excluded because we take its input and output from the contracts/results/{transactionIdOrHash} endpoint
293293
const contract =
294-
index !== 0 && action.call_operation_type === CallType.CREATE
294+
index !== 0 && action.call_operation_type === CallType.CREATE && action.to
295295
? await this.mirrorNodeClient.getContract(action.to, requestDetails)
296296
: undefined;
297297

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

Lines changed: 77 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,28 @@ describe('Debug API Test Suite', async function () {
6161
const CONTRACTS_RESULTS_BY_NON_EXISTENT_HASH = `contracts/results/${nonExistentTransactionHash}`;
6262
const CONTRACT_RESULTS_BY_ACTIONS_NON_EXISTENT_HASH = `contracts/results/${nonExistentTransactionHash}/actions`;
6363

64+
// Helper to reduce repetition when creating CREATE actions for tests
65+
const makeCreateAction = (overrides: Partial<any> = {}) => ({
66+
call_depth: 0,
67+
call_operation_type: 'CREATE',
68+
call_type: 'CREATE',
69+
caller: '0.0.1016',
70+
caller_type: 'ACCOUNT',
71+
from: senderAddress,
72+
gas: 247000,
73+
gas_used: 77324,
74+
index: 0,
75+
input: '0x',
76+
recipient: '0.0.1033',
77+
recipient_type: 'CONTRACT',
78+
result_data: '0x',
79+
result_data_type: 'OUTPUT',
80+
timestamp: '1696438011.462526383',
81+
to: contractAddress,
82+
value: 0,
83+
...overrides,
84+
});
85+
6486
const opcodeLoggerConfigs = [
6587
{
6688
disableStack: true,
@@ -199,44 +221,19 @@ describe('Debug API Test Suite', async function () {
199221

200222
const contractsResultsActionsResult = {
201223
actions: [
202-
{
203-
call_depth: 0,
204-
call_operation_type: 'CREATE',
205-
call_type: 'CREATE',
206-
caller: '0.0.1016',
207-
caller_type: 'ACCOUNT',
208-
from: '0x00000000000000000000000000000000000003f8',
209-
gas: 247000,
210-
gas_used: 77324,
211-
index: 0,
212-
input: '0x',
213-
recipient: '0.0.1033',
214-
recipient_type: 'CONTRACT',
215-
result_data: '0x',
216-
result_data_type: 'OUTPUT',
217-
timestamp: '1696438011.462526383',
218-
to: '0x0000000000000000000000000000000000000409',
219-
value: 0,
220-
},
221-
{
224+
makeCreateAction({ index: 0 }),
225+
makeCreateAction({
222226
call_depth: 1,
223-
call_operation_type: 'CREATE',
224-
call_type: 'CREATE',
225227
caller: '0.0.1033',
226228
caller_type: 'CONTRACT',
227-
from: '0x0000000000000000000000000000000000000409',
229+
from: contractAddress,
228230
gas: 189733,
229231
gas_used: 75,
230232
index: 1,
231-
input: '0x',
232233
recipient: '0.0.1034',
233234
recipient_type: 'CONTRACT',
234-
result_data: '0x',
235-
result_data_type: 'OUTPUT',
236-
timestamp: '1696438011.462526383',
237-
to: '0x000000000000000000000000000000000000040a',
238-
value: 0,
239-
},
235+
to: contractAddress2,
236+
}),
240237
],
241238
};
242239

@@ -284,7 +281,7 @@ describe('Debug API Test Suite', async function () {
284281
});
285282

286283
this.beforeEach(() => {
287-
cacheService.clear(requestDetails);
284+
cacheService.clear();
288285
});
289286

290287
describe('debug_traceTransaction', async function () {
@@ -640,6 +637,45 @@ describe('Debug API Test Suite', async function () {
640637
expect(address).to.eq(accountAddress);
641638
});
642639
});
640+
641+
describe('formatActionsResult with CREATE actions', async function () {
642+
it('should handle CREATE with to=null and return expected fields', async function () {
643+
const createActionWithNullTo = {
644+
actions: [makeCreateAction({ to: null, input: '0x608060405234801561001057600080fd5b50', index: 0 })],
645+
};
646+
647+
restMock.onGet(SENDER_BY_ADDRESS).reply(200, JSON.stringify(accountsResult));
648+
649+
const result = await debugService.formatActionsResult(createActionWithNullTo.actions, requestDetails);
650+
651+
expect(result).to.be.an('array').with.lengthOf(1);
652+
expect(result[0]).to.have.property('type', 'CREATE');
653+
expect(result[0]).to.have.property('from', '0xc37f417fa09933335240fca72dd257bfbde9c275');
654+
expect(result[0]).to.have.property('to', null);
655+
expect(result[0]).to.have.property('input', '0x608060405234801561001057600080fd5b50');
656+
});
657+
658+
it('should handle CREATE with to=null and skip getContract call', async function () {
659+
const createActionWithNullTo = {
660+
actions: [makeCreateAction({ to: null, input: '0x608060405234801561001057600080fd5b50', index: 1 })],
661+
};
662+
663+
restMock.onGet(SENDER_BY_ADDRESS).reply(200, JSON.stringify(accountsResult));
664+
// No mock for getContract call - should not be called
665+
const getContractSpy = sinon.spy(mirrorNodeInstance, 'getContract');
666+
const result = await debugService.formatActionsResult(createActionWithNullTo.actions, requestDetails);
667+
668+
expect(result).to.be.an('array').with.lengthOf(1);
669+
expect(result[0]).to.have.property('type', 'CREATE');
670+
expect(result[0]).to.have.property('to', null);
671+
expect(result[0]).to.have.property('input', '0x608060405234801561001057600080fd5b50');
672+
expect(result[0]).to.have.property('output', '0x');
673+
// Ensure getContract was never invoked with a null/undefined id (i.e., for 'to')
674+
const calledWithNullId = getContractSpy.getCalls().some((c) => c.args[0] == null);
675+
expect(calledWithNullId).to.be.false;
676+
getContractSpy.restore();
677+
});
678+
});
643679
});
644680
});
645681
});
@@ -710,7 +746,7 @@ describe('Debug API Test Suite', async function () {
710746
sinon.restore();
711747
restMock.reset();
712748
web3Mock.reset();
713-
cacheService.clear(requestDetails);
749+
cacheService.clear();
714750
});
715751

716752
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: undefined }, () => {
@@ -894,44 +930,25 @@ describe('Debug API Test Suite', async function () {
894930
const accountAddress = '0x00000000000000000000000000000000000003f8';
895931

896932
const actionsResponseMock = [
897-
{
898-
call_depth: 0,
899-
call_operation_type: 'CREATE',
900-
call_type: 'CREATE',
933+
makeCreateAction({
901934
caller: accountId,
902935
caller_type: 'ACCOUNT',
903936
from: accountAddress,
904-
gas: 247000,
905-
gas_used: 77324,
906-
index: 0,
907-
input: '0x',
908937
recipient: contractId,
909-
recipient_type: 'CONTRACT',
910-
result_data: '0x',
911-
result_data_type: 'OUTPUT',
912-
timestamp: mockTimestamp,
913938
to: contractAddress,
914-
value: 0,
915-
},
916-
{
939+
timestamp: mockTimestamp,
940+
index: 0,
941+
}),
942+
makeCreateAction({
917943
call_depth: 1,
918-
call_operation_type: 'CREATE',
919-
call_type: 'CREATE',
920944
caller: contractId,
921945
caller_type: 'CONTRACT',
922946
from: contractAddress,
923-
gas: 189733,
924-
gas_used: 75,
925-
index: 1,
926-
input: '0x',
927947
recipient: '0.0.1034',
928-
recipient_type: 'CONTRACT',
929-
result_data: '0x',
930-
result_data_type: 'OUTPUT',
931-
timestamp: mockTimestamp,
932948
to: '0x000000000000000000000000000000000000040a',
933-
value: 0,
934-
},
949+
timestamp: mockTimestamp,
950+
index: 1,
951+
}),
935952
];
936953

937954
const contractEntityMock = {
@@ -999,7 +1016,7 @@ describe('Debug API Test Suite', async function () {
9991016
sinon.restore();
10001017
restMock.reset();
10011018
web3Mock.reset();
1002-
cacheService.clear(requestDetails);
1019+
cacheService.clear();
10031020
});
10041021

10051022
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: true }, () => {

packages/server/tests/acceptance/debug.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,39 @@ describe('@debug API Acceptance Tests', function () {
319319
predefined.INVALID_PARAMETER("'tracer' for TracerConfigWrapper", 'Expected TracerType, value: InvalidTracer'),
320320
);
321321
});
322+
323+
it('@release should handle CREATE transactions with to=null in trace results', async function () {
324+
// Just use the existing basic contract deployment to test CREATE transactions
325+
// The deployment block should contain a CREATE transaction
326+
const deploymentBlockHex = numberTo0x(deploymentBlockNumber);
327+
328+
// Call debug_traceBlockByNumber for the deployment block
329+
const result = await relay.call(DEBUG_TRACE_BLOCK_BY_NUMBER, [
330+
deploymentBlockHex,
331+
TRACER_CONFIGS.CALL_TRACER_TOP_ONLY_FALSE,
332+
]);
333+
334+
expect(result).to.be.an('array');
335+
expect(result.length).to.be.at.least(1);
336+
337+
// Find a CREATE transaction in the result (from contract deployment)
338+
const createTxTrace = result.find((trace) => trace.result && trace.result.type === 'CREATE');
339+
expect(createTxTrace).to.exist;
340+
expect(createTxTrace.result).to.exist;
341+
342+
// Verify it's a CREATE transaction with proper structure
343+
expect(createTxTrace.result.type).to.equal('CREATE');
344+
expect(createTxTrace.result.from).to.exist;
345+
expect(createTxTrace.result.to).to.exist;
346+
expect(createTxTrace.result.gas).to.exist;
347+
expect(createTxTrace.result.gasUsed).to.exist;
348+
expect(createTxTrace.result.input).to.exist;
349+
expect(createTxTrace.result.output).to.exist;
350+
351+
// This test verifies that CREATE actions with to=null don't cause Mirror Node lookup errors
352+
// The main goal is that the debug_traceBlockByNumber call succeeds without throwing
353+
// "Invalid parameter: contractid" errors when processing CREATE transactions
354+
});
322355
});
323356

324357
describe('debug_traceTransaction', () => {

0 commit comments

Comments
 (0)