Three things the tier-1 case matrix needed (quantus/extension#7). **Storage keys and values (#4).** `storage_target` resolves a pallet and item to `twox128(prefix) ‖ twox128(item)` plus each map key hashed by the hasher the entry declares, and reports the value type and the entry's default. `decode_storage_value` reads the result. Nothing here knows that `System::Account` is a `Blake2_128Concat` map over an `AccountId32`; the hashers, both types and the default all come out of the metadata. The `Default` versus `Optional` distinction is carried deliberately. A `Default` entry that the node returns nothing for means the declared default — an account nobody has funded reads as a zero balance — where an `Optional` one means nothing. A wallet that conflated them would report a failure for an account that simply has no money in it. **A call nests inside a call.** A multi-field variant with named fields only accepted a positional array, so `Utility.batch_all` — whose `Vec<RuntimeCall>` holds calls spelled exactly like top-level ones — could not be encoded at all. It now takes the same object form at any depth. **Every integer renders as a decimal string**, whatever its width, and that is now stated rather than incidental. A u128 balance does not survive a JSON number (12 decimal places puts ordinary amounts past 2^53) and `scale_value` widens every unsigned integer to u128, so the width is not available to switch on. Emitting a number when it happens to fit and a string when it does not would make a consumer handle both shapes for the same field depending on the value. All of it proven against Heisenberg: an ML-DSA-65 account funded by a `batch_all` whose payload crossed the 256-byte BLAKE2b threshold, then signing and being included itself. Closes #4. Refs #3 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012uDUodEcRbBwNRi3UCmw8f
61 lines
2.8 KiB
TOML
61 lines
2.8 KiB
TOML
# Metadata-driven SCALE encode/decode for the Quantus chain, compiled to WASM.
|
|
#
|
|
# A separate crate from `quantus-crypto` for the same reason that one is separate
|
|
# from `wasm-crypto`: different dependency graphs, built independently. They ship
|
|
# as sibling packages and the extension uses both — this one decides *what bytes*
|
|
# get signed, that one signs them.
|
|
#
|
|
# Why this exists at all rather than `@polkadot/api`: quantus/api#1. In short,
|
|
# polkadot-js cannot decode a Quantus block (it reads the extrinsic preamble byte
|
|
# as a version when the top two bits are a type tag), it refuses fixed arrays
|
|
# longer than 2048 (ML-DSA signatures are 5261 and 7219 bytes), and — the part
|
|
# that matters after those are patched — it *guesses* that signed extensions it
|
|
# does not recognise contribute nothing to the signed payload. On a chain whose
|
|
# encoding has already changed between runtimes, a guess like that produces a
|
|
# valid signature over the wrong bytes, which arrives as `BadProof` and looks
|
|
# exactly like a wrong key. See quantus/wasm#3.
|
|
|
|
[package]
|
|
authors = ["Quantus Network Developers <hello@quantus.com>"]
|
|
description = "Metadata-driven SCALE codec for the Quantus chain, as WASM bindings."
|
|
edition = "2021"
|
|
license = "Apache-2.0"
|
|
name = "quantus_codec"
|
|
publish = false
|
|
repository = "https://git.lair.cafe/quantus/wasm"
|
|
resolver = "2"
|
|
version = "0.0.0"
|
|
|
|
[lib]
|
|
crate-type = ["cdylib", "rlib"]
|
|
|
|
[dependencies]
|
|
# Versions match blackbeard.observer's, which is the other consumer decoding this
|
|
# chain against its own metadata and the reference implementation for this crate.
|
|
frame-metadata = { version = "23", default-features = false, features = ["current", "decode"] }
|
|
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
|
scale-info = { version = "2", default-features = false }
|
|
scale-value = { version = "0.18", default-features = false }
|
|
# Only for `IgnoreVisitor`. `scale_value` sizes a sequence's Vec from the length
|
|
# prefix *before* decoding an item, so a blob that disagrees with the registry can
|
|
# ask for an allocation of any size and abort the process — there is no Err to
|
|
# catch. Pinned to the version `scale-value` itself resolves so both see one
|
|
# registry. blackbeard.observer took a 76 GiB allocation to find this.
|
|
scale-decode = { version = "0.16", default-features = false }
|
|
serde_json = "1"
|
|
wasm-bindgen = "0.2"
|
|
# Storage keys. Substrate hashes a pallet prefix and an item name with twox128
|
|
# and each map key with whatever hasher the metadata declares for it.
|
|
blake2 = { version = "0.10", default-features = false }
|
|
twox-hash = { version = "2", default-features = false, features = ["xxhash64"] }
|
|
|
|
[profile.release]
|
|
codegen-units = 1
|
|
debug = false
|
|
debug-assertions = false
|
|
incremental = false
|
|
lto = true
|
|
opt-level = "z"
|
|
panic = "abort"
|
|
rpath = false
|