diff --git a/OVERVIEW.md b/OVERVIEW.md index a27b66d7df..56809c08da 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -38,7 +38,6 @@ OPP uses a universal chain-agnostic address system: - **ChainKind** enum: `WIRE`, `EVM`, `SVM` - **ChainAddress**: `(ChainKind, raw_bytes_address)` — no translation or mapping needed -- **ChainSignature**: `(actor, key_type, signature_bytes)` — supports native chain signatures This allows a single unified message format across all supported chains. diff --git a/contracts/sysio.authex/include/sysio.authex/sysio.authex.hpp b/contracts/sysio.authex/include/sysio.authex/sysio.authex.hpp index ad3d88e875..785ed6aa64 100644 --- a/contracts/sysio.authex/include/sysio.authex/sysio.authex.hpp +++ b/contracts/sysio.authex/include/sysio.authex/sysio.authex.hpp @@ -51,7 +51,7 @@ namespace sysio { * data. It avoids visually ambiguous characters, making it suitable for * financial identifiers, cryptocurrency addresses, and similar contexts. * - * Defined `inline` in the header so cross-contract callers (e.g. sysio.msgch + * Defined `inline` in the header so cross-contract callers (e.g. sysio.synd * resolving an authex link by pubkey) can call it without linking against * sysio.authex.cpp's WASM. * @@ -93,7 +93,7 @@ namespace sysio { * @brief Render a public_key into its canonical "PUB__" string. * * Mirrors eosjs-ecc's spelling so that off-chain signers, the authex contract, - * and any cross-contract caller (e.g. sysio.msgch's bypubkey lookup) all hash + * and any cross-contract caller (e.g. sysio.synd's bypubkey lookup) all hash * the same byte sequence into the secondary-index key. * * Supported variants: diff --git a/contracts/sysio.authex/src/sysio.authex.cpp b/contracts/sysio.authex/src/sysio.authex.cpp index 24661ff3bc..112c381cdc 100644 --- a/contracts/sysio.authex/src/sysio.authex.cpp +++ b/contracts/sysio.authex/src/sysio.authex.cpp @@ -260,7 +260,7 @@ namespace sysio { // external wallet can hold several WireNodes NFTs and therefore legitimately back several Wire // accounts, so one ETH key -> many accounts is allowed on this path. The operator path // (createlink) keeps its 1:1 `bypubkey` check, which also guarantees an operator's link is the - // lowest-primary-key row for that key; sysio.msgch's resolve_account_from_op_address does a single + // lowest-primary-key row for that key; sysio.synd's linked_account does a single // `bypubkey.find()` (lowest-primary-key match), so it still lands on the operator's account rather // than a later node-owner duplicate. auto existing = by_namechain.find(to_namechain_key(account, chain_kind)); diff --git a/contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp b/contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp index 82f7a543fd..f0d447dc58 100644 --- a/contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp +++ b/contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp @@ -39,27 +39,22 @@ namespace sysio { * Grouped into one struct so `regchain` keeps a readable signature and * `setoutpost` replaces the whole set atomically. Encoding by kind: * * `EVM` — each field is a distinct `0x`-prefixed 20-byte hex address: - * the OPP contract, the OPPInbound contract, the OperatorRegistry - * (the `uw_commit` target), and the contract that emits the source - * swap-deposit event scanned by the underwriter's verify path. + * the OPP contract and the OPPInbound contract. * * `SVM` — `opp_addr` is the outpost program id (base58). The single - * program serves every role, so the other three MUST be empty; the - * daemons substitute `opp_addr` wherever they need one of them. + * program receives inbound envelopes too, so `opp_inbound_addr` MUST be + * empty. * * `WIRE` — all empty; the depot self-row has no remote deployment. * * Fields may be empty at registration (the remote contract is not deployed - * yet) and filled in later via `setoutpost`. Both operator daemons fail - * closed: a chain whose address they need but do not have is skipped, so an + * yet) and filled in later via `setoutpost`. The batch-operator relay fails + * closed: a chain whose address it needs but does not have is skipped, so an * unconfigured row never rides on another chain's deployment. */ struct outpost_addrs { std::string opp_addr; std::string opp_inbound_addr; - std::string operator_registry_addr; - std::string source_deposit_addr; - SYSLIB_SERIALIZE(outpost_addrs, - (opp_addr)(opp_inbound_addr)(operator_registry_addr)(source_deposit_addr)) + SYSLIB_SERIALIZE(outpost_addrs, (opp_addr)(opp_inbound_addr)) }; class [[sysio::contract("sysio.chains")]] chains : public contract { @@ -136,8 +131,8 @@ namespace sysio { uint64_t registered_at_ms = 0; uint64_t activated_at_ms = 0; /// Remote outpost contract identities for this chain — see {@link outpost_addrs}. - /// Read by batch_operator_plugin and underwriter_plugin; every operator - /// therefore relays through the same deployment without per-node config. + /// Read by batch_operator_plugin; every operator therefore relays through + /// the same deployment without per-node config. outpost_addrs outpost; uint64_t by_kind() const { return magic_enum::enum_integer(kind); } diff --git a/contracts/sysio.chains/src/sysio.chains.cpp b/contracts/sysio.chains/src/sysio.chains.cpp index 4e3ba03d84..9e352c8a66 100644 --- a/contracts/sysio.chains/src/sysio.chains.cpp +++ b/contracts/sysio.chains/src/sysio.chains.cpp @@ -35,14 +35,14 @@ void require_priv_caller() { // --------------------------------------------------------------------------- // Remote-address format validation // -// These addresses are consensus facts: every batch operator and underwriter -// reads the same row, so one malformed value breaks relay for the whole +// These addresses are consensus facts: every batch operator reads the same +// row, so one malformed value breaks relay for the whole // network rather than for a single misconfigured node. Validate the format at // the ingress boundary, where the caller can still fix it. // // Empty is allowed -- a chain may be registered before its remote contracts -// are deployed and filled in later via `setoutpost`; both daemons fail closed -// and skip a row whose address they need but do not have. The exception is a +// are deployed and filled in later via `setoutpost`; the batch-operator relay +// fails closed and skips a row whose address it needs but does not have. The exception is a // field that is structurally meaningless for the kind, which must be empty. // --------------------------------------------------------------------------- @@ -93,8 +93,7 @@ void check_empty(const std::string& addr, const char* label, const char* why) { /// must match the kind's format; structurally-unused fields must be empty. void validate_outpost_addrs(opp::types::ChainKind kind, const outpost_addrs& o) { // Applies to every kind, including ones with no format rule yet. - for (const auto* f : {&o.opp_addr, &o.opp_inbound_addr, - &o.operator_registry_addr, &o.source_deposit_addr}) { + for (const auto* f : {&o.opp_addr, &o.opp_inbound_addr}) { sysio::check(f->size() <= ADDR_MAX_BYTES, "sysio.chains: outpost address exceeds " + std::to_string(ADDR_MAX_BYTES) + " bytes"); @@ -103,26 +102,20 @@ void validate_outpost_addrs(opp::types::ChainKind kind, const outpost_addrs& o) switch (kind) { case opp::types::CHAIN_KIND_WIRE: { constexpr auto why = "the WIRE depot self-row has no remote deployment"; - check_empty(o.opp_addr, "opp_addr", why); - check_empty(o.opp_inbound_addr, "opp_inbound_addr", why); - check_empty(o.operator_registry_addr, "operator_registry_addr", why); - check_empty(o.source_deposit_addr, "source_deposit_addr", why); + check_empty(o.opp_addr, "opp_addr", why); + check_empty(o.opp_inbound_addr, "opp_inbound_addr", why); break; } case opp::types::CHAIN_KIND_EVM: // Each role is its own contract on an EVM chain. - if (!o.opp_addr.empty()) check_evm_addr(o.opp_addr, "opp_addr"); - if (!o.opp_inbound_addr.empty()) check_evm_addr(o.opp_inbound_addr, "opp_inbound_addr"); - if (!o.operator_registry_addr.empty()) check_evm_addr(o.operator_registry_addr, "operator_registry_addr"); - if (!o.source_deposit_addr.empty()) check_evm_addr(o.source_deposit_addr, "source_deposit_addr"); + if (!o.opp_addr.empty()) check_evm_addr(o.opp_addr, "opp_addr"); + if (!o.opp_inbound_addr.empty()) check_evm_addr(o.opp_inbound_addr, "opp_inbound_addr"); break; case opp::types::CHAIN_KIND_SVM: { - // One program serves every role; the daemons substitute opp_addr. + // One program sends and receives envelopes, named by opp_addr. constexpr auto why = "an SVM outpost is a single program, named by opp_addr"; if (!o.opp_addr.empty()) check_svm_addr(o.opp_addr, "opp_addr"); - check_empty(o.opp_inbound_addr, "opp_inbound_addr", why); - check_empty(o.operator_registry_addr, "operator_registry_addr", why); - check_empty(o.source_deposit_addr, "source_deposit_addr", why); + check_empty(o.opp_inbound_addr, "opp_inbound_addr", why); break; } default: diff --git a/contracts/sysio.chains/sysio.chains.abi b/contracts/sysio.chains/sysio.chains.abi index c86cb25143..835e92a863 100644 --- a/contracts/sysio.chains/sysio.chains.abi +++ b/contracts/sysio.chains/sysio.chains.abi @@ -80,14 +80,6 @@ { "name": "opp_inbound_addr", "type": "string" - }, - { - "name": "operator_registry_addr", - "type": "string" - }, - { - "name": "source_deposit_addr", - "type": "string" } ] }, diff --git a/contracts/sysio.chains/sysio.chains.wasm b/contracts/sysio.chains/sysio.chains.wasm index 4602f13292..4e118d1e5b 100755 Binary files a/contracts/sysio.chains/sysio.chains.wasm and b/contracts/sysio.chains/sysio.chains.wasm differ diff --git a/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp b/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp index 0850d96710..01e800c732 100644 --- a/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp +++ b/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp @@ -79,38 +79,6 @@ namespace sysio { [[sysio::action]] void chkdispute(uint64_t dispute_id); - // ----------------------------------------------------------------------- - // Underwriter-fault challenge (WIRE-297) - // ----------------------------------------------------------------------- - // - // The OPP envelope dispute's sibling. Same adjudication machinery — Tier-1 electorate - // snapshotted at open, fixed quorum floor(N/2)+1, record-only votes, permissionless tally - // crank, resolution through the `slashop` -> `opreg::slash` chokepoint — with the - // differences the problem forces: - // - // * A human FILES it (the depot can observe envelope divergence itself; a source-chain - // fault it cannot — someone must allege it), so filing is permissionless and priced - // with a challenger bond. - // * The ballot is a VERDICT (uphold / reject), not a choice among candidate versions. - // * It never pauses the epoch: an unresolved challenge is survivable — the challenged - // collateral lock simply lapses back to a normal release — so the chain keeps advancing. - // * The vote deadline is the commitment's own collateral-lock expiry, and there is NO - // after-deadline relaxed tally: a challenge "must be voted on before the window - // expires" (Jonathan, 2026-07-27); past it the challenge LAPSES with a full bond - // refund. Envelope disputes relax after their deadline only because a paused chain - // MUST eventually resolve. - - /// The fault a challenger alleges against the winning underwriter's commit. The council - /// adjudicates the allegation against source-chain state (not visible to the depot) and - /// votes; the enum classifies the case for the audit row and indexers. - - /// A Tier-1 voter's ballot in an underwriter challenge. The two reject flavours let the - /// council separate an honest mistake (bond refunded to the challenger) from a frivolous or - /// malicious challenge (bond forfeited to the wrongly-challenged underwriter) — forfeiture - /// only ever happens by explicit council judgment, never by default. - - /// Terminal verdict of a challenge (NONE while it is OPEN). - // ----------------------------------------------------------------------- // Tables // ----------------------------------------------------------------------- @@ -196,37 +164,6 @@ namespace sysio { using chalgstate_t = sysio::kv::global<"chalgstate"_n, chalg_state>; - /// Challenge primary key (auto-incrementing id). - - /// An underwriter-fault challenge. Opened permissionlessly against one CONFIRMED - /// commitment; resolved by a Tier-1 vote or lapsed at the lock window's end. The row - /// survives resolution as the guard that a commitment is challenged at most once, ever - /// (mirrors one-dispute-per-(outpost, epoch)) and as the verdict record. - /// - /// It does NOT survive at full size: `chkuwchal` compacts it to a fixed-width tombstone on - /// resolution, clearing the two variable-length fields (`detail`, `electorate`) and erasing - /// the challenge's ballot rows. Filing is permissionless and the bond comes back on every - /// non-forfeit outcome, so recycled bond capital could otherwise pin unbounded - /// caller-controlled bytes in RAM billed to `sysio`; compacting bounds the retained - /// variable-length state by the number of CONCURRENTLY OPEN challenges — each backed by a - /// live lock set and an escrowed bond — rather than by every challenge ever filed. The full - /// filing and every ballot stay permanently readable in the action-trace history that - /// indexers consume. - - /// Challenge-vote primary key (the voting Tier-1 owner). The vote table is scoped by - /// `chal_id`, so the owner alone is unique within a challenge. - - /// One Tier-1 ballot in an underwriter challenge. Scoped by `chal_id`. Live only while the - /// challenge is OPEN — it is the tally input and the one-vote-per-owner gate, and neither - /// applies to a resolved challenge, so `chkuwchal` erases the scope on resolution. - - /// Claimable-bond primary key (the account the payout is owed to). - - /// WIRE this contract holds on an account's behalf out of a resolved challenge's escrow. - /// `chkuwchal` credits it; `claimbond` pays it out and erases the row. Credits ACCUMULATE - /// per account, so the table is bounded by the number of accounts with an unclaimed payout - /// — not by the number of challenges ever resolved. - private: // Well-known accounts static constexpr name EPOCH_ACCOUNT = "sysio.epoch"_n; diff --git a/contracts/sysio.chalg/src/sysio.chalg.cpp b/contracts/sysio.chalg/src/sysio.chalg.cpp index 0924d72d2f..5dbf245e40 100644 --- a/contracts/sysio.chalg/src/sysio.chalg.cpp +++ b/contracts/sysio.chalg/src/sysio.chalg.cpp @@ -52,10 +52,9 @@ void chalg::slashop(name operator_acct, std::string reason) { check(has_auth(get_self()) || has_auth(EPOCH_ACCOUNT), "slashop requires sysio.chalg or sysio.epoch authority"); - // Slash via sysio.opreg — the canonical bond ledger. opreg routes the slashable portion - // (`balance - sum(active locks)`) to the matching LP on each (chain, token_kind) the operator has - // bond on, marks the operator SLASHED, and lets sysio.uwrit::release deferred-slash the locked - // portion as each underwriter lock resolves. + // Slash via sysio.opreg — the canonical bond ledger. opreg removes every collateral balance the + // operator holds, keeps the custody with the registry, marks the operator SLASHED, and records + // one OPERATOR_ACTION(SLASH) audit entry per (chain, token) balance. action( permission_level{get_self(), "active"_n}, OPREG_ACCOUNT, @@ -172,9 +171,8 @@ void chalg::votedispute(name owner, uint64_t dispute_id, checksum256 chosen_chec void chalg::chkdispute(uint64_t dispute_id) { // Permissionless crank, driven by `batch_operator_plugin`'s epoch tick // (`--batch-epoch-poll-ms`, 15s default) from every ACTIVE batch operator. - // That cadence is this action's ONLY driver: unlike `chkuwchal`, which - // `sysio.uwrit::chklocks` pokes from every `sysio.epoch::advance`, a dispute - // pauses `advance` itself, so no inline poke can reach here. + // That cadence is this action's ONLY driver: a dispute pauses + // `sysio.epoch::advance` itself, so no inline poke from the epoch cycle can reach here. disputes_t disputes(get_self()); auto d_pk = dispute_key{dispute_id}; auto d = disputes.get(d_pk, "dispute not found"); diff --git a/contracts/sysio.epoch/src/sysio.epoch.cpp b/contracts/sysio.epoch/src/sysio.epoch.cpp index fa82d89db2..4a1f484300 100644 --- a/contracts/sysio.epoch/src/sysio.epoch.cpp +++ b/contracts/sysio.epoch/src/sysio.epoch.cpp @@ -676,10 +676,10 @@ void epoch::advance() { // Drain matured rows from `sysio.opreg::wtdwqueue`. Operators that queued // a withdrawal at least WITHDRAW_WAIT_EPOCHS ago are now eligible — opreg - // subtracts from the balance and emits OPERATOR_ACTION(WITHDRAW_REMIT) to - // the matching outpost (or, for WIRE-direct withdraws, CREDITS the operator's - // `sysio.opreg::remitclaims` row, which it pulls with `claimremit` — nothing - // is transferred from this path, precisely because it runs inline from here). + // subtracts from the balance, records a WITHDRAW_REMIT entry in the + // operator's audit log, and CREDITS the operator's `sysio.opreg::remitclaims` + // row, which it pulls with `claimremit` — nothing is transferred from this + // path, precisely because it runs inline from here. No attestation is sent. // Slashed-during-the-wait rows are dropped silently inside // opreg's flushwtdw. See CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md §3.3. action( diff --git a/contracts/sysio.epoch/sysio.epoch.wasm b/contracts/sysio.epoch/sysio.epoch.wasm index 2cfc67510d..1b342a95c7 100755 Binary files a/contracts/sysio.epoch/sysio.epoch.wasm and b/contracts/sysio.epoch/sysio.epoch.wasm differ diff --git a/contracts/sysio.liq/sysio.liq.wasm b/contracts/sysio.liq/sysio.liq.wasm index a3871235ca..d3fa4ed931 100755 Binary files a/contracts/sysio.liq/sysio.liq.wasm and b/contracts/sysio.liq/sysio.liq.wasm differ diff --git a/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp b/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp index dfdd17c2d1..4e6253925b 100644 --- a/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp +++ b/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp @@ -67,16 +67,11 @@ namespace sysio { /// targeted depot → outpost envelopes: /// * `sysio.epoch::advance` — `OPERATORS`, `BATCH_OPERATOR_GROUPS` /// fanout to every active outpost. - /// * `sysio.reserv::matchreserve` — `RESERVE_READY` to the - /// reserve's owning outpost (chain_code). - /// * `sysio.reserv::oncnclrsv` — `RESERVE_CREATE_CANCELLED` to - /// the reserve's owning outpost on race-win cancel. - /// * `sysio.opreg::*` — `OPERATOR_ACTION` family (WITHDRAW_REMIT, - /// SLASH) — once the reserve-flow work lands the same pattern - /// reaches every depot-authorised outbound. + /// * `sysio.synd::desyndicate` — `DESYNDICATE_LIQ` releases from + /// the destination's syndicated LIQ custody. /// - /// Gated to the depot's own system contracts (sysio.epoch / .opreg / - /// .uwrit / .reserv, plus msgch itself): each sends under its own + /// Gated to sysio.epoch, sysio.opreg, sysio.synd, and msgch itself: + /// each sends under its own /// {self, active} authority. The gate is required because a forged /// READY attestation rides out inside the next group-signed outbound /// envelope, which the outpost authenticates by the group signature — @@ -295,11 +290,7 @@ namespace sysio { /// also carry `status = PROCESSED` (they go straight to dispatch), /// so the cleanup drains both. Once the atts table is empty, /// `available_primary_key()` resets to 0, our `std::max(1, ...)` - /// floor bumps it to 1 — and the next inbound `SwapRequest` gets - /// the SAME attestation_id as a prior phase. The downstream - /// `sysio.uwrit::createuwreq` idempotency guard - /// (`reqs.contains(pk)`) then short-circuits the second phase, - /// silently dropping the new swap. + /// floor bumps it to 1, reusing a prior attestation's audit identity. /// /// `att_seq` is a one-row table holding `next` — the next id to /// mint. `mint_att_id()` reads + bumps it atomically. Cleanup @@ -350,7 +341,6 @@ namespace sysio { private: using ChainKind = opp::types::ChainKind; - using ChainRequestStatus = opp::types::ChainRequestStatus; using MessageDirection = opp::types::MessageDirection; using MessageStatus = opp::types::MessageStatus; using EnvelopeStatus = opp::types::EnvelopeStatus; diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index c9dc4172ed..29151e45f1 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include // public_key / ecc_public_key for node-owner key validation #include #include // active-liq-token gate on the SYNDICATE_LIQ / LIQ_YIELD paths #include // dispute trigger + open-dispute gate (disputes table) @@ -37,7 +37,6 @@ constexpr auto EPOCH_ACCOUNT = "sysio.epoch"_n; constexpr auto OPREG_ACCOUNT = "sysio.opreg"_n; constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; -constexpr auto AUTHEX_ACCOUNT = "sysio.authex"_n; constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; constexpr auto TOKENS_ACCOUNT = "sysio.tokens"_n; @@ -139,10 +138,8 @@ constexpr const char* DISPUTE_NO_TIER_ONE_ELECTORATE_LOG = /// at every `attestations_t` insertion site. The `attseq` singleton survives the /// `buildenv` cleanup of `ATTESTATION_STATUS_PROCESSED` rows, so the /// monotonic counter keeps advancing across phases even when the atts -/// table is drained. Without this, Phase N+1's inbound `SwapRequest` -/// inherits Phase 1's attestation_id and collides with the existing UWREQ -/// row in `sysio.uwrit` — `createuwreq`'s idempotency guard then -/// silently drops the new swap. +/// table is drained. Reusing an erased row's id would give a later attestation +/// the same identity as an earlier audit record. /// /// First call materialises the row at `next = 2` and returns `1`. /// Subsequent calls return the current `next` and post-increment. @@ -286,39 +283,21 @@ void prune_expired_envelopes(name self, uint32_t current_epoch) { } } -/// Resolve `op_address` (chain-kind + raw pubkey bytes) to the operator's -/// WIRE account name via `sysio.authex::links`'s `bypubkey` index. Returns -/// `name{}` (zero) on miss — caller treats that as "operator not linked, -/// drop the attestation". -name resolve_account_from_op_address(const opp::types::ChainAddress& op_address) { - // No key a link could hold (unsupported kind, wrong width) is a miss, never a hash: - // `pubkey_to_checksum256` aborts on anything but an EM / ED variant. - const auto pk = public_key_from_op_address(op_address.kind, op_address.address); - if (!pk) return name{}; - auto digest = sysio::pubkey_to_checksum256(*pk); - sysio::authex::links_t links(AUTHEX_ACCOUNT); - auto by_pubkey = links.get_index<"bypubkey"_n>(); - auto it = by_pubkey.find(digest); - if (it == by_pubkey.end()) return name{}; - return it->username; -} - /// Inbound source-chain binding check (WSA-005). /// /// A consensus-reached envelope is delivered for exactly ONE proven source outpost: /// `proven_chain_code`, which `deliver` validated as an active, non-depot row on /// `sysio.chains` before the envelope reached consensus. Every value-bearing attestation the /// envelope carries ALSO embeds its own chain identifier in the decoded payload -/// (`OperatorAction.chain_code`, `UnderwriteIntentCommit.chain_code`, -/// `ReserveAmount.chain_code`, `ReserveCreateCancel.chain_code`). That embedded identifier MUST +/// (`SyndicateLIQ.chain_code`, `LIQYield.chain_code`). That embedded identifier MUST /// equal the proven source outpost: in the OPP model an attestation about chain X is always relayed /// by outpost X, as exercised by the active intake paths in `sysio.dispatch_tests`. /// /// A mismatch means an envelope proven from outpost A is asking the depot to apply a value-bearing /// effect on a different chain B. Batch-operator consensus proves only that A's operators agree on /// the envelope bytes; it does NOT prove the depositor acted on B. Applying the effect anyway would -/// let a compromised or malicious A-operator quorum drive deposits, withdrawals, swaps, underwrite -/// value-bearing mutations against an unrelated chain B's ledger — the +/// let a compromised or malicious A-operator quorum mutate an unrelated chain B's +/// syndication ledger — the /// cross-chain provenance forgery WSA-005 describes. /// /// Per `feedback_opp_handlers_never_throw`, dispatch must never abort the envelope on a single bad @@ -338,33 +317,6 @@ name resolve_account_from_op_address(const opp::types::ChainAddress& op_address) return false; } -/// Are a payload's FORGEABLE code fields canonical slug_names? -/// -/// `chain_code` is proven — `source_chain_binding_ok` binds it to the delivering -/// outpost. `token_code` / `reserve_code` are NOT: they arrive as raw protobuf -/// uint64s and reach a slug_name through the non-validating raw constructor, so a -/// forged payload can carry a value that does not round-trip through its spelling. -/// Such a value can never have been registered, and rendering is total, so it still -/// produces text: text that either FAILS validation on the way back, or silently -/// re-parses as a DIFFERENT, real code. -/// -/// Drop the attestation instead; never check(), per -/// feedback_opp_handlers_never_throw — a check() here halts evalcons and stalls -/// consensus. -/// -/// `path` labels the dispatch path in the diagnostic. True iff every code is canonical. -[[nodiscard]] bool payload_codes_canonical(std::initializer_list codes, - const char* path) { - for (const sysio::slug_name code : codes) { - if (!code.is_canonical()) { - sysio::print("msgch::", path, ": DROP attestation -- payload code ", code.value, - " has no canonical slug_name spelling\n"); - return false; - } - } - return true; -} - /// The syndicating user's key family must be the proven outpost's own: an outpost of family F /// verifies and emits F-family keys only, so a key of another family is a forgery whatever it /// resolves to, before `sysio.synd` holds any of it. Print + false, never a throw, for the reason @@ -692,48 +644,24 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) /// Per-attestation dispatch entry. Called from the inbound extraction loop /// in `evalcons` after a consensus envelope has been unpacked. Dispatch is /// best-effort — silently no-ops on unknown / out-of-scope types so the -/// inbound stream can keep flowing even when the depot hasn't yet wired up -/// every handler (e.g. the deferred STAKE / UNSTAKE / STAKE_UPDATE staking -/// lifecycle types). +/// inbound stream keeps flowing past a value this depot does not handle. /// /// `epoch_index` and `envelope_digest` identify the accepted envelope the attestation came in; the /// syndication paths forward them to `sysio.synd`. Returns true iff the attestation was routed to /// `sysio.synd` as syndication value (`onsynd` or `onyield` sent), which obliges the caller to close /// the envelope there once its last attestation is dispatched. -[[nodiscard]] bool dispatch_attestation(name self, uint64_t attestation_id, +[[nodiscard]] bool dispatch_attestation(name self, AttestationType type, const std::vector& data, uint64_t chain_code, - const checksum256& original_message_id, uint32_t epoch_index, const checksum256& envelope_digest) { bool carried_syndication_value = false; switch (type) { case AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION: - case AttestationType::ATTESTATION_TYPE_SWAP_REQUEST: - case AttestationType::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT: - // Retired external collateral and dual-commit swap ingress. - break; - - case AttestationType::ATTESTATION_TYPE_SWAP_REMIT: - // Depot → outpost outbound-only. No outpost ever echoes a - // SwapRemit back (verified: ETH `_handleSwapRemit` and SOL - // `handle_swap_remit` only pay the recipient + emit local - // events; failure is SWAP_REJECTED). The old "reflected remit - // = delivery ack → uwrit::release" dispatch here was dead code: - // success is implicit absent SWAP_REJECTED, and underwriter - // locks are released exclusively by the wall-clock challenge - // window sweep (`sysio.uwrit::chklocks` at epoch advance). A - // misbehaving outpost relaying one inbound is a benign no-op. + // Inbound echoes of the native collateral audit are inert. break; - // ATTESTATION_TYPE_SWAP_REJECTED was dispatched here → sysio.reserv::onreject. - // REMOVED: outposts no longer echo a rejection. Every REMIT is depot-initiated - // against a verified reserve ledger, so the destination outpost can always pay - // (or log+skips locally on a misconfig) — there is no post-underwriting rejection - // and no reserve-ledger reconciliation. The retired type (enum slot 60957) no - // longer exists; any stray inbound falls through to the default drop below. - case AttestationType::ATTESTATION_TYPE_SYNDICATE_LIQ: carried_syndication_value = dispatch_syndicate_liq(self, data, chain_code, epoch_index, envelope_digest); break; @@ -747,45 +675,8 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) // outpost echoing one inbound is a benign no-op. break; - case AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCEL: - // Retired external reserve lifecycle. - break; - - case AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED: - case AttestationType::ATTESTATION_TYPE_RESERVE_READY: - // Depot → outpost outbound-only. Should never appear inbound at - // the depot; if a misbehaving outpost relays one back it is a - // benign no-op. Silently drop per `feedback_opp_handlers_never_throw`. - break; - - case AttestationType::ATTESTATION_TYPE_RESERVE_BALANCE_SHEET: - // Per-epoch sanity check from the outpost. The depot is the - // ground truth; this is informational. Decode and emit a - // diagnostic event but do not auto-mutate the reserve — drift - // detection / alerting belongs to off-chain monitors that - // tail the chain log. Falling through silently is also - // acceptable today; the row is persisted in `attestations` - // for post-hoc inspection. - break; - - case AttestationType::ATTESTATION_TYPE_CHALLENGE_REQUEST: - case AttestationType::ATTESTATION_TYPE_CHALLENGE_RESPONSE: - // No depot-side handler. Envelope disputes are resolved on the WIRE side by evalcons - // opening a sysio.chalg dispute vote, not by inbound challenge attestations. - break; - - case AttestationType::ATTESTATION_TYPE_STAKE: - case AttestationType::ATTESTATION_TYPE_UNSTAKE: - case AttestationType::ATTESTATION_TYPE_STAKE_UPDATE: - case AttestationType::ATTESTATION_TYPE_STAKE_RESULT: - // Validator-staking lifecycle; depot-side handlers land in a later - // task alongside liqEth / liqsol-token wiring. - break; - // Outbound-only types (depot emits these, never receives them inbound) are dropped silently; // an outpost relaying one back is a benign no-op. - case AttestationType::ATTESTATION_TYPE_SWAP_REVERT: - case AttestationType::ATTESTATION_TYPE_DEPOSIT_REVERT: case AttestationType::ATTESTATION_TYPE_OPERATORS: case AttestationType::ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS: break; @@ -796,10 +687,6 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) dispatch_node_owner_reg(data, chain_code); break; - case AttestationType::ATTESTATION_TYPE_PRETOKEN_PURCHASE: - case AttestationType::ATTESTATION_TYPE_PRETOKEN_YIELD: - case AttestationType::ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE: - case AttestationType::ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR: case AttestationType::ATTESTATION_TYPE_UNSPECIFIED: default: break; @@ -967,16 +854,7 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) .ready_timestamp = now_sec, .processed_timestamp = now_sec, }); - // Reconstruct the OPP message_id as a checksum256 for downstream correlation. - checksum256 m_id; - { - auto& mid = msg.header.message_id; - std::array rawid{}; - const size_t n = std::min(mid.size(), 32); - for (size_t i = 0; i < n; ++i) rawid[i] = static_cast(mid[i]); - m_id = checksum256{rawid}; - } - if (dispatch_attestation(self, att_id, entry.type, entry.data, chain_code, m_id, epoch_index, + if (dispatch_attestation(self, entry.type, entry.data, chain_code, epoch_index, envelope_digest)) { carried_syndication_value = true; } @@ -1566,9 +1444,9 @@ void msgch::queueout(uint64_t chain_code, // Authorization gate: only the depot's own system contracts may queue an outbound // attestation. queueout carries no ABI-level auth, so without this check ANY account could // call it directly and inject a forged attestation that buildenv() then packs into the - // depot's group-signed outbound envelope — a forged SWAP_REMIT / WITHDRAW_REMIT / SLASH that - // the outpost authenticates by the group signature and executes. The intended callers - // (sysio.epoch / .opreg / .uwrit / .reserv / .synd) each send under their own {self, active} + // depot's group-signed outbound envelope — a forged DESYNDICATE_LIQ that + // the outpost authenticates by the group signature and executes. The authorized callers + // (sysio.epoch / .opreg / .synd) each send under their own {self, active} // authority; get_self() permits msgch's own inline use and governance. check(has_auth(EPOCH_ACCOUNT) || has_auth(OPREG_ACCOUNT) || has_auth(synd::account) || has_auth(get_self()), @@ -1577,7 +1455,7 @@ void msgch::queueout(uint64_t chain_code, // The chains registry is the ONLY authority on which chain codes exist. // // This is not an attack gate -- the authorization check above is. It is an - // ops gate: all eight production call sites derive their code from + // ops gate: production call sites derive their code from // sysio.chains, but a governance msig holding one of those authorities can // call queueout directly, and a typo'd code used to abort here. Without it // the row is created READY and is then unreachable forever: epoch::advance diff --git a/contracts/sysio.msgch/sysio.msgch.abi b/contracts/sysio.msgch/sysio.msgch.abi index b513a2158a..91c63abca7 100644 --- a/contracts/sysio.msgch/sysio.msgch.abi +++ b/contracts/sysio.msgch/sysio.msgch.abi @@ -577,50 +577,6 @@ "name": "ATTESTATION_TYPE_OPERATOR_ACTION", "value": 2001 }, - { - "name": "ATTESTATION_TYPE_STAKE", - "value": 3001 - }, - { - "name": "ATTESTATION_TYPE_UNSTAKE", - "value": 3002 - }, - { - "name": "ATTESTATION_TYPE_PRETOKEN_PURCHASE", - "value": 3004 - }, - { - "name": "ATTESTATION_TYPE_PRETOKEN_YIELD", - "value": 3006 - }, - { - "name": "ATTESTATION_TYPE_RESERVE_BALANCE_SHEET", - "value": 43520 - }, - { - "name": "ATTESTATION_TYPE_STAKE_UPDATE", - "value": 60928 - }, - { - "name": "ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE", - "value": 60930 - }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_RESPONSE", - "value": 60932 - }, - { - "name": "ATTESTATION_TYPE_SWAP_REQUEST", - "value": 60934 - }, - { - "name": "ATTESTATION_TYPE_SWAP_REMIT", - "value": 60944 - }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_REQUEST", - "value": 60945 - }, { "name": "ATTESTATION_TYPE_OPERATORS", "value": 60947 @@ -633,42 +589,6 @@ "name": "ATTESTATION_TYPE_NODE_OWNER_REG", "value": 60949 }, - { - "name": "ATTESTATION_TYPE_STAKE_RESULT", - "value": 60951 - }, - { - "name": "ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR", - "value": 60952 - }, - { - "name": "ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT", - "value": 60953 - }, - { - "name": "ATTESTATION_TYPE_SWAP_REVERT", - "value": 60955 - }, - { - "name": "ATTESTATION_TYPE_DEPOSIT_REVERT", - "value": 60956 - }, - { - "name": "ATTESTATION_TYPE_RESERVE_CREATE_CANCEL", - "value": 60959 - }, - { - "name": "ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED", - "value": 60960 - }, - { - "name": "ATTESTATION_TYPE_RESERVE_READY", - "value": 60961 - }, - { - "name": "ATTESTATION_TYPE_EMISSIONS_BLOCKED", - "value": 60962 - }, { "name": "ATTESTATION_TYPE_SYNDICATE_LIQ", "value": 60963 diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 938bb5137a..e5e9b295dd 100755 Binary files a/contracts/sysio.msgch/sysio.msgch.wasm and b/contracts/sysio.msgch/sysio.msgch.wasm differ diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/amm_math.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/amm_math.hpp index 3e6fe58420..96f0d995e9 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/amm_math.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/amm_math.hpp @@ -3,9 +3,8 @@ * @file amm_math.hpp * @brief Deterministic weighted constant-product (Bancor/Balancer) swap math. * - * Shared by `sysio.reserv` (reserve books + `swapquote`) and `sysio.uwrit` - * (the quote mirror) so the depot's books and its quotes ride exactly one - * curve. The math is **integer-only and self-contained** (no contract + * Used by `sysio.swap`, whose pair quotes (`out_given_in` at equal weights) + * and pair fees (`split_wire_fee`) both come from this one kernel. The math is **integer-only and self-contained** (no contract * intrinsics, no floating point): it is unit-testable on the host and * deterministic on-chain, satisfying the consensus-determinism rules in * CLAUDE.md (no floating point on the consensus path). @@ -228,7 +227,7 @@ inline constexpr uint32_t BPS_TOTAL = 10000; /// Decomposition of EVERY fee taken out of a swap's WIRE leg. /// /// Two independent fees ride the same leg (WIRE-281): the NETWORK fee -/// (`sysio.uwrit::fee_bps`, split between the winning underwriter, batch +/// (`fee_bps`, split between the winning underwriter, batch /// operators and optionally the emissions treasury) and each participating /// RESERVE's own owner fee. A chain-to-chain swap therefore pays three fees — /// two reserve owners plus the network — while a WIRE-endpoint swap touches one @@ -266,28 +265,24 @@ struct wire_fee { /// the leg `fee` is clamped to `wire_amount` and the shares sum to MORE than it /// (see the clamp note below). Computed in `u128` to avoid overflow. /// -/// **Callers must check `net > 0`.** Stacked rates can reach or exceed the leg, -/// and that is REACHABLE UNDER VALID CONFIGURATION rather than merely -/// theoretical: the caps bound each rate INDEPENDENTLY, so a network fee at -/// `sysio.uwrit::MAX_FEE_BPS` (9999) plus a single reserve owner fee at -/// `sysio.reserv::MIN_OWNER_FEE_BPS` (1) already totals exactly 100%. A -/// zero-`net` result is therefore an intentionally REJECTED CONFIGURATION that -/// every caller must handle — not unreachable defense-in-depth. +/// **Callers must check `net > 0`.** Stacked rates can reach or exceed the leg +/// even when each rate is individually below 100%: nothing here bounds their +/// SUM, so a 9999 bps network fee plus a single 1 bps reserve owner fee already +/// totals exactly 100%. A zero-`net` result is therefore a REJECTED +/// CONFIGURATION that every caller must handle — not unreachable +/// defense-in-depth. /// /// When the total reaches the leg, `fee` is CLAMPED to `wire_amount` and `net` /// is 0 — so in THAT case alone the per-share fields sum to more than `fee`, -/// which is harmless because no share is ever accrued: the swap is refused -/// before any accrual. `quote_swap` returns 0 (no quote), and `applyswap` / -/// `applyfromwire` / `refundwire` assert `net > 0` directly. `paywire` is the -/// exception worth knowing: it pays the caller's `wire_out` target rather than -/// `net`, so it never reads the field — it fails closed upstream on the zero -/// quote, and locally on its `reserve_wire_amount >= wire_out + fee` -/// sufficiency check. +/// which is harmless as long as the caller refuses the swap before accruing any +/// share. `quote_swap` returns 0 (no quote). `sysio.swap` keeps its pair fee +/// below 100% (`MAX_FEE`, 9999) and passes no reserve owner fee, so its quotes +/// always net at least one unit. /// -/// Paths with no winning underwriter (a revert refund) pass 0 for +/// A caller with no underwriter (every `sysio.swap` pair) passes 0 for /// `underwriter_share_bps`, sending the whole network fee into the rewards pool. /// The trailing rates default to 0, so a caller that charges only the network -/// fee — a revert, or a quote against a fee-free reserve — omits them. +/// fee omits them. inline wire_fee split_wire_fee(uint64_t wire_amount, uint32_t fee_bps, uint32_t underwriter_share_bps, diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp index fea838cbc1..bdd449d026 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/opp_table_types.hpp @@ -102,16 +102,6 @@ DataStream& operator>>(DataStream& ds, ChainAddress& t) { return ds >> t.kind >> t.address; } -// ChainSignature: { ChainAddress actor; ChainKeyType key_type; vector signature; } -template -DataStream& operator<<(DataStream& ds, const ChainSignature& t) { - return ds << t.actor << t.key_type << t.signature; -} -template -DataStream& operator>>(DataStream& ds, ChainSignature& t) { - return ds >> t.actor >> t.key_type >> t.signature; -} - // WireAccount: { string name; } template DataStream& operator<<(DataStream& ds, const WireAccount& t) { @@ -122,79 +112,6 @@ DataStream& operator>>(DataStream& ds, WireAccount& t) { return ds >> t.name; } -// WirePermission: { WireAccount account; string permission; } -template -DataStream& operator<<(DataStream& ds, const WirePermission& t) { - return ds << t.account << t.permission; -} -template -DataStream& operator>>(DataStream& ds, WirePermission& t) { - return ds >> t.account >> t.permission; -} - -// --------------------------------------------------------------------------- -// Registry-entity messages (Chain / Token / ChainToken / Reserve / ReserveAmount) -// --------------------------------------------------------------------------- - -template -DataStream& operator<<(DataStream& ds, const Chain& t) { - return ds << t.kind << t.code << t.external_chain_id << t.name << t.description - << t.is_depot << t.active << t.registered_at_ms << t.activated_at_ms; -} -template -DataStream& operator>>(DataStream& ds, Chain& t) { - return ds >> t.kind >> t.code >> t.external_chain_id >> t.name >> t.description - >> t.is_depot >> t.active >> t.registered_at_ms >> t.activated_at_ms; -} - -template -DataStream& operator<<(DataStream& ds, const Token& t) { - return ds << t.kind << t.code << t.symbol_name << t.description << t.precision - << t.address << t.active << t.registered_at_ms << t.activated_at_ms; -} -template -DataStream& operator>>(DataStream& ds, Token& t) { - return ds >> t.kind >> t.code >> t.symbol_name >> t.description >> t.precision - >> t.address >> t.active >> t.registered_at_ms >> t.activated_at_ms; -} - -template -DataStream& operator<<(DataStream& ds, const ChainToken& t) { - return ds << t.chain_code << t.token_code << t.contract_addr - << t.is_native << t.active << t.registered_at_ms << t.activated_at_ms; -} -template -DataStream& operator>>(DataStream& ds, ChainToken& t) { - return ds >> t.chain_code >> t.token_code >> t.contract_addr - >> t.is_native >> t.active >> t.registered_at_ms >> t.activated_at_ms; -} - -template -DataStream& operator<<(DataStream& ds, const Reserve& t) { - return ds << t.chain_code << t.token_code << t.code << t.name << t.description - << t.status << t.reserve_chain_amount << t.reserve_wire_amount - << t.connector_weight_bps << t.creator_addr - << t.requested_wire_amount << t.external_token_amount - << t.registered_at_ms << t.activated_at_ms << t.cancelled_at_ms; -} -template -DataStream& operator>>(DataStream& ds, Reserve& t) { - return ds >> t.chain_code >> t.token_code >> t.code >> t.name >> t.description - >> t.status >> t.reserve_chain_amount >> t.reserve_wire_amount - >> t.connector_weight_bps >> t.creator_addr - >> t.requested_wire_amount >> t.external_token_amount - >> t.registered_at_ms >> t.activated_at_ms >> t.cancelled_at_ms; -} - -template -DataStream& operator<<(DataStream& ds, const ReserveAmount& t) { - return ds << t.chain_code << t.reserve_code << t.amount; -} -template -DataStream& operator>>(DataStream& ds, ReserveAmount& t) { - return ds >> t.chain_code >> t.reserve_code >> t.amount; -} - } // namespace sysio::opp::types // ───────────────────────────────────────────────────────────────────────────── @@ -286,69 +203,7 @@ DataStream& operator>>(DataStream& ds, Envelope& t) { // ───────────────────────────────────────────────────────────────────────────── namespace sysio::opp::attestations { -// ReserveBalanceSheet (renamed from ChainReserveBalanceSheet; the -// parallel amounts[]/reserve_codes[] arrays were replaced by -// self-describing depot-frame `ReserveAmount` entries). -template -DataStream& operator<<(DataStream& ds, const ReserveBalanceSheet& t) { - return ds << t.chain_code << t.reserves; -} -template -DataStream& operator>>(DataStream& ds, ReserveBalanceSheet& t) { - return ds >> t.chain_code >> t.reserves; -} - -// PretokenStakeChange (deprecated; pre-launch only) -template -DataStream& operator<<(DataStream& ds, const PretokenStakeChange& t) { - return ds << t.actor << t.amount << t.index_at_mint << t.index_at_burn; -} -template -DataStream& operator>>(DataStream& ds, PretokenStakeChange& t) { - return ds >> t.actor >> t.amount >> t.index_at_mint >> t.index_at_burn; -} - -// PretokenPurchase (deprecated; pre-launch only) -template -DataStream& operator<<(DataStream& ds, const PretokenPurchase& t) { - return ds << t.actor << t.amount << t.pretoken_count << t.index_at_mint; -} -template -DataStream& operator>>(DataStream& ds, PretokenPurchase& t) { - return ds >> t.actor >> t.amount >> t.pretoken_count >> t.index_at_mint; -} - -// PretokenYield (deprecated; pre-launch only) -template -DataStream& operator<<(DataStream& ds, const PretokenYield& t) { - return ds << t.actor << t.amount << t.index_at_mint; -} -template -DataStream& operator>>(DataStream& ds, PretokenYield& t) { - return ds >> t.actor >> t.amount >> t.index_at_mint; -} - -// StakeUpdate -template -DataStream& operator<<(DataStream& ds, const StakeUpdate& t) { - return ds << t.actor << t.status << t.amount; -} -template -DataStream& operator>>(DataStream& ds, StakeUpdate& t) { - return ds >> t.actor >> t.status >> t.amount; -} - -// WireTokenPurchase -template -DataStream& operator<<(DataStream& ds, const WireTokenPurchase& t) { - return ds << t.actor << t.amounts; -} -template -DataStream& operator>>(DataStream& ds, WireTokenPurchase& t) { - return ds >> t.actor >> t.amounts; -} - -// OperatorAction — chain_code (codename uint64), and SLASH carries reserve_code. +// OperatorAction — chain_code (codename uint64); reserve_code is historical and left zero. template DataStream& operator<<(DataStream& ds, const OperatorAction& t) { return ds << t.action_type << t.op_address << t.type << t.status @@ -370,121 +225,6 @@ DataStream& operator>>(DataStream& ds, OperatorActionLog& t) { return ds >> t.action >> t.success >> t.timestamp >> t.error_message; } -// ReserveDisbursement -template -DataStream& operator<<(DataStream& ds, const ReserveDisbursement& t) { - return ds << t.actor << t.amount << t.signature; -} -template -DataStream& operator>>(DataStream& ds, ReserveDisbursement& t) { - return ds >> t.actor >> t.amount >> t.signature; -} - -// ProtocolState — chain_code (codename uint64) replaces ChainId chain_id. -template -DataStream& operator<<(DataStream& ds, const ProtocolState& t) { - return ds << t.chain_code << t.current_message_id << t.processed_message_id - << t.incoming_messages << t.outgoing_messages; -} -template -DataStream& operator>>(DataStream& ds, ProtocolState& t) { - return ds >> t.chain_code >> t.current_message_id >> t.processed_message_id - >> t.incoming_messages >> t.outgoing_messages; -} - -// SwapRequest — full codename triples for source + target. -template -DataStream& operator<<(DataStream& ds, const SwapRequest& t) { - return ds << t.actor << t.source_amount - << t.source_chain_code << t.source_reserve_code - << t.target_chain_code << t.target_token_code << t.target_reserve_code - << t.recipient << t.target_amount - << t.target_tolerance_bps << t.target_timestamp_ms - << t.source_tx_id; -} -template -DataStream& operator>>(DataStream& ds, SwapRequest& t) { - return ds >> t.actor >> t.source_amount - >> t.source_chain_code >> t.source_reserve_code - >> t.target_chain_code >> t.target_token_code >> t.target_reserve_code - >> t.recipient >> t.target_amount - >> t.target_tolerance_bps >> t.target_timestamp_ms - >> t.source_tx_id; -} - -// UnderwriteIntentCommit — (token_code, chain_code, reserve_code) triple -// disambiguates same-chain swap legs. Field order MUST match the generated proto struct -// (attestations.proto): uw_account, uw_ext_chain_addr, uw_request_id, signature, -// token_code, chain_code, reserve_code. (The legacy v5 `outpost_id` field — a per-outpost -// numeric id redundant with `chain_code` and read by no one — was removed in SEC-13/WSA-027.) -// These operators are currently unreached (UIC always travels as raw zpp::bits proto bytes). -template -DataStream& operator<<(DataStream& ds, const UnderwriteIntentCommit& t) { - return ds << t.uw_account << t.uw_ext_chain_addr << t.uw_request_id - << t.signature - << t.token_code << t.chain_code << t.reserve_code; -} -template -DataStream& operator>>(DataStream& ds, UnderwriteIntentCommit& t) { - return ds >> t.uw_account >> t.uw_ext_chain_addr >> t.uw_request_id - >> t.signature - >> t.token_code >> t.chain_code >> t.reserve_code; -} - -// SwapRevert — adds source_chain_code + source_reserve_code. -template -DataStream& operator<<(DataStream& ds, const SwapRevert& t) { - return ds << t.original_swap_message_id << t.depositor - << t.refund_amount << t.reason - << t.source_chain_code << t.source_reserve_code; -} -template -DataStream& operator>>(DataStream& ds, SwapRevert& t) { - return ds >> t.original_swap_message_id >> t.depositor - >> t.refund_amount >> t.reason - >> t.source_chain_code >> t.source_reserve_code; -} - -// SwapRemit — adds chain_code + reserve_code (destination identity). -template -DataStream& operator<<(DataStream& ds, const SwapRemit& t) { - return ds << t.recipient << t.amount << t.original_message_id - << t.underwriter << t.unlock_timestamp - << t.chain_code << t.reserve_code; -} -template -DataStream& operator>>(DataStream& ds, SwapRemit& t) { - return ds >> t.recipient >> t.amount >> t.original_message_id - >> t.underwriter >> t.unlock_timestamp - >> t.chain_code >> t.reserve_code; -} - -// SwapRejected DataStream operators removed — the SwapRejected message no longer -// exists (no post-underwriting rejection path; every REMIT is depot-initiated). - -// ChallengeOperatorHash — field name `operator_` (trailing underscore) because -// `operator` is a C++ keyword. -template -DataStream& operator<<(DataStream& ds, const ChallengeOperatorHash& t) { - return ds << t.operator_ << t.chain_hash; -} -template -DataStream& operator>>(DataStream& ds, ChallengeOperatorHash& t) { - return ds >> t.operator_ >> t.chain_hash; -} - -// ChallengeRequest -template -DataStream& operator<<(DataStream& ds, const ChallengeRequest& t) { - return ds << t.epoch_index << t.round << t.original_chain_hash - << t.operator_hashes; -} -template -DataStream& operator>>(DataStream& ds, ChallengeRequest& t) { - return ds >> t.epoch_index >> t.round >> t.original_chain_hash - >> t.operator_hashes; -} - // OperatorEntry — one row of the OPERATORS attestation roster. template DataStream& operator<<(DataStream& ds, const OperatorEntry& t) { @@ -525,103 +265,4 @@ DataStream& operator>>(DataStream& ds, BatchOperatorGroups& t) { return ds >> t.active_group_index >> t.epoch_index >> t.groups; } -// ReserveAmount — (chain_code, reserve_code, TokenAmount), depot-frame. -// (NOTE: ReserveAmount lives in `sysio::opp::types` per types.proto; -// the DataStream overloads are above in the types namespace.) - -// DepositRevert — adds chain_code. -template -DataStream& operator<<(DataStream& ds, const DepositRevert& t) { - return ds << t.original_deposit_message_id << t.depositor - << t.refund_amount << t.reason << t.chain_code; -} -template -DataStream& operator>>(DataStream& ds, DepositRevert& t) { - return ds >> t.original_deposit_message_id >> t.depositor - >> t.refund_amount >> t.reason >> t.chain_code; -} - -// NodeOwnerReg -template -DataStream& operator<<(DataStream& ds, const NodeOwnerReg& t) { - return ds << t.owner_address << t.token_id << t.nft_address; -} -template -DataStream& operator>>(DataStream& ds, NodeOwnerReg& t) { - return ds >> t.owner_address >> t.token_id >> t.nft_address; -} - -// --------------------------------------------------------------------------- -// Reserve-flow attestations -// --------------------------------------------------------------------------- - -template -DataStream& operator<<(DataStream& ds, const ReserveCreateCancel& t) { - return ds << t.chain_code << t.token_code << t.reserve_code << t.creator_addr; -} -template -DataStream& operator>>(DataStream& ds, ReserveCreateCancel& t) { - return ds >> t.chain_code >> t.token_code >> t.reserve_code >> t.creator_addr; -} - -template -DataStream& operator<<(DataStream& ds, const ReserveCreateCancelled& t) { - return ds << t.chain_code << t.token_code << t.reserve_code; -} -template -DataStream& operator>>(DataStream& ds, ReserveCreateCancelled& t) { - return ds >> t.chain_code >> t.token_code >> t.reserve_code; -} - -template -DataStream& operator<<(DataStream& ds, const ReserveReady& t) { - return ds << t.chain_code << t.token_code << t.reserve_code; -} -template -DataStream& operator>>(DataStream& ds, ReserveReady& t) { - return ds >> t.chain_code >> t.token_code >> t.reserve_code; -} - -// StakeResult -template -DataStream& operator<<(DataStream& ds, const StakeResult& t) { - return ds << t.owner_address << t.amount << t.success << t.error_reason; -} -template -DataStream& operator>>(DataStream& ds, StakeResult& t) { - return ds >> t.owner_address >> t.amount >> t.success >> t.error_reason; -} - -// AttestationProcessingError -template -DataStream& operator<<(DataStream& ds, const AttestationProcessingError& t) { - return ds << t.attestation_id << t.original_type << t.original_data - << t.error_message; -} -template -DataStream& operator>>(DataStream& ds, AttestationProcessingError& t) { - return ds >> t.attestation_id >> t.original_type >> t.original_data - >> t.error_message; -} - } // namespace sysio::opp::attestations - -// ───────────────────────────────────────────────────────────────────────────── -// Contract-local types with SYSLIB_SERIALIZE for multi_index table storage -// ───────────────────────────────────────────────────────────────────────────── -namespace sysio::opp_table { - -/// Locked amount entry for underwrite requests (uwreqs table). -/// Uses zpp protobuf types directly with CDT serialization via the -/// DataStream operators defined above. -struct locked_amount_t { - opp::types::ChainId chain_id; - opp::types::TokenAmount amount; - uint128_t lock_id = 0; - uint64_t lock_timestamp = 0; - - SYSLIB_SERIALIZE(locked_amount_t, - (chain_id)(amount)(lock_id)(lock_timestamp)) -}; - -} // namespace sysio::opp_table diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/registry_metadata.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/registry_metadata.hpp index 32abeaf4d7..1075271f3a 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/registry_metadata.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/registry_metadata.hpp @@ -3,10 +3,10 @@ * @file registry_metadata.hpp * @brief Byte bounds for the human-readable metadata every depot registry row carries. * - * The depot's three system-owned registries -- `sysio.chains::chains`, - * `sysio.tokens::tokens`, and `sysio.reserv::reserves` -- each persist two + * The depot's system-owned registries -- `sysio.chains::chains` and + * `sysio.tokens::tokens` -- each persist two * free-form strings alongside their typed columns: a short display LABEL - * (`Chain.name`, `Token.symbol_name`, `Reserve.name`) and a longer + * (`Chain.name`, `Token.symbol_name`) and a longer * DESCRIPTION. Both land in chain state rather than transient action data. * * Those rows bill to `ram_payer = "sysio"` -- the shared system pool -- so an @@ -16,8 +16,8 @@ * business-appropriate size, inflating state and making the registries expensive * to inspect. Every registration action bounds both strings before `emplace`. * - * The limits live here, once, so the three registries cannot drift apart, and - * so raising a bound is a single reviewed edit rather than three. The precedent + * The limits live here, once, so the registries cannot drift apart, and + * so raising a bound is a single reviewed edit rather than one per registry. The precedent * for bounding a persisted contract string is `sysio.token::issue`'s * `memo.size() <= 256`; `description_max_bytes` matches it deliberately. */ @@ -31,12 +31,11 @@ namespace sysio::opp::registry { /// Maximum byte length of a registry row's short display label -- -/// `Chain.name`, `Token.symbol_name`, `Reserve.name`. +/// `Chain.name`, `Token.symbol_name`. /// /// The bound exists to stop a row consuming up to the KV/action ceiling of /// system-paid state; it is NOT a house style for terse names. Real labels run -/// well past ticker length -- a reserve names its full leg -/// ("ETHEREUM-ETH/WIRE unlinked-creator reserve" is 42 bytes) -- so the bound +/// well past ticker length for descriptive chain and token names, so the bound /// is set with room for descriptive names rather than at the current longest. /// A label that has to be shortened to satisfy this is a sign the bound is /// wrong, not the label. diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/require_privileged.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/require_privileged.hpp index b5c921b1fc..8a5aa0a689 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/require_privileged.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/require_privileged.hpp @@ -19,8 +19,7 @@ inline constexpr std::string_view PRIVILEGED_REQUIRED_SUFFIX = ": privileged acc /// Refuse unless the contract now executing (`current_receiver()`) signed the action and is /// privileged: the gate of the launch-ingestion and registry-seeding actions a system contract runs -/// under its own authority (`sysio.liq::regliqpool`, `sysio.synd::importsynd`, -/// `sysio.reserv::regreserve`). The refusal names the contract: `: privileged account +/// under its own authority (`sysio.liq::regliqpool`, `sysio.synd::importsynd`). The refusal names the contract: `: privileged account /// required`. The ONE such gate; every system contract that needs it calls this. inline void require_privileged_self() { const name self = current_receiver(); diff --git a/contracts/sysio.opp.common/include/sysio.opp.common/safe_ops.hpp b/contracts/sysio.opp.common/include/sysio.opp.common/safe_ops.hpp index 29437d1deb..c058cafe34 100644 --- a/contracts/sysio.opp.common/include/sysio.opp.common/safe_ops.hpp +++ b/contracts/sysio.opp.common/include/sysio.opp.common/safe_ops.hpp @@ -15,9 +15,8 @@ * a wrap corrupts `>=` sufficiency guards and can let a later `asset()` / * `subtract_balance` abort slip through into settlement. * - * The helpers below were previously copied into individual contracts - * (`sysio.reserv`'s `add_capped_u64`). They live here so every OPP contract - * saturates the same way — one place to change, one place to audit. + * The helpers below live here so every OPP contract saturates the same way — + * one place to change, one place to audit. * * Name VALIDATION is deliberately not here: it belongs to `sysio::name` itself * (`name::is_valid_literal`, the predicate its own constructor uses). A copy in diff --git a/contracts/sysio.opreg/src/sysio.opreg.cpp b/contracts/sysio.opreg/src/sysio.opreg.cpp index 78ca771f85..a829b18495 100644 --- a/contracts/sysio.opreg/src/sysio.opreg.cpp +++ b/contracts/sysio.opreg/src/sysio.opreg.cpp @@ -716,9 +716,8 @@ namespace { /// Look up `account`'s registered public key for `chain_code` from /// `sysio.authex::links` (`bynamechain` index) and pack it into a /// `ChainAddress`. Returns `{UNKNOWN, []}` when the chain isn't registered -/// or no authex link exists — the downstream outpost / depot lookup then -/// fails gracefully (the depot's `dispatch_operator_action` rejects empty -/// `op_address.address`). +/// or no authex link exists; the OPERATOR_ACTION audit record then carries an +/// empty `op_address`. /// /// After the refactor: `authex::links.bynamechain` is still keyed by `(name, ChainKind)` /// and `ChainAddress.kind` is still `ChainKind`. opreg now stores chains by @@ -742,8 +741,7 @@ opp::types::ChainAddress operator_chain_address(name account, sysio::slug_name c /// Build the `OperatorAction(action_type=SLASH)` payload for a given /// (account, chain_code, token_code) slash. Returns the OperatorAction -/// ready for either logging on the operator's row or queueing as an -/// outbound OPERATOR_ACTION attestation. Pure — no side effects. +/// ready for logging on the operator's row. Pure — no side effects. /// /// This payload records a depot-local slash in the operator audit trail. OperatorAction build_slash_action(name account, @@ -772,8 +770,7 @@ OperatorAction build_slash_action(name account, /// /// Caller passes the operator's primary key + the OperatorAction payload /// (DEPOSIT_REQUEST / WITHDRAW_REQUEST / WITHDRAW_REMIT / SLASH) plus the -/// outcome. No-op if the operator entry doesn't exist (unknown-operator -/// path handles its own audit via DEPOSIT_REVERT outbound). +/// outcome. No-op if the operator entry does not exist. void append_action_log(opreg::operators_t& ops, const opreg::operator_key& op_pk, const OperatorAction& action, @@ -1289,9 +1286,10 @@ void opreg::slash(name account, std::string reason) { } }); - // Emit one OPERATOR_ACTION(SLASH) per (chain_code, token_code) with non-zero - // slashable, AND append each as a recent_actions log entry on the - // operator's row (success=true since the slash itself was applied). + // Record one SLASH entry per (chain_code, token_code) with non-zero slashable + // in the operator's recent_actions log (success=true since the slash itself + // was applied). Nothing is sent to an outpost: they learn the SLASHED status + // from the next OPERATORS roster. for (const auto& sp : to_slash) { auto slash_action = build_slash_action(op.account, op.type, sp.chain_code, sp.token_code, sp.amount, diff --git a/contracts/sysio.opreg/sysio.opreg.wasm b/contracts/sysio.opreg/sysio.opreg.wasm index 4663042a2f..5ac8c63139 100755 Binary files a/contracts/sysio.opreg/sysio.opreg.wasm and b/contracts/sysio.opreg/sysio.opreg.wasm differ diff --git a/contracts/sysio.synd/sysio.synd.wasm b/contracts/sysio.synd/sysio.synd.wasm index 2c0201caa7..80cbff714f 100755 Binary files a/contracts/sysio.synd/sysio.synd.wasm and b/contracts/sysio.synd/sysio.synd.wasm differ diff --git a/contracts/sysio.system/EMISSIONS.md b/contracts/sysio.system/EMISSIONS.md index 19b64eac6d..d23fbed159 100644 --- a/contracts/sysio.system/EMISSIONS.md +++ b/contracts/sysio.system/EMISSIONS.md @@ -77,9 +77,8 @@ by the full group size). Equal rosters are coalesced before their weighted slice is calculated, preserving one group-level rounding step per roster. A credit is made only for members that are opreg-ACTIVE, so the slices of skipped (inactive / slashed / terminated) members stay in the treasury rather than being redistributed -to the active ones. Batch operators are paid from emission only; no swap-fee -pool is folded into the distribution, so `producer_bps` / `batch_op_bps` split -the whole compute share. +to the active ones. Batch operators are paid from emission only, so +`producer_bps` / `batch_op_bps` split the whole compute share. Allocated is not the same as paid: only **eligible** shares are actually credited. Emission WIRE stays in the treasury when an **empty historical @@ -130,8 +129,6 @@ retained, making recovery distinguishable from an ordinary zero-eligible payout. cadence values greater than one retain the configured number of audit records. `epochlog.batch_emission_retained` records undistributed batch emission. -`epochlog.fee_distributed` and `epochlog.batch_fee_retained` are reserved -fields that `payepoch` always writes as zero; no swap-fee sweep feeds them. ## Retrieved via a claim action (pulled by recipient) diff --git a/contracts/sysio.system/include/sysio.system/emissions.hpp b/contracts/sysio.system/include/sysio.system/emissions.hpp index 6210fb4220..686af15537 100644 --- a/contracts/sysio.system/include/sysio.system/emissions.hpp +++ b/contracts/sysio.system/include/sysio.system/emissions.hpp @@ -476,27 +476,17 @@ struct [[sysio::table("epochlog"), sysio::contract("sysio.system")]] epoch_log { int64_t compute_amount = 0; int64_t capex_amount = 0; int64_t governance_amount = 0; - // Swap-fee rewards (sysio.reserv rewards_bucket) actually distributed to - // batch operators this period, ON TOP of the emission above. Sourced from - // collected swap fees, not the T5 treasury, so it is NOT included in - // total_emission / total_distributed. Excludes the underwriter half of every - // fee, which accrues in sysio.reserv and is claimed there — it never reaches - // this treasury and so never appears in this log. - int64_t fee_distributed = 0; // Durable attribution for WIRE left in the treasury by the batch payout. - // Retained emission includes incomplete-history recovery; retained swept - // fees arise only after a non-empty roster enabled the sweep, from another - // empty roster, inactive members, or integer-division remainders. Incomplete - // and all-empty history leave fees in sysio.reserv. + // Retained emission includes incomplete-history recovery, empty rosters, + // inactive members, and integer-division remainders. // history_complete distinguishes recovery from eligibility shortfalls. bool batch_history_complete = false; int64_t batch_emission_retained = 0; - int64_t batch_fee_retained = 0; SYSLIB_SERIALIZE(epoch_log, (sysio_epoch_index)(epoch_count)(timestamp)(total_emission) - (compute_amount)(capex_amount)(governance_amount)(fee_distributed) - (batch_history_complete)(batch_emission_retained)(batch_fee_retained)) + (compute_amount)(capex_amount)(governance_amount) + (batch_history_complete)(batch_emission_retained)) }; using epochlog_t = sysio::kv::table<"epochlog"_n, epochlog_key, epoch_log>; diff --git a/contracts/sysio.system/include/sysio.system/producer_rank.hpp b/contracts/sysio.system/include/sysio.system/producer_rank.hpp index 13c053723b..1c366a49df 100644 --- a/contracts/sysio.system/include/sysio.system/producer_rank.hpp +++ b/contracts/sysio.system/include/sysio.system/producer_rank.hpp @@ -12,7 +12,7 @@ // Producer ranking -- the packed sort key and the governance-tunable weights behind it. // -// This header is deliberately LIGHT: it carries no sysio.opreg / sysio.uwrit dependency, so +// This header is deliberately LIGHT: it carries no sysio.opreg dependency, so // sysio.system.hpp can include it for `producer_info::rank_score`'s default. The factors that read // the operator registry live in producer_score.hpp, included only by the translation units that // actually compute a score -- the same split opreg_status.hpp uses for the same reason. diff --git a/contracts/sysio.system/include/sysio.system/producer_score.hpp b/contracts/sysio.system/include/sysio.system/producer_score.hpp index a8fd8f65a7..3c698359d1 100644 --- a/contracts/sysio.system/include/sysio.system/producer_score.hpp +++ b/contracts/sysio.system/include/sysio.system/producer_score.hpp @@ -31,9 +31,8 @@ namespace sysiosystem { * opreg's `available()`: available() subtracts pending withdraws, and withdraw / cancelwtdw are * both free, uncapped and cooldown-free, so an operator could oscillate their own rank without * moving funds. Score tracks what can be taken from you, and a queued withdraw does not reduce - * exposure. Nothing is subtracted for sysio.uwrit locks: an account holds exactly one operator - * row of one type, and only ACTIVE underwriters ever carry locks, so a PRODUCER's whole balance - * is slashable. + * exposure. `sysio.opreg::slash` removes the whole balance, so a PRODUCER's whole balance is + * what it puts at stake. * * @param op the operator row read from sysio.opreg. * @param chain_code the chain slug of the pair. diff --git a/contracts/sysio.system/include/sysio.system/sysio.system.hpp b/contracts/sysio.system/include/sysio.system/sysio.system.hpp index bbd6430527..2c45b17ed3 100644 --- a/contracts/sysio.system/include/sysio.system/sysio.system.hpp +++ b/contracts/sysio.system/include/sysio.system/sysio.system.hpp @@ -730,10 +730,9 @@ namespace sysiosystem { * ACTUAL accrued-epoch count, rather than configured * pay_cadence_epochs, because a mid-period config change or shortened * genesis period can make those differ. Incomplete history retains the - * batch-emission slice in treasury, leaves swap fees in sysio.reserv - * for a later payable period, and is reset for the next period. A - * complete but all-empty history also leaves swap fees in sysio.reserv; - * neither condition may halt sysio.epoch::advance. + * batch-emission slice in treasury and is reset for the next period; + * neither it nor a complete but all-empty history may halt + * sysio.epoch::advance. * * Runtime conditions (config missing, treasury exhausted, balance * insufficient) are caught upstream by the gate, which records the diff --git a/contracts/sysio.system/src/emissions.cpp b/contracts/sysio.system/src/emissions.cpp index 9c04942a36..5faec53d92 100644 --- a/contracts/sysio.system/src/emissions.cpp +++ b/contracts/sysio.system/src/emissions.cpp @@ -687,19 +687,10 @@ void system_contract::rcrdbatch(uint32_t epoch_index, std::vector m // The unallocated remainder (period_emission - compute - capex - governance) // stays in sysio's balance. LIQ yield draws no treasury bonus. // -// Swap-fee rewards: when immutable roster history is complete and contains at -// least one non-empty roster, the batch-operator share of collected swap fees -// (sysio.reserv's rewards_bucket) is swept here via an inline drainrewards and -// allocated EXCLUSIVELY to the batch-operator distribution, on top of their -// emission share and weighted by the same historical-roster active-epoch count. -// Incomplete or all-empty history leaves the bucket in sysio.reserv for a later -// payable period. -// Producers are NOT paid out of swap fees, so producer_bps / batch_op_bps govern -// the emission split only -- see the fold-in comment at the drain. Allocated is -// not paid: only ELIGIBLE shares go out, and any skipped amount from a completed -// sweep stays in this treasury. The audit row records retained batch emission, -// retained swept fees, and whether roster history was complete. Fees are funded -// by the sweep (not the treasury) and so are excluded from total_distributed. +// The batch-operator slice is weighted by the immutable historical-roster +// active-epoch count. Allocated is not paid: only ELIGIBLE shares go out, and any +// skipped amount stays in this treasury. The audit row records retained batch +// emission and whether roster history was complete. // // Single-trx semantics guarantee gate conditions hold through this call; // payepoch trusts the gate-computed period_emission and does not recompute. @@ -955,9 +946,6 @@ void system_contract::payepoch(uint32_t epoch_index, // one subunit. Correcting it took a second pass that could overdraw the pool. const uint64_t slot_divisor = std::max(std::max(nominal_slots, produced_blocks), 1); - // Producers are paid the emission share only — swap fees go to the - // underwriter + batch operators (see the fold-in comment above). - // // A row's blocks are cleared ONLY when the block portion actually credited something. The // division is integer, so a small pool over a large divisor can round a real block count to // zero pay; clearing the count then would destroy work the producer did, which is the one @@ -1040,9 +1028,9 @@ void system_contract::payepoch(uint32_t epoch_index, } } else if (accrued_epochs > 0) { // Do not guess a roster during a mixed-version upgrade or an incomplete - // first period: retain its batch emission in the treasury, leave swap fees - // in sysio.reserv, and let the next period establish complete history. - sysio::print("batch roster history incomplete; retaining batch emission and deferring swap fees\n"); + // first period: retain its batch emission in the treasury and let the + // next period establish complete history. + sysio::print("batch roster history incomplete; retaining batch emission\n"); } const int64_t batch_emission_retained = batch_pool - batch_emission_paid; @@ -1110,10 +1098,8 @@ void system_contract::payepoch(uint32_t epoch_index, t5s.set(state, get_self()); - // Audit log: records the AUTHORIZED period emission + the four category - // amounts for the period that just paid, plus the swap-fee rewards folded - // into the batch-operator distribution (fee_distributed, sourced from swap - // fees rather than the treasury). (Producer / batch-op sub-distribution is + // Audit log: records the AUTHORIZED period emission + the three category + // amounts for the period that just paid. (Producer / batch-op sub-distribution is // implicit. Those shares are CREDITED to `payclaims` rather than transferred, so a // recipient no longer appears as its own transfer trace -- per-recipient attribution // comes from the `payclaims` row deltas, and the eventual `claimpay`.) One row per @@ -1127,10 +1113,8 @@ void system_contract::payepoch(uint32_t epoch_index, .compute_amount = compute_amount, .capex_amount = capex_amount, .governance_amount = governance_amount, - .fee_distributed = 0, // Reserved legacy audit field. .batch_history_complete = batch_history_complete, .batch_emission_retained = batch_emission_retained, - .batch_fee_retained = 0, // Reserved legacy audit field. }); // Head-first prune of the audit log past its retention cap. Rows are added diff --git a/contracts/sysio.system/sysio.system.abi b/contracts/sysio.system/sysio.system.abi index 129dcb6a49..278617fbd3 100644 --- a/contracts/sysio.system/sysio.system.abi +++ b/contracts/sysio.system/sysio.system.abi @@ -540,10 +540,6 @@ "name": "governance_amount", "type": "int64" }, - { - "name": "fee_distributed", - "type": "int64" - }, { "name": "batch_history_complete", "type": "bool" @@ -551,10 +547,6 @@ { "name": "batch_emission_retained", "type": "int64" - }, - { - "name": "batch_fee_retained", - "type": "int64" } ] }, diff --git a/contracts/sysio.system/sysio.system.wasm b/contracts/sysio.system/sysio.system.wasm index 07311b89e7..f3b7dac791 100755 Binary files a/contracts/sysio.system/sysio.system.wasm and b/contracts/sysio.system/sysio.system.wasm differ diff --git a/contracts/sysio.tokens/include/sysio.tokens/sysio.tokens.hpp b/contracts/sysio.tokens/include/sysio.tokens/sysio.tokens.hpp index cdf9dac363..84f6ff55ab 100644 --- a/contracts/sysio.tokens/include/sysio.tokens/sysio.tokens.hpp +++ b/contracts/sysio.tokens/include/sysio.tokens/sysio.tokens.hpp @@ -28,7 +28,7 @@ namespace sysio { * on other chains) and `is_native` (exactly one per Chain). Chain-native * decimal precision is NOT stored here — the depot's per-token precision * lives on `sysio.tokens::token_row.precision` (the single source of - * truth) and on each `sysio.reserv` reserve row; the outpost owns the + * truth); the outpost owns the * chain-native ↔ depot-frame conversion at its boundary. * * ## Lifecycle (unified bootstrap + post-bootstrap, per diff --git a/contracts/tests/amm_math_tests.cpp b/contracts/tests/amm_math_tests.cpp index 1df1ea9935..c78c9ed84a 100644 --- a/contracts/tests/amm_math_tests.cpp +++ b/contracts/tests/amm_math_tests.cpp @@ -141,8 +141,8 @@ BOOST_AUTO_TEST_CASE(convenience_wrappers) { /// WIRE leg, leaving `net == 0`. That degenerate post-fee leg is what let a /// from-WIRE / token-to-token swap debit destination reserve liquidity while /// crediting zero WIRE. Any NETWORK fee below 100% leaves a positive remainder -/// for every positive input, which is why `sysio.uwrit::setconfig` rejects -/// `fee_bps >= BPS_TOTAL`. +/// for every positive input, which is why a fee configuration must stay below +/// `BPS_TOTAL` (`sysio.swap` caps its pair fee at `MAX_FEE`, 9999). /// /// That cap does NOT make `net == 0` unconstructible at settlement, and this /// suite must not imply it does: reserve owner fees ride the same leg under a @@ -177,8 +177,8 @@ BOOST_AUTO_TEST_CASE(split_wire_fee_boundaries) { /// Stage 2 of the split: `emissions_share_bps` divides the REWARDS POOL (what is /// left after the underwriter's cut), not the whole fee. The default is 0 — the -/// whole pool is allocated to the batch-operator distribution and no fee leaves -/// `sysio.reserv` custody. +/// whole pool is allocated to the batch-operator distribution and nothing goes +/// to the emissions treasury. BOOST_AUTO_TEST_CASE(split_wire_fee_emissions_share_divides_the_rewards_pool) { constexpr uint64_t AMOUNT = 1'000'000ULL; constexpr uint32_t FEE_BPS = 1'000; // 10% -> fee 100'000 @@ -280,15 +280,13 @@ BOOST_AUTO_TEST_CASE(split_wire_fee_stacked_rates_cannot_wrap_the_total) { } /// A zero `net` is an intentionally REJECTED CONFIGURATION, not unreachable -/// defense-in-depth. The two caps bound their rates INDEPENDENTLY — nothing -/// cross-checks the sum — so the maximum network fee `sysio.uwrit::setconfig` -/// accepts plus the minimum owner fee `sysio.reserv::setrsvfee` accepts already -/// consumes the whole leg. This pins that reachability so the `net > 0` checks in -/// `applyswap` / `applyfromwire` are never mistaken for dead code again. +/// defense-in-depth. Each rate can sit below 100% on its own while their SUM +/// does not — the kernel never cross-checks it — so a 9999 bps network fee plus +/// a 1 bps owner fee already consumes the whole leg. This pins that reachability +/// so a caller's `net > 0` check is never mistaken for dead code. BOOST_AUTO_TEST_CASE(split_wire_fee_reaches_the_leg_under_valid_configuration) { - // Mirrors the on-chain caps: sysio.uwrit::MAX_FEE_BPS / sysio.reserv's - // [MIN_OWNER_FEE_BPS, MAX_OWNER_FEE_BPS]. Duplicated as literals because this - // suite tests the shared AMM kernel and does not link either contract. + // The highest network fee below 100% and the smallest nonzero owner fee; this + // suite tests the shared AMM kernel and links no contract. constexpr uint32_t MAX_FEE_BPS = 9'999; // 99.99% network fee constexpr uint32_t MIN_OWNER_FEE_BPS = 1; // 0.01% reserve owner fee diff --git a/contracts/tests/contract_test_support.hpp b/contracts/tests/contract_test_support.hpp index 3d9b8ebc48..28084d833f 100644 --- a/contracts/tests/contract_test_support.hpp +++ b/contracts/tests/contract_test_support.hpp @@ -133,35 +133,26 @@ inline fc::mutable_variant_object codename_mvo(std::string_view s) { } /// Every field empty — a chain registered before its remote contracts exist. -/// Valid for any kind; both operator daemons fail closed and skip such a row. +/// Valid for any kind; the batch-operator relay fails closed and skips such a row. inline fc::mutable_variant_object no_outpost_mvo() { return fc::mutable_variant_object() - ("opp_addr", std::string{}) - ("opp_inbound_addr", std::string{}) - ("operator_registry_addr", std::string{}) - ("source_deposit_addr", std::string{}); + ("opp_addr", std::string{}) + ("opp_inbound_addr", std::string{}); } -/// EVM form: each role is a distinct 0x-prefixed 20-byte hex contract address. -inline fc::mutable_variant_object evm_outpost_mvo(std::string_view opp, - std::string_view opp_inbound, - std::string_view operator_registry, - std::string_view source_deposit) { +/// EVM form: the OPP and OPPInbound contracts, each a 0x-prefixed 20-byte hex address. +inline fc::mutable_variant_object evm_outpost_mvo(std::string_view opp, std::string_view opp_inbound) { return fc::mutable_variant_object() - ("opp_addr", std::string{opp}) - ("opp_inbound_addr", std::string{opp_inbound}) - ("operator_registry_addr", std::string{operator_registry}) - ("source_deposit_addr", std::string{source_deposit}); + ("opp_addr", std::string{opp}) + ("opp_inbound_addr", std::string{opp_inbound}); } -/// SVM form: one base58 program id serves every role, so the other three fields -/// must stay empty — the contract rejects a set that fills them in. +/// SVM form: one base58 program id sends and receives envelopes, so +/// `opp_inbound_addr` must stay empty — the contract rejects a set that fills it in. inline fc::mutable_variant_object svm_outpost_mvo(std::string_view program_id) { return fc::mutable_variant_object() - ("opp_addr", std::string{program_id}) - ("opp_inbound_addr", std::string{}) - ("operator_registry_addr", std::string{}) - ("source_deposit_addr", std::string{}); + ("opp_addr", std::string{program_id}) + ("opp_inbound_addr", std::string{}); } /// The user-link signature domain for EM (hex compressed key) and ED (base58 diff --git a/contracts/tests/emissions_tests.cpp b/contracts/tests/emissions_tests.cpp index bb21f29eb2..322aa509c1 100644 --- a/contracts/tests/emissions_tests.cpp +++ b/contracts/tests/emissions_tests.cpp @@ -358,7 +358,7 @@ class sysio_emissions_tester : public tester { create_accounts({ OPREG, EPOCH, CHALG, MSGCH }); produce_blocks(1); - // OPREG, EPOCH and UWRIT have real contract code set below and need the + // OPREG and EPOCH have real contract code set below and need the // full RAM policy; CHALG and MSGCH are inline-target placeholders with // no code deployed. Sizing the placeholders down keeps the sixth // allocation within nodedaddy's tier-1 SYS pool. @@ -2570,12 +2570,9 @@ BOOST_FIXTURE_TEST_CASE( payepoch_recovers_from_incomplete_batch_roster_history, BOOST_REQUIRE(!log["batch_history_complete"].as_bool()); BOOST_REQUIRE_EQUAL(compute - producer_pool, log["batch_emission_retained"].as()); - BOOST_REQUIRE_EQUAL(int64_t(0), log["fee_distributed"].as()); - BOOST_REQUIRE_EQUAL(int64_t(0), log["batch_fee_retained"].as()); - // The next complete period must pick up the same retained fee bucket. This - // proves the recovery path did not merely leave it readable before silently - // clearing or replacing it on the following pay epoch. + // The next complete period pays from complete roster history again. This + // proves the recovery path reset history rather than leaving the period stuck. create_accounts({ BATCH_OP }, false, false, false, true); BOOST_REQUIRE_EQUAL(success(), register_operator(BATCH_OP, OperatorType::OPERATOR_TYPE_BATCH, @@ -4187,14 +4184,10 @@ BOOST_FIXTURE_TEST_CASE( single_active_producer_paid_per_block, sysio_emissions_ // while `setemitcfg` may change pay_cadence_epochs at any time. Normalizing by // cfg.pay_cadence_epochs instead of the accrued total made those two disagree: // cadence 3 -> 1 after one accrual leaves the counters summing to 2 against a -// divisor of 1, paying 2x batch_pool AND 2x fee_batch_pool. The surplus fee is the -// dangerous half — only ONE fee pool was swept from sysio.reserv, so the extra is -// drawn from this treasury, and fee payouts are excluded from total_distributed, -// so it never shows up against the emission curve. +// divisor of 1, paying 2x batch_pool, with the surplus drawn from this treasury. // -// Runs with an ACTIVE batch group and a NON-ZERO fee, because with either absent -// the overpayment is unobservable: no group means nothing is distributed, and a -// zero fee makes the fee half of the bug invisible. +// Runs with an ACTIVE batch group, because with none the overpayment is +// unobservable: nothing is distributed. BOOST_FIXTURE_TEST_CASE( cadence_drop_midperiod_does_not_multiply_batch_payout, sysio_emissions_tester ) try { const account_name BATCH_OP = "batchopb"_n; @@ -4241,10 +4234,6 @@ BOOST_FIXTURE_TEST_CASE( cadence_drop_midperiod_does_not_multiply_batch_payout, const int64_t got = get_wire_balance_paid(BATCH_OP).get_amount() - bal_before; BOOST_REQUIRE_EQUAL( got, batch_pool ); - // The load-bearing assertion: the fee is distributed ONCE. Under the old - // divisor this was 2 * fee_total, with the surplus drawn from the treasury. - BOOST_REQUIRE_EQUAL( log["fee_distributed"].as(), 0 ); - // And the emission side is not double-paid either: the producer's count spans both accrued // epochs and is paid once, over the two epochs' worth of slots. const int64_t capex = log["capex_amount"].as(); diff --git a/contracts/tests/sysio.chains_tests.cpp b/contracts/tests/sysio.chains_tests.cpp index 6153a2b531..412f2dad63 100644 --- a/contracts/tests/sysio.chains_tests.cpp +++ b/contracts/tests/sysio.chains_tests.cpp @@ -29,8 +29,6 @@ namespace { // Well-formed sample addresses for the accept paths. constexpr auto EVM_OPP = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; // OPP.sol constexpr auto EVM_INBOUND = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; // OPPInbound.sol -constexpr auto EVM_OPREG = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"; // OperatorRegistry.sol -constexpr auto EVM_DEPOSIT = "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9"; // SwapDeposit emitter constexpr auto SVM_PROGRAM = "So11111111111111111111111111111111111111112"; // 43-char base58 } // namespace @@ -116,33 +114,28 @@ class sysio_chains_tester : public tester { BOOST_AUTO_TEST_SUITE(sysio_chains_tests) -// ── EVM: all four role addresses are accepted and stored verbatim ── +// ── EVM: both contract addresses are accepted and stored verbatim ── BOOST_FIXTURE_TEST_CASE(regchain_evm_addresses_stored, sysio_chains_tester) { try { BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(EVM_OPP, EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT))); + evm_outpost_mvo(EVM_OPP, EVM_INBOUND))); BOOST_REQUIRE_EQUAL(std::string(EVM_OPP), stored_addr("ETH", "opp_addr")); BOOST_REQUIRE_EQUAL(std::string(EVM_INBOUND), stored_addr("ETH", "opp_inbound_addr")); - BOOST_REQUIRE_EQUAL(std::string(EVM_OPREG), stored_addr("ETH", "operator_registry_addr")); - BOOST_REQUIRE_EQUAL(std::string(EVM_DEPOSIT), stored_addr("ETH", "source_deposit_addr")); } FC_LOG_AND_RETHROW() } -// ── EVM: a malformed hex address is rejected in EVERY role, not just opp_addr ── +// ── EVM: a malformed hex address is rejected in both fields, not just opp_addr ── BOOST_FIXTURE_TEST_CASE(regchain_evm_bad_hex_rejected, sysio_chains_tester) { try { // Wrong length. BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo("0xdeadbeef", EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT)) + evm_outpost_mvo("0xdeadbeef", EVM_INBOUND)) .find("20-byte hex address") != std::string::npos); - // Non-hex character in an otherwise 42-char string (trailing 'z'), in each role. + // Non-hex character in an otherwise 42-char string (trailing 'z'), in each field. const std::string bad_hex = "0x5FbDB2315678afecb367f032d93F642f64180aaz"; BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(EVM_OPP, bad_hex, EVM_OPREG, EVM_DEPOSIT)) - .find("opp_inbound_addr contains a non-hex character") != std::string::npos); - BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(EVM_OPP, EVM_INBOUND, bad_hex, EVM_DEPOSIT)) - .find("operator_registry_addr contains a non-hex character") != std::string::npos); + evm_outpost_mvo(bad_hex, EVM_INBOUND)) + .find("opp_addr contains a non-hex character") != std::string::npos); BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(EVM_OPP, EVM_INBOUND, EVM_OPREG, bad_hex)) - .find("source_deposit_addr contains a non-hex character") != std::string::npos); + evm_outpost_mvo(EVM_OPP, bad_hex)) + .find("opp_inbound_addr contains a non-hex character") != std::string::npos); BOOST_REQUIRE(get_chain("ETH").is_null()); // nothing registered on reject } FC_LOG_AND_RETHROW() } @@ -177,18 +170,18 @@ BOOST_FIXTURE_TEST_CASE(regchain_uncanonical_code_rejected, sysio_chains_tester) BOOST_FIXTURE_TEST_CASE(regchain_evm_empty_addresses_allowed, sysio_chains_tester) { try { BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, no_outpost_mvo())); BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("ETH", "opp_addr")); - BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("ETH", "source_deposit_addr")); + BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("ETH", "opp_inbound_addr")); } FC_LOG_AND_RETHROW() } // ── EVM: an oversized string cannot reach sysio-billed state ── BOOST_FIXTURE_TEST_CASE(regchain_evm_oversized_addr_rejected, sysio_chains_tester) { try { const std::string too_long(4096, 'a'); BOOST_REQUIRE(!regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(too_long, EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT)).empty()); + evm_outpost_mvo(too_long, EVM_INBOUND)).empty()); BOOST_REQUIRE(get_chain("ETH").is_null()); } FC_LOG_AND_RETHROW() } -// ── SVM: base58 program id in opp_addr; the single program serves every role ── +// ── SVM: base58 program id in opp_addr; the single program also receives envelopes ── BOOST_FIXTURE_TEST_CASE(regchain_svm_program_id_accepted, sysio_chains_tester) { try { BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_SVM, "SOL", 900, svm_outpost_mvo(SVM_PROGRAM))); @@ -196,15 +189,10 @@ BOOST_FIXTURE_TEST_CASE(regchain_svm_program_id_accepted, sysio_chains_tester) { BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("SOL", "opp_inbound_addr")); } FC_LOG_AND_RETHROW() } -// ── SVM: every role other than opp_addr must be left empty ── -BOOST_FIXTURE_TEST_CASE(regchain_svm_other_roles_must_be_empty, sysio_chains_tester) { try { - const auto with = [&](const char* inbound, const char* opreg, const char* deposit) { - return regchain(ChainKind::CHAIN_KIND_SVM, "SOL", 900, - evm_outpost_mvo(SVM_PROGRAM, inbound, opreg, deposit)); - }; - BOOST_REQUIRE(with(SVM_PROGRAM, "", "").find("opp_inbound_addr must be empty") != std::string::npos); - BOOST_REQUIRE(with("", SVM_PROGRAM, "").find("operator_registry_addr must be empty") != std::string::npos); - BOOST_REQUIRE(with("", "", SVM_PROGRAM).find("source_deposit_addr must be empty") != std::string::npos); +// ── SVM: opp_inbound_addr must be left empty ── +BOOST_FIXTURE_TEST_CASE(regchain_svm_inbound_addr_must_be_empty, sysio_chains_tester) { try { + BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_SVM, "SOL", 900, evm_outpost_mvo(SVM_PROGRAM, SVM_PROGRAM)) + .find("opp_inbound_addr must be empty") != std::string::npos); BOOST_REQUIRE(get_chain("SOL").is_null()); } FC_LOG_AND_RETHROW() } @@ -218,7 +206,7 @@ BOOST_FIXTURE_TEST_CASE(regchain_svm_bad_base58_rejected, sysio_chains_tester) { // ── WIRE: the depot self-row has no remote deployment ── BOOST_FIXTURE_TEST_CASE(regchain_wire_rejects_addresses, sysio_chains_tester) { try { BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_WIRE, "WIRE", 0, - evm_outpost_mvo(EVM_OPP, "", "", "")) + evm_outpost_mvo(EVM_OPP, "")) .find("no remote deployment") != std::string::npos); // WIRE with every field empty is fine. BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_WIRE, "WIRE", 0, no_outpost_mvo())); @@ -229,18 +217,18 @@ BOOST_FIXTURE_TEST_CASE(setoutpost_updates_and_guards, sysio_chains_tester) { tr // Register EVM with empty addresses, then fill them in — the redeploy path. BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, no_outpost_mvo())); BOOST_REQUIRE_EQUAL(success(), - setoutpost("ETH", evm_outpost_mvo(EVM_OPP, EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT))); - BOOST_REQUIRE_EQUAL(std::string(EVM_OPP), stored_addr("ETH", "opp_addr")); - BOOST_REQUIRE_EQUAL(std::string(EVM_OPREG), stored_addr("ETH", "operator_registry_addr")); + setoutpost("ETH", evm_outpost_mvo(EVM_OPP, EVM_INBOUND))); + BOOST_REQUIRE_EQUAL(std::string(EVM_OPP), stored_addr("ETH", "opp_addr")); + BOOST_REQUIRE_EQUAL(std::string(EVM_INBOUND), stored_addr("ETH", "opp_inbound_addr")); // Same validation as regchain: a bad address is rejected and the row is unchanged. - BOOST_REQUIRE(setoutpost("ETH", evm_outpost_mvo("0xnothex", EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT)) + BOOST_REQUIRE(setoutpost("ETH", evm_outpost_mvo("0xnothex", EVM_INBOUND)) .find("20-byte hex address") != std::string::npos); BOOST_REQUIRE_EQUAL(std::string(EVM_OPP), stored_addr("ETH", "opp_addr")); // The whole set is replaced, so an omitted field is cleared rather than kept. - BOOST_REQUIRE_EQUAL(success(), setoutpost("ETH", evm_outpost_mvo(EVM_OPP, EVM_INBOUND, "", ""))); - BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("ETH", "operator_registry_addr")); + BOOST_REQUIRE_EQUAL(success(), setoutpost("ETH", evm_outpost_mvo(EVM_OPP, ""))); + BOOST_REQUIRE_EQUAL(std::string{}, stored_addr("ETH", "opp_inbound_addr")); // Unregistered code is rejected. BOOST_REQUIRE(setoutpost("NOPE", no_outpost_mvo()).find("not registered") != std::string::npos); @@ -251,14 +239,14 @@ BOOST_FIXTURE_TEST_CASE(setoutpost_updates_and_guards, sysio_chains_tester) { tr } FC_LOG_AND_RETHROW() } // ── Two same-kind EVM outposts each keep their own distinct deployment ── -// (the registry half of WSA-075: both operator daemons read these per row.) +// (the registry half of WSA-075: the batch-operator relay reads these per row.) BOOST_FIXTURE_TEST_CASE(two_evm_outposts_keep_distinct_bindings, sysio_chains_tester) { try { constexpr auto BASE_OPP = "0x1111111111111111111111111111111111111111"; constexpr auto BASE_INBOUND = "0x2222222222222222222222222222222222222222"; BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, - evm_outpost_mvo(EVM_OPP, EVM_INBOUND, EVM_OPREG, EVM_DEPOSIT))); + evm_outpost_mvo(EVM_OPP, EVM_INBOUND))); BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "BASE", 8453, - evm_outpost_mvo(BASE_OPP, BASE_INBOUND, EVM_OPREG, EVM_DEPOSIT))); + evm_outpost_mvo(BASE_OPP, BASE_INBOUND))); BOOST_REQUIRE_EQUAL(std::string(EVM_OPP), stored_addr("ETH", "opp_addr")); BOOST_REQUIRE_EQUAL(std::string(BASE_OPP), stored_addr("BASE", "opp_addr")); diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 3a52be73fa..d19863d9d0 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -1,11 +1,5 @@ -/// Cross-contract dispatch tests for sysio.msgch's per-attestation-type -/// routing (Task 4 of the operator-collateral plan). -/// -/// Data model: identity moved to slug_name-keyed registries. The dispatch -/// surface still routes `OPERATOR_ACTION` payloads into opreg, but the -/// payload schema now carries `chain_code` (slug_name uint64) instead of a -/// `ChainKind chain` field, and `TokenAmount.token_code` (slug_name uint64) -/// instead of `TokenAmount.kind` (TokenKind enum). +/// Cross-contract dispatch tests for accepted OPP envelopes: LIQ intake, +/// node-owner registration, source binding, and inert retired payloads. #include #include @@ -20,9 +14,6 @@ #include #include #include -// The depot's swap kernel — tests re-derive both the quote settlement must pay -// and (via token_to_wire) the challenge-bond reference math. -#include #include #include @@ -66,18 +57,6 @@ namespace { /// or above any outstanding shadow a dispatch test can reach. constexpr uint64_t CUSTODY_COVERING_ANY_SUPPLY = static_cast(sysio::chain::asset::max_amount); -/// One `chain_min_bond` entry for `sysio.opreg::setconfig`'s `req_*_collat` -/// vectors. `config_timestamp_ms` is stamped by the contract, so 0 here. -inline fc::variant chain_min_bond_mvo(std::string_view chain_code, - std::string_view token_code, - uint64_t min_bond) { - return fc::variant(mvo() - ("chain_code", chain_code) - ("token_code", token_code) - ("min_bond", min_bond) - ("config_timestamp_ms", uint64_t{0})); -} - /// One attestation of an envelope under construction: its type and its encoded payload. using typed_attestation = std::pair; @@ -155,18 +134,18 @@ std::vector encode_envelope_with_one_attestation( constexpr size_t MAX_ENVELOPE_BYTES = 32'768; /// Encode a decodable envelope whose serialised size is EXACTLY `target_bytes`, padded with a -/// single out-of-scope STAKE attestation (dispatch drops it with no value-bearing effect). Probe -/// once with `target_bytes` of padding to measure the fixed protobuf overhead, then rebuild with -/// the pad shrunk by that overhead: at sizes near the 32 KiB envelope cap every nested length +/// single OPERATORS attestation (an outbound-only roster echo that dispatch drops with no +/// value-bearing effect). Probe once with `target_bytes` of padding to measure the fixed protobuf +/// overhead, then rebuild with the pad shrunk by that overhead: at sizes near the 32 KiB envelope cap every nested length /// prefix and the `data_size` varint sit in the same 3-byte width band (16 KiB .. 2 MiB), so the /// second pass lands exactly on target — the final REQUIRE pins it. std::vector encode_envelope_padded_to(uint32_t epoch_index, size_t target_bytes) { auto probe = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_STAKE, std::string(target_bytes, 'x')); + epoch_index, sysio::opp::types::ATTESTATION_TYPE_OPERATORS, std::string(target_bytes, 'x')); BOOST_REQUIRE_GT(probe.size(), target_bytes); const size_t overhead = probe.size() - target_bytes; auto padded = encode_envelope_with_one_attestation( - epoch_index, sysio::opp::types::ATTESTATION_TYPE_STAKE, + epoch_index, sysio::opp::types::ATTESTATION_TYPE_OPERATORS, std::string(target_bytes - overhead, 'x')); BOOST_REQUIRE_EQUAL(target_bytes, padded.size()); return padded; @@ -347,6 +326,8 @@ class sysio_dispatch_tester : public tester { // path -- exercises the dispatch decode + routing without the account-creation machinery). static constexpr auto CLAIM_ACCOUNT = "claimacct"_n; static constexpr uint64_t ROA_NETWORK_GEN = 0; + /// Termination prune delay for the suite's opreg config: ten minutes, far beyond any test's wall clock. + static constexpr uint64_t TERMINATE_PRUNE_DELAY_MS = 10ULL * 60 * 1000; sysio_dispatch_tester() { produce_blocks(2); @@ -445,34 +426,25 @@ class sysio_dispatch_tester : public tester { uwrit_op_eth_pubkey = create_eth_authex_link(UWRIT_OP); } - /// Push `sysio.opreg::setconfig` with the dispatch-suite defaults, varying - /// the underwriter and (optionally) producer collateral requirements. Batch - /// minimums stay empty (those operators are bootstrapped or unused here). The - /// race resolver gates winner selection on ACTIVE UNDERWRITER, and - /// `req_uw_collat` is what promotes UWRIT_OP to ACTIVE via - /// `opreg::processuw`; the eligibility-gate tests tune these to make a - /// candidate ACTIVE (a funded producer) or keep one inactive while funded. - /// `prune_delay_ms` defaults to 10 minutes — far beyond any test's wall - /// clock, so `prune` cases that want to exercise a gate OTHER than the delay - /// lower it explicitly. - action_result opreg_setconfig_collat(const fc::variants& req_uw_collat, - const fc::variants& req_prod_collat = fc::variants{}, - uint64_t prune_delay_ms = 600000) { + /// Push `sysio.opreg::setconfig` with the dispatch-suite defaults. No role carries a collateral + /// minimum: the batch operator is bootstrapped, and UWRIT_OP is registered only as the linked + /// wallet whose balances the dispatch cases assert stay untouched. + action_result opreg_setconfig() { return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "setconfig"_n, mvo() ("max_available_producers", 21) ("max_available_batch_ops", 63) ("max_available_underwriters", 21) - ("terminate_prune_delay_ms", prune_delay_ms) + ("terminate_prune_delay_ms", TERMINATE_PRUNE_DELAY_MS) ("terminate_max_consecutive_misses", 5) ("terminate_max_pct_misses_24h", 5) ("terminate_window_ms", uint64_t{24ULL * 60 * 60 * 1000}) - ("req_prod_collat", req_prod_collat) + ("req_prod_collat", fc::variants{}) ("req_batchop_collat", fc::variants{}) - ("req_uw_collat", req_uw_collat)); + ("req_uw_collat", fc::variants{})); } // `outpost_code` / `outpost_kind` name the single bootstrapped outpost. They default to the EVM - // "ETH" chain used by the deposit/withdraw/swap/underwrite cases. The node-owner happy path passes + // "ETH" chain used by the LIQ syndication cases. The node-owner happy path passes // "ETHEREUM" so the source it binds against (msgch's NODE_OWNER_SRC_CHAIN) is the scheduled outpost // and reaches consensus; the non-EVM drop case passes an SVM "SOLANA" so its delivery also reaches // consensus and the drop is exercised at the source binding, not merely the consensus gate. The @@ -487,15 +459,7 @@ class sysio_dispatch_tester : public tester { ("batch_op_groups", 1) ("epoch_retention_envelope_log_count", 200))); - // A 1-unit ETH/ETH underwriter minimum: every swap-race test funds - // UWRIT_OP's ETH bond, so this promotes it to ACTIVE via - // `opreg::processuw` (it registers UNKNOWN — underwriters cannot be - // bootstrapped). The race resolver now gates winner selection on ACTIVE - // UNDERWRITER, so the happy-path winner tests need a genuinely-active - // underwriter. Adds NO balance row — deposit-routing assertions (exact / - // zero balances) are unaffected. - BOOST_REQUIRE_EQUAL(success(), - opreg_setconfig_collat(fc::variants{chain_min_bond_mvo("ETH", "ETH", 1)})); + BOOST_REQUIRE_EQUAL(success(), opreg_setconfig()); BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "regoperator"_n, mvo() @@ -649,14 +613,12 @@ class sysio_dispatch_tester : public tester { type, data, abi_serializer::create_yield_function(abi_serializer_max_time)); } - /** Produce a complete serialized fixed-size variant for a shim type. */ - // ── SEC-129 / WSA-223: real epoch-aging helpers ────────────────────────── // // The bootstrap advance gate-blocks on missing emissions state, so every - // dispatch test normally runs at epoch 0. The UWREQ lifecycle sweep is - // trigger-driven off real advances, so its tests configure emissions the - // way emissions_tests.cpp does and then genuinely advance the epoch index. + // dispatch test normally runs at epoch 0. Tests that need real advances + // configure emissions the way emissions_tests.cpp does and then genuinely + // advance the epoch index. /// Push a sysio.system action (ABI resolved from chain state — the system /// account runs this build's genesis code, like sysio.roa above). @@ -730,19 +692,6 @@ class sysio_dispatch_tester : public tester { produce_blocks(); } - /// Cross one epoch boundary and advance. epoch_duration_sec is 60 in this - /// fixture (bootstrap_for_dispatch); 124 half-second blocks = 62s crosses - /// it. advance is pushed with the epoch contract's own authority (advance - /// accepts sysio.msgch OR sysio.epoch post-genesis). Each successful - /// advance fires the real inline maintenance chain — chklocks, - /// pruneuwreqs(MAX_UWREQ_PRUNE_PER_EPOCH), drainfwq, buildenv — exactly as - /// in production. - void age_one_epoch() { - produce_blocks(124); - BOOST_REQUIRE_EQUAL(success(), - push(EPOCH_ACCOUNT, epoch_abi, EPOCH_ACCOUNT, "advance"_n, mvo())); - } - // ── sysio.synd inbound routing (SYNDICATE_LIQ / LIQ_YIELD) ──────────────── static constexpr auto TOKENS_ACCOUNT = "sysio.tokens"_n; @@ -917,7 +866,7 @@ BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispat const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, + sysio::opp::types::ATTESTATION_TYPE_OPERATORS, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); @@ -939,7 +888,7 @@ BOOST_FIXTURE_TEST_CASE(deliver_duplicate_from_same_operator_reverts, sysio_disp const auto eth_code = fc::slug_name{"ETH"}.value; auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, + sysio::opp::types::ATTESTATION_TYPE_OPERATORS, std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); @@ -1037,7 +986,7 @@ BOOST_FIXTURE_TEST_CASE(dispatch_routes_node_owner_reg_to_roa, sysio_dispatch_te } FC_LOG_AND_RETHROW() } // Only ATTESTATION_TYPE_NODE_OWNER_REG reaches the node-owner handler. The outbound-only types -// (OPERATORS, BATCH_OPERATOR_GROUPS, SWAP_REVERT, DEPOSIT_REVERT) are dropped on receipt even when +// (OPERATORS, BATCH_OPERATOR_GROUPS) are dropped on receipt even when // the payload is a well-formed NodeOwnerRegistration from the node-owner source outpost: a payload // that would register CLAIM_ACCOUNT under the real type must leave no owner row and no audit row. BOOST_FIXTURE_TEST_CASE(outbound_only_types_never_reach_node_owner_dispatch, sysio_dispatch_tester) { try { @@ -1053,8 +1002,7 @@ BOOST_FIXTURE_TEST_CASE(outbound_only_types_never_reach_node_owner_dispatch, sys sysio::opp::types::WIRE_KEY_TYPE_K1, wire_key, eth_bytes, eth_address); std::vector entries; - for (auto type : {ATTESTATION_TYPE_OPERATORS, ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS, - ATTESTATION_TYPE_SWAP_REVERT, ATTESTATION_TYPE_DEPOSIT_REVERT}) { + for (auto type : {ATTESTATION_TYPE_OPERATORS, ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS}) { entries.emplace_back(type, payload); } const auto trace = deliver_trace(eth_code, @@ -1657,9 +1605,7 @@ BOOST_FIXTURE_TEST_CASE(retired_attestations_do_not_mutate_collateral_or_block_l const auto eth = fc::slug_name{"ETH"}.value; const auto liqeth = fc::slug_name{"LIQETH"}.value; std::vector entries; - for (auto type : {ATTESTATION_TYPE_OPERATOR_ACTION, ATTESTATION_TYPE_SWAP_REQUEST, - ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - ATTESTATION_TYPE_RESERVE_CREATE_CANCEL}) { + for (auto type : {ATTESTATION_TYPE_OPERATOR_ACTION}) { entries.emplace_back(type, std::string(1, '\x0a')); } entries.emplace_back(ATTESTATION_TYPE_SYNDICATE_LIQ, @@ -1667,10 +1613,42 @@ BOOST_FIXTURE_TEST_CASE(retired_attestations_do_not_mutate_collateral_or_block_l liqeth, LIQ_UNIT, 1)); constexpr int32_t removed_reward_type = 60950; constexpr int32_t removed_reserve_create_type = 60958; + constexpr int32_t removed_pretoken_purchase_type = 3004; + constexpr int32_t removed_pretoken_yield_type = 3006; + constexpr int32_t removed_wire_purchase_type = 60930; + constexpr int32_t removed_reserve_cancel_type = 60959; + constexpr int32_t removed_reserve_cancelled_type = 60960; + constexpr int32_t removed_reserve_ready_type = 60961; + constexpr int32_t removed_reserve_snapshot_type = 43520; + constexpr int32_t removed_emissions_blocked_type = 60962; + constexpr int32_t removed_swap_request_type = 60934; + constexpr int32_t removed_swap_remit_type = 60944; + constexpr int32_t removed_underwrite_commit_type = 60953; + constexpr int32_t removed_swap_revert_type = 60955; + constexpr int32_t removed_deposit_revert_type = 60956; + constexpr int32_t removed_stake_type = 3001; + constexpr int32_t removed_unstake_type = 3002; + constexpr int32_t removed_stake_update_type = 60928; + constexpr int32_t removed_stake_result_type = 60951; + constexpr int32_t removed_processing_error_type = 60952; + constexpr int32_t removed_challenge_response_type = 60932; + constexpr int32_t removed_challenge_request_type = 60945; constexpr int32_t undeclared_type = 65000; const auto trace = deliver_trace(eth, encode_envelope_with_mixed_attestations(current_epoch(), entries, {}, {}, - {removed_reward_type, removed_reserve_create_type, undeclared_type})); + {removed_reward_type, removed_reserve_create_type, + removed_pretoken_purchase_type, removed_pretoken_yield_type, + removed_wire_purchase_type, + removed_reserve_cancel_type, removed_reserve_cancelled_type, + removed_reserve_ready_type, removed_reserve_snapshot_type, + removed_emissions_blocked_type, removed_swap_request_type, + removed_swap_remit_type, removed_underwrite_commit_type, + removed_swap_revert_type, removed_deposit_revert_type, + removed_stake_type, removed_unstake_type, + removed_stake_update_type, removed_stake_result_type, + removed_processing_error_type, + removed_challenge_response_type, + removed_challenge_request_type, undeclared_type})); BOOST_REQUIRE(trace != nullptr); BOOST_REQUIRE(!trace->except); BOOST_CHECK(get_operator(UWRIT_OP)["balances"].get_array().empty()); diff --git a/contracts/tests/sysio.epoch_flushwtdw_tests.cpp b/contracts/tests/sysio.epoch_flushwtdw_tests.cpp index f65f28adf7..f381894d59 100644 --- a/contracts/tests/sysio.epoch_flushwtdw_tests.cpp +++ b/contracts/tests/sysio.epoch_flushwtdw_tests.cpp @@ -71,9 +71,8 @@ class sysio_epoch_flushwtdw_tester : public tester { // Deploy OPP contracts. These are privileged because epoch::advance // sends inline actions to opreg with `permission_level{epoch, owner}`, // which require_auth(epoch) accepts only when epoch is privileged. - // epoch::advance also iterates sysio.chains::chains and inlines into - // sysio.uwrit::chklocks, so both must be deployed for those cross- - // contract calls to resolve. + // epoch::advance also iterates sysio.chains::chains, so it must be + // deployed for that cross-contract read to resolve. deploy(EPOCH_ACCOUNT, contracts::epoch_wasm(), contracts::epoch_abi(), epoch_abi); deploy(OPREG_ACCOUNT, contracts::opreg_wasm(), contracts::opreg_abi(), opreg_abi); deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi); diff --git a/contracts/tests/sysio.epoch_tests.cpp b/contracts/tests/sysio.epoch_tests.cpp index 7852733129..13dc5f9f10 100644 --- a/contracts/tests/sysio.epoch_tests.cpp +++ b/contracts/tests/sysio.epoch_tests.cpp @@ -320,8 +320,8 @@ BOOST_FIXTURE_TEST_CASE(regchain_wire_code_reserved_from_other_kinds, sysio_epoc /// `name` and `description` persist into a row billed to the shared `sysio` RAM pool, so both /// are bounded before emplace. CertiK WNS-10 raised this for `sysio.tokens::regtoken`; the same -/// unbounded pair existed on `sysio.chains::regchain` and `sysio.reserv::regreserve`, and all -/// three now share `sysio::opp::registry::check_metadata`. +/// unbounded pair existed on `sysio.chains::regchain`, and both now share +/// `sysio::opp::registry::check_metadata`. BOOST_FIXTURE_TEST_CASE(regchain_bounds_metadata, sysio_epoch_tester) { try { BOOST_REQUIRE(regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1, std::string(129, 'x')) .find("label exceeds 128 bytes") != std::string::npos); diff --git a/contracts/tests/sysio.msgch_chain_tests.cpp b/contracts/tests/sysio.msgch_chain_tests.cpp index 5c0be1adfe..0fa32f3dde 100644 --- a/contracts/tests/sysio.msgch_chain_tests.cpp +++ b/contracts/tests/sysio.msgch_chain_tests.cpp @@ -696,7 +696,7 @@ class sysio_msgch_chain_tester : public tester { // -- Inbound envelope builder -- - /// Encode a deliverable envelope carrying one out-of-scope STAKE attestation (dispatch drops + /// Encode a deliverable envelope carrying one OPERATORS roster echo (dispatch drops /// the attestation silently; acceptance is still fully observable via `outpcons` and the /// stored attestation row). The semantic header is derived per the spec — `apply_consensus` /// drops envelopes whose header fields do not recompute or whose message does not continue the @@ -713,7 +713,7 @@ class sysio_msgch_chain_tester : public tester { if (!prev.empty()) env.set_previous_envelope_hash(prev); if (!env_hash.empty()) env.set_envelope_hash(env_hash); auto* att = env.add_messages()->mutable_payload()->add_attestations(); - att->set_type(sysio::opp::types::ATTESTATION_TYPE_STAKE); + att->set_type(sysio::opp::types::ATTESTATION_TYPE_OPERATORS); att->set_data(att_data); att->set_data_size(static_cast(att_data.size())); oracle::finalize_header(*env.mutable_messages(0), prev_message_id, 1'775'612'516'983ULL); diff --git a/contracts/tests/sysio.msgch_tests.cpp b/contracts/tests/sysio.msgch_tests.cpp index 6b3c1c6d03..f0d5d74b20 100644 --- a/contracts/tests/sysio.msgch_tests.cpp +++ b/contracts/tests/sysio.msgch_tests.cpp @@ -353,11 +353,7 @@ using fc::slug_name_literals::operator""_s; /// the spelling `"ETH"`. ETH_OUTPOST_ID is the slug_name's packed value. constexpr uint64_t ETH_OUTPOST_ID = "ETH"_s.value; -/// Solana/SVM test outpost registered by `register_outpost(CHAIN_KIND_SVM, ...)`. -constexpr uint64_t SOL_OUTPOST_ID = "SOL"_s.value; - constexpr auto EVM_TEST_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_OPERATORS; -constexpr auto SWAP_REMIT_ATTESTATION_TYPE = opp::types::ATTESTATION_TYPE_SWAP_REMIT; } // anonymous namespace @@ -625,10 +621,10 @@ BOOST_FIXTURE_TEST_CASE(buildenv_packs_until_cap_then_leaves_remainder, // queueout carries no ABI-level auth. Without the depot-contract gate, any account could call it // directly and inject a forged READY attestation that buildenv() then packs into the depot's -// group-signed outbound envelope (a forged SWAP_REMIT / WITHDRAW_REMIT / SLASH the outpost executes). +// group-signed outbound envelope (a forged DESYNDICATE_LIQ the outpost executes). // A direct call from a non-depot account must revert at the auth gate, which is the first statement // in queueout (before any state read), so this holds regardless of epoch-state setup. The authorized -// path (msgch self / epoch / opreg / uwrit / reserv) is covered by the queueout()/buildenv() helpers +// path (msgch self / epoch / opreg / synd) is covered by the queueout()/buildenv() helpers // used throughout this suite, which sign as the depot and succeed. BOOST_FIXTURE_TEST_CASE(queueout_rejects_unauthorized_caller, sysio_msgch_tester) { try { std::vector forged(8, 0x7f); diff --git a/contracts/tests/sysio.opreg_tests.cpp b/contracts/tests/sysio.opreg_tests.cpp index 0676c39722..2a8d831560 100644 --- a/contracts/tests/sysio.opreg_tests.cpp +++ b/contracts/tests/sysio.opreg_tests.cpp @@ -312,8 +312,8 @@ constexpr std::string_view kWireEarnsNoYieldError = } // namespace /// Data model: per-chain identity has moved from `ChainKind` enums to -/// `sysio::slug_name`-keyed registries (`sysio.chains`, `sysio.tokens`, -/// `sysio.reserv`). The test fixture treats the codenames as opaque uint64 +/// `sysio::slug_name`-keyed registries (`sysio.chains`, `sysio.tokens`). +/// The test fixture treats the codenames as opaque uint64 /// values; per-chain spelling ("ETH", "SOL", "WIRE", "LIQETH", ...) maps to /// the host-side `fc::slug_name` packing algorithm so the bytes match what /// the contract emplaces under. diff --git a/contracts/tests/sysio.tokens_tests.cpp b/contracts/tests/sysio.tokens_tests.cpp index 2ef974af72..a614ba6d1c 100644 --- a/contracts/tests/sysio.tokens_tests.cpp +++ b/contracts/tests/sysio.tokens_tests.cpp @@ -74,8 +74,7 @@ class sysio_tokens_tester : public tester { } /// `chaintokens` is a uint128-keyed kv::table; `get_row_by_id` only supports - /// uint64 keys, so walk the table by its DB index and match the slug pair - /// (the same workaround sysio.reserv_tests uses for its checksum-keyed table). + /// uint64 keys, so walk the table by its DB index and match the slug pair. fc::variant find_chaintoken(std::string_view chain_code, std::string_view token_code) { const auto target_chain = fc::slug_name{chain_code}.value; const auto target_token = fc::slug_name{token_code}.value; diff --git a/docs/platform-bootstrap-config.md b/docs/platform-bootstrap-config.md index 6472bda4a6..8df9f2c903 100644 --- a/docs/platform-bootstrap-config.md +++ b/docs/platform-bootstrap-config.md @@ -36,10 +36,9 @@ canonical protobuf-JSON encoding of `BootstrapPlatformConfig`. earmarks, and repeated `ChainSpec` / `TokenSpec` / `LiqPoolSpec` / `SyndicationSpec`. -The spec messages deliberately differ from the registry carriers in -`sysio/opp/types/types.proto` (`Chain` / `Token` / `ChainToken`), which are wire -messages with packed-uint64 codes, raw `bytes` addresses, and lifecycle fields -that are outputs. A hand-authored config wants the opposite: +The spec messages deliberately differ from the registry rows of `sysio.chains` +and `sysio.tokens`, which hold packed-uint64 codes, raw `bytes` addresses, and +lifecycle fields that are outputs. A hand-authored config wants the opposite: - **Codes are strings** (`"ETHEREUM"`, `"USDC"`, `"LIQETH"`); the tool packs them via `slug_name` (`[A-Z][A-Z0-9_]{0,7}` -- a code must START with a @@ -53,7 +52,7 @@ that are outputs. A hand-authored config wants the opposite: values exceed 2^53. - **Enums appear by full value name** (`"TOKEN_KIND_ERC20"`), reusing the `ChainKind` / `TokenKind` enums via import. -- `ChainToken` binding is folded into `TokenSpec` (a token binds to exactly one +- The chain-token binding (`sysio.tokens` `chaintokens` row) is folded into `TokenSpec` (a token binds to exactly one chain; multi-chain assets use distinct codes such as `USDC` vs `USDCSOL`). - There is no `is_depot` field — the depot chain is the single `CHAIN_KIND_WIRE` entry. diff --git a/docs/retired-opp-carriers.md b/docs/retired-opp-carriers.md new file mode 100644 index 0000000000..f7cb4121b4 --- /dev/null +++ b/docs/retired-opp-carriers.md @@ -0,0 +1,91 @@ +# Retired OPP carrier audit + +This is the follow-up to the external settlement retirement and merged PR #670. +The earlier pass removed the implementations but left some shared schemas, +serializers, enum reflections, and descriptions advertising the retired flows. +The audit below distinguishes a payload's name from an executable producer or +settlement handler. Searches covered tracked source in Sysio, Ethereum, Solana, +Tools, Libraries, and Hub; generated build output was checked separately. + +## Removed by this follow-up + +| Surface | Evidence for retirement | Removal | +| --- | --- | --- | +| Reserve creation cancellation and matching | `sysio.reserv` was deleted in `e9ac87c194`; the remaining msgch cases were no-ops. Solana dispatch no longer handles these messages; Ethereum registers no handlers. | `ReserveCreateCancel`, `ReserveCreateCancelled`, `ReserveReady`, and their discriminants | +| External reserve reporting and custody registry | No producer remains; msgch's balance-sheet case did not decode or mutate state. `Reserve` and `ReserveAmount` only fed the retired schemas and unused serializers. | `ReserveBalanceSheet`, `ReserveDisbursement`, `Reserve`, `ReserveAmount`, `ReserveStatus` | +| Cross-chain emissions-block notification | The schema explicitly marked it as no longer emitted. Readiness and refusal diagnostics are depot-local in `sysio.epoch::blocklog`. | `EmissionsBlocked` and its discriminant; retain `EmissionsBlockReason` | +| Legacy pretoken purchase/yield | Ethereum's purchase/yield senders are deleted (documented in `StakingManager.sol`); remaining references are helper imports, display decoders, and classifications. | `PretokenPurchase`, `PretokenYield`, `WireTokenPurchase`, and their discriminants | +| Cross-chain swaps and dual-commit underwriting refunds | `sysio.reserv` and `sysio.uwrit` were deleted in `e9ac87c194`; remaining Sysio cases were no-ops. Solana dispatch handles only rosters, operator actions, and desyndication; Ethereum registers no corresponding settlement handlers. Remaining consumer references are retired classifications, display utilities, and fixtures. | `SwapRequest`, `UnderwriteIntentCommit`, `SwapRemit`, `SwapRevert`, `DepositRevert`, their discriminants, serializers, reflections, and inert depot cases | +| Old underwriting states | The two enums have no executable producer or consumer; the deleted dual-commit implementation owned them. Native `sysio.bond` defines its own request and bond states. | `UnderwriteRequestStatus`, `UnderwriteStatus` | +| Three-field node-owner payload | BAR emits `NodeOwnerRegistration`; msgch and the Tools decoder consume that full payload. The three-field message had only a declaration and unused serializers. | `NodeOwnerReg`; retain `NODE_OWNER_REG` and `NodeOwnerRegistration` | +| OPP state wrapper | `ProtocolState` had only a declaration and unused serializers across all audited repositories. Envelope transport uses `Envelope` and its message/header types directly. | OPP `ProtocolState`; native chain `protocol_state_object` is unrelated | +| Dead dispatch work | `attestation_id` and `original_message_id` were never read by the complete dispatcher after swap dispatch retirement. Stored attestation identities and semantic header validation remain intact. | Remove the unused arguments and message-id reconstruction performed solely for them | +| Documentation residue | Relay comments and registry documentation still described removed reserves or the deleted underwriter daemon. Three unreferenced OPP diagrams describe an obsolete pre-refactor schema. | Refresh current comments and remove the obsolete diagrams | +| Underwriter outpost roles in the chain registry | `operator_registry_addr` (the underwriter's `uw_commit` target) and `source_deposit_addr` (its swap-deposit verify path) had no reader once the underwriter daemon was deleted; the batch-operator relay reads only `opp_addr` and `opp_inbound_addr`, and the shared `resolve_role_addr` helper had no caller. | Both `sysio.chains::outpost_addrs` fields, their validation, their shared field names, and `resolve_role_addr` | +| Staking lifecycle carriers | Ethereum's only STAKE/UNSTAKE producers, `StakingManager.sendStakeAttestation` and `sendUnstakeAttestation`, are internal and have no caller (`stakeLiqETH` is a `_notWired` stub). Nothing produces `StakeUpdate` or `StakeResult`, msgch dropped all four types in one deferred case, and Solana listed them only to keep its classification exhaustive. | `PretokenStakeChange`, `StakeUpdate`, `StakeResult`, `StakeStatus`, the STAKE, UNSTAKE, STAKE_UPDATE and STAKE_RESULT discriminants, their serializers, reflections and depot case | +| Attestation processing error report | No chain produces `AttestationProcessingError`; msgch ignored it and the Tools codec only displayed it. | `AttestationProcessingError` and its discriminant | +| Challenge attestations | No chain produces CHALLENGE_REQUEST or CHALLENGE_RESPONSE; msgch dropped both, Solana listed them only to keep its classification exhaustive, and Tools only displayed them. `sysio.chalg` disputes run on `DisputeStatus` through evalcons, not on inbound attestations. | `ChallengeRequest`, `ChallengeOperatorHash`, their discriminants, serializers and depot case | +| Unused schema declarations | `ChainKeyType`, `ChainSignature`, `WirePermission`, `ChainRequestStatus`, `ChallengeStatus` and `DebugEnvelopeDataRecord` had only declarations, enum reflections or uncalled serializers. `Chain`, `Token` and `ChainToken` were described as the registry's carriers, but `sysio.chains` and `sysio.tokens` store their own row structs and nothing encodes these messages. | The declarations, their reflections and serializers; `ChainKind` and `TokenKind` stay | +| Swap-fee emission audit fields | The `sysio.reserv` rewards-bucket sweep that fed them was deleted; `payepoch` wrote both as constant zero. | `epochlog.fee_distributed`, `epochlog.batch_fee_retained`, and the fee-sweep descriptions | +| Dead depot helpers | msgch's operator-address resolver and its payload-code canonicality check served only the retired operator/swap/reserve ingress (the live LIQ paths reject unregistered codes through the token registry); the `uwreqs` locked-amount row type had no table; the underwriter-challenge declarations in `sysio.chalg` were gone but their documentation remained. | `resolve_account_from_op_address`, `payload_codes_canonical`, `opp_table::locked_amount_t`, the orphaned challenge documentation, and the dispatch suite's swap-race fixture configuration | + +No compatibility exports, replacement enum members, or reserved slots are added. +Existing active discriminants and payload field numbers keep their values. + +## Retained live or planned surfaces + +- `OperatorAction` and `OperatorActionLog` record native collateral changes in + `sysio.opreg`. They are audit records: the depot queues no OPERATOR_ACTION + attestation, and outposts learn a slashed status from the OPERATORS roster. + Removing external custody does not remove this audit/eligibility protocol. + The existing `reserve_code` field stays zero in depot-native audit records; + changing the live table payload is outside this carrier cleanup. +- `StakingManager.sol` remains the staking-track placeholder. The post-launch + staking design defines its own attestations; none of the removed carriers is + reserved for it. +- `sysio.chalg` owns live depot disputes (`DisputeStatus`, opened by evalcons); + only the producer-less challenge attestations were removed. +- Operators, batch-operator groups, node-owner registration, LIQ syndication, + yield, and desyndication remain active. Their schemas and routing are retained. +- Chain/token registries, native swaps and their AMM helper, native collateral, + envelope consensus, and opaque unknown-attestation handling remain active. + `sysio.swap` and everything it consumes (`amm_math`, `twap`, `shadow_yield`, + `sysio.andon`) keep their code unchanged: that implementation is not final. + Only `amm_math`'s comments, and its test's, stop naming the deleted + `sysio.reserv` / `sysio.uwrit` as its callers and fee authorities. + +## Consumer release coordination + +The four open companion PRs (#102 Libraries, #116 Tools, #226 Ethereum, #568 +Solana) belong to the #670 wave and are unchanged by this follow-up. Land that +wave first. The OPP Bundles workflow publishes on a `master` push that touches +`libraries/opp/**`, so merging this follow-up publishes a new model API. Prepare +the subsequent consumer changes before merging it and coordinate their landing +and the SDK regeneration/release as a separate wave: + +| Consumer | Remaining work for the new model API | +| --- | --- | +| Libraries | Regenerate contract declarations from the new producer ABI, including the two-field `sysio.chains` outpost address struct (`chains/Structs.ts`, `Types.ts`, `Actions.ts`, `Client.ts`) and the `sysio.system` `epochlog` row; remove the unused root OPP package dependency, which contradicts the generator/output boundary. | +| Tools | Remove retired display imports/routes (including `StakeUpdate`, `StakeResult`, `AttestationProcessingError`) and the retired swap-revert scanner; stop writing `operator_registry_addr` / `source_deposit_addr` in `RegistrySteps.ts` and its tests. Verify unknown entries still render raw beside active entries. | +| Ethereum | Remove dead pretoken helper imports/unions and the emissions-notification negative fixture; replace removed enum references in retired-handler assertions with opaque wire values. Remove `StakingManager`'s uncalled STAKE/UNSTAKE senders, `nativeTokenCode`, and the deploy script's STAKE/UNSTAKE endpoint roles; OPP routing tests use live types. | +| Solana | Remove retired enum classification/correlation code (including the staking and processing-error variants) and stale settlement test branches. Retain active desyndication replay checks, operator status mirroring, roster/consensus tests, and liquid-staking reserve-pool accounts. | + +Ethereum's unused reserve/swap custom errors and Solana's corresponding error +variants, comments, and old fixtures also deserve cleanup in that later consumer +wave. Liquid-staking reserve pools are live custody and must not be confused with +the retired cross-chain `Reserve` protocol. + +Opening this PR does not publish packages or edit the existing companion branches. +Its generated-model build can be validated locally independently of adopting the +new packages in those consumers. + +## Regression checks + +Generated C++ descriptors must omit retired payloads while retaining current +node-owner, staking, collateral audit, and LIQ messages. An opaque malformed +attestation round-trip must preserve a following decodable LIQ yield entry. +Contract dispatch receives all removed wire values alongside a real syndication: +no collateral/reward state changes, the live intake succeeds, and the envelope +continues. Rebuild host/CDT and Solidity/TypeScript/Solana model bundles through +the normal Sysio build, copy rebuilt contract ABI/WASM artifacts back to source, +and verify the removed declarations are absent from all generated model APIs. diff --git a/etc/schema/opp_entity_diagram-gen.puml b/etc/schema/opp_entity_diagram-gen.puml deleted file mode 100644 index 5c3286a151..0000000000 --- a/etc/schema/opp_entity_diagram-gen.puml +++ /dev/null @@ -1,315 +0,0 @@ -@startuml - -hide empty methods -hide empty fields -skinparam classBackgroundColor #EEEEEE -skinparam classArrowColor black -skinparam classBorderColor black -skinparam packageBackgroundColor white -skinparam packageBorderColor black - - -sysio.opp.attestations.OperatorAction -- sysio.opp.types.OperatorStatus -sysio.opp.attestations.OperatorAction -- sysio.opp.types.TokenAmount -sysio.opp.attestations.OperatorAction -- sysio.opp.types.ChainAddress -sysio.opp.attestations.OperatorAction -- sysio.opp.types.OperatorType -sysio.opp.attestations.OperatorAction -- sysio.opp.attestations.OperatorAction::ActionType - -sysio.opp.types.ChainId -- sysio.opp.types.ChainKind - -sysio.opp.attestations.ChainReserveBalanceSheet -- sysio.opp.types.ChainKind -sysio.opp.attestations.ChainReserveBalanceSheet -- sysio.opp.types.TokenAmount - - - -sysio.opp.attestations.PretokenPurchase -- sysio.opp.types.ChainAddress -sysio.opp.attestations.PretokenPurchase -- sysio.opp.types.TokenAmount - -sysio.opp.types.TokenAmount -- sysio.opp.types.TokenKind - -sysio.opp.types.ChainAddress -- sysio.opp.types.ChainKind - -sysio.opp.attestations.ReserveDisbursement -- sysio.opp.types.ChainAddress -sysio.opp.attestations.ReserveDisbursement -- sysio.opp.types.TokenAmount -sysio.opp.attestations.ReserveDisbursement -- sysio.opp.types.ChainSignature - - -sysio.opp.attestations.StakeUpdate -- sysio.opp.types.ChainAddress -sysio.opp.attestations.StakeUpdate -- sysio.opp.types.StakeStatus -sysio.opp.attestations.StakeUpdate -- sysio.opp.types.TokenAmount - -sysio.opp.MessagePayload -- sysio.opp.AttestationEntry - - - - - -sysio.opp.attestations.PretokenStakeChange -- sysio.opp.types.ChainAddress -sysio.opp.attestations.PretokenStakeChange -- sysio.opp.types.TokenAmount - - - -sysio.opp.types.ChainSignature -- sysio.opp.types.ChainAddress -sysio.opp.types.ChainSignature -- sysio.opp.types.ChainKeyType - -sysio.opp.Message -- sysio.opp.MessageHeader -sysio.opp.Message -- sysio.opp.MessagePayload - -sysio.opp.AttestationEntry -- sysio.opp.types.AttestationType - -sysio.opp.attestations.TimestampedMessage -- sysio.opp.Message - -sysio.opp.Envelope -- sysio.opp.Endpoints -sysio.opp.Envelope -- sysio.opp.types.ChainSignature - -sysio.opp.attestations.PretokenYield -- sysio.opp.types.ChainAddress -sysio.opp.attestations.PretokenYield -- sysio.opp.types.TokenAmount - - -sysio.opp.MessageHeader -- sysio.opp.Endpoints -sysio.opp.MessageHeader -- sysio.opp.types.EncodingFlags - -sysio.opp.attestations.WireTokenPurchase -- sysio.opp.types.ChainAddress -sysio.opp.attestations.WireTokenPurchase -- sysio.opp.types.TokenAmount - -sysio.opp.types.EncodingFlags -- sysio.opp.types.Endianness -sysio.opp.types.EncodingFlags -- sysio.opp.types.HashAlgorithm -sysio.opp.types.EncodingFlags -- sysio.opp.types.LengthEncoding - - -sysio.opp.Endpoints -- sysio.opp.types.ChainId - -sysio.opp.attestations.ProtocolState -- sysio.opp.types.ChainId -sysio.opp.attestations.ProtocolState -- sysio.opp.attestations.TimestampedMessage -sysio.opp.attestations.ProtocolState -- sysio.opp.Message -package sysio.opp.attestations { - class ChainReserveBalanceSheet { - kind: sysio.opp.types.ChainKind - amounts: sysio.opp.types.TokenAmount [*] - } - - class OperatorAction { - action_type: OperatorAction::ActionType - actor: sysio.opp.types.ChainAddress - type: sysio.opp.types.OperatorType - status: sysio.opp.types.OperatorStatus - amount: sysio.opp.types.TokenAmount - } - - enum OperatorAction::ActionType { - ACTION_TYPE_UNKNOWN - ACTION_TYPE_DEPOSIT - ACTION_TYPE_WITHDRAW - } - - class PretokenPurchase { - actor: sysio.opp.types.ChainAddress - amount: sysio.opp.types.TokenAmount - pretoken_count: Long - index_at_mint: Long - } - - class PretokenStakeChange { - actor: sysio.opp.types.ChainAddress - amount: sysio.opp.types.TokenAmount - index_at_mint: Long - index_at_burn: Long - } - - class PretokenYield { - actor: sysio.opp.types.ChainAddress - amount: sysio.opp.types.TokenAmount - index_at_mint: Long - } - - class ProtocolState { - chain_id: sysio.opp.types.ChainId - current_message_id: Bytes - processed_message_id: Bytes - incoming_messages: TimestampedMessage [*] - outgoing: sysio.opp.Message [*] - } - - class ReserveDisbursement { - actor: sysio.opp.types.ChainAddress - amount: sysio.opp.types.TokenAmount - signature: sysio.opp.types.ChainSignature [*] - } - - class StakeUpdate { - actor: sysio.opp.types.ChainAddress - status: sysio.opp.types.StakeStatus - amount: sysio.opp.types.TokenAmount - } - - class TimestampedMessage { - timestamp: Long - message: sysio.opp.Message - } - - class WireTokenPurchase { - actor: sysio.opp.types.ChainAddress - amounts: sysio.opp.types.TokenAmount [*] - } - -} -package sysio.opp.types { - enum AttestationType { - ATTESTATION_TYPE_UNSPECIFIED - ATTESTATION_TYPE_STAKE - ATTESTATION_TYPE_UNSTAKE - ATTESTATION_TYPE_PRETOKEN_PURCHASE - ATTESTATION_TYPE_PRETOKEN_YIELD - ATTESTATION_TYPE_RESERVE_BALANCE_SHEET - ATTESTATION_TYPE_STAKE_UPDATE - ATTESTATION_TYPE_NATIVE_YIELD_REWARD - ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE - ATTESTATION_TYPE_OPERATOR_REG_DEREG - ATTESTATION_TYPE_CHALLENGE_RESPONSE - ATTESTATION_TYPE_SLASH_OPERATOR - } - - class ChainAddress { - kind: ChainKind - address: Bytes - } - - class ChainId { - kind: ChainKind - id: Int - } - - enum ChainKeyType { - CHAIN_KEY_TYPE_UNKNOWN - CHAIN_KEY_TYPE_WIRE - CHAIN_KEY_TYPE_WIRE_BLS - CHAIN_KEY_TYPE_ETHEREUM - CHAIN_KEY_TYPE_SOLANA - CHAIN_KEY_TYPE_SUI - } - - enum ChainKind { - CHAIN_KIND_UNKNOWN - CHAIN_KIND_WIRE - CHAIN_KIND_ETHEREUM - CHAIN_KIND_SOLANA - CHAIN_KIND_SUI - } - - class ChainSignature { - actor: ChainAddress - key_type: ChainKeyType - signature: Bytes - } - - class EncodingFlags { - endianness: Endianness - hash_algorithm: HashAlgorithm - length_encoding: LengthEncoding - } - - enum Endianness { - ENDIANNESS_BIG - ENDIANNESS_LITTLE - } - - enum HashAlgorithm { - HASH_ALGORITHM_KECCAK256 - HASH_ALGORITHM_SHA256 - HASH_ALGORITHM_RESERVED_1 - HASH_ALGORITHM_RESERVED_2 - } - - enum LengthEncoding { - LENGTH_ENCODING_VARUINT - LENGTH_ENCODING_UINT32 - } - - enum OperatorStatus { - OPERATOR_STATUS_UNKNOWN - OPERATOR_STATUS_WARMUP - OPERATOR_STATUS_COOLDOWN - OPERATOR_STATUS_ACTIVE - OPERATOR_STATUS_TERMINATED - OPERATOR_STATUS_SLASHED - } - - enum OperatorType { - OPERATOR_TYPE_UNKNOWN - OPERATOR_TYPE_PRODUCER - OPERATOR_TYPE_BATCH - OPERATOR_TYPE_UNDERWRITER - OPERATOR_TYPE_CHALLENGER - } - - enum StakeStatus { - STAKE_STATUS_UNKNOWN - STAKE_STATUS_WARMUP - STAKE_STATUS_COOLDOWN - STAKE_STATUS_ACTIVE - STAKE_STATUS_TERMINATED - STAKE_STATUS_SLASHED - } - - class TokenAmount { - kind: TokenKind - amount: Long - } - - enum TokenKind { - TOKEN_KIND_WIRE - TOKEN_KIND_ETH - TOKEN_KIND_LIQETH - TOKEN_KIND_SOL - TOKEN_KIND_LIQSOL - } - -} -package sysio.opp { - class AttestationEntry { - type: sysio.opp.types.AttestationType - data_size: Int - data: Bytes - } - - class Endpoints { - start: sysio.opp.types.ChainId - end: sysio.opp.types.ChainId - } - - class Envelope { - envelope_hash: Bytes - endpoints: Endpoints - epoch_timestamp: Long - epoch_index: Int - epoch_envelope_index: Int - merkle: Bytes - previous_envelope_hash: Bytes - start_message_id: Bytes - end_message_id: Bytes - signatures: sysio.opp.types.ChainSignature [*] - } - - class Message { - header: MessageHeader - payload: MessagePayload - } - - class MessageHeader { - endpoints: Endpoints - message_id: Bytes - previous_message_id: Bytes - encoding_flags: sysio.opp.types.EncodingFlags - payload_size: Int - payload_checksum: Bytes - timestamp: Long - header_checksum: Bytes - } - - class MessagePayload { - version: Int - attestations: AttestationEntry [*] - } - -} -@enduml diff --git a/etc/schema/opp_entity_diagram-gen.svg b/etc/schema/opp_entity_diagram-gen.svg deleted file mode 100644 index 7482257f86..0000000000 --- a/etc/schema/opp_entity_diagram-gen.svg +++ /dev/null @@ -1 +0,0 @@ -sysiooppattestationstypesMessagePayloadversion: Intattestations: AttestationEntry [*]AttestationEntrytype: sysio.opp.types.AttestationTypedata_size: Intdata: BytesMessageheader: MessageHeaderpayload: MessagePayloadMessageHeaderendpoints: Endpointsmessage_id: Bytesprevious_message_id: Bytesencoding_flags: sysio.opp.types.EncodingFlagspayload_size: Intpayload_checksum: Bytestimestamp: Longheader_checksum: BytesEnvelopeenvelope_hash: Bytesendpoints: Endpointsepoch_timestamp: Longepoch_index: Intepoch_envelope_index: Intmerkle: Bytesprevious_envelope_hash: Bytesstart_message_id: Bytesend_message_id: Bytessignatures: sysio.opp.types.ChainSignature [*]Endpointsstart: sysio.opp.types.ChainIdend: sysio.opp.types.ChainIdOperatorActionaction_type: OperatorAction::ActionTypeactor: sysio.opp.types.ChainAddresstype: sysio.opp.types.OperatorTypestatus: sysio.opp.types.OperatorStatusamount: sysio.opp.types.TokenAmountChainReserveBalanceSheetkind: sysio.opp.types.ChainKindamounts: sysio.opp.types.TokenAmount [*]PretokenPurchaseactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountpretoken_count: Longindex_at_mint: LongReserveDisbursementactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountsignature: sysio.opp.types.ChainSignature [*]StakeUpdateactor: sysio.opp.types.ChainAddressstatus: sysio.opp.types.StakeStatusamount: sysio.opp.types.TokenAmountPretokenStakeChangeactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountindex_at_mint: Longindex_at_burn: LongTimestampedMessagetimestamp: Longmessage: sysio.opp.MessagePretokenYieldactor: sysio.opp.types.ChainAddressamount: sysio.opp.types.TokenAmountindex_at_mint: LongWireTokenPurchaseactor: sysio.opp.types.ChainAddressamounts: sysio.opp.types.TokenAmount [*]ProtocolStatechain_id: sysio.opp.types.ChainIdcurrent_message_id: Bytesprocessed_message_id: Bytesincoming_messages: TimestampedMessage [*]outgoing: sysio.opp.Message [*]OperatorAction::ActionTypeACTION_TYPE_UNKNOWNACTION_TYPE_DEPOSITACTION_TYPE_WITHDRAWOperatorStatusOPERATOR_STATUS_UNKNOWNOPERATOR_STATUS_WARMUPOPERATOR_STATUS_COOLDOWNOPERATOR_STATUS_ACTIVEOPERATOR_STATUS_TERMINATEDOPERATOR_STATUS_SLASHEDTokenAmountkind: TokenKindamount: LongChainAddresskind: ChainKindaddress: BytesOperatorTypeOPERATOR_TYPE_UNKNOWNOPERATOR_TYPE_PRODUCEROPERATOR_TYPE_BATCHOPERATOR_TYPE_UNDERWRITEROPERATOR_TYPE_CHALLENGERChainIdkind: ChainKindid: IntChainKindCHAIN_KIND_UNKNOWNCHAIN_KIND_WIRECHAIN_KIND_ETHEREUMCHAIN_KIND_SOLANACHAIN_KIND_SUITokenKindTOKEN_KIND_WIRETOKEN_KIND_ETHTOKEN_KIND_LIQETHTOKEN_KIND_SOLTOKEN_KIND_LIQSOLChainSignatureactor: ChainAddresskey_type: ChainKeyTypesignature: BytesStakeStatusSTAKE_STATUS_UNKNOWNSTAKE_STATUS_WARMUPSTAKE_STATUS_COOLDOWNSTAKE_STATUS_ACTIVESTAKE_STATUS_TERMINATEDSTAKE_STATUS_SLASHEDChainKeyTypeCHAIN_KEY_TYPE_UNKNOWNCHAIN_KEY_TYPE_WIRECHAIN_KEY_TYPE_WIRE_BLSCHAIN_KEY_TYPE_ETHEREUMCHAIN_KEY_TYPE_SOLANACHAIN_KEY_TYPE_SUIAttestationTypeATTESTATION_TYPE_UNSPECIFIEDATTESTATION_TYPE_STAKEATTESTATION_TYPE_UNSTAKEATTESTATION_TYPE_PRETOKEN_PURCHASEATTESTATION_TYPE_PRETOKEN_YIELDATTESTATION_TYPE_RESERVE_BALANCE_SHEETATTESTATION_TYPE_STAKE_UPDATEATTESTATION_TYPE_NATIVE_YIELD_REWARDATTESTATION_TYPE_WIRE_TOKEN_PURCHASEATTESTATION_TYPE_OPERATOR_REG_DEREGATTESTATION_TYPE_CHALLENGE_RESPONSEATTESTATION_TYPE_SLASH_OPERATOREncodingFlagsendianness: Endiannesshash_algorithm: HashAlgorithmlength_encoding: LengthEncodingEndiannessENDIANNESS_BIGENDIANNESS_LITTLEHashAlgorithmHASH_ALGORITHM_KECCAK256HASH_ALGORITHM_SHA256HASH_ALGORITHM_RESERVED_1HASH_ALGORITHM_RESERVED_2LengthEncodingLENGTH_ENCODING_VARUINTLENGTH_ENCODING_UINT32 diff --git a/etc/schema/opp_entity_diagram.puml b/etc/schema/opp_entity_diagram.puml deleted file mode 100644 index c13b42b144..0000000000 --- a/etc/schema/opp_entity_diagram.puml +++ /dev/null @@ -1,278 +0,0 @@ -@startuml OPP_Entity_Diagram -!theme plain - -title **Outpost Propagation Protocol — Entity Diagram** - -' ═══════════════════════════════════════════════════ -' Types -' ═══════════════════════════════════════════════════ -package opp.types #F5F5F5 { - - enum chain_kind_t <> { - unknown = 0, - wire = 1, - ethereum = 2, - solana = 3, - sui = 4, - } - - entity chain_address { - * chain_kind : chain_kind_t - * address : bytes - } - - enum token_kind_t <> { - wire = 0, - eth = 0x0100, - liqeth = 0x01F0, - sol = 0x0200, - liqsol = 0x02F0, - } - - entity token_amount { - * kind : token_kind_t - * amount : int64 <> - } - - enum encoding_flags_t <> { - big_endian = 0x00 - little_endian = 0x01 - -- - keccak256 = 0x00 - sha256 = 0x02 - reserved_alt_hash_1 = 0x04 - reserved_alt_hash_2 = 0x06 - -- - varuint_encoding = 0x00 - uint32_length = 0x08 - -- - reserved = 0xF0 - } - - enum attestation_type_t <> { - reserve_balance_sheet = 0xAA00 - stake = 0x0BB9 - unstake = 0x0BBA - pretoken_purchase = 0x0BBB - pretoken_yield = 0x0BBE - -- - stake_update = 0xEE00 - native_yield_reward = 0xEE01 - wire_token_purchase = 0xEE02 - operator_reg_dereg = 0xEE03 - challenge_response = 0xEE04 - slash_operator = 0xEE05 - } - - enum stake_status_t <> { - unknown = 0 - warmup = 0x01 - cooldown = 0x02 - active = 0x03 - terminated = 0xF0 - slashed = 0xF1 - } - - enum operator_type_t <> { - unknown = 0x00 - producer = 0x01 - batch = 0x02 - underwriter = 0x03 - challenger = 0x04 - - } - - enum operator_status_t <> { - unknown = 0x00 - warmup = 0x01 - cooldown = 0x02 - active = 0x03 - terminated = 0xF0 - slashed = 0xF1 - } -} - -' ═══════════════════════════════════════════════════ -' OPP MESSAGE -' ═══════════════════════════════════════════════════ - -package opp #E8EAF6 { - - entity message_endpoints { - * start : opp.types.chain_kind_t - * end : opp.types.chain_kind_t - } - - entity message_header { - * endpoints : message_endpoints - * message_id : bytes32 // (last 8B = seq#) - * previous_message_id : bytes32 - * encoding_flags : opp.types.encoding_flags_t - * payload_size : uint32 - * payload_checksum : bytes32 - * timestamp : uint64 // (ms since epoch) - * header_checksum : bytes32 - } - - entity message_payload { - * version : uint8 // (MSB==0; 0x00–0x7F) - * attestations : list - } - - entity attestation_entry { - * type : attestation_type_t - * data_size : uint16 - * data : bytes - } - - entity envelope { - * envelope_hash : bytes32 - * epoch_index : uint32 - * epoch_timestamp : uint64 - * previous_envelope_hash : bytes32 - * merkle : bytes32 - * start_message_id : bytes32 - * end_message_id : bytes32 - -- - * signatures : list - } -} - -' ═══════════════════════════════════════════════════ -' OUTPOST PRE-LAUNCH ASSERTIONS -' ═══════════════════════════════════════════════════ - -package opp.attestations #FCE4EC { - - entity reserve_balance_sheet { - * chain_id : chain_kind_t - * amounts : token_amount[] - } - - entity stake { - * actor : chain_address - * amount : token_amount - * pretoken_count : uint256 - * index_at_mint : uint256 - } - - entity unstake { - * unstaker : chain_address - * amount : token_amount - * pretoken_count : uint256 - * index_at_burn : uint256 - - } - - entity pretoken_purchase { - * actor : chain_address - * amount : token_amount - * pretoken_count : uint256 - * index_at_mint : uint256 - } - - entity pretoken_yield { - * actor : chain_address - * amount : token_amount - * index_at_mint : uint256 - } - - entity stake_update { - * actor : chain_address - * status : opp.types.stake_status_t - * amount : token_amount - } - - ' entity yield_reward { - ' Per-staker reward distribution - ' scheduled to $WIRE accounts - ' } - - entity wire_token_purchase { - * account : bytes - * amounts : token_amount[] - } - - entity operator_reg_dereg { - * account : bytes - * type : opp.types.operator_type_t - * status : opp.types.operator_status_t - } - - ' entity challenge_response { - ' Prioritized in next - ' message_chain build - ' -- - ' Details TBD - ' } - - ' entity slash_operator { - ' Reclaim collateral → - ' reward challengers - ' -- - ' Details TBD - ' } - - entity reserve_disbursement { - * actor : chain_address - * amount : token_amount - } - - entity protocol_state { - * chain_id : uint32 - * current_message_id : bytes32 - * processed_message_id : bytes32 - * incoming_messages : list> - * outgoing : list - } -} - -' ═══════════════════════════════════════════════════ -' RELATIONSHIPS -' ═══════════════════════════════════════════════════ - -' -- Message structure -- -' opp.message_header ||--|| opp.message_payload : contains > -' opp.message_payload ||--o{ -' attestation_entry : "0..N" - -' ' -- Enum references from message -- -' opp.message_header ..> opp.types.chain_kind_t : from_chain,\nto_chain -' opp.message_payload ..> opp.types.encoding_flags_t : encoding_flags_t - -' ' -- Assertion discriminant -- -' opp.attestation_entry --> opp.types.attestation_type_t : type\ndiscriminant - -' ' -- Epoch spans messages -- -' envelope ..> opp.message_header : spans > -' } - -' -- Assertion entry resolves to concrete payloads -- -opp.attestation_entry ..> opp.attestations.reserve_balance_sheet -opp.attestation_entry ..> opp.attestations.stake -opp.attestation_entry ..> opp.attestations.unstake -opp.attestation_entry ..> opp.attestations.pretoken_purchase -opp.attestation_entry ..> opp.attestations.pretoken_yield -opp.attestation_entry ..> opp.attestations.stake_update -' opp.attestation_entry ..> opp.attestations.yield_reward ' entity commented out -opp.attestation_entry ..> opp.attestations.wire_token_purchase -opp.attestation_entry ..> opp.attestations.operator_reg_dereg -' opp.attestation_entry ..> opp.attestations.challenge_response ' entity commented out -' opp.attestation_entry ..> opp.attestations.slash_operator ' entity commented out -opp.attestation_entry ..> opp.attestations.reserve_disbursement - -' -- Sub-message composition -- -opp.attestations.wire_token_purchase --> opp.types.token_amount : includes > -opp.attestations.reserve_disbursement --> opp.types.token_amount : includes > -opp.attestations.reserve_balance_sheet ..> opp.types.token_amount : includes > -opp.attestations.reserve_balance_sheet ..> opp.types.chain_kind_t - -' -- Enum usage in post-launch attestations -- -opp.attestations.stake_update --> opp.types.stake_status_t -opp.attestations.operator_reg_dereg --> opp.types.operator_type_t -opp.attestations.operator_reg_dereg --> opp.types.operator_status_t - -' -- State tracks message chain -- -opp.attestations.protocol_state ..> opp.message_header : tracks > - -@enduml diff --git a/libraries/libfc/include/fc/slug_name.hpp b/libraries/libfc/include/fc/slug_name.hpp index d3c9760af1..3cca85de37 100644 --- a/libraries/libfc/include/fc/slug_name.hpp +++ b/libraries/libfc/include/fc/slug_name.hpp @@ -145,8 +145,8 @@ using slug_name_literals::operator""_s; /// /// Render a slug as its spelling. TOTAL, like sysio::chain::name — a renderer is /// a READ path, and a throwing one turns one bad row into a failure of everything -/// that scans it (an unspellable code would stall every underwriter_plugin commit, -/// not just that cell). Validation lives on the WRITE path: from_variant's string +/// that scans it (an unspellable code would stall every relay that reads the +/// registry, not just that cell). Validation lives on the WRITE path: from_variant's string /// arm goes through the validating constructor, so a non-canonical spelling is /// refused at construction. /// diff --git a/libraries/opp/include/sysio/opp/depot/chains_registry.hpp b/libraries/opp/include/sysio/opp/depot/chains_registry.hpp index a383df47b1..b87d34b8be 100644 --- a/libraries/opp/include/sysio/opp/depot/chains_registry.hpp +++ b/libraries/opp/include/sysio/opp/depot/chains_registry.hpp @@ -1,16 +1,12 @@ #pragma once -#include -#include - /** * @file * Depot-side view of the `sysio.chains::chains` registry row. * - * Both `batch_operator_plugin` and `underwriter_plugin` read the same rows to - * discover the chains they serve and the remote contracts they talk to. The - * field spellings and the one non-obvious decoding rule live here so the two - * daemons cannot drift apart — no plugin dependency, no duplicated literals. + * The batch-operator relay reads these rows to discover its served chains and + * remote contracts. The shared field spellings keep registry clients + * consistent without plugin dependencies. * * Keep in lockstep with `chain_row` in * `contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp`. @@ -34,37 +30,9 @@ namespace field { /// set against the row's kind before it is stored, so a non-empty value here /// is already well-formed for that chain; a reader only has to check presence. namespace outpost_addr { - inline constexpr auto opp_addr = "opp_addr"; - inline constexpr auto opp_inbound_addr = "opp_inbound_addr"; - inline constexpr auto operator_registry_addr = "operator_registry_addr"; - inline constexpr auto source_deposit_addr = "source_deposit_addr"; + inline constexpr auto opp_addr = "opp_addr"; + inline constexpr auto opp_inbound_addr = "opp_inbound_addr"; } } -/** - * @brief Resolve the address of one remote role for a chain's deployment shape. - * - * An SVM outpost is ONE program serving every role: the registry stores it in - * `opp_addr` and `sysio.chains` REQUIRES the role fields to be empty, so every - * role resolves to `opp_addr`. An EVM outpost deploys a SEPARATE contract per - * role, so a role resolves to its own field and NOTHING else. - * - * The EVM case must not fall back to `opp_addr`. `setoutpost` accepts a partial - * EVM set — a row may legitimately carry the OPP address before its - * OperatorRegistry is deployed — and substituting `opp_addr` there would yield a - * plausible, non-empty, WRONG address: it passes any is-it-configured check and - * then sends commits or verification reads to the OPP contract. An unset EVM - * role stays empty so the caller fails closed and waits for `setoutpost`. - * - * @param role_addr Role-specific field from the row's `outpost` struct. - * @param opp_addr The row's `opp_addr` field. - * @param single_program True when one program serves every role (SVM). - * @return The role's address, or empty when this role is not configured yet. - */ -inline std::string resolve_role_addr(std::string_view role_addr, - std::string_view opp_addr, - bool single_program) { - return single_program ? std::string{opp_addr} : std::string{role_addr}; -} - } // namespace sysio::opp::depot::chains diff --git a/libraries/opp/include/sysio/opp/depot/opreg_status.hpp b/libraries/opp/include/sysio/opp/depot/opreg_status.hpp index 7512f73070..ec56dbe910 100644 --- a/libraries/opp/include/sysio/opp/depot/opreg_status.hpp +++ b/libraries/opp/include/sysio/opp/depot/opreg_status.hpp @@ -8,7 +8,7 @@ * @file * Depot-side helper for the `sysio.opreg::operators[].status` field. * - * Both `batch_operator_plugin` and `underwriter_plugin` poll their own status + * The batch-operator relay polls its own status * row each tick to decide whether to keep relaying. The spelling set IS the * protobuf `OperatorStatus` enum, so the string is parsed through the generated * descriptor rather than compared against copies of its names — a proto rename diff --git a/libraries/opp/include/sysio/opp/opp.hpp b/libraries/opp/include/sysio/opp/opp.hpp index 6bf650614a..0200a20b64 100644 --- a/libraries/opp/include/sysio/opp/opp.hpp +++ b/libraries/opp/include/sysio/opp/opp.hpp @@ -8,7 +8,7 @@ * Provides FC_REFLECT_ENUM for all OPP protobuf enum types so they work with * fc::variant serialization, ABI serializer, and the testing framework. * - * Used by: contract unit tests, batch_operator_plugin, underwriter_plugin, + * Used by: contract unit tests, batch_operator_plugin, * and any host-side code that needs to serialize OPP enum values. */ @@ -27,14 +27,6 @@ FC_REFLECT_ENUM(sysio::opp::types::ChainKind, (CHAIN_KIND_EVM) (CHAIN_KIND_SVM)) -FC_REFLECT_ENUM(sysio::opp::types::ChainKeyType, - (CHAIN_KEY_TYPE_UNKNOWN) - (CHAIN_KEY_TYPE_WIRE) - (CHAIN_KEY_TYPE_WIRE_BLS) - (CHAIN_KEY_TYPE_ETHEREUM) - (CHAIN_KEY_TYPE_SOLANA) - (CHAIN_KEY_TYPE_SUI)) - // --------------------------------------------------------------------------- // Operator enums // --------------------------------------------------------------------------- @@ -68,13 +60,6 @@ FC_REFLECT_ENUM(sysio::opp::types::TokenKind, (TOKEN_KIND_SPL_NFT) (TOKEN_KIND_LIQ)) -// Reserve lifecycle -FC_REFLECT_ENUM(sysio::opp::types::ReserveStatus, - (RESERVE_STATUS_UNKNOWN) - (RESERVE_STATUS_PENDING) - (RESERVE_STATUS_ACTIVE) - (RESERVE_STATUS_CANCELLED)) - // --------------------------------------------------------------------------- // Attestation enums // --------------------------------------------------------------------------- @@ -82,29 +67,9 @@ FC_REFLECT_ENUM(sysio::opp::types::ReserveStatus, FC_REFLECT_ENUM(sysio::opp::types::AttestationType, (ATTESTATION_TYPE_UNSPECIFIED) (ATTESTATION_TYPE_OPERATOR_ACTION) - (ATTESTATION_TYPE_STAKE) - (ATTESTATION_TYPE_UNSTAKE) - (ATTESTATION_TYPE_PRETOKEN_PURCHASE) - (ATTESTATION_TYPE_PRETOKEN_YIELD) - (ATTESTATION_TYPE_RESERVE_BALANCE_SHEET) - (ATTESTATION_TYPE_STAKE_UPDATE) - (ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE) - (ATTESTATION_TYPE_CHALLENGE_RESPONSE) - (ATTESTATION_TYPE_SWAP_REQUEST) - (ATTESTATION_TYPE_SWAP_REMIT) - (ATTESTATION_TYPE_CHALLENGE_REQUEST) (ATTESTATION_TYPE_OPERATORS) (ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS) (ATTESTATION_TYPE_NODE_OWNER_REG) - (ATTESTATION_TYPE_STAKE_RESULT) - (ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR) - (ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT) - (ATTESTATION_TYPE_SWAP_REVERT) - (ATTESTATION_TYPE_DEPOSIT_REVERT) - (ATTESTATION_TYPE_RESERVE_CREATE_CANCEL) - (ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED) - (ATTESTATION_TYPE_RESERVE_READY) - (ATTESTATION_TYPE_EMISSIONS_BLOCKED) (ATTESTATION_TYPE_SYNDICATE_LIQ) (ATTESTATION_TYPE_LIQ_YIELD) (ATTESTATION_TYPE_DESYNDICATE_LIQ)) @@ -127,9 +92,6 @@ FC_REFLECT_ENUM(sysio::opp::attestations::OperatorAction_ActionType, (OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT) (OperatorAction_ActionType_ACTION_TYPE_SLASH)) -// ReserveTarget_Kind removed in the data-model refactor — the carrier (renamed `ReserveAmount`) -// is now (chain_code, reserve_code, TokenAmount) in the depot 9-dec frame. - FC_REFLECT_ENUM(sysio::opp::types::AttestationStatus, (ATTESTATION_STATUS_PENDING) (ATTESTATION_STATUS_READY) @@ -139,13 +101,6 @@ FC_REFLECT_ENUM(sysio::opp::types::AttestationStatus, // Message channel / depot enums // --------------------------------------------------------------------------- -FC_REFLECT_ENUM(sysio::opp::types::ChainRequestStatus, - (CHAIN_REQUEST_STATUS_PENDING) - (CHAIN_REQUEST_STATUS_COLLECTING) - (CHAIN_REQUEST_STATUS_CONSENSUS_OK) - (CHAIN_REQUEST_STATUS_CONSENSUS_FAIL) - (CHAIN_REQUEST_STATUS_CHALLENGED)) - FC_REFLECT_ENUM(sysio::opp::types::MessageDirection, (MESSAGE_DIRECTION_INBOUND) (MESSAGE_DIRECTION_OUTBOUND)) @@ -162,31 +117,9 @@ FC_REFLECT_ENUM(sysio::opp::types::EnvelopeStatus, (ENVELOPE_STATUS_CONFIRMED)) // --------------------------------------------------------------------------- -// Underwriting enums +// Challenge and dispute enums // --------------------------------------------------------------------------- -FC_REFLECT_ENUM(sysio::opp::types::UnderwriteRequestStatus, - (UNDERWRITE_REQUEST_STATUS_PENDING) - (UNDERWRITE_REQUEST_STATUS_CONFIRMED) - (UNDERWRITE_REQUEST_STATUS_REJECTED) - (UNDERWRITE_REQUEST_STATUS_COMPLETED) - (UNDERWRITE_REQUEST_STATUS_EXPIRED)) - -FC_REFLECT_ENUM(sysio::opp::types::UnderwriteStatus, - (UNDERWRITE_STATUS_INTENT_CREATED) - (UNDERWRITE_STATUS_INTENT_SUBMITTED) - (UNDERWRITE_STATUS_INTENT_CONFIRMED) - (UNDERWRITE_STATUS_READY) - (UNDERWRITE_STATUS_RELEASED) - (UNDERWRITE_STATUS_SLASHED) - (UNDERWRITE_STATUS_DISQUALIFIED)) - -FC_REFLECT_ENUM(sysio::opp::types::ChallengeStatus, - (CHALLENGE_STATUS_CHALLENGE_SENT) - (CHALLENGE_STATUS_RESPONSE_RECEIVED) - (CHALLENGE_STATUS_RESOLVED) - (CHALLENGE_STATUS_ESCALATED)) - FC_REFLECT_ENUM(sysio::opp::types::DisputeStatus, (DISPUTE_STATUS_UNKNOWN) (DISPUTE_STATUS_OPEN) @@ -198,14 +131,6 @@ FC_REFLECT_ENUM(sysio::opp::types::NodeOwnerTier, (NODE_OWNER_TIER_T2) (NODE_OWNER_TIER_T3)) -FC_REFLECT_ENUM(sysio::opp::types::StakeStatus, - (STAKE_STATUS_UNKNOWN) - (STAKE_STATUS_WARMUP) - (STAKE_STATUS_COOLDOWN) - (STAKE_STATUS_ACTIVE) - (STAKE_STATUS_TERMINATED) - (STAKE_STATUS_SLASHED)) - FC_REFLECT_ENUM(sysio::opp::debugging::DebugOutpostEndpointsType, (DEBUG_OUTPOST_ENDPOINTS_TYPE_UNKNOWN) (DEBUG_OUTPOST_ENDPOINTS_TYPE_OUTPOST_ETHEREUM_DEPOT) diff --git a/libraries/opp/proto/sysio/opp/attestations/attestations.proto b/libraries/opp/proto/sysio/opp/attestations/attestations.proto index 756c0656af..9440607ef8 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -3,78 +3,15 @@ syntax = "proto3"; package sysio.opp.attestations; import "sysio/opp/types/types.proto"; -import "sysio/opp/opp.proto"; option cc_enable_arenas = true; -// --------------------------------------------------------------------------- -// Reserve balance snapshot (Outpost -> Depot) -// -// Reports the on-chain reserve balances the outpost is custodying. Depot -// uses this as a match-or-alert signal against `sysio.reserv::reserves`. -// Renamed from `ChainReserveBalanceSheet` for the chain-centric framing. -// --------------------------------------------------------------------------- - -message ReserveBalanceSheet { - // Chain identifier (codename) on which these reserves live. - uint64 chain_code = 1; - // (fields 2-3 retired — the parallel `amounts[]` / `reserve_codes[]` - // arrays were replaced by the self-describing `reserves` entries.) - reserved 2, 3; - reserved "amounts", "reserve_codes"; - // One `ReserveAmount` per reserve being reported. Each entry is - // self-describing — (chain_code, reserve_code, token + amount) — and the - // amount is in the depot's canonical 9-decimal frame (the outpost - // converts chain-native units at the boundary; see `ReserveAmount`). - repeated sysio.opp.types.ReserveAmount reserves = 4; -} - -// --------------------------------------------------------------------------- -// Pre-launch specific attestations (DEPRECATED — kept for proto compatibility -// during the deprecation pass) -// --------------------------------------------------------------------------- - -message PretokenStakeChange { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount amount = 2; - int64 index_at_mint = 10; - int64 index_at_burn = 11; -} - -message PretokenPurchase { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount amount = 2; - int64 pretoken_count = 3; - int64 index_at_mint = 10; -} - -message PretokenYield { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount amount = 2; - int64 index_at_mint = 3; -} - -// --------------------------------------------------------------------------- -// Post-launch attestations -// --------------------------------------------------------------------------- - -message StakeUpdate { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.StakeStatus status = 2; - sysio.opp.types.TokenAmount amount = 3; -} - -message WireTokenPurchase { - sysio.opp.types.ChainAddress actor = 1; - repeated sysio.opp.types.TokenAmount amounts = 2; -} - // --------------------------------------------------------------------------- // Operator action — DEPOSIT_REQUEST / WITHDRAW_REQUEST / WITHDRAW_REMIT / SLASH // -// Identity carriers updated for the data-model refactor: -// * `chain_code` (codename / uint64) — was `ChainKind chain`. -// * `amount.token_code` (codename) — was `amount.kind` (TokenKind enum). +// An audit record of a collateral change on `sysio.opreg`, kept in each +// operator's `recent_actions` (OperatorActionLog). Collateral is depot-native, +// so no outpost holds or releases it. // --------------------------------------------------------------------------- message OperatorAction { @@ -93,18 +30,14 @@ message OperatorAction { sysio.opp.types.TokenAmount amount = 5; // Links a WITHDRAW_REMIT back to its originating WITHDRAW_REQUEST. uint64 request_id = 6; - // Chain identifier (codename) of the outpost holding the escrow. For - // DEPOSIT_REQUEST / WITHDRAW_REQUEST identifies the holding outpost; for - // SLASH identifies the outpost expected to seize its share; for - // WITHDRAW_REMIT identifies the outpost that must release. + // Chain identifier (codename) of the balance row the action changed. uint64 chain_code = 7; // Human-readable reason recorded by the depot. Populated on SLASH and // on log-only failure paths; empty otherwise. string reason = 8; - // SLASH-only — identifies WHICH reserve on (chain, token) receives the - // seized funds; (chain_code, reserve_code, amount) assemble into a - // `ReserveAmount` for the outpost's routing. Empty (0) for non-SLASH - // action types. + // Historical external-reserve routing metadata. Native collateral audit + // records leave this field zero; no current depot action routes a slash + // into an external reserve. uint64 reserve_code = 9; } @@ -116,12 +49,6 @@ message OperatorActionLog { string error_message = 4; } -message ReserveDisbursement { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount amount = 2; - repeated sysio.opp.types.ChainSignature signature = 3; -} - // --------------------------------------------------------------------------- // Node owner registration // --------------------------------------------------------------------------- @@ -142,122 +69,6 @@ message NodeOwnerRegistration { sysio.opp.types.WireKey wire_pub_key = 9; } -// --------------------------------------------------------------------------- -// Protocol state tracking -// --------------------------------------------------------------------------- - -message ProtocolState { - // Chain identifier (codename). - uint64 chain_code = 1; - bytes current_message_id = 2; - bytes processed_message_id = 3; - repeated sysio.opp.Message incoming_messages = 10; - repeated sysio.opp.Message outgoing_messages = 11; -} - -// --------------------------------------------------------------------------- -// Cross-chain swap + underwriting attestations -// --------------------------------------------------------------------------- - -// Cross-chain swap request (source Outpost -> Depot). -// -// Carries explicit src + dst chain/token/reserve codes. The depot's -// underwriter race uses all three codes per leg to disambiguate same-chain -// swaps (multiple reserves of the same token on the same chain). -message SwapRequest { - sysio.opp.types.ChainAddress actor = 1; - sysio.opp.types.TokenAmount source_amount = 2; - uint64 source_chain_code = 3; - uint64 source_reserve_code = 4; - uint64 target_chain_code = 5; - uint64 target_token_code = 6; - uint64 target_reserve_code = 7; - sysio.opp.types.ChainAddress recipient = 8; - // Target destination amount at deposit time, in the depot's canonical - // 9-decimal frame (the SAME frame as `source_amount`; the outpost converts - // chain-native units at the boundary). This is the EXACT amount the recipient - // receives — never less — provided the depot's live POST-FEE `swapquote` is - // within `target_tolerance_bps` of it; if the quote has drifted outside that - // tolerance the swap is reverted (SwapRevert) rather than under-filled. The - // user need not have fetched a quote before submitting (they may be - // arbitraging or simply naming a preferred amount). - uint64 target_amount = 9; - // Variance tolerance in basis points (e.g. 50 = 0.5%). - uint32 target_tolerance_bps = 10; - // Timestamp the user set their target at — informational only. - uint64 target_timestamp_ms = 11; - // Source-chain transaction id of the funding deposit. - bytes source_tx_id = 12; -} - -// Underwriter intent commit (Outposts -> Depot, one per outpost / leg). -// -// Identity carriers updated: -// * `token_code` (codename) — was `token_kind` (TokenKind enum). -// * `chain_code` (codename) — identifies the outpost this commit is from. -// * `reserve_code` (codename) — disambiguates which reserve on that -// (chain, token) pair this leg covers. -message UnderwriteIntentCommit { - sysio.opp.types.WireAccount uw_account = 1; - // External-chain transaction-signer metadata populated by the submitting - // client: 20 bytes for EVM or 32 bytes for SVM. It is covered by the WIRE - // signature below. Each outpost decodes one canonical UIC representation - // to bind this address and `uw_account` to the authenticated caller, then - // relays the original canonical bytes unchanged for depot verification. - sysio.opp.types.ChainAddress uw_ext_chain_addr = 2; - uint64 uw_request_id = 3; - bytes signature = 5; - uint64 token_code = 6; - uint64 chain_code = 7; - uint64 reserve_code = 8; -} - -// Cross-chain swap revert (Depot -> source Outpost). -// -// Emitted when the variance-tolerance check on a SwapRequest fails. -message SwapRevert { - bytes original_swap_message_id = 1; - sysio.opp.types.ChainAddress depositor = 2; - sysio.opp.types.TokenAmount refund_amount = 3; - string reason = 4; - uint64 source_chain_code = 5; - uint64 source_reserve_code = 6; -} - -// Swap-payout remittance (Depot -> destination Outpost). -message SwapRemit { - sysio.opp.types.ChainAddress recipient = 1; - sysio.opp.types.TokenAmount amount = 2; - bytes original_message_id = 3; - // Settlement address selected through authex; unlike the UIC caller - // address, this is not the originating transaction caller. - sysio.opp.types.ChainAddress underwriter = 4; - uint64 unlock_timestamp = 5; - // Destination chain + reserve identity (codenames). - uint64 chain_code = 6; - uint64 reserve_code = 7; -} - -// SwapRejected (destination Outpost -> Depot) was here — REMOVED. Every REMIT is -// depot-initiated against a verified reserve ledger, so the destination outpost can -// always pay; there is no post-underwriting rejection path (enum slot 60957 retired). - -// --------------------------------------------------------------------------- -// Challenge attestations -// --------------------------------------------------------------------------- - -message ChallengeRequest { - uint32 epoch_index = 1; - uint32 round = 2; - bytes original_chain_hash = 3; - repeated ChallengeOperatorHash operator_hashes = 4; -} - -message ChallengeOperatorHash { - sysio.opp.types.ChainAddress operator = 1; - bytes chain_hash = 2; -} - // --------------------------------------------------------------------------- // Epoch & roster management attestations // --------------------------------------------------------------------------- @@ -352,81 +163,6 @@ message BatchOperatorGroup { repeated sysio.opp.types.ChainAddress operators = 1; } -// Deposit refund (Depot -> source Outpost). -message DepositRevert { - bytes original_deposit_message_id = 1; - sysio.opp.types.ChainAddress depositor = 2; - sysio.opp.types.TokenAmount refund_amount = 3; - string reason = 4; - uint64 chain_code = 5; -} - -// --------------------------------------------------------------------------- -// Operator Registry attestations -// --------------------------------------------------------------------------- - -message NodeOwnerReg { - sysio.opp.types.ChainAddress owner_address = 1; - bytes token_id = 2; - sysio.opp.types.ChainAddress nft_address = 3; -} - -message StakeResult { - sysio.opp.types.ChainAddress owner_address = 1; - sysio.opp.types.TokenAmount amount = 2; - bool success = 3; - string error_reason = 4; -} - -message AttestationProcessingError { - uint64 attestation_id = 1; - sysio.opp.types.AttestationType original_type = 2; - bytes original_data = 3; - string error_message = 4; -} - -// Retained carriers for the retired reserve cancellation/matching lifecycle. -// Outpost -> Depot. Creator cancels their PENDING reserve. -message ReserveCreateCancel { - uint64 chain_code = 1; - uint64 token_code = 2; - uint64 reserve_code = 3; - // Proof-of-creator: must match the depot row's creator_addr. - sysio.opp.types.ChainAddress creator_addr = 4; -} - -// Depot -> Outpost. Cancel was accepted while status was still PENDING. -// Outpost refunds the external_token_amount to its local reserve's `creator`. -message ReserveCreateCancelled { - uint64 chain_code = 1; - uint64 token_code = 2; - uint64 reserve_code = 3; -} - -// Depot -> Outpost. Matcher put up requested_wire_amount WIRE; reserve ACTIVE. -// Outpost marks its local reserve record ACTIVE — usable for swap routing. -message ReserveReady { - uint64 chain_code = 1; - uint64 token_code = 2; - uint64 reserve_code = 3; -} - -// DEPRECATED — no longer emitted; do not use in new code. -// Formerly broadcast by sysio.epoch::advance when its emissions readiness -// gate could not pay the next epoch. The gate state is depot-local now -// (sysio.epoch::blocklog + advance()'s console diagnostic): no outpost -// consumes a depot-pause notification, and an unconsumed attestation type -// is a liveness hazard on outposts that reject unknown types. Message -// retained for wire-format history only. -message EmissionsBlocked { - uint32 epoch_index = 1; - sysio.opp.types.EmissionsBlockReason reason = 2; - int64 attempted_emission = 3; - int64 treasury_remaining = 4; - int64 sysio_balance = 5; - uint32 first_blocked_at = 6; // unix seconds -} - // --------------------------------------------------------------------------- // liq syndication (simple_swap) // diff --git a/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto b/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto index 81e4624147..661aa74315 100644 --- a/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto +++ b/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto @@ -10,17 +10,16 @@ option cc_enable_arenas = true; // Platform bootstrap configuration — launch-day input shape. // // Human-authored JSON instances of `BootstrapPlatformConfig` drive the launch -// bootstrap tool, replacing the hard-coded chain / token / reserve dataset in the +// bootstrap tool, replacing the hard-coded chain / token dataset in the // cluster tooling. The tool replays the config onto the chain inside the // bootstrap window (sysio.epoch current_epoch_index == 0): every registration // is priv-gated and lands ACTIVE inline. // -// Authoring conventions — these differ DELIBERATELY from the registry -// carriers in sysio/opp/types/types.proto (`Chain` / `Token` / `ChainToken` -// / `Reserve`), which are wire messages with packed-uint64 codes, raw `bytes` -// addresses, and lifecycle fields that are outputs, not inputs: +// Authoring conventions — these differ DELIBERATELY from the registry rows +// of sysio.chains and sysio.tokens, which hold packed-uint64 codes, raw +// `bytes` addresses, and lifecycle fields that are outputs, not inputs: // -// * codes are STRINGS ("ETHEREUM", "USDC", "PRIMARY"); the tool packs them +// * codes are STRINGS ("ETHEREUM", "USDC"); the tool packs them // via slug_name ([A-Z][A-Z0-9_]{0,7} -- must start with a letter, <= 8 // chars). Packed uint64s are // write-only for humans and render as opaque digit strings in JSON. diff --git a/libraries/opp/proto/sysio/opp/debugging/debugging.proto b/libraries/opp/proto/sysio/opp/debugging/debugging.proto index ce467bc941..5a47a85812 100644 --- a/libraries/opp/proto/sysio/opp/debugging/debugging.proto +++ b/libraries/opp/proto/sysio/opp/debugging/debugging.proto @@ -16,16 +16,6 @@ enum DebugOutpostEndpointsType { DEBUG_OUTPOST_ENDPOINTS_TYPE_DEPOT_OUTPOST_SOLANA = 4; } -// --------------------------------------------------------------------------- -// Envelope data record — sent by opp_tracking_plugin to debugging server -// --------------------------------------------------------------------------- - -message DebugEnvelopeDataRecord { - string batch_op_name = 1; - DebugOutpostEndpointsType endpoints_type = 2; - bytes envelope_data = 3; -} - // --------------------------------------------------------------------------- // Envelope metadata record — stored alongside data files on the server // --------------------------------------------------------------------------- diff --git a/libraries/opp/proto/sysio/opp/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index c181e2f4fc..284046906f 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -23,27 +23,17 @@ enum ChainKind { // Chain instance identifier — used by `Envelope.Endpoints` to identify // source / destination chains by `(ChainKind, external_chain_id)`. The -// first-class registry row is `Chain` (defined below); `ChainId` is the -// compact projection carried on the OPP wire for envelope endpoints. +// registry row lives in `sysio.chains`; `ChainId` is the compact projection +// carried on the OPP wire for envelope endpoints. message ChainId { ChainKind kind = 1; uint32 id = 2; } -enum ChainKeyType { - CHAIN_KEY_TYPE_UNKNOWN = 0; - CHAIN_KEY_TYPE_WIRE = 1; - CHAIN_KEY_TYPE_WIRE_BLS = 2; - CHAIN_KEY_TYPE_ETHEREUM = 3; - CHAIN_KEY_TYPE_SOLANA = 4; - CHAIN_KEY_TYPE_SUI = 5; -} - // Wire public_key variant type. Identifies which `sysio::public_key` alternative the `WireKey.key` // raw bytes decode to. The bytes carry NO leading variant-index byte -- this enum is the type // discriminant instead. Values are (variant index + 1), leaving 0 for UNKNOWN per the proto3 -// convention used throughout this file. Distinct from `ChainKeyType`, which is keyed on chain family -// (WIRE/ETHEREUM/SOLANA/...) and cannot express R1 or WebAuthn. +// convention used throughout this file. enum WireKeyType { WIRE_KEY_TYPE_UNKNOWN = 0; WIRE_KEY_TYPE_K1 = 1; // secp256k1 (sysio::public_key variant index 0) @@ -70,35 +60,16 @@ message ChainAddress { bytes address = 2; } -message ChainSignature { - ChainAddress actor = 1; - ChainKeyType key_type = 2; - bytes signature = 3; -} - message WireAccount { string name = 1; } -message WirePermission { - WireAccount account = 1; - string permission = 2; // e.g. "active", "owner" -} - -enum UnderwriteRequestStatus { - UNDERWRITE_REQUEST_STATUS_PENDING = 0; - UNDERWRITE_REQUEST_STATUS_CONFIRMED = 1; - UNDERWRITE_REQUEST_STATUS_REJECTED = 2; - UNDERWRITE_REQUEST_STATUS_COMPLETED = 3; - UNDERWRITE_REQUEST_STATUS_EXPIRED = 4; -} - // --------------------------------------------------------------------------- // Token standards // // Per-token identity (ETH, USDC, POLY, LIQETH, …) is data, NOT enum values. // This enum carries only the TOKEN STANDARD; individual tokens get a -// `Token` registry row keyed by `Token.code` (slug_name / uint64). +// `sysio.tokens` registry row keyed by their code (slug_name / uint64). // --------------------------------------------------------------------------- enum TokenKind { @@ -129,100 +100,6 @@ message TokenAmount { int64 amount = 2; } -// --------------------------------------------------------------------------- -// Reserve lifecycle -// --------------------------------------------------------------------------- - -enum ReserveStatus { - RESERVE_STATUS_UNKNOWN = 0; - RESERVE_STATUS_PENDING = 1; // created on outpost; awaiting depot-side match - RESERVE_STATUS_ACTIVE = 2; // matched / bootstrap-seeded; usable for swaps - RESERVE_STATUS_CANCELLED = 3; // creator cancelled before match landed -} - -// --------------------------------------------------------------------------- -// Registry entities (Chain / Token / ChainToken / Reserve / ReserveAmount) -// -// These are the proto carriers for the depot's registry tables on the new -// contracts `sysio.chains` and `sysio.tokens` (+ existing `sysio.reserv`). -// Wire format: `code` fields are uint64 (packed slug_name); see slug_name.hpp -// on the contract side and fc/slug_name.hpp on the host side. -// --------------------------------------------------------------------------- - -message Chain { - ChainKind kind = 1; - uint64 code = 2; // PRIMARY KEY — slug_name - uint32 external_chain_id = 3; // e.g. 1 (ETH mainnet), 137 (Polygon), 0 (WIRE / SOL mainnet) - string name = 4; - string description = 5; - bool is_depot = 6; // exactly one row has true (code = "WIRE") - bool active = 7; - uint64 registered_at_ms = 8; - uint64 activated_at_ms = 9; -} - -message Token { - TokenKind kind = 1; - uint64 code = 2; // PRIMARY KEY — slug_name - string symbol_name = 3; // "Ethereum" / "USD Coin" / "Liquid ETH" - string description = 4; - uint32 precision = 5; // default 9 - ChainAddress address = 6; // canonical chain-of-origin address for non-native tokens; empty for NATIVE - bool active = 7; - uint64 registered_at_ms = 8; - uint64 activated_at_ms = 9; -} - -message ChainToken { - uint64 chain_code = 1; - uint64 token_code = 2; - bytes contract_addr = 3; // chain-specific encoding; empty for native - // (field 4 retired — `precision_override` was removed; precision conversion - // is purely an outpost-internal concern, not depot-tracked state.) - reserved 4; - reserved "precision_override"; - bool is_native = 5; // exactly one per Chain - bool active = 6; - uint64 registered_at_ms = 7; - uint64 activated_at_ms = 8; -} - -message Reserve { - uint64 chain_code = 1; - uint64 token_code = 2; - uint64 code = 3; - string name = 4; - string description = 5; - ReserveStatus status = 6; - uint64 reserve_chain_amount = 7; - uint64 reserve_wire_amount = 8; - uint32 connector_weight_bps = 9; // Bancor WIRE-side weight in bps (1..9999); 5000 = 50/50 pure constant product. Honored by the weighted swap curve. 10000 is rejected by sysio.reserv: it zeroes the token-side weight, making every quote 0 (a dead, unpriceable reserve). - ChainAddress creator_addr = 10; - uint64 requested_wire_amount = 11; - uint64 external_token_amount = 12; - uint64 registered_at_ms = 13; - uint64 activated_at_ms = 14; - uint64 cancelled_at_ms = 15; - uint32 source_token_precision = 16; // depot-frame precision of the source token side = min(native, 9); self-describing, never assumed. The WIRE/target side is always 9 (WIRE_PRECISION), so it is not carried. - reserved 17; // was wire_precision (always 9 = WIRE_PRECISION); the target side is never carried, so there is no target_token_precision - reserved "wire_precision"; -} - -// Canonical reserve-scoped amount carrier. Identifies WHICH reserve — -// (chain_code, reserve_code, amount.token_code) — holds or receives the -// amount, unambiguously even when multiple reserves exist for the same -// (chain, token) pair. `amount.amount` is ALWAYS expressed in the depot's -// canonical 9-decimal frame: outpost-side producers convert chain-native -// units at the boundary (EVM `PrecisionLib.toDepot`, SVM -// `precision::to_depot`) before shipping, exactly like the swap-request -// paths. Carried by RESERVE_BALANCE_SHEET (per-reserve balance entries) and SLASH routing -// (seized-funds destination). -message ReserveAmount { - uint64 chain_code = 1; - uint64 reserve_code = 2; - TokenAmount amount = 3; -} - // --------------------------------------------------------------------------- // Attestation discriminant // --------------------------------------------------------------------------- @@ -230,53 +107,14 @@ message ReserveAmount { enum AttestationType { ATTESTATION_TYPE_UNSPECIFIED = 0; ATTESTATION_TYPE_OPERATOR_ACTION = 2001; // 0x07D1 - ATTESTATION_TYPE_STAKE = 3001; - ATTESTATION_TYPE_UNSTAKE = 3002; - // DEPRECATED — pre-launch only, do not use in new code. - ATTESTATION_TYPE_PRETOKEN_PURCHASE = 3004; - // DEPRECATED — pre-launch only, do not use in new code. - ATTESTATION_TYPE_PRETOKEN_YIELD = 3006; - ATTESTATION_TYPE_RESERVE_BALANCE_SHEET = 43520; // 0xAA00 - ATTESTATION_TYPE_STAKE_UPDATE = 60928; // 0xEE00 // 60929 (0xEE01) was ATTESTATION_TYPE_NATIVE_YIELD_REWARD — removed; do not reuse. - // DEPRECATED — pre-launch only, do not use in new code. - ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE = 60930; - ATTESTATION_TYPE_CHALLENGE_RESPONSE = 60932; // 60933 was standalone SLASH_OPERATOR — removed; SLASH is now an OperatorAction sub-type. - ATTESTATION_TYPE_SWAP_REQUEST = 60934; - // 60935 was ATTESTATION_TYPE_UNDERWRITE_INTENT — removed; do not reuse. - // 60936 was ATTESTATION_TYPE_UNDERWRITE_CONFIRM — removed; do not reuse. - // 60937 was ATTESTATION_TYPE_UNDERWRITE_REJECT — removed; do not reuse. - // 60938 was ATTESTATION_TYPE_UNDERWRITE_UNLOCK — removed; do not reuse. - ATTESTATION_TYPE_SWAP_REMIT = 60944; - ATTESTATION_TYPE_CHALLENGE_REQUEST = 60945; // 60946 was ATTESTATION_TYPE_EPOCH_SYNC — removed; do not reuse. ATTESTATION_TYPE_OPERATORS = 60947; // 60948 was ATTESTATION_TYPE_REMIT_CONFIRM — removed; do not reuse. ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS = 60943; ATTESTATION_TYPE_NODE_OWNER_REG = 60949; - ATTESTATION_TYPE_STAKE_RESULT = 60951; - ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR = 60952; - ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT = 60953; - // 60954 was ATTESTATION_TYPE_UNDERWRITE_INTENT_REJECT — removed; do not reuse. - ATTESTATION_TYPE_SWAP_REVERT = 60955; - ATTESTATION_TYPE_DEPOSIT_REVERT = 60956; - // 60957 was ATTESTATION_TYPE_SWAP_REJECTED — removed; do not reuse. Every REMIT is - // depot-initiated against a verified reserve ledger, so the destination outpost can - // always pay; there is no post-underwriting rejection path. - - // --------------------------------------------------------------------------- - // Reserve-flow attestations (data-model refactor, 2026-05-19) - // Retained cancellation and matching carriers for the retired reserve flow. - // --------------------------------------------------------------------------- - ATTESTATION_TYPE_RESERVE_CREATE_CANCEL = 60959; // 0xEE1F outpost → depot - ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED = 60960; // 0xEE20 depot → outpost - ATTESTATION_TYPE_RESERVE_READY = 60961; // 0xEE21 depot → outpost - // DEPRECATED — no longer emitted; do not use in new code. The emissions - // gate state is depot-local (sysio.epoch::blocklog); no outpost consumes - // a cross-chain notification of it. Slot retained; do not reuse. - ATTESTATION_TYPE_EMISSIONS_BLOCKED = 60962; // 0xEE22 // --------------------------------------------------------------------------- // liq syndication attestations (simple_swap, 2026-09-12) @@ -292,8 +130,7 @@ enum AttestationType { // Reason codes recorded by sysio.epoch::advance's emissions readiness gate // in the depot-local sysio.epoch::blocklog table when it cannot pay the -// next epoch. (Formerly also carried by the retired EMISSIONS_BLOCKED -// attestation.) +// next epoch. These diagnostics remain depot-local. enum EmissionsBlockReason { EMISSIONS_BLOCK_REASON_UNSPECIFIED = 0; EMISSIONS_BLOCK_REASON_CONFIG_MISSING = 1; // sysio.system::setemitcfg not called @@ -306,15 +143,6 @@ enum EmissionsBlockReason { // Staker / Operator lifecycle // --------------------------------------------------------------------------- -enum StakeStatus { - STAKE_STATUS_UNKNOWN = 0; - STAKE_STATUS_WARMUP = 1; - STAKE_STATUS_COOLDOWN = 2; - STAKE_STATUS_ACTIVE = 3; - STAKE_STATUS_TERMINATED = 240; - STAKE_STATUS_SLASHED = 241; -} - enum OperatorType { OPERATOR_TYPE_UNKNOWN = 0; OPERATOR_TYPE_PRODUCER = 1; @@ -336,14 +164,6 @@ enum OperatorStatus { // Depot contract state enums // --------------------------------------------------------------------------- -enum ChainRequestStatus { - CHAIN_REQUEST_STATUS_PENDING = 0; - CHAIN_REQUEST_STATUS_COLLECTING = 1; - CHAIN_REQUEST_STATUS_CONSENSUS_OK = 2; - CHAIN_REQUEST_STATUS_CONSENSUS_FAIL = 3; - CHAIN_REQUEST_STATUS_CHALLENGED = 4; -} - enum MessageDirection { MESSAGE_DIRECTION_INBOUND = 0; MESSAGE_DIRECTION_OUTBOUND = 1; @@ -368,28 +188,6 @@ enum AttestationStatus { ATTESTATION_STATUS_PROCESSED = 2; } -enum UnderwriteStatus { - UNDERWRITE_STATUS_INTENT_CREATED = 0; - UNDERWRITE_STATUS_INTENT_SUBMITTED = 1; - UNDERWRITE_STATUS_INTENT_CONFIRMED = 2; - UNDERWRITE_STATUS_READY = 3; - UNDERWRITE_STATUS_RELEASED = 5; - UNDERWRITE_STATUS_SLASHED = 10; - // Candidate-specific, pre-settlement invalidity in the underwriter race (bad - // UIC signature, insufficient bond, missing destination authex link). The - // candidate is removed from contention but NO collateral was slashed — kept - // distinct from SLASHED (economic punishment) and RELEASED (clean race - // loser) so indexing/audits do not conflate them. - UNDERWRITE_STATUS_DISQUALIFIED = 11; -} - -enum ChallengeStatus { - CHALLENGE_STATUS_CHALLENGE_SENT = 0; - CHALLENGE_STATUS_RESPONSE_RECEIVED = 1; - CHALLENGE_STATUS_RESOLVED = 2; - CHALLENGE_STATUS_ESCALATED = 3; -} - // Node-owner registration tier (sysio.roa::nodeownreg). Mirrors the MockWireNodes NodeInfo tier and // the on-chain `check(tier >= 1 && tier <= 3)` range -- T1/T2/T3 are the only valid registration tiers. enum NodeOwnerTier { diff --git a/libraries/opp/test/test_chains_registry.cpp b/libraries/opp/test/test_chains_registry.cpp index d5ebdf1c01..3f8472dac4 100644 --- a/libraries/opp/test/test_chains_registry.cpp +++ b/libraries/opp/test/test_chains_registry.cpp @@ -1,11 +1,10 @@ /// Pure-logic unit tests for the shared `sysio.chains::chains` row view -/// consumed by `batch_operator_plugin` and `underwriter_plugin`. +/// consumed by `batch_operator_plugin`. /// /// The chain read itself happens through `chain_plugin::read_table_rows` /// (integration territory; covered by the flow tests in `wire-tools-ts`). /// What is pinned here is the part a refactor can silently break: the field -/// spellings the two daemons decode rows with, and the single-program rule -/// that lets one code path serve both EVM and SVM deployment shapes. +/// spellings the relay decodes rows with. #include @@ -17,47 +16,9 @@ namespace c = sysio::opp::depot::chains; BOOST_AUTO_TEST_SUITE(chains_registry_tests) -namespace { -constexpr auto EVM_OPP = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; -constexpr auto EVM_OPREG = "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0"; -constexpr auto SVM_PROGRAM = "So11111111111111111111111111111111111111112"; -constexpr bool ONE_PROGRAM = true; // SVM -constexpr bool PER_ROLE = false; // EVM -} // namespace - -/// EVM: each role names its own contract, so the role field wins. -BOOST_AUTO_TEST_CASE(role_specific_address_is_used_when_present) { - BOOST_REQUIRE_EQUAL(std::string{EVM_OPREG}, - c::resolve_role_addr(EVM_OPREG, EVM_OPP, PER_ROLE)); -} - -/// SVM: one program serves every role, so `sysio.chains` requires the role -/// fields to be empty and every role resolves to `opp_addr`. -BOOST_AUTO_TEST_CASE(single_program_outpost_resolves_every_role_to_opp_addr) { - BOOST_REQUIRE_EQUAL(std::string{SVM_PROGRAM}, - c::resolve_role_addr("", SVM_PROGRAM, ONE_PROGRAM)); -} - -/// The regression this signature exists to prevent: `setoutpost` accepts a -/// partial EVM set, so a row can carry the OPP address while its -/// OperatorRegistry is still undeployed. Substituting `opp_addr` there would be -/// a plausible, non-empty, WRONG address — it passes an is-it-configured check -/// and then commits to the OPP contract. The role must stay empty. -BOOST_AUTO_TEST_CASE(per_role_outpost_never_falls_back_to_opp_addr) { - BOOST_REQUIRE_EQUAL(std::string{}, c::resolve_role_addr("", EVM_OPP, PER_ROLE)); -} - -/// A row registered before any remote contract was deployed carries neither, -/// and must resolve to empty under either shape so the caller fails closed -/// rather than signing to the zero address. -BOOST_AUTO_TEST_CASE(unconfigured_row_resolves_to_empty) { - BOOST_REQUIRE_EQUAL(std::string{}, c::resolve_role_addr("", "", PER_ROLE)); - BOOST_REQUIRE_EQUAL(std::string{}, c::resolve_role_addr("", "", ONE_PROGRAM)); -} - /// Spelling regression guard — these must match `chain_row` and its nested /// `outpost_addrs` struct in `contracts/sysio.chains`. A contract-side rename -/// that misses this header would otherwise surface as both daemons quietly +/// that misses this header would otherwise surface as the relay quietly /// reading empty addresses and skipping every chain. BOOST_AUTO_TEST_CASE(field_spellings_match_the_contract_row) { BOOST_REQUIRE_EQUAL(std::string{"sysio.chains"}, std::string{c::account}); @@ -73,10 +34,6 @@ BOOST_AUTO_TEST_CASE(field_spellings_match_the_contract_row) { std::string{c::field::outpost_addr::opp_addr}); BOOST_REQUIRE_EQUAL(std::string{"opp_inbound_addr"}, std::string{c::field::outpost_addr::opp_inbound_addr}); - BOOST_REQUIRE_EQUAL(std::string{"operator_registry_addr"}, - std::string{c::field::outpost_addr::operator_registry_addr}); - BOOST_REQUIRE_EQUAL(std::string{"source_deposit_addr"}, - std::string{c::field::outpost_addr::source_deposit_addr}); } BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/opp/test/test_opreg_status.cpp b/libraries/opp/test/test_opreg_status.cpp index ac0b68f80e..6d9bd45c84 100644 --- a/libraries/opp/test/test_opreg_status.cpp +++ b/libraries/opp/test/test_opreg_status.cpp @@ -1,7 +1,7 @@ /// Pure-logic unit tests for `sysio::opp::depot::opreg_status::compute_is_active`. /// /// Exercises the awareness decision table consumed by `batch_operator_plugin` -/// and `underwriter_plugin` each tick. The actual chain read happens through +/// each tick. The actual chain read happens through /// `chain_plugin::read_table_rows` (integration territory; covered by the /// flow tests in `wire-tools-ts`); this test pins the status-to-decision /// mapping in isolation. diff --git a/libraries/opp/test/test_retired_carriers.cpp b/libraries/opp/test/test_retired_carriers.cpp new file mode 100644 index 0000000000..53b5f55107 --- /dev/null +++ b/libraries/opp/test/test_retired_carriers.cpp @@ -0,0 +1,165 @@ +/** + * @file test_retired_carriers.cpp + * @brief Removed lifecycle payloads stay out of generated APIs while opaque + * attestations round-trip without hiding the next live LIQ message. + */ +#include +#include + +#include +#include +#include +#include +#include + +namespace { + +/// Removed lifecycle discriminants, plus an undeclared control. +constexpr std::array removed_wire_types{3001, 3002, 3004, 3006, 43520, 60928, 60930, 60932, + 60934, 60944, 60945, 60950, 60951, 60952, 60953, + 60955, 60956, 60958, 60959, 60960, 60961, 60962, + 65000}; +constexpr auto type_field = "type"; +using fc::slug_name_literals::operator""_s; +constexpr auto chain_code = "SOLANA"_s.value; +constexpr auto token_code = "LIQSOL"_s.value; +constexpr int64_t reported_yield = 1'000'000; +constexpr uint64_t report_sequence = 7; +constexpr auto malformed_payload = "\x0a"; + +/// Removed `AttestationType` value names; none may reappear in the generated enum. +constexpr std::array removed_attestation_type_names{ + "ATTESTATION_TYPE_STAKE", + "ATTESTATION_TYPE_UNSTAKE", + "ATTESTATION_TYPE_PRETOKEN_PURCHASE", + "ATTESTATION_TYPE_PRETOKEN_YIELD", + "ATTESTATION_TYPE_RESERVE_BALANCE_SHEET", + "ATTESTATION_TYPE_STAKE_UPDATE", + "ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE", + "ATTESTATION_TYPE_SWAP_REQUEST", + "ATTESTATION_TYPE_SWAP_REMIT", + "ATTESTATION_TYPE_STAKE_RESULT", + "ATTESTATION_TYPE_ATTESTATION_PROCESSING_ERROR", + "ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT", + "ATTESTATION_TYPE_SWAP_REVERT", + "ATTESTATION_TYPE_DEPOSIT_REVERT", + "ATTESTATION_TYPE_RESERVE_CREATE_CANCEL", + "ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED", + "ATTESTATION_TYPE_RESERVE_READY", + "ATTESTATION_TYPE_EMISSIONS_BLOCKED", + "ATTESTATION_TYPE_CHALLENGE_RESPONSE", + "ATTESTATION_TYPE_CHALLENGE_REQUEST"}; +constexpr auto attestation_type_enum = "sysio.opp.types.AttestationType"; + +} // namespace + +BOOST_AUTO_TEST_SUITE(retired_opp_carriers) + +/// Generated descriptors must expose live custody messages, without the removed APIs. +BOOST_AUTO_TEST_CASE(generated_api_omits_retired_lifecycle_messages) { + const auto* pool = google::protobuf::DescriptorPool::generated_pool(); + BOOST_REQUIRE(pool->FindMessageTypeByName("sysio.opp.attestations.DesyndicateLIQ")); + BOOST_REQUIRE(pool->FindMessageTypeByName("sysio.opp.attestations.OperatorAction")); + BOOST_REQUIRE(pool->FindMessageTypeByName("sysio.opp.attestations.NodeOwnerRegistration")); + BOOST_REQUIRE(pool->FindEnumTypeByName("sysio.opp.types.EmissionsBlockReason")); + for (const auto* name : { + "sysio.opp.types.Reserve", "sysio.opp.types.ReserveAmount", + "sysio.opp.attestations.ReserveBalanceSheet", + "sysio.opp.attestations.ReserveDisbursement", + "sysio.opp.attestations.ReserveCreateCancel", + "sysio.opp.attestations.ReserveCreateCancelled", + "sysio.opp.attestations.ReserveReady", + "sysio.opp.attestations.EmissionsBlocked", + "sysio.opp.attestations.NodeOwnerReg", + "sysio.opp.attestations.ProtocolState", + "sysio.opp.attestations.PretokenPurchase", + "sysio.opp.attestations.PretokenYield", + "sysio.opp.attestations.WireTokenPurchase", + "sysio.opp.attestations.SwapRequest", + "sysio.opp.attestations.UnderwriteIntentCommit", + "sysio.opp.attestations.SwapRemit", + "sysio.opp.attestations.SwapRevert", + "sysio.opp.attestations.DepositRevert", + "sysio.opp.attestations.PretokenStakeChange", + "sysio.opp.attestations.StakeUpdate", + "sysio.opp.attestations.StakeResult", + "sysio.opp.attestations.AttestationProcessingError", + "sysio.opp.attestations.ChallengeRequest", + "sysio.opp.attestations.ChallengeOperatorHash", + "sysio.opp.types.ChainSignature", + "sysio.opp.types.WirePermission", + "sysio.opp.types.Chain", + "sysio.opp.types.Token", + "sysio.opp.types.ChainToken", + "sysio.opp.debugging.DebugEnvelopeDataRecord"}) { + BOOST_CHECK_MESSAGE(pool->FindMessageTypeByName(name) == nullptr, name); + } + for (const auto* name : {"sysio.opp.types.ReserveStatus", + "sysio.opp.types.UnderwriteRequestStatus", + "sysio.opp.types.UnderwriteStatus", + "sysio.opp.types.StakeStatus", + "sysio.opp.types.ChainKeyType", + "sysio.opp.types.ChainRequestStatus", + "sysio.opp.types.ChallengeStatus"}) { + BOOST_CHECK_MESSAGE(pool->FindEnumTypeByName(name) == nullptr, name); + } +} + +/// The generated `AttestationType` must define neither a removed value name nor a removed number. +BOOST_AUTO_TEST_CASE(generated_attestation_type_omits_removed_values) { + const auto* attestation_types = + google::protobuf::DescriptorPool::generated_pool()->FindEnumTypeByName(attestation_type_enum); + BOOST_REQUIRE(attestation_types); + BOOST_REQUIRE(attestation_types->FindValueByName("ATTESTATION_TYPE_LIQ_YIELD")); + for (const auto* name : removed_attestation_type_names) { + BOOST_CHECK_MESSAGE(attestation_types->FindValueByName(name) == nullptr, name); + } + for (const auto raw_type : removed_wire_types) { + BOOST_CHECK_MESSAGE(attestation_types->FindValueByNumber(raw_type) == nullptr, raw_type); + } +} + +/// Unknown wire discriminants and malformed opaque data must not corrupt a following live entry. +BOOST_AUTO_TEST_CASE(opaque_removed_entries_preserve_following_liq_attestation) { + sysio::opp::Envelope source; + auto* payload = source.add_messages()->mutable_payload(); + for (const auto raw_type : removed_wire_types) { + auto* entry = payload->add_attestations(); + const auto* field = entry->GetDescriptor()->FindFieldByName(type_field); + BOOST_REQUIRE(field); + entry->GetReflection()->SetEnumValue(entry, field, raw_type); + entry->set_data(malformed_payload); + entry->set_data_size(entry->data().size()); + } + sysio::opp::attestations::LIQYield report; + report.set_chain_code(chain_code); + report.mutable_amount()->set_token_code(token_code); + report.mutable_amount()->set_amount(reported_yield); + report.set_sequence(report_sequence); + const auto liq_payload = report.SerializeAsString(); + auto* live = payload->add_attestations(); + live->set_type(sysio::opp::types::ATTESTATION_TYPE_LIQ_YIELD); + live->set_data(liq_payload); + live->set_data_size(live->data().size()); + + sysio::opp::Envelope decoded; + BOOST_REQUIRE(decoded.ParseFromString(source.SerializeAsString())); + const auto& entries = decoded.messages(0).payload().attestations(); + BOOST_REQUIRE_EQUAL(entries.size(), removed_wire_types.size() + 1); + for (size_t index = 0; index < removed_wire_types.size(); ++index) { + const auto& entry = entries.Get(index); + BOOST_CHECK_EQUAL(entry.GetReflection()->GetEnumValue( + entry, entry.GetDescriptor()->FindFieldByName(type_field)), removed_wire_types[index]); + BOOST_CHECK_EQUAL(entry.data(), malformed_payload); + } + BOOST_CHECK_EQUAL(entries.rbegin()->type(), sysio::opp::types::ATTESTATION_TYPE_LIQ_YIELD); + BOOST_CHECK_EQUAL(entries.rbegin()->data(), liq_payload); + sysio::opp::attestations::LIQYield decoded_report; + BOOST_REQUIRE(decoded_report.ParseFromString(entries.rbegin()->data())); + BOOST_CHECK_EQUAL(decoded_report.chain_code(), chain_code); + BOOST_CHECK_EQUAL(decoded_report.amount().token_code(), token_code); + BOOST_CHECK_EQUAL(decoded_report.amount().amount(), reported_yield); + BOOST_CHECK_EQUAL(decoded_report.sequence(), report_sequence); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp b/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp index 3717f87b5b..70ddcd563d 100644 --- a/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp +++ b/plugins/batch_operator_plugin/src/batch_operator_plugin.cpp @@ -89,8 +89,8 @@ namespace { constexpr auto status = "status"; } // `OperatorStatus` enum spellings + the `is_active` decision live - // in `sysio/opp/depot/opreg_status.hpp` so underwriter_plugin can pull - // the same source of truth without a cross-plugin dependency. + // in `sysio/opp/depot/opreg_status.hpp`, beside the other shared depot + // row views, so any client reads the same source of truth. } namespace epoch { @@ -122,7 +122,7 @@ namespace { /// Chain registry was split out of `sysio.epoch` onto its own /// `sysio.chains` contract. The `outposts` table was replaced by the /// `chains` KV table, keyed by slug_name (uint64 packed). Field spellings - /// are shared with underwriter_plugin, which reads the same rows. + /// live in the shared `sysio/opp/depot/chains_registry.hpp` row view. namespace chains = sysio::opp::depot::chains; } @@ -401,11 +401,9 @@ struct batch_operator_plugin::impl { * * `sysio.chalg::opendispute` sends `sysio.epoch::pause`, and `chkdispute` is * the ONLY action that tallies the Tier-1 votes, dispatches the winning - * envelope and lifts that pause. Nothing on chain drives it: its sibling - * `chkuwchal` needs no cadence because `sysio.uwrit::chklocks` pokes it from - * every `sysio.epoch::advance` — which works precisely because an - * underwriter challenge does NOT pause the chain. An envelope dispute halts - * `advance` itself, so no inline poke can reach it. Without this crank a + * envelope and lifts that pause. Nothing on chain drives it: an envelope + * dispute halts `sysio.epoch::advance` itself, so no inline poke from the + * epoch cycle can reach it. Without this crank a * dispute stays OPEN after Tier-1 has already reached quorum, and epoch * advancement is paused indefinitely. * diff --git a/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp b/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp index 7fcd75cd05..b5e72392c9 100644 --- a/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp +++ b/plugins/outpost_client_plugin/include/sysio/outpost_client/outpost_client.hpp @@ -62,11 +62,8 @@ class outpost_client { * * Ethereum returns the 20-byte address derived from the configured * transaction signer. Solana returns the configured signer's 32-byte - * public key. Underwriter UIC construction signs this value into - * `uw_ext_chain_addr`, so the canonical payload contains no omitted - * default byte field and records the local transaction signer as signed - * metadata. The outpost binds this field and the claimed WIRE account to - * the authenticated caller and its current ACTIVE underwriter roster row. + * public key. The returned address identifies the configured transaction + * signer for chain-native client operations. * * @return Opaque chain-native address bytes; 20 bytes for Ethereum and 32 * bytes for Solana. diff --git a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp index 3cc05cad59..b6e67dfc4b 100644 --- a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp +++ b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin.hpp @@ -150,23 +150,17 @@ class outpost_ethereum_client_plugin : public appbase::plugin create_outpost_client(const std::string& eth_client_id, diff --git a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp index 9d6f34b829..735d41475e 100644 --- a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp +++ b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp @@ -76,7 +76,6 @@ constexpr auto tx_realize_yield = "realizeYield"; namespace opp_inbound_abi { constexpr auto view_envelope_chunk_state = "envelopeChunkState"; constexpr auto view_next_epoch_index = "nextEpochIndex"; -constexpr auto view_attestation_handlers = "attestationHandlers"; namespace field { constexpr auto epoch_index = "epochIndex"; constexpr auto owner = "owner"; @@ -279,8 +278,7 @@ outpost_ethereum_client::outpost_ethereum_client( FC_ASSERT(_entry && _entry->client, "ethereum_client_entry must carry a client"); // Each contract wrapper is materialized only if its address was - // supplied. A caller that only consumes one outpost capability (e.g. - // the underwriter calling `uw_commit` against OperatorRegistry) can + // supplied. A caller that only consumes one outpost capability can // pass empty strings for the addresses it doesn't use; the methods // covering an unprovisioned wrapper assert on entry with a clear // diagnostic. Per `outpost-client-spi.md`: address configuration is @@ -539,7 +537,7 @@ std::string outpost_ethereum_client::deliver_outbound_envelope( // `ethereum_contract_tx_fn` binds every argument as a non-const lvalue // reference, so each one needs a named local (the same constraint that - // shapes `uw_commit`'s hex local). + // preserves the RPC's chain-native byte encoding). uint32_t epoch_arg = epoch_index; uint16_t chunk_arg = chunk; uint16_t chunks_arg = total_chunks; diff --git a/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin.hpp b/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin.hpp index 89509f270f..b55064b3ed 100644 --- a/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin.hpp +++ b/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin.hpp @@ -97,8 +97,8 @@ struct opp_solana_outpost_client : fc::network::solana::solana_program_client { /// Anchor `remaining_accounts`. The cranker /// (`outpost_solana_client::deliver_outbound_envelope`) decodes the /// committed envelope and builds account metas for every effect account the - /// terminal handlers may touch: operator/depositor wallets, Reserve PDAs, - /// SPL vaults, canonical ATAs, mints, and token/ATA/system programs. Data + /// terminal handlers may touch: syndicated LIQ custody, recipient wallets, + /// canonical ATAs, mints, and token/ATA/system programs. Data /// chunks ignore the slice — only the zero-data terminal call's account /// list matters for dispatch. solana_program_tx_fn syndicated_liq_pool(); /// The outpost's per-epoch crank: liqsol-core's permissionless diff --git a/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp b/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp index ad0cff7bff..5864bb6c93 100644 --- a/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp +++ b/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp @@ -138,11 +138,10 @@ namespace outpost_solana_client_detail { namespace { -/// Wrap a 32-byte address slice from `op_address.address` / -/// `depositor.address` into a `solana_public_key`. Returns nullopt -/// on the wrong chain kind or a malformed length — caller drops these -/// attestations from the remaining_accounts list (the on-chain handler -/// will log+skip them too, so no fatal failure). +/// Wrap a 32-byte address slice from a DesyndicateLIQ `user.address` into a +/// `solana_public_key`. Returns nullopt on the wrong chain kind or a malformed +/// length — the caller drops that attestation from the remaining_accounts list +/// (the on-chain handler will log+skip it too, so no fatal failure). std::optional sol_pubkey_from_chain_address( const sysio::opp::types::ChainAddress& addr) { if (addr.kind() != sysio::opp::types::CHAIN_KIND_SVM) return std::nullopt; @@ -979,10 +978,10 @@ std::vector> build_dispatch_manif fc::network::solana::system::program_ids::TOKEN_2022_PROGRAM; // One read per DISTINCT custody mint for the whole build, matching the two - // caches around it. Without this every reserve-backed SPL attestation pays a + // caches around it. Without this every syndicated SPL payout pays a // `get_account_info(custody_mint)` -- two when the mint has a hook, since the - // reader also reads the validation PDA -- so an envelope with 30 SPL remits - // over 3 reserves would go from 3 reads to 33, inside a build that probes the + // reader also reads the validation PDA -- so repeated payouts of one mint + // would repeat those reads inside a build that probes the // deadline per effect and aborts wholesale on a transient RPC throw. // `nullopt` (no hook) is memoised too: that is every mint in the system today. std::map> hook_cache; @@ -1005,7 +1004,7 @@ std::vector> build_dispatch_manif std::vector> per_attestation(total_attestations); for (const auto& effect : effects) { - // The deadline is probed per effect, BEFORE its reserve read: a build + // The deadline is probed per effect, BEFORE its custody read: a build // that runs past the tick deadline must fail here, where the remaining // work is known, rather than deep inside the RPC layer with every // manifest already paid for and none of them dispatched. @@ -1264,7 +1263,7 @@ outpost_solana_client::outpost_solana_client( // the integrated liqsol-core program declares it -- a standalone outpost // IDL has no syndicated pool to release from, and its DistributionState // PDA never exists on chain. Where it IS declared, a drifted `liqsol_mint` - // would wedge every desyndication window the way a drifted Reserve would. + // would wedge every desyndication window. if (_program_client->get_program()->find_account(distribution_state::account_name)) { outpost_solana_client_detail::assert_distribution_state_shape( *_program_client->get_program()); @@ -1418,9 +1417,8 @@ std::string outpost_solana_client::drain_dispatch( // Per-attestation manifests, indexed by the SAME flat position the on-chain // dispatch cursor counts. Attestations needing no effect account keep an - // empty entry so the indices stay aligned. The build reads ONE account per - // distinct Reserve or CollateralPosition -- the exact PDA the handlers - // themselves branch on -- probes the deadline per effect, and degrades a + // empty entry so the indices stay aligned. The build reads the syndicated + // pool once, probes the deadline per effect, and degrades a // single absent account instead of abandoning the envelope's other // manifests. const auto effects = outpost_solana_client_detail::extract_inbound_effects(envelope_bytes); @@ -1454,7 +1452,7 @@ std::string outpost_solana_client::drain_dispatch( /// Read one custody mint's transfer-hook configuration off chain. /// -/// Called only for a Token-2022 custody reserve -- a legacy-SPL reserve cannot +/// Called only for a Token-2022 custody mint -- a legacy-SPL mint cannot /// carry the extension, so the caller gates and this never reads that mint. /// /// No TransferHook extension, or a hook explicitly disabled (an all-zero @@ -1468,7 +1466,7 @@ std::string outpost_solana_client::drain_dispatch( std::optional outpost_solana_client::mint_transfer_hook_for( const fc::network::solana::solana_public_key& custody_mint) { - // NOT degraded to `nullopt`: the custody mint is pinned on a Reserve that + // NOT degraded to `nullopt`: the custody mint is pinned on a syndicated pool that // demonstrably exists, so "absent" is an anomaly (a lagging bank behind the // default commitment, say), not a mint without a hook. Reporting "no hook" // here would build a hook-free manifest for a hook mint and abort inside the @@ -1476,7 +1474,7 @@ outpost_solana_client::mint_transfer_hook_for( // refuses for the validation PDA below. const auto mint_info = _entry->client->get_account_info(custody_mint); FC_ASSERT(mint_info.has_value() && !mint_info->data.empty(), - "custody mint {} is absent or empty; it is pinned on an existing Reserve, so this " + "custody mint {} is absent or empty; it is pinned on an existing syndicated pool, so this " "is a read anomaly rather than a mint without a transfer hook", custody_mint.to_string(fc::yield_function_t{})); @@ -1764,7 +1762,7 @@ std::vector outpost_solana_client::read_inbound_envelope( // field order drives the decode, so the same nodeop reads both the // standalone `opp_outpost` and integrated `liqsol_core` // `LatestOutboundEnvelope` layouts value-exactly (same path the class - // already uses for `Reserve` / `OutpostConfig`). For inbound-reading roles + // uses for `DistributionState`). For inbound-reading roles // the account declaration was validated at boot, so a decode failure here // signals IDL-vs-deployment drift and is logged at warning level. return outpost_solana_client_detail::decode_latest_envelope_account( diff --git a/plugins/outpost_solana_client_plugin/test/test_outpost_solana_client_plugin.cpp b/plugins/outpost_solana_client_plugin/test/test_outpost_solana_client_plugin.cpp index 78b07b9508..7e80332c69 100644 --- a/plugins/outpost_solana_client_plugin/test/test_outpost_solana_client_plugin.cpp +++ b/plugins/outpost_solana_client_plugin/test/test_outpost_solana_client_plugin.cpp @@ -767,12 +767,13 @@ std::vector envelope_with_entries( return std::vector(buf.begin(), buf.end()); } -/// Build an `OPERATOR_ACTION(WITHDRAW_REMIT)` entry pointing at -/// `op_addr`, remitting `token_code`. The decoder reads `op_address`, -/// `action_type` and `amount.token_code` (SOL-379/380 keys the -/// CollateralPosition PDA on it); other proto fields stay neutral. +/// The depot request id `desyndicate_liq_entry` stamps unless a case names one. constexpr uint64_t default_desyndication_request_id = 1; +/// Build a `DESYNDICATE_LIQ` entry releasing `token_code` to `user` under +/// `request_id`. The decoder reads `user` and `request_id` -- the pool mint comes +/// from `DistributionState`, not the payload -- so the other fields stay neutral. + sysio::opp::AttestationEntry desyndicate_liq_entry(uint64_t token_code, const sysio::opp::types::ChainAddress& user, uint64_t request_id = default_desyndication_request_id) { @@ -791,6 +792,8 @@ sysio::opp::AttestationEntry desyndicate_liq_entry(uint64_t return entry; } +/// Build an `OPERATOR_ACTION(WITHDRAW_REMIT)` entry for `op_addr`. The relay derives no Solana +/// effect from operator actions, so it must contribute no manifest entry. sysio::opp::AttestationEntry remit_entry(uint64_t token_code, const sysio::opp::types::ChainAddress& op_addr) { sysio::opp::attestations::OperatorAction oa; @@ -899,12 +902,12 @@ BOOST_AUTO_TEST_CASE(extract_effects_on_undecodable_envelope_is_empty) try { BOOST_AUTO_TEST_CASE(extract_effects_repeated_recipient_yields_one_entry_per_attestation) try { namespace detail = sysio::outpost_solana_client_detail; auto op_pk = filled_pubkey(0xCC); - // Two identical WITHDRAW_REMITs to the same operator and token_code. The + // Two identical DESYNDICATE_LIQs to the same recipient and token_code. The // walk is per-attestation with NO cross-attestation dedup -- the cursor // counts both, so collapsing them would misalign every later - // dispatch_limit -- and the duplicate ACCOUNTS (operator wallet, - // CollateralPosition PDA) merge later in `record_terminal_account` when a - // batch's manifests union (pinned by + // dispatch_limit -- and the duplicate ACCOUNTS (recipient wallet and its + // token account) merge later in `record_terminal_account` when a batch's + // manifests union (pinned by // record_terminal_account_dedupes_and_merges_writable). auto envelope = envelope_with_entries({ desyndicate_liq_entry(600, make_sol_addr(op_pk)), @@ -918,16 +921,6 @@ BOOST_AUTO_TEST_CASE(extract_effects_repeated_recipient_yields_one_entry_per_att BOOST_CHECK(effects[0].recipient->serialize() == op_pk); BOOST_CHECK(effects[1].recipient->serialize() == op_pk); } FC_LOG_AND_RETHROW(); -namespace { - -/// The depot request id `desyndicate_liq_entry` stamps unless a case names one. - -/// Build a `DESYNDICATE_LIQ` entry releasing `token_code` to `user` under -/// `request_id`. The decoder reads `user` and `request_id` -- the pool mint comes -/// from `DistributionState`, not the payload -- so the other fields stay neutral. - -} // namespace - BOOST_AUTO_TEST_CASE(extract_effects_desyndicate_liq_carries_the_user) try { namespace detail = sysio::outpost_solana_client_detail; // DESYNDICATE_LIQ MUST be surfaced: the handler resolves the pool's state @@ -970,7 +963,7 @@ BOOST_AUTO_TEST_CASE(extract_effects_skips_non_settling_operator_actions) try { BOOST_CHECK(effects.empty()); } FC_LOG_AND_RETHROW(); BOOST_AUTO_TEST_CASE(extract_effects_skips_non_solana_chain) try { - // A WITHDRAW_REMIT whose `op_address` carries kind=ETHEREUM is not + // A DESYNDICATE_LIQ whose `user` carries kind=ETHEREUM is not // for this outpost and must not contribute a SOL effect entry. auto eth_bytes = filled_pubkey(0x01); auto envelope = envelope_with_entries({ @@ -1018,20 +1011,20 @@ BOOST_AUTO_TEST_CASE(record_terminal_account_dedupes_and_merges_writable) try { BOOST_CHECK(metas[1].is_writable); } FC_LOG_AND_RETHROW(); // ── build_dispatch_manifests: the per-attestation account manifests the -// dispatch crank packs its windows from, driven through its Reserve and -// CollateralPosition read seams. These pin the properties the manifest +// dispatch crank packs its windows from, driven through its transfer-hook +// and DistributionState read seams. These pin the properties the manifest // build must hold before a single dispatch is sent: custody comes from the -// exact account the on-chain handler branches on, reads are memoised by the -// account's full PDA seed tuple, and an absent account degrades only itself. +// exact account the on-chain handler branches on, the pool singleton is read +// once per build, and an absent account degrades only itself. namespace { namespace manifest_detail = sysio::outpost_solana_client_detail; -/// Scripted stand-in for the Reserve and CollateralPosition reads a manifest -/// build performs. The record maps are on-chain truth; absent entries degrade -/// as uninitialized accounts. Every call is recorded so memoization is -/// assertable. +/// Scripted stand-in for the transfer-hook and DistributionState reads a +/// manifest build performs. The seeded values are on-chain truth; absent +/// entries degrade as uninitialized accounts. Every call is counted so +/// memoization is assertable. struct manifest_build_harness { solana_public_key program_id = measurement_pubkey(42); @@ -1347,7 +1340,7 @@ manifest_detail::extra_account_meta external_pda_meta(uint8_t program_index, /// A hook-bearing Token-2022 custody mint -- the case SOL-396 exists for, and /// the one the ATA-derivation cases above do NOT cover. /// -/// `reserve_vault_transfer` routes through +/// `settle_desyndication` pays through /// `spl_token_2022::onchain::invoke_transfer_checked`, which for such a mint /// resolves the hook program, its ExtraAccountMetaList PDA, and every account /// that PDA declares out of `remaining_accounts`. Omitting them aborts the CPI @@ -1915,7 +1908,7 @@ idl::program latest_envelope_program(std::vector fields, bool fields /// Build a minimal synthetic program declaring ONE account named `account_name` /// with `fields`, in either IDL field home — the drift shapes a boot check must /// refuse before a batch operator ever cranks. Shared by the `EpochDeliveries` -/// and `Reserve` shape tests so both drive the same declaration machinery. +/// and `DistributionState` shape tests so both drive the same declaration machinery. idl::program named_account_program(std::string account_name, std::vector fields, bool fields_in_types_section) { idl::program prog; diff --git a/tests/fixtures/solana-idl-opp-outpost-stub.json b/tests/fixtures/solana-idl-opp-outpost-stub.json index ef645d4cbb..fb647a1fd0 100644 --- a/tests/fixtures/solana-idl-opp-outpost-stub.json +++ b/tests/fixtures/solana-idl-opp-outpost-stub.json @@ -578,23 +578,6 @@ ] } }, - { - "name": "ReserveStatus", - "type": { - "kind": "enum", - "variants": [ - { - "name": "Pending" - }, - { - "name": "Active" - }, - { - "name": "Cancelled" - } - ] - } - }, { "name": "TokenAddressEntry", "type": { diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp index cbb0cf1270..56ab2eadef 100644 --- a/tests/get_table_tests.cpp +++ b/tests/get_table_tests.cpp @@ -3306,9 +3306,9 @@ BOOST_FIXTURE_TEST_CASE( get_kv_rows_scoped_bounds_test, validating_tester ) try /// kv::global — a single-row table with no scope, keyed on the table's own name. /// -/// The API doc lists kv::global among the supported table types and five production +/// The API doc lists kv::global among the supported table types and production /// singletons ship this shape (sysio.opreg::opconfig / opcounters, -/// sysio.chalg::chalgstate, sysio.uwrit::uwconfig / uwcounters), but nothing read one +/// sysio.chalg::chalgstate, sysio.epoch::epochstate, ...), but nothing read one /// through get_table_rows. Its ABI comes out key_names ["name"] / key_types ["name"], /// so the key decodes through the NAME leaf and the row's key is the table name itself. BOOST_FIXTURE_TEST_CASE( get_kv_rows_global_singleton_test, validating_tester ) try { diff --git a/unittests/be_key_codec_tests.cpp b/unittests/be_key_codec_tests.cpp index d5be7c361d..90737da7cf 100644 --- a/unittests/be_key_codec_tests.cpp +++ b/unittests/be_key_codec_tests.cpp @@ -191,10 +191,8 @@ BOOST_AUTO_TEST_CASE(slug_name_leaf_wins_over_a_shadowing_struct_def) { } BOOST_AUTO_TEST_CASE(slug_name_multi_leaf_keys_preserve_field_order_and_offsets) { - // THREE of the five registry tables key on more than one slug: + // Registry tables can key on more than one slug, e.g. // sysio.tokens::chaintokens ["slug_name","slug_name"] - // sysio.reserv::reserves ["slug_name","slug_name","slug_name"] - // sysio.uwrit::locksums ["name","slug_name","slug_name"] // Every other slug case here builds a SINGLE leaf, and a single leaf cannot // observe a field-ordering or offset error because there is only one field to // misplace. decode_key walks the shapes in order consuming a fixed width each, @@ -221,7 +219,7 @@ BOOST_AUTO_TEST_CASE(slug_name_multi_leaf_keys_preserve_field_order_and_offsets) BOOST_CHECK(bytes == codec::encode_key(decoded, shapes)); } - // 3-leaf, mirroring reserves. The middle leaf is a DIFFERENT length from its + // 3-leaf (a synthetic shape). The middle leaf is a DIFFERENT length from its // neighbours, so a decoder that mis-tracked its offset would land inside an // adjacent slug and still produce a string. { @@ -242,7 +240,7 @@ BOOST_AUTO_TEST_CASE(slug_name_multi_leaf_keys_preserve_field_order_and_offsets) BOOST_CHECK(bytes == codec::encode_key(decoded, shapes)); } - // name + 2 slugs, mirroring locksums — a mixed-KIND composite. + // name + 2 slugs — a mixed-KIND composite. { auto shapes = codec::build_key_shapes(abi, {"underwriter", "chain_code", "token_code"}, {"name", "slug_name", "slug_name"}); diff --git a/unittests/intrinsic_probe_tests.cpp b/unittests/intrinsic_probe_tests.cpp index 53dfa6715a..8ae2ecdbd9 100644 --- a/unittests/intrinsic_probe_tests.cpp +++ b/unittests/intrinsic_probe_tests.cpp @@ -557,8 +557,8 @@ BOOST_FIXTURE_TEST_CASE(set_action_return_value_empty, intrinsic_probe_fixture) // I. get_permission_lower_bound (P2) // // Wire-specific intrinsic (libraries/chain/webassembly/permission.cpp) backing CDT's sysio::get_permission -- -// the only host path a contract has to read another account's authority on-chain (sysio.roa::active_key_matches, -// sysio.uwrit). Each probe queries get_self()'s owner/active permissions (created with a single K1 key at +// the only host path a contract has to read another account's authority on-chain (sysio.roa::active_key_matches). +// Each probe queries get_self()'s owner/active permissions (created with a single K1 key at // threshold 1) and self-verifies the record via in-contract check(); the driver asserts the host stays silent, // so any layout / size-query / sentinel / lower-bound-semantics regression surfaces as a // sysio_assert_message_exception here. Not privileged-gated, so all run from the ordinary probe account. diff --git a/unittests/test-contracts/get_table_test/get_table_test.hpp b/unittests/test-contracts/get_table_test/get_table_test.hpp index 0009a034f1..90d3635b03 100644 --- a/unittests/test-contracts/get_table_test/get_table_test.hpp +++ b/unittests/test-contracts/get_table_test/get_table_test.hpp @@ -128,7 +128,7 @@ class [[sysio::contract]] get_table_test : public sysio::contract { typedef sysio::kv::table< "structobjs"_n, structobj_key, structobj > structobjs; // Slug-keyed kv::table — the shape every registry table ships - // (sysio.chains::chains, sysio.tokens::tokens, sysio.reserv::reserves). + // (sysio.chains::chains, sysio.tokens::tokens). // // This one is named after a builtin ON PURPOSE, the exact hazard // `composite_key` above exists to avoid: abigen matches builtins on the @@ -194,8 +194,8 @@ class [[sysio::contract]] get_table_test : public sysio::contract { sysio::const_mem_fun>> sslugobjs; // kv::global — a SINGLE-row table with no scope, keyed on the table's own name. - // Five production contracts ship this shape (sysio.opreg::opconfig / opcounters, - // sysio.chalg::chalgstate, sysio.uwrit::uwconfig / uwcounters) and the API doc + // Production contracts ship this shape (sysio.opreg::opconfig / opcounters, + // sysio.chalg::chalgstate, sysio.epoch::epochstate, ...) and the API doc // lists kv::global as supported, but nothing exercised it through // get_table_rows — so its ABI shape (key_names ["name"], key_types ["name"]) // and its empty-vs-set behaviour were both unverified. diff --git a/unittests/test-contracts/intrinsic_probe/intrinsic_probe.cpp b/unittests/test-contracts/intrinsic_probe/intrinsic_probe.cpp index e8f2596d1a..429ab4f40f 100644 --- a/unittests/test-contracts/intrinsic_probe/intrinsic_probe.cpp +++ b/unittests/test-contracts/intrinsic_probe/intrinsic_probe.cpp @@ -89,7 +89,7 @@ int32_t check_transaction_authorization( const char* trx_data, uint32_t trx_len // get_permission_lower_bound: aligned_span out-param (buffer). Wire-specific intrinsic // (libraries/chain/webassembly/permission.cpp) backing CDT's sysio::get_permission wrapper, used on-chain by -// sysio.roa::active_key_matches and sysio.uwrit to read an account's authority. by_owner lower_bound on +// sysio.roa::active_key_matches to read an account's authority. by_owner lower_bound on // (account, permission); returns -1 when no permission for `account` exists at/after the key, else the full // serialized record size (which may exceed buffer_size -- the size-query-then-fetch contract). The serialized // payload is pack(perm_name) ++ pack(parent_name) ++ pack(last_updated) ++ pack(authority). @@ -319,7 +319,7 @@ constexpr uint64_t U64_MAX = 0xFFFFFFFFFFFFFFFFULL; // ----------------------------------------------------------------------------- // get_permission_lower_bound record layout. A byte-for-byte mirror of CDT's // sysio::permission_record / perm_authority (libraries/sysiolib/contracts/sysio/permission.hpp): the exact -// shape on-chain consumers (sysio.roa::active_key_matches, sysio.uwrit) unpack from this intrinsic's output. +// shape on-chain consumers (sysio.roa::active_key_matches) unpack from this intrinsic's output. // Defined locally so the probe verifies the raw host serialization against the real consumer contract without // routing the CALL through the CDT wrapper. Note the host also packs a trailing `waits` vector that the // consumer struct intentionally omits -- unpack stops after `accounts` and tolerates the trailing bytes. @@ -1366,7 +1366,7 @@ class [[sysio::contract("intrinsic_probe")]] intrinsic_probe : public contract { // P2 -- get_permission_lower_bound // // Wire-specific intrinsic backing CDT's sysio::get_permission -- the only host path a contract has to read - // another account's authority on-chain (sysio.roa::active_key_matches, sysio.uwrit). These probes pin the + // another account's authority on-chain (sysio.roa::active_key_matches). These probes pin the // pieces the CDT wrapper and its callers depend on: the serialized record layout, the size-query / // small-buffer contract, the not-found -1 sentinel, and the lower-bound (NOT exact-match) lookup semantics. // All queries target get_self(), whose owner/active permissions the Boost driver creates with a single K1