Skip to content

Commit 1ad0b51

Browse files
authored
fix: address issue with getGenesis rpc call for local development (#53)
1 parent 9b299a9 commit 1ad0b51

File tree

3 files changed

+156
-28
lines changed

3 files changed

+156
-28
lines changed

src/core/rpc/__tests__/zks.test.ts

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -75,29 +75,69 @@ describe('rpc/zks.normalizeGenesis', () => {
7575
genesis_root: '0x' + '55'.repeat(32),
7676
};
7777

78-
it('normalizes tuples and camel-cases field names', () => {
78+
it('normalizes tuples and camel-cases field names (raw additional_storage)', () => {
7979
const normalized = normalizeGenesis(sample);
80+
8081
expect(normalized).toEqual({
8182
initialContracts: [
8283
{ address: sample.initial_contracts[0][0], bytecode: sample.initial_contracts[0][1] },
8384
{ address: sample.initial_contracts[1][0], bytecode: sample.initial_contracts[1][1] },
8485
],
8586
additionalStorage: [
86-
{ key: sample.additional_storage[0][0], value: sample.additional_storage[0][1] },
87+
{
88+
format: 'raw',
89+
key: sample.additional_storage[0][0],
90+
value: sample.additional_storage[0][1],
91+
},
8792
],
8893
executionVersion: sample.execution_version,
8994
genesisRoot: sample.genesis_root,
9095
});
9196
});
9297

93-
it('throws ZKsyncError on malformed response', () => {
94-
try {
95-
normalizeGenesis(null);
96-
throw new Error('expected to throw');
97-
} catch (e) {
98-
expect(isZKsyncError(e)).toBe(true);
99-
expect(String(e)).toContain('Malformed genesis response');
100-
}
98+
it('normalizes pretty additional_storage map format', () => {
99+
const addr = '0x' + 'aa'.repeat(20);
100+
const slot = '0x' + '33'.repeat(32);
101+
const val = '0x' + '44'.repeat(32);
102+
103+
const prettySample = {
104+
...sample,
105+
additional_storage: {
106+
[addr]: {
107+
[slot]: val,
108+
},
109+
},
110+
};
111+
112+
const normalized = normalizeGenesis(prettySample);
113+
114+
expect(normalized.additionalStorage).toEqual([
115+
{
116+
format: 'pretty',
117+
address: addr,
118+
key: slot,
119+
value: val,
120+
},
121+
]);
122+
});
123+
124+
it('falls back to additional_storage_raw when additional_storage is missing', () => {
125+
const fallbackSample = {
126+
initial_contracts: sample.initial_contracts,
127+
additional_storage_raw: [['0x' + '33'.repeat(32), '0x' + '44'.repeat(32)]],
128+
execution_version: sample.execution_version,
129+
genesis_root: sample.genesis_root,
130+
};
131+
132+
const normalized = normalizeGenesis(fallbackSample);
133+
134+
expect(normalized.additionalStorage).toEqual([
135+
{
136+
format: 'raw',
137+
key: fallbackSample.additional_storage_raw[0][0],
138+
value: fallbackSample.additional_storage_raw[0][1],
139+
},
140+
]);
101141
});
102142
});
103143

@@ -222,26 +262,60 @@ describe('rpc/zks.getReceiptWithL2ToL1', () => {
222262
});
223263

224264
describe('rpc/zks.getGenesis', () => {
225-
it('returns normalized genesis data', async () => {
265+
it('returns normalized genesis data (raw additional_storage)', async () => {
226266
const raw = {
227267
initial_contracts: [['0x' + '11'.repeat(20), '0x' + 'aa'.repeat(4)]],
228268
additional_storage: [['0x' + '22'.repeat(32), '0x' + '33'.repeat(32)]],
229269
execution_version: 9,
230270
genesis_root: '0x' + '44'.repeat(32),
231271
};
272+
232273
const rpc = createZksRpc(fakeTransport({ zks_getGenesis: raw }));
233274
const out = await rpc.getGenesis();
275+
234276
expect(out).toEqual({
235277
initialContracts: [
236278
{ address: raw.initial_contracts[0][0], bytecode: raw.initial_contracts[0][1] },
237279
],
238280
additionalStorage: [
239-
{ key: raw.additional_storage[0][0], value: raw.additional_storage[0][1] },
281+
{
282+
format: 'raw',
283+
key: raw.additional_storage[0][0],
284+
value: raw.additional_storage[0][1],
285+
},
240286
],
241287
executionVersion: raw.execution_version,
242288
genesisRoot: raw.genesis_root,
243289
});
244290
});
291+
it('returns normalized genesis data (pretty additional_storage)', async () => {
292+
const addr = '0x' + 'aa'.repeat(20);
293+
const slot = '0x' + '22'.repeat(32);
294+
const val = '0x' + '33'.repeat(32);
295+
296+
const raw = {
297+
initial_contracts: [['0x' + '11'.repeat(20), '0x' + 'aa'.repeat(4)]],
298+
additional_storage: {
299+
[addr]: {
300+
[slot]: val,
301+
},
302+
},
303+
execution_version: 9,
304+
genesis_root: '0x' + '44'.repeat(32),
305+
};
306+
307+
const rpc = createZksRpc(fakeTransport({ zks_getGenesis: raw }));
308+
const out = await rpc.getGenesis();
309+
310+
expect(out.additionalStorage).toEqual([
311+
{
312+
format: 'pretty',
313+
address: addr,
314+
key: slot,
315+
value: val,
316+
},
317+
]);
318+
});
245319
});
246320

247321
describe('rpc/zks.getBlockMetadataByNumber', () => {

src/core/rpc/types.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@ export type GenesisContractDeployment = {
3636
bytecode: Hex;
3737
};
3838

39-
export type GenesisStorageEntry = {
40-
key: Hex;
41-
value: Hex;
42-
};
39+
export type GenesisStorageEntry =
40+
| { format: 'raw'; key: Hex; value: Hex } // key = hashed_key
41+
| { format: 'pretty'; address: Address; key: Hex; value: Hex }; // key = slot
4342

4443
export type GenesisInput = {
4544
initialContracts: GenesisContractDeployment[];

src/core/rpc/zks.ts

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ function ensureBigInt(
174174
});
175175
}
176176

177+
function isRecord(x: unknown): x is Record<string, unknown> {
178+
return !!x && typeof x === 'object' && !Array.isArray(x);
179+
}
180+
177181
function normalizeContractTuple(tuple: unknown, index: number): GenesisContractDeployment {
178182
if (!Array.isArray(tuple) || tuple.length < 2) {
179183
throw createError('RPC', {
@@ -191,7 +195,11 @@ function normalizeContractTuple(tuple: unknown, index: number): GenesisContractD
191195
};
192196
}
193197

194-
function normalizeStorageTuple(tuple: unknown, index: number): GenesisStorageEntry {
198+
// Normalizes a "raw" storage entry tuple: [key, value]
199+
function normalizeRawStorageTuple(
200+
tuple: unknown,
201+
index: number,
202+
): Extract<GenesisStorageEntry, { format: 'raw' }> {
195203
if (!Array.isArray(tuple) || tuple.length < 2) {
196204
throw createError('RPC', {
197205
resource: 'zksrpc' as Resource,
@@ -203,11 +211,67 @@ function normalizeStorageTuple(tuple: unknown, index: number): GenesisStorageEnt
203211

204212
const [keyRaw, valueRaw] = tuple as [unknown, unknown];
205213
return {
214+
format: 'raw' as const,
206215
key: ensureHex(keyRaw, 'additional_storage.key', { index }),
207216
value: ensureHex(valueRaw, 'additional_storage.value', { index }),
208217
};
209218
}
210219

220+
// Normalizes additional storage entries from either "raw" or "pretty" format.
221+
function normalizeAdditionalStorage(
222+
value: unknown,
223+
record: Record<string, unknown>,
224+
): GenesisStorageEntry[] {
225+
const effective = value ?? record['additional_storage_raw'];
226+
227+
// Raw tuple format: [[key, value], ...]
228+
if (Array.isArray(effective)) {
229+
return effective.map((entry, index) => {
230+
const kv = normalizeRawStorageTuple(entry, index);
231+
return { format: 'raw' as const, key: kv.key, value: kv.value };
232+
});
233+
}
234+
235+
// Pretty format: { [address]: { [slot]: value } }
236+
if (isRecord(effective)) {
237+
const out: GenesisStorageEntry[] = [];
238+
for (const [addrRaw, slotsRaw] of Object.entries(effective)) {
239+
const address = ensureHex(addrRaw, 'additional_storage.address', {});
240+
241+
if (!isRecord(slotsRaw)) {
242+
throw createError('RPC', {
243+
resource: 'zksrpc' as Resource,
244+
operation: 'zksrpc.normalizeGenesis',
245+
message: 'Malformed genesis response: additional_storage[address] must be an object map.',
246+
context: { address, valueType: typeof slotsRaw },
247+
});
248+
}
249+
250+
for (const [slotRaw, valRaw] of Object.entries(slotsRaw)) {
251+
out.push({
252+
format: 'pretty' as const,
253+
address,
254+
key: ensureHex(slotRaw, 'additional_storage.key', { address }),
255+
value: ensureHex(valRaw, 'additional_storage.value', { address, key: slotRaw }),
256+
});
257+
}
258+
}
259+
return out;
260+
}
261+
262+
throw createError('RPC', {
263+
resource: 'zksrpc' as Resource,
264+
operation: 'zksrpc.normalizeGenesis',
265+
message:
266+
'Malformed genesis response: additional_storage must be an array (raw) or an object map (pretty).',
267+
context: {
268+
valueType: typeof effective,
269+
hasAdditionalStorage: 'additional_storage' in record,
270+
hasAdditionalStorageRaw: 'additional_storage_raw' in record,
271+
},
272+
});
273+
}
274+
211275
// Normalizes the genesis response into camel-cased fields and typed entries.
212276
export function normalizeGenesis(raw: unknown): GenesisInput {
213277
try {
@@ -232,23 +296,14 @@ export function normalizeGenesis(raw: unknown): GenesisInput {
232296
});
233297
}
234298

235-
const storageRaw = record['additional_storage'];
236-
if (!Array.isArray(storageRaw)) {
237-
throw createError('RPC', {
238-
resource: 'zksrpc' as Resource,
239-
operation: 'zksrpc.normalizeGenesis',
240-
message: 'Malformed genesis response: additional_storage must be an array.',
241-
context: { valueType: typeof storageRaw },
242-
});
243-
}
244-
245299
const executionVersion = ensureNumber(record['execution_version'], 'execution_version');
246300
const genesisRoot = ensureHex(record['genesis_root'], 'genesis_root', {});
247301

248302
const initialContracts = contractsRaw.map((entry, index) =>
249303
normalizeContractTuple(entry, index),
250304
);
251-
const additionalStorage = storageRaw.map((entry, index) => normalizeStorageTuple(entry, index));
305+
306+
const additionalStorage = normalizeAdditionalStorage(record['additional_storage'], record);
252307

253308
return {
254309
initialContracts,

0 commit comments

Comments
 (0)