Skip to content

feat(sdk-core): consume slug_name's canonical string carrier - #83

Merged
heifner merged 5 commits into
masterfrom
chore/slug-carrier-sibling
Sep 25, 2026
Merged

heifner merged 5 commits into
masterfrom
chore/slug-carrier-sibling

Conversation

@heifner

@heifner heifner commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Landing 5 of the slug_name carrier unification. Merge after wire-sysio#619 — the string carrier only exists once the depot's ABI builtin renders it.

What changes

A slug field arrives from a table read, and leaves in an action, as its canonical spelling. Both row unwrappers (rowSlugValue, reserveRowSlugValue) and both {value} writers (chainSlugData, reserveSlugData) are deleted — with the string carrier they were identity or wrong, and their call sites now use chainSlugValue/reserveSlugValue and chainSlugString/reserveSlugString directly.

A silent mis-decode removed

chainSlugValue and reserveRowSlugValue each had a numeric-string branch reading a digit-only string as a decimal. But the slug alphabet contains digits, so "12345678" is a legitimate code whose packed value is nothing like 12345678 — the branch mis-decoded exactly the codes that look like numbers. The packed form is passed as a number; a string is always parsed as a slug. This is the same ambiguity #619 closes host-side, and it is why the carrier could never have been a numeric string.

*SlugName stop being Struct subclasses

ChainsSlugName and ReservSlugName are now ABISerializableObject types modelled on Name. A Struct cannot express this type: the object decoder dispatches on type.fields before consulting a class's own from() (Decoder.ts:364-366), so a struct-shaped slug can only ever be written {value} — overriding static from to accept the string fails there, which is how this was found.

Wire bytes are unchanged: a struct of one uint64 packs as that uint64, so the new type emits identical bytes. The synchronous codec path and the AnyAction ABI fallback now both take the same spelling.

They stay per contract. One shared chain/SlugName.ts beside Name would be the right home, but that name collides with the published SlugName packing utility whose from() returns a number, and renaming that breaks a v1.0.90 surface. The duplication predates this — both already existed as @Struct.type("slug_name") structs, and Structs.ts is organised per contract.

Scope note

The plan listed this landing as "regenerate SysioContractTypes.ts once". That was under-specified: the regen alone leaves the four reader/writer helpers and uwrit/Client.ts's ReserveIdentitySlugs typed against the old object shape. All are converted here; ReserveIdentitySlugs now derives its three fields by indexed access off the generated uwreqs row so a future codegen change propagates.

The regenerated SysioContractTypes.ts comes from #619's new slug_name builtin mapping in generate-sysio-contract-types.py (the generator had no entry at all, and every slug field would have become unknown once abigen stops emitting the struct_def). Its diff is slug-only. A further regen follows landing 4's field conversions.

Verification

pnpm build clean, pnpm lint clean, 556/556 jest across 53 suites.

New coverage: the digit-only-code case ("12345678" parses as a slug, not its decimal, and a packed value passed as a string is rejected), and both forms of the new type asserted together — the packed wire value and the canonical spelling its toJSON returns.

pnpm-lock.yaml is deliberately not in this commit; the local delta is worktree sibling-link churn.

heifner added a commit that referenced this pull request Sep 22, 2026
A pre-builtin depot emits a slug_name as an object — the reflected struct
is object-only — and the depot still accepts that shape inbound. A
string-only reader cannot straddle the landing window, so #83 could not
merge before the node change; with this it can.

slugValue is now the one decoder for every carrier (string, number,
bigint, or the { value } object) and both per-contract readers delegate
to it instead of duplicating the body. Matches wire-tools' slugUtils,
which already takes all three. Delete SlugNameObject once no depot emits
the object form.

Change-Id: Icfbe2e544e4416194ab12e2434e90cb795189c7a

@jglanz jglanz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to rebase before trying to merge.

Nit: Number("123") is dangerous, parseInt("123",10) is the correct approach

A slug field now arrives and leaves as its canonical spelling, matching
the depot's ABI builtin (wire-sysio#619). The two row unwrappers and the
two {value} writers are gone, and chainSlugValue/reserveSlugValue no
longer read a digit-only string as a decimal — "12345678" is a valid slug
whose packed value is nothing like it, so that branch silently mis-decoded.

ChainsSlugName and ReservSlugName stop being Struct subclasses and become
ABISerializableObject types modelled on Name. A Struct cannot express this:
the object decoder dispatches on type.fields before consulting a class's
own from(), so a struct-shaped slug can only ever be written {value}. Wire
bytes are unchanged — a struct of one uint64 packs as that uint64.

Regenerates SysioContractTypes against the generator's new slug_name
builtin mapping; the diff is slug-only.

Change-Id: Id1e57c61100d20daa53d6275ed39c886a02e04d8
A code must start with a letter, so SlugName.from refuses a digit- or
underscore-leading spelling rather than packing one the depot will reject —
the failure surfaces at the parse with its reason instead of at pushAction
as an opaque ABI error.

toString stays total: it is a reader, and a throwing renderer in a scan loop
is what stalls a consumer.

Change-Id: Ibfcfee56719a9800f1ab7e41719f1dd527062576
A pre-builtin depot emits a slug_name as an object — the reflected struct
is object-only — and the depot still accepts that shape inbound. A
string-only reader cannot straddle the landing window, so #83 could not
merge before the node change; with this it can.

slugValue is now the one decoder for every carrier (string, number,
bigint, or the { value } object) and both per-contract readers delegate
to it instead of duplicating the body. Matches wire-tools' slugUtils,
which already takes all three. Delete SlugNameObject once no depot emits
the object form.

Change-Id: Icfbe2e544e4416194ab12e2434e90cb795189c7a
…ster

Generated from the wire-sysio#619 ABIs, which carry master's sysio.liq and
sysio.swap contracts; slug_name fields are the builtin string carrier.

Change-Id: Ia77e57d060a151de76b36d5bbfc4f74a8cd7d50b
Number() coerces "", " 12 ", "0x10" and "1e3", and parseInt stops at the
first non-digit, so neither rejects a malformed { value } carrier. Parse through
UInt64.from, which accepts only a base-10 integer or a safe integer, and convert
the slug structs with toNumber(), which throws past 53 bits instead of rounding.

Change-Id: Iff04918124c7dce134becefa1cc9e1a53ebf9e1e
@heifner
heifner force-pushed the chore/slug-carrier-sibling branch from 041a4e9 to 7420cd2 Compare September 25, 2026 13:57
@heifner

heifner commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

@jglanz Rebased onto master: the only conflict was the generated SysioContractTypes.ts, now regenerated from the wire-sysio#619 ABIs (d9ed232). The rebase dismissed your approval, so this needs another look.

On the Number() nit: agreed it was too loose, but parseInt(s, 10) isn't stricter. It stops at the first non-digit, so "12abc" gives 12 and "1e3" gives 1. In 7420cd2 the { value } carrier now goes through UInt64.from(v).toNumber(), which accepts only a base-10 integer string or a safe integer, and toNumber() throws past 53 bits. "", " 12 ", "0x10", "1e3", "12abc", "-5" and "1.5" are all rejected now, with tests. The two slug-struct toString() calls use this.value.toNumber() for the same reason. sdk-core: 576/576.

@heifner
heifner merged commit e2db4b9 into master Sep 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants