@quantus/codec: storage keys and values, so the wallet can read balances and nonces #4
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #3, which covers extrinsics only.
The extension needs to read chain state: an account's free balance for the
account list, and its nonce to build the next extrinsic. Tier 1
(quantus/extension#7) side-stepped both — it takes the nonce from
system_accountNextIndex, an RPC that happens to exist, and never shows abalance. Neither dodge survives contact with the UI.
What is needed
Storage addressing, read from the metadata the same way everything in #3 is:
storageKey(pallet, item, keys)—twox128(pallet) ‖ twox128(item)and theneach key hashed by the hasher the runtime declares for it. The hashers are in
the metadata; nothing here should know that
System::AccountisBlake2_128Concat.decodeStorage(pallet, item, bytes)— against the entry's declared value type.Defaultentry, which is what the chain means whenstate_getStoragereturns nothing. AnOptionalentry means nothing when itreturns nothing, and conflating the two reads an unfunded account as an error
rather than as zero.
blackbeard.observer'sStorageTargetandStorageMapincrates/blackbeard-core/src/runtime.rsare the reference; they already carry thedefault-vs-optional distinction and the "which hashers keep their key" table.
twox128is a new dependency (twox-hash), andBlake2_128Concatneeds blake2 —both already in blackbeard's graph.
Acceptance
The extension's account list shows a real Heisenberg balance for
qzk1Nxai…2vSn7matching what the chain reports, and the tier-1 harness gets itsnonce from
System::Accountrather than from an RPC convenience method.Fee estimation (
TransactionPaymentApi_query_info) is a runtime call rather thana storage read; it needs
state_calland a return type decoded from the metadata,and is worth its own issue once this lands.
Shipped in
@quantus/codec0.4.0.storageTarget(pallet, item, keys)returns{ key, valueTy, default }— the key beingtwox128(prefix) ‖ twox128(item)plus each map key hashed by the hasher the entry declares, with the number of keys checked against the number of hashers.decodeStorage(valueTy, bytes)reads the value. Nothing in it knows thatSystem::Accountis aBlake2_128Concatmap over anAccountId32; the prefix, hashers, key types, value type and default all come out of the metadata.The
Default/Optionaldistinction is carried as asked:defaultis hex for aDefaultentry andnullfor anOptionalone, so an account nobody has funded reads as a zero balance rather than as a failure.Proven against Heisenberg through
quantus/extension's tier-1 harness, which now reads real balances and asserts they moved — including catching that twobatch_allcalls it had reported as reverted had in fact funded their targets with exactly 50 HEI each. Unit tests pin the key layout (16+16+16+32 forSystem::Account) and the declared default decoding to a zero balance, against real spec-148 metadata.Closing short of one stated acceptance criterion, deliberately. The extension's account list does not yet show a balance — that is extension-side UI work with its own issue to be written, not a gap in this package. The tier-1 harness also still takes its nonce from
system_accountNextIndexrather thanSystem::Account, and that turns out to be the correct call for choosing a nonce: it counts pending pool transactions, which is exactly what a signer wants. It is the wrong call for waiting on inclusion, and the harness no longer uses it for that — see quantus/extension#7.One other thing came out of the same work and is worth noting here, since it is the same package: a multi-field variant with named fields only accepted a positional array, so
Utility.batch_all— whoseVec<RuntimeCall>holds calls spelled exactly like top-level ones — could not be encoded at all. Nested calls now take the object form at any depth.