feat(wseg-build): full AtomicAssets shape — v2 writer (Cycle B)#10
Conversation
…(Cycle B) Carry the fields the full eosio-contract-api response needs. ASSET_VERSION 1->2 (lockstep with the wormdb-domain-atomicassets reader; forces a segment rebuild). - aa_binfmt.rs: encode_template +transferable/burnable/max_supply(u32)/issued_supply(u32) (TemplateRec); new encode_collection/decode_collection (TABLE_AA_COLL_FWD): version| collection|author|allow_notify|auth_count|authorized|notify_count|notify|market_fee(f64, the contract double)|data attrs; cross-repo byte goldens golden_template_v2 / golden_collection_v2. - aa_builder.rs: push_collection (Mongo atomicassets-collections -> COLL_FWD), keying collection data attrs by the config collection_format; push_template reads the 4 new fields; finish() emits TABLE_AA_COLL_FWD; warns if an account list exceeds the 255 cap. - bin/aa_build.rs: streams collections after config (collection_format index dependency); adds transferable/burnable/max_supply/issued_supply to the template projection. - aa_live.rs: compaction reuses decode_template's struct (+the new fields). created_at_* deliberately NOT written: the snapshot's block_num is the snapshot block (uniform across rows), not per-entity creation — those are history-tier (Phase 3). Tests: 31 pass (incl. template/collection round-trips + cross-repo byte goldens); clippy + fmt clean. Built the Jungle 4 segment: 31 collections, 188 v2 templates. Pairs with wormdb-domain-atomicassets (the v2 reader) — review/merge together.
There was a problem hiding this comment.
Code Review
This pull request upgrades the asset binary format to version 2, adding new fields (transferable, burnable, max_supply, issued_supply) to template forward records and introducing support for collection forward records (TABLE_AA_COLL_FWD). The reviewer feedback highlights minor issues in vector capacity calculations for both encode_template and encode_collection which could lead to immediate reallocations due to missing attribute count prefixes. Additionally, it is recommended to safely cast indices to u8 when building coll_format_idx to prevent silent overflow if the collection format contains more than 256 fields.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| issued_supply: u32, | ||
| immutable: &[Attr], | ||
| ) -> Vec<u8> { | ||
| let mut o = Vec::with_capacity(23 + immutable.len() * 12); |
There was a problem hiding this comment.
The capacity calculation 23 + immutable.len() * 12 under-allocates the vector. The fixed fields take 23 bytes (1 for version, 4 for template_id, 8 for schema, 1 for transferable, 1 for burnable, 4 for max_supply, and 4 for issued_supply). However, put_attrs also writes a 2-byte attribute count prefix (attrs.len() as u16). Therefore, the fixed overhead is 25 bytes, and the capacity should be initialized with 25 + immutable.len() * 12 to avoid an immediate reallocation when writing attributes.
| let mut o = Vec::with_capacity(23 + immutable.len() * 12); | |
| let mut o = Vec::with_capacity(25 + immutable.len() * 12); |
| ) -> Vec<u8> { | ||
| let nauth = authorized.len().min(255); | ||
| let nnotify = notify.len().min(255); | ||
| let mut o = Vec::with_capacity(27 + (nauth + nnotify) * 8 + data.len() * 12); |
There was a problem hiding this comment.
The capacity calculation 27 + (nauth + nnotify) * 8 + data.len() * 12 under-allocates the vector. The fixed fields take 28 bytes (1 for version, 8 for collection, 8 for author, 1 for allow_notify, 1 for nauth, 1 for nnotify, and 8 for market_fee). Additionally, put_attrs writes a 2-byte attribute count prefix. Therefore, the fixed overhead is 30 bytes, and the capacity should be initialized with 30 + (nauth + nnotify) * 8 + data.len() * 12 to avoid an immediate reallocation.
| let mut o = Vec::with_capacity(27 + (nauth + nnotify) * 8 + data.len() * 12); | |
| let mut o = Vec::with_capacity(30 + (nauth + nnotify) * 8 + data.len() * 12); |
| self.coll_format_idx = fmt | ||
| .iter() | ||
| .enumerate() | ||
| .map(|(i, (n, _))| (n.clone(), i as u8)) | ||
| .collect(); |
There was a problem hiding this comment.
If fmt contains more than 256 fields, casting i as u8 will silently overflow and wrap around, leading to duplicate or incorrect indices in coll_format_idx. To prevent silent corruption, use u8::try_from(i).expect(...) to safely check for overflow.
self.coll_format_idx = fmt
.iter()
.enumerate()
.map(|(i, (n, _))| {
let idx = u8::try_from(i).expect("collection_format has too many fields (max 256)");
(n.clone(), idx)
})
.collect();There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 082a863595
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self.base.for_each_entry(TABLE_AA_SCHEMAS, |key, blob| { | ||
| b.push_schema_raw(key, &decode_schema_format(blob)); | ||
| }); | ||
| self.base.for_each_entry(TABLE_AA_TMPL_FWD, |_k, blob| { |
There was a problem hiding this comment.
Preserve collection forward records during compaction
With the new builder path now populating TABLE_AA_COLL_FWD, this compaction carry-over only re-emits schemas/templates before folding assets and calling finish(). In a live server that compacts a v2 segment, the rebuilt segment will therefore have an empty collection forward table (stats.collections == 0), so collection metadata such as author, notify accounts, market fee, and collection data disappears after the first compaction even though the source base contained it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
Adds the AtomicAssets v2 writer shape to wseg-build, expanding what gets emitted into the .wseg segment so it matches the full API surface (templates gain v2 fields; collections gain a new forward table), and bumps the on-blob version byte to ASSET_VERSION = 2 for cross-repo byte-locking with the reader.
Changes:
- Bumps
ASSET_VERSIONto2and extends the template FWD blob layout to includetransferable,burnable,max_supply, andissued_supply. - Introduces collection FWD encoding/decoding (
encode_collection/decode_collection) and streamsatomicassets-collectionsinto the segment build. - Updates builder + live compaction paths to use the new template shape (but compaction currently does not carry over collections).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
crates/wseg-build/src/bin/aa_build.rs |
Streams collections before schemas/templates and reports collections in output stats. |
crates/wseg-build/src/aa_live.rs |
Updates compaction to re-emit v2 template records (but currently drops the new collection FWD table). |
crates/wseg-build/src/aa_builder.rs |
Builder support for collection FWD emission + v2 template fields; adds helpers for bool/f64/name arrays. |
crates/wseg-build/src/aa_binfmt.rs |
Bumps version byte to v2; updates template blob layout and adds collection blob codec + byte-golden tests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let mut b = AtomicBuilder::new(self.facet_fields.clone()); | ||
| // schemas + templates carry straight over from the base (overlay add/extend would also fold here) | ||
| self.base.for_each_entry(TABLE_AA_SCHEMAS, |key, blob| { | ||
| b.push_schema_raw(key, &decode_schema_format(blob)); | ||
| }); | ||
| self.base.for_each_entry(TABLE_AA_TMPL_FWD, |_k, blob| { |
| pub fn decode_template(b: &[u8]) -> TemplateRec { | ||
| let mut p = 1usize; // skip version | ||
| let template_id = gi32(b, &mut p); | ||
| let schema = gu64(b, &mut p); | ||
| let transferable = b[p]; |
| pub fn decode_collection(b: &[u8]) -> CollectionRec { | ||
| let mut p = 1usize; // skip version | ||
| let collection = gu64(b, &mut p); | ||
| let author = gu64(b, &mut p); |
- encode_template / encode_collection: correct the Vec::with_capacity hints to include the attr-count prefix (25 = 23 header + 2; 30 = 28 fixed + 2) — a hint, so no behavior change, just avoids a reallocation. - push_config: guard the collection_format index against a silent `as u8` wrap (u8::try_from + filter_map) — field_idx is a u8 so fields past 255 are unrepresentable and skipped (never happens for a real collection_format). Bytes unchanged (goldens still pass); 31 tests + clippy + fmt clean.
|
Addressed (commit
31 tests + clippy + fmt clean. |
- aa_live compaction: carry TABLE_AA_COLL_FWD over into the rebuilt segment (new push_collection_raw) — Cycle B added collections to the builder, so without this, compaction silently dropped every collection forward record. - decode_template / decode_collection: debug_assert the version byte so a v1/corrupt blob fails with a clear "version byte mismatch" instead of a confusing bounds panic. - coll_format_idx already guards the `as u8` wrap with u8::try_from (prior commit). 31 tests + clippy + fmt clean.
|
Second pass addressed (commit
31 tests + clippy + fmt clean. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 537053ca3c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| debug_assert_eq!( | ||
| b[0], ASSET_VERSION, | ||
| "decode_template: version byte mismatch (rebuild the segment)" | ||
| ); |
There was a problem hiding this comment.
Enforce template version checks in release builds
When a release build compacts or otherwise carries over a pre-v2 segment, this debug_assert_eq! is compiled out, so the v2 decoder proceeds to interpret the old v1 template layout as transferable/burnable/supply fields before the attr list. In that upgrade scenario the code can panic later on bounds checks or, for unlucky bytes, re-emit corrupted template metadata instead of failing closed as the version bump comment requires; use a runtime check/Result or explicit panic before decoding.
Useful? React with 👍 / 👎.
#10 review) decode_template / decode_collection used debug_assert_eq! for the version byte, which is compiled out in release — so a release compaction over a pre-v2 base would mis-parse it. Promote to assert_eq! so a version mismatch fails loud (these decode ~hundreds of templates/collections, not the per-asset hot path).
|
Addressed (commit |
Cycle B — the writer half. Pairs with wormdb-domain-atomicassets#3 (the v2 reader); they're byte-locked via
ASSET_VERSION 1→2and should be reviewed/merged together (a v2 reader can't read a v1 segment and vice-versa; the deploy rebuilds the segment from Mongo).What it writes
encode_template+transferable/burnable/max_supply(u32)/issued_supply(u32) →TemplateRec.encode_collection/TABLE_AA_COLL_FWD:version|collection|author|allow_notify|auth_count|authorized|notify_count|notify|market_fee(f64)|data attrs.market_feeis the contractdouble(f64, no precision loss).aa_builder:push_collection(Mongoatomicassets-collections→ COLL_FWD, data keyed by configcollection_format);push_templatereads the 4 new fields; streams collections after config.golden_template_v2/golden_collection_v2(mirrored on the reader side).Design calls (data-verified)
created_at_*is NOT written — the snapshot'sblock_numis the snapshot block (uniform across all rows, confirmed via Mongo), not per-entity creation. Those are history-tier (Phase 3).market_fee= f64 (the contract'sdouble), not f32 — avoids parity-diff precision loss.Verification
clippy --release --all-targets -D warnings+fmtclean.