diff --git a/src/evm/SolidityMetadata.test.ts b/src/evm/SolidityMetadata.test.ts index bf1eb5385d..ff6c76bf9a 100644 --- a/src/evm/SolidityMetadata.test.ts +++ b/src/evm/SolidityMetadata.test.ts @@ -107,12 +107,27 @@ it("keeps the compiler version beside a shortest-form empty text", () => { expect(result.entries).toHaveLength(2); }); +it("decodes distinct byte keys and distinct nested-container keys", () => { + expect( + inspectSolidityMetadata(bytecode("a2410101410202")).entries.map( + ({ key }) => key, + ), + ).toEqual([ + { kind: "bytes", value: "01" }, + { kind: "bytes", value: "02" }, + ]); + expect(inspectSolidityMetadata(bytecode("a2810101810202"))).toMatchObject({ + status: "partial", + }); +}); + it.each([ ["invalid text", "a1617861ff"], ["truncated value", "a16178"], ["unsupported tag", "a16178c000"], ["floating point", "a16178fa3f800000"], ["duplicate key", "a2617801617802"], + ["duplicate byte key", "a2410101410102"], ["excessive nesting", "a16178" + "81".repeat(65) + "00"], ["impossible collection", "a161788a00"], ])("keeps exact ranges and unavailable interpretation for %s", (_name, hex) => { diff --git a/src/evm/SolidityMetadata.ts b/src/evm/SolidityMetadata.ts index 8a82bceed4..b5cdfb64b1 100644 --- a/src/evm/SolidityMetadata.ts +++ b/src/evm/SolidityMetadata.ts @@ -122,8 +122,21 @@ export const inspectSolidityMetadata = ( }); if (!(raw instanceof Map)) throw new Error("Terminal CBOR candidate did not decode to a root map."); - for (const [key, value] of raw as Map) - result.entries.push({ key: scalar(key), value: scalar(value) }); + // cborg compares Map keys by identity, so repeated byte-string keys (each + // its own Uint8Array) pass its duplicate check; compare scalars by value. + const seen = new Set(); + for (const [key, value] of raw as Map) { + const entry = { key: scalar(key), value: scalar(value) }; + if (entry.key.kind !== "uninterpreted") { + const identity = JSON.stringify(entry.key); + if (seen.has(identity)) + throw new Error( + `CBOR decode error: found repeat map key ${identity}`, + ); + seen.add(identity); + } + result.entries.push(entry); + } result.status = result.entries.some( ({ key, value }) => key.kind === "uninterpreted" || value.kind === "uninterpreted",