Skip to content

WIRE-301: Retire legacy staking attestations - #544

Open
huangminghuang wants to merge 17 commits into
masterfrom
fix/wire-301-retire-stake-unstake
Open

WIRE-301: Retire legacy staking attestations#544
huangminghuang wants to merge 17 commits into
masterfrom
fix/wire-301-retire-stake-unstake

Conversation

@huangminghuang

@huangminghuang huangminghuang commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

WIRE-301: Retire legacy STAKE and UNSTAKE attestations

Removes the obsolete pre-release staking attestation surface from sysio: protobuf definitions, generated OPP table types, ABI entries, schemas, and contract tests. The message-channel dispatcher remains tolerant of historical unknown raw attestations rather than treating them as current staking operations.

This PR is the protocol-side change for the companion updates in wire-ethereum#175, wire-libraries-ts#57, wire-tools-ts#52, and wire-platform-manifest#4.

Validation

  • Remote Release platform E2E: green run 32053044069, pinned to b6064b30287abae8b2dc431d7b444fd8ca649efc and the companion PR SHAs.
  • Local Phase A: Release //:platform; CDT 24/24, libraries 885/885, tools 1711/1711; focused sysio dispatch tests 89 cases and focused Ethereum outpost tests 43 passing.
  • A serial local Phase B confirmation is continuing after the parallel harness attempt exposed the known host-global Hardhat deploy lock; seven completed flows are green so far.

BUILD.bazel contains no PR change: the local Release-vcpkg/triplet override was validation-only, uncommitted, and excluded from this branch.

Change-Id: I6295e0e573de1a523870927964b07419518febd3
Change-Id: I5089a8c7ef276f6977909595d7616e448e7e751d
Change-Id: I854a2f145483551ecde57249a92b19265cc2e9ce

@heifner heifner 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.

Review — high effort

Read the full diff plus the surrounding buildenv / dispatch_attestation / estimate_svm_dynamic_accounts code, the KV secondary-index erase contract in wire-cdt, the ABI enum fallback in abi_serializer, the FC_REFLECT_ENUM reflector, and the Solidity generator.

4 findings inline — 1 medium (the proto reserved list is incomplete), 3 low.

Verified clean — stating these so they don't get re-litigated

  • The it = idx.erase(std::move(it)) pattern matches the two pre-existing loops later in the same function. CDT's erase advances before erasing, the move is required (move-only iterator), every loop path increments, and the it != end() short-circuit holds — no infinite loop, no end-iterator deref.
  • Dropping the two values from sysio.msgch.abi / sysio.uwrit.abi does not break table reads: abi_serializer::_binary_to_variant falls back to the raw integer for unknown enum values.
  • Dropping them from FC_REFLECT_ENUM is safe — the only host-side consumer (underwriter_plugin) resolves via AttestationType_Parse/_Name, which returns false/"" for unknown values and continues. I diffed the FC_REFLECT_ENUM member list against the proto enum: exact match.
  • dispatch_attestation has a default: break;, so retired wire values genuinely land on the documented no-op path.
  • The reserved statements sitting above ATTESTATION_TYPE_UNSPECIFIED = 0 do not violate proto3's first-value-must-be-zero rule, and the max enum value is unchanged (60962) — no Solidity UDVT width change from this PR.
  • encode_envelope_padded_to's switch from STAKE (2-byte varint) to CHALLENGE_RESPONSE (3-byte varint) is self-correcting via the probe pass, and CHALLENGE_RESPONSE has no dispute side effect in evalcons — an equivalent inert padding type.

The two I'd act on before merge are the proto reserved gap (mechanical, closes the whole class of retired-slot reverts) and the unbounded prune (a bounded per-call cap, matching the pattern 3673936b9f already established).

// ---------------------------------------------------------------------------

enum AttestationType {
reserved 3001, 3002;

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.

Only 2 of the ~12 retired slots are reserved.

The new generator passthrough keys off reserved_range, so only 3001/3002 get the fromRaw blackhole. The same enum documents ~10 other retired numeric slots as bare comments — 60929 (NATIVE_YIELD_REWARD), 60933 (SLASH_OPERATOR), 60935–60938 (UNDERWRITE_*), 60946 (EPOCH_SYNC), 60948 (REMIT_CONFIRM), 60954, 60957 — and those still hit revert InvalidEnumValue in the generated AttestationTypeLib.fromRaw.

Concrete failure: a historical depot→ETH envelope carrying attestation type 60933 reaches OPPInbound. The whole envelope decode reverts, the outpost never reaches consensus for that epoch, and OPP circulation halts — the exact failure mode this PR's blackhole was built to prevent, just for a different retired number.

Adding those numbers to the reserved list costs nothing and closes the class.

// Erase them before destination-specific estimation so neither the SVM
// terminal-account gate nor an outpost decoder can be blocked by a
// protocol value that no longer has a generated enum/message type.
if (is_retired_staking_attestation(it->type)) {

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.

Unbounded prune inside an inline action.

This erases every retired READY row across all chains in a single transaction, and buildenv runs as an inline action inside sysio.epoch::advance.

If the residual legacy row count is large enough that N × (primary erase + 3 secondary-index erases) exceeds the transaction CPU budget, advance reverts. The erases roll back with it, so every subsequent advance retries the identical work and reverts again — a deterministic, non-self-healing epoch stall.

The immediately preceding master commit (3673936b9f, uwrit UWREQ expiry) used bounded epoch-driven pruning for this same class of migration. A per-call cap plus a "skip, don't candidate" branch for the over-budget remainder would make this drain across epochs instead of all-or-nothing.

Rating this low because the realistic residual count is small — an EVM-bound STAKE row would already have been packed and drained by the pre-upgrade contract, so only SVM-bound rows survive — but the failure mode has no recovery path if it does land.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed on current head 2c29ce1. buildenv caps retired-row erasure at 32 per call and continues skipping every over-budget retired row as an envelope candidate, so cleanup drains deterministically across calls without forwarding tombstones or turning one advance into an unbounded erase sweep. Coverage includes 33 retired rows followed by an active row; the active row emits and exactly one tombstone remains READY.

values: EnumValueInfo[],
reservedRanges: EnumReservedRangeInfo[] = []
): string {
const maxReserved = reservedRanges.map(range => range.end)

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.

reserved N to max; would silently widen the UDVT.

reserved N to max; is a common protobuf idiom and yields end = 2147483647. Folding that into the width computation promotes the enum from uint8 to uint32, changing the ABI encoding of every generated Solidity struct that contains it — a wire-incompatible break triggered by a declaration that adds no valid values.

A reserved slot is by definition never a valid value, so it arguably shouldn't participate in sizing at all: either clamp/ignore open-ended ranges, or size on declared values only and range-check in fromRaw before the cast.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed on current head 2c29ce1. Solidity enum width is derived only from declared active values. fromRaw checks type(underlying).max before any reserved-value cast, so an open-ended reservation cannot widen the UDVT or be truncated during narrowing. Generator regressions cover both active-value width selection and overflow-before-cast; all 84 generator tests pass.

Comment thread contracts/tests/sysio.msgch_tests.cpp Outdated
/// value are tombstoned before destination-specific envelope construction.
/// Active rows behind the tombstone still emit normally, so one legacy row
/// cannot strand the queue or reach an outpost decoder.
BOOST_FIXTURE_TEST_CASE(buildenv_tombstones_retired_staking_rows,

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.

Coverage gap: this only exercises the EVM path.

The scenario the tombstone primarily protects against is the SVM one — where an untombstoned retired row hits check(estimate.has_value(), "no Solana terminal account estimate for READY attestation") and hard-reverts buildenv. That path is untested.

Also untested: the retired-row-is-the-last-READY-row case, where erase must return the end iterator and the loop condition has to short-circuit before dereferencing.

Both are cheap additions to this same fixture.

Change-Id: I54ece9a5e0960db22552d75ab8a85dc6e7a9390f
…-stake-unstake

Change-Id: I35809cf5227b54138a5588c96c5d45c56a04a6c4
Change-Id: I07bc8da01d67db07ef97f2349fe10d6d9eaffd53
Change-Id: I5c06591c04db2c66d2fa9d792f37579c4c258c5b
@huangminghuang
huangminghuang requested a review from heifner August 4, 2026 18:30

@huangminghuang huangminghuang left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed all four review findings on current head 2c29ce1; implementation and validation details are replied inline.

// Erase them before destination-specific estimation so neither the SVM
// terminal-account gate nor an outpost decoder can be blocked by a
// protocol value that no longer has a generated enum/message type.
if (is_retired_staking_attestation(it->type)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed on current head 2c29ce1. buildenv caps retired-row erasure at 32 per call and continues skipping every over-budget retired row as an envelope candidate, so cleanup drains deterministically across calls without forwarding tombstones or turning one advance into an unbounded erase sweep. Coverage includes 33 retired rows followed by an active row; the active row emits and exactly one tombstone remains READY.

@heifner heifner 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.

Reviewed 2c29ce1b03 against the previously reviewed 1672016757. All four findings are addressed. One new LOW — found by walking the proto's git history rather than its comments, which is where my original list came from.


Verification of the four findings

1. MEDIUM — incomplete reserved list → addressed for everything I listed

reserved 3001, 3002, 60929, 60933, 60935 to 60938, 60946, 60948, 60954, 60957; — an exact match to the ten I named. See the new finding below; my list was itself incomplete.

2. LOW — unbounded prune → FIXED, exactly the suggested shape

MAX_RETIRED_STAKING_PRUNE_PER_BUILD = 32, with over-budget retired rows taking the ++it skip branch so they are never envelope candidates (sysio.msgch.cpp:1733-1741). I re-walked the loop: every path still advances it, and the it != end() && status == READY condition still short-circuits. Drain is 32 per buildenv call per chain, and the scan continues past the cap so active rows behind tombstones still emit — which the new test asserts directly.

3. LOW — reserved N to max; widening the UDVT → FIXED, and the guard is real

computeUnderlyingType now sizes on declared values only, and fromRaw gets if (_raw > type(<underlying>).max) revert emitted before the reserved-range casts.

I checked the thing that would have made this cosmetic: fromRaw's parameter is always uint64 regardless of the underlying type (enum.ts:105), so the guard genuinely catches an unrepresentable reserved value rather than being a tautology. Ordering is right — active values match by equality first, reserved values below the cap wrap, reserved values above it revert. The two new generator tests assert that ordering via indexOf rather than mere presence.

4. LOW — coverage gap → FIXED, and the fixture hack is sound

All three cases landed: SVM path, last-row erase, and the 33-row cap with an active row behind.

I checked the part that could have made the SVM test hollow. retarget_attestation_for_upgrade_test mutates chainbase KV directly, so I verified the claim in its comment: attestations_t declares exactly three secondary indices — bystatus, bytype, byepoch — and none of them indexes chain_code (sysio.msgch.hpp:192-199), so the retarget leaves no stale index entry behind. The hack is also genuinely necessary: queueout:1657-1660 hard-checks estimate_svm_dynamic_accounts(...).has_value() for SVM destinations, so a retired type cannot be queued there through the action.

Also verified

  • The committed sysio.msgch.wasm hashes to a01ce94d99b98566845092bb33f4699028b3d151973934deb4360d73bb6a2df1, matching the PR body.
  • The diff against the merge base is still 23 WIRE-301-scoped files, so the master merge did not drag unrelated content into the PR's own diff.
  • Active max is still 60962 → uint16, so no UDVT width change.

New finding

LOW — five more historically-declared slots are still unreserved

libraries/opp/proto/sysio/opp/types/types.proto:232

My original list came from the retirement comments in the enum, and the reserved list now matches it exactly. But walking all 33 revisions of types.proto turns up 17 values that were once declared and are gone today — and five of them are in neither the active set nor reserved:

Slot Former name Where it went
60931 ATTESTATION_TYPE_OPERATOR_REG_DEREG became OPERATOR_ACTION = 2001
60939 ATTESTATION_TYPE_CHALLENGE_REQUEST renumbered to 60945
60940 ATTESTATION_TYPE_EPOCH_SYNC renumbered to 60946, then retired
60941 ATTESTATION_TYPE_ROSTER_UPDATE no current equivalent
60942 ATTESTATION_TYPE_REMIT_CONFIRM renumbered to 60948, then retired

These hit revert InvalidEnumValue in the generated AttestationTypeLib.fromRaw exactly like the ten now covered, and the PR's own stated criterion is "reserve every historical raw protobuf slot that no longer has an active enum member".

I would calibrate the severity down from my original framing, and it is worth saying plainly: per no-back-compat-before-release.md nothing is deployed, so there is no field envelope carrying 60931 — the decode-revert scenario is bounded to long-lived test clusters whose state predates the change.

What does hold regardless of release status is reserved's primary purpose: preventing accidental reuse of a vacated number. That hazard is not hypothetical here — the history shows 60957 already carried two different names (SWAP_REJECTED, then EMISSIONS_BLOCKED) before being retired. Adding five numbers to the existing line costs nothing.


Note

ATTESTATION_TYPE_EMISSIONS_BLOCKED = 60962 arrived from the master merge documented as "DEPRECATED — no longer emitted", but stays declared rather than deleted-and-reserved. Keeping it declared is the safer state (it decodes rather than reverts), so this is not a defect — but the release now carries two different retirement conventions for the same enum. Worth deciding which one is the standard before a third retirement picks arbitrarily.


I did not rebuild the WASMs from source. I verified the committed sysio.msgch.wasm matches the hash you published, but source-to-binary reproduction across the six changed contracts is the Release build's job, not something confirmable from the diff.

…-stake-unstake

Change-Id: I85dc5a951f109d851e895643b9e10adf6fca0e19
Change-Id: I07c0b476f48297807dc3a1c45fcff926e3a397bf

@heifner heifner 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.

Re-reviewed 90e92f54d2 against the previously reviewed 2c29ce1b03. The PR-owned delta is one file — types.proto — plus a master merge that brought only PR #547's Python test.

The open finding is fixed, and I verified it mechanically rather than against my list

My original list came from reading the retirement comments, which is why it was incomplete twice. This time I scripted it: walked all 35 revisions of types.proto, extracted every ATTESTATION_TYPE_* = N ever declared, and diffed against head.

  • 42 distinct values ever declared. Head has 25 active + 17 reserved = 42. Zero gaps.
  • Also zero reserved numbers that were never declared — the list is not over-reserved either.
  • protoc parses the whole head proto tree clean, which independently proves no reserved value collides with a declared one.
  • The descriptor dump confirms 10 reserved_range entries, with 60935 to 60942 as one inclusive range.

I checked the thing that could have made that last point cosmetic: the Solidity plugin hand-rolls EnumDescriptorProto rather than using the official descriptor, and its reserved_range field number (4) and start/end (1/2) match descriptor.proto. Enum reserved ranges are inclusive on both ends there — unlike message reserved ranges — so the generator genuinely sees all 17 and the emitted bounds are right.

The final commit cannot have invalidated the committed binaries

  • protoc-gen-zpp contains no reference to reserved anywhere in its source, so a reserved-only proto edit cannot change the generated CDT headers. The WASMs predating this commit are still correct for it.
  • The final master merge brought only tests/nodeop_chainbase_allocation_test.py — no contract sources.
  • sysio.msgch.wasm still hashes a01ce94d99b98566845092bb33f4699028b3d151973934deb4360d73bb6a2df1.

I did not rebuild from source; that remains the Release build's job.

New finding — LOW: four other OPP enums are still in the pre-PR state

Running the same history walk across every enum in every OPP proto turns up four with vacated slots documented by comment only, or not at all:

Enum Vacated slot(s) Former name Documented?
ChainKind 4 CHAIN_KIND_SUI comment only
TokenKind 256, 257, 258, 259, 496, 512, 752 ETH, ERC20, ERC721, ERC1155, LIQETH, SOL, LIQSOL comment only
ActionType 5 ACTION_TYPE_WITHDRAW_CONFIRMED no
UnderwriteStatus 4 UNDERWRITE_STATUS_SLASHED (now 10) no

The reuse hazard is not hypothetical here. TokenKind's own comment records that 257/258/259 were re-issued at slots 2/3/4. AttestationType did it too: slot 60953 carried EMISSIONS_BLOCKED on 5/04 and was reused for UNDERWRITE_INTENT_COMMIT on 5/08.

One precision worth stating: for TokenKind the vacated values sit above the narrowed uint8, so the overflow guard added in this PR fires first and fromRaw still reverts. Reserving those buys reuse-prevention only, not decode tolerance. ChainKind 4, ActionType 5, and UnderwriteStatus 4 would get both.

Fine to take here or as a follow-up — the point is that the exhaustive-walk methodology should apply enum-wide rather than to one enum.

New finding — LOW: the runtime tombstone covers 2 of the 17 retired slots

is_retired_staking_attestation hardcodes 3001/3002. estimate_svm_dynamic_accounts ends in default: return std::nullopt, and buildenv hard-checks estimate.has_value(), so an SVM-bound legacy READY row carrying any of the other 15 retired values reproduces exactly the failure this PR's tombstone was built for. The generated Solidity blackhole now covers all 17; the contract-side gate covers 2.

Calibrating this honestly, and downward: the other 15 were vacated between 4/13 and 5/20, and the SEC-94 SVM gate landed 7/01, so a residual row would have to have survived unclaimed since well before either — much thinner than the 3001/3002 window, which stayed open until this PR. Redeploy is the recovery pre-launch, so this is not a stuck-state argument.

What I would act on is the coupling. A future retirement now has to remember two places with nothing linking them. Either widen the predicate to a named constexpr array of retired slots, or at minimum put a pointer in the proto's reserved line to the contract predicate so the next retirement finds it.


Nothing else changed since the last round, and the four earlier findings remain fixed.

…-stake-unstake

Change-Id: I5a3b357118626f452abe6449cd77aa10af03a73e
Change-Id: I819c3799420430cdb24cc17dcb988f35ed0e70f6
…-stake-unstake

Change-Id: I5a092f3111c3f2044ec179ce67b92000801b41bf

# Conflicts:
#	contracts/sysio.reserv/sysio.reserv.wasm
#	contracts/sysio.uwrit/sysio.uwrit.wasm
@huangminghuang
huangminghuang marked this pull request as draft August 8, 2026 23:39
Change-Id: If79c1c0ca74d9ae52145860e224ef8d5882c3059
@huangminghuang
huangminghuang requested a review from heifner August 10, 2026 13:52
@huangminghuang
huangminghuang marked this pull request as ready for review August 10, 2026 13:53

@heifner heifner 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.

Re-reviewed c2c10fa70a against the previously reviewed 90e92f54d2.

The PR changed character: c2c10fa70a reverted the tombstone, the bounded prune, the Solidity reserved_range passthrough, the reserved list, and the tests added for them. What's left is a plain deletion (15 files, +13/−86). Both round-3 findings are resolved by deletion — and the failure mode that motivated the tombstone is gone from master entirely: e751620a9e ("Drive resumable Solana dispatch from the depot") removed estimate_svm_dynamic_accounts, solana_terminal_budget.hpp, and the check(estimate.has_value(), ...) gate.

CI is green on the head.

1. BLOCKING (mechanical) — needs a rebase; one of the two remaining C++ hunks edits a function master deleted

mergeStateStatus: DIRTY, three conflicts:

  • contracts/sysio.msgch/src/sysio.msgch.cpp lines 86–156 — delete/modify against e751620a9e. Resolve by taking master's deletion, at which point that half of the PR's source delta simply evaporates.
  • sysio.chalg.wasm, sysio.roa.wasm — binary, and not PR-authored: both blobs entered via the 08-04 merges and exist in master's own history (f68b985dc6 at dc8d2cf5cf, 4db1d3d41c at dcf8f48e64). Take master's.

Everything in the body's Validation section is measured against merge-base 03b610de39, now ~40 commits stale — including the companion-repo revisions.

2. MEDIUM — 3001/3002 removed with no marker of any kind

libraries/opp/proto/sysio/opp/types/types.proto:233

Every other retirement in this enum leaves one:

// 60929 (0xEE01) was ATTESTATION_TYPE_NATIVE_YIELD_REWARD — removed; do not reuse.
// 60933 was standalone SLASH_OPERATOR — removed; SLASH is now an OperatorAction sub-type.
// 60935 was ATTESTATION_TYPE_UNDERWRITE_INTENT — removed; do not reuse.   (…60936–60938, 60946, …)

3001/3002 get nothing — the enum now goes straight from OPERATOR_ACTION = 2001 to PRETOKEN_PURCHASE = 3004. That makes this the only retirement in the enum's history with no trace, and the body still claims the numbers were reserved.

The failure is silent: a future author sees an unmarked gap and picks 3001; anything built before this PR decodes it as ATTESTATION_TYPE_STAKE and drops it on the default arm while the depot records it delivered and processed. No error anywhere. The reuse hazard is documented in this file's own past — 60957 carried two different names before being retired.

To be explicit, I am not asking to restore the reserved list as back-compat — dropping it is a defensible read of no-back-compat-before-release. Protoc enforcement and a comment are schema hygiene, not compatibility paths. reserved 3001, 3002; + reserved "ATTESTATION_TYPE_STAKE", "ATTESTATION_TYPE_UNSTAKE"; makes reuse mechanically impossible; two comment lines match the eight already in the file.

3. LOW (pre-existing) — missing break in the switch this PR edits

contracts/sysio.msgch/src/sysio.msgch.cpp:937 (head) / :890 (master)

// Outbound-only types ... are dropped silently.
case ATTESTATION_TYPE_SWAP_REVERT:
case ATTESTATION_TYPE_DEPOSIT_REVERT:
case ATTESTATION_TYPE_OPERATORS:
case ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS:
case ATTESTATION_TYPE_NODE_OWNER_REG:
   dispatch_node_owner_reg(data, chain_code);
   break;

Four types the comment calls "dropped silently" are routed into the node-owner claim handler. Not reachable — I checked each against the guards: dispatch_node_owner_reg requires tier ∈ [1,3] (field 4) and a usable wire_pub_key (field 9). Operators has only field 1, so tier decodes as 0 and it returns at the tier gate. SwapRevert/DepositRevert have string reason at field 4 (wire-type mismatch against a varint) and no field 9. BatchOperatorGroups is the near-miss — field 4 is epoch_duration_sec, same number and wire type as tier — but it has no field 9 either.

So no type reaches newnameduser; the defect is that the code does not do what its comment says. Pre-existing at master and at the merge base, so not WIRE-301's to fix — but this PR edits that exact switch, so a 4-line split is cheap, or it gets a ticket alongside WIRE-329.

4. MINOR — CHALLENGE_RESPONSE is a less durable test fixture than STAKE was

contracts/tests/sysio.dispatch_tests.cpp:1070 (also :153, :158, :1407, sysio.msgch_chain_tests.cpp:378)

STAKE was retired and could never grow a handler. CHALLENGE_RESPONSE is an active type whose depot-side handler is deferred by choice ("resolved on the WIRE side by evalcons … not by inbound challenge attestations"). The day one is wired, dispatch_silently_drops_out_of_scope_types' balances.size() == 0 assertion goes vacuous and the pad payload starts being decoded — surfacing as a failure in an unrelated PR. ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR or a PRETOKEN_* value is permanently inert.

The two-pass sizing itself is fine: the 3001→60932 change widens the type varint from 2 to 3 bytes, both passes use the same type so overhead absorbs it, and the trailing BOOST_REQUIRE_EQUAL pins it.

5. NITS

  • The body is stale in three places: it claims the numbers were reserved (there are none), claims tracked Release WASM artifacts were regenerated (the PR now authors no wasm change), and says "Rollout: Draft" while the PR is not a draft. Earlier rounds verified specific artifacts from this body, so staleness here misleads the next reader.
  • Two comments left ragged by the mechanical STAKECHALLENGE_RESPONSE substitution: dispatch_tests.cpp ~189 (effect). Probe / once with …) and msgch_chain_tests.cpp:360 (~110 cols). One reflow each.

Verified clean — stating these so they don't get re-litigated

  • The retirement is complete. Zero references to ATTESTATION_TYPE_STAKE/UNSTAKE/PretokenStakeChange anywhere in the tree at head, and zero in the merged tree.
  • Reflection parity holds through the merge. proto AttestationType vs FC_REFLECT_ENUM in opp.hpp: 25/25 at head, 25/25 merged, 27/27 on master — exact match at each.
  • ABI coverage is complete. sysio.msgch.abi and sysio.uwrit.abi are the only ABIs embedding the enum; both regenerated, no stale entries. abi_serializer.cpp:612 still falls back to the raw integer for unknown enum values, so dropping them does not break table reads.
  • CHALLENGE_RESPONSE is genuinely inert today, despite the sysio.chalg work that landed on master since — dispatch_attestation still routes CHALLENGE_REQUEST/RESPONSE to break. The substitution survives the merge, and encode_envelope_padded_to's comment merges coherently with master's 32 KiB cap (acd2b7e494 lowered it from 64 KiB).
  • WASM hygiene improved. At 90e92f54d2 the PR moved six contract binaries; at head it authors none. uwrit/reserv never had a source change to justify moving, so reverting them is the right end state. The remaining msgch source delta (two case labels in an arm behaviourally identical to default:) is codegen-neutral, consistent with the committed wasm matching base — but the rebase touches that file anyway, so the artifact question resettles afterward.

I did not rebuild from source; that remains the Release build's job.

Round 3's other finding — ChainKind, TokenKind, ActionType, UnderwriteStatus with vacated slots documented by comment only or not at all — is untouched. If finding 2 isn't taken, AttestationType joins that group.

…-stake-unstake

Change-Id: I4f46e5b40885fe475fdf57c3d041da716942a9bd

# Conflicts:
#	contracts/sysio.chalg/sysio.chalg.wasm
#	contracts/sysio.msgch/src/sysio.msgch.cpp
#	contracts/sysio.roa/sysio.roa.wasm
Change-Id: If570d4fac4f6ae24f1f1f1794bee3ad20ecb0e71
…-stake-unstake

Change-Id: I9177fef3b24dadba8554d357dd9f73eaeee1edf9
…-stake-unstake

Change-Id: I775172c82d11c8fbece32b25be8a232cd1c99a35
@huangminghuang
huangminghuang requested a review from heifner August 21, 2026 21:56
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