Skip to content

docs: add Antelope-to-Wire contract migration guide - #111

Open
heifner wants to merge 12 commits into
masterfrom
docs/migrating-from-antelope
Open

docs: add Antelope-to-Wire contract migration guide#111
heifner wants to merge 12 commits into
masterfrom
docs/migrating-from-antelope

Conversation

@heifner

@heifner heifner commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds docs/migrating-from-antelope.md — a guide for developers arriving with a working contract from EOS, Telos, WAX, Jungle or another Antelope-family chain. Documentation only; no code changes.

#112 and #113 are merged, and this branch is rebased onto them. The guide describes the current toolchain and keeps the pre-merge behaviour as a caveat for anyone on an older CDT. It now also depends on #115, which fixes the kv::global + _i defect the review turned up — see below.

It is organised as the port actually goes: install and build something first, then the mechanical renames, then storage, then the resource model, then the host-function diff.

What it covers

  • Getting started on Wire. Toolchain install (base and -dev packages, plus CMake, a build tool and jq, which the guide's own commands need), the eosio-*cdt-* / cleosclio mapping, cdt-init, add_contract, native testing, getting an account, sysio.roa::addpolicy, and deploy. The tarball's bin/ holds the unprefixed clang/lld/llvm-*, so putting it on PATH shadows the distro toolchain — that is called out, along with the cdt-cpp name collision with AntelopeIO CDT 3.0+.
  • The rename surface. Full eosiosysio table, a sed first pass that explicitly covers the eosio_assert / eosio_exit C API a \beosio\b pattern silently skips, and a caution about string literals. The ABI version becomes sysio::abi/1.2. table_id and secondary_indexes are the Wire additions to table_def; index_type, key_names and key_types are original EOSIO fields — what changed is that Wire populates them to describe the key layout.
  • Storage. db_*_i64 and db_idx* are gone from CDT and the chain. sysio::multi_index is a compatibility shim — table declarations carry over, but the postfix it++/it-- operators are deleted and need a mechanical sweep. Secondary keys must be std::is_trivially_copyable, not merely serializable. get_table_rows now returns {key, value}. Use _i only above 13 characters, and note that _n is not a universal fallback — its alphabet is .12345a-z, so a name containing _ is a compile error. Reviewing that rule turned up a toolchain defect, now fixed in fix(abigen): keep the annotated table name for a _i-named table #115 rather than documented: abigen named a table by decoding its raw template parameter, which is a hash for _i, so kv::global, multi_index and kv_multi_index emitted a garbage twin beside the annotated name — two mismatched entries under 13 characters, a table_id collision at or above it. kv::table was never affected, because its code path already preferred the annotation. With fix(abigen): keep the annotated table name for a _i-named table #115 merged, _i works at any length on every table kind, and kv-global.md carries a caveat for older toolchains instead of a prohibition.
  • Resources. Scoped to what changes in contract code: the emplace(user, …)emplace(get_self(), …) decision, why sysio.payer at index 0 welds RAM billing to CPU/NET (and the advanced @sysio.code delegation that separates them), and that CPU is billed per top-level action across the whole call tree while NET is fixed by what reaches the wire.
  • Host functions. Added (22 × kv_*, get_ram_usage, get_permission_lower_bound, blake2b_256), removed (db_*_i64, the 50 db_idx*, send_deferred/cancel_deferred, get_permission_last_used/get_account_creation_time), and the unchanged surface.
  • A porting checklist and a reading list across both repos.

Verification

Every claim was checked against the code rather than assumed, and doing so corrected several of my own errors — including two rounds of review findings that were all accurate.

Notable things the checks changed:

  • get_block_num is standard Antelope, so it is not a Wire addition; blake2b_256 is not in Antelope's crypto_ext.h, so it is one.
  • The addpolicy example had a C++/ABI camelCase-vs-snake_case note that described a divergence that does not exist — I had read wire-system-contracts' copy of sysio.roa.hpp, abandoned on a 2025 branch, rather than the authoritative wire-sysio/contracts copy.
  • The _i short-name table_id mismatch is real and reproduced independently: user_table hashes to 61956 at runtime and 3509 in the ABI, because abigen routes annotated names of ≤13 characters through string_to_name while _i always hashes.
  • The NET overhead split is overhead / actions + 1 — integer division then an unconditional +1, which over-bills by up to a byte where the division is exact.

Related

Corrections worth calling out

Two of the guide's claims were confidently wrong and are fixed here, because both would have misled the reader in the direction that costs money:

  • RAM. Two revisions of this guide got the direction wrong, in opposite directions. It depends on rows-per-scope: the table_id_object is billed per (code, scope, table), so a table with one row per scope — a token balance, a per-user settings row — sheds a whole 108-byte object and comes out 38–41% cheaper, while a dense table with few scopes pays 10–16% more. Across EOS mainnet the net is a 2–6% saving. The guide now gives all four rows from wire-sysio's kv-ram-billing.md and says to size from your own ratio rather than assuming either way.
  • clio get table ... -S <gen>. Scoped-table scopes are declared as name, and the chain tries name(scope) before falling back to an integer. 1-5 are valid name characters, so -S 1 is read as the name "1" (576460752303423488) and the query returns no rows — which reads as "your issuer is not a node owner". The command now omits -S.

Not in this PR

The .gitignore hunk was dropped — it was a strict subset of the identical one on #112 and #113 and conflicted with both. It landed on #113, and this branch now takes master's version.

@huangminghuang huangminghuang 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 the complete diff at 54b55e7. CI is green, but I found several correctness issues in the migration path, including an unrunnable provisioning command and source/API compatibility claims that do not hold. Inline comments contain reproductions and suggested corrections, so I am not approving this revision. As the PR body notes, the cross-repo ROA link also requires Wire-Network/wire-sysio#583 to merge first.

Smaller accuracy corrections: docs/migrating-from-antelope.md:71 has -wasm2wast where the Antelope command is eosio-wasm2wast; line 85 says -bare emits only the header/source skeleton although it also emits the contracts Markdown and README files; line 428 groups sha3 under the assert_ forms although no assert_sha3 is exposed; and the README summary at line 42 should say the contract is billed by default so it does not erase the explicit-payer exception.

Comment thread docs/migrating-from-antelope.md Outdated

> **If AntelopeIO CDT 3.0+ is already installed**, both packages publish a `cdt-cpp` (and `cdt-cc`,
> `cdt-ld`, `cdt-init`) on `PATH`. Install Wire CDT from the portable tarball to `/opt/wire-cdt` and
> put `/opt/wire-cdt/bin` ahead on `PATH` for Wire work, or check `which cdt-cpp` before every build.

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.

The tarball setup contradicts the isolation guarantee above. InstallCDT.cmake installs unprefixed clang, clang++, lld, ld.lld, opt, llc, wasm-ld, and the LLVM tools into this same bin, so putting /opt/wire-cdt/bin first shadows the distro tools. Please scope lines 41–43 to deb/rpm and recommend an absolute cdt-* path, alias, or public-entry-only symlink directory for a coexisting tarball.

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.

Confirmed and fixed in a951e0a. InstallCDT.cmake:59 installs unprefixed clang, clang++, lld, ld.lld, opt, llc, wasm-ld and the llvm-* set into the same bin/, so my advice was actively harmful for the tarball.

The isolation guarantee is now scoped to deb/rpm, where only public entry points are symlinked into /usr/bin. The tarball gets its own callout saying plainly that its bin/ holds everything and that putting it on PATH shadows the distro toolchain, with the three safe options: absolute path, alias, or a private symlink dir holding just the cdt-* names. The AntelopeIO name collision moved into that callout too, since the fix is the same.

Comment thread docs/migrating-from-antelope.md Outdated
> put `/opt/wire-cdt/bin` ahead on `PATH` for Wire work, or check `which cdt-cpp` before every build.

```bash
sudo apt install ./wire-cdt_<version>_amd64.deb # deb / rpm → /usr/lib/cdt

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.

This installs only the base component. The native-test workflow linked at line 128 needs libnative*, gen_native_dispatch.py, and share/cdt/native-contract-src, all packaged in wire-cdt-dev; CMake and a build tool are not base-package dependencies either. On a clean machine the later build/native-test steps are therefore unavailable. Please install/document wire-cdt-dev, CMake, and the build tool, or state that those steps assume them.

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.

Confirmed and fixed in a951e0a. libnative*, scripts/gen_native_dispatch.py and share/cdt/native-contract-src are all COMPONENT dev, and neither package depends on CMake or a build tool — so a reader following the page top to bottom would hit "Test without a chain" with nothing to run it.

The install step now takes both packages and adds cmake ninja-build, with a sentence saying which package carries what and why the dev one is needed for the native path further down.

Comment thread docs/migrating-from-antelope.md Outdated

```bash
clio push action sysio.roa addpolicy \
'{"owner":"mycontract","issuer":"<nodeowner>","netWeight":"0.1000 SYS", \

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.

This mandatory command cannot be parsed as written for two independent reasons. The line-ending backslash is inside the still-open single-quoted JSON string, so the shell passes a literal backslash/newline; and the ABI fields are snake_case (net_weight, cpu_weight, ram_weight, time_block, network_gen), not camelCase. Current clio convert pack_action_data fails with Missing field 'net_weight'. Please keep the JSON on one quoted line (or use a safe file/input form) and use the ABI field names.

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.

Confirmed and fixed in a951e0a — and the camelCase was my own regression. The original draft had snake_case; I "corrected" it against sysio.roa.hpp, whose parameters are netWeight etc., without checking that contracts/sysio.roa/sysio.roa.abi declares net_weight / cpu_weight / ram_weight / time_block / network_gen. Good catch.

The command is now a single line with the ABI names, plus a note on both traps: the ABI spelling is not the C++ parameter spelling, and a wrapping \ lands inside the single quotes rather than continuing the line.

Comment thread docs/migrating-from-antelope.md Outdated
-p <nodeowner>@active
```

Without it, every call fails with `account mycontract net usage is too high: 132 > 0` — which looks

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.

Please scope this to ordinary/default contract-paid calls. A provisioned caller or relayer can opt in as the explicit CPU/NET payer using an index-0 sysio.payer authorization plus the matching real authorization, so it is not true that nobody can call the contract or that every call fails when the contract itself has zero CPU/NET.

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.

Agreed, fixed in a951e0a. It also contradicted the ROA page I link to, which says an unprovisioned contract is "inert for ordinary users, not universally inert".

Now scoped: an ordinary call fails, and because billing keys on the payer alone, a caller naming itself with sysio.payer pays for the action and never consults the contract's zero limits — so a provisioned caller or relayer can still drive it.

Comment thread docs/migrating-from-antelope.md Outdated

```bash
grep -rl 'eosio\|EOSIO\|EOSLIB' src include \
| xargs sed -i -e 's/\beosio\b/sysio/g' -e 's/\bEOSIO_/SYSIO_/g' -e 's/\bEOSLIB_/SYSLIB_/g'

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.

The word boundary prevents exactly the C API renames listed above: _ is a word character, so \beosio\b does not match the eosio prefix in eosio_assert or eosio_exit. Those identifiers remain unchanged. Please add explicit substitutions (or a token-aware rename) so the advertised first pass covers them.

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.

Confirmed and fixed in a951e0a. _ is a word character, so \beosio\b has no boundary to match in eosio_assert — the recipe skipped precisely the C API entries listed in the table right above it.

Verified both directions on a fixture containing eosio_assert, eosio_assert_message, eosio_exit, eosio::name, EOSIO_DISPATCH and EOSLIB_SERIALIZE: the old recipe leaves the three C API lines untouched, the new one converts all six. The added expression names them explicitly, and the text says why it is not redundant.

Comment thread docs/migrating-from-antelope.md Outdated

### `multi_index` still works

`sysio::multi_index` is a drop-in replacement implemented over the KV intrinsics. It keeps

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.

multi_index is not fully source-compatible: Wire explicitly deletes postfix operator++(int) and operator--(int) on both primary and secondary iterators, while Antelope provides them. Common loops such as for (...; it != table.end(); it++) fail to compile (this repo's existing examples use that form). Please document the required it++++it / it----it migration or restore those operators, and soften “drop-in” / “usually does not change at all.”

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.

Confirmed and fixed in a951e0a. operator++(int) and operator--(int) are = delete at kv_multi_index.hpp:422-423 (primary) and 807-808 (secondary).

"Drop-in" is now "near-drop-in", the TL;DR row says to expect a mechanical it++++it sweep, and there is a short subsection showing the failing loop, the rewrite, and why a postfix increment cannot work when the iterator owns a host-side handle. Added to the porting checklist as its own step.

Worth flagging separately: examples/multi_index_example and examples/multi_index_large use itr++ in five places, so those examples cannot compile as written — they are evidently not built by CI. I have left them alone as out of scope for a docs PR, but they look like a real breakage.

Comment thread docs/migrating-from-antelope.md Outdated
preserved.

Secondary key types carried over: `uint64_t`, `uint128_t`, `double`, `long double`, and
`checksum256` (and anything else with a CDT serializer, via the generic encoder). Iteration order

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.

The generic encoder does not make every serializable type usable here. secondary_index_view has a std::is_trivially_copyable static assertion, so serializer-supported types such as std::string and std::vector are rejected. Please state the actual constraint rather than “anything else with a CDT serializer.”

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.

Confirmed and fixed in a951e0a. The static_assert(std::is_trivially_copyable<secondary_key_type>::value, ...) at kv_multi_index.hpp:769 is the real constraint, and "anything else with a CDT serializer" claimed the opposite of what it enforces.

The text now states the actual rule and notes that std::string / std::vector keys are rejected at compile time despite being serializable, with the practical workaround — hash to a checksum256, or truncate to uint64_t and disambiguate against the primary row. Also added as a checklist step.

kv::table<"user_balance_history"_i, my_key, my_val> users(get_self());
```

Annotate the value struct with `[[sysio::table("user_balance_history")]]` so the ABI carries the

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.

This recipe is unsafe for annotated _i table names of 13 characters or fewer. Runtime _i always DJB2-hashes the literal, but abigen::add_table sends short annotated names through legacy string_to_name; the runtime and ABI table_id then differ (for example, user_table is 61956 at runtime versus 3509 in the ABI). RPC metadata can point at the wrong table. Please fix ABI generation first or document/restrict the safe naming case.

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.

Confirmed and fixed in a951e0a — and I reproduced your figures independently before changing anything. abigen.hpp:297-306 branches on t.name.size() <= 13, taking string_to_name for short names, while _i in hash_id.hpp DJB2-hashes unconditionally:

name runtime (_i) ABI
user_table (10) 61956 3509
accounts (8) 64120 25660
user_balance_history (20) 26461 26461

So it is every short annotated name, not an edge case.

I documented the restriction rather than attempting the fix here, since changing add_table moves the table_id of any contract already using that path and is a breaking ABI change that wants its own PR and a pinned toolchain test. The guide now says to use _i only above 13 characters — where both sides hash and agree, which is the case _i exists for — and _n below, where both use string_to_name and likewise agree. The table above is in the page so the failure mode is recognisable.

Happy to open the codegen fix as a follow-up if you would rather have it than the restriction.

works at all.

**On Wire, the account billed for an action's CPU and NET is the contract that action invokes, not
the account that signed it.** An ordinary transaction names no payer, so the signer is neither

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.

Please qualify this as objective billing. Subjective billing is enabled by default for API/P2P traffic, and the pre-execution check applies the subjective failure/CPU budget to non-payer first authorizers. Thus the signer has no objective CPU/NET charge, but can still be limit-checked and throttled after failed attempts.

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.

Confirmed and fixed in a951e0a. verify_init_subjective_billing takes set_difference(first_authorizers, payers) and, for each authorizer that is not a payer, throws tx_cpu_usage_exceeded / greylist_cpu_usage_exceeded when the subjective budget is gone — so the signer is very much limit-checked, just not by consensus.

The claim is now scoped to objective billing, with a following paragraph on subjective billing metering the first authorizer, that it is node-local rather than consensus, and that a signer failing transactions in a loop can be throttled.

Comment thread docs/migrating-from-antelope.md Outdated

CPU is metered per **top-level** action — timed across that action's entire execution, including
every inline action and notification handler it triggers — and billed to that top-level action's
payer. NET is likewise charged per top-level action, on the transaction's serialized bytes; inline

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.

NET is not based on the whole transaction's serialized bytes. packed_transaction::get_action_billable_size() charges a fixed per-action overhead plus only that top-level action's serialized billable size (and its matching context-free data when applicable); it excludes headers, signatures, and other actions. Please describe that per-action formula, since it changes the provisioning/sizing guidance.

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.

Half agreed — the sentence was wrong and is fixed in a951e0a, but I do not think the replacement formula is right, so flagging rather than adopting it silently.

get_action_billable_size is indeed per-action, and my "on the transaction's serialized bytes" was wrong. But the overhead it adds is not a fixed constant excluding headers and signatures — it is billable_net_per_action_overhead, computed in packed_transaction::init:

int64_t size = config::fixed_net_overhead_of_packed_trx;      // 16
size += fc::raw::pack_size(signatures);
size += fc::raw::pack_size(unpacked_trx.transaction_extensions);
size += fc::raw::pack_size(static_cast<const transaction_header&>(unpacked_trx));
billable_net_per_action_overhead = (size / unpacked_trx.total_actions()) + 1;

Signatures, extensions and the header are all in there; they are apportioned across the actions rather than excluded. Only other actions' payloads are excluded, which is the part your comment gets right and my sentence got wrong.

That distinction is exactly what changes the sizing guidance, so the page now gives the full formula:

action NET = that action's own serialized billable size
           + (16 + signatures + extensions + header) / number of actions, rounded up
           + its matching context_free_data, for a context-free action

with the two consequences that follow: signatures cost NET, so co-signing is dearer; and batching amortizes the overhead. This also matches wire-sysio's docs/roa-overview.md, which the page links to. If I have misread init(), say so and I will correct it again.

heifner added a commit that referenced this pull request Aug 28, 2026
Eleven review findings on #111, each verified against the code before the text
changed. Several were factual errors, not imprecision.

Toolchain install. The tarball has no public-entry separation: InstallCDT.cmake
puts unprefixed clang, clang++, lld, ld.lld, wasm-ld, opt, llc and llvm-* in the
same bin/, so the previous advice to put /opt/wire-cdt/bin on PATH shadowed the
distro toolchain. That guarantee holds only for deb/rpm, where /usr/bin gets
symlinks for public entry points alone; the tarball now recommends an absolute
path, an alias, or a private symlink dir. `apt install` also installed only the
base component, while the native-testing path linked further down needs
libnative*, gen_native_dispatch.py and share/cdt/native-contract-src -- all
COMPONENT dev -- and neither package pulls in CMake or a build tool.

The addpolicy command could not run. Its line-continuation backslashes sat inside
the still-open single-quoted JSON, and the fields were camelCase from the C++
parameters where sysio.roa.abi declares net_weight / cpu_weight / ram_weight /
time_block / network_gen. One line, ABI names.

multi_index is not the drop-in the guide claimed. Wire deletes operator++(int)
and operator--(int) on both the primary and secondary iterators, so the classic
`it++` loop stops compiling -- this repo's own examples/ still use that form. The
required ++it sweep is documented and the "drop-in" framing softened. Secondary
key types are constrained by a std::is_trivially_copyable static_assert in
secondary_index_view, not by "has a serializer", so std::string and std::vector
keys are rejected; the guide said the opposite.

The `_i` recipe was unsafe for short names. Runtime `_i` always DJB2-hashes, but
abigen routes an annotated name of <= 13 characters through legacy
string_to_name, so the row is written under one table_id and described in the ABI
under another (user_table: 61956 at runtime, 3509 in the ABI -- reproduced
independently). Restricted to names over 13 characters, where both sides hash and
agree.

cdt-abidiff cannot show the change the guide pointed it at: it compares tables
only by name and type, ignoring index_type, key_names, key_types, table_id and
secondary-index metadata, and its version check reduces eosio::abi/1.2 and
sysio::abi/1.2 to the same number. Replaced with a normalized jq diff.

The rename recipe skipped what it advertised. `_` is a word character, so
\beosio\b finds no boundary in eosio_assert and left the whole C API unrenamed --
the exact identifiers the table above it lists. Those are now named explicitly.

Billing claims scoped. "The signer is neither charged nor limit-checked" is true
only of objective billing: verify_init_subjective_billing applies a subjective
CPU budget to first authorizers who are not payers and can terminate the
transaction. NET is not the transaction's serialized bytes either -- each action
is billed its own billable size plus an equal share of a 16-byte constant, the
signatures, the extensions and the header, so signatures cost NET and batching
amortizes overhead.

The porting checklist gains the iterator sweep, the secondary-key constraint, the
C API rename and the -dev package, and no longer recommends cdt-abidiff.

@huangminghuang huangminghuang 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 the complete PR and the follow-up delta at a951e0a. The follow-up resolves the original 11 inline findings and all current-head CI checks are green, but I found additional compatibility and resource-model issues, including a state-integrity break in the current multi_index::emplace shim. Inline comments have the implementation evidence and requested wording, so I am not approving this revision.

The PR description also needs the required full-diff refresh: it still calls multi_index a drop-in shim requiring no source changes, contrary to the follow-up commit and the current guide. Smaller corrections remain at docs/migrating-from-antelope.md:89 (eosio-wasm2wast, not -wasm2wast), line 103 (cdt-init -bare also emits the contracts Markdown and README), and line 167 (the C++ addpolicy parameters are snake_case too, not camelCase). Keep the documented merge ordering behind Wire-Network/wire-sysio#583; that PR is still open and the master-branch ROA link still does not resolve.

Correction to my prior review body: sysio::assert_sha3 does exist as a library wrapper in crypto_ext.hpp, so that earlier minor note was wrong and is withdrawn.

Comment thread docs/migrating-from-antelope.md Outdated
below needs. Neither package pulls in CMake or a build tool, so on a clean machine also:

```bash
sudo apt install cmake ninja-build # or build-essential for make

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.

These clean-machine prerequisites are not sufficient for the commands the guide gives. This installs Ninja, but line 99 invokes the default generator and make; line 253 newly requires jq, which is not installed here. Please either install build-essential jq, or select Ninja consistently (-G Ninja / cmake --build) and add jq.

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.

Fixed in 2653497. The prereq line installed Ninja while the guide then ran the default generator and make, and never installed jq at all. Now sudo apt install cmake build-essential jq, with a sentence saying which command each is for — keeping make, since that is what the cdt-init project generates.

Comment thread docs/migrating-from-antelope.md Outdated

**A new account holds zero CPU and zero NET.** For a user account that does not matter — see
[Step 3](#step-3--resources-the-contract-pays). For the account that will *hold your contract* it is
fatal, because on Wire the contract is the payer. Before anyone can call it, a node owner must

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.

The later paragraph now documents the explicit-payer exception correctly, but this sentence (and the introduction at line 5) still says nobody can call an unprovisioned contract. Please say before an ordinary/default contract-paid call. Also, the explicit payer bypasses only the contract's zero CPU/NET; any new RAM the contract bills to itself still needs RAM headroom.

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.

Fixed in 2653497, at both places — the introduction now says the contract is what has to be provisioned "before ordinary calls will go through", and the deploy section says "ordinary contract-paid call".

The RAM point is a good catch and I have added it explicitly: the sysio.payer route bypasses the contract's zero CPU/NET only, so RAM the contract bills to itself still comes out of its own quota — an unprovisioned contract can be driven no further than its first write to its own tables.

Comment thread docs/migrating-from-antelope.md Outdated
issue it a policy:

```bash
clio push action sysio.roa addpolicy '{"owner":"mycontract","issuer":"<nodeowner>","net_weight":"0.1000 SYS","cpu_weight":"0.1000 SYS","ram_weight":"1.0000 SYS","time_block":0,"network_gen":0}' -p <nodeowner>@active

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.

Hard-coding network_gen:0 is not valid for a generic shared network. addpolicy opens nodeowners in the supplied generation and requires the issuer there; after a generation rollover, a valid issuer registered elsewhere fails or this draws from an old generation's allocation. Use a placeholder and explain how to query/select the issuer's intended generation.

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.

Fixed in 2653497. addpolicy opens nodeowners scoped to the generation passed and requires the issuer there, so a literal 0 is wrong on any network that has rolled over.

It is now <gen>, with a paragraph explaining it is the generation the issuer is registered in and two commands to determine it — read network_gen from the roastate singleton, then confirm the issuer appears in that generation's nodeowners.

Comment thread docs/migrating-from-antelope.md Outdated

### `multi_index` still works

`sysio::multi_index` is a near-drop-in replacement implemented over the KV intrinsics. It keeps

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.

near-drop-in is still unsafe as written. Wire emplace calls kv_set without kv_contains; when the primary key already exists, the chain takes its update branch, whereas legacy db_store_i64/emplace rejects the duplicate. If the secondary key changed, store_secondaries then adds the new mapping without removing the old one, leaving stale index state. Separately, primary lower_bound/upper_bound accept only uint64_t, so code using a name primary key no longer compiles while upstream accepts its templated PK. Please fix the shim or explicitly document both safeguards/conversions, and remove the one source change framing.

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.

Both confirmed, and I fixed the shim rather than documenting around it — #113.

The orphan reproduced exactly as you described, and I demonstrated it against the real runtime before changing anything: emplace pk=1 sec="aaa", emplace pk=1 sec="bbb", then look up "aaa".

before:  ORPHAN: stale secondary A still resolves after duplicate emplace
after:   object with the same primary key already exists

emplace now checks kv_contains first, the way kv::table::emplace already did — the compat shim was the one missing what the perf-first wrapper pays for. lower_bound/upper_bound are templated on the primary key type like upstream's to_raw_key; making that work surfaced three further sites passing primary_key() straight into pk_to_bytes, which now use the same to_pk_uint64 conversion.

Here in 2653497: the "one source change" framing is gone, multi_index is a compatibility shim rather than near-drop-in, and the section states that duplicate-emplace and the bounds signatures match upstream. This PR now depends on #113.

Comment thread docs/migrating-from-antelope.md Outdated
> | `user_balance_history` (20 chars) | 26461 | 26461 ✓ |
>
> Above 13 characters both sides hash, so they agree — which is the case `_i` exists for. Short
> names should use `_n`, where runtime and ABI both use `string_to_name` and likewise agree.

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.

The guide's own user_table example cannot follow this advice: _n rejects _ (its alphabet is .12345a-z, with an additional 13th-character restriction), so it is a compile-time error. Only short identifiers that are valid Antelope names can switch to _n; other short _i identifiers must be renamed/lengthened until abigen is fixed.

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.

Confirmed and fixed in 2653497 — the fallback I offered was impossible for the very example above it. basic_name's alphabet is .12345a-z and symbol() calls sysio::check on anything outside it, which in a constexpr _n literal is a compile error, so "user_table"_n never builds.

The callout now says _n is the fix only for short names that are already valid Antelope names, and that a short identifier containing _, a digit outside 1-5, or an uppercase letter has to be renamed or lengthened past 13 characters until abigen is fixed.

Comment thread docs/migrating-from-antelope.md Outdated

```
action NET = that action's own serialized billable size
+ (16 + signatures + extensions + header) / number of actions, rounded up

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.

This is presented as an exact formula, but consensus computes integer division followed unconditionally by +1: floor(overhead / action_count) + 1. That differs from mathematical ceiling whenever the overhead divides evenly (including every one-action transaction), understating each action by one byte. Please show the actual integer formula.

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.

Agreed that "rounded up" was wrong, and fixed in 2653497 — but I read the direction the other way, so flagging rather than adopting it silently.

billable_net_per_action_overhead = (size / unpacked_trx.total_actions()) + 1 is floor-then-+1. When the division is exact, floor(x/n) + 1 is one more than ceil(x/n), so each action is billed a byte over, not under. transaction.cpp:380 agrees:

// +1 rounds up so per-action shares sum to at least total overhead; may over-bill
// by up to total_actions() bytes per trx (negligible).

The guide now shows the arithmetic in two steps rather than calling it a ceiling, and says it over-bills by up to a byte per action where the division is exact. If I have the sign backwards I will correct it again — but the code comment reads the same way I do.

Comment thread docs/migrating-from-antelope.md Outdated
- A contract notifies your `on_notify` handler. The top-level payer pays your handler's CPU; your
own policy covers only the RAM your handler writes.

The CPU and NET a contract must be provisioned for is therefore the cost of everything its actions

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 CPU has the whole-call-tree property. As line 453 correctly says, inline actions and notifications add zero NET; NET consists of the input action's bytes, its apportioned transaction overhead, and matching context-free data. Please split this conclusion so it does not say a contract's NET provisioning covers everything the action causes.

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.

Confirmed and fixed in 2653497. The conclusion had generalised a CPU property to NET, contradicting the sentence four lines above it.

It now says explicitly that only CPU has to cover everything the actions cause, since the whole tree is timed against the top-level payer, while NET is fixed by what reaches the wire — the input action's bytes, its share of the overhead, and its context-free data — and no inline action or notification adds to it however deep the tree goes.

Comment thread docs/migrating-from-antelope.md Outdated

| Function(s) | What to do instead |
|---|---|
| `db_store_i64`, `db_update_i64`, `db_remove_i64`, `db_get_i64`, `db_next_i64`, `db_previous_i64`, `db_find_i64`, `db_lowerbound_i64`, `db_upperbound_i64`, `db_end_i64` | Use `multi_index` (unchanged source) or the `kv_*` intrinsics. |

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.

unchanged source contradicts the mandatory iterator rewrite above, the name-primary bounds mismatch, and the fact that direct db_* callers necessarily must be rewritten. Please point readers to the compatibility adjustments instead.

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.

Fixed in 2653497. The checklist entry now says direct db_*_i64 / db_idx* callers must be rewritten, and that multi_index users need only the iterator sweep in the preceding step. The "unchanged source" phrasing in the removed-intrinsics table is replaced with a pointer to the compatibility adjustments in Step 2.

Comment thread docs/kv-storage-guide.md Outdated

> Porting a contract from EOS/Telos/WAX or another Antelope chain? Start with
> [Migrating a Contract from an Antelope Chain to Wire](migrating-from-antelope.md) — `multi_index`
> is a drop-in shim there, so this section is the optional second step.

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.

This new cross-link still says drop-in, contradicting the migration guide's postfix caveat and the additional verified emplace/name-primary differences. Please call it a compatibility shim or near-drop-in and link the required adjustments rather than promising unchanged behavior.

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.

Fixed in 2653497. That cross-link now calls multi_index a compatibility shim rather than a drop-in, names the required it++++it sweep as the reason, and tells the reader to make those adjustments before treating the KV migration as an optional second step.

Comment thread README.md Outdated
[docs/migrating-from-antelope.md](./docs/migrating-from-antelope.md) walks through getting started on
Wire and porting an existing contract — the `eosio` → `sysio` renames, the KV storage layer that
replaces the legacy `db_*_i64` tables, the host functions Wire adds and removes, and the resource
model change that matters most: on Wire the **contract** is billed for CPU, NET and RAM, not the

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.

The current guide now documents sysio.payer, so this summary should say the contract is billed by default. RAM is also selected by the contract's payer argument rather than categorically never landing on the signer.

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.

Fixed in 2653497. The README summary now says the contract is billed for CPU and NET by default rather than the signer, and that RAM follows whichever account the contract names as payer instead of categorically never reaching the signer.

@heifner

heifner commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Also addressed the four items from the review body, in 2653497:

  • :89eosio-wasm2wast, not the -wasm2wast shorthand.
  • :103cdt-init -bare emits four files, not two. write_ricardian() runs unconditionally (cdt-init.cpp:206), so a bare project also gets <name>.contracts.md and README.txt.
  • :167 — you were right, and this one was my own regression. I had "corrected" the field names to camelCase against wire-system-contracts' copy of sysio.roa.hpp, which sits on a feature branch last touched 2025-09-18; the authoritative wire-sysio/contracts/sysio.roa/sysio.roa.hpp is snake_case on both the C++ and ABI sides. There is no divergence to describe, so the note is deleted rather than reworded.
  • Merge ordering — the ROA overview is now referenced through wire-sysio#583 rather than a master URL that 404s, and the guide says so in both places it appears.

PR description refreshed for the full diff, as required. It no longer calls multi_index a drop-in needing no source changes.

This PR now depends on #113, which fixes the two code defects from your :274 comment. The orphan reproduced exactly as you described — I demonstrated it against the real runtime before touching the shim, and the storage section here describes the post-#113 semantics rather than documenting a hazard we are removing.

Thanks for both rounds — the stale-repo one in particular caught a mistake I had introduced while fixing an earlier finding.

heifner added a commit that referenced this pull request Sep 2, 2026
Final review round found that nothing in the tree pins the one fact
receiving_account() depends on. Changing cdt-codegen.cpp:83 from
sysio_set_contract_name(r) to (c) leaves ctest 30/30 and the integration
suite 24/24 green: every in-tree action is self-sent, so r == c, and the
native test drives the global directly rather than through apply(). The
divergence appears only under notification, on chain -- and the one
downstream contract that would catch it, wire-sysio's ram_restrictions_test,
is not rebuilt by that repo's CI (SYSIO_BUILD_TEST_CONTRACTS: "OFF").

dispatch_receiver_tests.sh inspects the emitted dispatch text, which no other
test looks at: it asserts the call passes `r`, and that it precedes any action
dispatch. Registered under unit_tests so it runs in required CI. Verified to
gate: with the argument changed to `c` it fails and everything else still
passes.

Also from the same review:
- The header comment added in the previous commit cited
  docs/kv-multi-index.md for the divergences, but this PR touches no docs --
  those edits are on the #111 branch, and on THIS branch that file still
  calls the shim a drop-in replacement. The divergences are listed inline
  instead, and the comment now records that sysio::multi_index and
  sysio::singleton are both aliases of this template, so the new guards reach
  the singleton surface too.
- The foreign-code case's `rows.count(seeded) == 1` cannot fail: the mock
  installs no kv_erase and kv_set only assigns, so nothing can reduce the
  count. The value comparison beside it is what carries the property; the
  comment said otherwise and now says which is which.
@heifner

heifner commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed against merged master (532f3841b, which now carries both #112 and #113) and pushed 7ed8f269c. This branch was 57 commits behind, so it described both as forthcoming.

One claim had gone false

The Unchanged host functions list promised "every privileged.h setter". #112 removed set_kv_parameters_packed, which was one of them. Fixed, and the two things #112 deleted now have rows in Removed on Wire — with the diagnostic each actually produces, since they differ and the difference is what a porter sees:

#include <sysio/security_group.h>   → fatal error: 'sysio/security_group.h' file not found
set_kv_parameters_packed(...)       → error: use of undeclared identifier

The header is gone outright in the first case; in the second <sysio/privileged.h> is still there and set_privileged from it still compiles — I checked both.

Tense, where it changed the advice

The ABI section told readers not to reach for cdt-abidiff, on the strength of limitations #112 fixed. That advice is now backwards, so it is inverted: the section lists what the tool compares today — version as the full string, structs, types, actions, tables with the full metadata and secondary indices, ricardian_clauses, enums, protobuf_types, variants, action_results, error_messages — and keeps the old-toolchain caveat plus the jq fallback for anyone on an older CDT.

The legacy-database and multi_index sections, and the three matching blocks in docs/kv-multi-index.md, now describe the current toolchain with the pre-merge behaviour as the caveat rather than the other way round.

One gap in both docs

The #113 receiver guard reaches sysio::singleton and neither doc said so. get_or_create, set and remove all mutate through the kv_multi_index it holds, so a singleton handle on another account's code is read-only exactly like a table handle. Added to both. While there: docs/kv-multi-index.md said singleton "is backed by" kv_multi_index, which reads as inheritance — it holds one as a member.

Verified, not reworded

Rebuilt CDT at merged master and compiled the claims:

Claim Result
it++ is deleted rejected — overload resolution selected deleted operator '++', the exact text the guide quotes
hand-declared db_store_i64 is now a link error wasm-ld: undefined symbol: db_store_i64
the static_cast<...>(&table::lower_bound) escape hatch compiles
lower_bound takes name, uint64_t and {42} all compile
22 kv_* intrinsics, none in the allow-list 22 / 0 ✓
imports/cdt.imports.in after the trim 87 entries, 0 db_*
default ABI version 1.2, 1.3 with protobuf matches default_minor / protobuf_minor

The RAM tables, _i / hash_id and get_table_rows sections are untouched by either PR and I left them alone.

Still worth doing before merge: this branch is 57 commits behind master. It merges clean, but the diff is easier to trust rebased.

@huangminghuang huangminghuang 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 current head 7ed8f269. CI is green, but I found remaining accuracy and portability issues, so I cannot approve this revision yet. The inline comments below are all against the current head.

Please also refresh the PR description after the follow-up commits: #113 is now merged, key_names/key_types are upstream ABI fields rather than Wire additions, and the _i/example impact statements no longer match this revision.

Comment thread docs/kv-global.md Outdated

**ABI requirement:** When using `_i`, annotate the value struct with `[[sysio::table("app_configuration")]]` so CDT generates the ABI entry.

> **Prefer `_n` for a global.** A `_i`-named `kv::global` with a matching `[[sysio::table]]`

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.

[P2] Make the examples follow this warning. The page still says both literal forms work, shows the >13-character "app_configuration"_i form that this block says fails to link, and its complete example uses short "app_config"_i with a table annotation, which this block says emits mismatched ABI metadata and breaks readable get_table_rows. Please make the runnable examples use _n (and drop the annotation/hash include if no longer needed), reserving _i for a labeled limitation. Also qualify the name rule: position 13 only accepts .12345a-j, not the full alphabet.

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.

You were right that the examples contradicted the block — but chasing it turned up that the block itself was describing a toolchain bug, not a property of _i. Opened #115 to fix it rather than document it.

abigen's add_table() named each entry by decoding the raw template parameter, which is a DJB2 hash for _i, not a name encoding — so it emitted a garbage twin (idrzzw4ktxljf, 5p.tmiidfxofh) beside the annotated name. Under 13 characters that is the two mismatched entries this block describes; at or above it the two hash to one table_id and the link fails. add_kv_table() — the kv::table path — already preferred the [[sysio::table]] attribute, which is exactly why kv::table + long _i has always worked and is tested. multi_index and kv_multi_index were affected too.

So this page no longer tells readers _i is unsuitable. Changes in 64b40a9eb:

  • the block is scoped to a pre-fix(abigen): keep the annotated table name for a _i-named table #115 toolchain and says what it was, rather than presenting it as how _i behaves;
  • the alphabet rule is qualified as you asked — _n covers up to 13 characters of .12345a-z, with position 13 restricted to .12345a-j, since it is encoded in 4 bits rather than 5. Confirmed in name.hpp: value & 0x0Full and >> (i == 12 ? 60 : 59);
  • the examples use _n where the name fits one, and the hash_id.hpp include is dropped from the complete example, which no longer needs it.

One more thing fell out while making that example runnable. It emitted no ABI table entry at all — and neither does the shipped examples/kv_global_example. A namespace-scope struct carrying only [[sysio::table]] is not reachable from the contract, so abigen skips it; it needs to be declared inside the contract class, or to carry sysio::contract("...") beside the table attribute. The example is now in-class and emits ('appconfig', 46121), and the "ABI requirement" note states the reachability rule instead of implying the annotation alone is enough. examples/kv_global_example has the same defect and is not fixed here — it is out of scope for a docs PR, and I would rather raise it separately than smuggle it in.

Comment thread docs/kv-multi-index.md Outdated
divergences are:

- the postfix iterator operators `it++` / `it--` are deleted, because copying a KV iterator
duplicates a host-side handle. Rewrite those to `++it` / `--it`; the compiler finds every one;

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.

[P2] Reverse iteration escapes this compiler check. rbegin()/rend() expose std::reverse_iterator, whose postfix operator still compiles and copies the underlying KV handle. The main guide at lines 349-352 already explains this. Mirror that warning here and require a manual sweep of reverse loops instead of promising that the compiler finds every use.

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.

Confirmed, and the contradiction was internal — the main guide already says "The compiler does not find every one." while this page promised the opposite. Fixed: the bullet now says the compiler finds every direct use but not the reverse ones, spells out that rbegin()/rend() return a std::reverse_iterator whose postfix operators belong to the adaptor and are not deleted, gives the loop that compiles clean while doing the handle-duplicating copy, and requires a manual sweep.

Comment thread docs/kv-multi-index.md Outdated

- the postfix iterator operators `it++` / `it--` are deleted, because copying a KV iterator
duplicates a host-side handle. Rewrite those to `++it` / `--it`; the compiler finds every one;
- the primary `lower_bound` / `upper_bound` take a `name` as well as a `uint64_t`, where

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.

[P2] Document the actual overload source break. An upstream call such as t.template lower_bound<uint64_t>(k) (or upper_bound) is valid because those APIs are member templates, but Wire's concrete overloads reject that syntax; a wrapper convertible to both name and uint64_t can also become ambiguous. Current kv_multi_index.hpp comments document both cases. Add them here and to the porting checklist; the bare member-address example is not a Wire-only incompatibility.

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.

Confirmed by compiling it:

error: 'lower_bound' following the 'template' keyword does not refer to a template

That is a much better example than the bare member address, and you are right that the member-address case is not Wire-only — the guide even says upstream cannot deduce PK there either. Restructured into the two real breaks: the explicit template argument, and the dual-convertible wrapper ambiguity (where against a single uint64_t parameter it used to pick the uint64_t conversion). The member-address case is demoted to a parenthesis noting it fails on both sides, with the static_cast escape hatch kept.

Comment thread docs/kv-multi-index.md Outdated
**The `name` overload landed in
[wire-cdt#113](https://github.com/Wire-Network/wire-cdt/pull/113)**; on a CDT built before it the
bounds took `uint64_t` only, and a `name` primary key needed `.value` at the call;
- secondary key types must be `std::is_trivially_copyable`, not merely serializable — a

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.

[P2] Upstream does not accept arbitrary serializable secondary keys. Its index backend supports only uint64_t, uint128_t, double, long double, and checksum256/fixed_bytes<32>; those types are trivially copyable. The supported fixed type set is therefore a shared restriction. Wire differs mainly in when and how the static_assert diagnoses it, so please remove this false contrast.

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.

You are right, and the guide contains its own disproof: the "Removed on Wire" table lists the upstream secondary-index backend as exactly db_idx64_*, db_idx128_*, db_idx256_*, db_idx_double_*, db_idx_long_double_* — five families, the same fixed set, all trivially copyable. Removed the false contrast. The bullet now says the supported set is upstream's and that what differs is the diagnosis: Wire's static_assert sits in secondary_index_view, so it fires at the first get_index<...>() rather than at the declaration.

Comment thread docs/kv-scoped-table.md Outdated
Use `scoped_table` when your data is naturally partitioned by an account or category (e.g., token balances per account, proposals per proposer). Use `kv::table` when scope adds no value (singleton-like tables, global registries).

`scoped_table` is a drop-in replacement for `multi_index` with the same scope semantics but without the object cache overhead:
`scoped_table` is a source-compatible alternative to `multi_index` (note the payer moves to the first parameter of the mutators, see below) with the same scope semantics but without the object cache overhead:

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.

[P2] This is not source-compatible. The replacement changes the template from <Name, T, ...> to <Name, K, V, ...>, splits the row/key representation, changes primary-key access and mutator signatures, and moves the payer. This file's own migration example shows the required rewrite. Call it a migration target or API-similar alternative rather than source-compatible.

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.

Confirmed — and as you say, the page's own worked conversion demonstrates it. Reworded to "a migration target for multi_index, not a drop-in", listing what the port actually costs: <Name, T, ...><Name, K, V, ...>, the row splitting into separate key and value types, primary-key access changing with it, and the payer moving to the first parameter of every mutator. The performance points that followed are now framed as what the rewrite buys.

Comment thread docs/migrating-from-antelope.md Outdated
| Dense table, few scopes | 124 | 144 (**+16%**) | 136 (+10%) |
| Dense table + 1 index | 252 | 280 (+11%) | 264 (+5%) |

Measured across EOS mainnet contracts the net effect is a 2–6% saving. The full breakdown is in

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.

[P2] Qualify the 2-6% result. The linked analysis calls this an estimate and obtains 2-6% only when excluding xsat. Including xsat, secondary storage is approximately break-even for kv::table and 5% higher for multi_index. Please say estimated 2-6% excluding xsat, or show both population results.

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.

Confirmed against the source: kv-ram-billing.md:130 says "On the EOS mainnet (excluding xsat), KV saves 2-6%", and §§ at lines 92/100 give both populations. Fixed to say estimated, excluding xsat, and to give the other side too — including it, secondary storage alone is break-even for kv::table and ~5% higher for multi_index, with the reason (legacy index256_object stores its 32-byte key inline; KV always pays a shared_blob offset pointer). 345M of the 355.6M idx256 entries come from xsat, which is why the population choice moves the number.

Comment thread docs/migrating-from-antelope.md Outdated
`set_proposed_producers_ex`, `get_blockchain_parameters_packed`,
`set_blockchain_parameters_packed`, `set_privileged` — but not `set_kv_parameters_packed`, removed
above), `set_finalizers`, and the full
cryptographic surface — `sha1`/`sha256`/`sha512`/`ripemd160` with their `assert_` forms, plus `sha3` (which has no

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.

[P2] assert_sha3 exists. Current crypto_ext.hpp defines sysio::assert_sha3, and the unit tests exercise it. The same header also exposes keccak and assert_keccak, which are omitted from this claimed full cryptographic surface. Preserve assert_sha3 and include the Keccak pair (or narrow the wording if this is not intended to be exhaustive).

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.

Confirmed — all four exist in crypto_ext.hpp and compile. Fixed in 64b40a9eb: the line now reads sha1/sha256/sha512/ripemd160 with their assert_ forms, then sha3 and keccak with theirs (assert_sha3, assert_keccak). The "no assert_ form, here or upstream" claim was simply wrong.

Comment thread docs/migrating-from-antelope.md Outdated
need only the iterator sweep in step 4.
6. Check secondary-index key types are `std::is_trivially_copyable`; give any `std::string` or
`std::vector` key a fixed-width surrogate.
7. **Find every `emplace` / `modify` that names a user as payer.** Decide, per table, whether the

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.

[P2] Audit singleton payer calls too. Existing ports commonly call singleton::set(value, user) or get_or_create(user, ...); kv_singleton forwards that payer into kv_multi_index::emplace/modify, so these calls hit the same missing-sysio.payer runtime rejection. Generalize this to every storage mutator accepting a payer and name the singleton calls explicitly.

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.

Confirmed — kv_singleton::set and get_or_create both forward their payer into _t.emplace / _t.modify, so they hit the same rejection. Checklist item 7 is generalized to "every storage mutator that names a user as payer" and names the singleton calls explicitly alongside emplace/modify.

Comment thread docs/migrating-from-antelope.md Outdated
7. **Find every `emplace` / `modify` that names a user as payer.** Decide, per table, whether the
contract absorbs the RAM (`get_self()`) or the client will supply `sysio.payer`.
8. Replace any `send_deferred` with an inline action, an off-chain relayer, or a crank action.
9. Regenerate the ABI and diff it — `jq -S . old.abi > a && jq -S . new.abi > b && diff -u a b`,

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.

[P2] Current cdt-abidiff does compare this metadata. #112 is merged: diff_tables() now checks table_id, key_names, key_types, and secondary_indexes as well as type/index_type. Recommend current cdt-abidiff, keeping the raw JSON diff only as a fallback for pre-#112 installations. Also include abi_extensions if lines 299-302 are meant to enumerate every compared section.

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.

Both correct, and both were mine from the previous commit — I fixed the ABI section and left its duplicate in the checklist 400 lines later, which is the exact failure mode I keep flagging in these reviews.

Checklist item 9 now recommends cdt-abidiff and keeps the raw JSON diff as the pre-#112 fallback. And abi_extensions is added to the enumerated list — I had derived that list by grepping diff_*() calls, which misses it because it goes through diff_opaque_section("abi_extensions") at line 660 rather than a diff_abi_extensions().

Comment thread docs/migrating-from-antelope.md Outdated
Under the deb/rpm layout the toolchain lives in `/usr/lib/cdt`, and only an enumerated list of
public entry points is symlinked into `/usr/bin`: `cdt-cc`, `cdt-cpp`, `cdt-ld`, `cdt-abidiff`,
`cdt-init`, `cdt-codegen`, `cdt-protoc`, `cdt-protoc-gen-zpp`, `cdt-pp`, `cdt-wast2wasm`,
`cdt-wasm2wast` and their `sysio-*` originals. The bundled `clang`, `lld`, `wasm-ld`, `opt`, `llc`

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.

[P3] Most of these tools have no sysio-* original. The install rules retain sysio-* aliases only for pp, wast2wasm, and wasm2wast; there is no corresponding sysio-cc, sysio-cpp, sysio-ld, sysio-abidiff, sysio-init, sysio-codegen, or protobuf command. Name the three aliases explicitly so readers do not look for commands that are not installed.

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.

Confirmed — the installed bin/ has exactly three: sysio-pp, sysio-wasm2wast, sysio-wast2wasm. The text implied all eleven entry points had sysio-* originals. Now names the three and says explicitly there is no sysio-cc, sysio-cpp, sysio-ld, sysio-abidiff, sysio-init, sysio-codegen or sysio-protoc, so readers reach for the cdt- name.

Adds docs/migrating-from-antelope.md for developers porting a contract from
EOS, Telos, WAX or another Antelope-family chain.

Covers getting started on Wire (toolchain install, cdt-init, add_contract,
native testing, provisioning and deploy), the eosio -> sysio rename surface,
the storage change from the legacy db_*_i64 tables to the KV layer, the
resource-model change, and a verified diff of the host-function surface.

The resource section is deliberately scoped to what changes in contract code
-- where a contract bills RAM, why sysio.payer welds RAM to CPU/NET, and that
CPU/NET are billed per top-level action so a contract's policy funds the whole
inline and notification tree beneath it. The model itself is left to
wire-sysio's docs/roa-overview.md rather than restated here.

Also broadens the CLion build-dir ignore to cmake-build-*/ and ignores
prequel's local review state.
Eleven review findings on #111, each verified against the code before the text
changed. Several were factual errors, not imprecision.

Toolchain install. The tarball has no public-entry separation: InstallCDT.cmake
puts unprefixed clang, clang++, lld, ld.lld, wasm-ld, opt, llc and llvm-* in the
same bin/, so the previous advice to put /opt/wire-cdt/bin on PATH shadowed the
distro toolchain. That guarantee holds only for deb/rpm, where /usr/bin gets
symlinks for public entry points alone; the tarball now recommends an absolute
path, an alias, or a private symlink dir. `apt install` also installed only the
base component, while the native-testing path linked further down needs
libnative*, gen_native_dispatch.py and share/cdt/native-contract-src -- all
COMPONENT dev -- and neither package pulls in CMake or a build tool.

The addpolicy command could not run. Its line-continuation backslashes sat inside
the still-open single-quoted JSON, and the fields were camelCase from the C++
parameters where sysio.roa.abi declares net_weight / cpu_weight / ram_weight /
time_block / network_gen. One line, ABI names.

multi_index is not the drop-in the guide claimed. Wire deletes operator++(int)
and operator--(int) on both the primary and secondary iterators, so the classic
`it++` loop stops compiling -- this repo's own examples/ still use that form. The
required ++it sweep is documented and the "drop-in" framing softened. Secondary
key types are constrained by a std::is_trivially_copyable static_assert in
secondary_index_view, not by "has a serializer", so std::string and std::vector
keys are rejected; the guide said the opposite.

The `_i` recipe was unsafe for short names. Runtime `_i` always DJB2-hashes, but
abigen routes an annotated name of <= 13 characters through legacy
string_to_name, so the row is written under one table_id and described in the ABI
under another (user_table: 61956 at runtime, 3509 in the ABI -- reproduced
independently). Restricted to names over 13 characters, where both sides hash and
agree.

cdt-abidiff cannot show the change the guide pointed it at: it compares tables
only by name and type, ignoring index_type, key_names, key_types, table_id and
secondary-index metadata, and its version check reduces eosio::abi/1.2 and
sysio::abi/1.2 to the same number. Replaced with a normalized jq diff.

The rename recipe skipped what it advertised. `_` is a word character, so
\beosio\b finds no boundary in eosio_assert and left the whole C API unrenamed --
the exact identifiers the table above it lists. Those are now named explicitly.

Billing claims scoped. "The signer is neither charged nor limit-checked" is true
only of objective billing: verify_init_subjective_billing applies a subjective
CPU budget to first authorizers who are not payers and can terminate the
transaction. NET is not the transaction's serialized bytes either -- each action
is billed its own billable size plus an equal share of a 16-byte constant, the
signatures, the extensions and the header, so signatures cost NET and batching
amortizes overhead.

The porting checklist gains the iterator sweep, the secondary-key constraint, the
C API rename and the -dev package, and no longer recommends cdt-abidiff.
Twelve inline findings plus four from the review body, each verified against the
code first.

Factual corrections. The addpolicy field-name note claimed a camelCase/snake_case
divergence between the C++ action and its ABI -- there is none; I had read
wire-system-contracts' copy of sysio.roa.hpp, abandoned on a 2025 branch, where
the authoritative wire-sysio/contracts copy is snake_case on both sides. The
note is gone rather than reworded. `-wasm2wast` becomes `eosio-wasm2wast`;
`cdt-init -bare` emits four files, not two, since write_ricardian runs
unconditionally; the clean-machine prerequisites now install build-essential and
jq, which the guide's own `make` and `jq` commands needed; and `network_gen` is a
placeholder with instructions for finding the issuer's generation, because
addpolicy scopes nodeowners to the value passed and hard-coding 0 breaks after a
rollover.

The `_n` fallback for short `_i` names was itself impossible. `_n`'s alphabet is
`.12345a-z`, so the guide's own `user_table` example is a compile error. Only
short names that are already valid Antelope names can switch; others must be
renamed or lengthened until abigen is fixed.

Over-absolute claims scoped. An unprovisioned contract blocks ordinary
contract-paid calls, not every call -- with the added caveat that the sysio.payer
escape hatch covers bandwidth only, so RAM the contract bills itself still needs
headroom. Separating user-paid RAM from contract-paid bandwidth is possible via a
persistent `<contract>@sysio.code` delegation and an inline action, so that claim
is now scoped to the direct top-level call shown. Subjective billing meters each
top-level action's first authorizer, so several accounts can be throttled, not
one signer.

Billing mechanics. The NET overhead split is `overhead / actions + 1` -- integer
division then an unconditional +1, which over-bills by up to a byte per action
where the division is exact, rather than being a ceiling. And only CPU covers the
whole call tree; NET is fixed by what reaches the wire, so the conclusion is
split.

Compatibility framing. multi_index is a compatibility shim, not a drop-in, here
and in kv-storage-guide.md; the postfix iterator rewrite is the source change,
and the duplicate-emplace and templated-bounds semantics are called out as
matching upstream. The checklist no longer says direct db_* callers have nothing
to do. README now says the contract is billed by default and that RAM follows the
contract's payer argument.

The ROA overview is referenced through wire-sysio#583 rather than a master URL
that does not resolve yet.

Depends on #113 for the emplace and lower_bound/upper_bound semantics this
describes.
Third review round. Three findings were factual errors in text presented as
checked, which is worse than an omission in a guide whose whole premise is
that its claims were run.

- The two `clio get table` invocations were CLI parse errors. `get table`
  declares two required positionals (account, table) with scope as
  `-S/--scope` (clio/main.cpp:2697-2699); both lines passed three. `roastate`
  is a `kv::global` and therefore unscoped, so the scope argument was wrong
  in concept as well. Added in round 2 to answer a review comment, never run.

- The duplicate-key abort and the `name` bounds were stated as verified, but
  both arrive with wire-cdt#113 and are absent from any CDT built before it.
  The external dependency (wire-sysio#583) was already caveated; the in-repo
  one was not. Now says so, and records #113's receiver guards, which change
  behaviour for a port that opens a handle on another account.

- "Before anyone can call it, a node owner must issue it a policy" is
  contradicted twenty lines later by the note that a `sysio.payer` caller
  reaches an unprovisioned contract fine. Now "before an ordinary caller".

Also corrected, all verified against the tree rather than reasoned about:

- `kv::global` + `_i` is broken at every name length, and the guide's advice
  to lengthen past 13 characters makes it worse. Below 14 the ABI carries two
  entries (`app_config` -> 38424 alongside a decoded-hash name
  `idrzzw4ktxljf` -> 21489, which is where the row actually lands); at 16 it
  fails to link outright with `table_id collision: 'app_config_table' and
  'wdfp4hyupu.q2' both have table_id 42322`. The rule as written holds for
  `kv::table`. Documented with both rows rather than half-fixed; the in-tree
  `_i` globals are left alone because renaming them hits the second row.
- The `kv::table` sample using `kv::table` included only `hash_id.hpp`, which
  pulls serialize.hpp and name.hpp and nothing else.
- `table_id` is DJB2 over the eight big-endian bytes of the name's raw
  uint64, not over the string.
- The byte-count row contradicted the row above it: Antelope's key is four
  8-byte fields (32 B) and Wire's is a 2-byte table_id plus 16 (18 B).
- `sed -i` is GNU-only and this project supports macOS.
- Failed transactions are free objectively but still accrue subjective CPU
  against the first authorizer, which the same section says can throttle.
- A KV iterator is copyable; the cost of duplicating its handle is why the
  postfix operators are deleted, not an inability to copy.
- kv-multi-index.md called the shim a "drop-in replacement", the stronger
  form of the claim this PR already softened in kv-storage-guide.md.

The `.gitignore` hunk is dropped from this PR. It was byte-identical to a
strict subset of the one on #112 and #113 and conflicted with both; it now
lands once, on #113.
The guide said a failure accrues subjective CPU against the first
authorizer, which is true but omits the part that matters in a
contract-pays guide: disable-subjective-payer-billing defaults to ON
(producer_plugin.cpp:1478), so subjective_bill_failure skips the payer
loop (subjective_billing.hpp:177) and runs only the authorizer loop
(:184). Under contract-pays the payer is the contract and the first
authorizer is the caller, so the caller absorbs the throttling and the
contract does not -- which is what that default exists to do.
…caveat

Third review round on the guide. Three findings, all of them cases where the
text was confident and wrong.

- The `-S <gen>` I added last round parses, but silently returns nothing for
  the generation a reader is most likely to try. CDT declares every scoped
  table's scope as a `name` and the chain honours that, trying `name(scope)`
  before falling back to an integer. `1`-`5` are valid name characters, so
  `-S 1` is read as the name "1" -- 576460752303423488 -- and the query
  returns no rows, which reads as "your issuer is not a node owner". Only
  generations containing a 0 or a 6-9 work, because name() rejects those
  characters and the fallback runs. Verified by computing the encoding for
  0,1,2,5,6,9,10,11,21,100. The command now omits -S and reads network_gen
  off the rows.

- The "Bytes per row" row was wrong on both sides and inverted the
  conclusion. Antelope's key_value_object is 108 + value with a 16-byte key,
  not a 32-byte one; Wire's kv_object is 112 + key + value. More importantly
  the guide implied Wire is cheaper per row. wire-sysio's own kv-ram-billing.md
  -- which this guide links -- gives 124 legacy vs 144 for sysio::multi_index
  (+16%) and 136 for kv::table (+10%) on a dense table. A ported contract
  needs MORE RAM, not less, and telling an author otherwise is how a
  provisioning policy comes up short. Says so now.

- kv-multi-index.md gained a bullet asserting #113's guards as shipped fact,
  in the same commit that added the "arrives with #113" caveat to
  migrating-from-antelope.md. The caveat landed in one of the two files. It is
  in both now, and the "one known divergence" line -- there are three at this
  commit -- lists them.

Also:
- macOS sed guidance was insufficient and named the wrong package. `sed -i ''`
  alone does not help: BSD sed has neither \b nor \|, so \beosio\b matches a
  literal "beosiob" and renames nothing, silently, while the \(assert\|...\)
  group is a syntax error. gsed, from gnu-sed.
- The db_* paragraph said "links but fails at deploy" while citing #112 in the
  same breath; after #112 removes them from the --allow-undefined-file list it
  is a link error. Both states described.
- The table_id collision diagnostic comes from cdt-codegen's link-stage ABI
  finalize, not wasm-ld; `cdt-cpp -c` on the same file succeeds.
- Upstream's bounds are member templates whose parameter cannot be deduced,
  not an overload set -- same outcome for &table::lower_bound, different
  reason, and the named static_cast escape hatch is Wire-only.
- hash_id::max_length is 128 but nothing validates against it or against an
  alphabet; presented as a convention rather than a check.
- kv-storage-guide.md still said "Drop-in EOSIO replacement", the stronger
  form of the claim this PR already softened elsewhere in the same file.
"broken at every length" overstated it. Reads and writes are correct --
the contract and the host both use the compile-time table_id, so the row
is stored and fetched consistently, and hash_id_tests passes its
integration suite (4/4) exercising exactly that path on a real chain.

What is actually wrong is narrower: the ABI carries two table entries
instead of one, so a get_table_rows by the readable name finds nothing.
That is the same name-vs-hash mismatch the section already describes; the
only kv::global-specific part is that lengthening the name past 13
characters -- the remedy for kv::table -- turns it into a table_id
collision at link instead of fixing it.
Fourth review round.

The "Budget more RAM, not less" paragraph was inserted INSIDE the comparison
table, which terminated it: the `get_table_rows` row that followed became a
lazy continuation of the paragraph and stopped rendering as a table row --
the very row the next sentence refers to. Moved out.

More importantly the advice it carried was over-corrected. Last round fixed a
claim that Wire is cheaper per row by asserting the opposite; both are wrong,
because the direction depends on rows-per-scope. The `table_id_object` is
billed per (code, scope, table), so a table with one row per scope -- a token
balance, a per-user settings row, the most common Antelope shape -- sheds a
whole 108-byte object per scope: wire-sysio's figures are 232 -> 144, a 38%
SAVING. Only dense tables with few scopes pay more (124 -> 144, +16%), and
across EOS mainnet the net is a 2-6% saving. The guide now gives the full
four-row table and says to size from your own ratio.

Also:
- The BSD sed note claimed `\(assert\|...\)` is a syntax error. It is not:
  without REG_ENHANCED macOS sed treats `\|` as a literal `|`, so the group
  matches the literal text "assert|assert_message|..." and, like `\b`, simply
  renames nothing. Both halves fail silently, which is the part worth
  stating -- a reader told to expect an error concludes the script worked.
- kv-multi-index.md's divergence list gained a `name`-bounds bullet last
  round that was itself an uncaveated #113 claim, in the same file where the
  guards bullet had just been caveated. Now carries the caveat, describes
  upstream's member template correctly, and gives the static_cast form that
  works on Wire.
- Added the fourth real divergence, the trivially-copyable secondary-key
  static_assert (kv_multi_index.hpp:769), which the migration guide lists and
  its sibling did not.
- Fixed a duplicated "the", a swallowed sentence, a bare #113 that does not
  autolink, a duplicated #112 mention, and a summary bullet that restated the
  _i 128-character limit as a rule after the body had just called it a
  convention.
Fifth review round.

The `static_cast` the guide offers as the one working way to take a member
pointer at the primary bounds does not compile: `const_iterator` is nested in
the table and is not in scope at a use site, so it gives `unknown type name
'const_iterator'`. Verified, along with the qualified form that does work.
The text was lifted from the header's own comment, where it sits inside the
class and is correct in place. Fixed in both docs that carry it.

"The compiler finds every one" was false for the postfix sweep. rbegin()/
rend() return std::reverse_iterator, whose postfix operators belong to the
adaptor and are NOT deleted -- `for (auto rit = t.rbegin(); rit != t.rend();
rit++)` compiles clean and performs exactly the handle-duplicating copy the
deletion exists to prevent. Verified both ways: rit++ compiles, it++ gives
"overload resolution selected deleted operator '++'". Reverse loops need a
manual grep, and the guide now says so.

The examples/hash_id_example claim was wrong on two counts: its ABI has ZERO
table entries, not the extra one (its structs sit at namespace scope, so
abigen's discovery gate never sees them), and examples/ is not in the build
graph or ctest, so "their integration tests pass" described a test that does
not exist. Only hash_id_tests is affected, and its integration test does pass.

The trivially-copyable rejection was over-stated: the static_assert lives in
secondary_index_view, so it fires at get_index<...>(), not at declaration. A
std::string-keyed secondary index compiles and can even be written through
emplace.

key_names/key_types were called Wire additions; they are original EOSIO
table_def fields (abi_def.hpp:74-75). Only table_id and secondary_indexes are
new -- what changed is that Wire populates the other two.

The /usr/bin symlink rule was wrong: it is an enumerated allowlist, and eight
cdt-* binutils aliases (cdt-ar, cdt-ranlib, cdt-nm, cdt-objcopy, cdt-objdump,
cdt-readobj, cdt-readelf, cdt-strip) are deliberately excluded
(InstallCDT.cmake:84-98). A reader invoking cdt-ar off PATH after a deb
install gets command-not-found.

Sibling drift, the recurring one: docs/kv-global.md's worked example is
verbatim the pattern the guide calls a defect, and its long-name variant is
the build-failure case; docs/kv-storage-guide.md's matrix still said `_i` was
fine for kv::global. Both caveated, and kv-scoped-table.md's "drop-in
replacement" is corrected the way the other two already were.

Also: the #112 caveat now covers the cdt-abidiff limitations (fixed there) and
distinguishes the two db_* declaration forms -- one carrying
sysio_wasm_import emits an explicit import and links regardless of the
allow-list, so it keeps failing at deploy rather than at link. Plus the exact
deleted-operator diagnostic, the full collision message, protobuf's 1.3, the
double/long-double sign-flip, per-action rather than per-transaction first
authorizer, nodeowners being scoped-by-generation/keyed-by-owner, and the
absence of an assert_sha3.
Both PRs the docs referenced as pending are merged, and one of them made a
sentence here false. Every claim below was re-verified against a CDT built from
merged master, not just reworded.

The "unchanged host functions" list promised "every privileged.h setter". #112
removed set_kv_parameters_packed, which was one. The list now names the setters
that remain, and the two removals get rows in the "Removed on Wire" table with
the diagnostic each produces -- they differ, and the difference is useful when
porting: <sysio/security_group.h> is gone outright ("file not found"), while
privileged.h is still there and set_kv_parameters_packed is an undeclared
identifier in it. Both confirmed by compiling; set_privileged from the same
header still builds.

The ABI section told the reader not to reach for cdt-abidiff, on the strength of
limitations #112 fixed. Inverted: it now lists the sections the tool compares --
version as the full string, structs, types, actions, tables with the full
metadata, clauses, enums, protobuf_types, variants, action_results and
error_messages -- and keeps the old-toolchain caveat and the jq fallback for
anyone on an older CDT.

The legacy-database and multi_index sections described #112 and #113 as
forthcoming. They describe the current toolchain now, with the pre-merge
behaviour as the caveat. Verified against merged master: `it++` is still
rejected with "overload resolution selected deleted operator '++'", a hand
declared db_store_i64 now fails with "wasm-ld: undefined symbol: db_store_i64",
and the documented static_cast escape hatch plus lower_bound on name, uint64_t
and {42} all compile.

Both docs also now say the #113 receiver guard reaches sysio::singleton:
get_or_create, set and remove mutate through the kv_multi_index it holds, so a
singleton handle on another account's code is read-only like a table handle.
The kv-multi-index singleton section also said singleton "is backed by"
kv_multi_index, which reads as inheritance; it holds one.
Each correction below was checked against a CDT built from master before it was
written.

migrating-from-antelope.md

  Floating secondary keys do not sort as they did. The legacy idx_double /
  idx_long_double indices rejected NaN and folded -0.0 onto +0.0; Wire's
  encoder is a pure bit transform, so it admits NaN and sends the signed zeros
  to distinct ranges (-0.0 below +0.0). Narrowed to the integer types, with the
  edge cases spelled out.

  The 2-6% RAM figure is an estimate that excludes xsat, whose 345M idx256
  entries dominate the population. Both populations are now given.

  assert_sha3 exists, and so do keccak / assert_keccak -- all four are in
  crypto_ext.hpp and compile. The claim that sha3 has no assert_ form was
  simply wrong.

  Only three tools carry a sysio-* alias: sysio-pp, sysio-wast2wasm,
  sysio-wasm2wast. The text implied all eleven entry points did.

  The payer checklist item covered emplace and modify only. singleton::set and
  get_or_create forward their payer into kv_multi_index::emplace/modify and hit
  the same rejection, so they are named.

  Two of my own from the previous commit: the checklist still told readers to
  avoid cdt-abidiff for table metadata, contradicting the section 400 lines
  earlier that this branch had already fixed; and the section's list of compared
  sections omitted abi_extensions, which diff_opaque_section handles.

kv-multi-index.md

  The postfix sweep is not fully compiler-caught: rbegin/rend hand back a
  std::reverse_iterator whose postfix operators are not deleted. The main guide
  said so; this page promised the opposite.

  The real bound overload break is an explicit template argument --
  t.template lower_bound<uint64_t>(k) is valid upstream and is
  "error: 'lower_bound' following the 'template' keyword does not refer to a
  template" here -- plus the dual-convertible ambiguity. The bare member-address
  case does not compile upstream either, so it was never Wire-specific.

  Upstream does not accept arbitrary serializable secondary keys: its backend is
  exactly the five db_idx* families, the same fixed set. Only the diagnosis
  differs. Removed the false contrast.

kv-global.md

  The _i failure is a kv::global ABI bug, fixed by #115, not a property of _i --
  which works and is tested on kv::table. Scoped the warning to a pre-#115
  toolchain and stopped implying _i is generally unsuitable. The name-alphabet
  rule now says the 13th position takes only .12345a-j, being 4 bits not 5.

  The complete example emitted NO ABI table entry: a namespace-scope struct with
  only [[sysio::table]] is not reachable from the contract, and abigen skips it.
  Moved it inside the contract class -- it now emits ('appconfig', 46121) -- and
  the ABI note says what reachability requires. The shipped
  examples/kv_global_example has the same defect and is not fixed here.

kv-scoped-table.md

  Not source-compatible with multi_index: the template goes <Name,T,...> ->
  <Name,K,V,...>, the row splits, primary-key access changes and the payer
  moves. Called a migration target, which its own worked conversion shows it to
  be.
@heifner
heifner force-pushed the docs/migrating-from-antelope branch from 7ed8f26 to 64b40a9 Compare September 7, 2026 16:25
The guide told readers to use _i only above 13 characters, because a shorter
annotated name was routed through string_to_name by the ABI generator while the
literal hashed -- user_table ran at 61956 and was advertised as 3509.

wire-cdt#115 makes the template-derived table_id authoritative, so both lengths
now agree. Restated as a pre-#115 caveat, matching how this guide already
handles #112 and #113, and the "has to be renamed, or lengthened past 13
characters, until abigen is fixed" advice is dropped -- _i is now the answer for
a name outside the _n alphabet at any length.

The _n alphabet note also gains the 13th-position restriction (.12345a-j),
which is 4 bits rather than 5.
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