linera-sdk: derive macro StableEnum (backport of #6309) - #6543
Draft
ma2bd wants to merge 2 commits into
Draft
Conversation
We want enum variant tags on the wire to be stable under variant reordering, similar to Solidity's 4-byte function selectors. * Today, BCS serializes the serde variant *index* (0, 1, 2, …, ULEB128-encoded), which changes whenever variants are reordered. * This is especially a problem when we want an ABI to extend several existing ones. (Typically different flavors of `fungible`). After this PR, we could occasionally merge enums with solutions like https://github.com/rafaeltheraven/extendable-data. Add a `#[derive(StableEnum)]` macro in `linera-sdk-derive` that emits, in one shot: * `Serialize` / `Deserialize` impls calling `serialize_*_variant(name, tag, …)` / matching `tag` from `EnumAccess`, where the tag is derived at compile time from the variant name: ``` tag = (be_u32(Keccak-256(name)[..4]) & 0x07FF_FFFF) | 0x0800_0000 ``` Masking the top 5 bits to `00001` constrains the tag to `[2^27, 2^28)`, which is exactly the range whose ULEB128 encoding is **always 4 bytes** — so every variant tag on the wire takes exactly 4 bytes. * A `StableEnumTrace` impl exposing the `(name, tag)` pairs and a `trace_all_variants` method that drives `serde_reflection::Tracer` without caller-supplied samples. (The default `Tracer::trace_type` cannot terminate on non-contiguous u32 indices like ours, so we provide our own tracing entry point.) Companion additions in `linera-sdk::formats`: * `trait StableEnumTrace` (with `const STABLE_VARIANTS: &[(&str, u32)]` and `fn trace_all_variants`). * `trait StableEnum: StableEnumTrace + Serialize + DeserializeOwned {}` blanket-implemented — usable as a single bound. * `TracerExt::trace_stable_enum_type::<T>(&Samples) -> Result<Format>` extension method on `Tracer`. * `StableEnumInCrate` variant of the derive (paths via `crate::…` instead of `::linera_sdk::…`) for use inside `linera-sdk` itself. The `StableEnumTrace` impl is `#[cfg(not(target_arch = \"wasm32\"))]` so applications keep building for wasm without `serde-reflection`. The `counter` example is migrated as a reference; other examples still use plain `#[derive(Serialize, Deserialize)]` and can be migrated in follow-up PRs. Note: Importantly, the variant index used by Serde is different from the "discriminant" used by Rust in memory. Assigning new tag values using `repr(u32)` and the Rust syntax only changes the discriminant. * New unit test `formats::tests::stable_enum_round_trip` in `linera-sdk` covering: * BCS roundtrip for unit / newtype / tuple / struct variants * Every variant tag is exactly 4 ULEB128 bytes on the wire (continuation-bit pattern asserted explicitly) * Unknown tags are rejected on deserialization * `STABLE_VARIANTS` constant matches what BCS emits * `trace_stable_enum_type` populates the registry with hashed keys * End-to-end `bcs_to_json` against the reflected registry * `counter` snapshot test updated: `CounterOperation::Increment` is now registered under tag `186772159` (`0x0b21eabf`) instead of index `0`. * All other counter tests (`single_chain`, contract/service unit tests) continue to pass. This should backported to `testnet_conway`. --------- Signed-off-by: Mathieu Baudet <1105398+ma2bd@users.noreply.github.com> Co-authored-by: Mathieu Baudet <mathieu.baudet@linera.io> Co-authored-by: Andreas Fackler <afck@users.noreply.github.com>
…tableEnum backport
ma2bd
marked this pull request as draft
June 20, 2026 22:40
21 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Backport of #6309 to
testnet_conway. This is the prerequisite for backporting #6314 (applyStableEnumto all ABI Operation/Response enums), which the release plan calls for.Proposal
Cherry-pick #6309, which adds the
#[derive(StableEnum)]/StableEnumInCratemacros tolinera-sdk-deriveplus the companionStableEnumTrace/StableEnumtraits andTracerExt::trace_stable_enum_typeinlinera-sdk::formats. Stable enums lock each variant tag to a 4-byte Keccak-derived ULEB128 value, decoupling the wire format from declaration order.Conway-specific adaptations
The cherry-pick did not apply cleanly because conway's
counterexample still usestype Operation = u64— theCounterOperationenum onmainwas introduced by the unrelated #6264 (application introspection), which is not ontestnet_conway. I applied only the operation-enum migration that #6309 depends on, leaving conway's other differences (ANCHOR docs,QueryRoot, non-Arcservice state) untouched:examples/counter(lib.rs,contract.rs,service.rs,tests/single_chain.rs): migrate the operation type fromu64toCounterOperation::Increment { value }and add#[derive(StableEnum)].linera-sdk/tests/fixtures/meta-counter/src/contract.rsandlinera-core/src/unit_tests/wasm_client_tests.rs: wrap the cross-application / user operations inCounterOperation::Incrementso the wire format matches the migrated contract.sha3dependency edge onlinera-sdk-derive(root,examples, andlinera-sdk/tests/fixtures).Test Plan
linera-sdkunit tests pass, includingformats::tests::stable_enum_round_trip.counterexample tests pass, including theformatsnapshot test (regenerated Keccak tag) and thesingle_chainend-to-end test (real wasm round-trip ofCounterOperation).cargo check -p linera-core --tests --features wasmercompiles (coverswasm_client_testsand themeta-counterfixture).cargo +nightly fmt --checkandcargo clippyclean on the root,examples, and fixtures workspaces; all three lockfiles verified--locked-consistent.Release Plan
testnetbranch (this PR targetstestnet_conway).Links
StableEnum#6309StableEnumfor all ContractAbi Operation/Response enums #6314