From 15e98cc9db69baf7bc79987491c607504df96ec3 Mon Sep 17 00:00:00 2001 From: Edgars Date: Wed, 1 Jul 2026 22:52:15 +0100 Subject: [PATCH] feat(calldata)!: move method-call key from "method" to "" (empty string) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.6 genvm-manager renamed the method-call calldata map key from "method" to "" (empty string). The empty key sorts first in canonical calldata map encoding (keys ordered by unicode-codepoint array; the empty array precedes any non-empty key), so the method name becomes a prefix of the encoded binary calldata. makeCalldataObject now writes the method name under ret[""]. The decoder is unchanged: it builds a generic Map from raw key bytes and already reads whatever key is present. No back-compat toggle — there is no calldata versioning mechanism in this repo, so this is a hard cutover. The genvm-manager still auto-remaps "method" -> "" but logs an error each time; this migration stops that error. BREAKING CHANGE: method-call calldata now uses the "" key instead of "method". Encoded bytes for every write/read/view/schema call change. Peers must run the v0.6 genvm-manager (or a node/studio build that expects the "" key). --- src/abi/calldata/encoder.ts | 4 +++- tests/calldata.test.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/calldata.test.ts diff --git a/src/abi/calldata/encoder.ts b/src/abi/calldata/encoder.ts index 63a792d..b3395db 100644 --- a/src/abi/calldata/encoder.ts +++ b/src/abi/calldata/encoder.ts @@ -142,6 +142,8 @@ export function encode(data: CalldataEncodable): Uint8Array { } // Constructs a calldata object for contract calls, omitting empty args/kwargs for compactness. +// Method names use the empty-string key, which sorts first in canonical calldata encoding +// and makes the method name a prefix of the encoded binary. export function makeCalldataObject( method: string | undefined, args: CalldataEncodable[] | undefined, @@ -150,7 +152,7 @@ export function makeCalldataObject( let ret: {[key: string]: CalldataEncodable} = {}; if (method) { - ret["method"] = method; + ret[""] = method; } if (args && args.length > 0) { diff --git a/tests/calldata.test.ts b/tests/calldata.test.ts new file mode 100644 index 0000000..9b798bc --- /dev/null +++ b/tests/calldata.test.ts @@ -0,0 +1,35 @@ +import {describe, expect, it} from "vitest"; +import {calldata} from "@/abi"; +import type {CalldataEncodable} from "@/types/calldata"; + +function decodeMap(data: Uint8Array): Map { + const decoded = calldata.decode(data); + expect(decoded).toBeInstanceOf(Map); + return decoded as Map; +} + +describe("calldata method-call encoding", () => { + it("encodes method calls with the empty-string method key", () => { + const encoded = calldata.encode(calldata.makeCalldataObject("my_method", [1n, 2n], undefined)); + const decoded = decodeMap(encoded); + + expect(decoded.has("")).toBe(true); + expect(decoded.get("")).toBe("my_method"); + expect(decoded.has("method")).toBe(false); + }); + + it("orders the empty method key before args in the encoded map body", () => { + const encoded = calldata.encode(calldata.makeCalldataObject("my_method", [1n, 2n], undefined)); + const decoded = decodeMap(encoded); + + expect(Array.from(decoded.entries())[0]).toEqual(["", "my_method"]); + }); + + it("orders the empty method key before kwargs in the encoded map body", () => { + const encoded = calldata.encode(calldata.makeCalldataObject("my_method", undefined, {count: 1n})); + const decoded = decodeMap(encoded); + + expect(Array.from(decoded.entries())[0]).toEqual(["", "my_method"]); + expect(decoded.has("kwargs")).toBe(true); + }); +});