From 02dfa26a25210aabbe3c6e84a8948ff3e2410812 Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Fri, 9 Oct 2026 15:39:29 -0400 Subject: [PATCH 1/2] Remove retired OPP carriers and their leftovers External reserve and dual-commit implementations were retired, but their shared protocol definitions still advertised unused lifecycle APIs, and the contracts, plugins and tests around them kept the surfaces those flows needed. Remove reserve cancellation, matching, snapshot, and registry types, and cross-chain swap, dual-commit, remittance and refund carriers; retire the unused purchase/yield and emissions notification payloads, obsolete state enums, old node-owner payload, and OPP state wrapper. Remove dead dispatch correlation inputs and the msgch helpers that only served the retired ingress: operator-address resolution, payload-code canonicality and the uwreqs locked-amount row type. Remove the staking lifecycle carriers as well: PretokenStakeChange, StakeUpdate, StakeResult, the StakeStatus enum and the STAKE, UNSTAKE, STAKE_UPDATE and STAKE_RESULT discriminants. Their only producers were two uncalled StakingManager senders on Ethereum, and msgch dropped all four in one deferred case. AttestationProcessingError goes too: no chain produces it and msgch ignored it. Drop the underwriter outpost roles from the sysio.chains registry (operator_registry_addr, source_deposit_addr) and the shared role resolver: the batch-operator relay reads only the OPP and OPPInbound addresses. Drop the epochlog swap-fee audit fields, which payepoch wrote as constant zero once the reserv fee sweep was deleted. Keep live native collateral audit, node-owner registration, LIQ custody, local emissions diagnostics, and the native sysio.swap AMM. Retained payload fields and wire discriminants are unchanged. Rebuild the chains, epoch, liq, msgch, opreg, synd and system artifacts, verify that opaque removed wire values cannot block a following live LIQ intake, and refresh documentation that still described the deleted contracts. Document producer/consumer evidence and the consumer release wave. The sysio.chains, epochlog and msgch ABI changes need the matching libraries, tools, Ethereum and Solana consumers, which land with this change. Change-Id: I06515e3ac32964be90eb1b5f9db1ead6b611b788 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ARSeK35wwkiAy4et9mCkVm --- .../include/sysio.authex/sysio.authex.hpp | 4 +- contracts/sysio.authex/src/sysio.authex.cpp | 2 +- .../include/sysio.chains/sysio.chains.hpp | 21 +- contracts/sysio.chains/src/sysio.chains.cpp | 29 +- contracts/sysio.chains/sysio.chains.abi | 8 - contracts/sysio.chains/sysio.chains.wasm | Bin 39121 -> 33571 bytes .../include/sysio.chalg/sysio.chalg.hpp | 63 ---- contracts/sysio.chalg/src/sysio.chalg.cpp | 12 +- contracts/sysio.epoch/sysio.epoch.wasm | Bin 78983 -> 77496 bytes contracts/sysio.liq/sysio.liq.wasm | Bin 40186 -> 39771 bytes .../include/sysio.msgch/sysio.msgch.hpp | 19 +- contracts/sysio.msgch/src/sysio.msgch.cpp | 144 +------- contracts/sysio.msgch/sysio.msgch.abi | 72 ---- contracts/sysio.msgch/sysio.msgch.wasm | Bin 145482 -> 143328 bytes .../include/sysio.opp.common/amm_math.hpp | 35 +- .../sysio.opp.common/opp_table_types.hpp | 285 +--------------- .../sysio.opp.common/registry_metadata.hpp | 15 +- .../sysio.opp.common/require_privileged.hpp | 3 +- .../include/sysio.opp.common/safe_ops.hpp | 5 +- contracts/sysio.opreg/src/sysio.opreg.cpp | 8 +- contracts/sysio.opreg/sysio.opreg.wasm | Bin 85305 -> 83740 bytes contracts/sysio.synd/sysio.synd.wasm | Bin 103962 -> 103547 bytes contracts/sysio.system/EMISSIONS.md | 7 +- .../include/sysio.system/emissions.hpp | 18 +- .../include/sysio.system/producer_rank.hpp | 2 +- .../include/sysio.system/producer_score.hpp | 5 +- .../include/sysio.system/sysio.system.hpp | 7 +- contracts/sysio.system/src/emissions.cpp | 34 +- contracts/sysio.system/sysio.system.abi | 8 - contracts/sysio.system/sysio.system.wasm | Bin 200474 -> 200361 bytes .../include/sysio.tokens/sysio.tokens.hpp | 2 +- contracts/tests/amm_math_tests.cpp | 22 +- contracts/tests/contract_test_support.hpp | 31 +- contracts/tests/emissions_tests.cpp | 23 +- contracts/tests/sysio.chains_tests.cpp | 64 ++-- contracts/tests/sysio.dispatch_tests.cpp | 136 +++----- .../tests/sysio.epoch_flushwtdw_tests.cpp | 5 +- contracts/tests/sysio.epoch_tests.cpp | 4 +- contracts/tests/sysio.msgch_chain_tests.cpp | 4 +- contracts/tests/sysio.msgch_tests.cpp | 8 +- contracts/tests/sysio.opreg_tests.cpp | 4 +- contracts/tests/sysio.tokens_tests.cpp | 3 +- docs/retired-opp-carriers.md | 88 +++++ etc/schema/opp_entity_diagram-gen.puml | 315 ------------------ etc/schema/opp_entity_diagram-gen.svg | 1 - etc/schema/opp_entity_diagram.puml | 278 ---------------- libraries/libfc/include/fc/slug_name.hpp | 4 +- .../sysio/opp/depot/chains_registry.hpp | 42 +-- .../include/sysio/opp/depot/opreg_status.hpp | 2 +- libraries/opp/include/sysio/opp/opp.hpp | 56 +--- .../sysio/opp/attestations/attestations.proto | 251 +------------- .../proto/sysio/opp/bootstrap/bootstrap.proto | 8 +- .../opp/proto/sysio/opp/types/types.proto | 123 +------ libraries/opp/test/test_chains_registry.cpp | 49 +-- libraries/opp/test/test_opreg_status.cpp | 2 +- libraries/opp/test/test_retired_carriers.cpp | 151 +++++++++ .../src/batch_operator_plugin.cpp | 14 +- .../sysio/outpost_client/outpost_client.hpp | 7 +- .../sysio/outpost_ethereum_client_plugin.hpp | 16 +- .../src/outpost_ethereum_client.cpp | 6 +- .../sysio/outpost_solana_client_plugin.hpp | 8 +- .../outpost_solana_client.hpp | 6 +- .../src/outpost_solana_client.cpp | 32 +- .../test_outpost_solana_client_plugin.cpp | 51 ++- .../fixtures/solana-idl-opp-outpost-stub.json | 17 - tests/get_table_tests.cpp | 4 +- unittests/be_key_codec_tests.cpp | 8 +- unittests/intrinsic_probe_tests.cpp | 4 +- .../get_table_test/get_table_test.hpp | 6 +- .../intrinsic_probe/intrinsic_probe.cpp | 6 +- 70 files changed, 551 insertions(+), 2116 deletions(-) create mode 100644 docs/retired-opp-carriers.md delete mode 100644 etc/schema/opp_entity_diagram-gen.puml delete mode 100644 etc/schema/opp_entity_diagram-gen.svg delete mode 100644 etc/schema/opp_entity_diagram.puml create mode 100644 libraries/opp/test/test_retired_carriers.cpp 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 4602f132924a8c8565d7aa465739918eccc7e517..4e118d1e5ba9654462a1b5be2d7b0f1c0f21144a 100755 GIT binary patch delta 9548 zcmeHMdvF!kc|YgueMwgrZ5qHrl3{lxoU35DvQhQorNtItz?j&$C^oSrAU0l%cr*}b zY*yG9WFEHcgHs$k4W2SnPvf?7uia7`JE9>iaTS+%JQ--1TA3y(O%#Wwo~bhFO#1uI z+1JnGWP^q<~8_UxYTobNm5d;HG#o%`Ka#2@VuLmvv^@08=>W`3=lYdB`k$W6(G zzbWZ*xyi80M&DsspSh#&$gdbp>BGGIS3l=F<-5keW7+K~rm*vID zWsYGn&#m;_O`9FoB2LO5Bqs9_Ii6VfiD3+~s1NNtv?FqP@;ZLH@Wtc@mHwG!*#tAw z*&k09DWC7GW9|iGy_2qIR@yRJ#F$~Wi%yH@AjW-en3*gKf@n1^a4YQ&{0-QhGtiTE zFL19#{1ERhZxQFI1=Xx=5u=LU09vb&r=bm)(rQ>3k@tK&Ezj)G!?8vhoXCrWm1tzl zF=fQ1zX42IjVise z5xkoE^(0<17+vMzVbUYxnX@czrOeq@dOmA4juC&>VBmF#UZIwkh(A<;=a50qmkiIG zgSO2x=fXy+yG1b339Y6Z*)8MTVV2RzG7EDsQdf@DiuvvZh7HowoR{NSDF0b#lVvf> zaEG{;7tFzEOfl=S^2U9(+c^)*-BAOu+9?_)tg=aIVrO7jCZEf@(cDPz4 z&#tJ;*F&pm67Ae&ywd()(_UMXCbu|WY5Uk1?Kd#sDQ)zt+-Z`JfG~47G zaMGIzNMa5;&Sh^+^#nMgU5*jj9P)`8yM3J7${(5gYra|G!%x1W@gWb`AbxpqNZ1*2 z*}NFyP6at&g;^&j)Nb84t|3KFA;agzpl}7E0KWdSZ)!#)&b*~MT%cjztk$OD1?=oH z@)L@nE<0m^_m&*XUNJP5E*_GG4e4&#DRmkmj55FC+%oNEyZEhyg_g*Rwi+v8iAL50QAmX{HPqQmYuaXI zt6mzTy7e+sr-qHZzx02!cmW`4XpmNwFZNH6)a~ zZ(-2VEM81f9F3_c83U{$X691h)nyntNn#aKt?#){{yj5qbIKI^ODe^>qEbpV`%5sp zB$LZ%c2-~?tT*ZQcy6w!SIwAjxsq~k^vjCrQ(0bQNWYiRpp&X`GD?dW0}UbvL{FKO z@oX3`a87P?0#TA=Wv%|NK|44;-7GB%6c}~rBVFWte;M>8i^Fc$6U&M{-v|a}(1kfN z_?&R7oh0Xs`MS18`s3Nd|LZP}#DRWP3LE-(8B`4YCMfo@ z+ArNRZ_C?mkm6-g_(`+P`H<{uT5;|3LU3K$45PF$03QZz>9~RRxO~6q`pj{vCi3FA zeEcT6o{UQl%*{I-Zr)i{TKG|WA5UxFO3L?V-@=CqUz^p=K|ectvHZhX zU4?aXW^(?Td|~cd@z810j7O0@ zvFr;*;YT+=&a+fHaaAikCV$oCK;F~^jaQJjGB(+XP3F~L1FIpm%)XWk%i|$AZSi%J z#uAV&0M8JV@Nh2SWlTQePpKYDI0D~G5QC_t^->aW1M?9%rvQE+@8iH$nJkEKXpB-Y*ax6Hg7J)_CsXvY9-S=Ibl zh$R|~W2shSG#QM+JptB>F{WqEuxY6@v(~eG-CaxF(3k3N^Bg)FXn0%1Qc;>hHce4K zi5i8d0Wvl*9VTn3$#?_Ta0w$(Ki<$!<9UbAo=8~|3sz+Pm zKrGdZD-@;oSTB?lOAiBTUZ{~;eOR4&PdJa(YOZpZeD$ZiWSSC{S7P&YjRROeW0)r4 z5?)UmPJ*yDp>HuLbqnF&eO11BXI1q}sbX%4`;#Jhw>)vrWT<;wC4ai)hnwuiHH1Dt z+ym+2R8Yn#0aC#3_LoxLD^kwI#9_{Vy|cAS(iAvMKF$n6~F`syNrnF%`i2h$XMuW-&O_(qXKCvXS?6cD#haV#*= z$hy^0Y)}+{6!BI8_#FRc+e5~uVR{~8$in9 ziKM)iW|G4u=t+zc<{)r4fmO=n%A@=l`6nws#UGdNuDrW<4+sLnv6my|MIga}4j?fC zAi?d0S}cIX2!Mpnd5UNw01_ht64kGPgs$M^VE3hgBy>fY`|L$P0v<6Sc?x);Z|T4< zhBy#zk&&SB*|ZSAOl(mB%or-J2~bs7M{36gBA79JH3)XlgA~pr@e$yR?o}|83VLA~ z-5Y_79t{KO{>Vs5PyjM|44Fy~Qs%;ugmQvJk#>eEvt4buQ??5D7>s~^nJkeftuZBfK!6KP}V9}s`0PK_(DZ}XAQdkVA3Ko@` zFkZuAK*5*nyl3KF1cNy2fxdVG2JsAG5U+s2IFQ7566{ukNL;JXynJ!>B-m+p44LS9 zJb)Ra1RCM^5i}Yl&cr9mb-^k5Dy{?xDVGX6z~I-Q0wjiboZb;_}q=pUi$3E zy@m2THDNnkO2DO{112~2Ov&oW;{c!4@_Rk44f-25yXI<|eJ1ogHG6q9`?Q)}4_q%Y z7pvR~q-oeSdf}gRRe%q9V{f&u*@9rM#{UP3^(hrWxTgjz(8Ul1pE!iv2eSyd+R$K& zP2>`T+{WKA4cM=Z9p}Yvjtfu2t*02Iwa>s+s6knq;nLD)*yY5I7GlmoI$BYUVpOF9 z2Ej6}pm2v#W|aet+GMrYbVqs7JIZy5i`b$PHE@}r?nNgIyeje|Y(F}#Kv*6U@}gVb zx@Ky{_F_lB+`pzqzU8Il4Qp$}_ebQCwe|96Yt-xWef7oOMSM-+(AtzRwsYf|jZ?B4 zkz`UvP+=aD@83V2rlubFI`5Hx@xblnrwn}#An$wYzFWK2Vb_Tkv6dPui4^f6?IzvO z1=flu?5Vv3+AmrTRe9(Z!`iU##mQo6U|3*a(Xhj0*fp9hfn#J?g|}l$HIZG{DnpW4 z^V+NynX{X$N%*4<5pAlXr7R@ju+ZmVp_zQC{M>$99?X}?XVy#<7g%A~+ab6oJ2u>b z=fMq)&AmEhUylZd0yR~h`4Ac$1{)mwNr*azk^^JCI}OzdWxXTvgAL8RPuBIh{2}@I zzGl+y^L?|aIosD6)BPrS@8)X0Sag_Z~lEmSv?javdq3e2EeXUCAj?D6)DiTFxkJUaYZJ71s%Z+fQ7sKZhZcRi&L5tFY33 z0mXyiM@ubLXSt8Am&g1G)+&(oL|OqAYOA8^Dj2v-zPWo!M(qGs6LRjuU*Ye{vk!kp zyddQDkNiRo?3|X{g>1>t+0sB#p4dP2LGlai4u2newxLzq$V`N&DplDmZqmOHRQy2= z`V?h!SE|s7>7kS+!yr|rgMlEJ0BouasDjt7EZod9UDF|~RtrND39F3>tJT8l zFd7;Ns}F_MYGFE6P;sM#T_;x^XxP9h4^!KtHWJwx6WOUnBJ^V$JQ+qhc1VK*p^HMH zotN3+dQLeIB_ZUa^zHxxSxg*Vl7a##iHW1LAynwnqPP{IINZLHLND6moa}xC4aJD5 zufX9)gk94ewL_WAXpL{%{;h~i7EyFE$pM}nyx0l$BG3abKsuz{;0bEMc^|h{DAxP94OgK1{Co$SfZ@t5 z_D8)99jsFIR(j@snhd|#OD!BjwX_a!Q$BC*lWlwQe3$Iq(~0Ll@0pH+d}78}tPU0G zxQic@?Ryu1*uQrXp5NX(U77=1xLoD1geApUV{S_Gl229mQt`}KFtY-4T{EMT7%rR zG=o_bL?oq7H&EE>^csO(A5pFk`Y9EvQoVGDTk_Pt8L=hfHihoT)MDt}w~}Vv)i37b zt;JX9g~dcmq}xf<%Uc*W4}j|iy3rfKRZ$HgM(NGQanRLR84)5>M(U}xLtN<$QLs5d zpB)&EE7R|a!ioKhd6JW%WO}77|LWi_E@k_Zr}(o4@l=BIFBj5-S^kN&mH0;maCUFd neqPo+U7z{`p3R<}P`K@BGr`{~-1}@dFZ|%kV?5FGDaQT_89Hq%jsofI__a04HS%11aL9k}Vgc*epQ>Xk>2Yenq|KtJxq{lxBRPG%pojPgNCB9rUs`^yO&y(~xB`AfmQwPebvm$E+ zkg9tSyxy_1VaCT*Kxix!icAUPVV_2WV#S6GgeJ7uJl^*DRnkXOQKY6H(bRN% zc4={PW~x0kxib5%rZ-^4t>)CsWTDO*%(@<;>vj4u;82;SX}jsL+u(}knM)kOG1 z;bT(^N1LuB!xJJLo@$+#%$piC@LXvxwVGa}-o_t_jxK{;(|d`^UeZOZ>7|*bcSHCS z{;Kcw_lNoy2Elg>4sHwwzw^6-LI1|UzacD_HhF%&NHnez=E+5)W~} zS0fk>yhfOuT8YEt(%=8xeRD^W)dzm!^l>3ov$npq1%rA>7k&kCNF89owANxWLVnOo zyz6TTpToiP`@u9i)?fy~7{6TUbNd@%?9CGeL9xpj{yK2RR$g9O+E%0h%zJKbKj4|J!W-J(>Gvf|0mk#)^F>Y+bB;M=zettjQ z0w|w|kp5jm;(%rty{zF4`iWOJQ7cR7&6ym%+pJxNR_?^~t$Lp*1qJZfmjl-CeB_u60|N7#mU2 z%eH=_h-F@dBOMxHOv7~ zUz&?UCJUT}`68gOYeX9M2~fx&>j0|G)&5)PrtT;2{epA=RPdAkkw0zF1euMSARzrY zqYA64Fge4SayKa^#I$T#`pN&SI0#_@0;GA?TV4wxbdWEbgi+%KCQ(v9qlS%X6ZqZJ zug&K2OYiCH!T=@(tO}Tfz(SnjJe)K!i%>9ubWgO|Ae!{{up_GJ(Wmo3ymB~_jwjB5;oo(1F!G4=<6y&-k{t>6hwg%Qhu2^*rb4C-{b2S?d zN_1MjTdQO^s5(VYMUd2F41?tQ7Bc0}MR6Tg#rRbiUuF@^8Iaf?3#t+UHTB!r15*{W zO--PbMrsLyxx}*u*VlynMc*%Bf0D)6pN|6#CPiDvJ~`Cu8)SN`_5Nh3Zm1@IcInb3 zY1RY-8_UUKK7PBQU$wi2n8x)j$*E8us$+%w<7&KN2#7VX4}v|hIVl09C80z?pS+eJ zW|>yvK0`B%%lvHg&(}9lAIAN+Wi42ubexOJbMc0>yPsHfH3A2tjf`t8@mr2IeQLe| zuSP`EC^mmL`#;p|nOOx2EM18LVB%jk`-+==0O|p=ucWiD*z7BX*;m$^eLB|-y`~t( z8|GhVmK8TkH<@3T9GV7_v&1q!c?6d;>Cad>gmX%bG@O47r{;(rjx@?cjWA`sxxQkz z2|-K^jm;wf3CR%dzZ<{_hxq2Cu}k>m-WJ4+CPw&q!3r5v%=V+3;a2U_VgNrjXMn$D zF#nZ|+9C*|VctKhkHwz!*F*j>>15+=gTB_}F;uk7tQLA`&aC53qjdYA%2~@;13taP zi?(GPllz)F3c*~ys^#dpVphMgv|49tL__zv!Tw>rn3lN4$A>gh9F_(%ydJomh0Sh_yCA4K}fj%*prN$zP!kb{Q*sMjYWx-=# z9K3M|@xi^Ox$4zS;+1)|MUe`bSUyYXGj$6)nu`enTSJ!TH6{Z{!%3vvTDVSv3FRr= z9Br>X93W1)&0SU{;~rUfKw=TW#)$VQ%0((jxy8SI=9zECVl zH{+6Iy4lnW=ymC4X*mInLkN~atQ={Sq}3=k-#vW_(K5z=4b#1YENKTxA8l<6*nDyv zv|Nv^o;Pkis@k!e8GDMg~K>yqOtXi?5-fqaM- z{bHkoro0Z5+*_~X-kANifc&s$vs30Ph7#V!D6zv8<-y#qn zauEphJpIu3$(i57gr6yYt>YAI5ZZV5g;nDevl+q(P0k2*4UiW;s(eJ>k5u+WuZb(HlJL*f+1ncOctMX) zgUKY+@!VsnZhXl0vsCJ>vpc~#qtOT$?AFqjv;(iL9x=9oM>Nu^SdZtrE1&lSAgg?~yYg$r z%5B~|a*!8_70GDVaIYx>9PX+bDO8!-PrN&XVpI}tmE?SAvr#aFLYqw`xfm2+RFc!y z#vxoup6jlBKIn{4NuKSl{93VcoA-2(P6s{|Ym!fOcl({L%E!AapD0!i2d4u>t}rKN zg}#EHJOpfuRv$nAz8l|cYv(h@M9G$+KVOJs)UA`xap&sjktTA8>Y zL)*NGhSyR&2cl6yXvXoK=!tvi2$A3v<>;?6IjSVjJ2~p6{3rY_%73aLN0sEU?#joz zDnHO&`Dn2c%%O?NW+i#JSi?TAYrvxTv3j~Iiy~MV4n{kKFMrDmQ_O@iQDQS1$&wT$ z=q)Be|L#9IH9(C|MDx19^YJCR1j=b26P!m&bJ1P_z8TwLI_ULsyL#!LMzy6CSly?c z&OWv&rhV%DbOWFys9p>AFl-e*(r|97q+|Ht+saK&U+Jr^;!Yf>chW4F|{b!kJFtLSC3_s^Q>3UO3um z`I@|NxKlWi7D_V_K$m73hzNe-`s0y;s!gmg8x;))j|EA<3P&&hA7hB4QgrK08L`9Y z^+spY?aUOay5(!MWssIU5|QFtRGo{5`0$zH`}SOa-3tp(=$}(l|Hfxii+@d1Yteo4 zn0>ABOjFBeDc7R$`XDah>Zi_p@`LAo>*?Fa!;yG!FR|xS1b8o9y8glXVB(vTZ%TNd z6kp~hVxFIS(u-~r)YX8!M6lMz_4iZmiKDJ48`@2vFo?$YeEfGLJ0#I!la0nGA!jZQ4+@o1Xx8GFN znvUn`*jMa`fxqn*?{N*@XM>;9Bb%6y>3eWIe8Rr>;0E>t-*l8 ziIwC_Hr&Px&%R`UeNH;Lb#Z*!f!O4{csxEZjyoVWT?GjINnx?YVSzgdh?_egw#?Q0 zQY_Y;2(F&oz|~cW22){6z^AP0WB=KU?g$X>ZQip%YLqI;Rr;*kyeB(-AW!bn1MOU= z20j&QU~J}x!TMqivXa#xX|4u5A1kqGtlj8E)o4SohtGbqd!PS(X{Xv>hJHo+=chc| zKr9dwUHve1)MjJ;R!j{%$>dCteUp;t3BT!NLt6zk}Ora@@=*q&8)i z+B+YHE}3IPVd~guj-8f@4U5#dyF`8r4i48i4DYn=hwq~&kI6UvagSr9 z!|~P}NAv!+d3#b$sGDOC;ee4&;Y402&s*zCG{_6%PT{a|t;0w~NnAS}tRxTEvBqUA z3X(6bH`S|RQE-h#FOo%AP1mVGb<3v*EG@$IfuK%m67daAp*Vdw;q>7M zBcZGNO;?W!rDJjbP-MF(NSZAfbYIF33bWNgp;m{}eTw^Q5DEvhWGb2kK|U!u1*I)8 zO~%#Sbs@WJoed}d@ejP@J%8Xuci}62E`Kg1Pv_5J@|W2&Cmal8Ne$iY+prTZ;kjanW-Sz|$T}c!w$$`0K5X0i+0C^W&lq#H!iqf$0 zGHHl9Jl>Qu{&Eqib+l#8Y0F-GYgC6C{l+^v4mym8DIxTM^OxaN_d_(fPEcb31-oD3 z(2;PQ9bKyi9G|$n=r_}%vZm=;-R=5q=#>0!+NeqqVBVkpyzb6AN2|8*Z56Yc9p^TD zeK_~Ue>~?pJoCSP+}hCLR=S{dzC8yg`ka&E7UzRV?_IbrTAc3LElztDy)eyqGN=7O z+1F^>hlBYBz6?u6CmA}k!|}v%^dco^`aCpBdp!bhQMIn9b+9M-#lO%wr8UGHpADh` zIO9b>Abo8IlYVqJDFKBBgEz%q3^5WvdK0;57whz`N+B>ol&%&GgF1)(wULj8Yr_>AwV45Tcs9mZRi}4 zf>29UNqF+rB{t{0yN1*PVaa}l(UyHW<_&632br8*2n%xdj8Xue(HZb4XE7#d!+{Qy z*2)@^{P|zV8Y67agwb`Rmz?@R856pQkkOlS!4p1c1J#(Yy`|wtKXj8&%OZ`WH&Er` zu71*WaO8I#9Qg^P?yNaKDIOg8-I04=yk*SP;p`k7`I3xIMc2WRk6;$6iU&vJJEm2d zN(ouE;HcFRYb#&-YSwX_%({Brt)eiennZ{bwycz z=ozU+fRxZ)X4P zb|Jx8Z_GmNXeiGjyPe^0kwO3XZ~po-zx=5$V^Wfeg=CffNbEBXUgM#T_g2r44$U1D z1!r~HLmLEBPC@2+kTU&uUnDCBP0j|a8VwLE_VI|F=W=`P4WK6*w;4-@#@m#86|!k~ z6=o0l+TtuDPx2g+dKB1_!ivm0w@rb)n`G#iZJK59ZhO{l$*UdC?3w(gGNTiRHNUCc zbBtU>E}iVW1}20pUe==^Uaug=p`&NZ3=oKm$WKq2el6<3C%GGK8b_! zmeffi735j3#Ovi@Qt^$=dWDFu@@ntVwVO2HAQxEjO~#(GkLvOy*l$Tn6*6#J3)0ru z&53Hr1`{Ci3PBOD2=d?-9n%Miais9oy^z_(srN=XE@>8+4U;&9%n0Xk*n7f^uLJ}E zHuQ)L2E9RKLBHO3KxJiIB}{{`0rLc)3)+o`I9 zMN920?UT$yL3ZgWHuWm^Q@C-J_{IYcvP5NJH$>7kL^m{Q(c*wTtJ%GFzCJ-0g=6!w0-@Q$@5S#P~d9|>XLrq;$Vb@OJ zA7#UF8axh%AeiNIN?X>>uB$7f&8e#Fp(8_}l5q(qvzq~7o^i>sXs??AXmquVQ6n)fF9xLyHb%vs0*n00HB^ zhya0RC$%k20A7~|2n4PeAXw9(6ChB-e{_Ig4b+|i0u9g=AXw9>r(>v2T>*kMoq7fc z)Tt{#u%^>x0Rp&YX>(q5QXbJY=lz@0dH=|@#gj8;jYxk}ES?8-gQNBjpYRgN=qfJ8_(w8_)Xlr&_ zsUDPT+sM*k6ey*!1*N1Ur3v{xDscF?SME-fl=gh)qZ2HpgwMPLPmdBl^AfZuC43gJ zzF=j1Iyd`DcoTdBgbTqr%+Z{b@Oibk6WpT%pQ&SSdXy5hX@=L8upsD42bX*9ut2L) z!spduL82)g_>4~bH}Qli#<{J%-jkt}eU*Lgw!qX_NE3dU0Zwf>_A={W*7lkyox_hv z!~Wll+TiWilmQy&nlk8U<8_q5$7|U&rE_JA3R$>>#jZGtor|F7(Hpvpg$5Z5E|#Pt z*l+R{oAMmGmxqhw#^1=^ACjkS4GPhiivq)uj@3C7=*9lvlmG@%T`o6+Y5%I|%|&(( z4je~(Qe9bb#0yP0W})yr`yB!R?1}mDq&7eLq39IYt%lNAV99R_q5y)6goXq0rso-r zhFo)rR(%;k>-4+^rA&bfT`LqohcJKO+8+gWf`$sJu*2vOPILYH;b72FLB8lH5- z=sPZ>o>6Tz5GAxDp&hUv@R>s_EnV%HFqvUz&yvXI71@PGT4IYWCgE}}6mtZxbcaPI zrQ40LKO9cC@u02TH3$)=22zP=L`brT6XqLob#G%fYkQS&dlsxVXCq^_yGAy%o{dbh zp6!DeIlCjk$hsq7Bb&8n2Y==6J-<{uZtLWinG*u((_V#0B>`bqF2Bn~Q^f5}t~8VR z5Mk&cQauEU5Owq&*lt3DFG5;jyci7Ougj(xSvaV;pnB2e}GqaU&xwT_azu zv}j}}Emtw}x0AHMuUuZFpi+4um?@oyhg()C=NQ40G{wRl6>t^06#Z)#<3LdowZo@F zdy&b`CuahY)&q~E*yktJM2ukOX`0}!n_`G~y%r*7zpXJ)e^S5QslgxiyS(HJ70XMG zcE=o}aAOnAk=@wTBl`4dz9lk2azUCqc^cRAwR}Iv_YX8s@hV4=+UW^WCkT)Jv4QTw?*E;7jQsSPJ9u1z!-y}B+w zsFcGWSGrlHC>Sf9K+~t?xz2SpZ2nyPT=m*Wk5tCP$Fq^7x?Llw<5yd%y2e7UrsX_Q zp0$_icI|nhyuvTF*N)4T^F+B)EqVXI#|WfHI3nqQ`bePyS?*Lk zQm8-#zr?*+m&2JnQ|X{Z&G}BvQ{6Sc)2aDvcg+)>nooDvobA+npu1+ZQ*)+RlRV`X zdwOnaWDK;ri?cafxB(kz%U!Ho?W$yT7c2j|VBDGQft(C~UaTO3QS9{DuDX$~x`_hy z$cHVib|mRSMLPUJ<*1>=SAE`qz#*ZzMX1f_eLk{)k9FJ*b3%8Diy^K$FDNy{omI}u z3Y~4{J9@ReW!AHx@$G(3q`&iJEEm8v$yYE4Yz;@o!(sYs?HT}Ex`L=xYbCa5RigE3 zCAM5uqLpeTwoqknGzk=#ds;}TT1txKrb$NbQc@&06>rUZJX4@G6?fznxP-YjreaH8 zVYi9U?ZP3umQhOLv^97l5HR6mIuns4<;@IzFriP%gV!$fC1-KvOQRel?stV)QXK}x zWh)EzFsVK)YpKD-EZNzHy=;K2UtVQkovn3+gf*3@1**$>D81CT>@xypX=8v0cr|({ z8yhUJYBK%k;N0BaT09{k>fb7SuVd@>YHs}%8 z#=HY%;>rS}OpTR zZ{W(MYh1Z08+-#-E{)^LP35q_SOaYRD0DtRXg&bbG#|i9vFvn_Q0ArGK0sP)K7avt zgW&_DHJbiqK0rE>`2YkD@B#SDd;lKt0dl_^J{Ej{a6KOYP8RHQ!3PMFq7P88=m<@r zN5MG=lcEoRO3ZwKFe&-~NLTIykWE;yz|`bE0M}}|YjPhTOo~1LeR3ZlOo~1LHMtKE zCPg2Bn%oEAT?xf;dienMN|xemiatQt?E@e(1s?#a7kz-R%P4_GW|YD%qZD@e08F=F zew5}?M5W*Z!2fO^0M?oh@Npf|m@P@UXwfm{ek(x0MuCJXJ2iL8zQAhLMGdFsU7A?zmAcHB*6pQJJ6y67v&@EE{|W_S!39$Y-c%4VpLTg2*O@4^L} z)ZT&(w;LTc14Q#!TyFPno4`(#Da8f{xjWL7VZ+^FVZ&FBjf{7O%_fJ9S@8@T645ej zGCL18afZ#Ne*!jKL-xUjuN)gW7#TKO95&`@WY`RMu*rN8u(>(IW{a?~%r59>w7FVN zd9}Z(_%#{dr|=hbqur%4VcnybG|KzrWuaHf$*R5KxE#IAy&PDZdwVUmIhhK>m7^PU zQ$nWxysS0fpS5o?aK?fsc}zZQHv1fV^#kZqnfOn4ia$;9lvJ#1#k*{xgZlYIc1yxK zJWf2-Bri-J2U<^lXCP=0O$)?hT_7ImfOzCeAZXw~DC%nRU|8InBY|tV9ul0Rb-D!Y zWPpIDEqkqsJ{FJtSlfG(g(&TPE}h=tDiNE!J7TOmgFU-%NxSoYpHBDSEH@JK?#Nwr zz3$3IQ-Yi00h5bnL_mB777q_Z!&$LfrLw*-cszWE^UU7F7YyYx?Qn3;=K>>k@Y6nm z&hJn8^?o{YTx(oox2-YEqKm+!rCO9_ED%EJcCwv)TVul+*s|?4V{=| zEbcbeV!X>}qTBKX?#{o9z;tbXGe4d71-r)^4?EsI`?So6;r1%ukH!O?Lf+V(7G8cG zjg~S&+&Obse-pTnZzE)aIyJbU#hP~B1*s`k+?!Pl2g?o@;2^>22)x{;^Y$IqMqd0x zTAVH1)SZ2~2+yV$r+ZJHD201tr!n(Mc$ayL5B(9*Fe}uh7<7E1x1PqXh>d|A^Y;W` z?^yMc%K=mi0F+RLW8M?Mi^AYzCAV)#-v<~-;<-2Kjy+Bc_PA^bYgow?9ZtY4#qyJi5L`8mMiE7Nf zUhQ-$mc^#cP9K+nc?%QAJ#vg>ZuOQy+LD*35SYE^kTO*Tn9`1nanzF4A=WK`8%HIK#R8PQBwMRtkD! za5fPOgEj#ha<;O{2^0;KI{h+!#(p#nwFXe>eDM-PeQn{ZcHHgOi(B9U+DjEhM|3#C zI0Mno`%IaolCGC<-m<~oL344W^WSkY;1f$buISNnqR^5yLc zxih}O9y7fq0ZZTB&#(j*%m9}#z=9?K{EVli21{gI-f@?x^%Agd7PLU_JXaPNewj}I)=JO z@61p?U+f(&r*>Msb35ImcV?%FH5SIcKQ+_potx<%y)!fI=1v1=ra2}xEA8Iqms{x% zz{xutLoraY(g?&>s8<*LYy&S4S7mzHp0gUFA43WXd(E%E#l_0yubAsPZlPj1GjnPF zQ*IY$@(Ng5w4&KD3%n3~8uVjgVINKS*!$gY9ZDYioHuu~=S3q(@4N1A@WD}ktycRrMc|Up zea@?+U?8ls#z_@pY#GnV|5a7;=;u9E*=Fsg=wGELR4SfNPRXFDvhIa|&`@>>A^C0^ z`ua(m*dO8%LAtb-m9B z=q0}ol`w^RO*28tz5e}JOyR~~yn)cP;{rMKjmvxAb#m{k?tdr_PUG5^X^7ejjiPZ- zdnB-1U4eEsk8$$}9m&Z`eyxkw_ol{;wGOn3pcSP<7_uz{<4NlXeD{YvD(uq)(zO_Wd#eQ@U zj$tVe^F@$OU)V?9udi7DQUea=MlX5AWlkb?F2LlXOyZ8IqrhQ9g=*Cxh?W7#Zi13c zS-jBFD=CON1!x)bZdgoc8;dKBfTqAii)N3vc`<*x=N9ix6rPjmS;wDzQ4Z;AFLQ|z zfo_wY`ojg2j%aG0UhSQZu2*m(S`gek1`MpcZoQ2h#ewieE81%Dh8SujdLnA-Cj6;Z zp6)0HdIJI@gj+n|*dpP+0r$5eTU_iaZDsNzZ?0&tPO1-~dnV4DW(}2*YEDbRbi3!B zrY8GqKzO&{z%-wb+fR6idhNJk4tOP0yp#DfM?@OJFscU{(XB+!7N*0O5hAG}77#u! ze}So;97k93#9yw8z8gp~HpWbW#R6f87*F@s(7SEDR3AYr)dO^G$UnSKCdpX?(#Leb z@R_cSNq{m1Cl9Pp5BB#Z%JwG$_;V?`b=8i;mM!PXT%DCBEn(|Z&NHVO5}DIdy$s^p z$h-qWx{5z7kPIAdl=uTm04K|9K59Ev8JEzMh7&wW1{UCzW*j8EGtD<_oiiwh)c%AE zJ}E2D`Rn2BVBuTS81D}LLMue4c9oKIRCpSCmr0JrO?Uy=<&yI^U9U`+Z~#RFrLBmK zl`yHc)(X8({hUvIxNmfVXfkO+bsgc#sU zhZu0v2{N|KouH_WP$*}w;wlP`rNmfc7!0W^2zWbd$3c(F##;KhDX^4G z?$uC2A{(ymRTbFUF>An|d~xLinxmBuq~id9Ri<~P7=Y-6o0`{fl+%LnYInP}XVM7m zGeu|rdxr33e=AmNQL362)TTkjB+ct6iKK2Rp!Ki)0gy+$qw_5E({)drAkl{&?$j8lI; zsmvwEkI0dL03uL_s6p2vxGEveXC&e8A;hc*o&9D)Yzu_-&?Us~3ZN4)W4J!ZaK%s& zA4Y049adlBD>;d!!J1`HY*Md9BgvC28GlWXQo-Y^Ab3=#f|r)3f=78#@ZOfo73MX? zMD%mIx1**l#moJrEy-2r)O5h#G)$Z7-m9r;*_PU~{Z71|VovUIJIm}Wem|3A2Fftz z&#G}%<(wYQ@}~s8Q-bR$z@zMDr#U#==g5vW^6p&%vh~6@S|?-qtk5xV%7$R0D@7$R zT^Y+M3DXEI%fi?4XNQcaavZO3DD1jxbaItfeNi`AW$RqXRldJ78ETSMK_Vy_eHVc= zje9@XuevN_mX7NW@(%zE;o~8OMzOjZHf4 zG5(rNQqp0>dzW{N_v$<$O5_=LWg=_uZxp3fmV4ZF!02}!Fj`akZ1Zr;EC%g&oiq9g z`NqY{XA1jdf{a-LjNOVtqTF7YitNzI&qM2qNxFkEL<`1SYYR-Vi9yobf|+{xO5gEd zLh2$_dX3DMwDsoRty7T_Y+&hY!hNx|)_dWG`O-n3oJTA~Zy--*uWUn+xRy`SlAcd` zbASxIUU)v8b-Ivs;+1vkgsQTs_UhF0mbp5Sle6wf=|x%SE*{vIMc^?A@)1I1wRzD` zkb0KxIv@0rH@BxILpFpm>nsof^(el+ zQ+&M@Z#7sW1gGge;l8BHXX?nGZG2ebYnH{Cs1*m9?(Y8pCt# zij?%nwJTEC1e7#7E44tWoPIk=OThfLNZd1PtLUMgO*Qm1?&}muG@) z(WaJ0vRyytJLC0pKGw^sZ^)}DF7k>#Y>acvgKdh1!(L7TUK)e~(`;Si?Q$0SZgo>~p`DhzIXa z#Si<0t}&^GATbd?z+81+$$9{;HnSysF#vwmCj`;)DnJyJEsKL_k=~%h?u-q%!YGzw z@UWk5uGco=)nU%>1ppy798X>wW~uib^7!*{$PrvOd30z;pvA&0j$)RJPp~CFROm%# zZ@pReI`Dw=NQV;zM8iaaQzQE(t>S0>TH6lc2m^NJA87u392v@w1_Lw;CGx4my_dYV z;Tnc}D|Qm+Le4BOFW6H6LI#a{wHmKbbL)zjk_1{|#S|?QdC;S$kRJr@ zWzy+kjY$;VA}>TWx0geYkGhit;iCi1b_+oU2O~S@1D6v@$sWSm?1fS@x=E&e2i(rTG8Kkcl zD?68|En%?Jt8PEwA&w9(o6x z-5XCy=JXx$U2lBctLUu@``V>)Jlqu}Ba>8yCBs@+?y*g78c<_b3!B|Y*qbX3B|!qB z6DM_Z7u9@ia_uqkXV*9qq%Z8?w&LIj8BmQMIxHsvJMt%{l!sB)u_I zz@-oEDqN{}bFQ4;oWm^oNv1s0J9@5pSvTjht8%yWI#ldBQTzomWr97J-p-LCW{D%- zUk!?>szR1#GA5mK*!*D1bZ3mzDTb$5NH#)Ek&(pet26 zOGVyTiV9fj2v#nK&R_X!adCz7R~=C4rjx$IJ5&FHR6m{lVff^h=+IwR_w+|=pr)Y^ zv9bwsq1O4rSZlwoWFNC=>gG~zvAB=;jLaz{f98fnP0Mfq4iTA+44^A3>z!BqwA?0$ zXWoKl@20ut80nwo4Cx5ukIaLQmH3k~EFgzy^^srv<6nOAw@$qo;nMF9{n}rA;*bC9 z-ZzT@{QTqJ`0%HG`$Ie4LTHkSME7tYt?$Y7`;fi`#oY`CNaF4Fqnm2AK`$DD=|G7- zMC#S)>qD&cIj{CW^`5EOh2~7$UW%s{7M7-`+Rb=*>QHmVTRE{ZyY#)&_fE|&uG|vO z&o0iyhmWqbFX$BrF4cZ}}XwPS3@_>PGkyLXQ4+`e(yK-+l#r&t2=(Dug^yZr< za*#cVP~eULt;-qLu7KdwbbI!g8$ccJSvtD7mYWXgB1htTr&d@340Cvir2>5ymQEaQ zF1FXucDXfsjD>XwUQA6-16r6JIXWwexy8wXm%)MrON+D9Qw#CR!qG#Mi&KZ2@yc>@ zVF7}8Ub6S~x9+>`_B-zU!Mon@#{GA{>CJEXp|}3<+ur_;A9?4<_8mJ%ca4ot?4I;i z+AUF&>9v=Z;)NxiS<e zGfPbe`JP#zw^vX?)AK-ISy*bn012Sw!qma$0zy08Y|gBp;|`u^qrGmCuuZpSmrdC6 z+Ttp|C0;t(US2|_P0h@(awPdF5b@pCargSk7Mak>lHzTg`iBj{{`K$Tra`zPEWiqkxBGF1Drd85*J7rn;QL zML-9qR+_seGQ460YsdS&ZnfYSCIScevt4k8XY-)(JsGgIv;M7cS2IG#cR znF5F+$kMuq1iTV!nk>|#i_PQ9O=NN=Znj!T9pYs3nlu5>L;!Sj8q#}*nr-i3a}rfD z>40O;P%E}lrdo%ZS*1on#^GJFbq~_*@!h-!$mtSlP9vJl*1A~U*P^-`O$Pylt)~Qm N746kZ4}Xv6{eRr%Z|DF3 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/sysio.epoch.wasm b/contracts/sysio.epoch/sysio.epoch.wasm index 2cfc67510dfb755928bb48d70140563cd3252796..1b342a95c709089df44515dcca55109c31c647f7 100755 GIT binary patch delta 1944 zcmb7EYfMx}7`ez5reAo2;)P4AIA^p@O!Z>iVfa(9!j$%GT6hnys*$Qg2$^pbDLIdYy{ zAQ#Cc5?y!N(#`uUZ4TmZ;2Sf>M8pkBnscNI&(9H>XHcSgW`Vsl!X?j*l~h^Dq;p1> zjHt0`SwwPKjWw&1$)m?Cf6m-z5&QZr=Dw0e^XZIPF;$D#(Yjd*omg1TUwP9x;_0-t z??08cnos$l0cKjn@M%A-cruMoEPND0S_scBasqzd`AIzR1--mB4`0&A;#zFw^~Jy2 z&`9rIOrkr=M$(?LXf#vjC8t>XrR?E|X5x&EfnAkUyJ}Gy`Ky<=koXqjj3W7Jv`5ig zHWf+>_jz+7ahaakwiH+BUzLe`y6+sQ%a_Cxs+uh5<0q?60R6nA#vypnn%Z0(rv0_g z({(j=+FZPlFWq?nq>FItE|)>HYIh#6nfC3egO_T1ufoe`?y~`#=){*p-PTh0=#Q-}*v7XWUJNr>L}&O5g}Mj*ZBZ41 zsV7ZSTItfGepK-Iw$a3t)j<~?^WiAHb!-aSXmWeAnD@4eIltc?DP-o*b0AP+yUx=G z9VO!6qR#CCCAMphfl}1Dh#%^HISv8CIJibz%Th65+9qJyX5b&T2?T9({VCgAr#^2s zPwev(+~DW>-v(O|hj{w>-8lYIAUBijBDCnw-{`=-aT~l=kPX6w7%NM;vLqU96O9(1 zPz+&3r_p~g$RuCLDg|W~jh8%99u(8WL47^aOvzYI&^*#KB4)!Kkg`a!;x@KSBZ^&> zRbm1NM47JlWxprHG-1Rr1Y_7J43idwG5r*VQHNo|juSBMFictyhDf=Bih(j=gIQ-* zuAC)^OgHMB9|?z)WI7KRxE4+M%ouH&Qf`b1 znqu-v7JT&RD6Ax?XKUBYBUmoCOu;eeucG> zI6R<(7Za>a=IlsW3Yu9?B2v)E-WiKKY-$qb!q1A6@G*|)cax9~a6Kj&5r{fQgxtbC zZiZBhVv|#_9PO+o1zsFyP6ukBvn~hDqJynZMGrdJ3u%~)E`3!R9ub_@Kg_^J3$C)k uXR%CZUEqcbt@;`_DuFa`Hnz!24ILsSQXR0F!8dIPwrPZ^IlqW%Us0TlrN delta 3682 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 Ixcf@RKPFufo&W#< delta 2574 zcmbVOYitx%6rOWu+Yq*o0b5#2+nwDubsO2*25c)z>=1c`M*Qm}w#DeWp@@B;1wol15{-z3i;?OI!e4!ZRcEkv$t{BQx z#bFZ#2ZrKy=(DX;1zg<$t|)?$n~LA3K&-;jZ2YPCy+YB_ofh-jXy%OwlPi`j;l}0h z)Mb90P$gV7DNsp3t%bidzt%z?MHfb#U+d)sNlAJyrRby!pAv5k%eWX#b><)0=cB2chn{hb5r)IBlNK_s=f;VcE@*>#uW)Fi*gd9a z@>mD$Hhl$NA|5T-RM?zmoh`phCkGZkYB93RtBY??>VAqvsC1!`(>YN=C=>)m21wD4 z^AAgUsFN#~&cxck)aBamP$@voBoiBq3MbZ3^2Lw?@g3s5aqhsr-$=K10x9*{oI%BJ z1cesm5k_E+)Gbq19!VL=3Dv7xDG%ktXyj!Pzu!gs2hc}p zE+kI9Uk~ojfrWtdzdVp(N*?wL>GWqR&Qn6DJ*A19R(w1W^=&n+j*C-dlgbJZ`W=2GEar-kQQ8jX{K8ftC;=l3Sl{k6vj#B>m`S1R# z?Bh%&djMcx$s(-RiytxFFSbxp#Bw?Gw#i{4iwK?8zcLj#zh7xk&`f@bh+52)RT(0- z&u&q#h|#)eckKyH^nX!tB`GSss3OV?-5c_MNq+RCG+DU@;@TK}z+G{Rr zm`Aja53he3>tE}AcaKwy4;x552yg^>cH?b2009@5G)=+R&ZZpKrZC#@B}w2_Zy57I z9%!0JZ*pGqbn4}*=KP!y1#yT(k%W9NX zRkVSpbx);QuItXCR^Hz|pVss5-H%ZlKh)!Wswv(H9i+8W_~W(6M|Jq1Axs;?v^h*W NA{PT@sOS8ce*kt=S$_Zk diff --git a/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp b/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp index dfdd17c2d1..b00d8cfcba 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 diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index c9dc4172ed..094164d3ba 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. + // Inbound echoes of the native collateral audit are inert. 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. - 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,14 @@ 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 +693,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 +860,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 +1450,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 +1461,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..1ff7a70e72 100644 --- a/contracts/sysio.msgch/sysio.msgch.abi +++ b/contracts/sysio.msgch/sysio.msgch.abi @@ -577,46 +577,10 @@ "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 @@ -633,42 +597,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 938bb5137aad7e20f3bfc4287c7be252fdb1dfda..e5e9b295dd07b0942ebf3c85f862aa81d48f4ca1 100755 GIT binary patch delta 39058 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 V!+-fGNVzIVS(&+Uta0@4{{!mwB^m$# 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..f73482d06e 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 @@ -132,10 +132,6 @@ 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 @@ -169,32 +165,6 @@ DataStream& operator>>(DataStream& ds, ChainToken& t) { >> 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 +256,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,98 +278,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 @@ -525,103 +341,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..001decdb40 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 @@ -772,8 +771,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, diff --git a/contracts/sysio.opreg/sysio.opreg.wasm b/contracts/sysio.opreg/sysio.opreg.wasm index 4663042a2fb3983f929012cb5eccae0eeef8f7b6..5ac8c63139f47657100d3e43c5a0b264ef53633e 100755 GIT binary patch delta 2389 zcma)7U2IfE6rMA4x0GIrBebP%xAg9ANp=fYYKpt2pzc&q(5lrKgi-_y59S&Z^@YSl zb`yB$11QND7Yvxbn2;#RlEqXE$kG^Xlt@j8VnR$GBq^Z}4NoLA;yE*WyDbuBAMVVY zIp=)e`I|laS>*Ufk zh@i<)5u`Xv)S&{;iJTWvGC;(EO^u)rcP0AKvE24A*V|EY32z3YQv;shCf zj&?T02}kc*-LZCt1@XmJsA%7S)EzYrfgHkhf{ZZWc*N2h)~8md+}k`+M`Y?`mtMN& zL%-yh&<(+e>@s;#e~G|nY%ZGVFdBWiEBXw1Q)d=xoC`I~<;jJYrSOutP(lvsm;L4! zhlMG}&ELavPzI?0l*@BE9%|80{y7` zc*f?&F9xB0{*lSuxc|R~qZHlSa>9$4R;-N%4b)=hf!ssx%h3If;9v3byFGD$Qh9dW z&IW##mv=m!pyO1oN9-&+Ao4hrCGmCwI?&s^ZHE2V z%j%cVyrCkb&?Lc8=LK_zO>3<5D;Hw;Fl`yW+apGkEReHUHfi;TYNYW-6}Yz%)HT#uNyCh=LrAm0i6&TU zq;P?w>sF*JZ*ZfgF0pxo0_uvM-McF7vsiU>Ky?ydNea$(D537_kM!F^@ek!*w0dQW#VKhO2P?somb$kJAZ zuOGIB4CE73i5MMXc+ls&ll7jH7OAXA>D%40M!a|AcO1W%-|`pxnvU^Nq5FED5+`ng zKG3_Rky7RfRUgO-kLuaMmzI0NXRXyt2<1^G^uy~rdeDN#hFZrHSauvb0y6$6-}v4;K__$HXP6P@vu*bIU<*se)D>2(kRI9aNRQPG%g1WvLl3rm z%(OtYsLVaii`SOHt>W%>=xDr%nGl#ag#DF|?rnnSp&okKHjlZV7B{p&JB$?vTj0%x z>(m6;JMwz*F&EfRIi3Qm)I2V3{hhim9s29%G4<}P-GPs_+WC+zLdquW{}0Z zO^`+=75u}a1kgIiB{z>t&T<(y1rEu4kHe1PU^aY4%I1wq@y#KdTrCIx#in?z8I~gn ztYN(R2GTx(z0KxP=(iMXkA)N_=27UcQFtw+(Ep$bDgN0E%ZhhaLlWDjV+};({{V|1 B-faK? delta 3920 zcmdT{YiJx*6uys{m}axdRJS%JYdf=9bkk<-N<&g(X>%*JRY8MjwLWO{pHqKW|0r19 z1%pIMH8SE|i}*_-;JU7S|t_w+I~9lX-FyL{MoEtmC4 zHY5+ymJi7(Ta37tI%xa&ow9vr*Y-3cf+RvhRu8?$oNGw5AfA?LnO~e{im4|+X4R+^r(qu=2R22CYw%< zH!_QCStdG9=~_5tp6+&*N;%uT6l7kOzGA2wwkMJ;L1RH0h#ZgBS+fFNumfN{O{*9bhiOGW91LDR z@2v7s`jW%!8Cp}@m@9jRWL2svlA+N;fU+Z#wzB-3t>)N{G$UV@8XXXeP(gjPtwR3@ zpZG?j5fy?TzR#R7><#!$Y8HZr`&XBK-vQFKfexcAQbDcka_1&|PTN7xzRfeAZYW9d zsGMot=<(I3nDJ08GmpCri%%&DDjQez)Qq=Gjq^yTW`t@ixNBo~+XUCqFf}`~Zs5i? z@symoe@m`er)bdm@s_{PNkY*1>y{>+&u`r*ODEg7UB+-aF8Ahm9+6N9C#4uc8dyPC zibzvPQk6E7GU&XhYt^L82@ozrGcsylEfStTz_aB^sl|Nf?CyvsTwZ`i=NIk@^c|>x z6vmS$v7Iwp$dKI$?m}o#jMGYN(ycA^42lYO$%P7LL=7Nu%#r;M*+ioz8+~V^?@iHn1MExX zh*nC}kPY-r0SeFy#t1e>8e^-UCF(0e~P!JYXbA8VCc)?!zrFA--dM25JdaJwRpZt9K`h&4ObNL zZ9Q3}LTc(ADQVCo-b>XqRy1N7m`Hr%NemfcUaX%>vdBiFz@@^7s~;<}Y{H0#lnZf3 zQ*pDXE|Lzm4zDdg@dG_}5OETvjQ}#;7&^4UHG#*XOOX6eqPZqU#J`P(s-~l%TSC&y zInPq}$7(ZMg}!6olzTZHb%@TE@fdC-F4V5&>cKt_cJ1;05#qcGf;=iUm% zhZz8rIuM@U!!zNJoqPp97M|+lH}iMGXL|VMo`tEiTID#bVH+-zDrqP5`Bwr8T~?fp27?4k_X}eS~H>Oe2R!w=C0x2|vJz z`BOwlOvTW3`I&pjEB$)Gg1IF|NM$>~729psc2$N>$EKb7Y^=eoR8veam}ay9qlG2w+bWqIaE*E!tsegn&Y B;ROHy diff --git a/contracts/sysio.synd/sysio.synd.wasm b/contracts/sysio.synd/sysio.synd.wasm index 2c0201caa7e96d9d147f8c0ec277a56912255fc8..80cbff714f379ab6952c96a65139bb92b1fd4dab 100755 GIT binary patch delta 708 zcmY*WO=wd=82n~8Nz*>lu1d7TwAsX1o|J&q($r8&wpK(zkhYc}6iN>&ucFa|iU^4y zcu|BtaJ439OBE4(@B@fds6ez0jx>!f>mFjHkPg`}BnlL2%8Q3AVL-L< zGqdvo>W%3AllRywu0I_@+CTlam)Czli$V1HU!UwsSQn=In=!=1BW8uIuSJ&d?`BG2 zm{96!5Bl}XLG18HmZIBDS6J-d+IgTp?DJl%taIZ;|>)UXF~*=0K!AljkxL zuCvirY!%*bEhJlo>?XmgidMF}m33;&zPsCt6&66^1fnQV zNs{wGucndfuQ3Ug^hAwiNHW#wiMruHvK)0PI#wXpOvqH1$?Jg(+>R!}j3a8nnGCM6 z*bHF*TQt90s{@D!@qPH-gL?3L3?pc-+baf)KrlLvcQMrU#VIH~K7k|u@rMai3_d=I zBx=FgNwi_3bla$`PvM{q`=(*Tr_*>IF=6>CcDLWL+QOQ-F$2vyns=FTs$Ac?gj0Hc X7NdrWV;PSn8=l_>*oNM|^!@nfxsd2O(OnZ|t^53%{GR2q8y3~fPASrH}85XHc z-V|OG?yA{N(usC7MG7<+AhT86dN-`#hVf)LOt@hv>h*RU4J-<7gt86Idn=DHE}(<+ zOBfa&4%4^{>|vg$=(NgIXTt~w4F&t%8M~28!k&VTNH`7ClKk^PdC%;rsEB3OezuJFhW?;4P$83 zABM5x!go2-*eFhdud`z?*V;>X;p6MZO*Gfcn*2sk{^Mn6!R{MZpgGEa6&v+zx3A;7 Kmn}!Hg#Q5kd-O~I 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 07311b89e74a3263b643c3476c50c89eaa66c24c..f3b7dac7916394352b2e37e6506015cce6a5bf04 100755 GIT binary patch delta 5778 zcmb7IeQ;FO6@T}OKj)2OS{-?{H?vca}9 z{b%pJ=bm%!Ilpu6Id|XVzliVudHnEGk#KRRh-EaV*tRdyBOY&CA607bYroXP$620oM_Y;s^u(SLLeDtG9l~5p%h;GcHU>uy>=3pN z`!J@N7^7t#Lw{R5K5IHCX=1}*0Pi}QZD18pX7;z;5Vu9K?e6%8;@r`ViPviAeEZQw zGxnI(rFP+nP3)2*7Hhm3YH$1J{AcG3sf`u9lcM2vVO|}J&DHqGMy+*cr4X;OZM|QZ zJ}0Z_zD&!`F)~k%k>ORYZSsQYb8?k3yT-~K7$d{0j<#c)f@*xU+IijIL_EdzC4Vt{~M7Z4w7 z3#M+qT6tg(wCK29>bYgdFwGzeF?}F3J_Z7 ze%YK8$uKrpJ}d9di4XG6yddriL>U(70D!XLxL74R*A1Hx|L=!bt_4t3lHZ%-A307Oyuf!TVG+}KIeL_u!vq=n)&dAgJ4i(Wb0 zNz23=a?vw1C)^uMMxd`K?|Oz-emOUKSPLJ9jM;{fGqz+{;pN(m_BboBfC@oGI^diS zu`Mk@*ZfP6D+8a3qzEB9K`(`*v70WX;gD?HO*11yA!T6cQMq)L9c8(@jo* zP4e|Uw24X)xoj^@oUsktaUZXU6|thOU29c=*PiUjmkjxXy|i%_qYBG{b`;7cLyuaQ z3>&9dRaV4JR;2;COzfkp>q~}f-pa;WwO%~R5|+nw*FLIe6CT}1b~9pP{xw+V1tOuv z6q3&W2d|e?gCY6pKAM{tHh@f4Ak_0kpIp*KOEBEmMVB|63E7-H$dU`-;Vcj6Xlu8XEG^fF}_j=ZFUqd(!=RTu%jW4U%UrrDTln?9*axHddMe#!d%G=1icfI*xcIO|1%*My)@J zYQ^jb#^Dl>?B3#U-_@Vf{1_Z;tA)ud;!W-Zxp zlU_7f-pR)K<`seGN%UzB&+2t?gI#7PDukRTT7>yBN{4r4Q}krQ z(N}pe>`FMOB!ZIW95m$W=V)4{82M~1cS@?jmv7Zj*sZvZr7)lA^oHF= z4OiPsBj=2g@Ds`D_8UZ5gO`adVb_XWbT@B=DqPNPrF67Ua9>HPw8mrASgtXOT(XL= zo^KLk3a2Mn^&6bpji%I5vFleRhY5*GoK2?tX zJx6ensfPY4Gk^*Si$h#BSQMG3W;&w(~}*uuGIFe2gxmtUS; z2r)1o&jseKVcTT$^E8FWJD#WMnZa@^Qng1lB{tQw07!B7Ig7Ud69$YVG^4N@$w>^D zsvF!$&&EcCu5q@@u$M;AqsN^Vo~09gtDs&@JE$Qymg#{;i_`kCa^$7qODuE9S4VGoL3;4*i z)^W@%-#SXEOkbQUT6Lp_KEYU`$+jf?bROnWU5MFw!7v|FrlJXRA5fjQOdhwKMHMOK zZI5&YQ?iuTr-8M(OE{18}S*@>W+duWMk2aw6iw_l*mlNd=jc?NAHu{1x4n@n9}LoV`j6RZfi^)zP+`jLVR6K>RZ2J^BOO4@FTI>J81cajFkNj}O7c4;I9*Y`EQi^fAtn< z{OID2t0+l>yEYc$U=waRPjHpE9;#!TbXBjXHtAjbM_}S&t%JvSlfH{bRD~RmAw1)F z)S;P27;zvIOykcG4(FhjYm5q_(}D-t`$5FMGN%6sqC`c!?DqduvEytgoOe9#ti1g=U6SD$(8;Fa`WU&H)WNW}x5dXFf$ zR{0u=;c8`E_2+}HLw??ibVL_>^XE=dy?#XzA6n}0k)uPn99cG1_;ph_yM=n35Sz>2 zWbpM2A1J_zmpy-Yk;_ie!bpd(@G&k$yXF0-XbLoLNb3B3$08_FkG8$u<6gIDwr)1* z*MOg#TGuJ?S02pG5Ai1qTAYIX{VAGfmyN=SV8pNa1dJqZDPeFGHTk?nQ}e4{q4#xp z(@dH4<-GduwlFCy|i}>^ho%uBdywAj+ z@+;n;g+Z}3zvT=)7!>EE@iyHc?v!77o32`G!ow$3Z$KTAc^lW5&vSc+1en6NuS4|w-3_G*Lb_!1#ChQDBb*+naL7rSob602lVS*I*VKIwohmJ|4v2GC5EJr;lY!xh&!KJq(fX;HkO@xK=~xS&aM7F{a; zaF*iYyYl>5x=PM}hbDyOmo1kqct_ ZP$CLa;u=j0W1N`3Hm>(lpm?pO{Tur^{}KQI delta 5861 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 VUS=2(e9y`KE^Z#AP~i)P@o&h+|7!pM 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..fad03b6edd 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,38 @@ 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 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, 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/retired-opp-carriers.md b/docs/retired-opp-carriers.md new file mode 100644 index 0000000000..29d8263e29 --- /dev/null +++ b/docs/retired-opp-carriers.md @@ -0,0 +1,88 @@ +# 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 | +| 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`. Solana also uses operator actions to mirror slashed status. + 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. +- Challenge/dispute types remain separate from the deleted underwriter race. + `sysio.chalg` owns live depot disputes; this change does not retire that work. +- 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..866fab51f2 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. */ @@ -68,13 +68,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 +75,11 @@ 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 +102,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) @@ -162,25 +134,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) @@ -198,14 +154,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..219dce2dfe 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -3,72 +3,9 @@ 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 // @@ -101,10 +38,9 @@ message OperatorAction { // 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 +52,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,106 +72,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 // --------------------------------------------------------------------------- @@ -352,81 +182,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..e544e6a7c7 100644 --- a/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto +++ b/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto @@ -10,17 +10,17 @@ 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` +// 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, 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/types/types.proto b/libraries/opp/proto/sysio/opp/types/types.proto index c181e2f4fc..b5f3cae682 100644 --- a/libraries/opp/proto/sysio/opp/types/types.proto +++ b/libraries/opp/proto/sysio/opp/types/types.proto @@ -85,14 +85,6 @@ message WirePermission { 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 // @@ -130,21 +122,10 @@ message TokenAmount { } // --------------------------------------------------------------------------- -// 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) +// Registry entities (Chain / Token / ChainToken) // // These are the proto carriers for the depot's registry tables on the new -// contracts `sysio.chains` and `sysio.tokens` (+ existing `sysio.reserv`). +// contracts `sysio.chains` and `sysio.tokens`. // 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. // --------------------------------------------------------------------------- @@ -187,42 +168,6 @@ message ChainToken { 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 +175,16 @@ 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 +200,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 +213,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; @@ -368,21 +266,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; 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..4c64990c7b --- /dev/null +++ b/libraries/opp/test/test_retired_carriers.cpp @@ -0,0 +1,151 @@ +/** + * @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, 60934, + 60944, 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"}; +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"}) { + 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"}) { + 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 From 4291581446fb58c8284698248eb5be3fb9e5aab5 Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Sat, 10 Oct 2026 15:57:40 -0400 Subject: [PATCH 2/2] Remove unused OPP schema and challenge carriers An audit of every surface left in the OPP protobufs found several with no producer: CHALLENGE_REQUEST and CHALLENGE_RESPONSE with their ChallengeRequest / ChallengeOperatorHash payloads (msgch dropped both; sysio.chalg disputes run on DisputeStatus through evalcons), and declarations nothing encodes: ChainKeyType, ChainSignature, WirePermission, ChainRequestStatus, ChallengeStatus, DebugEnvelopeDataRecord, and the Chain / Token / ChainToken messages, which were described as registry carriers although sysio.chains and sysio.tokens store their own row structs. Remove them with their reflections, serializers and depot case, and rebuild the msgch ABI (the two enum values; every wasm is unchanged). Extend the retired-carrier regressions to the new names and wire values. Correct comments that said sysio.opreg emits OPERATOR_ACTION: it records withdraw and slash entries in the operator audit log and queues no attestation; outposts learn a slashed status from the OPERATORS roster. Point the bootstrap-config docs at the registry tables. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ARSeK35wwkiAy4et9mCkVm Change-Id: I4435e434bd867b3b4b936df7a0f17328fa47b8c3 --- OVERVIEW.md | 1 - contracts/sysio.epoch/src/sysio.epoch.cpp | 8 +- .../include/sysio.msgch/sysio.msgch.hpp | 1 - contracts/sysio.msgch/src/sysio.msgch.cpp | 6 -- contracts/sysio.msgch/sysio.msgch.abi | 8 -- .../sysio.opp.common/opp_table_types.hpp | 76 --------------- contracts/sysio.opreg/src/sysio.opreg.cpp | 10 +- contracts/tests/sysio.dispatch_tests.cpp | 6 +- docs/platform-bootstrap-config.md | 9 +- docs/retired-opp-carriers.md | 9 +- libraries/opp/include/sysio/opp/opp.hpp | 23 ----- .../sysio/opp/attestations/attestations.proto | 27 +----- .../proto/sysio/opp/bootstrap/bootstrap.proto | 7 +- .../proto/sysio/opp/debugging/debugging.proto | 10 -- .../opp/proto/sysio/opp/types/types.proto | 93 +------------------ libraries/opp/test/test_retired_carriers.cpp | 26 ++++-- 16 files changed, 55 insertions(+), 265 deletions(-) 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.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.msgch/include/sysio.msgch/sysio.msgch.hpp b/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp index b00d8cfcba..4e6253925b 100644 --- a/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp +++ b/contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp @@ -341,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 094164d3ba..29151e45f1 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -675,12 +675,6 @@ 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_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; - // 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_OPERATORS: diff --git a/contracts/sysio.msgch/sysio.msgch.abi b/contracts/sysio.msgch/sysio.msgch.abi index 1ff7a70e72..91c63abca7 100644 --- a/contracts/sysio.msgch/sysio.msgch.abi +++ b/contracts/sysio.msgch/sysio.msgch.abi @@ -577,14 +577,6 @@ "name": "ATTESTATION_TYPE_OPERATOR_ACTION", "value": 2001 }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_RESPONSE", - "value": 60932 - }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_REQUEST", - "value": 60945 - }, { "name": "ATTESTATION_TYPE_OPERATORS", "value": 60947 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 f73482d06e..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,49 +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; -} - -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; -} - } // namespace sysio::opp::types // ───────────────────────────────────────────────────────────────────────────── @@ -278,29 +225,6 @@ DataStream& operator>>(DataStream& ds, OperatorActionLog& t) { return ds >> t.action >> t.success >> t.timestamp >> t.error_message; } -// 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) { diff --git a/contracts/sysio.opreg/src/sysio.opreg.cpp b/contracts/sysio.opreg/src/sysio.opreg.cpp index 001decdb40..a829b18495 100644 --- a/contracts/sysio.opreg/src/sysio.opreg.cpp +++ b/contracts/sysio.opreg/src/sysio.opreg.cpp @@ -741,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, @@ -1287,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/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index fad03b6edd..d19863d9d0 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -1631,6 +1631,8 @@ BOOST_FIXTURE_TEST_CASE(retired_attestations_do_not_mutate_collateral_or_block_l 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, {}, {}, @@ -1644,7 +1646,9 @@ BOOST_FIXTURE_TEST_CASE(retired_attestations_do_not_mutate_collateral_or_block_l 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, undeclared_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/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 index 29d8263e29..f7cb4121b4 100644 --- a/docs/retired-opp-carriers.md +++ b/docs/retired-opp-carriers.md @@ -24,6 +24,8 @@ Tools, Libraries, and Hub; generated build output was checked separately. | 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 | @@ -33,15 +35,16 @@ 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`. Solana also uses operator actions to mirror slashed status. + `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. -- Challenge/dispute types remain separate from the deleted underwriter race. - `sysio.chalg` owns live depot disputes; this change does not retire that work. +- `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, diff --git a/libraries/opp/include/sysio/opp/opp.hpp b/libraries/opp/include/sysio/opp/opp.hpp index 866fab51f2..0200a20b64 100644 --- a/libraries/opp/include/sysio/opp/opp.hpp +++ b/libraries/opp/include/sysio/opp/opp.hpp @@ -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 // --------------------------------------------------------------------------- @@ -75,8 +67,6 @@ FC_REFLECT_ENUM(sysio::opp::types::TokenKind, FC_REFLECT_ENUM(sysio::opp::types::AttestationType, (ATTESTATION_TYPE_UNSPECIFIED) (ATTESTATION_TYPE_OPERATOR_ACTION) - (ATTESTATION_TYPE_CHALLENGE_RESPONSE) - (ATTESTATION_TYPE_CHALLENGE_REQUEST) (ATTESTATION_TYPE_OPERATORS) (ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS) (ATTESTATION_TYPE_NODE_OWNER_REG) @@ -111,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)) @@ -137,12 +120,6 @@ FC_REFLECT_ENUM(sysio::opp::types::EnvelopeStatus, // Challenge and dispute enums // --------------------------------------------------------------------------- -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) diff --git a/libraries/opp/proto/sysio/opp/attestations/attestations.proto b/libraries/opp/proto/sysio/opp/attestations/attestations.proto index 219dce2dfe..9440607ef8 100644 --- a/libraries/opp/proto/sysio/opp/attestations/attestations.proto +++ b/libraries/opp/proto/sysio/opp/attestations/attestations.proto @@ -9,9 +9,9 @@ option cc_enable_arenas = true; // --------------------------------------------------------------------------- // 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 { @@ -30,10 +30,7 @@ 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. @@ -72,22 +69,6 @@ message NodeOwnerRegistration { sysio.opp.types.WireKey wire_pub_key = 9; } -// --------------------------------------------------------------------------- -// 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 // --------------------------------------------------------------------------- diff --git a/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto b/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto index e544e6a7c7..661aa74315 100644 --- a/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto +++ b/libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto @@ -15,10 +15,9 @@ option cc_enable_arenas = true; // 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`), -// 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"); the tool packs them // via slug_name ([A-Z][A-Z0-9_]{0,7} -- must start with a letter, <= 8 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 b5f3cae682..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,27 +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" -} - // --------------------------------------------------------------------------- // 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 { @@ -121,53 +100,6 @@ message TokenAmount { int64 amount = 2; } -// --------------------------------------------------------------------------- -// Registry entities (Chain / Token / ChainToken) -// -// These are the proto carriers for the depot's registry tables on the new -// contracts `sysio.chains` and `sysio.tokens`. -// 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; -} - // --------------------------------------------------------------------------- // Attestation discriminant // --------------------------------------------------------------------------- @@ -176,9 +108,7 @@ enum AttestationType { ATTESTATION_TYPE_UNSPECIFIED = 0; ATTESTATION_TYPE_OPERATOR_ACTION = 2001; // 0x07D1 // 60929 (0xEE01) was ATTESTATION_TYPE_NATIVE_YIELD_REWARD — removed; do not reuse. - ATTESTATION_TYPE_CHALLENGE_RESPONSE = 60932; // 60933 was standalone SLASH_OPERATOR — removed; SLASH is now an OperatorAction sub-type. - 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. @@ -234,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; @@ -266,13 +188,6 @@ enum AttestationStatus { ATTESTATION_STATUS_PROCESSED = 2; } -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_retired_carriers.cpp b/libraries/opp/test/test_retired_carriers.cpp index 4c64990c7b..53b5f55107 100644 --- a/libraries/opp/test/test_retired_carriers.cpp +++ b/libraries/opp/test/test_retired_carriers.cpp @@ -15,9 +15,10 @@ namespace { /// Removed lifecycle discriminants, plus an undeclared control. -constexpr std::array removed_wire_types{3001, 3002, 3004, 3006, 43520, 60928, 60930, 60934, - 60944, 60950, 60951, 60952, 60953, 60955, 60956, - 60958, 60959, 60960, 60961, 60962, 65000}; +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; @@ -45,7 +46,9 @@ constexpr std::array removed_attestation_type_names{ "ATTESTATION_TYPE_RESERVE_CREATE_CANCEL", "ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED", "ATTESTATION_TYPE_RESERVE_READY", - "ATTESTATION_TYPE_EMISSIONS_BLOCKED"}; + "ATTESTATION_TYPE_EMISSIONS_BLOCKED", + "ATTESTATION_TYPE_CHALLENGE_RESPONSE", + "ATTESTATION_TYPE_CHALLENGE_REQUEST"}; constexpr auto attestation_type_enum = "sysio.opp.types.AttestationType"; } // namespace @@ -80,13 +83,24 @@ BOOST_AUTO_TEST_CASE(generated_api_omits_retired_lifecycle_messages) { "sysio.opp.attestations.PretokenStakeChange", "sysio.opp.attestations.StakeUpdate", "sysio.opp.attestations.StakeResult", - "sysio.opp.attestations.AttestationProcessingError"}) { + "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.StakeStatus", + "sysio.opp.types.ChainKeyType", + "sysio.opp.types.ChainRequestStatus", + "sysio.opp.types.ChallengeStatus"}) { BOOST_CHECK_MESSAGE(pool->FindEnumTypeByName(name) == nullptr, name); } }