Files
Jędrzej Stuczyński d2833c76c0 experiment: attempt to retroactively generate specs for node families and ecash contracts (#6813)
* experiment: add openspec details for node families contract

* add openspec for the ecash contract

* fix(ecash): correct latest_deposit off-by-one

DepositStorage::latest_deposit() returned the counter value, but the
counter holds the *next* free id (after next_id() saves counter+1). The
GetLatestDeposit handler then tried try_load_by_id(counter), which
always returned None — meaning the query yielded { deposit: None }
both on a fresh contract and after every successful deposit.

Fix: return counter.checked_sub(1) so latest_deposit() yields the most
recently assigned id (or None on a fresh contract). The
getting_latest_deposit unit test is updated to assert Some(0) and
Some(1) after one and two next_id() calls respectively.

No downstream consumer was relying on the buggy semantics
(validator-client exposes the query as a passthrough trait method that
nothing currently calls).

* experiment: add openspec details for ecash contract

Reverse-engineered openspec change `ecash-contract-spec` documenting
the existing CosmWasm contract at `contracts/ecash/`. Mirrors the
node-families workflow: docs-only deliverable, no migration, no
dependency changes. Archived as
openspec/changes/archive/2026-05-21-ecash-contract-spec/ and promoted
to openspec/specs/ecash-contract/spec.md as the canonical reference.

The spec captures 25 normative requirements with 64 scenarios covering
instantiation, migration, deposit submission (default + reduced tier),
RequestRedemption + redemption-proposal reply, legacy RedeemTickets
(dead code retained), stubbed blacklist surface, the ticketbook-size
invariant tripwire, the full query surface, and the public storage /
event / error surface.

Key documented points the source-of-truth phrasing pins down:
- The contract stores claimed ed25519 pubkeys opaquely; ownership is
  enforced off-chain by nym-api signers via `validate_deposit`.
- Per-signer-local de-duplication via `state.already_issued`; no
  on-chain "issued" state.
- Raw 32-byte deposit storage under the `"deposit"` namespace; deposit
  ids are sequential `u32` starting at 0.
- Statistics invariant: default_count + sum(custom_count) = total.
- `cw_controllers::Admin` is used as a generic address-equality helper
  for the `multisig` slot (the wrapper's full admin semantics are not
  exercised on that slot).
- `RedeemTickets` is dead code retained on the public surface; flagged
  as a candidate for removal.

Stubbed-blacklist final disposition is the only Open Question left for
the redesign change owner.

* docs(ecash): add rustdoc derived from archived ecash-contract spec

Drop short doc-comments on the ecash contract surface — handlers,
storage slots, message variants, error variants, event constants,
shared types — derived from the canonical spec at
openspec/specs/ecash-contract/spec.md (archived 2026-05-21).

Coverage:
- contracts/ecash/src/*.rs: crate-root summary, both DepositStorage
  and DepositStatsStorage with their invariants called out, every
  #[sv::msg(...)] handler in contract/mod.rs, reply id constants,
  Config + invariants snapshot, migration entry point.
- common/cosmwasm-smart-contracts/ecash-contract/src/*.rs: every
  ExecuteMsg / QueryMsg variant, every reachable EcashContractError
  variant (with unreachable-but-preserved variants flagged), every
  event constant, every response type, Deposit + DepositId.

Explicitly out of scope (separate concerns):
- Removing event_attributes::BANDWIDTH_PROPOSAL_ID (dead constant,
  documented as such for now).
- Removing ExecuteMsg::RedeemTickets (dead handler, documented as such;
  removal is a breaking-schema change).
- contracts/ecash/Cargo.toml version bump (docs-only).

No behaviour change; all 38 contract tests pass and cargo doc emits
no warnings on the touched crates.
2026-05-22 15:30:08 +01:00

193 lines
5.2 KiB
JSON

{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"description": "Look up a blacklist entry by its bs58-encoded ed25519 public key. Always returns `None` on a freshly deployed contract because the blacklist execute paths are stubbed.",
"type": "object",
"required": [
"get_blacklisted_account"
],
"properties": {
"get_blacklisted_account": {
"type": "object",
"required": [
"public_key"
],
"properties": {
"public_key": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Paginated listing of blacklist entries. Always empty today (see stubbed blacklist surface). Defaults: limit 50, max 75.",
"type": "object",
"required": [
"get_blacklist_paged"
],
"properties": {
"get_blacklist_paged": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Default per-deposit price (`Config::deposit_amount`). The `GetRequiredDepositAmount` aliases are kept for backwards compatibility.",
"type": "object",
"required": [
"get_default_deposit_amount"
],
"properties": {
"get_default_deposit_amount": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Per-address reduced deposit price override, if any. `None` for any non-whitelisted address.",
"type": "object",
"required": [
"get_reduced_deposit_amount"
],
"properties": {
"get_reduced_deposit_amount": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Enumerate every reduced-deposit whitelist entry in ascending address order. Unpaginated by design (the whitelist is expected to stay small).",
"type": "object",
"required": [
"get_all_whitelisted_accounts"
],
"properties": {
"get_all_whitelisted_accounts": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Look up a deposit by id. Returns `{ id, deposit: None }` when the id has not yet been assigned.",
"type": "object",
"required": [
"get_deposit"
],
"properties": {
"get_deposit": {
"type": "object",
"required": [
"deposit_id"
],
"properties": {
"deposit_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Most recently assigned deposit (or `{ deposit: None }` on a fresh contract). See `DepositStorage::latest_deposit`.",
"type": "object",
"required": [
"get_latest_deposit"
],
"properties": {
"get_latest_deposit": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Paginated listing of deposits in ascending id order. Defaults: limit 50, max 100.",
"type": "object",
"required": [
"get_deposits_paged"
],
"properties": {
"get_deposits_paged": {
"type": "object",
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"description": "Aggregate statistics: global totals + per-account custom-price breakdowns. Reassembled in a single read pass from `PoolCounters` and `DepositStatsStorage`.",
"type": "object",
"required": [
"get_deposits_statistics"
],
"properties": {
"get_deposits_statistics": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}