d2833c76c0
* 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.
243 lines
7.2 KiB
JSON
243 lines
7.2 KiB
JSON
{
|
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
"title": "ExecuteMsg",
|
|
"oneOf": [
|
|
{
|
|
"description": "Submitted by clients to escrow funds and register a claimed ed25519 identity key. Mints a sequential `deposit_id`. The contract does not verify control of the identity key - that proof is checked off-chain by nym-api signers at blind-sign time.",
|
|
"type": "object",
|
|
"required": [
|
|
"deposit_ticket_book_funds"
|
|
],
|
|
"properties": {
|
|
"deposit_ticket_book_funds": {
|
|
"type": "object",
|
|
"required": [
|
|
"identity_key"
|
|
],
|
|
"properties": {
|
|
"identity_key": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "Submitted by gateways to request batch redemption of spent tickets. Dispatches a `Propose` SubMsg to the multisig contract; the actual transfer effect is gated behind multisig approval.",
|
|
"type": "object",
|
|
"required": [
|
|
"request_redemption"
|
|
],
|
|
"properties": {
|
|
"request_redemption": {
|
|
"type": "object",
|
|
"required": [
|
|
"commitment_bs58",
|
|
"number_of_tickets"
|
|
],
|
|
"properties": {
|
|
"commitment_bs58": {
|
|
"type": "string"
|
|
},
|
|
"number_of_tickets": {
|
|
"type": "integer",
|
|
"format": "uint16",
|
|
"minimum": 0.0
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "**Legacy / dead code.** Only callable by the multisig; bumps the unredeemed-tickets counter and emits a `ticket_redemption` event with `moved_to_holding_account = \"false\"`. No known consumer depends on the side effects; candidate for removal in a follow-on breaking-schema change.",
|
|
"type": "object",
|
|
"required": [
|
|
"redeem_tickets"
|
|
],
|
|
"properties": {
|
|
"redeem_tickets": {
|
|
"type": "object",
|
|
"required": [
|
|
"gw",
|
|
"n"
|
|
],
|
|
"properties": {
|
|
"gw": {
|
|
"type": "string"
|
|
},
|
|
"n": {
|
|
"type": "integer",
|
|
"format": "uint16",
|
|
"minimum": 0.0
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "Transfer the contract admin role. Only the current admin may sign. Dispatches via the cw_controllers `execute_update_admin` handshake.",
|
|
"type": "object",
|
|
"required": [
|
|
"update_admin"
|
|
],
|
|
"properties": {
|
|
"update_admin": {
|
|
"type": "object",
|
|
"required": [
|
|
"admin"
|
|
],
|
|
"properties": {
|
|
"admin": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "Overwrite `Config::deposit_amount`. Only callable by the contract admin. Rejects values below `nym_network_defaults::TICKETBOOK_SIZE` and trips `TicketBookSizeChanged` if the snapshotted invariant has diverged from the current crate constant.",
|
|
"type": "object",
|
|
"required": [
|
|
"update_default_deposit_value"
|
|
],
|
|
"properties": {
|
|
"update_default_deposit_value": {
|
|
"type": "object",
|
|
"required": [
|
|
"new_deposit"
|
|
],
|
|
"properties": {
|
|
"new_deposit": {
|
|
"$ref": "#/definitions/Coin"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "Set (or overwrite) a reduced deposit price for a specific address. Only callable by the contract admin.",
|
|
"type": "object",
|
|
"required": [
|
|
"set_reduced_deposit_price"
|
|
],
|
|
"properties": {
|
|
"set_reduced_deposit_price": {
|
|
"type": "object",
|
|
"required": [
|
|
"address",
|
|
"deposit"
|
|
],
|
|
"properties": {
|
|
"address": {
|
|
"type": "string"
|
|
},
|
|
"deposit": {
|
|
"$ref": "#/definitions/Coin"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "Remove the reduced deposit price for a specific address, reverting them to the default price. Returns an error if the address has no custom price set. Only callable by the contract admin.",
|
|
"type": "object",
|
|
"required": [
|
|
"remove_reduced_deposit_price"
|
|
],
|
|
"properties": {
|
|
"remove_reduced_deposit_price": {
|
|
"type": "object",
|
|
"required": [
|
|
"address"
|
|
],
|
|
"properties": {
|
|
"address": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "**Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`. Storage, reply handler, and helper paths exist but are unreachable from the public ExecuteMsg surface. Preserved for the redesign.",
|
|
"type": "object",
|
|
"required": [
|
|
"propose_to_blacklist"
|
|
],
|
|
"properties": {
|
|
"propose_to_blacklist": {
|
|
"type": "object",
|
|
"required": [
|
|
"public_key"
|
|
],
|
|
"properties": {
|
|
"public_key": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
{
|
|
"description": "*Stubbed**: always returns `EcashContractError::UnimplementedBlacklisting`.",
|
|
"type": "object",
|
|
"required": [
|
|
"add_to_blacklist"
|
|
],
|
|
"properties": {
|
|
"add_to_blacklist": {
|
|
"type": "object",
|
|
"required": [
|
|
"public_key"
|
|
],
|
|
"properties": {
|
|
"public_key": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
}
|
|
],
|
|
"definitions": {
|
|
"Coin": {
|
|
"type": "object",
|
|
"required": [
|
|
"amount",
|
|
"denom"
|
|
],
|
|
"properties": {
|
|
"amount": {
|
|
"$ref": "#/definitions/Uint128"
|
|
},
|
|
"denom": {
|
|
"type": "string"
|
|
}
|
|
},
|
|
"additionalProperties": false
|
|
},
|
|
"Uint128": {
|
|
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
|
|
"type": "string"
|
|
}
|
|
}
|
|
}
|