From 582e3e51353402e7fa1096abf579e4aea98d814d Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Sun, 4 Oct 2026 16:07:02 -0400 Subject: [PATCH 1/5] Simplify syndication custody and settlement Retain acknowledged underwriting outcomes and synchronize releases and issuance independently through bounded cursors and shared accessors. Keep operational obligations and replay protection instead of cumulative reporting history, with explicit governance recovery for pending returns. Credit LIQ directly through restricted custody settlement. Bank backed WIRE claims separately so recipient callbacks cannot block LIQ release, and retain unbacked rounding residuals until they can be settled. Include rebuilt contracts, regression tests, and integration guidance. Existing deployments require a coordinated table migration. Validation: all 41 contract suites passed (1,100 cases). Underwriting and emergency live flows passed before the final constants cleanup. The rate-limit live run stalled on Solana compute exhaustion; external wallet completion for that run remains unverified. Change-Id: Ic7e3a819ecdc5acb362bdc33a49e39fe7f11a710 --- .../include/sysio.bond/sysio.bond.hpp | 93 ++++- contracts/sysio.bond/src/sysio.bond.cpp | 109 +++-- contracts/sysio.bond/sysio.bond.abi | 105 +++++ contracts/sysio.bond/sysio.bond.wasm | Bin 42102 -> 46200 bytes contracts/sysio.liq/README.md | 7 + .../sysio.liq/include/sysio.liq/sysio.liq.hpp | 12 +- contracts/sysio.liq/src/sysio.liq.cpp | 38 +- contracts/sysio.liq/sysio.liq.abi | 23 ++ contracts/sysio.liq/sysio.liq.wasm | Bin 42408 -> 43308 bytes .../include/sysio.synd/sysio.synd.hpp | 195 +++++---- contracts/sysio.synd/src/sysio.synd.cpp | 375 ++++++++++-------- contracts/sysio.synd/sysio.synd.abi | 240 ++++++++--- contracts/sysio.synd/sysio.synd.wasm | Bin 98404 -> 105651 bytes .../block_transfer/block_transfer.cpp | 15 +- .../block_transfer/block_transfer.wasm | Bin 2792 -> 3172 bytes contracts/tests/sysio.bond_tests.cpp | 148 +++++-- contracts/tests/sysio.liq_tests.cpp | 34 ++ contracts/tests/sysio.synd_tests.cpp | 345 +++++++++++----- docs/sysio-bond-integration.md | 58 ++- docs/sysio-synd.md | 159 ++++---- 20 files changed, 1368 insertions(+), 588 deletions(-) diff --git a/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp b/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp index 294543a20b..9020bbedff 100644 --- a/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp +++ b/contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp @@ -34,6 +34,9 @@ #include #include #include +#include +#include +#include #include #include @@ -46,6 +49,17 @@ namespace sysio { +namespace bond_validation { +inline constexpr std::string_view bond_increment_msg = "amount must be a positive multiple of the bond increment"; +inline constexpr std::string_view precision_msg = "token precision is below the bond increment"; +inline constexpr std::string_view unsupported_token_msg = "unsupported bond token"; +inline constexpr std::string_view statement_len_msg = "statement exceeds the maximum length"; +inline constexpr std::string_view duplicate_msg = "a request for this statement already exists"; +inline constexpr std::string_view window_msg = "window must be positive"; +inline constexpr std::string_view amount_range_msg = "amount exceeds the asset range"; +} // namespace bond_validation + + class [[sysio::contract("sysio.bond")]] bond : public contract { public: using contract::contract; @@ -65,8 +79,7 @@ namespace sysio { static constexpr uint32_t MAX_STATEMENT_BYTES = 1024; /// Hold bond, in basis points of the covered amount, until `setconfig` sets another. static constexpr uint32_t DEFAULT_HOLD_BPS = 1000; - /// Seconds a terminal request stays after its ruling before `prune` may erase it: seven days, so - /// an issuer that polls the request row, rather than being notified, always sees the ruling. + /// Minimum retention for ordinary requests. Durable requests additionally require issuer acknowledgement. static constexpr uint32_t PRUNE_RETENTION_SEC = 7 * 24 * 60 * 60; /// One whole, in basis points. static constexpr uint32_t BPS_DENOMINATOR = 10000; @@ -146,6 +159,15 @@ namespace sysio { sysio::slug_name token_code, uint64_t covered, uint64_t bounty, uint32_t window_sec); + /// Issue a request with durable terminal delivery. Identical to `request`, except pruning also + /// requires the issuer's `ack`. Rulings and claims remain independent of acknowledgement. + [[sysio::action]] void requestkeep(name issuer, name schema, std::vector statement, + sysio::slug_name token_code, uint64_t covered, uint64_t bounty, + uint32_t window_sec); + + /// Acknowledge a terminal outcome after durably consuming it. Auth=issuer; idempotent. + [[sysio::action]] void ack(uint64_t request_id); + /// Raise the bounty of request `request_id`, not yet terminal, by `amount`, pulled from the /// issuer. Auth=issuer. [[sysio::action]] void addbounty(uint64_t request_id, uint64_t amount); @@ -186,18 +208,34 @@ namespace sysio { /// Pay `account` what terminal request `request_id` owes it, in one call: /// - to the issuer of an INVALID request, the forfeit: the whole bonded amount in ONE - /// `transfer` with memo `sysio.bond::forfeit`; + /// LIQ settlement (a `transfer` with memo `sysio.bond::forfeit` for WIRE); /// - for its bond on an APPROVED or VALID request, the bond plus its stake's share of the /// bounty and the hold bond, and the WIRE the bond earned; for its bond on an INVALID /// request, nothing -- the WIRE the forfeited bond earned is paid into `sysio.liq` as bonus /// yield of the token, or kept by the contract while the token has no supply; /// - what a ruling awarded it out of an escrow (`escrow_row::payout`); /// - for an escrow it funded, the WIRE the escrow earned. - /// Everything in the request's token but the forfeit goes in one transfer, the WIRE in another. + /// LIQ uses callback-free settlement; earned WIRE is recorded for separate `claimwire`. + /// WIRE-denominated requests retain ordinary transfers. Residual unpaid yield prevents pruning. /// Refused when nothing is owed, and while the `sysio.andon` cord is pulled (what is owed stays /// recorded until the cord clears). Permissionless. [[sysio::action]] void claim(uint64_t request_id, name account); + /// Withdraw the backed WIRE yield balance recorded by LIQ claims. Permissionless, fixed payee; + /// a rejecting recipient leaves this balance intact without blocking LIQ settlement. + [[sysio::action]] void claimwire(name account); + + struct wire_key { + name account; + SYSLIB_SERIALIZE(wire_key, (account)) + }; + struct [[sysio::table("wireclaims")]] wire_claim { + name account; + uint64_t amount = 0; + SYSLIB_SERIALIZE(wire_claim, (account)(amount)) + }; + using wireclaims_t = kv::table<"wireclaims"_n, wire_key, wire_claim>; + /// Walk the terminal requests from id `from_id` up and act on at most `limit` of them. A request /// whose rows are all paid or owe nothing, and that was ruled at least PRUNE_RETENTION_SEC ago, is /// erased with its bond and escrow rows. Any other terminal request keeps its row, but the bond rows @@ -256,12 +294,14 @@ namespace sysio { /// other state. uint64_t forfeit_pending; + bool outcome_acknowledged = true; ///< false for durable requests until the issuer acknowledges + /// The key of the `bystatement` index, which admits one request per issuer and statement. checksum256 by_statement() const { return statement_key_of(issuer, statement_digest); } SYSLIB_SERIALIZE(request_row, (id)(issuer)(schema)(statement)(statement_digest)(token_code)(covered)(bonded) (bounty)(window_sec)(state)(created_at)(bonded_at)(hold_bond)(hold_beneficiary)(held_at) - (resolved_at)(resolved_index)(forfeit_pending)) + (resolved_at)(resolved_index)(forfeit_pending)(outcome_acknowledged)) }; /// Secondary index of `requests` on `request_row::by_statement` -- the one declaration of its name. @@ -271,6 +311,34 @@ namespace sysio { using requests_t = kv::table<"requests"_n, request_key, request_row, kv::index>>; + /// Resolve the stable issuer-and-statement identity without exposing index layout to clients. + static std::optional find_request(name code, name issuer, name schema, + const std::vector& statement) { + requests_t requests(code); + const auto index = requests.get_index(); + const auto it = index.find(statement_key_of(issuer, statement_digest_of(schema, statement))); + return it == index.end() ? std::nullopt : std::optional{*it}; + } + + /// Shared admission checks. Empty means admissible; the refusal is safe for intake to report + /// without throwing. Transfer authority and available bounty remain the caller's responsibility. + static std::string_view request_refusal(name code, name issuer, name schema, + const std::vector& statement, sysio::slug_name token_code, + uint64_t covered, uint64_t bounty, uint32_t window_sec) { + if (statement.size() > MAX_STATEMENT_BYTES) return bond_validation::statement_len_msg; + const auto token = opp::custody::resolve_depot_native_token(LIQ_ACCOUNT, TOKEN_ACCOUNT, token_code); + if (!token) return bond_validation::unsupported_token_msg; + const auto increment = increment_of(token->sym); + if (!increment) return bond_validation::precision_msg; + if (covered == 0 || covered % *increment != 0) + return bond_validation::bond_increment_msg; + if (covered > static_cast(asset::max_amount) || bounty > static_cast(asset::max_amount)) + return bond_validation::amount_range_msg; + if (window_sec == 0) return bond_validation::window_msg; + if (find_request(code, issuer, schema, statement)) return bond_validation::duplicate_msg; + return {}; + } + /// Key of `bonds`. struct bond_key { uint64_t request_id; ///< the request bonded @@ -332,6 +400,21 @@ namespace sysio { return escrow_key{.request_id = request_id, .kind = magic_enum::enum_integer(kind)}; } + /// Principal still awarded to an account by settlement, excluding its underwriting stake and + /// escrow yield. Clients need not interpret escrow rows or their paid flags. + static uint64_t settlement_due(name code, uint64_t request_id, name account) { + requests_t requests(code); + const auto req = requests.try_get(request_key{request_id}); + if (!req) return 0; + uint64_t due = req->issuer == account ? req->forfeit_pending : 0; + escrows_t escrows(code); + for (const auto kind : magic_enum::enum_values()) { + const auto escrow = escrows.try_get(escrow_key_of(request_id, kind)); + if (escrow && escrow->payee == account) due += escrow->payout; + } + return due; + } + /// Key of `yieldpool`. struct pool_key { uint64_t token_code; ///< the shadow token's registry code value diff --git a/contracts/sysio.bond/src/sysio.bond.cpp b/contracts/sysio.bond/src/sysio.bond.cpp index 6c7d02902a..dd5e992142 100644 --- a/contracts/sysio.bond/src/sysio.bond.cpp +++ b/contracts/sysio.bond/src/sysio.bond.cpp @@ -24,6 +24,10 @@ namespace sysio { namespace { +constexpr name settle_action = "settle"_n; +constexpr auto request_is_not_resolved_msg = "request is not resolved"; + + using opp::custody::depot_native_token; using opp::custody::pull_depot_native; using opp::custody::transfer_depot_native; @@ -65,22 +69,22 @@ constexpr std::string_view claim_memo = "sysio.bond::claim"; constexpr std::string_view claim_yield_memo = "sysio.bond::claim yield"; /// Message of the `check` a covered or bonded amount off the token's increment raises. -constexpr std::string_view bond_increment_msg = "amount must be a positive multiple of the bond increment"; +using bond_validation::bond_increment_msg; /// Message of the `check` a token with fewer than BOND_INCREMENT_DECIMALS decimals raises. -constexpr std::string_view precision_msg = "token precision is below the bond increment"; +using bond_validation::precision_msg; /// Message of the `check` a token code that is neither WIRE nor a shadow LIQ symbol raises. -constexpr std::string_view unsupported_token_msg = "unsupported bond token"; +using bond_validation::unsupported_token_msg; /// Message of the `check` a statement longer than MAX_STATEMENT_BYTES raises. -constexpr std::string_view statement_len_msg = "statement exceeds the maximum length"; +using bond_validation::statement_len_msg; /// Message of the `check` a second request for the same issuer and statement raises. -constexpr std::string_view duplicate_msg = "a request for this statement already exists"; +using bond_validation::duplicate_msg; /// Message of the `check` a zero challenge window raises. -constexpr std::string_view window_msg = "window must be positive"; +using bond_validation::window_msg; /// Message of the `check` `setconfig` raises for a hold percentage of zero or above one whole. constexpr std::string_view hold_bps_range_msg = "hold_bps must be positive and at most the basis-point denominator"; @@ -89,7 +93,7 @@ constexpr std::string_view hold_bps_range_msg = "hold_bps must be positive and a constexpr std::string_view amount_positive_msg = "amount must be positive"; /// Message of the `check` an amount no `asset` can carry raises. -constexpr std::string_view amount_range_msg = "amount exceeds the asset range"; +using bond_validation::amount_range_msg; /// Message of the `check` an action naming an unknown request raises. constexpr std::string_view request_missing_msg = "request not found"; @@ -276,6 +280,7 @@ bool fully_paid(name self, const bond::request_row& req, u128 live_index) { bond::bonds_t bonds(self); for (auto it = bonds.lower_bound(bond::bond_key{.request_id = req.id, .underwriter = name{}}); it != bonds.end() && it->request_id == req.id; ++it) { + if (it->yield.owed_wire > 0) return false; if (it->paid) continue; if (returns_bonds || owed_at(it->yield, it->amount, req.token_code, req.resolved_index) > 0) return false; } @@ -283,7 +288,7 @@ bool fully_paid(name self, const bond::request_row& req, u128 live_index) { for (const auto kind : magic_enum::enum_values()) { const auto row = escrows.try_get(bond::escrow_key_of(req.id, kind)); if (!row) continue; - if (row->payout > 0) return false; + if (row->payout > 0 || row->yield.owed_wire > 0) return false; if (!row->paid && owed_at(row->yield, row->amount, req.token_code, live_index) > 0) return false; } return true; @@ -326,15 +331,9 @@ void bond::setconfig(uint32_t hold_bps) { void bond::request(name issuer, name schema, std::vector statement, sysio::slug_name token_code, uint64_t covered, uint64_t bounty, uint32_t window_sec) { require_auth(issuer); - check(statement.size() <= MAX_STATEMENT_BYTES, statement_len_msg); - + const auto refusal = request_refusal(get_self(), issuer, schema, statement, token_code, covered, bounty, window_sec); + check(refusal.empty(), refusal); const auto custody = resolve_bond_token(token_code); - check(custody.has_value(), unsupported_token_msg); - const auto increment = increment_of(custody->sym); - check(increment.has_value(), precision_msg); - check(is_increment_multiple(covered, *increment), bond_increment_msg); - check(covered <= max_asset_amount && bounty <= max_asset_amount, amount_range_msg); - check(window_sec > 0, window_msg); request_row row{ .id = 0, @@ -376,6 +375,24 @@ void bond::request(name issuer, name schema, std::vector statement, sysio: } } +void bond::requestkeep(name issuer, name schema, std::vector statement, sysio::slug_name token_code, + uint64_t covered, uint64_t bounty, uint32_t window_sec) { + request(issuer, schema, statement, token_code, covered, bounty, window_sec); + const auto row = find_request(get_self(), issuer, schema, statement); + requests_t(get_self()).modify(same_payer, request_key{row->id}, [](request_row& r) { + r.outcome_acknowledged = false; + }); +} + +void bond::ack(uint64_t request_id) { + requests_t requests(get_self()); + const auto row = requests.try_get(request_key{request_id}); + check(row.has_value(), request_missing_msg); + require_auth(row->issuer); + check(is_terminal(row->state), request_is_not_resolved_msg); + requests.modify(same_payer, request_key{request_id}, [](request_row& r) { r.outcome_acknowledged = true; }); +} + void bond::addbounty(uint64_t request_id, uint64_t amount) { requests_t requests(get_self()); const auto req = requests.try_get(request_key{request_id}); @@ -556,10 +573,9 @@ void bond::sweepyield(sysio::slug_name token_code) { // claim - pay `account` what terminal request `request_id` owes it. // -// Every payout of a request is pulled here, one account per call, so a party that refuses transfers -// blocks only its own claim. The issuer of an INVALID request takes the forfeit in ONE transfer -// under `forfeit_memo`; everything else in the request's token goes in one transfer under -// `claim_memo`, and the WIRE yield in another. +// Each account settles independently. LIQ credits invoke no recipient code. Earned WIRE is +// recorded into a backed claim balance and withdrawn separately; WIRE-denominated requests keep +// ordinary transfer claims. // // A returned bond and an escrow are still held here, so they settle at sysio.liq's LIVE index: what // they earn up to the claim is their owner's. A forfeited bond settles at the request's @@ -567,7 +583,7 @@ void bond::sweepyield(sysio::slug_name token_code) { // sysio.liq (the custody library's obligation 3), so what the rows take is covered by WIRE that // arrives ahead of the payouts queued after it. The rows are marked paid before any transfer is // queued, so a transfer-notify handler that re-enters `claim` finds nothing left. What the pool -// cannot cover of a row's yield is at most rounding dust and stays in the contract. +// cannot cover stays owed on the original row and prevents pruning until paid. // // An escrow keeps earning while it holds shares or a payout not yet claimed, so its funder may claim // more than once; the funder's claim marks it paid once no bond is owed a share of it and its payout @@ -604,16 +620,20 @@ void bond::claim(uint64_t request_id, name account) { bonds_t bonds(get_self()); const bond_key bkey{.request_id = request_id, .underwriter = account}; - if (auto row = bonds.try_get(bkey); row && !row->paid) { + if (auto row = bonds.try_get(bkey); row && (!row->paid || row->yield.owed_wire > 0)) { uint64_t earned = 0; bool earns = false; if (shadow) { - custody::settle(row->yield, row->amount, forfeit ? req->resolved_index : live); + if (!row->paid) custody::settle(row->yield, row->amount, forfeit ? req->resolved_index : live); earns = row->yield.owed_wire > 0; earned = custody::take(row->yield, pool.pool); } bool paid = false; - if (!forfeit) { + if (row->paid) { + if (forfeit) forfeit_wire = earned; + else wire_out = earned; + paid = earned > 0; + } else if (!forfeit) { const uint64_t bounty_share = bond::share_of(row->amount, req->bounty, req->covered); const uint64_t hold_bond_share = bond::share_of(row->amount, req->hold_bond, req->covered); add_to_transfer(token_out, row->amount); @@ -649,10 +669,10 @@ void bond::claim(uint64_t request_id, name account) { row->payout = 0; changed = true; } - if (shadow && !row->paid && row->funder == account) { - custody::settle(row->yield, row->amount, live); + if (shadow && (!row->paid || row->yield.owed_wire > 0) && row->funder == account) { + if (!row->paid) custody::settle(row->yield, row->amount, live); if (row->yield.owed_wire > 0) { - wire_out = opp::safe::add_sat_u64(wire_out, custody::take(row->yield, pool.pool)); + add_to_transfer(wire_out, custody::take(row->yield, pool.pool)); row->paid = row->payout == 0 && shares_settled(get_self(), *req); changed = true; } @@ -665,9 +685,24 @@ void bond::claim(uint64_t request_id, name account) { check(found, nothing_msg); if (shadow) yieldpools_t(get_self()).set(ram_payer, pool_key{req->token_code.value}, pool); - if (forfeit_out > 0) transfer_depot_native(get_self(), account, *custody_token, forfeit_out, forfeit_memo); - if (token_out > 0) transfer_depot_native(get_self(), account, *custody_token, token_out, claim_memo); - if (wire_out > 0) transfer_depot_native(get_self(), account, wire_custody(), wire_out, claim_yield_memo); + const auto pay = [&](uint64_t amount, std::string_view memo) { + if (amount == 0) return; + if (shadow) { + action(permission_level{get_self(), active_permission}, LIQ_ACCOUNT, settle_action, + std::make_tuple(get_self(), account, asset{static_cast(amount), custody_token->sym})).send(); + } else { + transfer_depot_native(get_self(), account, *custody_token, amount, memo); + } + }; + pay(forfeit_out, forfeit_memo); + pay(token_out, claim_memo); + if (wire_out > 0) { + wireclaims_t claims(get_self()); + const wire_key key{account}; + auto balance = claims.try_get(key).value_or(wire_claim{account, 0}); + add_to_transfer(balance.amount, wire_out); + claims.set(ram_payer, key, balance); + } // sysio.liq refuses a distribution to a symbol with no supply: the WIRE then stays in the contract. if (forfeit_wire > 0 && has_supply(req->token_code)) { action(permission_level{get_self(), active_permission}, LIQ_ACCOUNT, opp::shadow::ADDYIELD_ACTION, @@ -676,6 +711,16 @@ void bond::claim(uint64_t request_id, name account) { } } +void bond::claimwire(name account) { + andon::check_clear(andon::ANDON_ACCOUNT); + wireclaims_t claims(get_self()); + const wire_key key{account}; + const auto balance = claims.try_get(key); + check(balance && balance->amount > 0, nothing_msg); + claims.erase(key); + transfer_depot_native(get_self(), account, wire_custody(), balance->amount, claim_yield_memo); +} + // prune - erase terminal requests nothing is owed on, with their rows, and paid bond rows. // // Walks the requests in id order from `from_id` and acts on up to `limit` of them. A request still owed @@ -697,7 +742,7 @@ void bond::prune(uint64_t from_id, uint32_t limit) { const auto custody_token = resolve_bond_token(it->token_code); if (!custody_token) continue; const bool retained = now < it->resolved_at + seconds(PRUNE_RETENTION_SEC); - if (!retained && fully_paid(get_self(), *it, live_index_of(it->token_code, *custody_token))) { + if (!retained && it->outcome_acknowledged && fully_paid(get_self(), *it, live_index_of(it->token_code, *custody_token))) { prunable.push_back(it->id); ++acted; continue; @@ -705,7 +750,7 @@ void bond::prune(uint64_t from_id, uint32_t limit) { const size_t before = paid_bonds.size(); for (auto bit = bonds.lower_bound(bond_key{.request_id = it->id, .underwriter = name{}}); bit != bonds.end() && bit->request_id == it->id; ++bit) { - if (bit->paid) paid_bonds.push_back(bond_key{.request_id = it->id, .underwriter = bit->underwriter}); + if (bit->paid && bit->yield.owed_wire == 0) paid_bonds.push_back(bond_key{.request_id = it->id, .underwriter = bit->underwriter}); } if (paid_bonds.size() != before) ++acted; } diff --git a/contracts/sysio.bond/sysio.bond.abi b/contracts/sysio.bond/sysio.bond.abi index 4122be7978..e221e9a621 100644 --- a/contracts/sysio.bond/sysio.bond.abi +++ b/contracts/sysio.bond/sysio.bond.abi @@ -21,6 +21,16 @@ } ] }, + { + "name": "ack", + "base": "", + "fields": [ + { + "name": "request_id", + "type": "uint64" + } + ] + }, { "name": "addbounty", "base": "", @@ -119,6 +129,16 @@ } ] }, + { + "name": "claimwire", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + } + ] + }, { "name": "escrow_key", "base": "", @@ -360,6 +380,44 @@ { "name": "forfeit_pending", "type": "uint64" + }, + { + "name": "outcome_acknowledged", + "type": "bool" + } + ] + }, + { + "name": "requestkeep", + "base": "", + "fields": [ + { + "name": "issuer", + "type": "name" + }, + { + "name": "schema", + "type": "name" + }, + { + "name": "statement", + "type": "bytes" + }, + { + "name": "token_code", + "type": "slug_name" + }, + { + "name": "covered", + "type": "uint64" + }, + { + "name": "bounty", + "type": "uint64" + }, + { + "name": "window_sec", + "type": "uint32" } ] }, @@ -403,6 +461,30 @@ } ] }, + { + "name": "wire_claim", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "amount", + "type": "uint64" + } + ] + }, + { + "name": "wire_key", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + } + ] + }, { "name": "yield_pool", "base": "", @@ -432,6 +514,11 @@ "type": "accept", "ricardian_contract": "" }, + { + "name": "ack", + "type": "ack", + "ricardian_contract": "" + }, { "name": "addbounty", "type": "addbounty", @@ -447,6 +534,11 @@ "type": "claim", "ricardian_contract": "" }, + { + "name": "claimwire", + "type": "claimwire", + "ricardian_contract": "" + }, { "name": "hold", "type": "hold", @@ -462,6 +554,11 @@ "type": "request", "ricardian_contract": "" }, + { + "name": "requestkeep", + "type": "requestkeep", + "ricardian_contract": "" + }, { "name": "rslvinvalid", "type": "rslvinvalid", @@ -531,6 +628,14 @@ } ] }, + { + "name": "wireclaims", + "type": "wire_claim", + "index_type": "i64", + "key_names": ["account"], + "key_types": ["name"], + "table_id": 22082 + }, { "name": "yieldpool", "type": "pool_row", diff --git a/contracts/sysio.bond/sysio.bond.wasm b/contracts/sysio.bond/sysio.bond.wasm index e0c83b8025e53692558e31ea6ea954f94e3241e4..cc4391e4b870c728fe45fad8b3530ef2e7da5e5c 100755 GIT binary patch delta 14943 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 0, "quantity must be positive"); const currency_stats st = stat_of(quantity.symbol.code()); - check(quantity.symbol == st.supply.symbol, "symbol precision mismatch"); + check(quantity.symbol == st.supply.symbol, symbol_precision_mismatch_msg); check(grow_supply(quantity.symbol.code(), static_cast(quantity.amount)), "supply exceeds the asset range"); adjust_account(holder, quantity, ram_payer); } @@ -228,20 +237,39 @@ void liq::transfer(name from, name to, asset quantity, string memo) { require_recipient(from); require_recipient(to); - check(quantity.is_valid(), "invalid quantity"); + check(quantity.is_valid(), invalid_quantity_msg); check(quantity.amount > 0, "must transfer positive quantity"); - check(quantity.symbol == st.supply.symbol, "symbol precision mismatch"); + check(quantity.symbol == st.supply.symbol, symbol_precision_mismatch_msg); check(memo.size() <= MAX_MEMO_BYTES, "memo has more than 256 bytes"); adjust_account(from, -quantity, ram_payer); adjust_account(to, quantity, ram_payer); } +void liq::settle(name custodian, name beneficiary, asset quantity) { + check(custodian == SYND_ACCOUNT || custodian == bond_account, unsupported_custodian_msg); + require_auth(custodian); + andon::check_clear(andon::ANDON_ACCOUNT); + check(is_account(beneficiary), beneficiary_account_does_not_exist_msg); + check(quantity.is_valid(), invalid_quantity_msg); + check(quantity.amount > 0, must_settle_positive_quantity_msg); + const currency_stats st = stat_of(quantity.symbol.code()); + check(quantity.symbol == st.supply.symbol, symbol_precision_mismatch_msg); + if (custodian == beneficiary) { + const auto row = accounts(get_self(), custodian.value).try_get(symbol_key{quantity.symbol.code().raw()}); + check(row && row->balance.amount >= quantity.amount, overdrawn_balance_msg); + adjust_account(custodian, asset{0, quantity.symbol}, ram_payer); + } else { + adjust_account(custodian, -quantity, ram_payer); + adjust_account(beneficiary, quantity, ram_payer); + } +} + void liq::open(name owner, symbol symbol, name ram_payer_) { require_auth(ram_payer_); check(is_account(owner), "owner account does not exist"); const currency_stats st = stat_of(symbol.code()); - check(st.supply.symbol == symbol, "symbol precision mismatch"); + check(st.supply.symbol == symbol, symbol_precision_mismatch_msg); accounts holdings(get_self(), owner.value); if (!holdings.contains(symbol_key{ symbol.code().raw() })) { adjust_account(owner, asset{ 0, symbol }, ram_payer_); @@ -417,7 +445,7 @@ void liq::settle_and_adjust(opp::shadow::account& row, u128 index, const asset& row.owed_wire = opp::shadow::owed(row, index); // settle before mutate row.index_checkpoint = index; row.balance += delta; - check(row.balance.amount >= 0, "overdrawn balance"); + check(row.balance.amount >= 0, overdrawn_balance_msg); } void liq::adjust_account(name owner, const asset& delta, name payer) { diff --git a/contracts/sysio.liq/sysio.liq.abi b/contracts/sysio.liq/sysio.liq.abi index cb8e1718f7..2edc2235ed 100644 --- a/contracts/sysio.liq/sysio.liq.abi +++ b/contracts/sysio.liq/sysio.liq.abi @@ -311,6 +311,24 @@ } ] }, + { + "name": "settle", + "base": "", + "fields": [ + { + "name": "custodian", + "type": "name" + }, + { + "name": "beneficiary", + "type": "name" + }, + { + "name": "quantity", + "type": "asset" + } + ] + }, { "name": "symbol_key", "base": "", @@ -433,6 +451,11 @@ "type": "setkicker", "ricardian_contract": "" }, + { + "name": "settle", + "type": "settle", + "ricardian_contract": "" + }, { "name": "transfer", "type": "transfer", diff --git a/contracts/sysio.liq/sysio.liq.wasm b/contracts/sysio.liq/sysio.liq.wasm index 6519bcf19d9515c497015f07e0da78f1a6ce2278..51835d1afb2dd34d7733f67486361a96c7e14a28 100755 GIT binary patch delta 4484 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 delta 3684 zcma)9c~n%_8Gqk>1A+tktWicpc_83`q7uOvjl$zZiCP(v*kh{JjU_xp5mXR`L0my? zMm}*J78MoOpf53st%Yb5#C?mTF0JvL#M+bE(vxr+7JR&+H*s;Cp&yr%kI!*fN~txIfz|o)@FA00?5-c-|_+W z{aLNJOFMu+YU{d=1^;|sS&%{TP$lO~Ql%0Il$k*0q_DTZ{*fj?nIw->DCqE_iU44@2ecb@_aaj4%!DAU&a^<+@2X*~)1Ck zfIYl~J>?llXNQFd1btI7|}0!=Ovg#T;CEOW#g&+5#|8_#|fYJ1a}b}Tf5!4mA< zcLh0furI3TKzOKKzD8j`?~>p!#~C!RVq ziV!XvdJjJsF0hM9PK_>C?%PYV(lDT~BVQ69&0NU1)9jnSE0$dH(F zcI6DV#Po+`^i3|PiqS1LoA$K4*fRjB82kQjpb$5|Z)4SsTAvYjq1*mO zr*sx73W091!USEzmyOsfuK(L*X9X#mR2xFYP2f6f(OC#GQFfEkWPun1++~9@Kvf<$ zYRls80jQWdYBi+ezeb&gS@_eaV7xnel8;LoIdZg(G%aC_7lTDuB?n>ASbv(`m;2-8 zu`8W; zOq#sDt>tG?o)QVKY!vw}T3hqjzv=#?aOq5dGo3h#)rEGm&IeRv1J zh@^R(Xm?T&<@>p$9I)buA(rwXzrbM^_TJv#gEjjWX!fKf3CS}`aoJlB=yH}X>M4M zZs(hRv+nq~Ovxlg%Yd8mNZ?L(%J3s6`enpY`ANOI$7VK}e=u@!i*TQxhiC>T-3o|C}2&Q4gKt zte&576^`Y5sgY_7^$V_z}zi2Wg|Gq^D^nJESgXMT=aVlMh=$ntQ5fA3~=(>boeELm9 z7V?>i+lr9dlcNI#iBN?T3TC~#6*}^^3bJU?>zB#=W;Pg&0lb=l?k`8@(MP)1%K4h= zxdNXQ`~a2sz?uj)99B41+y=fFU*hXYlp?=`?O0nF4cqWhVF=hXpCu_kgat)UVTYEt zv^zkxwqaQkzX_kNNTjc2rKzjcaZyU?fh>rtXY_=ii@RW@PaMCXNqrGlPp%a6F^E8< zfmam`I^Hx=w&>-@Z%!BtjL!g+h__wAb7DvKfY%Gq2U1%&B)V9L*e>sKxZPgWV4iywYLZv-TbMO#ACv6+qK| z{Rsp394)qfa6~JxbrPXYv*EX3Ep|RVS`h`;+lX8`&Ry_wfJC0@piyLtUp|=}Jt17^ zpg)$(tr2nUJ04M?94Ecg(Lm^Puz6|x0gM6s6(OkaRB_JdpPogBHkjVxrs?_c^M?6voKNrKvW-EM zv2`0q>j@rgGVzoIYDFZ3@H)vys@#0tvyQEvP1_Vr3{{(|ee60;^gvdndgqkr&)}rZ zQB)V}HjgB_o0})mw_H#HP%%5WO3S;VX-juqepuppXoG{%PZ#H~mc)^%TpV}#0vOGXL#nlU zu8YF#(#1o4(Mfno4>Jz1w<8Vv%Y$)>-PpxN$P19;z$ISBe0u;~!Ag4~KxVF9Jg`aL8M=gtxCrFqRr9I?@gNw!ELU=9>M}2WO91 z=@lol*h2kx|0-G&yqUhPDk=N!JAEy-o}b&h(>mU}9RUf_YDZ6Gu`gcOcJ6!l)w#M> zQG1-C6TA%$*2de_w;StPSqq6ZSU*|kan= outstanding`, because every message in flight moves * custody up or the outstanding down: a syndication is locked before it is minted, a desyndication burned * before it is paid (or while its payout is stored as pending), yield claimed before it is reported; an - * INVALID envelope is burned while the outpost keeps the custody, a `sysio.liq::recredit` after an outpost - * refusal grows the supply the outpost still holds, and anyone may send tokens to a custody account. An + * INVALID envelope is burned while the outpost keeps the custody, a request-keyed `refundreturn` after + * definitive external cancellation restores supply the outpost still holds, and anyone may send tokens to a custody account. An * excess is printed (`EXCESS`) and nothing else. A shortfall writes a `mismatch` row, pulls the * `sysio.andon` cord when this contract is a registered puller (`andon::may_pull`) and the cord is clear, * prints why when it does not, and the message is then held, minted and added to its envelope exactly as * without it. A total is not a slashable fact. Nothing on this path throws. The check reads live balances - * only: the ledger's running sums are Part B's forensic record and are not read by it, so a governance - * `recredit`, which grows the supply directly, is seen by the next comparison with no entry here. + * only. Each pair retains its latest unresolved incident, cleared only by explicit governance reconciliation. + * No lifetime reporting totals or completed-return archive are retained. * * Emergency stop. While the `sysio.andon` cord is pulled nothing leaves this contract, and nothing it * holds is burned: the queue step still refreshes requests, records outcomes and issues requests, but @@ -265,25 +248,41 @@ namespace sysio { uint64_t synd_refill, uint64_t desynd_burst, uint64_t desynd_refill, uint32_t window_sec, uint64_t bounty, uint64_t challenge_extra); - /// Run one step of the queue with at most `limit` units of work: each item released, burned or - /// found unable to move spends one, and so does each request issued. Refreshing an envelope from - /// its request spends none, so the envelopes the step reaches are refreshed whatever the pair's - /// flows. An item of a flow that has stopped for the step -- syndications behind an empty or unset - /// bucket, yields behind a full headroom -- is passed over without spending. A separate cap of - /// 4 x `limit` examinations -- one per pair visited (its ledger, config and first envelope - /// lookup), one per envelope looked at, one per item row read -- bounds the rows the step reads - /// to a constant multiple of `limit`, whatever the number of pairs, idle or blocked. The step - /// ends when either runs out, and the next starts at the pair it ended on, or at the pair after it - /// when that pair released and burned nothing (`syndstate.queue_cursor`): a pair that keeps moving - /// holds the cursor until its backlog drains, and a pair that moves nothing never holds it. A pair - /// the cap reached before it could look at any of its envelopes keeps the cursor. A pair whose - /// blocked backlog is longer than one step's window needs a `crank` with a large limit before - /// `sysio.bond`'s prune retention elapses, or a later envelope's outcome can be pruned unrecorded. - /// Permissionless, and never throws -- what cannot move now waits for the next step. While the - /// `sysio.andon` cord is pulled the step releases, forwards and burns nothing (the file comment's - /// emergency stop). + /// Run bounded outcome synchronization, FIFO release, and underwriting, in that order. Sync + /// visits at most `limit` envelopes; underwriting and release each have `limit` work units and + /// `4 * limit` examinations, with independent cursors. Permissionless. Blocked work waits; while + /// frozen only synchronization and request issuance run. Durable outcomes have no polling deadline. [[sysio::action]] void crank(uint32_t limit); + /// Synchronize one issued envelope's request identity and terminal outcome without releasing, + /// burning or claiming funds. Permissionless; safe while frozen and independent of FIFO release. + [[sysio::action]] void syncenv(sysio::slug_name chain_code, sysio::slug_name token_code, + uint32_t epoch_index); + + /// Synchronize at most `limit` envelopes from a persistent cursor, including DONE envelopes + /// still awaiting a ruling. Permissionless; no recipient code or funds movement. + [[sysio::action]] void sync(uint32_t limit); + + /// Erase at most `limit` settled prefix envelopes, retaining a replay floor. Stops at the first + /// unfinished row; DONE before finality, unacknowledged outcomes, items and pending shares stay. + /// Permissionless, counts every examined envelope, and never moves funds. + [[sysio::action]] void pruneenv(sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t limit); + + /// Governance attests that custody is reconciled at `reported` base units, which must cover + /// current outstanding supply. Erases this pair's incident, leaving the global Andon cord alone. + /// This is a trusted governance statement, not an on-chain measurement of external custody. + [[sysio::action]] void reconcile(sysio::slug_name chain_code, sysio::slug_name token_code, uint64_t reported); + + /// Governance confirms an outstanding return was externally paid/completed. Erases the pending + /// obligation without depot credit. External finality is established off-chain; auth=sysio. + [[sysio::action]] void finishreturn(uint64_t request_id); + + /// Governance attests irreversible external rejection/cancellation: nothing was paid and no + /// payable external obligation remains. Restore exactly the net burned amount to the original + /// holder, atomically erase the outstanding row, and reject a duplicate. Auth=sysio. No timeout + /// or mere queued/pending/duplicate-request observation is sufficient evidence for this action. + [[sysio::action]] void refundreturn(uint64_t request_id); + /// Drop the envelope of outpost `chain_code`, token `token_code` and depot epoch `epoch_index`, OPEN or /// WAITING with no request issued: burn its SYNDICATION items out of this contract's `sysio.liq` row /// with `sysio.liq::burn` (the WIRE they earned stays in the pool as slack), drop its YIELD items, @@ -300,8 +299,8 @@ namespace sysio { /// INVALID ruling. The envelope becomes HELD, and the pair's `queue_epoch` is rewound to it so the /// queue processes the ruling even for an envelope it had finished. A charge of zero (a hold bond /// that rounds to zero and no `challenge_extra`) is refused. One challenge per request. - /// `challenger` may not be this contract, `sysio.bond`, or an account with contract code (a share - /// of the hold bond may be forwarded to it inside the queue step, which must never throw). + /// `challenger` may not be this contract or `sysio.bond`. Contract accounts receive refunds + /// through ledger settlement without recipient execution. /// Challenger's authority; any row the challenge creates bills the `sysio` RAM pool. [[sysio::action]] void challenge(name challenger, sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t epoch_index); @@ -322,14 +321,10 @@ namespace sysio { /// cord is pulled. Permissionless. [[sysio::action]] void sweep(name account, opp::types::ChainKind chain_kind); - /// AuthX link completed for `account` on `chain_kind`: deliver every parked row of `pubkey` to - /// `account`, its banked WIRE included. Inline from `sysio.authex::createlink`, so a missing - /// account, this contract itself, or a pubkey that does not fit `chain_kind` returns without - /// effect rather than aborting the user's link. The shadow is delivered by a `sysio.liq::transfer`, - /// which notifies `account`: a contract account whose transfer handler throws aborts its own - /// `createlink` and so cannot link while it holds parked shadow (the banked WIRE is credited - /// through `sysio.liq::creditowed`, which notifies no one). While the `sysio.andon` cord is pulled it - /// delivers nothing and returns, so the link stands and `sweep` delivers after the clear. + /// AuthX link completed: deliver parked principal by callback-free LIQ settlement and banked + /// WIRE by creditowed. A missing account or malformed key returns quietly. Contract accounts, + /// including this custodian, can receive without executing code. While Andon is pulled, the + /// link stands and sweep delivers after the clear. /// Auth=sysio.authex. [[sysio::action]] void linkswept(name account, opp::types::ChainKind chain_kind, std::vector pubkey); @@ -343,12 +338,12 @@ namespace sysio { /// a pair with no `syndconfig` row. The quantity moves from the holder's own `sysio.liq` row (never /// a held or parked balance) to this contract. The fee, `quantity * desynd_fee_bps / /// BPS_DENOMINATOR` floored, stays in `feepot`; the net rest is burned with `sysio.liq::burn`, and - /// is what the attestation, `desyndlog` and the ledger's `desyndicated_sum` carry. The bucket drops + /// is what the attestation, `returns` carry. The bucket drops /// by the whole quantity. A fee that rounds to zero burns and queues the whole quantity; a fee of /// the whole quantity is refused. The request takes the next id from `syndcounters`. The attestation - /// and `desyndlog` also carry `total_syndicated`, the depot's outstanding shadow of the symbol after + /// carries `total_syndicated`, the depot's outstanding shadow of the symbol after /// this burn (`liq::outstanding_of`: supply plus parked yield, net of the burned amount). The burn is - /// final: an outpost refusal is reconciled by governance through `sysio.liq::recredit`. Refused while + /// final: an outpost refusal is reconciled by governance through request-keyed `refundreturn`. Refused while /// the `sysio.andon` cord is pulled. Holder's authority. [[sysio::action]] void desyndicate(name holder, asset quantity); @@ -472,18 +467,16 @@ namespace sysio { SYSLIB_SERIALIZE(ledger_key, (chain_code)(token_code)) }; - /// Running sums of everything admitted for one outpost and token, from the first message on. + /// Operational queue positions and replay floor for one outpost/token; no lifetime totals. struct [[sysio::table("ledger")]] ledger_row { sysio::slug_name chain_code; ///< the outpost sysio::slug_name token_code; ///< the liq token - uint64_t syndicated_sum = 0; ///< base units of every SYNDICATE_LIQ admitted - uint64_t yield_sum = 0; ///< base units of every LIQ_YIELD admitted - uint64_t desyndicated_sum = 0; ///< base units of every DESYNDICATE_LIQ queued, net of fees /// The queue's cursor: no envelope of an earlier epoch is left to refresh or release, so the /// queue step starts here. uint32_t queue_epoch = 0; - SYSLIB_SERIALIZE(ledger_row, (chain_code)(token_code)(syndicated_sum)(yield_sum)(desyndicated_sum) - (queue_epoch)) + uint32_t underwriting_epoch = 0; ///< oldest envelope still gating issuance, independent of release + uint64_t retained_from_epoch = 0; ///< epochs below this floor were compacted and cannot reopen + SYSLIB_SERIALIZE(ledger_row, (chain_code)(token_code)(queue_epoch)(underwriting_epoch)(retained_from_epoch)) }; /// Ledger rows by outpost and token. @@ -555,27 +548,26 @@ namespace sysio { /// Parked rows by token, chain family and pubkey. using parkeds_t = kv::table<"parked"_n, parked_key, parked_row>; - /// Key of `desyndlog`: one row per DESYNDICATE_LIQ request. - struct desynd_key { - uint64_t request_id; ///< the request id the attestation carries - SYSLIB_SERIALIZE(desynd_key, (request_id)) + /// Stable identity of an outstanding external return. IDs are never reused. + struct return_key { + uint64_t request_id; + SYSLIB_SERIALIZE(return_key, (request_id)) }; - /// One queued DESYNDICATE_LIQ, the running sum of its outpost and token after it, and the - /// outstanding shadow total the attestation carried. - struct [[sysio::table("desyndlog")]] desynd_row { - uint64_t request_id = 0; ///< the request id the attestation carries - sysio::slug_name chain_code; ///< the outpost it was queued to - sysio::slug_name token_code; ///< the liq token - uint64_t amount = 0; ///< base units burned and queued, net of the fee - uint64_t desyndicated_sum = 0; ///< the ledger's `desyndicated_sum` after this request - uint64_t total_syndicated = 0; ///< the depot's outstanding shadow after this burn, as carried - SYSLIB_SERIALIZE(desynd_row, (request_id)(chain_code)(token_code)(amount)(desyndicated_sum) - (total_syndicated)) + /// A burned return awaiting an externally established outcome. Contains only what governance + /// needs to identify the obligation and restore its original holder if it is definitively rejected. + struct [[sysio::table("returns")]] return_row { + uint64_t request_id = 0; ///< DESYNDICATE_LIQ identity + name holder; ///< original depot holder; later wallet linking cannot redirect a refund + sysio::slug_name chain_code; ///< destination outpost + sysio::slug_name token_code; ///< burned token + opp::types::ChainKind chain_kind; ///< destination key family + std::vector pubkey; ///< destination captured when queued + uint64_t amount = 0; ///< exact net burned amount; the fee is not refundable here + SYSLIB_SERIALIZE(return_row, (request_id)(holder)(chain_code)(token_code)(chain_kind)(pubkey)(amount)) }; - - /// Desyndications by request id. - using desyndlog_t = kv::table<"desyndlog"_n, desynd_key, desynd_row>; + /// Outstanding returns only; completed/refunded rows are erased atomically. + using returns_t = kv::table<"returns"_n, return_key, return_row>; /// Contract-wide state. struct [[sysio::table("syndstate")]] synd_state { @@ -584,7 +576,9 @@ namespace sysio { /// the last step ran out on (budget or examination cap), or the one after it when that pair /// moved nothing. uint64_t queue_cursor = 0; - SYSLIB_SERIALIZE(synd_state, (import_complete)(queue_cursor)) + uint64_t underwriting_cursor = 0; ///< independent pair cursor for request issuance + envelope_key sync_cursor{}; ///< next envelope inspected by the independent outcome sweep + SYSLIB_SERIALIZE(synd_state, (import_complete)(queue_cursor)(underwriting_cursor)(sync_cursor)) }; /// The state singleton. @@ -655,18 +649,15 @@ namespace sysio { /// Fee pots by token code. using feepots_t = kv::table<"feepot"_n, token_key, fee_row>; - /// Key of `mismatch`: the outpost and the sequence of the message that reported the shortfall. An - /// admitted sequence is unique per outpost (`admit_sequence`) and only an admitted message is - /// compared, so the row needs no id counter of its own (the spec's `id` key, spec 4.2). + /// One unresolved custody incident per outpost/token. struct mismatch_key { uint64_t chain_code; ///< the outpost's registry code value - uint64_t sequence; ///< the outpost sequence of the message - SYSLIB_SERIALIZE(mismatch_key, (chain_code)(sequence)) + uint64_t token_code; ///< the affected token + SYSLIB_SERIALIZE(mismatch_key, (chain_code)(token_code)) }; - /// One custody shortfall: an admitted SYNDICATE_LIQ or LIQ_YIELD whose `total_syndicated`, the - /// outpost's live custody of the token, was below the depot's outstanding shadow of it. Rows - /// accumulate: nothing prunes them at v1, and each one bills the `sysio` RAM pool. + /// Latest shortfall evidence for an unresolved incident. A healthy report does not implicitly + /// clear it; governance explicitly reconciles it against current outstanding supply. struct [[sysio::table("mismatch")]] mismatch_row { sysio::slug_name chain_code; ///< the outpost that reported it sysio::slug_name token_code; ///< the liq token @@ -682,7 +673,7 @@ namespace sysio { (at)) }; - /// Custody shortfalls by outpost and sequence. + /// Current custody incidents by outpost and token. using mismatches_t = kv::table<"mismatch"_n, mismatch_key, mismatch_row>; private: @@ -693,7 +684,7 @@ namespace sysio { /// The solvency check of one admitted message on path `path`: `reported`, the outpost custody of the /// shadow `st` the message carried, against `expected`, the depot's outstanding shadow of it. At or /// above: nothing, but an excess prints `EXCESS`. Below: a `mismatch` row keyed `(chain_code, - /// sequence)` for the message of `kind` in the envelope of depot epoch `epoch_index`, and a + /// token_code)` for the message of `kind` in the envelope of depot epoch `epoch_index`, and a /// `sysio.andon::pull` when `andon::may_pull` admits this contract and the cord is clear; otherwise /// the reason the cord was not pulled is printed. Never throws; the caller processes the message /// as usual either way. diff --git a/contracts/sysio.synd/src/sysio.synd.cpp b/contracts/sysio.synd/src/sysio.synd.cpp index 1e92e44586..18e9b25f6d 100644 --- a/contracts/sysio.synd/src/sysio.synd.cpp +++ b/contracts/sysio.synd/src/sysio.synd.cpp @@ -32,6 +32,17 @@ namespace sysio { namespace { +constexpr name ack_action = "ack"_n; +constexpr name settle_action = "settle"_n; +constexpr name requestkeep_action = "requestkeep"_n; +constexpr name syncenv_action = "syncenv"_n; +constexpr auto outpost_token_not_found_msg = "outpost token not found"; +constexpr auto custody_incident_not_found_msg = "custody incident not found"; +constexpr auto custody_still_below_outstanding_msg = "custody still below outstanding"; +constexpr auto outstanding_return_not_found_msg = "outstanding return not found"; +constexpr auto return_request_id_space_exhausted_msg = "return request id space exhausted"; + + using opp::types::AttestationType; using opp::types::ChainKind; @@ -52,9 +63,7 @@ constexpr name creditowed_action = "creditowed"_n; constexpr name transfer_action = "transfer"_n; /// `sysio.msgch`'s outbound queue. constexpr name queueout_action = "queueout"_n; -/// `sysio.bond::request`, which a WAITING envelope sends, and `sysio.liq::mintyield`, which a -/// released yield item sends. -constexpr name request_action = "request"_n; +/// `sysio.liq::mintyield`, which a released yield item sends. constexpr name mintyield_action = "mintyield"_n; /// `sysio.bond::hold`, which a challenge sends, and `sysio.bond::claim`, which pulls what a ruling awards /// this contract as the issuer. @@ -67,14 +76,11 @@ constexpr name pull_action = "pull"_n; constexpr uint64_t max_asset_amount = static_cast(asset::max_amount); /// Memos of the transfers a parked delivery and a desyndication send. -constexpr std::string_view parked_delivery_memo = "sysio.synd parked delivery"; constexpr std::string_view desyndicate_memo = "sysio.synd desyndicate"; /// Memo of the transfers a release to a linked account sends. -constexpr std::string_view release_memo = "sysio.synd release"; /// Memo of the transfer that pulls a challenger's charge, and of the one that forwards a challenger /// the share of the hold bond a VALID ruling returns. constexpr std::string_view challenge_memo = "sysio.synd challenge"; -constexpr std::string_view hold_share_memo = "sysio.synd hold share"; /// Memo of the transfer that pays `sysio` the WIRE the fee pot earned. constexpr std::string_view fee_yield_memo = "sysio.synd fee pot yield"; @@ -91,7 +97,6 @@ constexpr std::string_view fee_bps_message = "fee must be at most 10000 ba constexpr std::string_view window_sec_message = "window_sec must be positive"; constexpr std::string_view config_range_message = "bucket, bounty and challenge amounts must fit an asset"; constexpr std::string_view challenger_role_msg = "the challenger cannot be sysio.synd or sysio.bond"; -constexpr std::string_view challenger_code_msg = "a challenger may not have contract code"; constexpr std::string_view no_envelope_message = "envelope not found"; constexpr std::string_view challenged_message = "the envelope's request is already challenged"; constexpr std::string_view unchallengeable_msg = @@ -130,8 +135,6 @@ constexpr std::string_view digest_reason = "envelope digest does not ma constexpr std::string_view no_bond_reason = "sysio.bond is not deployed; the envelope stays WAITING"; constexpr std::string_view bond_token_reason = "sysio.bond cannot bond the token; the envelope stays WAITING"; constexpr std::string_view covered_range_reason = "covered exceeds the asset range; the envelope stays WAITING"; -constexpr std::string_view statement_len_reason = "statement too long for sysio.bond; the envelope stays WAITING"; -constexpr std::string_view duplicate_reason = "statement already requested; the envelope stays WAITING"; constexpr std::string_view missing_request_reason = "request row not found in sysio.bond; the pair waits"; constexpr std::string_view unknown_state_reason = "request in a state the queue does not know; the pair waits"; constexpr std::string_view dropped_reason = @@ -139,8 +142,6 @@ constexpr std::string_view dropped_reason = constexpr std::string_view invalid_burning_reason = "request ruled INVALID; burning the envelope's items continues next step, the pair waits"; constexpr std::string_view invalid_burned_reason = "request ruled INVALID; the envelope is burned and INVALID"; -constexpr std::string_view contract_holder_reason = - "the challenger has contract code; its share of the hold bond goes to the fee pot"; constexpr std::string_view unset_bucket_reason = "no syndconfig row: the syndication bucket is unset, nothing releases"; constexpr std::string_view empty_bucket_reason = "syndication bucket is empty until it refills"; @@ -216,6 +217,8 @@ synd::envelope_key envelope_key_of(sysio::slug_name chain_code, sysio::slug_name /// `token_code`: the row does not exist yet, or it is OPEN with the same digest. Never throws. bool envelope_accepts(name self, sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t epoch_index, const checksum256& digest) { + const auto state = synd::ledger_t(self).try_get(synd::ledger_key{chain_code.value, token_code.value}); + if (state && epoch_index < state->retained_from_epoch) return false; synd::envelopes_t envelopes(self); const auto row = envelopes.try_get(envelope_key_of(chain_code, token_code, epoch_index)); return !row || (row->state == synd::envelope_state::OPEN && row->digest == digest); @@ -274,22 +277,12 @@ void add_to_envelope(name self, sysio::slug_name chain_code, sysio::slug_name to envelopes.set(ram_payer, key, row); } -/// Add `synd_amount`, `yield_amount` and `desynd_amount` base units to the running sums of -/// `(chain_code, token_code)`, creating the row on the first, and return it as stored. The ONE writer -/// of the three sums. Saturating; never throws. -synd::ledger_row add_to_ledger(name self, sysio::slug_name chain_code, sysio::slug_name token_code, - uint64_t synd_amount, uint64_t yield_amount, uint64_t desynd_amount) { - synd::ledger_t ledger(self); - const synd::ledger_key key{.chain_code = chain_code.value, .token_code = token_code.value}; - synd::ledger_row row = ledger.try_get(key).value_or(synd::ledger_row{ - .chain_code = chain_code, - .token_code = token_code, - }); - row.syndicated_sum = opp::safe::add_sat_u64(row.syndicated_sum, synd_amount); - row.yield_sum = opp::safe::add_sat_u64(row.yield_sum, yield_amount); - row.desyndicated_sum = opp::safe::add_sat_u64(row.desyndicated_sum, desynd_amount); - ledger.set(ram_payer, key, row); - return row; +/// Ensure the pair has operational queue positions; later intake leaves existing cursors untouched. +void ensure_ledger(name self, sysio::slug_name chain_code, sysio::slug_name token_code) { + synd::ledger_t ledger(self); + const synd::ledger_key key{chain_code.value, token_code.value}; + if (!ledger.contains(key)) + ledger.set(ram_payer, key, synd::ledger_row{.chain_code = chain_code, .token_code = token_code}); } /// The key of `item` in `items`: its envelope row, then its id. @@ -445,28 +438,11 @@ synd::bucket_row tick_bucket(name self, sysio::slug_name chain_code, sysio::slug return row; } -/// True iff the queue step may push a transfer to `account`: an existing account without contract code, -/// other than this contract. A transfer notifies its recipient, and a contract that refused it would -/// make the step, and with it the envelope path, fail for every holder. Never throws. -bool pushable_account(name self, name account) { - return account != name{} && account != self && is_account(account) && !has_code(account); -} - -/// The account a release of `pubkey`'s shadow is pushed to: the one the pubkey has linked on -/// `chain_kind` at release time, when `pushable_account` admits it. An empty name otherwise, and the -/// release goes into `parked`, where the account takes delivery through `sweep` or `linkswept`, in which -/// a refusal fails only its own action. Never throws. +/// Linked accounts receive a ledger credit, including contracts and this custodian itself. +/// No recipient code executes; only genuinely unlinked identities need parked principal. name deliverable_account(name self, ChainKind chain_kind, const std::vector& pubkey) { const name account = linked_account(chain_kind, pubkey); - return pushable_account(self, account) ? account : name{}; -} - -/// Base units request `request_id`'s escrow of `kind` still awards `self` on `sysio.bond`, to claim: 0 -/// when the escrow does not exist, awards another account, or was claimed. Never throws. -uint64_t award_of(name self, uint64_t request_id, bond::escrow_kind kind) { - bond::escrows_t escrows(synd::BOND_ACCOUNT); - const auto row = escrows.try_get(bond::escrow_key_of(request_id, kind)); - return row && row->payee == self ? row->payout : 0; + return account != name{} && is_account(account) ? account : name{}; } /// The statement `sysio.bond` underwrites for envelope `row`: its outpost, epoch, digest and token. @@ -479,19 +455,43 @@ std::vector statement_of(const synd::envelope_row& row) { }); } -/// The key `sysio.bond`'s `bystatement` index holds a request by `self` for `statement` under. -checksum256 request_key_of(name self, const std::vector& statement) { - return bond::statement_key_of(self, bond::statement_digest_of(opp::envelope_statement::schema, statement)); +/// Resolve an envelope's stable issuer-and-statement identity through bond's public accessor. +std::optional find_request(name self, const synd::envelope_row& row) { + return bond::find_request(synd::BOND_ACCOUNT, self, opp::envelope_statement::schema, statement_of(row)); } -/// The `sysio.bond` request `self` issued for envelope `row`, found through its `bystatement` key rather -/// than the stored id; `std::nullopt` when there is none (never issued, or pruned). Never throws. -std::optional find_request(name self, const synd::envelope_row& row) { - bond::requests_t requests(synd::BOND_ACCOUNT); - const auto by_statement = requests.get_index(); - const auto it = by_statement.find(request_key_of(self, statement_of(row))); - if (it == by_statement.end()) return std::nullopt; - return *it; +/// Snapshot a terminal outcome before acknowledging it. This function moves no funds and includes +/// DONE envelopes whose release finished before challenge finality. Settlement work rewinds release. +void synchronize(name self, synd::envelope_row& row) { + if (row.state == synd::envelope_state::OPEN || row.state == synd::envelope_state::WAITING || + row.outcome != synd::request_outcome::PENDING) return; + const auto req = find_request(self, row); + if (!req) return; + row.request_id = req->id; + using state = bond::request_state; + using outcome = synd::request_outcome; + if (req->state == state::APPROVED) row.outcome = outcome::APPROVED; + else if (req->state == state::VALID) { + row.outcome = outcome::VALID; + row.hold_share = bond::share_of(req->covered - req->bonded, req->hold_bond, req->covered); + row.hold_beneficiary = req->hold_beneficiary; + row.share_pending = row.hold_share > 0 && row.hold_beneficiary != name{}; + } else if (req->state == state::INVALID) { + row.outcome = outcome::INVALID; + row.forfeit = req->bonded; + row.bounty_returned = req->hold_beneficiary == name{} ? req->bounty : 0; + } else return; + if (row.outcome == outcome::INVALID || row.share_pending) { + if (row.state == synd::envelope_state::DONE) row.state = synd::envelope_state::HELD; + synd::ledger_t ledger(self); + const synd::ledger_key key{row.chain_code.value, row.token_code.value}; + auto lrow = ledger.try_get(key); + if (lrow && lrow->queue_epoch > row.epoch_index) { + lrow->queue_epoch = row.epoch_index; + ledger.set(ram_payer, key, *lrow); + } + } + action(active_of(self), synd::BOND_ACCOUNT, ack_action, std::make_tuple(req->id)).send(); } /// Record that the queue step left `(chain_code, token_code, epoch_index)` where it was, for `reason`. @@ -501,14 +501,10 @@ void note(sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t epo " -- ", std::string(reason), "\n"); } -/// One step of the queue (the file header's three parts), bounded by a budget of work units and by an -/// examination cap of EXAMINE_FACTOR times that budget, which bounds the rows it reads whatever the -/// number of pairs. Every -/// outpost and token is walked from its `queue_epoch` in epoch order, which within a pair is item id -/// order, so no pair's backlog stops another's. Never throws of its own; the one check it can reach is -/// `custody::pull`'s, which fails only on corrupt `sysio.liq` state. While the `sysio.andon` cord is -/// pulled the step releases, forwards and burns nothing: it refreshes, records outcomes and issues -/// requests only, so it sends no action that moves funds out of this contract. +/// One bounded underwriting or release pass. Each owns its work/examination budget and pair cursor; +/// underwriting also uses a separate per-pair epoch cursor so release backlogs cannot stall issuance. +/// Both refresh reached outcomes before decisions. Only the release pass may move funds, and it waits +/// while frozen. Independent synchronization additionally covers envelopes already marked DONE. class queue_step { public: /// Examinations a step may make per work unit of its budget. @@ -516,8 +512,8 @@ class queue_step { /// A step of at most `limit` work units and `EXAMINE_FACTOR * limit` examinations, run by the contract /// `self`. - queue_step(name self, uint32_t limit) - : self_(self), left_(limit), looks_(static_cast(std::min( + queue_step(name self, uint32_t limit, bool underwriting = false) + : underwriting_(underwriting), self_(self), left_(limit), looks_(static_cast(std::min( uint64_t{EXAMINE_FACTOR} * limit, std::numeric_limits::max()))), epoch_(epoch::current_epoch_index()), frozen_(andon::pulled(andon::ANDON_ACCOUNT)) {} @@ -532,7 +528,7 @@ class queue_step { if (frozen_) sysio::print(std::string(frozen_step_note)); synd::syndstate_t state(self_); synd::synd_state stored = state.get_or_default(synd::synd_state{}); - const uint64_t start = stored.queue_cursor; + const uint64_t start = underwriting_ ? stored.underwriting_cursor : stored.queue_cursor; liq::stats statstable(synd::LIQ_ACCOUNT); std::optional stopped_at; bool hold = false; // the next step starts at the pair it ran out on @@ -559,7 +555,8 @@ class queue_step { cursor = next != statstable.end() ? next.key().symbol_code : 0; } if (cursor != start) { - stored.queue_cursor = cursor; + if (underwriting_) stored.underwriting_cursor = cursor; + else stored.queue_cursor = cursor; state.set(stored, ram_payer); } } @@ -620,7 +617,8 @@ class queue_step { // envelope can move -- the rest is WAITING or still OPEN. synd::envelopes_t envelopes(self_); std::vector keys; - auto first = envelopes.lower_bound(envelope_key_of(st.chain_code, st.token_code, lrow->queue_epoch)); + const uint32_t start_epoch = underwriting_ ? lrow->underwriting_epoch : lrow->queue_epoch; + auto first = envelopes.lower_bound(envelope_key_of(st.chain_code, st.token_code, start_epoch)); const bool has_envelope = first != envelopes.end() && first.key().chain_code == lkey.chain_code && first.key().token_code == lkey.token_code; if (has_envelope && collectable() == 0) return false; // the cap ran out on the entry look @@ -631,7 +629,7 @@ class queue_step { if (it->state == synd::envelope_state::WAITING) break; } - uint32_t queue_epoch = lrow->queue_epoch; + uint32_t queue_epoch = underwriting_ ? lrow->underwriting_epoch : lrow->queue_epoch; bool finished = true; // every envelope walked so far is DONE or INVALID bool unbonded = false; // an earlier request is neither bonded nor ruled: nothing moves past it for (const auto& key : keys) { @@ -648,10 +646,11 @@ class queue_step { row->state == synd::envelope_state::HELD) { refresh(*row, unbonded, st); } - if (row->state == synd::envelope_state::RELEASABLE && !unbonded && !frozen_) { + if (!underwriting_ && row->state == synd::envelope_state::RELEASABLE && !unbonded && !frozen_) { release_items(*row, config, st); } - if (row->state == synd::envelope_state::WAITING && !unbonded && spend()) issue_request(*row, config); + if (underwriting_ && row->state == synd::envelope_state::WAITING && !unbonded && spend()) + issue_request(*row, config); // The outcome's snapshots (`forfeit`, `bounty_returned`, `hold_share`, `hold_beneficiary`) are only // ever written with the outcome; `share_pending` clears on its own when the forward is paid. if (row->state != before || row->outcome != outcome || row->request_id != request_id || @@ -659,15 +658,21 @@ class queue_step { envelopes.set(ram_payer, key, *row); } - const bool done = row->state == synd::envelope_state::DONE || row->state == synd::envelope_state::INVALID; + const bool done = underwriting_ + ? (!unbonded && (row->outcome != synd::request_outcome::PENDING || + (row->state != synd::envelope_state::WAITING && + row->state != synd::envelope_state::OPEN && + row->state != synd::envelope_state::REQUESTED))) + : (row->state == synd::envelope_state::DONE || row->state == synd::envelope_state::INVALID); if (finished && done) { queue_epoch = key.epoch_index + 1; } else { finished = false; } } - if (queue_epoch != lrow->queue_epoch) { - lrow->queue_epoch = queue_epoch; + if (queue_epoch != (underwriting_ ? lrow->underwriting_epoch : lrow->queue_epoch)) { + if (underwriting_) lrow->underwriting_epoch = queue_epoch; + else lrow->queue_epoch = queue_epoch; ledger.set(ram_payer, lkey, *lrow); } return true; @@ -684,12 +689,13 @@ class queue_step { void refresh(synd::envelope_row& row, bool& unbonded, const liq::currency_stats& st) { using request_state = bond::request_state; using outcome = synd::request_outcome; + synchronize(self_, row); if (row.outcome == outcome::INVALID) { - invalidate(row, st, unbonded); + if (!underwriting_) invalidate(row, st, unbonded); return; } if (row.outcome != outcome::PENDING) { - if (row.share_pending) forward_hold_share(row, st); + if (!underwriting_ && row.share_pending) forward_hold_share(row, st); row.state = synd::envelope_state::RELEASABLE; return; } @@ -708,27 +714,6 @@ class queue_step { } else if (req->state == request_state::HELD) { row.state = synd::envelope_state::HELD; if (req->bonded < req->covered) unbonded = true; - } else if (req->state == request_state::APPROVED) { - row.outcome = outcome::APPROVED; - row.state = synd::envelope_state::RELEASABLE; - } else if (req->state == request_state::VALID) { - // The challenger's share is snapshotted with the outcome, frozen or not, so the forward never - // depends on the request row, which sysio.bond may prune once it is paid. - row.outcome = outcome::VALID; - if (req->hold_bond > 0 && req->hold_beneficiary != name{}) { - row.hold_share = bond::share_of(req->covered - req->bonded, req->hold_bond, req->covered); - row.hold_beneficiary = req->hold_beneficiary; - row.share_pending = row.hold_share > 0; - } - if (row.share_pending) forward_hold_share(row, st); - row.state = synd::envelope_state::RELEASABLE; - } else if (req->state == request_state::INVALID) { - // The forfeit and the bounty sysio.bond returns are snapshotted now: the burn may span steps, - // and sysio.bond may prune the row once they are claimed. - row.outcome = outcome::INVALID; - row.forfeit = req->bonded; - row.bounty_returned = req->hold_beneficiary == name{} ? req->bounty : 0; - invalidate(row, st, unbonded); } else { note(row.chain_code, row.token_code, row.epoch_index, unknown_state_reason); unbonded = true; @@ -741,25 +726,17 @@ class queue_step { /// `sysio.andon` cord is pulled it waits, pending, for the first step after the clear. The award is /// claimed first while `sysio.bond` still owes it (a claim with nothing owed would throw; anyone may have /// claimed it for this contract already, and a request pruned since was fully paid, so the share sits in - /// this contract's row either way), then sent by `sysio.liq::transfer`. A challenger that has gained - /// contract code since could refuse the transfer and fail the step, so its share goes to the fee pot. + /// this contract's row either way), then credited by `sysio.liq::settle` without recipient execution. void forward_hold_share(synd::envelope_row& row, const liq::currency_stats& st) { if (frozen_) { note(row.chain_code, row.token_code, row.epoch_index, frozen_share_reason); return; } pull_first(st); - if (award_of(self_, row.request_id, bond::escrow_kind::HOLD_BOND) > 0) send_claim(row.request_id); - if (pushable_account(self_, row.hold_beneficiary)) { - action(active_of(self_), synd::LIQ_ACCOUNT, transfer_action, - std::make_tuple(self_, row.hold_beneficiary, - asset{static_cast(row.hold_share), st.supply.symbol}, - std::string{hold_share_memo})) - .send(); - } else { - note(row.chain_code, row.token_code, row.epoch_index, contract_holder_reason); - add_to_feepot(self_, row.token_code, st.supply.symbol.code(), row.hold_share); - } + if (bond::settlement_due(synd::BOND_ACCOUNT, row.request_id, self_) > 0) send_claim(row.request_id); + action(active_of(self_), synd::LIQ_ACCOUNT, settle_action, + std::make_tuple(self_, row.hold_beneficiary, + asset{static_cast(row.hold_share), st.supply.symbol})).send(); row.share_pending = false; } @@ -810,11 +787,7 @@ class queue_step { return; } - bond::requests_t requests(synd::BOND_ACCOUNT); - const auto req = requests.try_get(bond::request_key{row.request_id}); - if (req && (req->forfeit_pending > 0 || award_of(self_, req->id, bond::escrow_kind::BOUNTY) > 0)) { - send_claim(req->id); - } + if (bond::settlement_due(synd::BOND_ACCOUNT, row.request_id, self_) > 0) send_claim(row.request_id); const uint64_t repaid = std::min(row.released, row.forfeit); burn(row, repaid); add_to_feepot(self_, row.token_code, code, opp::safe::add_sat_u64(row.forfeit - repaid, row.bounty_returned)); @@ -835,20 +808,9 @@ class queue_step { action(active_of(self_), synd::BOND_ACCOUNT, claim_action, std::make_tuple(request_id, self_)).send(); } - /// The id `sysio.bond` will assign to the next request this step sends. Its counter is read once: - /// the requests this step sends run after it, in order, with nothing between them, so they take - /// consecutive ids from there. - uint64_t take_request_id() { - if (!next_request_id_) { - next_request_id_ = - bond::bondcounters_t(synd::BOND_ACCOUNT).get_or_default(bond::bond_counters{}).next_request_id; - } - return (*next_request_id_)++; - } - /// Ask `sysio.bond` to underwrite WAITING envelope `row`: every condition `request` checks is /// verified first, so the inline action cannot fail; on a failure the envelope stays WAITING and the - /// reason is printed. The request id is the one `sysio.bond` will assign (`take_request_id`). + /// reason is printed. The queued `syncenv` resolves the assigned id after request creation using the stable statement identity. void issue_request(synd::envelope_row& row, const std::optional& config) { if (!has_code(synd::BOND_ACCOUNT)) { note(row.chain_code, row.token_code, row.epoch_index, no_bond_reason); @@ -868,26 +830,23 @@ class queue_step { return; } std::vector statement = statement_of(row); - if (statement.size() > bond::MAX_STATEMENT_BYTES) { - note(row.chain_code, row.token_code, row.epoch_index, statement_len_reason); - return; - } - if (find_request(self_, row)) { - note(row.chain_code, row.token_code, row.epoch_index, duplicate_reason); + const uint32_t window_sec = config ? config->window_sec : synd::DEFAULT_WINDOW_SEC; + const auto refusal = bond::request_refusal(synd::BOND_ACCOUNT, self_, opp::envelope_statement::schema, + statement, row.token_code, *covered, 0, window_sec); + if (!refusal.empty()) { + note(row.chain_code, row.token_code, row.epoch_index, refusal); return; } - - const uint32_t window_sec = config ? config->window_sec : synd::DEFAULT_WINDOW_SEC; const uint64_t bounty = take_bounty(self_, row.token_code, custody_token->sym.code(), config ? config->bounty : uint64_t{0}); - const uint64_t request_id = take_request_id(); // sysio.bond pulls the bounty from this contract's liq row, where the fee pot's share of it sat. - action(active_of(self_), synd::BOND_ACCOUNT, request_action, + action(active_of(self_), synd::BOND_ACCOUNT, requestkeep_action, std::make_tuple(self_, opp::envelope_statement::schema, std::move(statement), row.token_code, *covered, bounty, window_sec)) .send(); - row.request_id = request_id; - row.state = synd::envelope_state::REQUESTED; + row.state = synd::envelope_state::REQUESTED; + action(active_of(self_), self_, syncenv_action, + std::make_tuple(row.chain_code, row.token_code, row.epoch_index)).send(); } /// Release the items of RELEASABLE envelope `row` in id order while the budget lasts; the envelope @@ -1004,8 +963,8 @@ class queue_step { item.position.owed_wire = 0; } if (net > 0) { - action(active_of(self_), synd::LIQ_ACCOUNT, transfer_action, - std::make_tuple(self_, account, asset{static_cast(net), sym}, std::string{release_memo})) + action(active_of(self_), synd::LIQ_ACCOUNT, settle_action, + std::make_tuple(self_, account, asset{static_cast(net), sym})) .send(); } credit_owed(self_, account, code, banked); @@ -1061,13 +1020,14 @@ class queue_step { return *pair_.pool; } + bool underwriting_; ///< issuance pass independent of release budget and cursor name self_; ///< this contract uint32_t left_; ///< work units left uint32_t looks_; ///< examinations left uint32_t epoch_; ///< the depot epoch the buckets tick to bool frozen_; ///< the `sysio.andon` cord is pulled: nothing leaves or burns pair_state pair_; ///< the pair being walked - std::optional next_request_id_; ///< id of the next request this step sends, once read + }; } // anonymous namespace @@ -1121,7 +1081,7 @@ void synd::onsynd(sysio::slug_name chain_code, uint32_t epoch_index, checksum256 st->supply.symbol.code()); add_item(get_self(), std::move(item)); add_to_envelope(get_self(), chain_code, token_code, epoch_index, digest, amount, 0); - add_to_ledger(get_self(), chain_code, token_code, amount, 0, 0); + ensure_ledger(get_self(), chain_code, token_code); // resolve_intake verified every condition `mint` checks: the shadow exists and the amount is // positive and within its headroom. This contract is an account, so the mint cannot fail. @@ -1162,7 +1122,7 @@ void synd::onyield(sysio::slug_name chain_code, uint32_t epoch_index, checksum25 custody::settle_and_adjust(item.position, no_shadow, 0, LIQ_ACCOUNT, st->supply.symbol.code()); add_item(get_self(), std::move(item)); add_to_envelope(get_self(), chain_code, token_code, epoch_index, digest, 0, amount); - add_to_ledger(get_self(), chain_code, token_code, 0, amount, 0); + ensure_ledger(get_self(), chain_code, token_code); } void synd::closeenv(sysio::slug_name chain_code, uint32_t epoch_index, checksum256 digest) { @@ -1183,7 +1143,7 @@ void synd::closeenv(sysio::slug_name chain_code, uint32_t epoch_index, checksum2 row->state = envelope_state::WAITING; envelopes.set(ram_payer, key, *row); } - queue_step(get_self(), CLOSEENV_QUEUE_LIMIT).run(); + crank(CLOSEENV_QUEUE_LIMIT); } // --------------------------------------------------------------------------- @@ -1223,7 +1183,95 @@ void synd::setconfig(sysio::slug_name chain_code, sysio::slug_name token_code, u void synd::crank(uint32_t limit) { // Permissionless and never throwing: a step only moves what the rules already allow. + sync(limit); queue_step(get_self(), limit).run(); + queue_step(get_self(), limit, true).run(); +} + +void synd::syncenv(sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t epoch_index) { + envelopes_t envelopes(get_self()); + const auto key = envelope_key_of(chain_code, token_code, epoch_index); + auto row = envelopes.try_get(key); + if (!row) return; + synchronize(get_self(), *row); + envelopes.set(ram_payer, key, *row); +} + +void synd::sync(uint32_t limit) { + if (limit == 0) return; + syndstate_t state(get_self()); + auto stored = state.get_or_default(synd_state{}); + envelopes_t envelopes(get_self()); + std::vector keys; + auto it = envelopes.lower_bound(stored.sync_cursor); + if (it == envelopes.end()) it = envelopes.begin(); + for (; it != envelopes.end() && keys.size() < limit; ++it) keys.push_back(it.key()); + const auto next = it == envelopes.end() ? envelope_key{} : it.key(); + if (next.chain_code != stored.sync_cursor.chain_code || next.token_code != stored.sync_cursor.token_code || + next.epoch_index != stored.sync_cursor.epoch_index) { + stored.sync_cursor = next; + state.set(stored, ram_payer); + } + for (const auto& key : keys) syncenv(sysio::slug_name{key.chain_code}, sysio::slug_name{key.token_code}, key.epoch_index); +} + +void synd::pruneenv(sysio::slug_name chain_code, sysio::slug_name token_code, uint32_t limit) { + ledger_t ledger(get_self()); + const ledger_key lkey{chain_code.value, token_code.value}; + auto state = ledger.try_get(lkey); + if (!state || limit == 0) return; + envelopes_t envelopes(get_self()); + items_t items(get_self()); + bond::requests_t requests(BOND_ACCOUNT); + auto it = envelopes.lower_bound(envelope_key_of(chain_code, token_code, 0)); + uint32_t examined = 0; + while (it != envelopes.end() && examined++ < limit) { + const auto row = *it; + if (row.chain_code != chain_code || row.token_code != token_code) break; + if (row.state != envelope_state::DONE && row.state != envelope_state::INVALID) break; + if (row.share_pending) break; + const bool dropped = row.state == envelope_state::INVALID && row.request_id == 0; + if (!dropped && row.outcome == request_outcome::PENDING) break; + const auto item = items.lower_bound(item_key{chain_code.value, token_code.value, row.epoch_index, 0}); + if (item != items.end() && item->chain_code == chain_code && item->token_code == token_code && + item->epoch_index == row.epoch_index) break; + const auto request = requests.try_get(bond::request_key{row.request_id}); + if (request && !request->outcome_acknowledged) break; + state->retained_from_epoch = uint64_t{row.epoch_index} + 1; + it = envelopes.erase(it); + } + ledger.set(ram_payer, lkey, *state); +} + +void synd::reconcile(sysio::slug_name chain_code, sysio::slug_name token_code, uint64_t reported) { + require_auth(SYSTEM_ACCOUNT); + const auto st = liq::find_stat_by_token(LIQ_ACCOUNT, token_code); + check(st.has_value() && st->chain_code == chain_code, outpost_token_not_found_msg); + mismatches_t incidents(get_self()); + const mismatch_key key{chain_code.value, token_code.value}; + check(incidents.contains(key), custody_incident_not_found_msg); + check(reported >= liq::outstanding_of(LIQ_ACCOUNT, *st), custody_still_below_outstanding_msg); + incidents.erase(key); +} + +void synd::finishreturn(uint64_t request_id) { + require_auth(SYSTEM_ACCOUNT); + returns_t pending(get_self()); + const return_key key{request_id}; + check(pending.contains(key), outstanding_return_not_found_msg); + pending.erase(key); +} + +void synd::refundreturn(uint64_t request_id) { + require_auth(SYSTEM_ACCOUNT); + returns_t pending(get_self()); + const return_key key{request_id}; + const auto row = pending.try_get(key); + check(row.has_value(), outstanding_return_not_found_msg); + // The erase and mint share one transaction: any mint failure restores the outstanding obligation. + pending.erase(key); + action(active_of(get_self()), LIQ_ACCOUNT, mint_action, + std::make_tuple(row->holder, row->token_code, row->amount)).send(); } // dropenv - the escape hatch for an envelope the queue can never request. @@ -1269,7 +1317,6 @@ void synd::challenge(name challenger, sysio::slug_name chain_code, sysio::slug_n uint32_t epoch_index) { require_auth(challenger); check(challenger != get_self() && challenger != BOND_ACCOUNT, challenger_role_msg.data()); - check(!has_code(challenger), challenger_code_msg.data()); envelopes_t envelopes(get_self()); const envelope_key key = envelope_key_of(chain_code, token_code, epoch_index); @@ -1372,7 +1419,7 @@ void synd::linkswept(name account, ChainKind chain_kind, std::vector pubke require_auth(AUTHEX_ACCOUNT); // Inline from the user's own createlink: anything short of a deliverable pubkey returns quietly // rather than abort the link. - if (!is_account(account) || account == get_self() || !pubkey_fits(chain_kind, pubkey)) return; + if (!is_account(account) || !pubkey_fits(chain_kind, pubkey)) return; // Never abort the link: while the cord is pulled the parked balance stays for `sweep` after the clear. if (andon::pulled(andon::ANDON_ACCOUNT)) { sysio::print(std::string(frozen_linkswept_note)); @@ -1434,19 +1481,18 @@ void synd::desyndicate(name holder, asset quantity) { syndcounters_t counters(get_self()); synd_counters c = counters.get_or_default(synd_counters{}); + check(c.next_request_id < std::numeric_limits::max(), return_request_id_space_exhausted_msg); const uint64_t request_id = c.next_request_id++; counters.set(c, ram_payer); - const ledger_row lrow = add_to_ledger(get_self(), st.chain_code, st.token_code, 0, 0, amount); - - desyndlog_t desyndlog(get_self()); - desyndlog.set(ram_payer, desynd_key{request_id}, desynd_row{ - .request_id = request_id, - .chain_code = st.chain_code, - .token_code = st.token_code, - .amount = amount, - .desyndicated_sum = lrow.desyndicated_sum, - .total_syndicated = total_syndicated, + returns_t(get_self()).set(ram_payer, return_key{request_id}, return_row{ + .request_id = request_id, + .holder = holder, + .chain_code = st.chain_code, + .token_code = st.token_code, + .chain_kind = *kind, + .pubkey = *pubkey, + .amount = amount, }); opp::attestations::DesyndicateLIQ msg; @@ -1526,8 +1572,8 @@ void synd::check_custody(std::string_view path, const liq::currency_stats& st, s sysio::print("sysio.synd::", std::string(path), std::string(shortfall_verdict), reported, std::string(outstanding_label), expected, "\n"); - // The key is unique: only an admitted message reaches here, and admit_sequence consumed its sequence. - mismatches_t(get_self()).set(ram_payer, mismatch_key{.chain_code = chain_code.value, .sequence = sequence}, + // Replace current evidence for this pair; never accumulate historical shortfall rows. + mismatches_t(get_self()).set(ram_payer, mismatch_key{.chain_code = chain_code.value, .token_code = st.token_code.value}, mismatch_row{ .chain_code = chain_code, .token_code = st.token_code, @@ -1587,9 +1633,8 @@ void synd::deliver_parked(name account, ChainKind chain_kind, const std::vector< } if (balance > 0) { - action(active_of(get_self()), LIQ_ACCOUNT, transfer_action, - std::make_tuple(get_self(), account, asset{static_cast(balance), sym}, - std::string{parked_delivery_memo})) + action(active_of(get_self()), LIQ_ACCOUNT, settle_action, + std::make_tuple(get_self(), account, asset{static_cast(balance), sym})) .send(); } credit_owed(get_self(), account, code, banked); diff --git a/contracts/sysio.synd/sysio.synd.abi b/contracts/sysio.synd/sysio.synd.abi index e17cb53312..a7c02ce7c5 100644 --- a/contracts/sysio.synd/sysio.synd.abi +++ b/contracts/sysio.synd/sysio.synd.abi @@ -125,46 +125,6 @@ } ] }, - { - "name": "desynd_key", - "base": "", - "fields": [ - { - "name": "request_id", - "type": "uint64" - } - ] - }, - { - "name": "desynd_row", - "base": "", - "fields": [ - { - "name": "request_id", - "type": "uint64" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - }, - { - "name": "desyndicated_sum", - "type": "uint64" - }, - { - "name": "total_syndicated", - "type": "uint64" - } - ] - }, { "name": "desyndicate", "base": "", @@ -307,6 +267,16 @@ } ] }, + { + "name": "finishreturn", + "base": "", + "fields": [ + { + "name": "request_id", + "type": "uint64" + } + ] + }, { "name": "import_credit", "base": "", @@ -439,20 +409,16 @@ "type": "slug_name" }, { - "name": "syndicated_sum", - "type": "uint64" + "name": "queue_epoch", + "type": "uint32" }, { - "name": "yield_sum", - "type": "uint64" + "name": "underwriting_epoch", + "type": "uint32" }, { - "name": "desyndicated_sum", + "name": "retained_from_epoch", "type": "uint64" - }, - { - "name": "queue_epoch", - "type": "uint32" } ] }, @@ -483,7 +449,7 @@ "type": "uint64" }, { - "name": "sequence", + "name": "token_code", "type": "uint64" } ] @@ -678,6 +644,96 @@ } ] }, + { + "name": "pruneenv", + "base": "", + "fields": [ + { + "name": "chain_code", + "type": "slug_name" + }, + { + "name": "token_code", + "type": "slug_name" + }, + { + "name": "limit", + "type": "uint32" + } + ] + }, + { + "name": "reconcile", + "base": "", + "fields": [ + { + "name": "chain_code", + "type": "slug_name" + }, + { + "name": "token_code", + "type": "slug_name" + }, + { + "name": "reported", + "type": "uint64" + } + ] + }, + { + "name": "refundreturn", + "base": "", + "fields": [ + { + "name": "request_id", + "type": "uint64" + } + ] + }, + { + "name": "return_key", + "base": "", + "fields": [ + { + "name": "request_id", + "type": "uint64" + } + ] + }, + { + "name": "return_row", + "base": "", + "fields": [ + { + "name": "request_id", + "type": "uint64" + }, + { + "name": "holder", + "type": "name" + }, + { + "name": "chain_code", + "type": "slug_name" + }, + { + "name": "token_code", + "type": "slug_name" + }, + { + "name": "chain_kind", + "type": "ChainKind" + }, + { + "name": "pubkey", + "type": "bytes" + }, + { + "name": "amount", + "type": "uint64" + } + ] + }, { "name": "setconfig", "base": "", @@ -752,6 +808,34 @@ } ] }, + { + "name": "sync", + "base": "", + "fields": [ + { + "name": "limit", + "type": "uint32" + } + ] + }, + { + "name": "syncenv", + "base": "", + "fields": [ + { + "name": "chain_code", + "type": "slug_name" + }, + { + "name": "token_code", + "type": "slug_name" + }, + { + "name": "epoch_index", + "type": "uint32" + } + ] + }, { "name": "synd_config", "base": "", @@ -845,6 +929,14 @@ { "name": "queue_cursor", "type": "uint64" + }, + { + "name": "underwriting_cursor", + "type": "uint64" + }, + { + "name": "sync_cursor", + "type": "envelope_key" } ] }, @@ -907,6 +999,11 @@ "type": "dropenv", "ricardian_contract": "" }, + { + "name": "finishreturn", + "type": "finishreturn", + "ricardian_contract": "" + }, { "name": "importdone", "type": "importdone", @@ -932,6 +1029,21 @@ "type": "onyield", "ricardian_contract": "" }, + { + "name": "pruneenv", + "type": "pruneenv", + "ricardian_contract": "" + }, + { + "name": "reconcile", + "type": "reconcile", + "ricardian_contract": "" + }, + { + "name": "refundreturn", + "type": "refundreturn", + "ricardian_contract": "" + }, { "name": "setconfig", "type": "setconfig", @@ -946,6 +1058,16 @@ "name": "sweepyield", "type": "sweepyield", "ricardian_contract": "" + }, + { + "name": "sync", + "type": "sync", + "ricardian_contract": "" + }, + { + "name": "syncenv", + "type": "syncenv", + "ricardian_contract": "" } ], "tables": [ @@ -957,14 +1079,6 @@ "key_types": ["uint64","uint64","uint8"], "table_id": 59616 }, - { - "name": "desyndlog", - "type": "desynd_row", - "index_type": "i64", - "key_names": ["request_id"], - "key_types": ["uint64"], - "table_id": 52931 - }, { "name": "envelopes", "type": "envelope_row", @@ -1001,7 +1115,7 @@ "name": "mismatch", "type": "mismatch_row", "index_type": "i64", - "key_names": ["chain_code","sequence"], + "key_names": ["chain_code","token_code"], "key_types": ["uint64","uint64"], "table_id": 57982 }, @@ -1013,6 +1127,14 @@ "key_types": ["uint64","uint64","bytes"], "table_id": 38854 }, + { + "name": "returns", + "type": "return_row", + "index_type": "i64", + "key_names": ["request_id"], + "key_types": ["uint64"], + "table_id": 25676 + }, { "name": "syndconfig", "type": "synd_config", diff --git a/contracts/sysio.synd/sysio.synd.wasm b/contracts/sysio.synd/sysio.synd.wasm index 4fe7756df3346fe636bd4866926172081137555e..cdef0c25b788efa08593d25b465022b92ae80769 100755 GIT binary patch delta 37508 zcmchAd0FE@-X(KA&^nOeRR%Z~Mp3mbv%c_1tsL zJ?GqWm%Q5=+xSGRz?!w>TxD67YInxpSg`N7SZYNI|EU!-BaErkOtk_(7XI1t8DT!Y zqAbRU!(hdYOK(+TDWvUvlZ9M>8s+pzcZn@ zfSK;%##ytG-8^^Uf*G@CHP5mN^&?Jcsuwf+psl&HFPyPx@!WG~EnYTbUgI*WH!}*D z(LCcURBmoGz2upsOcBC?BInPVzqHX*%0)`!;#o@?EthQ+p52HR7S3**an7tI=UCEV z4tiS9ymYRWim*u0l4VQgE}SuI$&$v!rmR)uJmfs!SUJkd$x*6L-@biwRAEkGKULV@ zi4L%%_P}V7io~44;z1>LY1!cN3NMkYtWr6J)g=!8s~J*L>%{G9+bJAcQ&U)D+rw(? z92<=|c7YmR;~*>?n3EdX$0w*u8gU0dPGKJ;*rN(_@Y{c% zLZ@zD+qU;XA2~(Nel?C`J0nIQsB!GNnxYZa``4g1r`*YLa_l}i92=nUKQvxgi2rB)v+-KrU$*WP8ui`~F+1kF@zQz8&9oVCG+8^6> zf46tppV*(;pWD0bFYNsHu82LL4~dlw-7?3seE0mMYDxkdTjuzdZ`Ilx<|G}@YVvzG z`HRj=T4Ru)TVwr^uY=f;eb%9vC#k-5C_*HE6sxwMp0ja4&axuAeU84dxI_)uxUTpm zrC!k&m&{Qs^&d(`;kT%?48NmF55e#J(vl%vR+E?S@3xz~0^e@-cW$+sYHh2?w&Et) zFJ}Hqy}oo%=}Oh)yG=taE7^OjV}<#-CjT92v7&6BB9!XcqMlJ!9iJYi<@?+94P}!D zOu@G`)Lzvv)|rH8r-0F+KPs!*uij*>M%Fk)+a+sUlRK%vj(gTn$4c}vC03$D9inym z@WG?{*P6t&Nc0e`B@~{%YH(Ffu}5HwH$FCat~!DZc*@Ihr+WDft^Dutr*7SEtkbHz zd}%!ie!CFy+_BCg1UX}!a}XeLCK?F`r{`6as4jhF#l8bvq+%d#=r$Kom)+(z=@%+K zvadZ{Kjm$(+mF!aCg$0<*&Bb8s8sIFTP-&RqOW`oaQK_H>cYwcny$4~BvonoZT7rm zw94|L4c1tv){A-wJOuVw$3*}ra=oa(11OIA9)EJ1+*9p(yWWeAv{u?j6oP#cF)upQ zX|+9P{mF$YZrbWnrm^jMW@UN)c4+{0Pt(^_Hrs9XO;snW)AT7-AKEJ?>WS5h?6!&e ziRx+UJGxiRYV}?HNX$Xj|UDy8P_K8ABcSfNFFi0{5kn<7GZSva<4BZe5 zj5GC(Lx<^8h8B*T1ss06R>!}rHLicBCZpBdI- zcWvERKYX(qykpO%P=%=E_|MY&){fBiBQI04_12M3sK$*eM*TslIeJ#zq3UdXcipk- z9Q{$=cNrldp zA8GO|_uP#i?>|=Ax7vEx0Y|Fk`n&_KRyXT+#tzc`#!OUq>+g;^9KUyusmuFZISOWj zO6hM7Derx=LhIXrN>Dx5l^V`HS2_B?35RoPC1q|{`qByeN5JQnequs$VlS^(^1z`V(4++^GLD&u{hc+#==^r>$73p~g9;$wC zCf{AAPZ&Q#{Y5`Eerw6Cc6^qyNv|%@%b*N$)N)_ZZ3o}0cIq*QOj7MSb;!Pjw>l+| zUUsb=aotSMkHPK6u@kD49sEh3erOB&ee2MX_AN@U zeqoTVte;@tW6N+_>nn@zu^|J8hUmH6#_%~R_ZIzB{XT`as1oKN#c_YAKd<*x(Z*sQ zD85_QHsC`a)ljzI**1w53N0ISw~6U%ZG;5?P!O#$AE2ZPZahi#I~lYjm$4AWb*=68 zNw8R(4L%0%SQFzn+UcgdBJ+|FBnuI@5z3G;Ktz%(i%m$fb%*8k2IE9(Z7J(DK?Cd< zSDxZ*KqIKYBo~;I20NM3nc)gH7=97j4DH+@)4s8Hvpi$6ZuggvdbC%#>S_QxKXJ_ecZ&y283EH=1!%=T}{-* zZ*z2V(ur!3K7Z0A2=kv#+K@k&z(EeX=jmlf9pJ#y(6y6)n7YZ9`4%GwwyJqa5)Y_% zFYs(njj>%&!#cKMj}K1FmoP ztGuvB)0r1`lhwmO=>jcuBfUsBIpJ}`f6f;{M3ZmIt6ar>(>-FDn4b$c+?Q^h5~ zg8#JLA=bW zywB#^c`2I{aCKB@$bPB4(qvmj64W+vRw5CsPlW54j}ImRz%yBS{4)6{$zoeDjTvqT z!?m2&V2afYcTiNY1$1TmtD@nT+fwS+adJt`u1E%u?T8YJR}rF7JGG4oQs3V$ZJ|XV z#$938^M9T_(pqcFNNg5xLx6$8Rz>uRw1AzObU0oIatvmV!wd+@BNHC*Cx=^szD8d4)9}tF90nFTOGr5vvI2tN-+`6p1HZT2-zyJmPb-1IFKcqU zXarc4;ZP4M{$d6+^U|mSFPHqh_tC=$W9{RC5nP2!mQG;|CDn zcXybOkx^2{cl}%r!73Ftis_G!uCuRDI(f|Su`Z+r@zGC07)w{Gge~#_iO2-zkO{5; z7Su?qm`gAJgV%KZu{G*E{opYZPF)n{82r>WC2~k|sodQ%#JuOlPGevHeINVjTaVo* zbp;Rw021IwG?s|6Tfa?xi(R0U`t{%zSkj(dnuubJ=*6ZA6}b}WMv4~|DuviXUQfP8 zH?4M))(a1^?|feWYGHNpo6lQrlo%<%JOWr~6j=Gji!7qVKDVb>(?=#t#30D#6(PH0mxs?-2XPN(|RG&Py`7;-KO^t?qweh@Y`|bn+`iFv>oN@pmO`3$Z_o^rpr2TsN#6sO$e3(e9Eyxr3m+Q1Vpc$+*lzp?7e(Ud>&X`#JA}$c z_`h9IK*QxE{ET1sTC^kc1wu1;WUiVQoYY&@^t}$^%8LIuy|SW}pkG)}s#fVgFG!@S zA-Je2jKL0j9M3OL733a>|_*DtAinytnCc3X5GRNRyM{Q z@wZ^ir=gd~NNcqbVHBYe_bL=(k!}_>g^?v@Fx(P=;RlKmQ16;r27|5&fVn209qME$OOnnSDN z{9PFj-7|m}%BwXF##af47J;<5JwMZ8C!^r{mGQADXM?EEp=c0;N8I0?$L2bv=?yte z?vKDD1ZgLgZOO~h~s+5ym#!Mto>6(7r zn^e*nY2`|@{=0cJ%xKLdV$&cdp}f2D&;r7@^TZ&_5qX=0yk^^ZutM^VDus#W#X!Kl?F%I^<)$_@~&PzZS0_~Jfc2_##kh6jB(gqdgrw2yh~I9M5wiQ=<<`s4BbKg zW=%1C01U=WV3Jsp#?uB=F7T~eP8v94hhq$;t~{DPESE6-Itk3ne6C`EB?fHILp`An zOr|c-3BBae)?@4gOaRi5WjT zr2>=qQl*bPd31{Gn4y2tDHR*3fRvE22&@=ep_pSN4m3aftKbAk0YrNfI}R5I2`&p+ z03<3xbbDN)eGuIQ1FA1V)5^ZvLan_MzkUbWS`Q>A zatlBWOrqQb*Y3)F3X?8T>T&8hT@(o-C{oUL$^@)6*D~yYQ~(=Gb-W`kgH`Qz_+%xe|isHeK6rd8P!U|y$o`Q0|15byIX|Y7J%O<r5mZSfnPG>Y(F%9z-*xbTwWO!bG%-LjOn&)|msSpqhTfDsWEs2Cz z?p3rTDne?NOaY|8LK6QfF?SgNfVE(n1llTTu-xJJ-hyw$y$XJdYl7~HLlae#y^=(d zU2lMw^8{1U7~9EeCa-mtd)3nx8w9RGD@m`?!`_;-x{|Cp)LBKkuX1!}(||!Mon!^m zx}wXIiSCZPgkJc)fml|eN@&X`yozb-mrp58Q~_^Z6_^4uw7=(>g+n=f*|5_OqAgJoB>VsS%TJoF3w0({WJ57&w@{OB&3Q7dvI-)H+5;{1=0# zY$^39Ol7`dj4+017H@&##QY&A^#iwofX3^PLIS5S{=4PRZ(>7=+C6(pQ^kkY!UoQ2 zjD2SWGrPcd-5U7zq^j8tqw!a2coq6cECwp)BtF)%6@+V|UZ^Ns7;exFDcB1q0%c&d zblLvScC^`@Ej>%r03p!`Q+)+akTabvEQKRor$*hYfYcT1(Sb_*Q zdd3)85wY1K*~)}`Xlysg{ZN+}B<_rcZKPD(hZzKgPp1+cuK@}pOEj0X zP)K$soLq`;psuzhIoKQQm7H8U9nIPl2_GEKtzUG`i8It8*OOAn+-*^S8IhV0%44#u zzUG$q-g`wu>}|npYG_S$D^E->QU|tyaNa6#9`z~3|H^RYtwvg_nR$_(GcQ6B50Gdr zwzD+djP$d2$j2CaYk^0*3}V6k8QP~-=EMX2VL3qc8o7-ModqJ|NvB#gm6d}gXhnLK zpu!+LS+bnlgu-AQ7&Vm=b0Bj=O*42JWI}tyE1gmZp)LBQ0JFB%ux5(=jI=uP)A(FF zMfTPAhETZNGYIv}bf=Uv)4iJjKG1-P3$q+)`Q(^l_jP5E+EyUi4>_PZ2oDTU%#Q8AdfFtTQ%UfND#SjiCJiG_c}VqXC}C7nx3%NGp)fXB7-rD}R@s1|tpp(5 zpn7&e$Sf;4Hofrl{;`$$G*a|6rw>b2(8?o}sM2XC6D>6P(*GlQASL^{@2R9W1?cnK zz9b@b#8_Zpt#w@=B9Q0VJBNK8+Ev z%#d1TEVJ^{yeh9;EHh+wMHA&D5t3S9AXQI;oi>b-mPEC*UKv_xl`zkSqa*`@f(?RU zKmd~6DNKNimLFdA9%dR0MSuW0(LzI^&_Wx*JDg8>WAA=7<46 z2*F#PEbpG*UU_JXwWS+^Nm*%Zv2vPVu*GOV!@rsbSm5P#F>IVSu#(j+$x5_e1rdR< za=c)uO0OEz0(|HtX|y3kb6~PA&Jc)V;UdFqWTJ3kog#5aj2;X{vP9gi%GBgCVNZJg zYdUhM5Q^ob=AM>znwY~fhBmf5X=#V_te%F!R0};38cVwx6dVRZ+QNlic}T+%Al^tr z!;u)lPWW6vF0VYav|B*|#7Wm4miBP3JR$Vj4WBN^SnH!?NXJnGc&A$KjY`vzM?^+= zBfVi>wa%R}A~sCus0+>-nt}(%R3niM)rJF4-te>qE#wP{LD)GhjRj4(N?<`(8Oj-^ zE%!>M7m{WrEeVhxF`CaD!wH2>T9RiLJ=;^H38*SdxPLjPLRxrxqlK&wu;@uKdW|tuoI_MRo1ks;6Jl9?oeEX3&A?DypoE^athG2A_?nA6?9pSD(QA%@jx6W==WynM7c*_ z2AAk%UImTp^qyNEAP)i~y|o85OF+Zfn=!VH4bZ~`=vG2vV1G-^hZy@0Y92zv7})ROnYMy5L>o&RZmfpFElXf_%%D zRi@}h8ruFAATYfKm6FE1G&xz)5Ee5HO7|SL$%3Q@%m;B!a41-;8f%%`hFGk|Xs_h; zili*twy-+n)}&+Bt~6u0rAOO#&$a71vvx(}>Gc~rj?t2`y&Rj`0`(g8-wx~Xz@V=v26 z9Q|9*11`1*er(`MVv$o@=fv|IYcrY8Te)!&-Uc=NtQ3E0h93M(cuhcz-1ls~{ z5=Kf(C|Qp4AT$P(j&C1TXg4UVU=ii!DdVv80VNA0A{caOL;i|Hsa|+?|5O)FQ^*2; zwJh1u+FC>gH>Btgp^IZZZFPtpZ}61$wCGDA^hN@3h>4-j%p_L)gAH?G1nt%|-=eAI zqggX=TTl>Y6yy@uYuy|q^R%IL?Jd{6 z_TvX1zn7~!3MZiy-9~J6L%zS^Rm5m@mnI$exv+h+c4CQj1!grsjKW|J0YW&PEn)1y zdaa$3_40OE<&f4Q5HY6{qT<-Pv;viqpG-i$GAERyU<|HTXz$Qtur_2AKx>RptVrFT zD6-f{tKE2Zg;gLx062zAc1nHQ!yLlzZVsN}1zyKeFTsD*B^<^oSu8S^ zW#k$UBu)Kf*)*Pi2+drilQrndh7HL1mQ19ki7Fs-Kqa(zvCtJ+mNC;I@PGo08yYx~ z6a|(@qM32V6G?U)j?WJAF=$h^hfc2W;0YuM^~TCYIm1k_FOga^ZaHk!&<|M)H4lAU z7P=w9SSW#YnO{pEWN~p4=aw+if8l~GPTNljugs|F3X%qDc>`2rW^D=GvpQ;vwUM@; z2)73{o$)EkD1m2Bme5`))HMB!Br{oA)=QwFI30{IPyIB*JcJRuBCJ5aHl!4$9oDe; z9t!_9)Gg&8Vq}6>U$}JRy7ymyGP9OWi&3*O%bEq@o>iKWp*x}CY1PpmWe=9^Lnc5& zbBfWPhVBrUH|QJZ4@{vZU}ia43#PqVvbIKIxVENd`Z*>Oc`xnM!qU3iCE8h#<rmUE6YMD&jUcOe>~x72 zF*P?-dZF^SLLZwAA00qr?;MZV18B>4u*FDQK$Z%UdU3eLbQCNN51za85)KL=Li>SR zLs2gWo-J%LCJKNI=5fyvvL?qXoR|_FO~XSB705-0Fhk$6e=HzD+x#P`P(2);+L_N5 zGC7|hhsZ|GCp_H7ZRV^o%7!z?ncw8jV=XbM{PsLK3Ia>V@~jkg5e;XIx; zB83(G&x=g8PMkcr&_uU~-L~3OFgIJnq6$S(WhCm-h)g4Z8!2cAj^k;-7;U_BKr#d~ z5hz$1h)!V?Q(e?BPB;NQB+ZgfSEiXo7O3C-ncN6gE?3-LgtW(i<3DjUHe+B zPBXR90S;1ES@F%v93<&X-*zwBmy%27jwiqva&>fB?);Y>p#)edHK2 z{3YZH97A^8+k?wIHK}YfJ%)Q9SE>&ng-n zP;0<7&c|TZ%8?m4*VBq`e|rk2S|WZMth92vH!TkwWtQG?lv#RT4W&gP4UL43AwMF1hkybvBhS5n_<&#~ z4ybUQmBQW^W3U%D88{bDl5TH#le3o|~{Ruo#8C zADzg`EAx1l<%HB?v+zXu)p^Xuatesy&~OSHxZ5k}5r1`zAaBWl+y?eT4`^nsteerp zcAR3Sf<&|n1~8)7ppnCGJ7S(W(i-tnz#VXb`!PHn=daB)cw?r)Ha2Lg0wXd8N<0F$ zzCpn~x|2AjcQ(W!LSPg}d|b_ASq8hrt2w_7d~ApqU1PZ^QAkL_SyroCA#o@?RhBN6 zE`@sCg`iw9oRJm*Bs{e?Hi|`u3EtkuuMs=Q*_pFvQ=i0`k^+8+Y1gJ}E*y}H8#fSy z-#2Fi3j=(m&j#w?>nc@aZubNes?|MFdR#h!-&U)A?NuspYt%$_YcRb=tqF6!s8Q9- z@rJ0Q``#KJ7(|^;V94>iqQR(M>cYI+AA$0H|@9;f_r2b z5)@G`Xm9y{{F6Ck_F`uE}2wFlb-NId0A; zv;s5vR!9)kk;Osk44yOuG1w$z4>~@`f)B=Y1_|Dtb@mae0OIX7;K}B;4z{c5_nf49 zTRMVs&n62VUK620fbdhql)I}|B`M&&CigBIyhnU{xO@fHqEv5}Q{Ptn7^U>bQwP;4 z7l&72=gI{y9udWOmzMau{zxH;FFEt+mS@;Dc%V^D)gP_6FcsK_&o_XjaFwfj_P8K5 zv@#OX4kgB$D+T*@xt3xIoLi}+av zFi+-94_>fI2-xo-VO?4^-!UY$jR*iq?z!{PGLnRR&zj4<M&K-?_mlxVNY1?>6|Nvsfs+)$O+$~P4=ubSes4ORiBw86$G=G97sV#&ig}gh>|IiA&cpx^<6nE z!4n2qGtra266^`80Nd&?sTi9n?pekz1YgK9B@`ZJnG)0wz@2#jM=3TAGyya4coIKH zLm-)T3Q&WU4;L=f2JUSP@x*VQ3>dTLA{QWh6! zbqKYYj)GpFDHqvQRHX>(n5XAA@0&LZw17Cc^mTn-b3^)8Kx>X9Zhl?wZuaaKUe_f{ ztL-v%gM?gP{ zP9#C@ZlLI}-A8ruqUY6P`h$xq)U~?c;*sWFaK*qL>q402;Q~xAx_Df}nSi1n0S6u! zhdgrdzJcxE_y%8=h=9TUd*7f~@7LttFOd;V{v(Lsq&6NI*r@cucxAy}UZ-XR|2Rnv zcieYWFydsjuU^$sqqgavwp0$GJ=#;wyGZhB4rS|mowwr60$O3Pk;GWjb5h6Yiq`S{ zyX^Q6^P}ckzgi)e-FHUyyw*gDS1Zk4qW?J{mi=9r@!>-iJECCB826MB!n?bjAsiK* z458^e5c+LENfeeWe>>p4JPq?XI?~z@4Jl9UV3OXA;ASCj(Sc1K*v{TkDEKb#G1~Pg zlEVcTio|eH*T=IU&A3vI19vnyUT1MYCFfXZZJ3Vb93p#bjYzBCf3v%{H4d7>!M*O@ zP;QMQotdtDJ7w(ApGh?|*=lE-Kn_hHw$A&n0K#0Tf~P8LK??zmH$ZSL00rfGTa@a> zeO%${L6j;ndH}X4cjS0E^yowOtb|y>?gTIgt2~7=VyHK?y@dS?ILSoP>_(%Z)ZfV{ zP(I0Eyt^076jWwkxPL;KmcZp|o+~84J47%dwM!*qS?30FTa1}>)o%=*QeMoeg~|8ldu&XrIiNsW;W-5 z9VGC%4=ivfU4^4K9H@&Fp!IUlFuib59Q?5nn~8-aZ|6lJWW1bECWCQ!FSP*lBy(cX zx8=1fe)Z>L=@#T?Xtu)3E!ruECDPByAK|4l=0CBW~n z)9`PDM$N#_x>3FJs;l@0$Fh}^%xflZqX-NcSkLnQ$@?oO<^34kO&vZ(FS>cKKIQ6i zyhL*T)$`R)^dGNYfrnqtyard*eyY{AN2Lb-8M*XkANg12Z%+$lXy*$_|iTbM@ z;^)>t#Z+HCU0)2F;Z?n#apY+4=8399f7MW}ue-SdG5@eK{rt^Cko)n?r`WI&`RvTu zw^TBU>#j)04Ojf~b(z<0*-t&FbHAU!LoCC-|1&)&sLHa0bRgEwlfeY_GyO%dTs^DL zUo{kbwR+Vg@YVaP9wlGhe%nyBRX=x|R|t7Z%K$H__?^${zu)#-^@Q%ceN_nOrFR^q zp3~dzI2^x4cb=?%uFtu1EFK|Qedh#N3ctVedi9}R@Plzkx&H^hSHI9#-F17yKfyp+fO|2q_?{SRH0$5kGW@}`h&jvSJnE7dn)Wd*!tm{OZBJscp?91KbsRyk9YN?A6}py z(Ct60-Y;WGiq40}8`jwiWCvWTD-b=k`)$dBol1+){iPnbW|`Wy@y0c0DSPKu{n@=E zQr|1crNNUcJ7av$D8@Ubt$3Y7R(mw)cSpoIolzY-`Hghk?JzD4dbYd5M7*0Jo^5%6 z1~<=O((S~wUIefr*r+s4a_SD~NLaHekPzp9au~+QC}`BvP~Qi=xxR#I1Vo|X1eRJz1}=G4fl??7}2G4)&WoQ z&%(YsopjX{%{6h~(5(cd!Uks02D_c%>M+D=R=!&irU$kN6@iFMKhzX}eWxmgc%s-V z=vMf~R&m{D!K7#aK4(7Nr@WN3)(MRR^BzKzXexpk0`Ze;OfG_y2Mpsob8?7g3cfSO z3vRZCVhs4{P>DVUSU9G-A)x6J@o@PV*cPP*#8^!!d_b0lN4~qARc$KSw=~(0L+w<_ z{--f=A=DcZ2bci^U&+4cH5on48{kFdbSesUVK19P7fCI+h}544zXvkXlISng?Gv7f z>C<2(iVJ4okQYKU3F$r%9UvKICl@M=dj0v?Z5d)`3Vqbd9xRF&3?v6J(Od2fGN)m9 z^#SKHi*asypf3MGbq>%$!1U$^hH)zoI;D>}5?4hh-G zel5v9Xl(!vcw*EHqyVpOwl?8u5|*N_LNemVl@?kD`(|92$*W zuW!gKyzeD$Pw)nZ44$AfA)CDo6y`SAg*`dQn09?liS*8IU$(K#HXSu9<9_A=zCHHSAocs>+j*8qn z&o#;&ZEtguxn{p7mxm!hY5nNB1LETCgl{kc%kATJqn$y?sQ%_p)AaK7L)D-3ee3se z{;Ep!%PWiZ&h;Ce4-kL&k-_@ehkvU+(04!5X8+Z^y>`ro-ymVgqvh(a`p8E|6rvLQ zfGl&|`*rG3KB>0)(f#xPrabBw$NfA5rdjW~;a+=MlUF2ZyKr`q4J^k-p)v zhN^cXb_A-18?K>mz4M{sHrwm+*s z+%!@BQjdB3dG+N@KN>&i#p*nBu7lc3t#I%!hp&XS=_x-Nm}O)v`q5FT?Q~~s2T!+i zMHXpLC!*aPvvzL%ozbHdyDQ1?bKH;2tp<21DX>ZSMF+Mi6}nMV}aVY?Gi9BTSivtqteo*+SU?PwYx;t`{6Rld!X zc{vL_?6ErGvH?59fQ@aMR}&H4i74E194CG^2(<9K{0RF$(rLI%zZB2sKmeI-En#hG zhK=pCeL!Iz^lH#ict2TE7~T>=>E!p9Fbj>bI>-F00=E;mQVV>Z# zj;20^I^wucE3NJ63vlGQqONDH8Cd)0ph=vh#JtF;Pml@;l#`GOTvZCMfrMO-$Wxdi zN{PE1S^7h20ugzo&~XVo<(jaeRN0BX^NB$zY@+dPI%$Vvf>h&}GmZd=#XWE!u7L35 zOBIwNb|BuH!&4U^)c=rAqGND5PpN`3_x_@8EDQ_4>sH_V4(LorSPW<2l1eog<*1Cfpc4& zrVXi(=izOV3A%{uMcBl0@L>@eH|>C_yXTyHHu|RQBhJXu1}a!+=5&#Pj$dR+FgisJ zP>{y&Bt|K@=tgnGu_6M^Sl&X8nMmJro_oYA2DF_5rdutVIf1rt)In6EbeIr&nofu# zZkkYNhJ6^543h{e12%Ytus3SsoRvBarB$MrEFQ#9hXa)m z?)L)$i_r(BJBsI^=r`Y3CqtH~hMbkjUKk)=n!>RSDc$Q}%fiWk9`FzxRJNOvz32H6 z!W8!};@O?JA8lIS;H*fh25;s4NDx@j;T~Xmc%i$`o}&y~#r2InTTT5J&C=uXZE#(H z3$J861lKj-y8p*^Qyv~|F;v+T4{SL4KR1ka6D}AcPaw8Im*as$4X+qQ$t^2dtIE3q;-A;q1vBn5yLDp4=g=;!z{P zidT>*0C2?7B#>U_}HH36%h5VR{*d*7ngAGu1cDd zQ}^R@FTlzifo)Uh707NCDku72?1b8{LE|~|{N-reSBuXkGIACq`|?B_J=1%EWuBK~ zV8{VO5eA&^78vH4=>&!HZG;I7y;-3zr2^HQ3zIhoq)8M})8+XQ;7$YyrdM!& z*v-w75PHEgr71EP_{s%eWr+fJ&lw9!=XgaSk#o$P;&jL$?q7%iA#zcZU$XZG{Rnuc zw=VqIz?2y{<>lTv1#)CB6JB__h;#)3gad`KA=mz|z+o?g^LMh;un&^gVZDe5Fl5VB-Asb%DGGxGTVb>x`T9HJ3**hUfU z6ittuq7Ea2i#em6LQ$8B8ubL1w?P9oCqngv;%PKsx=|*O<^^QQ73paQJYd@O54FLqn5j6yz%KJ;uv@ly6zV7oWvs8Ma0V^zL<->F&AAKx0LR^m#Kr!vjuLJ zIyNH-)p5^($Xvu_OK9-&AwqE! z5n_h0qQ@*+h9cCj6%?fTUf3RoyHlVu`5v}|Afd?ceNh?-rr{8`JsWnZkfGwc$)C?X z+@6>a9mF}0p@Ekm#W4jTCa~TR=1Ap`JGyZcvPXy`A`8NSC^EA`xPvIdTPLtuY`jHR z0EUbsz&HyKz)zEh0Oq2LAQzS#0=!Sm0LKZ65MYgJBY?r4mq_*IW$`}v1go$acs3Eh zmM7JF)BL2R4gehg-nVgnNR0Q;Hg6?fxJ%2zw2&dnY3_GC{^0vB{0P_9xY%gjwDQ?| z=`Vy?|Lye=-=Z^zThoiyy)TmXTqFI4UH`9ZBrpLkBZ+$q#y=J|veZnk5Je!;9V8i5 z>6K$4lM2K2bhF`{vTSsXG5dgZ;bw=Yko}fI|B;baExkB; z*z=>++j_?H#KkX9ztJMQ_ z>o51q*?>1)yIyv5ukV-2CRkbST65gW(T^WkVRoP#9C^~;90)J3>{JLJ<^rCWITNf8 zJ*Y&E|H08BfBJzQ#u)647*9pz@WG~tzZG*)R?Eh>OD4quEj-#M-@H-4Ro>b#Tpfnu zOGZQ3^@P`*o5QXNxW__oB417Ai7u#RFXMZ8bQP6n}9c zHi9pHv8LcD<9Xz6<_EerXMQLi6k z?@;=euT|@(U!N`s!*%@|i`ABm8{dG-98g{PW;=sPzZr+W8FI;QPEjB0H-9skN!4$i z$l#*4&cd$q@7@|P_(dEJ;hD%u2;h8xRNz*TU$7Xi?T0rOy*(FySz_bGzkOY)m-Gq0 zpOAWAh1>d|hN`ka4e*k2WSryiG#UnM@Gl=;NCEg1>EMu{*aSSe2h$cO>9|*rRgGUg z>OtO0#a3*kN#sIP75DLYz*Fku7wA=_xk*Sr=#80#drRX$Z=td9i!z5K)1}8{J7_LL zXLP24sc#6!ibKh`^8kTX+@w3ZD(p5#f82GjeWRoId1rVEjx{(T!Xwa#;l4l;jy)W* zgM9VudG3Ai^u$M7ER{LCh*N`~u_Ig}{+yv4YeWbg0->QO)_18mcI4$*^~`Z?;M&e? zrq**vo__P48ujo^f4Fi8HV*)!5fyS4Pq|3)fBp#VJ5GK%$6T)0{~>+$qhHq9546mq zA==ULKR$}{kU#!o62D!4tW_`Sym#BxZ}kiBHWYpzV?jZfQ1FG<=pG5xi+&Ae-)DQyOFj;xJzF)yzzC*B*_)<=dlCxDUhcc3f5|)>S*M z!#sFw$I}F8(ffxoc=Y{iAfOKZ%PR9xrN8`3S>bN<1q{{N1uzGK=kru~E=+150Tqt= z>+$wiFYDX>dUj^u<389o95~N(IdVbc6MfMKN3qT4KA12~25#~?GBBBMOg{r7hX(6G zj)gg3^bm4WN*ILnHlL6(yYwl4yG~#7QL+B&Z{-`0%6ADIsDS)u0R->PEHs#)&RmMj=Gz zo2bwJ?nb@I?;f`K&I~)OpBXQ*tI_J{p3QN_ZmqJQ%7 zK58-ZVs)I_m*~P>12aXwvul5~OW&}of7m8n(+-X{0aK6t{a|&up7K?(-Dzq6 z?q)CeADg{_&E^0v|3`R)#*h7d9FE}C;!iuFa)qhV^sQxII<~8uDKq%4$uJgN#j+#dP+=O%8Y)zg z3NP%M0R75l5}$N8GwIWPK{gKO8Oht#Gq1O4Mh1>nKGQ2d9j%_1lCOR0fsmhlx(|d@ z&1a*(HA6nr^FAw6pX-&M`LTzwi{k!T|LU_j8QRbPd@MR%^LdqhwXI+He2RKiTHSwl zv3-rLkKOHYaxL6_B7;TmSLq$QOR~Ew&WySqqkdJGP1s+jz9@s;rSJTre!z9Gsog(N z-d~SXCc^)S-1PZi?S9!q9^GIEPZg*V{IN8Yaybva`{iWVb9(KU6%cM6UyjFlugE_} zQgn5V}WLa_RVOtPpHuV$q%T>@CQSFU?-_GdS%@x>m@hJYDJmg zD)C)*@Q$qp^xegpGV>7O^R;N_}QLR3`+GxJoxyx!Qu5N#DanTsnXZCyF`=8j~7EbxLs6{Fss zaPNXAOoBfR0*>wp_9;avxS!WkMaZ1arDW>GmgXwCC4$>pvFYT;4Y6UW*-0>W6Q|)ZQYs>FUDG>14i-i_dlgPpNr`hcB>2@d6tiwNbvj7L{!l zY{=pYjF%56ctQ$|#&`}5w=OQl6MsB$4ahpAZ5#P?kX&vyx&awpT1$7|*fvC)Fm*_9=@3=r{0is0%{URjiEQB!4|5UCa$kb`4(sVKt=J2$O^A4VFo9dr(;%)q)EMgENObVfC?Nd3 z=YkhUsWqvWv&7mGv)0U#YNCxHjekw}MYbV?+LCnW#O{@9lKNfrcz9#Hx9E3BRn#z+ zJ|adtCeXjAVowErR1L}~s(tEI&cW1<7>7{;CW@Ys>O1jgLc!bfc0DdAg?fC?+w^Pq zSM>w#BJlgt#E_1mEHK@b-hD%*`pw3&woytwqL04uG__sdcjFOQ#(r?)1rQ+zS#Yt; zx~Zj~i2|{2f?4ID7v$;J`>QMCJRb(j08I8|aKmUdNz_#T;BTWdgb&IOP1 zJP2as#6faoNxg5;D<)-};4k~BF{SU=@nQ=lal7vT+j2cvw0NHr6aH2^xN@ADSM|p% zCLDy4qEA6I04N*==ynDD4^(xuGA16VE=Xhglq^h7Ph+|(Z4JClYrs25Fn!)Z*_fWL zwEA;!=xNZ$r;k@<>b>A11n`RmkDjWoGg+gN`kjMSIa4n_SPdPWo(vKKw9iz6Ob>TU z0YIip#HEt}(DM?ecnWp|+YeS#*ut?u&)Ml5a03Dko;9P|11eKTt{%Nuc|8nv0V?aa0{G%Yy+T`za0Y;v}7`#-S{f- zj|D57Jy{JP04apWq^T@#Xh7tp*B7!Zz|4?*Obf}!oREAhSk1bjr^E~1nXCpC^>7oy zBk2Z@jshKwB(4dLJz6a|?3=8aFxJe3v^@zioQ{I|S{<-imL%>5tEWgk&F`_RTLOM zKUs|^pmZ}z$9F_}t1ZD5r>GPDe@l{^Pg6_mzzOocqjsnb!Qa25rrE2UVA6Nh?+2!Z zHHdLLHdYkmE+pR_!GzQCcLMJWPCi{#aXiaUS7%@a@Yd;O^;LO>n#g1>_|s;BC?ug zj4KxrSnQ_E9gB>$)wH#PEXQmpKl0_(oBoP`R57H*!kbamRl4Nm665)^}!Xd3m4@puu zB#CfH5`jI32$g|(^6wyvGuOuRXrdszplFs#5Sa90x}QGYEWh`jkas3@5yZlfGu3ej zH(zk3!gcwz-Ft1}RzemE#BTrvBz^;k1%b;8-34|0j>G8r7B>J1V1$g0Rp~-mElW!% zloZbcnnY9K`&1c2F|_qxrvd&3#+k5d)y{9L#ged%pO6N#_5KHNPAGG7M z_5!)lvr-d-=ogAzbZ7bpoYHibpqB4(VZnBgRT2!N|65yzlM%LOD2{ueCIiKzWD&tT zH?`2aFc^A&zz*)34RRXl4>zosHM}gWf>{iK&;m-NQIkcTe`j%_CqfzGYVS}!XdiBn zCC~}D23kS3K#T9X5c$BBlq0UBfSEz@FLc8lHRhrvFF0d6I=sDXf4x;-eg}k+qhGwE z>R>+g!xIxMgq&^$MJ`)Uwo*-LuL!J#;f5 z(arghMy4Rl^oc&c?W)@g7d;)yahaN=Pd(OHc0GMfx%>$h=HbauFo!tZ&X}X>3cPSN z{1YdBK!6>k;ZuiV*@jd%77vk-1_($Rp4&ulI^2M!>$H8^Fi zYKT2YUHtRluDR-F^^0J1lbQ*q(=AQvGy8^UaNRsPk^so<@S5ITuS~?>)(8`Rdd;1i ze%f-3lnNDtp~{j;xpFEzCgyA$FhXy5eZ?lm|;&-K4@|E~Cm ztYrLJDrO39zyIDBzg&-kI3>f9-=Jg~j=#VD{5@|rr2A+Op_qzOrFO%H-#z*IhnxQL z%O_FQ)V$PA_pwXrnR4(|w6=CWc+JGAh2T#>@W88g{2ret6kSK{FKL>Pbe((*y=wqieTz| zwNG(@e1DZ5)guJ^%BNm=D_AvORi;etWd?E?67#GycK)*Fm9KvN+9&wtkns}8Cm+lA z359rm7TtW06&bla9r==oX3ENR$|dQ@jp@i$>BtvV>C&y~q<^GSu1u$V9qfOus_y-~ z6OX@D6T_|McI>X@26N6;wF4I}ZC@LUi*O0Tp$OkZSb^{hgs5VjwRB1I!r9Bb zxeLykJG*f~b9aB4tR>BJ=g;@fY@EOF0xt#NmNd^=FnjKTv#oiJ%d(1R<#>x5o0l%m zf*l=g8g9YqWLcZxp6rLPXq06YBfK5qY=i^q@DE{Fb#dc)OB;cNxwE|`i)NkG=rvw= z&a9%R8jm^t&_eZ#yj4KpG^GA5 zxOkCTlxz8ybXf6AbA!JwQg;No7pSU%XEx3Nl4dM!oHe^~u?HeHE}jE42E#8 zkYL^U$_-vz3bn8Uc~vM=ILuJ83L$W7O+h#g;nfJs5w1lz5Mg_m4}NNHJeRzef$W?{ z@7!4z&OLYOx!(N71!p&(V=Y~r3xDCEuZFwN#R&pjWVEuG&ychUStZ{Zy8Useo?&R4~U zTNlh-FdH)=-AcL*YZkcP@a&w0i_r=o=^ojv`Q(9R7~$L{%}au!#cJT@zg(aq3S4vL zb}N2)Zt&rSsxlaG5uPh3Z6b+9UBa2Rg)uAz%Ks}3`9~n delta 32069 zcmchAd0-Vq^8ZxNyOQ@pCY&J`^Ii^KfIzreKw#nlhzANPx*kLcLO?*k0|j1GT){Is zx&s~%@xlwE;DRn{6u-N8A>OFCupTR(8(nvo-F5Z%sp@&j3*vr{KYr}`x~He7y1J^m zx~jTnvgwJ?#|@#FQM>3oG7N*7&0pLY8(2ikDK$NmTAso`T0Y&tl|n5wJ&;;{V{Nx^ zfbUT1#tYU2!>Q%Vm%F)iOkQ9)T8-&JZa`_K+(k4d7GxNFiPlXoQWf>jp1tU-+H=mC zf5E)PMwVGO@50cW3(s0qx7f($iyYituyFQ;v**^GTQ|eVa)+B^PLJ{(YhhjO3_Q7b z_WXHg&8S^mYs9!YhNovQKIG1 z=ggnCxOVouMH+{^MN1aVo_|*DqD6HJb$27rTxDKm8euZRVWQ4mx^xLsEF9}fu~auR z*j)uxk6<1JLS`(#XMrj#DlX||#}lPx6pob_n5J3LyCP|3sd8n;Dl00?3Z?o~sBn}3 z#l-(A`&O8$GuKqVShzF)nE1RZ{`6^7{3E6OjDWt`=h8Z2c#H1m3>@>C2X@lgDa%-+LeCeMjmF>m>gh*0JKxSQ?@ z(5i<54m}ilVi+=mdSv~}m}LAL&X1ak_?dDwVniTF3#fDFph@n5U~X3HCq^`7IIW+! zR;YTw1~aRI42Pn5-_W=87y6F=N`IsNZ>x7y^=|d9dQZKtK2U#9dsX(jox!Wz`JsYw zd+Kb%u`WzdeL@XWd+Hp+F_P-dx`b&P^-gZRv*7%MF$@iAoUI&dv29GsK^N4ip8LBm zgpTUG3y;~-%Q42HL~|VI?yK80A-p87cz<0qU|8X3DN(A_aO^n=v&^u`R<>z%+w_P0 zokZ`s4;Rd&2DfWr|B?o(cjA^RaLjnj&UVaUYJ-WgQPtzO!JS@s7&@*kEa<)4;Fbeu zk2z|w^W}D?ZUGB3*M|YoqYok)Ft{HBQLv@Fs(c;F7%Lb0nx0-OP8r2aDuDafu5K~dx zs5FguC*5HsIwVm|x)&Aq?`G@94QR|q)#j)2-6xC7!j{drTAMy8o=tNwd*H`lK)kRW zsWEB{>jXQyrh)%G{@XQ!hMO1L+0r9H!_5V#M~0iTP=tq@(@~&t8sO<^wi@+#vgNPq zTb|rKUc+ek_zgFO4!hlry#{uPpoV!i0xiaZspbB)*Pm6x;qIU8X0`1IcXfP@`UhYX+YRevHz%3zPAM%Yd5$Q+tlvj-5<&1jSYr$~cat5oQSNfz zDJ_ZZ!qkEo!#Y)sbAK#dtoD(6LD|W4s{3-;*J{O4?zZv;>e{2+{uNW{H1~>%CiNfU1dhkjNcTzlRXE{5-=tj9BN28Q&bNfEPCzQK!pOHHq$el3w_Cq`u`^j^$ z4$s|~oKg@3+JI(fRhs)L0qXAHE0inUezMq)GwaM810$_uOfj2m)FbuI-5SMqqy+u5+(@53?#{}bs_91_>Zyl* z_NfuwHp{&b!0&?SW;y$*-l}y^>a$F3+wQ*E=NzhaNAz8$p5MM{OW!S2{PMw|@CZ|x zsd%<~P1PfGjytOV6EtJfU;BSZROjxl9!oRb{DH^Px$ew?Q|Q!9PY&!$sabZ|X$sVr z8PPAH8Ce_}Ar4`PLzu;3CCfAj)xf36k{q@b3vrm2QCNgF>_BL(*|>t1sn*XWbz$ow zUq5W=ha*@%{^OApfK(Z?3Y}_8C5)OBliY%u3oK12Rz*__hnrY#`bIu(1T${rr*GJ} z5z4rMVHq>Me1wyNqitA>b0hI|7en}M!#!{CoPs&F5j{#z-kOsjR)bCeYucK->D$4> zNgW`!@6e-YnY(c4Dq836`ntfbCv-N>fKx6{jsE^>p=Z4VGY zw+Om@F!B_2o9Uk66uVPKm4ug?$w9gL;Kw7F%v>-&FF=s+yO@x zx@*Sd<@~4$z(18#0c#t1wPUHIi((we+DQf2flxKA%iaBB2Ke;YG;nMgsi$9ePZ_ri z7`!#E%KQ}-xPKU#?-n~_=rY&&w8Xu{DMf9q)17|hKJE<2K@TRH59}Mq7TmkL$(ny8 zR+!FTK2p#d0cTsV-VP+ywlM#$RQ!9bvUr-^RZG}yc)FsSrZw57vrSVTBo#iH>#s81 zFFr4Kmwnkw-A(S=FUzFq#z%|zvi6xmb(iVB{AhWukoo0yI)k;+v(sav=x+Cq+e*~> ztK8a8C|r+@U>Yi0`*QmH#My+w8|`S0FL$-=_#vSA@_$@5@x>D=uIC> z6|tL5CbPvdJkxD{sMtOF$XL$xKg*RHaL4cd(WaY^ypd>zTQUBzgjT-(z7?vG^=_TO zO1Vif>>C5_*W;(qe79!81nB!~CNyWyVwONvTMOJHCk{;oY8rWq!)BI`DKI2ns{!l$ zVaje-qSo|y5Ra{fZZbw12C7(@>1qqi8NYQzJ?k63Kd7w@w;Gwv#we=B5W$f~8Lq5) zYat5v@`-ER*G}kj(zC?M7XNeh1KeN#U|7$Q4SUImqi~Aqt?evKFvtwd%ciIsG6N|E z&jiY@1gc$zS@gkT%UoiwGIE_Rnq={qcPWR74SlM=GD zB(BsrMlPvv4^vF4SPn(eLRCgS+kDRLFSvWBRL}x9G53}Z+?+zyy3 zP1_Eg#zXyYhNxap;Ex&=1Xz!P$O%Ibvnr)Ask$bB)da@X{maxrxfjbbaZDJa#)Gak zwQ?v74cjP;2cs}GLMJfIZEW4xm(%nDCEInkbx9OATHFDXDk$GQdD39&>0UjlKV9b? zTSGnEU6Zmp_k>DvAWPUW2--kd*STLzI_j|X*r}K?P`&+_3J@e*Ro6QW=;!pSw+afF zUP;vtylGIdp@9i^u$6U}JMWl2IR%)q2HFoT_Z(B+4Y~lmL@`S(sVwVCckeNG=G?C! zWu{)3@49cE+M7J@%(*ngtv)S+>TPrBs50fOQ}xylP?P{$Wvo+>#*+XJi3mg`^fay)JZ{&M3up^!ypix@;T~>Yg$o z`21g|A;!Q$S4(qGmGvY86lnunYF{VGhRR#ctlY>>-2 zAiw*wVF$3lf~=U{zHCS+#SPv71>y#NRx64ou3`!7&mNvK8(>uMMQb)J4%D3;Sq!vE zfHs8dty@R~Xo0on1K5fxb9OUIV5=VDTiBL%XAe`Y0n~vmaNO@#f%!gE9YDkHR)N1S zP;cF=RLVzpWtgb|G#Y^dV|F56I;+FHYBhS?!fO!r2Cu3_06YpzEaZhK)8u(O0ILVG zZcz!d%8-?-oXuJFP$VoM@eo!eP;Jfv0F@jM+~+v?=Ow}#HtZvYon=SttciukW1WV4 z1Q;dkWSCeQ9a4-uLs6UEGo}yk*__4H4#N>l1e^!odf+YN_?#fnX>uQ)UY_-91p^j+ zH@NRlAC}qx0pqK?Q3^JgcM`}X0&@~kXV!U6{@nURWEk#ajHpArsv{UI3O`Vwm~jxD zpN#uY#s!nR&3FXfo*fvbKsPRU1RIcqNm;}21fH|OyLd#S@DLnI=A?Au&S5y1ZgXuJ z{AcVs%$R_cWEH~%6~*`13x)+1g6kFJ&{0en%Tum-_K*}y0o+u@ih6GV59w)zG_r}D zqlB?dv3l$daQ;9YvmOp!JQr1Pk6C1)x+5wb^0))~qC;oDiVj^+T@kF0cSYHV$0POe zZm4cRQ>r_pKB&fl%UW2pI`>x>gD}%V7-G+g*Gh}b5tXfhcn{|8%0OJW+z^QOJr2>k`fEa-KG8UoS3t?Z4KnK&X2S1~i zyx+lE17BD<=1Ai^Q=~#2Yy2RW;2D`2*7~)JdjWnl`5k-BnmF64g|Eu zhl4dteGesurNtfg2IAS8#gHx2F~@^+**q7NY#;em(dA8e!dJzs{b4*VS4QjMSD~VX z+>6#*x59{FxmX#6-b8tAG!e!UqjErE?$->wu{>vPAlL2fWw;>Bo@ESE70Ho9PBFzMh=)6X96(-O;_LY>8A({ zK^Q@&c8sv0HpPTB@nS8q`?2;~@sMp}S4UqB9l{fJEUO|B(8iXIRk4DHv>WIJ#of%k z+2l^(Vyj$0QO9gnnyp~%?SRF~(VD^(fbA7a1ddGcg!t)}SXyi2Rx{s9-vJ2L=b$;0 z!lzyFQjWigbgjlxhtm)IvETxUlBG;UyM)654FMZ#8ViGok%$GN*qT^Gp`ciXS7+y> zFube}(6fOU#7_tcAEb?-wpMtUju0)gzXM2!8ewAsj4T#a%X$J3`Fdqca6)Z$5UdpJ z62;44!K9c@+RWL(tb#4I3nj~;&=Zfef+Nf|Js1``>b&1*PDsc?=!#Y*^OHQeVrZMR zsO-kn5SwAN4wjXbRPvL#?PJ$>sM?(c5F zA*mXIc30~~N|ai6DCS)$Yf#v~)>^C?eiX~3%+8cOGjqmv2^=V%IV42S55HS6pBX70 zhygUkSl?kq#}bjkM7cBt6BVa%Wdq+sYj3+dba5hin%&z@o&al$Z#D!@tPHfXvT`caGx{+zQ#Nk++^m?2$y5k8Q7*G1;9mv!R|z&MkwZJ_bFeR1*H3e> zA7;=DdV^VZ(q~``u2Bb9J2J43os0{&*oS8(wz%If%qp%D_JDy^X$Cd{KwrC`U1?Xi ziADXoSEd#guh)|xfhj?*c-d0ST-c}O zHfa}6%VEY9FJl=%4Mso^a+0^Od+L9-h7^ zQd7Si=#?#tm#J}jb(Z?%2d(3kn*5-Q(~R{}4sw^K$&YK)^?GV2SUhf*f}y}-D`Zm0 z-?oOZ4zWViQoBzZ3jhRkg`}5bQMyy+SBCU5avRRCbRV3bohnZ+m^c$Fo~Ame#_IqJ zs0kK#Ce;}yrg{=HEuC5zFO@0SrMx3c+sSBSSfxUE#wZvsh4^CuVIlGIVKlJ|!%R%3 zL>Jl#yVQCKM~Zlk`>kf1lOIeZMKgAMMjincNj#E<%LYiXZFAIvM_?_(8WDiwmPf2q zoegk1_U+`OlZf4gl>r7aCfHq|Ij|Lr7UD^FhJsLVd{N^&5&R$=qg^gcse=xHdm3P$ z6R^GN-jttW6|nWKD_{N7v3hUnxaJRS75JpazP_8umbMD^$py|AqqgrUSqP5FZkDzR zK8|K={rln1rL->xt6+ELEr9U7JlPQ(?WCaBju;yYkt)MzZ)=W<0l_3M1&hk8&Ctdj zxMo|wX1YV`X(PfePMZ-au_J_c*u0DFBKXx&HY#6wbdIKD+w{HbJbLh(WR`)p|T1`hbp20Gjsmw3{cUDF#%cw0V+zKT*Uq z*s$ZK9T(AC>(n)JkenOA|I0 zW6{)y*${_Lw@W5J%;q{fgH@P`&7G$uieRd1j$%qJ##C5^0K;(gYFvhn0J3bdm?liF zhRLFqeo8L(h{4;GW#{U3ZCku}b2T7-$bzwrYaENqy2mvR%yaSMUkiDfa1^%>a>`=S z3?Gh-KuvcXWWop#m7?YDHkgjg6xik>MO&kDoXb)X5Jz)%B~XcXqv08T$Bc5vHRmed12mG)= zyD(~Bi6I_fNTslpq3IczHe=aQ5ItbcVCfMFDr6)W8hed0*><}YyKX5rwFVN2sR;6+ zniA6%H&~81;snc&6@4N(72vdui*lJAhE;^^%T6py+vm_y+OEYO952(`av7i?YRyJa zI*!57aMbO@K}Jz;yC`06j|C1R{9QRM#R;&W(yU%;$J3j#jg1~VasnGzUyJ~`LdnN&KHY#lv(3fCM%AjLHE^7@wZvH4*tgl? zE5g2Ar1$M2H0n0&+eHiuQ5cqcahnX3*f;UZx^_LKL>+NhV5L4C0-jJB00miTQ*hev zs=@g-FD9S844}z(Z}x$odJBNFRaAqsC>fi{Ot8QkAWTWMHLAA^ILLrriFFn)g@T6w z$&wSz@z_J3z5Qgnr|v6Q89yz~H4PhXx$do}9(ep7^pgb*S(ZhP9hYMPFjUlcTw=kq zf6U%IFLt)iv3;~DfQF!BrQp_^T4$x*dNb|T<8g6AITW41g}C)7eYc+Heqe3oqwN0-^|6n`AiV^My;_mX!U+sN0h2ea?5I5wO$g+-(;pFcO@C)sjE_N$T%O@WS@qU9Bl-@R zg#1i75p8zoo?fW#Q||5KdZir1>YKn8K4@8u619AUJrEbxiRv20FZQ$27Xityp_{)hhP0OgZOu_JKM{InT1LmSb6yo3K`{!GoCoweBNp zUr^WMlzx2)-QccSS4EPC(W{3(vu)3M@#Wh6_qq{IEzs=BGeepo*J0wB@aL5CueUfK zF96ftx#4X@deFU%e~q*g03T^Y+{zm-buV0htox5E=D6F}kIH*aMK@)e3ON-b7Y!k|%U$u5 zP3%+woj<p~dCZcjW92XC2rKg3zP93BnZMDt5d5T(XJFB@Xd-qjZ|ET!T(4 zGJb;2?x3V3+ClcGd|C4cLDP4EM94`6plLDs-+3tR_+20#(z``S+=`GB;;dPmt}b?7 zHV{8%T<_kk5p!A)kPwSk2G$lYfHVdK=*QAcjbpf*O^n7zC7FY5Hd8Vh)IN36TgA0AbPc?dHXnsed^J0 z3J*w;I5Gp{a1?p+2gJHUYbzmi*?#H~UY5eK2B!R$Oc|Xcc6ez5PqI5`IM==>ln@Fd zsSo%nnAvcF;jxQB6v2?tTy|LB~f{BL-$wB&Xa|CKx96K>5V8`aecmZsOpbGh^5VEnr(-?2n!iBa_39LZoiqAMU z03}osLL2_2Ev*n|OyUqCC`mveHnWhFz}Gi}ak^@UhAHGW1)bZ|yhP9>$hlEsa|N** zQbNw_Ab{wC?m$dC`?%SdzR5Rkh79|6s*DMKgd~7olihXq_D+GJnUa)jBQT@MGh!0A zV43~gY(PUsUQ-*BflnxQw^f8Vy{9;9iUZvsi$RNgol05QUvMTTAOS z_m%s4q(EbV$>Jqg*0UuciyPX>2D-ad8s9yT{Wg5jyCKAT&j!+&(=9q{5<;W^!Laf~ zyTw@x+fW}+#J3Xa`Tl0hnPCWmGc_fj3|pJ*!jdT}r65^=Yw8pazeTGxDVdL7L8| z1{aUvnX9H!1=YwnT_;zlGyQyzmcjyvr--8`|$68AuIIP4#Le8ZCeV9TNLskLb zsWL{eo&jO-3p%VENp zZeqh#oHYC3h6%jCn+{7Nh?&k_bqnvGkkv#$33l3K_xwkT-BtIOz<}Lw|6ID$?e@TO zq<-K0z*?g9?%;=xq89g(hf1>ULO>7N_nY_JJ0BYL-=y-ggn<_gZo$KSe4D4&&)7Wa z3{&s;(X_nt2gD{kQgJym5Tm*8czZ`voSygSFq-17cz8Q)b0=&Zj^BojhvE0>jfaUBlxBUnDyjdhP~z5xc@Tg`)%pQjyv6q#3&_v>E&XfLiJ z=huDl(cXUccJ#3^^pH3NvmPrAJ=Eq9;5IU?@%!{+N7MCg)+U`;-Q9IIJ=ArHUT(-E z=hta4eeBp?$zbZ^^`1=C(UoRflA!zLrt%E)xnQ%H$j;_5bg$d6c`4oJ{(EyJSlRpW z31H=d#~)=@Cbm@42KSgPb`CZ~wiaK;hPeMZcgdCyXtjIF*403(b?e#ce)2v@QJH)3 z6TRqucl8q|(*y3GpBRqZ-`-D-!Rr|_pS&J~|Lc<@(9-|6d+9;9)3!_ed9U9#2G9I) zn-jYKP;<_A&wFZ+n7sjc{YTd0P3|L4X)>(I-7Qu));KRX%XODLJxGhNpMcHgtR%|6 z@$`7w;ok9OxjXKeUZ_ofX0$J8nJMMI9KPtj`piXiojbmzd{CRyAw~o|O)RC2vH+`$ zn8@7od$fJQw_tid;;wC3Ld~1VasyRHt2pBU zX9eN`Z~;<$ju{W5C%h{V3Wq&#gy0J2*Tc4fXM|Q%v;EfC!Z^Za(uyjwC!8kq#bY_B z?m+$-sz}OsD4q*t8?f;{)h>j``SfTtQVn*P$k;HU!;se|qwx3*oIKCZT8}dAfMPSF zsK#}`S2EG^pg*yaCFPD`A~`Kj&q=85r{c8@w_n+LoL|rx@G%b_65;@wAOdi&KUS(0 zTUHt-PmRftc1wlENC=j)#V=t3%XJgV^u#1R9F%Aq2v8WYMpX!v!5So|oqfVEyR&s4 z1QaiSKtmmX9^UPPYlZEwi|iQE8$qWoP(#rDS@`kyf^3i&*KL9MY~DJu+O5xQe(wk( z1Ia-kn2;DK*e*bkAwckK?autd7m}0&SKeHJJ$4{#h9*4{P+=DcKPaIJ6Wtgf?Cb8Q z$*lPvT6^dzgJcQbcN4+W>>hSdPIS?|LB)f-=b>qt-OcXK*%A3%Sr+duqjXLAVdSnb z=6j+m7XXPpJs$(06*1oczvP3e-JLJn&3i<)Fh(^8khzPdK>|cYF3(cCK*x^*Ouw#n zPoy{G3oMWe9q_IIZa5M5_Rhz=z9;Zp#3otK9=B5|}Pe87UGy>~09qZpASrHz79y?v02cn1O#1(G!70E98e0 zAOI}|zUgCRL5a_Fg^Bxdm)A>&hp}?d3i-Ak^WX7lTTya8xQT<4b})S~jPy`pGw{h{ zcZTs{?fo(Q{gdK_aFA5xBrQOEmJ8k9=GcVoLUvaazc+=8_kp6R z?$Q@3=_U8x7Y3+Z9)SG$$SkjM{Rtm*xml(2lSeI=Sz*yO8RxFV_tp_ExlhU zpop4_lil#6xt7 zs{&JAo0Ym8%(A%-?C=YB0KfdoRO%K+6XWjIFn6YL#7)f%fyD5dOa~BH#qsY}M0k;N zh=&oglzkwmBP-YJ#VmCbze;0*1_9_fs7OS9c!x>jITBeH3<19IY;1Y{0$Y2VYt&SYUuusx(N4r~Y}6RtN9>?KhOxM-3Y zHkuqv3bh53LN*MfWW96XIaak{fNTNa3&JK8}u zr&C4>vBkzizUE~#S@Gr%fF?{tbUaBWHeIIO7D>WXI#9vw^LAkhTL7z9i4d6%VqH2| zB;GBaci3|jqOh_Rx`?uc(9YHR4PyLL=W@D#kJa^zwCaN-?nrA}Jim*z?KxmpxLx6f zksZPcSE!Ycw|foEB&LFvmWG$#8Kb2sb2cCtGbGM*gFVcmD7--;fXowT;5<*Hy#S!I z4W!pq#*kq<&!_ort>8cU zM?U~)npl!kGt)Lx0yQnNt9{}iaU8)Xd^$kHWhI{XF>c^0hQWg10thoTdF3);LEl-r zh1d0TR56Q-b;}M^7XS|gEZQ;P@ss|tvVIHL3=19!ptABCrZ9l>rbrreBHO}D2gF45 z>VO-9=#Bvw1l=d|JzjP3EGz(CSg#dE|fk#`)kZM@eN zx(_p5h(WND#kUHO!=I|wj*)tj+{jJ2rld)I0=y1b{@Z77s(V5{uD|F0l{MWIk0$93{*)$ zDx>(I3cpy_Kyo}JE$aVFmJUTF7cwS_LF zux^Yv+5f$66qLh=a<%a%s|F`AtjM$Cp>_u0m=IBwgB5v}R^$p67ZiC21|O_w=mhB< z`l%k*xB?Vw#@wGHMCw22a={m0T4IGG;?%xL0C+YsGPMp6LS$<~WdDx|0UZEAg$@wV zIGz(i1k;3I-H5LQ;drWz5WaE@rU?<$gb4bCkltxRU~#gtWFd&M+%8WqQcwbmv z=Xkj|YB zvA!^PriRYg!u3qBVpFoTI!;CSjs$%B&>;O4+s<|V7?v;o8ZPXjp92f^l#99JtPq4p zB*s1B!_Fzp2$6v{K0}-Gab7_4IU5xSd=TDIdJ{zNpa^`1*&^B=Tb8gqtx&`eh=KUN zLOC>gDbbvA?82r?@~7gSI|qF2#1n^!ArrT>xLX}3o>qY!>51d4T<|mA#&n!{YH3HL z4LncDF2#!;6NB_X%VX6+QNZ7bvowTR8mNsvzBo{N9gwCE`RhQ$f$4*IH|!dPpm+?) zciAW)h*1>SP$DRhy_bt3lETaKDDk2(7kI1KLcvi-6nM3eyW%Z?JQR=&27{m~3igUv zdv;=rM|>T);?6yLU_MuC+neokSnSRaHnq`}4QXg|w!?Qk{=jE1{s!m3u;1|}#^#mV z?_t*frv0HKa4_-NI_5xerr-22uG{^l>FA;TLpaJ$F*9JYr@f}1cMoBUAJRXB^XEs^ z#QPl4<~Q=aA#h*0}oh^X@mTCxAfbEiU0l!K<<`rtLR1dlW(t}IqvMg9HU+$ z_ls}K-93MqCJlYv>EA7+6`S7p?lnQ~-oLdlaC2AI(ii9+<+# ztq0CQhT#tfx)(o+LsCv$o`3@WHZ&F=Ry#3#qsUsdY2ZI*X7pgINuOINqxy57~*6Gfx^*W zhL^yRRzCQx?rUdKpHDS65&Rix@Yjn308&&W>@OjTOVG;a?cSYzsaS3Lz1Py0Mr4JN ztO_RMa!g;GEZ_F;LF#WfVE^}M^>^jw{LuGQgt;+rhfm`mghEJhgf6kw9P4UCG@?Tw zb~fQVH#pqK%}BlVKBf<*T6?(6=TG3EwCk;__+ey;>~?SbA)g*~ANXM~NdAW(e$^X+ zZ>%OP>-M}wG&#R(%Qcn^+d}u6AJdr&T{AKluvc(m!#|Y!*^iG>!!5T_0>4jFk~ZJ+ zDYYO^VFOW34)S1tH4ww3w|Rdj+DQN8RD%MqMNt9r4c>>QQU6lj=_UYl_b8W!QV;KO zlLqDKBk;{IgiPmd1h3xp4wy7aHT;)%WPrwud5_hzzIy;lIj(2f{&TXG#_beLd&k?8Dz>cwg*PE6_6ZxUL zvS^GgbJTZtw4tZ3AzMI?vZ-4yjkRuOR?E!wC6@heZ)i4MN9TEz56ZrmO(pF(42V*H z9|s+*;yw0VZ(fv+WE>uk(nxyGYmL%;@40Ns_b!TMKK*ixh7fSb!4a7r6jQ)N_An4Z zLSHjwGp~iR8TZ?#ru)m(GVYH^&q?1N(h(X@kvg#~>)SR@`R*L5rAzgci#t(q`;^b` zM1AO8@AghKfR;;#P&IF(4c?;`^=R++qeX);xmUYVH=m+xs+dTt0f8EGX*8uY_~yb8k*#>UuPwtVPG#sdW+LOxEe)4h(Xfkcl16)`@`RX(B8Vj%l zAV04TCfj?xfClpgmQI=1w-Bqo0}8|12n3tQ51F02O3GH%gw(Zf{)?9j*RM-WNsG3#&U?OrxmD z8($1s-RUhYhL&pMWo-!!q?h&l9zR6SMxwTHCb#Mc%VEx!!p; z-d;f)-n#JHx1haTsqbma{sg{VEX(B}&HXNzgMhyEZ!Yn{y?;}JRcEJ+Pn~s=FP$Ox z6z#bg?Qn?j1vDN@>w1PDiv}gr@<)oZzUI#z;?sQ(K&72tj;1|ZmX}j6#mvb&h05Y_ zy{iZU*h-|tRw5;Y>EGmi+?%E_(|?hKR=mP{ElKyJR&ZELU`NkMZ%e>jb|vO5S23-0 z6#!+XnBlK;bQ%)S)HpWr8hD#?F7tEGKYE^xILDL(0dN65yv2R! zs5A+^eSN5qFaF(!MtB1*r!epQ)DM#XabN1{9lKn&z!n(m-KHQrZM^fle&?dea`@m;^e22fEp zI`J7JI&V2=0A&$P^k4Y60K1fR58h8&)iZK07$`4%oOtcU+1EjmVQKKaop&VMzg=74 z-8h!Y-9P`bK6M|wHzRsmc8E`}P5A&`h|S#weCiD5t{TTAOMd|p$*Yhjwq%g#!tY<; z;6T5Rf)6fD!%-75kI}$;!)?z-L8q95nJ#stGjF4YQ0%00&_B_=LZc zgtkZ`o8MalraX}TjFP_1sb%|8>~F}4w9?;Eg7MzM>H9ozm9dlu!%-07fCsUO_5<2h zoI3F)fH&v@{4wPnJO^Y7y^=t;;q+QkRpV)UO6PnGee_p!&|S2PT(w^^L_A5gPxl4q%*=VI{*qxAd;Fv zas|=AcyRL5ZaWKY(0Um0D+aq4UL}7#Gh?l!G8fA}EEb%+`HKZBxx-o!k=g}Of~d8< z4a=g%3vL?HwIFTIJ`q{S7wV8WYokAOGvlTutqBW_-{6!OObY9?(wv@Ncr3A*;8~J+ zap0S2$O=QM1j-JQjFoA@hrs0aArlfSw30I?k-Z?*T_(N{0W~6HSk0_aKoFpNaYEF@ z^aRGoJ0{3JV=4(sqDAa5_{IH@DWJPHKze-MagqW#__Xa*?!rm~1*;LCw4!+SgO(P< z=Fxde0xf~1(1Ce&vHl7xvc0|s$zXYaAk17$PrmJa8~!#y`jfZ2;Nc@TS;X&9Do;4T zc)-J|RQo6Ckpmc6ED-%k0IlOR)dk~eoWPM{J%RXFCehh;ge3A&wLZKSu5z9PZt%wV z2s718iz!Z%H$cjmrKmQOWX~~4rZL4e(!9XX(n$VSsf70!-bn!--+^qiF4iv%nb=~S z)|WW*gQM4QxH8`vLZ%LftDz0Bbx1yfOv*n==Rx%OI)8#w4$}cyq1Hf%PZ}mL2Y4Im zgdgsURfhuygec_IAqn=SlkGHF+R|Qcl~7*K8ar`d3JcE843e&98gZu zJ~+-P5(q(LdF&_^2Ot%ALyOfBkkf0MxgdRo_MCttoPd1u`zTm;t5E^Vrsv z-swZAI~m@hA#^k~cux8iyrk}8cI{>F|XG!yghKWcg8TfO8rW* z<-8w<(L}n;n>d^6l9zo^#Yct-f#_ho@hc&>Miu`w*e z{cdl`U@GappAx|EAftA-_b|L2w*BDsjBpRTB7qMrPrt(Dx4@^b7=iCCuUXOdz2&_t z{O>Ju#uN-Vd{@N8_m=;;VnWe+{%Qg_%?Nm)3M0v}IXK1F8{9Lm9D+1P_sWU*{ou-^ zciItDs4*<|+#_g6)pJUJik_ol{KhlzY_q%}VKBl$7$5m}-P#+vGs2zf;P<@eEx3>z zI3^fOJLIUaY7uMwO;jFWOH)TlP~l}Ho%g3Jt*AW6eV`rmEu_ro3eoWaE!Z(COtSxDte8#+Go=D{&p5PX~wytkAr7J9T;(WEBvKy-b zd<(bePr!ShJBX0<^h#>*9+7-U+}7U^2amN(e~)j7d#M^4PrvbgUqjQ2)1L~*Ns80Z z_Nj2l4VL|7-Vuk>g&7|UpNpp7A5Q0$^B2e=ve3O;LpT?;4X~&X-}T$_!GSybP|vii zbeW1(z+w(<%5TjOK6mV$e>Q~wqvyTr&ZY{l=tu;AE*?xV@7^P+^GRv)|CULWL4Ik6 zpteO(dF3C<_tuN5!@Ak+x=&Xz9)HQfj20cT3mUDbRkWAf zWaO@aP%;SM<@>wVGb&oH?gR<~D2)Q8{@7dT;WPBk`(V(&NAX{Pn?zOSaN&c$$? zIac6nqX0P9V;~|s>%Cb65!8LwyKx|$PIq}<4}?J9<&7IeCHaRNtW4NP=2O#lAKCk4 z5cPBCj~rUOT}AT^%o_XOP&k$N1{|ueVJ!U7jAy-;8PqN70~Sh_{Em|DeR>p)>;41Z z9LS0y&Z3u=ys<~qucFVgA!2jr?g4Mt(KG?G8Z#epn*oy$8N1FqZ$2Vk(5Z96lNmze zR1E)+_rz2hrGCI4EjfiMxjB9kmC~!;ktflRgPOZ}zdeaM(Jt@BlW0LN{$9W0190WU z#jwMU?^M-WufbK|(9)?VYxFNVnHJOQ-j^p+slS|$I~Z;*9}u%`#Uih)H@xOkXh=7^Hj27SB*TC4v*tDM&w@r!q>IXGfpsI*?fVDMDMky(vV2HoA=(SbT%Wh z@0Zlgn{pbR)OPXAX|xw{)^)!`BuIC7uw!T9x<8x%dLjw`xmp7R@I04eWp`5g=SKo4(5W z3;yB(FpCGgA7@jc_uF$Ritxdf_4ODiE4_xl&{K|{M?k4n%Vi;eT(T9s+(p~2{7w9CDbP+5mgp&sj%rQF=RG< ze}&a8rmx(BcGi8^^pzGieXrs^Z2C$In^{s}GfOIL`tBrFxNMKChOx=wukbY|QNp%2 z{1S>YGJH^+87VGH{l|qO%+FXTa*c)jE=B$44k7|80?b~J!gEsEeu?K+DtyWcYgvs0 zrn0+xxtCH7ui%nP={S^I=3PqoiqW<0e?!SXkIooK!tK~kB;1}cibUC~d9>)E+!Kvj z+eX>eDFd?L3;a_8N)V6M{wGZ$ilD2nzz#tuAsA4Sa)jTn1&ovit?>>)%JMW$z~x5f zea0+Ba6n5ey7NQO{26iovJ}|O(k%0UeoD_LV>*5h86Q2Q#3&hL`5#!5K26eC%1EqT zCkpokLl8yE49o=^!5lLJQREs#k$y}RY0#_0eZTR9#7{9JTe{_7BPGD~_Y#-@g6A z`gd#4fJ^*^f{nLazx|DCQYY|7U2%<5h0Xswa5t_I_>k+@H$3sf-Ki6EU}ZzD{y_Xy zI==Cjw_m6Np!SXpyB@fD{0T@I=iB$cdHY^mo`}C}!;gK8$GlgrLS}}&k_O}>e1yh7 zqS43dv%BB;{`I%M^UhsKr73;)!}Lg>>wAa>X_P+u?i=ol`Lef0*5}-jD<7pRd()Lq z)0NhAN{b!Vw zC^zD#7s{O|(PWhLHSpSzVO)v$UssfSQRbsOx4&V`K>6VSjD@nH+Az8p#@WWrsB}Za z11NJ)X5+uDsPjXwp`49!)Ih@+gmNCr&M0#+2%)?T*Flt5q0B>xzl&_oo4?qei9g}Y zgRMb(fw%J-n$ihjO``-&i!NBOVD1v{d=K9Gglp-}tggU%DsIg7zP*+fc=gxO?TkVh zh;bH(M7L1aPK#&N*|qZw=cye=^zw)|a5b!xVK>m=nP=DW-(o(C|Ne8`LK}nDEu1-b z{zZ&PveKSE&#s#{!=Akuf6#gH{DlVY*3InLgpuy4FpR+{-$luMYQ@NBqnuK17^k3o zu-sdB15A}jBN7#sG*Vd)G@|ceD8`^<;!Q<4*;|kIwNvd}w`a|tJHuWyt9D_X!815# zR_)xmb@R>zruh5NHvd)Vh4`b@sLZGv1uPfozV`f?Hg}h&&z_G7oIAg6k-d1nw9c%n zvlq-?Y%Huh|AM+ji|vK;FKQ>bJ$s(_uSPnhM^SG*rzNxNfI{8H=hW5B00?jKja1%k z7H%E^q#s7fqV^uj&fdK@(glZQAiuC~ZXN#_Z2Zel8pbe`$D-OmQ0_pzPg2;rY#S9^frv@)s*9{UPC>p+HX z?K7fR1-JZb4IG(*49YC1T{x$1hCQQh?(7Te7B2CQy^W3tfeiTPU4I)w+B0saNr6@@ GnEwZ~uf}Hp diff --git a/contracts/test_contracts/block_transfer/block_transfer.cpp b/contracts/test_contracts/block_transfer/block_transfer.cpp index 1240f4e0ce..b162e98d90 100644 --- a/contracts/test_contracts/block_transfer/block_transfer.cpp +++ b/contracts/test_contracts/block_transfer/block_transfer.cpp @@ -8,7 +8,7 @@ /// Transfer-notify blocking regression helper. /// -/// Deployed onto an account that a system path pays. `sysio.token::transfer` calls +/// Deployed onto an account that a system path pays. `sysio.token::transfer` and `sysio.liq::transfer` call /// `require_recipient(to)`, and `apply_context::exec` dispatches notified receivers in a bare loop /// with no exception isolation -- so the assert below aborts the ENTIRE transaction containing the /// transfer, every parent inline action included. @@ -39,6 +39,19 @@ class [[sysio::contract]] block_transfer : public sysio::contract { /// very setup steps (e.g. `swapfromwire`'s escrow leg) needed to reach the path under test. [[sysio::on_notify("sysio.token::transfer")]] void on_transfer(sysio::name from, sysio::name to, sysio::asset quantity, std::string memo) { + reject_incoming(to); + } + + /// Reject ordinary LIQ transfers too, so a settlement regression proves that the recipient + /// notification was bypassed rather than merely reaching a handler for a different token. + [[sysio::on_notify("sysio.liq::transfer")]] + void on_liq_transfer(sysio::name from, sysio::name to, sysio::asset quantity, std::string memo) { + reject_incoming(to); + } + +private: + /// Outgoing transfers remain available for setup and escrow funding on either token contract. + void reject_incoming(sysio::name to) { if (to != get_self()) return; sysio::check(false, "block_transfer: rejecting incoming transfer"); } diff --git a/contracts/test_contracts/block_transfer/block_transfer.wasm b/contracts/test_contracts/block_transfer/block_transfer.wasm index 92cb6181f57e8ae82a06a796b96a27a47cfad88c..673976424db4845b37ad1d6caad1df232ae19664 100755 GIT binary patch delta 188 zcmaDM`b1*FEFlST239s8Vq<1wX9fZGiK`=7rMa$%if`s*^km}}l~Q0-VA5dX5fGak z%O1G-5K|Q6ac3%Mi%RpuptR(9p2sYU}xD zzCpYUAaSRLGhItIAJ}!n9V8FrHXQi62*eHI1?oX?1w{C{L425M0X}YS9tHtc1)wGc LCIP9*30xfjV5>C{ delta 127 zcmaDN@j`UMEJ1NG239s8Vq<1wX9j_ZYa&^txNZuGZRTS1WaAc*QeaeI(qQ5d5S<*y z9ypnkb2_8g=7pThjEv%wmvWuewP8?T5RhPU04ie$wT{ql=fLtc0 ahK7b6S6k0N^96DzAK;c|l$?Bry8{5dZXjy_ diff --git a/contracts/tests/sysio.bond_tests.cpp b/contracts/tests/sysio.bond_tests.cpp index a4d365cff8..1a7907fe9d 100644 --- a/contracts/tests/sysio.bond_tests.cpp +++ b/contracts/tests/sysio.bond_tests.cpp @@ -484,6 +484,20 @@ class sysio_bond_tester : public tester { } return seen; } + std::vector settlements() { + std::vector seen; + if (!last_trace) return seen; + for (const auto& at : last_trace->action_traces) { + if (at.act.account != LIQ_ACCOUNT || at.act.name != "settle"_n || at.receiver != LIQ_ACCOUNT) continue; + const auto v = liq_abi_ser.binary_to_variant("settle", at.act.data, + abi_serializer::create_yield_function(abi_serializer_max_time)); + seen.push_back({v["custodian"].as(), v["beneficiary"].as(), v["quantity"].as(), {}}); + } + return seen; + } + action_result claimwire(name account) { + return push_bond(account, "claimwire"_n, mvo()("account", account)); + } /// The `sysio.liq::addyield` actions the last pushed transaction executed, as `(from, quantity)`. std::vector> addyields() { std::vector> seen; @@ -916,7 +930,7 @@ BOOST_FIXTURE_TEST_CASE(approve_after_the_window, sysio_bond_tester) try { BOOST_REQUIRE_EQUAL(success(), approve(1)); BOOST_REQUIRE(!notified("issuer"_n, "approve"_n)); // a state change only - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); const auto row = request_row_of(1); BOOST_REQUIRE_EQUAL(STATE_APPROVED, request_state_of(1)); const auto resolved_at = row["resolved_at"].as(); @@ -1081,7 +1095,7 @@ BOOST_FIXTURE_TEST_CASE(approved_returns_bonds_and_splits_the_bounty_by_stake, s BOOST_REQUIRE_EQUAL(success(), claim(1, "bob"_n)); BOOST_REQUIRE_EQUAL(bob + 6 * UNIT + 6 * UNIT / 10, liq_balance("bob"_n)); - const auto paid = transfers_on(LIQ_ACCOUNT, liq_abi_ser); + const auto paid = settlements(); BOOST_REQUIRE_EQUAL(1u, paid.size()); // bond and bounty share in one transfer BOOST_REQUIRE_EQUAL("bob", paid.front().to.to_string()); BOOST_REQUIRE_EQUAL(6 * UNIT + 6 * UNIT / 10, paid.front().quantity.get_amount()); @@ -1109,7 +1123,7 @@ BOOST_FIXTURE_TEST_CASE(valid_after_a_hold_pays_the_hold_bond_to_the_underwriter BOOST_REQUIRE(!notified("issuer"_n, "rslvvalid"_n)); BOOST_REQUIRE_EQUAL(STATE_VALID, request_state_of(1)); BOOST_REQUIRE(request_row_of(1)["resolved_at"].as() != time_point{}); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); // a ruling sends nothing + BOOST_REQUIRE(settlements().empty()); // a ruling sends nothing BOOST_REQUIRE_EQUAL(issuer, liq_balance("issuer"_n)); // Fully bonded: no escrow awards anything outside the underwriters. BOOST_REQUIRE_EQUAL(0u, raw_escrow_of(1, KIND_BOUNTY_BYTE).payout); @@ -1141,7 +1155,7 @@ BOOST_FIXTURE_TEST_CASE(valid_while_partly_bonded_pays_the_unbonded_share_to_sys // hold bond's to the issuer. BOOST_REQUIRE_EQUAL(success(), rslvvalid(SYSIO_ACCOUNT, 1)); BOOST_REQUIRE(!notified("issuer"_n, "rslvvalid"_n)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); BOOST_REQUIRE_EQUAL(sysio, liq_balance(SYSIO_ACCOUNT)); BOOST_REQUIRE_EQUAL(issuer, liq_balance("issuer"_n)); auto bounty = raw_escrow_of(1, KIND_BOUNTY_BYTE); @@ -1157,9 +1171,8 @@ BOOST_FIXTURE_TEST_CASE(valid_while_partly_bonded_pays_the_unbonded_share_to_sys BOOST_REQUIRE_EQUAL(sysio + 6 * UNIT / 10, liq_balance(SYSIO_ACCOUNT)); BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n)); BOOST_REQUIRE_EQUAL(issuer + 6 * UNIT / 10, liq_balance("issuer"_n)); - const auto paid = transfers_on(LIQ_ACCOUNT, liq_abi_ser); + const auto paid = settlements(); BOOST_REQUIRE_EQUAL(1u, paid.size()); - BOOST_REQUIRE_EQUAL(std::string(claim_memo), paid.front().memo); BOOST_REQUIRE_EQUAL(wasm_assert_msg(std::string(nothing_msg)), claim(1, SYSIO_ACCOUNT, "carol"_n)); // The escrows keep what remains for the underwriters. BOOST_REQUIRE_EQUAL(4 * UNIT / 10, raw_escrow_of(1, KIND_BOUNTY_BYTE).amount); @@ -1181,11 +1194,11 @@ BOOST_FIXTURE_TEST_CASE(valid_with_nothing_bonded, sysio_bond_tester) try { const auto sysio = liq_balance(SYSIO_ACCOUNT); BOOST_REQUIRE_EQUAL(success(), rslvvalid(SYSIO_ACCOUNT, 1)); BOOST_REQUIRE_EQUAL(STATE_VALID, request_state_of(1)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); BOOST_REQUIRE_EQUAL(success(), claim(1, SYSIO_ACCOUNT)); BOOST_REQUIRE_EQUAL(sysio + 1 * UNIT, liq_balance(SYSIO_ACCOUNT)); BOOST_REQUIRE_EQUAL(issuer, liq_balance("issuer"_n)); - BOOST_REQUIRE_EQUAL(1u, transfers_on(LIQ_ACCOUNT, liq_abi_ser).size()); + BOOST_REQUIRE_EQUAL(1u, settlements().size()); BOOST_REQUIRE_EQUAL(0u, raw_escrow_of(1, KIND_BOUNTY_BYTE).amount); BOOST_REQUIRE_EQUAL(wasm_assert_msg(std::string(nothing_msg)), claim(1, "issuer"_n)); @@ -1223,7 +1236,7 @@ BOOST_FIXTURE_TEST_CASE(invalid_after_a_hold_forfeits_bonds_to_the_issuer_and_pa BOOST_REQUIRE_EQUAL(success(), rslvinvalid(SYSIO_ACCOUNT, 1)); BOOST_REQUIRE(!notified("issuer"_n, "rslvinvalid"_n)); BOOST_REQUIRE_EQUAL(STATE_INVALID, request_state_of(1)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); BOOST_REQUIRE_EQUAL(issuer, liq_balance("issuer"_n)); BOOST_REQUIRE_EQUAL(10 * UNIT, request_row_of(1)["forfeit_pending"].as_uint64()); BOOST_REQUIRE_EQUAL("carol"_n, raw_escrow_of(1, KIND_BOUNTY_BYTE).payee); @@ -1234,10 +1247,9 @@ BOOST_FIXTURE_TEST_CASE(invalid_after_a_hold_forfeits_bonds_to_the_issuer_and_pa // Every bond reaches the issuer's claim as ONE transfer under the forfeit memo. BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n, "carol"_n)); BOOST_REQUIRE_EQUAL(issuer + 10 * UNIT, liq_balance("issuer"_n)); - auto sent = transfers_on(LIQ_ACCOUNT, liq_abi_ser); + auto sent = settlements(); BOOST_REQUIRE_EQUAL(1u, sent.size()); BOOST_REQUIRE_EQUAL("issuer"_n, sent.front().to); - BOOST_REQUIRE_EQUAL(std::string(forfeit_memo), sent.front().memo); BOOST_REQUIRE_EQUAL(10 * UNIT, sent.front().quantity.get_amount()); BOOST_REQUIRE_EQUAL(0u, request_row_of(1)["forfeit_pending"].as_uint64()); BOOST_REQUIRE_EQUAL(wasm_assert_msg(std::string(nothing_msg)), claim(1, "issuer"_n)); @@ -1245,9 +1257,8 @@ BOOST_FIXTURE_TEST_CASE(invalid_after_a_hold_forfeits_bonds_to_the_issuer_and_pa // The challenger pulls the hold bond and the bounty in one transfer. BOOST_REQUIRE_EQUAL(success(), claim(1, "carol"_n)); BOOST_REQUIRE_EQUAL(carol + 1 * UNIT + 1 * UNIT, liq_balance("carol"_n)); - sent = transfers_on(LIQ_ACCOUNT, liq_abi_ser); + sent = settlements(); BOOST_REQUIRE_EQUAL(1u, sent.size()); - BOOST_REQUIRE_EQUAL(std::string(claim_memo), sent.front().memo); BOOST_REQUIRE_EQUAL(0u, liq_balance(BOND_ACCOUNT)); BOOST_REQUIRE_EQUAL(0u, raw_escrow_of(1, KIND_BOUNTY_BYTE).amount); BOOST_REQUIRE_EQUAL(0u, raw_escrow_of(1, KIND_HOLD_BOND_BYTE).amount); @@ -1266,16 +1277,14 @@ BOOST_FIXTURE_TEST_CASE(invalid_on_a_partly_bonded_request_forfeits_the_partial_ BOOST_REQUIRE_EQUAL(success(), rslvinvalid(SYSIO_ACCOUNT, 1)); BOOST_REQUIRE(!notified("issuer"_n, "rslvinvalid"_n)); BOOST_REQUIRE_EQUAL(STATE_INVALID, request_state_of(1)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n)); BOOST_REQUIRE_EQUAL(issuer + 4 * UNIT + 1 * UNIT, liq_balance("issuer"_n)); // forfeit and bounty returned // The forfeit and the returned bounty arrive as two transfers; only the forfeit carries its memo. - const auto sent = transfers_on(LIQ_ACCOUNT, liq_abi_ser); + const auto sent = settlements(); BOOST_REQUIRE_EQUAL(2u, sent.size()); - BOOST_REQUIRE_EQUAL(std::string(forfeit_memo), sent.front().memo); BOOST_REQUIRE_EQUAL(4 * UNIT, sent.front().quantity.get_amount()); - BOOST_REQUIRE(sent.back().memo != std::string(forfeit_memo)); BOOST_REQUIRE_EQUAL(1 * UNIT, sent.back().quantity.get_amount()); BOOST_REQUIRE_EQUAL(0u, liq_balance(BOND_ACCOUNT)); } FC_LOG_AND_RETHROW() @@ -1323,13 +1332,13 @@ BOOST_FIXTURE_TEST_CASE(a_missing_hold_bond_row_is_no_hold_bond, sysio_bond_test BOOST_REQUIRE(escrow_row_of(1, KIND_HOLD_BOND_BYTE).is_null()); BOOST_REQUIRE_EQUAL(success(), rslvvalid(SYSIO_ACCOUNT, 1)); BOOST_REQUIRE_EQUAL(STATE_VALID, request_state_of(1)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); BOOST_REQUIRE_EQUAL(success(), request("issuer"_n, "oppenvelope"_n, bytes(32, 'b'), LIQX, 1, 0)); BOOST_REQUIRE_EQUAL(success(), hold("issuer"_n, 2, "carol"_n)); BOOST_REQUIRE_EQUAL(success(), rslvinvalid(SYSIO_ACCOUNT, 2)); BOOST_REQUIRE_EQUAL(STATE_INVALID, request_state_of(2)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); pass_retention(); BOOST_REQUIRE_EQUAL(success(), prune(0, 10)); @@ -1373,20 +1382,81 @@ BOOST_FIXTURE_TEST_CASE(a_hostile_issuer_blocks_only_its_own_claim, sysio_bond_t BOOST_REQUIRE_EQUAL(success(), claim(2, SYSIO_ACCOUNT, "carol"_n)); BOOST_REQUIRE_EQUAL(sysio + 6 * UNIT / 10, liq_balance(SYSIO_ACCOUNT)); - // The issuer's own claims fail on its rejection, and what it is owed stays recorded. + // LIQ credits cannot invoke the issuer's rejecting code. const auto issuer = liq_balance("issuer"_n); - BOOST_REQUIRE(claim(3, "issuer"_n, "carol"_n).find("rejecting all notifications") != std::string::npos); - BOOST_REQUIRE(claim(2, "issuer"_n, "carol"_n).find("rejecting all notifications") != std::string::npos); - BOOST_REQUIRE_EQUAL(issuer, liq_balance("issuer"_n)); - BOOST_REQUIRE_EQUAL(6 * UNIT, request_row_of(3)["forfeit_pending"].as_uint64()); - BOOST_REQUIRE_EQUAL(1 * UNIT, raw_escrow_of(3, KIND_BOUNTY_BYTE).payout); - BOOST_REQUIRE_EQUAL(6 * UNIT / 10, raw_escrow_of(2, KIND_HOLD_BOND_BYTE).payout); + BOOST_REQUIRE_EQUAL(success(), claim(3, "issuer"_n, "carol"_n)); + BOOST_REQUIRE(!notified("issuer"_n, "settle"_n)); + BOOST_REQUIRE_EQUAL(success(), claim(2, "issuer"_n, "carol"_n)); + BOOST_REQUIRE(!notified("issuer"_n, "settle"_n)); + BOOST_REQUIRE_EQUAL(issuer + 7 * UNIT + 6 * UNIT / 10, liq_balance("issuer"_n)); + BOOST_REQUIRE_EQUAL(0u, request_row_of(3)["forfeit_pending"].as_uint64()); } FC_LOG_AND_RETHROW() // --------------------------------------------------------------------------- // Yield // --------------------------------------------------------------------------- +BOOST_FIXTURE_TEST_CASE(unbacked_yield_survives_pruning_and_never_repays_principal, sysio_bond_tester) try { + bonded_request(1, 'a', 3600); + const auto start = liq_index(); + uint64_t earned = 0, received = 0; + for (unsigned i = 0; i < 20 && earned <= received; ++i) { + BOOST_REQUIRE_EQUAL(success(), addyield("carol"_n, UNIT)); + BOOST_REQUIRE_EQUAL(success(), sweepyield(LIQSOL)); + earned = static_cast(static_cast(10 * UNIT) * (liq_index() - start) / YIELD_INDEX_SCALE); + received = pool_row_of()["pool"]["received"].as_uint64(); + } + BOOST_REQUIRE_GT(earned, received); + pass_window(3600); + BOOST_REQUIRE_EQUAL(success(), approve(1)); + const auto principal_before = liq_balance("alice"_n); + BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n)); + BOOST_REQUIRE_EQUAL(principal_before + 10 * UNIT, liq_balance("alice"_n)); + const auto residual = raw_bond_of(1, "alice"_n).yield.owed_wire; + BOOST_REQUIRE_GT(residual, 0u); + BOOST_REQUIRE(raw_bond_of(1, "alice"_n).paid); + pass_retention(); + BOOST_REQUIRE_EQUAL(success(), prune(0, 100)); + BOOST_REQUIRE(!request_row_of(1).is_null()); + BOOST_REQUIRE_EQUAL(residual, raw_bond_of(1, "alice"_n).yield.owed_wire); + // Unattributed custody earns slack that can back the already-recorded residual. + BOOST_REQUIRE_EQUAL(success(), mint(BOND_ACCOUNT, 10 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), addyield("carol"_n, UNIT)); + BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n)); + BOOST_REQUIRE_EQUAL(principal_before + 10 * UNIT, liq_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(0u, raw_bond_of(1, "alice"_n).yield.owed_wire); + const auto wire_before = wire_balance("alice"_n); + BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); + BOOST_REQUIRE_EQUAL(wire_before + earned, wire_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(success(), prune(0, 100)); + BOOST_REQUIRE(request_row_of(1).is_null()); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(rejected_wire_cannot_block_liq_or_expire_with_request, sysio_bond_tester) try { + bonded_request(1, 'a', 3600); + BOOST_REQUIRE_EQUAL(success(), addyield("carol"_n, 100 * UNIT)); + pass_window(3600); + BOOST_REQUIRE_EQUAL(success(), approve(1)); + const auto before_liq = liq_balance("alice"_n); + const auto before_wire = wire_balance("alice"_n); + const auto earned = liq_owed(BOND_ACCOUNT); + set_code("alice"_n, contracts::util::block_transfer_wasm()); + produce_blocks(); + BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n)); + BOOST_REQUIRE_EQUAL(before_liq + 10 * UNIT, liq_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(before_wire, wire_balance("alice"_n)); + BOOST_REQUIRE(!notified("alice"_n, "settle"_n)); + BOOST_REQUIRE(!notified("alice"_n, "transfer"_n)); + BOOST_REQUIRE(claimwire("alice"_n) != success()); + pass_retention(); + BOOST_REQUIRE_EQUAL(success(), prune(0, 100)); + set_code("alice"_n, std::vector{}); + produce_blocks(); + BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); + BOOST_REQUIRE_EQUAL(before_wire + earned, wire_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(wasm_assert_msg(std::string(nothing_msg)), claimwire("alice"_n)); +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE(bond_yield_is_paid_with_the_bond, sysio_bond_tester) try { constexpr uint32_t window_sec = 3600; bonded_request(1, 'a', window_sec); // alice bonds 10 UNIT of LIQSOL, no bounty @@ -1408,6 +1478,7 @@ BOOST_FIXTURE_TEST_CASE(bond_yield_is_paid_with_the_bond, sysio_bond_tester) try BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n)); BOOST_REQUIRE_EQUAL(alice_liq + 10 * UNIT, liq_balance("alice"_n)); // alice is the only bond: she is paid exactly what was pulled. + BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); BOOST_REQUIRE_EQUAL(alice_wire + owed, wire_balance("alice"_n)); BOOST_REQUIRE_EQUAL(bond_wire, wire_balance(BOND_ACCOUNT)); BOOST_REQUIRE_EQUAL(owed, pool_row_of()["pool"]["credited"].as_uint64()); @@ -1431,6 +1502,7 @@ BOOST_FIXTURE_TEST_CASE(claim_pulls_the_yield_itself_up_to_the_claim, sysio_bond const auto alice_wire = wire_balance("alice"_n); BOOST_REQUIRE_EQUAL(success(), claim(1, "alice"_n)); + BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); BOOST_REQUIRE_EQUAL(alice_wire + at_claim, wire_balance("alice"_n)); BOOST_REQUIRE_EQUAL(0u, liq_owed(BOND_ACCOUNT)); } FC_LOG_AND_RETHROW() @@ -1455,6 +1527,7 @@ BOOST_FIXTURE_TEST_CASE(escrow_yield_after_approval_reaches_the_funder, sysio_bo BOOST_REQUIRE(first > 0); auto issuer_wire = wire_balance("issuer"_n); BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n)); + BOOST_REQUIRE_EQUAL(success(), claimwire("issuer"_n)); BOOST_REQUIRE_EQUAL(issuer_wire + first, wire_balance("issuer"_n)); auto escrow = raw_escrow_of(1, KIND_BOUNTY_BYTE); BOOST_REQUIRE_EQUAL(1 * UNIT, escrow.amount); // alice's share is still held @@ -1473,6 +1546,7 @@ BOOST_FIXTURE_TEST_CASE(escrow_yield_after_approval_reaches_the_funder, sysio_bo issuer_wire = wire_balance("issuer"_n); BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n)); // The pool is first-come-first-served; the last taker may be short one atomic unit of rounding. + BOOST_REQUIRE_EQUAL(success(), claimwire("issuer"_n)); const auto second_paid = wire_balance("issuer"_n) - issuer_wire; BOOST_REQUIRE(second_paid <= second && second_paid + 1 >= second); BOOST_REQUIRE(raw_escrow_of(1, KIND_BOUNTY_BYTE).paid); @@ -1498,7 +1572,7 @@ BOOST_FIXTURE_TEST_CASE(forfeited_bond_yield_returns_to_the_ledger_as_bonus_yiel // The underwriter receives nothing; the forfeited bond's WIRE goes to sysio.liq for LIQSOL. BOOST_REQUIRE_EQUAL(alice_wire, wire_balance("alice"_n)); BOOST_REQUIRE_EQUAL(alice_liq, liq_balance("alice"_n)); - BOOST_REQUIRE(transfers_on(LIQ_ACCOUNT, liq_abi_ser).empty()); + BOOST_REQUIRE(settlements().empty()); const auto added = addyields(); BOOST_REQUIRE_EQUAL(1u, added.size()); BOOST_REQUIRE_EQUAL("sysio.bond", added.front().first.to_string()); @@ -1536,6 +1610,7 @@ BOOST_FIXTURE_TEST_CASE(escrow_is_paid_once_every_bond_is_paid_despite_a_remaind const auto issuer_wire = wire_balance("issuer"_n); BOOST_REQUIRE_EQUAL(success(), claim(1, "issuer"_n)); + BOOST_REQUIRE_EQUAL(success(), claimwire("issuer"_n)); BOOST_REQUIRE_GT(wire_balance("issuer"_n), issuer_wire); escrow = raw_escrow_of(1, KIND_BOUNTY_BYTE); BOOST_REQUIRE_EQUAL(1u, escrow.amount); @@ -1728,6 +1803,25 @@ BOOST_FIXTURE_TEST_CASE(prune_keeps_a_ruling_for_the_retention_period, sysio_bon BOOST_REQUIRE(request_row_of(1).is_null()); } FC_LOG_AND_RETHROW() +/// Durable results survive a fully paid settlement and arbitrary delay until the authenticated issuer +/// acknowledges; acknowledgement itself neither settles a request nor blocks independent claims. +BOOST_FIXTURE_TEST_CASE(durable_outcome_requires_issuer_acknowledgement, sysio_bond_tester) try { + BOOST_REQUIRE_EQUAL(success(), push_bond("issuer"_n, "requestkeep"_n, mvo() + ("issuer", "issuer"_n)("schema", "oppenvelope"_n)("statement", bytes(32, 'a')) + ("token_code", codename_mvo(LIQSOL))("covered", 10 * UNIT)("bounty", 0)("window_sec", WINDOW_SEC))); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("request is not resolved"), + push_bond("issuer"_n, "ack"_n, mvo()("request_id", 1))); + BOOST_REQUIRE_EQUAL(success(), rslvinvalid(SYSIO_ACCOUNT, 1)); + pass_retention(); + BOOST_REQUIRE_EQUAL(success(), prune(0, 10)); + BOOST_REQUIRE_EQUAL(STATE_INVALID, request_state_of(1)); + BOOST_REQUIRE_NE(success(), push_bond("alice"_n, "ack"_n, mvo()("request_id", 1))); + BOOST_REQUIRE_EQUAL(success(), push_bond("issuer"_n, "ack"_n, mvo()("request_id", 1))); + BOOST_REQUIRE_EQUAL(success(), push_bond("issuer"_n, "ack"_n, mvo()("request_id", 1))); + BOOST_REQUIRE_EQUAL(success(), prune(0, 10)); + BOOST_REQUIRE(request_row_of(1).is_null()); +} FC_LOG_AND_RETHROW() + // A request still owed a claim keeps its row, but the bond rows already paid out go: one unclaimed // payout holds no other underwriter's RAM, and the unpaid rows claim exactly as before. BOOST_FIXTURE_TEST_CASE(prune_erases_the_paid_bonds_of_a_request_it_keeps, sysio_bond_tester) try { diff --git a/contracts/tests/sysio.liq_tests.cpp b/contracts/tests/sysio.liq_tests.cpp index 6a3ebb75b9..8719902f44 100644 --- a/contracts/tests/sysio.liq_tests.cpp +++ b/contracts/tests/sysio.liq_tests.cpp @@ -300,6 +300,36 @@ BOOST_AUTO_TEST_SUITE(sysio_liq_tests) // Registration // --------------------------------------------------------------------------- +BOOST_FIXTURE_TEST_CASE(custody_settlement_preserves_supply_yield_and_ignores_callbacks, sysio_liq_tester) try { + BOOST_REQUIRE_EQUAL(success(), mint(SYND_ACCOUNT, 100 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), mint("bob"_n, 100 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), addyield("carol"_n, 10 * UNIT)); + const auto custody_owed = owed(SYND_ACCOUNT); + const auto bob_owed = owed("bob"_n); + const auto before_supply = supply(); + auto settle = [&](name signer, name custodian, name beneficiary, asset quantity) { + return push_liq(signer, "settle"_n, mvo()("custodian", custodian)("beneficiary", beneficiary)("quantity", quantity)); + }; + BOOST_REQUIRE(mentions(settle("alice"_n, "alice"_n, "bob"_n, asset(UNIT, LIQSOL_SYM)), "unsupported custodian")); + BOOST_REQUIRE(mentions(settle("alice"_n, SYND_ACCOUNT, "bob"_n, asset(UNIT, LIQSOL_SYM)), "missing authority")); + BOOST_REQUIRE(mentions(settle(SYND_ACCOUNT, SYND_ACCOUNT, "bob"_n, asset(0, LIQSOL_SYM)), "positive")); + BOOST_REQUIRE(mentions(settle(SYND_ACCOUNT, SYND_ACCOUNT, "bob"_n, asset(101 * UNIT, LIQSOL_SYM)), "overdrawn")); + BOOST_REQUIRE(mentions(settle(SYND_ACCOUNT, SYND_ACCOUNT, "bob"_n, asset(UNIT, symbol::from_string("4,LIQSOL"))), "precision")); + BOOST_REQUIRE_EQUAL(success(), settle(SYND_ACCOUNT, SYND_ACCOUNT, SYND_ACCOUNT, asset(100 * UNIT, LIQSOL_SYM))); + BOOST_REQUIRE_EQUAL(100 * UNIT, shadow_balance(SYND_ACCOUNT)); + BOOST_REQUIRE_EQUAL(custody_owed, owed(SYND_ACCOUNT)); + BOOST_REQUIRE(mentions(transfer_shadow(SYND_ACCOUNT, SYND_ACCOUNT, UNIT), "cannot transfer to self")); + set_code("bob"_n, contracts::util::block_transfer_wasm()); + produce_blocks(); + BOOST_REQUIRE(transfer_shadow(SYND_ACCOUNT, "bob"_n, UNIT) != success()); + BOOST_REQUIRE_EQUAL(success(), settle(SYND_ACCOUNT, SYND_ACCOUNT, "bob"_n, asset(40 * UNIT, LIQSOL_SYM))); + BOOST_REQUIRE_EQUAL(60 * UNIT, shadow_balance(SYND_ACCOUNT)); + BOOST_REQUIRE_EQUAL(140 * UNIT, shadow_balance("bob"_n)); + BOOST_REQUIRE_EQUAL(before_supply, supply()); + BOOST_REQUIRE_EQUAL(custody_owed, owed(SYND_ACCOUNT)); + BOOST_REQUIRE_EQUAL(bob_owed, owed("bob"_n)); +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE(create_binds_an_active_liq_token, sysio_liq_tester) try { const auto st = stat_row(); BOOST_REQUIRE_EQUAL(0, st["supply"].as().get_amount()); @@ -725,6 +755,10 @@ BOOST_FIXTURE_TEST_CASE(a_freeze_admits_transfers_into_custody_and_refuses_the_r BOOST_REQUIRE_EQUAL(frozen, transfer_shadow("sysio.bond"_n, "bob"_n, UNIT)); BOOST_REQUIRE_EQUAL(frozen, transfer_shadow("alice"_n, SWAP_ACCOUNT, UNIT)); BOOST_REQUIRE_EQUAL(0, shadow_balance("bob"_n)); + for (auto beneficiary : {"bob"_n, SYND_ACCOUNT, "sysio.bond"_n}) { + BOOST_REQUIRE_EQUAL(frozen, push_liq(SYND_ACCOUNT, "settle"_n, + mvo()("custodian", SYND_ACCOUNT)("beneficiary", beneficiary)("quantity", asset(UNIT, LIQSOL_SYM)))); + } // A holder's claim and queueyield are refused; a custody contract claims its own row. const int64_t alice_owed = owed("alice"_n); BOOST_REQUIRE_LT(0, alice_owed); diff --git a/contracts/tests/sysio.synd_tests.cpp b/contracts/tests/sysio.synd_tests.cpp index 24c1e3f90d..54ff058a82 100644 --- a/contracts/tests/sysio.synd_tests.cpp +++ b/contracts/tests/sysio.synd_tests.cpp @@ -140,7 +140,6 @@ constexpr std::string_view CHALLENGE_ZERO = "challenge charge is zero; conf constexpr std::string_view DROPENV_STATE = "only an OPEN or WAITING envelope with no request issued can be dropped"; constexpr std::string_view CHALLENGE_FINAL = "the envelope's request is approved or ruled; it cannot be challenged"; constexpr std::string_view CHALLENGE_NO_ROW = "envelope not found"; -constexpr std::string_view CHALLENGE_CODE = "a challenger may not have contract code"; constexpr std::string_view CHALLENGE_ROLE = "the challenger cannot be sysio.synd or sysio.bond"; /// sysio.bond's hold bond, in basis points of the covered amount, while no `setconfig` sets another. constexpr uint64_t HOLD_BPS = 1000; @@ -764,8 +763,8 @@ class sysio_synd_tester : public tester { const auto row = parked_row(token_code, kind, pubkey); return row.is_null() ? 0 : row["balance"].as_uint64(); } - fc::variant desynd_row(uint64_t request_id) { - return decode(synd_abi_ser, "desynd_row", get_row_by_id(SYND_ACCOUNT, SYND_ACCOUNT, "desyndlog"_n, request_id)); + fc::variant return_row(uint64_t request_id) { + return decode(synd_abi_ser, "return_row", get_row_by_id(SYND_ACCOUNT, SYND_ACCOUNT, "returns"_n, request_id)); } fc::variant pool_row(std::string_view token_code) { return decode(synd_abi_ser, "pool_row", @@ -911,7 +910,6 @@ BOOST_FIXTURE_TEST_CASE(onsynd_holds_linked_and_unlinked_alike, sysio_synd_teste BOOST_REQUIRE_EQUAL(100 * UNIT, envelope["synd_total"].as_uint64()); BOOST_REQUIRE_EQUAL(2u, envelope["item_count"].as()); BOOST_REQUIRE_EQUAL(digest, envelope["digest"].as()); - BOOST_REQUIRE_EQUAL(100 * UNIT, ledger_row(SOLANA, LIQSOL)["syndicated_sum"].as_uint64()); BOOST_REQUIRE_EQUAL(2u, cursor_row(SOLANA)["last_sequence"].as_uint64()); BOOST_REQUIRE(mentions(onsynd(SOLANA, 1, digest, 3, ChainKind::CHAIN_KIND_SVM, linked, LIQSOL, UNIT, "alice"_n), @@ -1048,7 +1046,6 @@ BOOST_FIXTURE_TEST_CASE(onyield_holds_the_amount_without_minting, sysio_synd_tes const auto envelope = envelope_row(SOLANA, LIQSOL, 1); BOOST_REQUIRE_EQUAL(7 * UNIT, envelope["yield_total"].as_uint64()); BOOST_REQUIRE_EQUAL(0u, envelope["synd_total"].as_uint64()); - BOOST_REQUIRE_EQUAL(7 * UNIT, ledger_row(SOLANA, LIQSOL)["yield_sum"].as_uint64()); const auto cursor = cursor_row(SOLANA); BOOST_REQUIRE_EQUAL(1u, cursor["last_sequence"].as_uint64()); BOOST_REQUIRE_EQUAL(42u, cursor["last_epoch"].as_uint64()); @@ -1118,9 +1115,6 @@ BOOST_FIXTURE_TEST_CASE(closeenv_closes_the_envelope_and_records_totals, sysio_s // The ledger sums every envelope of the outpost and token. const auto ledger = ledger_row(SOLANA, LIQSOL); - BOOST_REQUIRE_EQUAL(16 * UNIT, ledger["syndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(2 * UNIT, ledger["yield_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(0u, ledger["desyndicated_sum"].as_uint64()); // Closed: a late message for it is dropped, its sequence left for the next. BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 3, digest, 6, SVM, pubkey, LIQSOL, UNIT)); @@ -1262,7 +1256,8 @@ BOOST_FIXTURE_TEST_CASE(liq_has_none_of_the_moved_actions, sysio_synd_tester) tr BOOST_REQUIRE(!has_table(liq_abi, "parked"_n)); BOOST_REQUIRE(!has_table(liq_abi, "liqcounters"_n)); BOOST_REQUIRE(has_table(synd_abi, "parked"_n)); - BOOST_REQUIRE(has_table(synd_abi, "desyndlog"_n)); + BOOST_REQUIRE(!has_table(synd_abi, "desyndlog"_n)); + BOOST_REQUIRE(has_table(synd_abi, "returns"_n)); BOOST_REQUIRE(has_table(synd_abi, "syndconfig"_n)); BOOST_REQUIRE(has_table(synd_abi, "syndstate"_n)); BOOST_REQUIRE(has_table(synd_abi, "buckets"_n)); @@ -1346,7 +1341,6 @@ BOOST_FIXTURE_TEST_CASE(parked_rows_accrue_and_are_delivered_on_link, sysio_synd // the chain family, an account that does not exist, and sysio.synd itself. BOOST_REQUIRE_EQUAL(success(), linkswept("dave"_n, ChainKind::CHAIN_KIND_EVM, pubkey2)); BOOST_REQUIRE_EQUAL(success(), linkswept("nobody"_n, ChainKind::CHAIN_KIND_SVM, pubkey2)); - BOOST_REQUIRE_EQUAL(success(), linkswept(SYND_ACCOUNT, ChainKind::CHAIN_KIND_SVM, pubkey2)); BOOST_REQUIRE_EQUAL(20 * UNIT, parked_balance(LIQSOL, ChainKind::CHAIN_KIND_SVM, pubkey2)); const int64_t dave_wire = wire_balance("dave"_n); BOOST_REQUIRE_EQUAL(success(), linkswept("dave"_n, ChainKind::CHAIN_KIND_SVM, pubkey2)); @@ -1483,9 +1477,8 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_burns_and_queues_the_attestation, sysio_synd BOOST_REQUIRE_EQUAL(static_cast(150 * UNIT), supply()); } FC_LOG_AND_RETHROW() -// Every desyndication is logged under its request id with the running sum of its outpost and token -// after it, and the ledger's `desyndicated_sum` is that sum. A refused desyndication logs nothing. -BOOST_FIXTURE_TEST_CASE(desyndlog_records_the_running_sum, sysio_synd_tester) try { +/// Outstanding returns retain the original beneficiary and exact obligation, without lifetime totals. +BOOST_FIXTURE_TEST_CASE(outstanding_returns_record_the_original_obligation, sysio_synd_tester) try { BOOST_REQUIRE_EQUAL(success(), liq_mint("alice"_n, LIQSOL, 100 * UNIT)); BOOST_REQUIRE_EQUAL(success(), liq_mint("alice"_n, LIQTWO, 100 * UNIT)); BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, ed_key())); @@ -1497,33 +1490,69 @@ BOOST_FIXTURE_TEST_CASE(desyndlog_records_the_running_sum, sysio_synd_tester) tr BOOST_REQUIRE_EQUAL(wasm_assert_msg("overdrawn balance"), desyndicate("alice"_n, 1'000 * UNIT)); BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 7 * UNIT)); - const auto check_log = [&](uint64_t id, std::string_view token, uint64_t amount, uint64_t sum) { - const auto row = desynd_row(id); + const auto check_log = [&](uint64_t id, std::string_view token, uint64_t amount) { + const auto row = return_row(id); BOOST_TEST_CONTEXT("request " << id) { BOOST_REQUIRE(!row.is_null()); BOOST_REQUIRE_EQUAL(id, row["request_id"].as_uint64()); BOOST_REQUIRE_EQUAL(SOLANA, row["chain_code"].as_string()); BOOST_REQUIRE_EQUAL(token, row["token_code"].as_string()); BOOST_REQUIRE_EQUAL(amount, row["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(sum, row["desyndicated_sum"].as_uint64()); + BOOST_REQUIRE_EQUAL("alice", row["holder"].as_string()); + BOOST_REQUIRE_EQUAL(32u, row["pubkey"].as>().size()); } }; - check_log(1, LIQSOL, 10 * UNIT, 10 * UNIT); - check_log(2, LIQTWO, 5 * UNIT, 5 * UNIT); - check_log(3, LIQSOL, 7 * UNIT, 17 * UNIT); - BOOST_REQUIRE(desynd_row(4).is_null()); - - const auto liqsol = ledger_row(SOLANA, LIQSOL); - BOOST_REQUIRE_EQUAL(17 * UNIT, liqsol["desyndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(0u, liqsol["syndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(0u, liqsol["yield_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(5 * UNIT, ledger_row(SOLANA, LIQTWO)["desyndicated_sum"].as_uint64()); - - // Intake adds to the same row without touching the desyndicated sum. + check_log(1, LIQSOL, 10 * UNIT); + check_log(2, LIQTWO, 5 * UNIT); + check_log(3, LIQSOL, 7 * UNIT); + BOOST_REQUIRE(return_row(4).is_null()); + + BOOST_REQUIRE(ledger_row(SOLANA, LIQSOL).is_null()); + + // Only intake needs operational queue positions. BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 1, digest_of("envelope-1"), 1, ChainKind::CHAIN_KIND_SVM, ed_bytes(ed_key()), LIQSOL, 3 * UNIT)); - BOOST_REQUIRE_EQUAL(3 * UNIT, ledger_row(SOLANA, LIQSOL)["syndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(17 * UNIT, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); +} FC_LOG_AND_RETHROW() + +/// Governance closes externally settled returns or refunds a definitively cancelled obligation. +/// Both consume the pending record exactly once; fees stay charged and refund destinations are immutable. +BOOST_FIXTURE_TEST_CASE(return_resolution_is_authorized_exact_and_once, sysio_synd_tester) try { + BOOST_REQUIRE_EQUAL(success(), liq_mint("alice"_n, LIQSOL, 100 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, ed_key())); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {.desynd_fee_bps = 1000})); + BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 10 * UNIT)); + const auto resolve = [&](name signer, name action, uint64_t id) { + return push(SYND_ACCOUNT, synd_abi_ser, signer, action, mvo()("request_id", id)); + }; + BOOST_REQUIRE(mentions(resolve("alice"_n, "refundreturn"_n, 1), "missing authority of sysio")); + BOOST_REQUIRE(mentions(resolve("alice"_n, "finishreturn"_n, 1), "missing authority of sysio")); + BOOST_REQUIRE(!return_row(1).is_null()); + BOOST_REQUIRE_EQUAL(success(), resolve(SYSIO_ACCOUNT, "refundreturn"_n, 1)); + BOOST_REQUIRE_EQUAL(static_cast(99 * UNIT), liq_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(UNIT, feepot_balance(LIQSOL)); + BOOST_REQUIRE(return_row(1).is_null()); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("outstanding return not found"), resolve(SYSIO_ACCOUNT, "refundreturn"_n, 1)); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("outstanding return not found"), resolve(SYSIO_ACCOUNT, "finishreturn"_n, 1)); + BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 10 * UNIT)); + const auto before = liq_balance("alice"_n); + BOOST_REQUIRE_EQUAL(success(), resolve(SYSIO_ACCOUNT, "finishreturn"_n, 2)); + BOOST_REQUIRE_EQUAL(before, liq_balance("alice"_n)); + BOOST_REQUIRE(return_row(2).is_null()); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("outstanding return not found"), resolve(SYSIO_ACCOUNT, "refundreturn"_n, 2)); + BOOST_REQUIRE_EQUAL(3u, counters_row()["next_request_id"].as_uint64()); +} FC_LOG_AND_RETHROW() + +/// A failed inline restoration leaves the outstanding record available for later recovery. +BOOST_FIXTURE_TEST_CASE(return_refund_failure_preserves_obligation, sysio_synd_tester) try { + BOOST_REQUIRE_EQUAL(success(), liq_mint("alice"_n, LIQSOL, 10 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, ed_key())); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {})); + BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 10 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), liq_mint("bob"_n, LIQSOL, static_cast(asset::max_amount))); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("supply exceeds the asset range"), + push(SYND_ACCOUNT, synd_abi_ser, SYSIO_ACCOUNT, "refundreturn"_n, mvo()("request_id", 1))); + BOOST_REQUIRE(!return_row(1).is_null()); + BOOST_REQUIRE_EQUAL(0, liq_balance("alice"_n)); } FC_LOG_AND_RETHROW() /// The DESYNDICATE_LIQ attestation `id` queued, decoded. @@ -1556,15 +1585,12 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_carries_the_outstanding_shadow_after_its_bur BOOST_REQUIRE_EQUAL(static_cast(7 * UNIT), pending()); const uint64_t first_total = 68 * UNIT; BOOST_REQUIRE_EQUAL(first_total, queued_desyndication(*this, 1).total_syndicated()); - BOOST_REQUIRE_EQUAL(first_total, desynd_row(1)["total_syndicated"].as_uint64()); // The next one carries the total after its own burn: 20 with a fee of 0.5 burns 19.5. BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 20 * UNIT)); const uint64_t second_total = first_total - (19 * UNIT + UNIT / 2); BOOST_REQUIRE_EQUAL(static_cast(second_total - 7 * UNIT), supply()); BOOST_REQUIRE_EQUAL(second_total, queued_desyndication(*this, 2).total_syndicated()); - BOOST_REQUIRE_EQUAL(second_total, desynd_row(2)["total_syndicated"].as_uint64()); - BOOST_REQUIRE_EQUAL(first_total, desynd_row(1)["total_syndicated"].as_uint64()); // Outstanding plus headroom is the asset range. const uint64_t range = static_cast(asset::max_amount); @@ -1595,10 +1621,8 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_charges_the_fee_and_queues_the_net_amount, s BOOST_REQUIRE_EQUAL(slug_value(LIQSOL), msg.amount().token_code()); BOOST_REQUIRE_EQUAL(static_cast(39 * UNIT), msg.amount().amount()); BOOST_REQUIRE_EQUAL(1u, msg.request_id()); - const auto log = desynd_row(1); + const auto log = return_row(1); BOOST_REQUIRE_EQUAL(39 * UNIT, log["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(39 * UNIT, log["desyndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(39 * UNIT, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); BOOST_REQUIRE_EQUAL(60 * UNIT, desynd_bucket_row(SOLANA, LIQSOL)["level"].as_uint64()); // The syndication direction is untouched. BOOST_REQUIRE(synd_bucket_row(SOLANA, LIQSOL).is_null()); @@ -1608,8 +1632,6 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_charges_the_fee_and_queues_the_net_amount, s BOOST_REQUIRE_EQUAL(UNIT + UNIT / 2, feepot_balance(LIQSOL)); BOOST_REQUIRE_EQUAL(static_cast(UNIT + UNIT / 2), liq_balance(SYND_ACCOUNT)); BOOST_REQUIRE_EQUAL(static_cast(19 * UNIT + UNIT / 2), queued_desyndication(*this, 2).amount().amount()); - BOOST_REQUIRE_EQUAL(58 * UNIT + UNIT / 2, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(58 * UNIT + UNIT / 2, desynd_row(2)["desyndicated_sum"].as_uint64()); BOOST_REQUIRE_EQUAL(40 * UNIT, desynd_bucket_row(SOLANA, LIQSOL)["level"].as_uint64()); // The whole quantity as a fee is refused, and changes nothing. @@ -1642,7 +1664,7 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_refuses_over_budget, sysio_synd_tester) try BOOST_REQUIRE_EQUAL(wasm_assert_msg("desyndication exceeds the current budget"), desyndicate("alice"_n, 1)); BOOST_REQUIRE_EQUAL(static_cast(90 * UNIT), liq_balance("alice"_n)); BOOST_REQUIRE_EQUAL(2u, counters_row()["next_request_id"].as_uint64()); - BOOST_REQUIRE(desynd_row(2).is_null()); + BOOST_REQUIRE(return_row(2).is_null()); } FC_LOG_AND_RETHROW() // The bucket refills by `desynd_refill` per depot epoch up to the burst, and drops by the whole quantity @@ -1674,7 +1696,6 @@ BOOST_FIXTURE_TEST_CASE(passes_after_refill, sysio_synd_tester) try { BOOST_REQUIRE_EQUAL(static_cast(76 * UNIT), liq_balance("alice"_n)); BOOST_REQUIRE_EQUAL(static_cast(24 * UNIT) / 10, liq_balance(SYND_ACCOUNT)); BOOST_REQUIRE_EQUAL(24 * UNIT / 10, feepot_balance(LIQSOL)); - BOOST_REQUIRE_EQUAL(216 * UNIT / 10, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); } FC_LOG_AND_RETHROW() // Review Focus 5: a fee that rounds down to zero keeps nothing; the whole quantity is burned, queued, @@ -1692,8 +1713,7 @@ BOOST_FIXTURE_TEST_CASE(desyndicate_fee_rounding_to_zero, sysio_synd_tester) try BOOST_REQUIRE_EQUAL(0, liq_balance(SYND_ACCOUNT)); BOOST_REQUIRE(feepot_row(LIQSOL).is_null()); BOOST_REQUIRE_EQUAL(static_cast(quantity), queued_desyndication(*this, 1).amount().amount()); - BOOST_REQUIRE_EQUAL(quantity, desynd_row(1)["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(quantity, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); + BOOST_REQUIRE_EQUAL(quantity, return_row(1)["amount"].as_uint64()); // One more base unit makes the fee 1: 334 * 30 / 10000 is 1.002, floored to 1. BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, quantity + 1)); @@ -2138,22 +2158,22 @@ BOOST_FIXTURE_TEST_CASE(a_link_gained_while_held_releases_to_the_account, sysio_ BOOST_REQUIRE_EQUAL(0, liq_balance(SYND_ACCOUNT)); } FC_LOG_AND_RETHROW() -// A linked account with contract code takes its release through `parked`: the transfer would notify it, -// and a refusal would stall the queue for every holder. `sweep` still delivers, at the account's own risk. -BOOST_FIXTURE_TEST_CASE(a_linked_contract_account_takes_delivery_through_parked, sysio_synd_tester) try { +// Linked contracts receive spendable LIQ without invoking their rejecting transfer handler. +BOOST_FIXTURE_TEST_CASE(a_linked_contract_account_receives_without_notifications, sysio_synd_tester) try { const auto key = ed_key(); const auto pubkey = ed_bytes(key); BOOST_REQUIRE_EQUAL(success(), link_svm("bob"_n, key)); - set_code("bob"_n, contracts::util::reject_all_wasm()); + set_code("bob"_n, contracts::util::block_transfer_wasm()); produce_blocks(); BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {})); syndicate_and_close(1, 1, pubkey, LIQSOL, 9 * UNIT); underwrite_envelope(SOLANA, LIQSOL, 1); BOOST_REQUIRE_EQUAL(success(), crank()); + for (const auto& trace : last_trace->action_traces) BOOST_REQUIRE(trace.receiver != "bob"_n); BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); - BOOST_REQUIRE_EQUAL(0, liq_balance("bob"_n)); - BOOST_REQUIRE_EQUAL(9 * UNIT, parked_balance(LIQSOL, ChainKind::CHAIN_KIND_SVM, pubkey)); + BOOST_REQUIRE_EQUAL(9 * UNIT, liq_balance("bob"_n)); + BOOST_REQUIRE_EQUAL(0, parked_balance(LIQSOL, ChainKind::CHAIN_KIND_SVM, pubkey)); } FC_LOG_AND_RETHROW() // The syndication bucket starts full at its burst, refills by its refill per depot epoch up to the @@ -2417,6 +2437,133 @@ BOOST_FIXTURE_TEST_CASE(a_pruned_request_keeps_its_pair_releasing, sysio_synd_te BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); } FC_LOG_AND_RETHROW() +/// A DONE envelope still consumes a later ruling; a fully paid bond cannot prune that ruling before +/// targeted synchronization, even after the ordinary retention deadline. +BOOST_FIXTURE_TEST_CASE(done_envelope_durably_consumes_late_outcome, sysio_synd_tester) try { + const auto key = ed_key(); + BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, key)); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {.synd_burst = 10 * UNIT, .synd_refill = 10 * UNIT, + .window_sec = 1})); + syndicate_and_close(1, 1, ed_bytes(key), LIQSOL, UNIT); + const auto id = envelope_row(SOLANA, LIQSOL, 1)["request_id"].as_uint64(); + underwrite_envelope(SOLANA, LIQSOL, 1); + BOOST_REQUIRE_EQUAL(success(), crank()); + BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); + produce_block(fc::seconds(2)); + BOOST_REQUIRE_EQUAL(success(), push(BOND_ACCOUNT, bond_abi_ser, "carol"_n, "approve"_n, mvo()("request_id", id))); + BOOST_REQUIRE_EQUAL(success(), bond_claim(id, "dave"_n)); + produce_block(fc::seconds(PRUNE_RETENTION_SEC + 1)); + BOOST_REQUIRE_EQUAL(success(), bond_prune(id, 1)); + BOOST_REQUIRE(!bond_request(id).is_null()); + BOOST_REQUIRE_EQUAL(success(), push(SYND_ACCOUNT, synd_abi_ser, "carol"_n, "syncenv"_n, mvo() + ("chain_code", codename_mvo(SOLANA))("token_code", codename_mvo(LIQSOL))("epoch_index", 1))); + BOOST_REQUIRE_EQUAL("APPROVED", envelope_row(SOLANA, LIQSOL, 1)["outcome"].as_string()); + BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); + BOOST_REQUIRE_EQUAL(success(), bond_prune(id, 1)); + BOOST_REQUIRE(bond_request(id).is_null()); +} FC_LOG_AND_RETHROW() + +/// Governance may invalidate a fully released envelope without a prior challenge. Synchronization +/// must rewind settlement so the forfeit covers the released amount exactly once. +BOOST_FIXTURE_TEST_CASE(late_invalid_outcome_rewinds_done_envelope, sysio_synd_tester) try { + const auto key = ed_key(); + BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, key)); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {})); + syndicate_and_close(1, 1, ed_bytes(key), LIQSOL, UNIT); + const auto id = envelope_row(SOLANA, LIQSOL, 1)["request_id"].as_uint64(); + underwrite_envelope(SOLANA, LIQSOL, 1); + BOOST_REQUIRE_EQUAL(success(), crank()); + BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); + BOOST_REQUIRE_EQUAL(success(), rule(id, false)); + BOOST_REQUIRE_EQUAL(success(), push(SYND_ACCOUNT, synd_abi_ser, "carol"_n, "syncenv"_n, mvo() + ("chain_code", codename_mvo(SOLANA))("token_code", codename_mvo(LIQSOL))("epoch_index", 1))); + BOOST_REQUIRE_EQUAL(1u, ledger_row(SOLANA, LIQSOL)["queue_epoch"].as_uint64()); + BOOST_REQUIRE_EQUAL(0u, envelope_row(SOLANA, LIQSOL, 1)["burned"].as_uint64()); + BOOST_REQUIRE_EQUAL(success(), crank()); + BOOST_REQUIRE_EQUAL(STATE_INVALID, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); + BOOST_REQUIRE_EQUAL(UNIT, envelope_row(SOLANA, LIQSOL, 1)["burned"].as_uint64()); + BOOST_REQUIRE_EQUAL(static_cast(UNIT), liq_balance("alice"_n)); + BOOST_REQUIRE_EQUAL(success(), crank()); + BOOST_REQUIRE_EQUAL(UNIT, envelope_row(SOLANA, LIQSOL, 1)["burned"].as_uint64()); +} FC_LOG_AND_RETHROW() + +/// Fully released is not final. Pruning requires consumed finality, then retains a replay floor +/// that rejects both old and fresh sequences at erased epochs, even with a different digest. +BOOST_FIXTURE_TEST_CASE(prune_requires_finality_and_preserves_replay_floor, sysio_synd_tester) try { + const auto key = ed_key(); + const auto pubkey = ed_bytes(key); + BOOST_REQUIRE_EQUAL(success(), link_svm("alice"_n, key)); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {})); + syndicate_and_close(1, 1, pubkey, LIQSOL, UNIT); + const auto id = envelope_row(SOLANA, LIQSOL, 1)["request_id"].as_uint64(); + underwrite_envelope(SOLANA, LIQSOL, 1); + BOOST_REQUIRE_EQUAL(success(), crank()); + const auto prune = [&] { return push(SYND_ACCOUNT, synd_abi_ser, "carol"_n, "pruneenv"_n, mvo() + ("chain_code", codename_mvo(SOLANA))("token_code", codename_mvo(LIQSOL))("limit", 1)); }; + BOOST_REQUIRE_EQUAL(success(), prune()); + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE_EQUAL(success(), rule(id, true)); + BOOST_REQUIRE_EQUAL(success(), prune()); // final bond outcome has not been consumed yet + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE_EQUAL(success(), crank()); + BOOST_REQUIRE_EQUAL(success(), prune()); + BOOST_REQUIRE(envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE_EQUAL(2u, ledger_row(SOLANA, LIQSOL)["retained_from_epoch"].as_uint64()); + const auto before = supply(); + BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 1, digest_of("changed"), 2, ChainKind::CHAIN_KIND_SVM, + pubkey, LIQSOL, UNIT)); + BOOST_REQUIRE_EQUAL(success(), onyield(SOLANA, 1, digest_of("changed"), 2, 2, LIQSOL, UNIT)); + BOOST_REQUIRE_EQUAL(success(), closeenv(SOLANA, 1, digest_of("changed"))); + BOOST_REQUIRE(envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE_EQUAL(before, supply()); + BOOST_REQUIRE_EQUAL(1u, cursor_row(SOLANA)["last_sequence"].as_uint64()); + BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 2, digest_of("new"), 2, ChainKind::CHAIN_KIND_SVM, + pubkey, LIQSOL, UNIT)); + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 2).is_null()); +} FC_LOG_AND_RETHROW() + +/// Compaction is bounded and prefix-only; unresolved earlier envelopes protect later replay markers. +BOOST_FIXTURE_TEST_CASE(prune_stops_at_open_prefix_and_respects_limit, sysio_synd_tester) try { + const auto pubkey = ed_bytes(ed_key()); + for (uint32_t n = 1; n <= 3; ++n) + BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, n, digest_of(std::to_string(n)), n, + ChainKind::CHAIN_KIND_SVM, pubkey, LIQSOL, UNIT)); + BOOST_REQUIRE_EQUAL(success(), dropenv(SOLANA, LIQSOL, 2)); + BOOST_REQUIRE_EQUAL(success(), dropenv(SOLANA, LIQSOL, 3)); + const auto prune = [&](uint32_t limit) { return push(SYND_ACCOUNT, synd_abi_ser, "carol"_n, "pruneenv"_n, mvo() + ("chain_code", codename_mvo(SOLANA))("token_code", codename_mvo(LIQSOL))("limit", limit)); }; + BOOST_REQUIRE_EQUAL(success(), prune(10)); + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 2).is_null()); + BOOST_REQUIRE_EQUAL(success(), dropenv(SOLANA, LIQSOL, 1)); + BOOST_REQUIRE_EQUAL(success(), prune(0)); + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE_EQUAL(success(), prune(1)); + BOOST_REQUIRE(envelope_row(SOLANA, LIQSOL, 1).is_null()); + BOOST_REQUIRE(!envelope_row(SOLANA, LIQSOL, 2).is_null()); + BOOST_REQUIRE_EQUAL(success(), prune(2)); + BOOST_REQUIRE(envelope_row(SOLANA, LIQSOL, 2).is_null()); + BOOST_REQUIRE(envelope_row(SOLANA, LIQSOL, 3).is_null()); + BOOST_REQUIRE_EQUAL(4u, ledger_row(SOLANA, LIQSOL)["retained_from_epoch"].as_uint64()); +} FC_LOG_AND_RETHROW() + +/// Tiny independent synchronization sweeps reach the tail of an empty-bucket backlog without a +/// larger release budget. Issuance progresses past each fully bonded request. +BOOST_FIXTURE_TEST_CASE(small_sync_sweeps_progress_past_blocked_release, sysio_synd_tester) try { + const auto pubkey = ed_bytes(ed_key()); + BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {.synd_burst = 0, .synd_refill = 0})); + for (uint32_t n = 1; n <= 20; ++n) { + syndicate_and_close(n, n, pubkey, LIQSOL, UNIT); + BOOST_REQUIRE_EQUAL(STATE_REQUESTED, envelope_row(SOLANA, LIQSOL, n)["state"].as_string()); + underwrite_envelope(SOLANA, LIQSOL, n); + } + const auto id = envelope_row(SOLANA, LIQSOL, 20)["request_id"].as_uint64(); + BOOST_REQUIRE_EQUAL(success(), rule(id, false)); + for (unsigned n = 0; n < 21; ++n) + BOOST_REQUIRE_EQUAL(success(), push(SYND_ACCOUNT, synd_abi_ser, "carol"_n, "sync"_n, mvo()("limit", 1))); + BOOST_REQUIRE_EQUAL("INVALID", envelope_row(SOLANA, LIQSOL, 20)["outcome"].as_string()); + BOOST_REQUIRE_EQUAL(0u, envelope_row(SOLANA, LIQSOL, 20)["burned"].as_uint64()); +} FC_LOG_AND_RETHROW() + // A pair that can move nothing more costs the rest of the step nothing: 17 held syndications of a token // with no syndconfig row do not starve the other token of closeenv's small inline step, which still // requests it and, once bonded, releases it. @@ -2488,18 +2635,8 @@ BOOST_FIXTURE_TEST_CASE(envelopes_behind_an_empty_bucket_leave_closeenv_its_budg } FC_LOG_AND_RETHROW() // The examination cap bounds a step whatever the pairs hold: six pairs (LIQA-LIQE, LIQSOL), each with a -// bonded envelope of 16 syndications behind an empty bucket, then LIQTWO last in queue order; LIQETH, with -// nothing admitted, sits between LIQE and LIQSOL. closeenv's step has budget 16 and cap 64. A pair costs one -// look on entry, one for its envelope and one per item it collects -- min(budget, looks) of them -- and -// one unit of budget for the first item, which finds the bucket empty. From the start of the queue: -// LIQA 1+1+16 (looks 64 -> 46, budget 15), LIQB 1+1+15 (29, 14), LIQC 1+1+14 (13, 13), -// LIQD 1+1+11 (0, 12): the cap ends step 1 in the fourth pair, which moved nothing, so the cursor -// passes it to LIQE and LIQTWO is still WAITING. -// Step 2 from LIQE: LIQE 18 (46, 15), LIQETH 1 (45), LIQSOL 1+1+15 (28, 14), LIQTWO 1+1 and its request -// (26, 13), then LIQA 1+1+13 (11, 12), LIQB 1+1+9 (0, 11): the cursor passes LIQB to LIQC. -// Step 3, the envelope now bonded, from LIQC: LIQC 18 (46, 15), LIQD 17 (29, 14), LIQE 16 (13, 13), -// LIQETH 1 (12), LIQSOL 1+1+10 (0, 12): the cursor passes LIQSOL to LIQTWO, not yet refreshed. -// Step 4 starts at LIQTWO and releases its envelope. +// A blocked release pass retains its examination cap and cursor. Underwriting has an independent +// budget and reaches a later pair immediately, even when release exhausts its own budget first. BOOST_FIXTURE_TEST_CASE(the_examination_cap_bounds_a_step_over_many_blocked_pairs, sysio_synd_tester) try { const auto key = ed_key(); const auto pubkey = ed_bytes(key); @@ -2532,30 +2669,17 @@ BOOST_FIXTURE_TEST_CASE(the_examination_cap_bounds_a_step_over_many_blocked_pair BOOST_REQUIRE(state_row().is_null()); BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQTWO, {})); - // Step 1, closeenv of the LIQTWO envelope: the cap ends it in LIQD. + // Release stops in LIQD, while the independent issuance pass reaches LIQTWO. const uint32_t two = ++epoch; syndicate_and_close(two, ++sequence, pubkey, LIQTWO, 3 * UNIT); - BOOST_REQUIRE_EQUAL(STATE_WAITING, envelope_row(SOLANA, LIQTWO, two)["state"].as_string()); - BOOST_REQUIRE_EQUAL(symbol::from_string("9,LIQE").to_symbol_code().value, - state_row()["queue_cursor"].as_uint64()); - - // Step 2 requests the LIQTWO envelope and ends in LIQB. - ++epoch; - syndicate_and_close(epoch, ++sequence, pubkey, LIQTWO, UNIT); BOOST_REQUIRE_EQUAL(STATE_REQUESTED, envelope_row(SOLANA, LIQTWO, two)["state"].as_string()); - BOOST_REQUIRE_EQUAL(symbol::from_string("9,LIQC").to_symbol_code().value, + BOOST_REQUIRE_EQUAL(symbol::from_string("9,LIQE").to_symbol_code().value, state_row()["queue_cursor"].as_uint64()); - - // Step 3, the envelope bonded, ends in LIQSOL before LIQTWO. underwrite_envelope(SOLANA, LIQTWO, two); - ++epoch; - syndicate_and_close(epoch, ++sequence, pubkey, LIQTWO, UNIT); - BOOST_REQUIRE_EQUAL(STATE_REQUESTED, envelope_row(SOLANA, LIQTWO, two)["state"].as_string()); - BOOST_REQUIRE_EQUAL(LIQTWO_SYM.to_symbol_code().value, state_row()["queue_cursor"].as_uint64()); - - // Step 4 releases it; every blocked pair still holds its items. - ++epoch; - syndicate_and_close(epoch, ++sequence, pubkey, LIQTWO, UNIT); + // Repeated bounded steps eventually deliver that pair without draining any empty-bucket pair. + for (unsigned step = 0; step < 8 && + envelope_row(SOLANA, LIQTWO, two)["state"].as_string() != STATE_DONE; ++step) + BOOST_REQUIRE_EQUAL(success(), crank(16)); BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQTWO, two)["state"].as_string()); BOOST_REQUIRE_EQUAL(static_cast(3 * UNIT), liq_balance("alice"_n, LIQTWO_SYM)); for (uint32_t e = 1; e <= blocked.size(); ++e) { @@ -2824,11 +2948,11 @@ BOOST_FIXTURE_TEST_CASE(challenge_refused_on_an_unrequested_envelope, sysio_synd BOOST_REQUIRE(mentions(challenge("carol"_n, SOLANA, LIQSOL, 9), CHALLENGE_NO_ROW)); BOOST_REQUIRE(mentions(challenge(SYND_ACCOUNT, SOLANA, LIQSOL, 1), CHALLENGE_ROLE)); BOOST_REQUIRE_EQUAL(success(), liq_mint("bob"_n, LIQSOL, 5 * UNIT)); - set_code("bob"_n, contracts::util::reject_all_wasm()); + set_code("bob"_n, contracts::util::block_transfer_wasm()); produce_blocks(); - BOOST_REQUIRE(mentions(challenge("bob"_n, SOLANA, LIQSOL, 1), CHALLENGE_CODE)); + BOOST_REQUIRE_EQUAL(success(), challenge("bob"_n, SOLANA, LIQSOL, 1)); BOOST_REQUIRE_EQUAL(static_cast(5 * UNIT), liq_balance("carol"_n)); - BOOST_REQUIRE_EQUAL(REQUEST_OPEN, bond_request(1)["state"].as_string()); + BOOST_REQUIRE_EQUAL(REQUEST_HELD, bond_request(1)["state"].as_string()); BOOST_REQUIRE_EQUAL(0u, feepot_balance(LIQSOL)); } FC_LOG_AND_RETHROW() @@ -3014,6 +3138,8 @@ BOOST_FIXTURE_TEST_CASE(invalid_burn_spans_steps_within_the_budget, sysio_synd_t auto envelope = envelope_row(SOLANA, LIQSOL, 1); BOOST_REQUIRE_EQUAL(STATE_REQUESTED, envelope["state"].as_string()); BOOST_REQUIRE_EQUAL(2 * UNIT, envelope["burned"].as_uint64()); + // The ruling clears issuance even while the independent release pass still has items to burn. + BOOST_REQUIRE_EQUAL(2u, ledger_row(SOLANA, LIQSOL)["underwriting_epoch"].as_uint64()); BOOST_REQUIRE_EQUAL(success(), crank()); BOOST_REQUIRE(executed(BOND_ACCOUNT, "claim"_n)); @@ -3099,25 +3225,24 @@ BOOST_FIXTURE_TEST_CASE(invalid_accounts_for_a_forfeit_claimed_by_someone_else, BOOST_REQUIRE_EQUAL(static_cast(15 * UNIT), supply()); } FC_LOG_AND_RETHROW() -// A challenger that has gained contract code by the VALID ruling could refuse the forwarded share and -// fail the step for every holder: its share goes to the fee pot instead, and release goes on. -BOOST_FIXTURE_TEST_CASE(valid_after_challenge_pots_the_share_of_a_contract_challenger, sysio_synd_tester) try { +// Contract challengers receive their refund directly without recipient execution. +BOOST_FIXTURE_TEST_CASE(valid_after_challenge_credits_a_contract_challenger, sysio_synd_tester) try { const auto pubkey = ed_bytes(ed_key()); BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {})); syndicate_and_close(1, 1, pubkey, LIQSOL, 10 * UNIT); const uint64_t id = envelope_row(SOLANA, LIQSOL, 1)["request_id"].as_uint64(); BOOST_REQUIRE_EQUAL(success(), liq_mint("bob"_n, LIQSOL, 5 * UNIT)); BOOST_REQUIRE_EQUAL(success(), challenge("bob"_n, SOLANA, LIQSOL, 1)); - set_code("bob"_n, contracts::util::reject_all_wasm()); + set_code("bob"_n, contracts::util::block_transfer_wasm()); produce_blocks(); BOOST_REQUIRE_EQUAL(success(), rule(id, true)); // Nothing was bonded: the whole hold bond is the issuer's award. - const uint64_t hold_bond = 10 * UNIT * HOLD_BPS / BPS_DENOMINATOR; BOOST_REQUIRE_EQUAL(success(), crank()); + for (const auto& trace : last_trace->action_traces) BOOST_REQUIRE(trace.receiver != "bob"_n); BOOST_REQUIRE(executed(BOND_ACCOUNT, "claim"_n)); - BOOST_REQUIRE_EQUAL(static_cast(5 * UNIT - hold_bond), liq_balance("bob"_n)); - BOOST_REQUIRE_EQUAL(hold_bond, feepot_balance(LIQSOL)); + BOOST_REQUIRE_EQUAL(static_cast(5 * UNIT), liq_balance("bob"_n)); + BOOST_REQUIRE_EQUAL(0u, feepot_balance(LIQSOL)); BOOST_REQUIRE_EQUAL(STATE_DONE, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); } FC_LOG_AND_RETHROW() @@ -3732,7 +3857,6 @@ BOOST_FIXTURE_TEST_CASE(a_syndication_shortfall_records_pulls_and_still_holds, s const auto envelope = envelope_row(SOLANA, LIQSOL, 1); BOOST_REQUIRE_EQUAL(15 * UNIT, envelope["synd_total"].as_uint64()); BOOST_REQUIRE_EQUAL(2u, envelope["item_count"].as()); - BOOST_REQUIRE_EQUAL(15 * UNIT, ledger_row(SOLANA, LIQSOL)["syndicated_sum"].as_uint64()); BOOST_REQUIRE_EQUAL(2u, cursor_row(SOLANA)["last_sequence"].as_uint64()); // The envelope closes as usual; the frozen queue step waits. @@ -3764,7 +3888,6 @@ BOOST_FIXTURE_TEST_CASE(a_yield_shortfall_records_pulls_and_still_holds, sysio_s BOOST_REQUIRE_EQUAL(static_cast(10 * UNIT), supply()); BOOST_REQUIRE_EQUAL(0, pending()); BOOST_REQUIRE_EQUAL(3 * UNIT, envelope_row(SOLANA, LIQSOL, 1)["yield_total"].as_uint64()); - BOOST_REQUIRE_EQUAL(3 * UNIT, ledger_row(SOLANA, LIQSOL)["yield_sum"].as_uint64()); BOOST_REQUIRE_EQUAL(7u, cursor_row(SOLANA)["last_epoch"].as_uint64()); } FC_LOG_AND_RETHROW() @@ -3801,7 +3924,6 @@ BOOST_FIXTURE_TEST_CASE(a_desyndication_in_flight_leaves_the_check_passing, sysi BOOST_REQUIRE_EQUAL(success(), setconfig(SOLANA, LIQSOL, {.desynd_burst = 100 * UNIT})); BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 40 * UNIT)); BOOST_REQUIRE_EQUAL(static_cast(60 * UNIT), supply()); - BOOST_REQUIRE_EQUAL(60 * UNIT, desynd_row(1)["total_syndicated"].as_uint64()); const auto digest = digest_of("envelope-1"); // Unpaid: the pool still holds the 40, plus the 10 just locked. @@ -3829,7 +3951,6 @@ BOOST_FIXTURE_TEST_CASE(a_desyndication_with_a_fee_in_flight_leaves_the_check_pa BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 40 * UNIT)); BOOST_REQUIRE_EQUAL(UNIT, feepot_balance(LIQSOL)); BOOST_REQUIRE_EQUAL(static_cast(61 * UNIT), supply()); - BOOST_REQUIRE_EQUAL(61 * UNIT, desynd_row(1)["total_syndicated"].as_uint64()); const auto digest = digest_of("envelope-1"); // Unpaid: the pool still holds the 39, plus the 10 just locked. @@ -3926,8 +4047,8 @@ BOOST_FIXTURE_TEST_CASE(a_recredit_is_seen_by_the_next_comparison, sysio_synd_te BOOST_REQUIRE_EQUAL(success(), desyndicate("alice"_n, 4 * UNIT)); BOOST_REQUIRE_EQUAL(static_cast(16 * UNIT), supply()); // The outpost refused it; governance returns the 4 to alice. - BOOST_REQUIRE_EQUAL(success(), push(LIQ_ACCOUNT, liq_abi_ser, LIQ_ACCOUNT, "recredit"_n, mvo() - ("holder", "alice"_n)("quantity", asset(static_cast(4 * UNIT), LIQSOL_SYM)))); + BOOST_REQUIRE_EQUAL(success(), push(SYND_ACCOUNT, synd_abi_ser, SYSIO_ACCOUNT, "refundreturn"_n, + mvo()("request_id", 1))); BOOST_REQUIRE_EQUAL(static_cast(20 * UNIT), supply()); const auto ledger_before = ledger_row(SOLANA, LIQSOL); @@ -3937,8 +4058,6 @@ BOOST_FIXTURE_TEST_CASE(a_recredit_is_seen_by_the_next_comparison, sysio_synd_te BOOST_REQUIRE(!console_has(EXCESS)); require_no_shortfall(); // The recredit wrote nothing to the ledger's running sums: the check does not read them. - BOOST_REQUIRE_EQUAL(4 * UNIT, ledger_before["desyndicated_sum"].as_uint64()); - BOOST_REQUIRE_EQUAL(4 * UNIT, ledger_row(SOLANA, LIQSOL)["desyndicated_sum"].as_uint64()); } FC_LOG_AND_RETHROW() // Review focus 2: only an admitted message is compared. A replayed sequence, or a message dropped for any @@ -3964,6 +4083,26 @@ BOOST_FIXTURE_TEST_CASE(a_message_not_admitted_is_never_compared, sysio_synd_tes BOOST_REQUIRE_EQUAL(static_cast(10 * UNIT), supply()); } FC_LOG_AND_RETHROW() +/// Healthy reports do not erase an incident. Only governance may reconcile it, against live +/// outstanding supply, and reconciliation does not implicitly clear the global emergency stop. +BOOST_FIXTURE_TEST_CASE(incident_reconciliation_is_explicit_and_pair_local, sysio_synd_tester) try { + const auto pubkey = ed_bytes(ed_key()); + BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 1, digest_of("one"), 1, ChainKind::CHAIN_KIND_SVM, + pubkey, LIQSOL, 10 * UNIT, MSGCH_ACCOUNT, UNIT)); + BOOST_REQUIRE_EQUAL(success(), onyield(SOLANA, 1, digest_of("one"), 2, 2, LIQSOL, UNIT, + MSGCH_ACCOUNT, 10 * UNIT)); + BOOST_REQUIRE_EQUAL(1u, mismatch_rows().size()); + const auto reconcile = [&](name signer, uint64_t reported) { return push(SYND_ACCOUNT, synd_abi_ser, + signer, "reconcile"_n, mvo()("chain_code", codename_mvo(SOLANA)) + ("token_code", codename_mvo(LIQSOL))("reported", reported)); }; + BOOST_REQUIRE(mentions(reconcile("alice"_n, 10 * UNIT), "missing authority of sysio")); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("custody still below outstanding"), reconcile(SYSIO_ACCOUNT, 9 * UNIT)); + BOOST_REQUIRE_EQUAL(success(), reconcile(SYSIO_ACCOUNT, 10 * UNIT)); + BOOST_REQUIRE(mismatch_rows().empty()); + BOOST_REQUIRE(cord_pulled()); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("custody incident not found"), reconcile(SYSIO_ACCOUNT, 10 * UNIT)); +} FC_LOG_AND_RETHROW() + // Review focus 4: with no sysio.andon deployed, a shortfall is recorded and printed as not pulled, and the // syndication and its envelope are processed as usual. BOOST_FIXTURE_TEST_CASE(a_shortfall_without_sysio_andon_is_recorded_and_aborts_nothing, @@ -3984,7 +4123,7 @@ BOOST_FIXTURE_TEST_CASE(a_shortfall_without_sysio_andon_is_recorded_and_aborts_n BOOST_REQUIRE_EQUAL(10 * UNIT, item_row(1)["amount"].as_uint64()); BOOST_REQUIRE_EQUAL(success(), onyield(SOLANA, 1, digest, 2, 7, LIQSOL, UNIT, MSGCH_ACCOUNT, 9 * UNIT)); BOOST_REQUIRE(console_has("sysio.synd::onyield" + std::string(CORD_NOT_PULLED))); - BOOST_REQUIRE_EQUAL(2u, mismatch_rows().size()); + BOOST_REQUIRE_EQUAL(1u, mismatch_rows().size()); BOOST_REQUIRE_EQUAL(success(), closeenv(SOLANA, 1, digest)); BOOST_REQUIRE_EQUAL(STATE_REQUESTED, envelope_row(SOLANA, LIQSOL, 1)["state"].as_string()); } FC_LOG_AND_RETHROW() @@ -4013,7 +4152,7 @@ BOOST_FIXTURE_TEST_CASE(a_shortfall_by_an_unregistered_puller_is_recorded_and_ab // Review focus 4: a shortfall on a cord already pulled writes its own row, sends no second pull and aborts // nothing; the cord keeps its first pull. -BOOST_FIXTURE_TEST_CASE(a_shortfall_on_a_pulled_cord_adds_a_row_and_aborts_nothing, sysio_synd_tester) try { +BOOST_FIXTURE_TEST_CASE(a_shortfall_on_a_pulled_cord_updates_the_incident, sysio_synd_tester) try { const auto pubkey = ed_bytes(ed_key()); constexpr auto SVM = ChainKind::CHAIN_KIND_SVM; const auto digest = digest_of("envelope-1"); @@ -4031,15 +4170,15 @@ BOOST_FIXTURE_TEST_CASE(a_shortfall_on_a_pulled_cord_adds_a_row_and_aborts_nothi BOOST_REQUIRE(console_has("sysio.synd::onyield" + std::string(CORD_ALREADY))); BOOST_REQUIRE(!executed(sysio_system::test_support::andon::account, "pull"_n)); const auto rows = mismatch_rows(); - BOOST_REQUIRE_EQUAL(2u, rows.size()); - require_mismatch(rows[1], SOLANA, LIQSOL, 1, 2, MISMATCH_YIELD, 8 * UNIT, 10 * UNIT); - BOOST_REQUIRE_EQUAL(1u, rows[0]["sequence"].as_uint64()); + BOOST_REQUIRE_EQUAL(1u, rows.size()); + require_mismatch(rows[0], SOLANA, LIQSOL, 1, 2, MISMATCH_YIELD, 8 * UNIT, 10 * UNIT); + BOOST_REQUIRE_EQUAL(2u, rows[0]["sequence"].as_uint64()); BOOST_REQUIRE(first_pull == cord_bytes()); // A later syndication short again records its own row too, and the cord still holds its first pull. BOOST_REQUIRE_EQUAL(success(), onsynd(SOLANA, 1, digest, 3, SVM, pubkey, LIQSOL, UNIT, MSGCH_ACCOUNT, UNIT)); BOOST_REQUIRE(console_has("sysio.synd::onsynd" + std::string(CORD_ALREADY))); - BOOST_REQUIRE_EQUAL(3u, mismatch_rows().size()); + BOOST_REQUIRE_EQUAL(1u, mismatch_rows().size()); BOOST_REQUIRE(first_pull == cord_bytes()); BOOST_REQUIRE_EQUAL(static_cast(11 * UNIT), supply()); BOOST_REQUIRE_EQUAL(success(), closeenv(SOLANA, 1, digest)); diff --git a/docs/sysio-bond-integration.md b/docs/sysio-bond-integration.md index 8f66a6332c..985f663bf5 100644 --- a/docs/sysio-bond-integration.md +++ b/docs/sysio-bond-integration.md @@ -94,7 +94,7 @@ Every payment is pulled by `claim(request_id, account)`, one account per call, s over the underwriters and no party's refusal blocks another. Anyone may call it; the payment goes to `account`. A claim with nothing owed is refused with `nothing to claim`. -- **Underwriters** claim the bond and its shares in one transfer, and the WIRE yield in a second. +- **Underwriters** settle the bond and its shares with `claim`, then withdraw earned WIRE with `claimwire`. - **The issuer** of an INVALID request claims the forfeit, in its own transfer (below), and the bounty back when there was no hold. On VALID after a hold of a partly bonded request it claims the unbonded share of the hold bond; the hold bond of a fully bonded request is all the underwriters'. @@ -107,7 +107,7 @@ when claimed. ## The emergency stop -While the depot's `sysio.andon` cord is pulled, `claim` is refused (`the andon cord is pulled: funds +While the depot's `sysio.andon` cord is pulled, `claim` and `claimwire` are refused (`the andon cord is pulled: funds cannot leave custody`): every payout waits for the clear. Everything else runs -- `request`, `addbounty`, `accept`, `hold`, `approve`, the rulings, `sweepyield` and `prune` -- and every transfer into `sysio.bond` (a bounty, a bond, a hold bond) succeeds, so bonds can be placed and requests held @@ -117,12 +117,11 @@ contract that claims from a path that must not throw reads the cord first with `andon::pulled(andon::ANDON_ACCOUNT)` (`sysio.andon.hpp`) and waits while it is pulled, as the syndication contract does. -## The forfeit transfer +## The forfeit payment -The issuer's `claim` on an INVALID request pays every bond in ONE `transfer` of the whole bonded -amount, whatever the number of underwriters, with the memo `sysio.bond::forfeit`. An issuer contract -recognizes a forfeit by that memo on an incoming transfer from `sysio.bond`. The bounty and the hold -bond never arrive under that memo. +The issuer's `claim` on an INVALID request pays its bonded forfeit through a LIQ ledger settlement, +or through an ordinary transfer with memo `sysio.bond::forfeit` for WIRE-denominated requests. +Clients read the request's recorded outcome; LIQ invokes no incoming-transfer handler. What a client does with a forfeit depends on the token: @@ -138,7 +137,7 @@ position (`docs/shadow-custody-integration.md`). `sweepyield(token_code)` pulls owed; `claim` also pulls before it pays. - A returned bond is held until it is claimed, and earns until then: its position settles at the - live index at the claim, and the WIRE goes to its underwriter with the bond. + live index at the claim, and its WIRE is banked for the underwriter to withdraw with `claimwire`. - A forfeited bond becomes the issuer's at `rslvinvalid`, which records the index in `resolved_index`; its position settles there. What the forfeit earns while it waits for the issuer's claim is slack. The bond's WIRE never goes to the underwriter: claiming it pays the WIRE @@ -154,7 +153,44 @@ owed; `claim` also pulls before it pays. `prune(from_id, limit)` walks the requests from id `from_id` up and acts on at most `limit` terminal requests. It erases a request with its rows once they are all paid or owe nothing -- the forfeit and -every award claimed -- and its ruling is at least `PRUNE_RETENTION_SEC` (seven days) old, so a polling -issuer always sees the ruling first. A request it keeps still loses its bond rows already paid out: an -unclaimed payout holds no other underwriter's RAM. A request whose token no longer resolves is skipped. +every award claimed, with no residual owed yield -- and its ruling is at least `PRUNE_RETENTION_SEC` +(seven days) old. Durable requests also require issuer acknowledgement. Paid bond rows with no +residual yield may be erased independently; an unrelated unclaimed payout does not hold their RAM. A request whose token no longer resolves is skipped. Anyone may call it. + +## Durable outcome consumption + +Polling contract issuers should use `requestkeep` (same arguments as `request`) and call `ack(request_id)` +after storing a terminal result. Only the issuer may acknowledge; acknowledgement is idempotent and +requires a terminal request. Rulings never notify the issuer, and claims do not require acknowledgement. +`prune` requires acknowledgement in addition to its existing retention and fully-paid checks for these +requests. Ordinary `request` retains its existing retention behavior. + +The public `find_request`, `request_refusal`, and `settlement_due` accessors provide stable statement +lookup, shared admission rules, and outstanding settlement principal without client knowledge of counters +or escrow representation. Statement deduplication lasts while the request exists; durable delivery does +not add a permanent replay archive after acknowledged requests are pruned. Issuers must enforce their +own replay policy (syndication retains its envelope/sequence guards). + +The durable request flag and syndication's independent cursors change table layouts. A deployment with +existing rows needs a coordinated table migration; fresh prelaunch state needs none. This change does +not provide a migration action or recover outcomes already pruned under the old contract. + +## Ledger settlement and independent WIRE claims + +LIQ principal, awards and forfeits use `sysio.liq::settle(custodian, beneficiary, quantity)`. +Only `sysio.synd` and `sysio.bond` can debit their own existing LIQ through this action. +It settles both yield positions, leaves supply unchanged, invokes neither account, and requires +Andon clear even for a custody recipient or self-settlement. Ordinary token transfers keep their +notifications and existing behavior. + +`claim(request_id, account)` credits LIQ immediately and records earned WIRE in the account's +backed `wireclaims` balance. `claimwire(account)` withdraws that WIRE separately to the named +account. It is permissionless and respects Andon; a failed recipient transfer rolls back only the +WIRE withdrawal. The WIRE balance survives request pruning and never expires. Checked additions +reject overflow atomically. Yield the pool cannot cover remains on the original bond or escrow row; +that residual prevents pruning and can be retried without paying principal twice or earning yield +on returned principal. WIRE-denominated requests retain their ordinary, per-recipient claims. + +Clients must read the recorded outcome and balances rather than infer a LIQ forfeit from a transfer +memo. LIQ settlement has no memo or transfer notification. diff --git a/docs/sysio-synd.md b/docs/sysio-synd.md index da2ebfaec6..676a783589 100644 --- a/docs/sysio-synd.md +++ b/docs/sysio-synd.md @@ -39,43 +39,33 @@ shadow's headroom. An envelope with no syndication or yield report writes nothin ### One step -`closeenv` runs a step inline; anyone may run one with `crank(limit)`. A step walks every outpost and -token from its `ledger.queue_epoch`, oldest envelope first, spending one unit of `limit` per item -released, burned or found unable to move and per request issued. Refreshing an envelope from its -request spends no budget: every envelope the walk reaches is refreshed whatever the pair's flows. An -item of a flow that has stopped for the step (syndications behind an empty or unset bucket, yields -behind a full headroom) is passed over for free, so envelopes stuck behind an empty bucket cost no -budget. A separate examination cap, 4 x `limit`, counts one per pair visited (its ledger and config -rows and the lookup of its first envelope), one per envelope looked at and one per item row read, and -never touches the budget. A step therefore reads at most a constant multiple of `limit` rows, however -many pairs exist, idle or blocked: `closeenv`'s inline step makes at most 64 examinations. The step -ends when either runs out. The next step starts at the pair it ended on, or at the pair after it when -that pair released and burned nothing (`syndstate.queue_cursor`): a pair that keeps moving holds the -cursor until its backlog drains, and a pair that moves nothing never holds it. A pair the cap reached -before it could look at any of its envelopes keeps the cursor, so a small `crank` cannot skip it -forever. A step never -throws: what cannot move now waits for the next step, and the reason is printed as -`sysio.synd::queue: epoch -- `. - -**Operational rule.** A step sees at most as many envelopes of a pair as its budget and cap allow, -counted from the pair's oldest unfinished envelope (16 for `closeenv`'s inline step). A pair whose -blocked backlog is longer than that window needs a `crank` with a large limit before `sysio.bond`'s -prune retention (7 days after a ruling) elapses; otherwise the request of an envelope past the window -can be ruled and pruned before any step records its outcome, and that envelope strands. - -1. **Refresh.** An envelope whose request is issued reads its `sysio.bond` row, found through the - request's `bystatement` key (`sysio.bond` notifies no one). BONDED makes it RELEASABLE; HELD makes - it HELD. The first time the row is APPROVED, VALID or INVALID the outcome is recorded on the - envelope (`outcome`) and the row is never read again, since `sysio.bond` may prune it a week after - the ruling: APPROVED and VALID make the envelope RELEASABLE, INVALID burns it (below). -2. **Release.** The items of a RELEASABLE envelope are released in id order, through the gates below. - An envelope with no item left is DONE. -3. **Request.** The oldest WAITING envelope of a pair asks `sysio.bond` to underwrite its statement, - but only when no earlier envelope of the pair holds a request that is neither bonded nor ruled: one - request at a time per outpost and token, in envelope order. The statement is the packed - `(chain_code, epoch_index, digest, token_code)` under schema `oppenvelope`. Covered is the - envelope's syndicated plus yield total, rounded up to `sysio.bond`'s increment. The bounty is the - pair's `bounty`, or what the fee pot holds if that is less. +`closeenv` runs a bounded step inline; anyone may run `crank(limit)`. Each crank runs three +independent bounded phases: + +1. **Synchronize outcomes.** `sync(limit)` visits at most `limit` envelopes from a persistent cursor, + including DONE envelopes whose funds released before finality. `syncenv(chain_code, token_code, + epoch_index)` targets one envelope directly. Both record the terminal result and its settlement + amounts before acknowledging bond; neither releases, burns nor claims funds. They work while frozen. +2. **Release and settle.** The FIFO release walk retains `ledger.queue_epoch`, its independent pair + cursor, `limit` work budget and `4 * limit` examination cap. Existing release gates, yield accounting, + burns and challenge settlements apply. The phase refreshes reached requests before moving funds. +3. **Advance underwriting.** A separate per-pair `underwriting_epoch` and global pair cursor advance + past fully bonded or ruled requests. The oldest WAITING envelope is issued only after those before + it meet that gate. This phase has its own `limit` work budget and `4 * limit` examination cap; an + empty release bucket never consumes it. + Running issuance after settlement preserves the existing rule that fees earned in this crank can + fund the next request's bounty. Its budget and cursor remain independent of release. + +Syndication uses bond's durable `requestkeep` interface. A terminal request cannot be pruned before +syndication records and acknowledges its outcome, even when all payouts were claimed and seven days +have passed. There is no polling deadline. Delayed synchronization delays progress, not recoverability. +An INVALID outcome or pending challenger share discovered after DONE rewinds settlement to that envelope. + +The stable request identity is `(issuer, schema, statement)`, resolved through bond's public accessor. +An inline `syncenv` after creation records the actual numeric ID; syndication never predicts bond's +counter. Admission validation is shared with bond. The statement remains the packed +`(chain_code, epoch_index, digest, token_code)` under `oppenvelope`; coverage is the envelope's totals +rounded to the bond increment and the bounty is capped by the fee pot. ### The gates of a syndication @@ -94,8 +84,8 @@ request is OPEN, HELD or INVALID. item earned while held is credited to that account's own `sysio.liq` row with `sysio.liq::creditowed` and claimed there with `sysio.liq::claim`; nothing is pushed. WIRE the yield pool cannot cover yet stays on the item, and once the item is released whole it moves to the pubkey's parked row, which a - later `sweep` pays. A pubkey with no link, or linked to an account with contract code, is paid into - `parked` instead. + later `sweep` pays. LIQ is credited by `sysio.liq::settle` without notifications, including to contract accounts. + Only a pubkey with no link needs parked principal. A YIELD item is released whole into `sysio.liq`'s pending yield by `mintyield`. It pays no fee and uses no budget; it waits only when the shadow's headroom cannot take it. @@ -109,7 +99,7 @@ request's hold bond plus the pair's `challenge_extra`; a charge of zero is refus zero; configure challenge_extra`). The extra stays in the fee pot, and `sysio.synd` holds the request through `sysio.bond::hold` naming the challenger as beneficiary. The envelope is HELD until `sysio` rules, and the pair's `queue_epoch` is rewound to it so the ruling is processed. One challenge per -request; a challenger may not have contract code. Any row the challenge creates bills `sysio`. +request; contract accounts may challenge. Any row the challenge creates bills `sysio`. A challenge of a request that is not yet fully bonded freezes the whole pair until `sysio` rules: a held request is neither bonded nor ruled, so no later envelope of the pair is requested. That costs the @@ -141,9 +131,8 @@ refused as closed. A parked balance sits in `sysio.synd`'s own `sysio.liq` row with a shadow-custody position, so it keeps earning WIRE. It is delivered, balance and banked WIRE together, when the pubkey is linked (`sysio.authex::createlink` sends `linkswept` inline) or by the permissionless `sweep(account, -chain_kind)` for a link that already exists. The balance arrives by `sysio.liq::transfer`, which notifies -the account: a contract account whose transfer handler throws aborts its own `createlink` and cannot -link while shadow is parked for its key. The banked WIRE is credited with `sysio.liq::creditowed`, which +chain_kind)` for a link that already exists. The balance arrives by `sysio.liq::settle`, which invokes no recipient code; +a rejecting transfer handler cannot block linking or delivery. The banked WIRE is credited with `sysio.liq::creditowed`, which notifies no one. WIRE the yield pool cannot cover yet is kept: the parked row stays at balance 0 with it banked, and a later delivery pays it. Parked funds have already paid the fee and used the budget. @@ -162,11 +151,9 @@ banked, and a later delivery pays it. Parked funds have already paid the fee and zero leaves the whole quantity to burn; a fee of the whole quantity (10000 bps) is refused, since nothing would reach the outpost. 6. The net rest is burned with `sysio.liq::burn` and queued to the outpost as `DESYNDICATE_LIQ`, with - the next request id from `syndcounters`. The net is what the attestation, `desyndlog` and the - ledger's `desyndicated_sum` carry. The attestation and `desyndlog` also carry `total_syndicated`, - the depot's outstanding shadow of the symbol after this burn: `sysio.liq`'s supply plus the yield - parked in `liqpending` (`liq::outstanding_of`), net of the burned amount. The outpost checks its - custody after the payout covers it. + the next request id from `syndcounters`. `returns` retains the original holder, destination key, + outpost, token and exact net amount until its external outcome is established. The attestation + carries `total_syndicated`, the live outstanding shadow after the burn; no historical total is stored. The burn is final. An outpost either pays the release, stores it and pays it later, or skips it: @@ -188,28 +175,26 @@ The burn is final. An outpost either pays the release, stores it and pays it lat `amount out of range` and `unresolvable recipient` (`SyndicationPool.sol:594-618`), each a `DesyndicationDropped(requestId, depotAmount, reason)` event (`:220`). -**The recredit rule.** Governance returns shadow to the holder with `sysio.liq::recredit(holder, -quantity)` (`contracts/sysio.liq/src/sysio.liq.cpp:85-92`) only for a release the outpost **skipped**, -and only after confirming the outpost has no stored payout for the request and never paid it: - -- Solana: no account exists at the `PendingPayout` PDA `["pending_desyndication", request_id_le8]` - (`pending_payout.rs:85-90`), and no `DesyndicateLIQDeferred` or `pay_pending_desyndication: - request_id= paid` log names the id; -- Ethereum: `pendingDesyndications(requestId)` returns a zero `depotAmount` and - `desyndicationsPaid(requestId)` is false (`SyndicationPool.sol:145`, `:182`). - -A stored release is paid by the crank; recrediting it as well pays the holder twice. It also turns into -a real shortfall: the crank's payment takes custody down by the amount while the recredit has raised the -outstanding by the same amount. The `already paid` and `already pending` drops on Ethereum -(`SyndicationPool.sol:609-612`, `:638-641`, `:687-690`) and `DesyndicateLIQAlreadyPending` on Solana (`inbound.rs:5361-5378`) -are re-emits of an id the outpost has already paid or stored: never recredit them. The quantity to -recredit is the `desyndlog` row's net `amount` for the request id; the holder is the account that signed -the `desyndicate`. - -Nothing is recorded in `sysio.synd` for a recredit. `recredit` grows the supply directly -(`sysio.liq.cpp:91`), so the next message from the outpost is compared with an outstanding that already -includes it, and the custody the outpost never paid out still covers it. The ledger's running sums -(`syndicated_sum`, `yield_sum`, `desyndicated_sum`) are not part of the comparison. +**Return resolution.** Both actions below require `sysio` and an existing outstanding request: + +- `finishreturn(request_id)` attests the external obligation was paid/completed and removes it without + any depot credit. +- `refundreturn(request_id)` attests irreversible external rejection/cancellation: nothing was paid and + no payable external obligation remains. It restores the exact stored net amount to the original + holder, then consumes the obligation atomically. The fee stays charged. A failed mint rolls back the + removal. Repeating either action on an already resolved ID fails; IDs are never reused. + +These are trusted governance determinations based on external evidence, not on-chain verification of +external finality. A queued or pending payout, timeout, transient refusal, or duplicate-request response +never justifies a refund: the external obligation may still pay. Governance must establish irreversible +rejection/cancellation, including no stored payout and no prior payment. In particular an `already paid` +or `already pending` response is not rejection of the underlying obligation. + +No receipt protocol, automatic timeout refund, or permanent return history is introduced. The original +holder and destination are retained while needed; governance does not reconstruct them from old traces. +`sysio.liq::recredit(holder, quantity)` remains its existing exceptional privileged supply-repair facility, +with unchanged authorization. It is not the normal return recovery interface and has no request-level +deduplication; operators must use `refundreturn` for these outstanding obligations. ## The solvency check @@ -336,9 +321,9 @@ clio -u "$DEPOT_URL" get table sysio.synd mismatch -S sysio.synd ``` For that `chain_code`, `syndcursors.last_sequence` must reach the post-repair message's `sequence`, -and there must be no `mismatch` row for `(chain_code, sequence)`; read all result pages. Record the +and the latest `(chain_code, token_code)` incident must be reconciled explicitly; read all result pages. Record the message's emission after repair and its actual admission. `admit_sequence` drops any sequence at or -below the cursor, so a cursor already past the message plus no mismatch does not alone prove that +below the cursor, so a cursor already past the message plus no incident does not alone prove that message was admitted: verify its admission trace or observe a later post-repair message being admitted. Only after this check passes for every repaired outpost may the depot cord clear. Mismatch rows for earlier pre-repair sequences reporting the known shortfall are expected and must be recorded as part @@ -502,14 +487,14 @@ within it: a pull in the epoch the previous freeze was cleared in starts its fre | `items` | envelope, then id (arrival order) | kind, beneficiary key family and pubkey, amount, remaining, yield position | | `parked` | token, key family, pubkey | balance and yield position of an unlinked pubkey | | `buckets` | outpost, token, direction | level, the depot epoch last ticked and the cord's frozen-epoch count at that tick (`frozen_mark`), one per direction (SYNDICATION, DESYNDICATION) | -| `ledger` | outpost, token | syndicated, yield and desyndicated (net) running sums; the queue's `queue_epoch` cursor | -| `desyndlog` | request id | outpost, token, net amount, the desyndicated sum after it, the outstanding shadow after the burn (`total_syndicated`) | +| `ledger` | outpost, token | release and underwriting cursors; `retained_from_epoch` replay floor | +| `returns` | request id | original holder, destination family/key, outpost, token and exact net burned amount; erased upon explicit resolution | | `feepot` | token | collected fees, with a yield position; bounties are paid from it, and its yield goes to `sysio` | | `yieldpool` | token | the WIRE `sysio.synd`'s holder row earned, pulled from `sysio.liq` and credited out to held, parked and fee positions | | `syndcursors` | outpost | the highest sequence admitted (replay guard) | | `syndcounters` | singleton | the next item id and the next desyndication request id | | `syndstate` | singleton | whether the launch import is closed; the pair the next queue step starts at | -| `mismatch` | outpost, sequence | each custody shortfall: token, depot epoch, the message's sequence and kind (SYNDICATION or YIELD), the custody it reported, the outstanding it was compared with, and when. An admitted sequence is unique per outpost, so the row needs no id. Rows accumulate: nothing prunes them at v1 | +| `mismatch` | outpost, token | latest unresolved shortfall evidence: epoch, sequence, kind, reported custody, expected outstanding, and time | Envelope states: OPEN, WAITING, REQUESTED, RELEASABLE, HELD, INVALID, DONE. @@ -554,7 +539,7 @@ desyndication. The row must exist, with the buckets set, before the pair carries left where it was, with the reason. - `envelopes` shows where each envelope stands; `ledger.queue_epoch` is where each pair's queue starts; `buckets` shows each pair's remaining budget. -- `mismatch` lists every custody shortfall an outpost reported; a `SHORTFALL` line on the console +- `mismatch` lists the current unresolved incident for each affected outpost/token; a `SHORTFALL` line on the console goes with each row, and an `EXCESS` line with each message that reported more custody than the depot has outstanding. - `crank(limit)` moves the queue when no envelope is arriving. It is permissionless and never throws. @@ -593,3 +578,25 @@ Later envelopes of the same pair wait behind it; other pairs are not affected. the row lifts the stall; the queue resumes on the next step. - **An unset desyndication bucket.** With no row, or a zero `desynd_burst`, every `desyndicate` of the pair is refused. This refuses the signed action; it does not stall the queue. + +## Operational retention and reconciliation + +No historical reporting API or archive is maintained. Lifetime ledger totals are removed. Replay +sequences, counters, import completion, active custody/yield obligations and unresolved returns remain. + +`pruneenv(chain_code, token_code, limit)` is permissionless and examines at most `limit` envelopes from +the pair's oldest retained row, stopping at the first unfinished one. It erases only DONE/INVALID rows +with a consumed terminal outcome (or a never-issued governance drop), no items or pending challenger +share, and an acknowledged bond result if that request still exists. DONE before finality is retained. +Settled rows behind a live earlier envelope remain temporarily; this prefix rule avoids a permanent +per-envelope tombstone table. The widened `retained_from_epoch` floor rejects all future admission below +it, including fresh sequences, different digests, and previously absent epochs in that prefix. + +A later shortfall replaces the same pair's incident; a healthy report does not erase it. +`reconcile(chain_code, token_code, reported)` requires `sysio` and an existing incident, verifies the +attested custody covers current live outstanding supply, and erases only that incident. `reported` is a +governance attestation, not a fresh oracle measurement. The global Andon cord must be cleared separately +after every relevant incident and other cause has been resolved. + +Existing deployed tables require a coordinated migration for the changed layouts and renamed return +table; no automatic migration or recovery from previously discarded data is included. From 30bf24e57a4f091456c04af03e786a0990de2f78 Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Sun, 4 Oct 2026 20:44:10 -0400 Subject: [PATCH 2/5] Remove retired external settlement paths Remove sysio.reserv, sysio.uwrit and the swap-underwriter plugin. Retire external collateral, reserve and swap dispatch, clients and fixtures while preserving native collateral, LIQ settlement and OPP. Keep protocol identifiers and compatibility fields stable. Validation: all-target build; 39 contract suites; host relay tests; 13 enabled flows mapped to 67 native cases; live LIQ wallet settlement. Change-Id: I322fd657526d821325c740f44a706dfd206e0893 --- CLAUDE.md | 2 +- cmake/chain-tools.cmake | 1 - contracts/CMakeLists.txt | 2 - contracts/sysio.andon/sysio.andon.wasm | Bin 13756 -> 14067 bytes contracts/sysio.authex/sysio.authex.wasm | Bin 36689 -> 44293 bytes contracts/sysio.bios/sysio.bios.wasm | Bin 40789 -> 53891 bytes contracts/sysio.bond/sysio.bond.wasm | Bin 46200 -> 60601 bytes contracts/sysio.chains/sysio.chains.wasm | Bin 39121 -> 41528 bytes contracts/sysio.chalg/CMakeLists.txt | 2 - .../include/sysio.chalg/sysio.chalg.hpp | 182 - contracts/sysio.chalg/src/sysio.chalg.cpp | 510 -- contracts/sysio.chalg/sysio.chalg.abi | 327 +- contracts/sysio.chalg/sysio.chalg.wasm | Bin 68874 -> 32009 bytes contracts/sysio.councl/sysio.councl.wasm | Bin 48345 -> 62727 bytes contracts/sysio.dclaim/CMakeLists.txt | 1 - contracts/sysio.dclaim/sysio.dclaim.wasm | Bin 31229 -> 40384 bytes contracts/sysio.epoch/CMakeLists.txt | 2 - .../include/sysio.epoch/sysio.epoch.hpp | 2 - contracts/sysio.epoch/src/sysio.epoch.cpp | 87 - contracts/sysio.epoch/sysio.epoch.wasm | Bin 79865 -> 95314 bytes contracts/sysio.liq/sysio.liq.wasm | Bin 43308 -> 50926 bytes contracts/sysio.msgch/src/sysio.msgch.cpp | 297 +- contracts/sysio.msgch/sysio.msgch.wasm | Bin 173859 -> 177916 bytes contracts/sysio.msig/sysio.msig.wasm | Bin 36752 -> 39453 bytes contracts/sysio.opreg/CMakeLists.txt | 1 - .../include/sysio.opreg/sysio.opreg.hpp | 207 +- contracts/sysio.opreg/src/sysio.opreg.cpp | 603 +- contracts/sysio.opreg/sysio.opreg.abi | 93 - contracts/sysio.opreg/sysio.opreg.wasm | Bin 106138 -> 115210 bytes contracts/sysio.reserv/CMakeLists.txt | 47 - .../include/sysio.reserv/sysio.reserv.hpp | 741 -- contracts/sysio.reserv/src/sysio.reserv.cpp | 1268 ---- contracts/sysio.reserv/sysio.reserv.abi | 909 --- contracts/sysio.reserv/sysio.reserv.wasm | Bin 89303 -> 0 bytes contracts/sysio.roa/sysio.roa.wasm | Bin 53726 -> 76719 bytes contracts/sysio.swap/sysio.swap.wasm | Bin 49661 -> 59215 bytes contracts/sysio.synd/sysio.synd.wasm | Bin 105651 -> 141906 bytes contracts/sysio.system/src/emissions.cpp | 93 +- contracts/sysio.system/sysio.system.wasm | Bin 202818 -> 277387 bytes contracts/sysio.token/sysio.token.wasm | Bin 10465 -> 12623 bytes contracts/sysio.tokens/sysio.tokens.wasm | Bin 30245 -> 33639 bytes contracts/sysio.uwrit/CMakeLists.txt | 50 - contracts/sysio.uwrit/README.md | 101 - .../include/sysio.uwrit/sysio.uwrit.hpp | 1007 --- contracts/sysio.uwrit/src/sysio.uwrit.cpp | 2783 -------- contracts/sysio.uwrit/sysio.uwrit.abi | 940 --- contracts/sysio.uwrit/sysio.uwrit.wasm | Bin 161033 -> 0 bytes contracts/sysio.wrap/sysio.wrap.wasm | Bin 5033 -> 6814 bytes contracts/tests/CMakeLists.txt | 24 +- contracts/tests/contracts.hpp.in | 4 - contracts/tests/depot_flow_coverage.json | 4 +- contracts/tests/emissions_tests.cpp | 456 +- contracts/tests/sysio.dispatch_tests.cpp | 6236 +---------------- contracts/tests/sysio.dispute_tests.cpp | 41 +- .../tests/sysio.epoch_flushwtdw_tests.cpp | 190 +- contracts/tests/sysio.msgch_chain_tests.cpp | 40 +- contracts/tests/sysio.opreg_tests.cpp | 259 +- contracts/tests/sysio.reserv_tests.cpp | 1935 ----- contracts/tests/sysio.uwrit_tests.cpp | 471 -- .../sysio/opp/uic_signature_canonical.hpp | 148 - .../opp/test/uic_signature_test_utils.hpp | 147 - plugins/CMakeLists.txt | 1 - .../test/mocks/mock_outpost_client.hpp | 24 +- .../sysio/http_plugin/api_category.hpp | 7 +- plugins/http_plugin/src/http_plugin.cpp | 28 +- plugins/http_plugin/test/unit_tests.cpp | 43 +- .../sysio/outpost_client/outpost_client.hpp | 37 - .../test/test_outpost_client_interface.cpp | 3 - .../sysio/outpost_ethereum_client_plugin.hpp | 28 +- .../outpost_ethereum_client.hpp | 10 +- .../src/outpost_ethereum_client.cpp | 42 +- .../src/outpost_ethereum_client_plugin.cpp | 4 +- .../test/test_ethereum_transaction_policy.cpp | 9 - .../test_outpost_ethereum_client_plugin.cpp | 6 - .../sysio/outpost_solana_client_plugin.hpp | 83 +- .../outpost_solana_client.hpp | 339 +- .../src/outpost_solana_client.cpp | 650 +- .../test_outpost_solana_client_plugin.cpp | 1370 +--- plugins/underwriter_plugin/CMakeLists.txt | 20 - plugins/underwriter_plugin/README.md | 276 - .../underwriter_plugin/routing_detail.hpp | 175 - .../solana_source_deposit_scanner.hpp | 324 - .../source_deposit_constants.hpp | 113 - .../source_deposit_hash_detail.hpp | 62 - .../sysio/underwriter_plugin/sync_detail.hpp | 149 - .../uic_construction_detail.hpp | 53 - .../uic_signature_detail.hpp | 279 - .../underwriter_plugin/underwriter_plugin.hpp | 72 - .../variant_enum_detail.hpp | 51 - .../src/underwriter_plugin.cpp | 3054 -------- plugins/underwriter_plugin/test/main.cpp | 2 - .../test/test_underwriter_plugin.cpp | 1238 ---- .../test/test_underwriter_routing.cpp | 233 - .../test/test_underwriter_sync.cpp | 147 - programs/nodeop/main.cpp | 2 - .../fixtures/solana-idl-opp-outpost-stub.json | 199 +- 96 files changed, 785 insertions(+), 28561 deletions(-) delete mode 100644 contracts/sysio.reserv/CMakeLists.txt delete mode 100644 contracts/sysio.reserv/include/sysio.reserv/sysio.reserv.hpp delete mode 100644 contracts/sysio.reserv/src/sysio.reserv.cpp delete mode 100644 contracts/sysio.reserv/sysio.reserv.abi delete mode 100755 contracts/sysio.reserv/sysio.reserv.wasm delete mode 100644 contracts/sysio.uwrit/CMakeLists.txt delete mode 100644 contracts/sysio.uwrit/README.md delete mode 100644 contracts/sysio.uwrit/include/sysio.uwrit/sysio.uwrit.hpp delete mode 100644 contracts/sysio.uwrit/src/sysio.uwrit.cpp delete mode 100644 contracts/sysio.uwrit/sysio.uwrit.abi delete mode 100755 contracts/sysio.uwrit/sysio.uwrit.wasm delete mode 100644 contracts/tests/sysio.reserv_tests.cpp delete mode 100644 contracts/tests/sysio.uwrit_tests.cpp delete mode 100644 libraries/opp/include/sysio/opp/uic_signature_canonical.hpp delete mode 100644 libraries/opp/test/include/sysio/opp/test/uic_signature_test_utils.hpp delete mode 100644 plugins/underwriter_plugin/CMakeLists.txt delete mode 100644 plugins/underwriter_plugin/README.md delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/routing_detail.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/solana_source_deposit_scanner.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_constants.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_hash_detail.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/sync_detail.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_construction_detail.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_signature_detail.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/underwriter_plugin.hpp delete mode 100644 plugins/underwriter_plugin/include/sysio/underwriter_plugin/variant_enum_detail.hpp delete mode 100644 plugins/underwriter_plugin/src/underwriter_plugin.cpp delete mode 100644 plugins/underwriter_plugin/test/main.cpp delete mode 100644 plugins/underwriter_plugin/test/test_underwriter_plugin.cpp delete mode 100644 plugins/underwriter_plugin/test/test_underwriter_routing.cpp delete mode 100644 plugins/underwriter_plugin/test/test_underwriter_sync.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 6c3c147795..5c7b5538b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -405,7 +405,7 @@ find contracts/ unittests/test-contracts/ -name "*.actions.cpp" -o -name "*.disp After building contracts, you MUST copy the compiled `.wasm` and `.abi` files from the build directory back to the source tree. This is required before generating system contract types and before testing. ```bash -for c in epoch opreg msgch uwrit chalg authex; do +for c in epoch opreg msgch chalg authex synd bond; do cp "$BUILD_DIR/contracts/sysio.$c/sysio.$c.wasm" "contracts/sysio.$c/" 2>/dev/null cp "$BUILD_DIR/contracts/sysio.$c/sysio.$c.abi" "contracts/sysio.$c/" 2>/dev/null done diff --git a/cmake/chain-tools.cmake b/cmake/chain-tools.cmake index a1c26ea906..c62891999d 100644 --- a/cmake/chain-tools.cmake +++ b/cmake/chain-tools.cmake @@ -12,7 +12,6 @@ macro(chain_target TARGET) PRIVATE batch_operator_plugin external_debugging_plugin - underwriter_plugin chain_api_plugin db_size_api_plugin net_api_plugin diff --git a/contracts/CMakeLists.txt b/contracts/CMakeLists.txt index 1dcd1ff0d5..ab2759f5ea 100644 --- a/contracts/CMakeLists.txt +++ b/contracts/CMakeLists.txt @@ -50,10 +50,8 @@ add_subdirectory(sysio.tokens) add_subdirectory(sysio.epoch) add_subdirectory(sysio.opreg) add_subdirectory(sysio.msgch) -add_subdirectory(sysio.uwrit) add_subdirectory(sysio.chalg) add_subdirectory(sysio.councl) -add_subdirectory(sysio.reserv) add_subdirectory(sysio.dclaim) add_subdirectory(sysio.andon) diff --git a/contracts/sysio.andon/sysio.andon.wasm b/contracts/sysio.andon/sysio.andon.wasm index a7f829daac5adb5862f05d148108b9a0aa353f29..38afcff125a1dec72b5b919a85455e9285f43695 100755 GIT binary patch literal 14067 zcmeI3eQaLWUB}Nk_dYN7V>dT#x@AeX-sdiW1IZ7UT;)2L9jX_B`Use>Vcw8AEhEkXzl2~E|I&?F|p z=leVNJ}B>*A=9@0z9g`s%Cc?qo}I`De@VIbps#8Lp^#h2P=V!>(ju`Qp^t z!ubn}YYQjlFZ9niudd0(^%L`_POV&6?z>=P)-SIwuAG=(Utd`3yF^`$`S-8Tc4{}z zEu33l=)1IjIkkGZdRe-7LSGvC(q3I#T<))*I2%`(I(1=fZDF~8qQ7`1v?uPnH>b+0YVp9aRkH#cG3^^$7~rxq43F0936 zn=JbfP)usf?onLW?ev1{BzGTnQ`2s0YMOr&x1_s#vvW%}G0{nO-_qtevHR9rZ=IOn z|A~bECX(B3OB;UGd*4XB^UYT!8@{}{={g&(y!OSxqje}1X;#)Q2RC^l;;^uMI`N$o zC(bO)ubz1S{Q5!?8uP2GXD_>-p8V(s4yP`=wX^1vjWCzHX;->kos!=(Ve|gnmD$C@ z_lj_Yx7hpa{Y8?yUb(ARuAVR4k?`#OOblh#&)tKXPTTi1KKC@bzQbof=gN%-^tJr0 zO_zmEo^*oi9PxhR_3W`!@a0IDVlw+q%>*}G{u%boZJW~F$8+<3riBea^ZI#kf7ge( zge|hHaMQkYT?78P`-||_X`hGP&P};aCwHAD>twgZ$L{#Jz0+`6;;P@D^-v*q$KBDu z(6SBQ0?P@3LypbPPt%|KgTY1w5N!7VmLWM*)3BM4GHLba8A!X`O~Rc1gC?DK&=<;G zYefoKvfH|VpF`J_M_L9bW@{e_?I{nmqGEs6(Pu==g-j>+d6I{$u}`hgE1eD4)e#CG zfG7b7L_yXB-Xbae#hhQ~i)4S1{%iz3PnTF?|2~5^d8;}BH23vtxd*0Qf`G1(-Uv>| zXrz6Z$%DDt=gRqVqgPJaw(Q4l5ez|4-f-t56pj5wvXpxf2b=*FP?8=fB@(Udj(U2x z!S(`5xaZ7jXjmw3`17zi-Ny_GmJwEnvZLW)phxNS_=*bmPiFzpf?rU=tux5?G+Pmv;MS~H+%XDo}$FWQZ>w%lN!;^ZWk+% z698lzzlNMdhEGDX+#OAxzrTYDw>y94qc&~2r6L?~$FJ(&=4Zb8Kqf}{%$xdQGNgVX zcLx(DAA_uWVZEd;AoxiZA8q<`(fpWxwR?2W_MG?4!Rso@_K0A8`-!U zC(Tro%DZ~fWT3Wr<)-T$aGgg3l8Y6{mx|i2LU+p@-7eWiq z3niWi6)BEC^NCM>6?fFZIex=Mmp_nOu0f zJBdw{rD4wG@f+?Jk;%#9t;f1Z=rnqP${h(G3W!{qPk<21BGb*c?JwF#!f(*e2HiBw zvD&YimL$mHo+8yGX6H0M9A~$|vK@Ow(RSJT8I!aRe$gb&Kt`xFf=y)bP=W0A`^wLK z31h}ak7LPLU`7kJv*tb);pIc2Fx^SBR|(oDf@I|rAtC0tV)7y?A?L{J7?qeU(Jqjg z_e{HNs*9(Yv%T1@+6!;hE3bxZ8a6xF#+;OQ^c;Zn(Krlq?FEo+fY||8e&_Rs7ZRgY z+CnXkE88fi6bEG%o3dm3B!6kPr`y;8sx2^K6|!k9CG#Lbqa|YbyispL!)QIa1EhpL zA<_pdHrC5KFLwUL59x$=6`ACk^vf-ocp!>&bW@}R9(*zyX~{{^C|mutS+`3d%Lu1S zr5oXBH4mOhnj}6rH9UprfEuzWLMJ&%^9F*(D%uniE&vLTf+ce!OOFc6U}>gCoT~-# z#Bo6gpp}Ru@aUpHh`G+62K+X9Q&~O`!Xh0Tu|s0;OBlezw#`NCJkDUx)cs zTfg>*^}`aF6`Kipk#yOCJtScV%$}Gbp&Rl_8gu{dX>XcdU{L^nDkvn$H4NYPsZB&m zrX+`;JPveIp@(5c*#$g0vs7O7cq>N#l}4Laz&d9?RKdvT5sbS`(t0BRAE*GX%*(=UsW9*pHQ;b@%bOFKdaw z5DvhyGTp44;Gr>CoP6a@|MC1TjnZECm1m9 z0A0I+F0EZBOW|G!aq)sSj_nBwSQ+FMO7Pb1VuwW^;vAv)7=fmRwMqG5FC=5MpOha` zP%hq$4Z?eR@zO?D5NfC-ouP^#AMkEI}KYWx19jUC&On3%3 zU@I@~!Tq^^*M7nS$#|4vBzmY0*};lEPYz-Kl)*k=+%~Kt%-6iU`wJ=IId=Ry5o`Wj3B5cl@FO|xP`5C{IFCU_!E z9b+C0cO4A}FgYd*d&v-MYpJNAid_d?$Vw$MRhNVcR>JB^q?&YyH^{f5V;g-}4?&a& zy7Hc?>mJQAMn?E0;$#d}C;UB_%qf^}RFa`lbIkvW862oYqaIv-x$dM<@1%hFokzo4 zEMTtVV-^4@x!F;SghTb-Rg z9G&_WV3WYl4U_^S#9jlz$_F22s(y%x4MMCw8+=1z^&(>B6fhjW`pjcbe^jZ_(c}!T zFI@ZWkAM5QPj3=sn-eSl{jZbZ7$Bw~V}8pUd2sQO%tA$!7N_7x-5i)lDl-&HVv??p zhhuGlJXf82?G5oWlPU2Nhfz^iVleSTKW5(k>oMGv`RjUF!8=sgz&@$Apm*Fy9^?no z5+|4>bJB7~S6)TYsM!EH78wFO0i@AT3_l)1OGe*_9HJT;CSobJ@|`~nflAYOR`Tax z-%OQ|5ar$>Vyil`SJA*lU{LnoDBq;Kqw#dNi5ON{NYk%znl+W|+1p+8)?m#oqm42eGTa%({c6L*<Y4_ zgNlH$C?Z|9Z1#7XNx` zdZ3<}K{B8>B?IV{5S1q+_ppp9>t)IbMk>B3&Sww-%nTxc!-L3x;RFq5>Xg8hfAIxZ z-fP0E9qz@^-AhWqB6xNJVInD|1s@icd_ObH3mRj2L6A3A z_fvKc<4u8+GA9Z*JtPuRFYZ_}L?nd}O9~KT73pE9gaj3v$?hBhCWdm^ZsB%V!Rr5Q z)DFBHYTpRM^lnGCNl1Um=pWu2+3ufm700yjhJ$keGR`$E6BKRt=1uJzutUZ@q+ z*^V{kkIT(hgP3}kbC6y#L+yvvRgl*)ZmKibBJ}fQO?J0z5h>+O>@0-`3a*jA*gXZp zR`GWQr!;o-tXwBu%I>w}0A?6Kc6?Qd0iW&I9JmsyIBY63Et+aB5s|P^g8{@OW{Il> zWVYe!FdpR`Rq(pb|C^hg&CMaGiXPPQ$W5D@>&e)uOU*0sRY_2SmX7m^+(B{G&bh3XaAD=Q?-TngPAC*_8cs!e^o;z2645HXc9VGPTF{$07_$en&6+$&uuVL$f=# z0bz$aL=0(}59Jkw)naIqDQ|Y9P$fUs&|zST(-^!H*Qdj3oJABW_Q&rv29W_fp&{e2 zGbVXB0n!;X3uZpw1tea9_FT3HT!uq;Q|*>c2l%A&DUPMs@) z?ybmpgr)4LS(YlKQFWew6*f;2OidE)^yWR$}& zJ7)4Za0P7H9?Tf_E7xlH8>}HGx{zmzsxR|wuTn_cR|VnzUI#XKYum-?-s^?Gcuu{oNaP z#^!(AXwhg^i?!;h?Rv3^x@WTP>C`<_>JfO4JtBt>g3y!_iM~IgUy?#Vy)$C=x`^3p zg_%MbzGUOb?XFk8wchKtx@S+_b9>$Ms#lEMtLv$I>&0GE_w1{C_SZdksHa8_kGKPJ zlwLbuY7CA++wmCGtk=}JT&Jxl_C_=gzD#g@=Kjlv&v!ink(e)*O{ybA-If;&8RZmfb!Jd?H1j1*%=1z%Gb{4N!l z@^7Dd;VE6UvH=9dQJHjkNydgBPG?`x7~tjcg5z|yDccuMW}Ec_xL0(FsE$+E7JeT6 zQc4SIMv@dFDVX;$mIKQ3L=NSW}x@7k9 zM#!tk!(l1r0CY933U#T2!*x~8i~3q03ZN0HEyzw(2Q@M90!@rlb+pQTEIUkAWaV=# zimaV>B8r44n#|C*O^bKqm3dvBLvD0W#74;!5vV@EGF4?BMo*<;b$uGmigCAU8(Uuj zW+9+rzs^Vg*UdF|0!GdCo1(dn<~+&=+7W|1nQ9L0?Oun_fa)^Z;#U_g(Lj z2SKC7F_UBaYKm?xQ*>*YqFc)p-C8(a)AUO_X*v>OVvHt3=Ss`v7KV2(l(7X}z!<9d z)(aS8DqhW&ireLjO2y%eE*?NOuT%|FmN!hrQ)PNyx6JUh0~N23*h(Z4pZ`H3rCfK5 zHpH}DAd?1|{6sQQISoq|*XgcGybmJX9o`247n03*A0+CoRmp_5*?Dwo^ghUV^}muS z+pyR%XjHi$=^2k3tAJJU)-H>;un`ilVf?w1z!N#c(X9e+Fni*`o0%FoJhDR+FI>OmzwUqv-_P>ph zLSj(;|DLz%zuj;gySYckhF7mHbnky!L6~f1B9E4*@d97E;{`sT*?xgfBBg^^S&ElD zn36Y@Ii%Kzw+)(#HFcw2GzjcC$8Z$60tYg5gAVsD5k7{g4DMPf43Ts^Y;a>*{tIz$-pKR#bq`hx`{hz<`wd3#MO195^_67yN^}rWD_SADu*W)zsdR(U-_~Te` zP56oGd05Z5zV3f8t8~YGc1x!-<+5El)))uzQ=QIp2^X%eZo1B=lj_fs^3y8|>-q9Z zKVP`CxZZakm|r}*a60d=eEYHsB6H#mjQLsJ#=>E{qtwHqNcxm zTJHn-xqQCgUpTkgkBABv;9T$9Cj42E)1Nr)jDbLpI0)ENXBXzz#-X*Yad1W4mjv~t UfcoXNMI-$PRPeNuUh6vdpH={W$^ZZW literal 13756 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 XU9zr6z{h%vCblsO(1$ePNW1$lR6j0Y diff --git a/contracts/sysio.authex/sysio.authex.wasm b/contracts/sysio.authex/sysio.authex.wasm index 28f7a317bec0c75339c8ffff0e1539dd003c49ff..7ff42952e2906d95a5f848558ec05016c3521756 100755 GIT binary patch literal 44293 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 II?ZhSKPU2LF8}}l literal 36689 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 diff --git a/contracts/sysio.bios/sysio.bios.wasm b/contracts/sysio.bios/sysio.bios.wasm index 00e966b25d7b5d9968d902ab80ea0efa401c4348..08ab88e9c1f065f5a1d707f934746a52d46ff596 100755 GIT binary patch literal 53891 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 literal 40789 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 L7jsjKJ@5YlzMD|4 diff --git a/contracts/sysio.bond/sysio.bond.wasm b/contracts/sysio.bond/sysio.bond.wasm index cc4391e4b870c728fe45fad8b3530ef2e7da5e5c..c58fed5c11139f5b2a2508cfd7d43b8bd342ca7c 100755 GIT binary patch literal 60601 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 literal 173859 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≦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 S1pz4A50mDM@0u?;&i?={Y_JUg diff --git a/contracts/sysio.msig/sysio.msig.wasm b/contracts/sysio.msig/sysio.msig.wasm index 0f890ca017faaa08b66c3dab7c642e1acf58aebe..cda85eb812b490d25b133b90251eb96bf57d3b1f 100755 GIT binary patch literal 39453 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 Z?_~sw$KO9&0*y_70#|e!#p{yf{{i5kz7YTb literal 36752 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 diff --git a/contracts/sysio.opreg/CMakeLists.txt b/contracts/sysio.opreg/CMakeLists.txt index 611db605ba..7586c2ae58 100644 --- a/contracts/sysio.opreg/CMakeLists.txt +++ b/contracts/sysio.opreg/CMakeLists.txt @@ -34,7 +34,6 @@ if(BUILD_SYSTEM_CONTRACTS) $ $ $ - $ $ $ $ diff --git a/contracts/sysio.opreg/include/sysio.opreg/sysio.opreg.hpp b/contracts/sysio.opreg/include/sysio.opreg/sysio.opreg.hpp index bd4ded126e..f370b51596 100644 --- a/contracts/sysio.opreg/include/sysio.opreg/sysio.opreg.hpp +++ b/contracts/sysio.opreg/include/sysio.opreg/sysio.opreg.hpp @@ -18,28 +18,11 @@ namespace sysio { /** * @brief sysio.opreg — operator registry on WIRE. * - * Holds the **authoritative** collateral ledger for every operator type - * (producers, batch operators, underwriters, plus their standby tiers). - * Per the corrected ledger model in - * `CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md`: - * - * - One aggregate balance per (operator, chain, token_kind) — NOT a - * vector of collateral entries, NOT a locked/available/amount trio. - * - `deposit` (operator-callable, depot-native WIRE or shadow LIQ) and `depositinle` - * (msgch-dispatched from outposts) credit the matching balance row; - * `withdraw` (operator-callable, depot-native) and `withdrawinle` - * (msgch-dispatched from outposts) enqueue a delayed subtraction; - * `slash` zeros the unlocked portion immediately and leaves locks - * for `sysio.uwrit::release` to slash deferred. - * - Underwriter locks live entirely in `sysio.uwrit::locks` — opreg - * reads them directly via the public `sysio::uwrit::locks_t` table - * type to compute `available()`. - * - Pending withdraws are also subtracted by `available()` so an - * operator can't double-use queued funds; `cancelwtdw` lets them - * walk back a queued withdraw before it executes. - * - Termination is administrative (status -> TERMINATED, balance - * remitted to the operator's authex destination); slashing is - * punitive (status -> SLASHED, balance routed to the matching LP). + * Authoritative depot-native operator collateral ledger. + * Deposits custody WIRE or shadow LIQ; withdrawals reserve balances until + * maturity, then credit pull claims. Termination returns the remaining + * balance through claims. Slashing removes the balance and retains custody. + * Pending withdrawals reduce availability; shadow collateral earns yield. */ class [[sysio::contract("sysio.opreg")]] opreg : public contract { public: @@ -48,7 +31,7 @@ namespace sysio { // Well-known accounts static constexpr name EPOCH_ACCOUNT = "sysio.epoch"_n; static constexpr name MSGCH_ACCOUNT = "sysio.msgch"_n; - static constexpr name UWRIT_ACCOUNT = "sysio.uwrit"_n; + static constexpr name CHALG_ACCOUNT = "sysio.chalg"_n; static constexpr name AUTHEX_ACCOUNT = "sysio.authex"_n; static constexpr name TOKEN_ACCOUNT = "sysio.token"_n; @@ -140,11 +123,6 @@ namespace sysio { static constexpr uint32_t MAX_DELLOG_PRUNE_PER_WRITE = 4; static constexpr uint32_t MAX_DELLOG_PRUNE_PER_CRANK = 64; - /// Bounded sweep size for TERMINATED operator rows in `prune`, counted in - /// rows EXAMINED (not rows erased). Every candidate costs a - /// `sysio.uwrit::locks` index probe whether or not it turns out to be - /// prunable, so the cap must bound the scan itself to keep this - /// permissionless crank inside its transaction CPU deadline. static constexpr uint32_t MAX_OPERATOR_PRUNE_PER_CRANK = 20; // Per-operator audit log: ring-buffer cap (newest-in / oldest-out) and @@ -237,60 +215,9 @@ namespace sysio { [[sysio::action]] void deposit(name account, sysio::slug_name token_code, uint64_t amount); - /// Internal: credit an outpost-side collateral row. Called by - /// `sysio.msgch` when it dispatches an `OPERATOR_ACTION(DEPOSIT_REQUEST)` - /// attestation that came in from an outpost. - /// - /// A credit naming the depot chain (`chain_code == opp::wire::chain_code`) is dropped and - /// logged, never credited: depot-native collateral is bonded only through `deposit`, whose - /// custody transfer funds it, and no outpost holds anything to revert. - /// - /// Validation failures (unknown account, slashed/terminated operator, - /// zero amount) DO NOT revert — they are recorded in the operator's - /// `recent_actions` log (when an entry exists) and trigger an outbound - /// `DEPOSIT_REVERT` attestation back to the source outpost so escrowed - /// funds get refunded to the depositor (minus the outpost-side gas - /// penalty). Reverting would abort the entire envelope's dispatch. - /// - /// `actor_chain` + `actor_address` form the depositor's source-chain - /// `ChainAddress` (refund target on DEPOSIT_REVERT). They're split - /// here per the no-proto-messages-in-actions rule — - /// `opp::types::ChainAddress` would leak `bytes` typedefs into the - /// ABI. `original_message_id` is the OPP message id of the inbound - /// DEPOSIT_REQUEST attestation — outposts match on it to scope the - /// refund to one specific in-flight deposit. - [[sysio::action]] - void depositinle(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code, - uint64_t amount, - opp::types::ChainKind actor_chain, - std::vector actor_address, - checksum256 original_message_id); - - /// Operator-callable: queue a withdrawal of depot-native collateral from the - /// `(opp::wire::chain_code, token_code)` row (WIRE or a shadow LIQ symbol; see `deposit`), - /// subject to the WITHDRAW_WAIT_EPOCHS wait. When it matures, `flushwtdw` credits a - /// `remitclaims` row in `token_code` that the operator pulls with `claimremit`. - /// Outpost-held collateral is withdrawn by calling the holding outpost's withdraw entry - /// point — the outpost emits an OPERATOR_ACTION(WITHDRAW_REQUEST) inbound that reaches - /// `withdrawinle` instead. - /// - /// A `token_code` this contract cannot custody reverts, like `deposit`. Every other - /// validation failure DOES NOT revert — it is appended to the operator's `recent_actions` - /// ring buffer (matching the OPP-dispatched path's failure semantics). [[sysio::action]] void withdraw(name account, sysio::slug_name token_code, uint64_t amount); - /// Internal: queue an outpost-side collateral withdrawal. Called by - /// `sysio.msgch` when it dispatches an `OPERATOR_ACTION(WITHDRAW_REQUEST)` - /// attestation that came in from an outpost. Subject to the - /// WITHDRAW_WAIT_EPOCHS wait. Validation failures are logged on the - /// operator's `recent_actions` ring buffer; the dispatch tx commits - /// so other attestations in the same envelope still apply. - [[sysio::action]] - void withdrawinle(name account, sysio::slug_name chain_code, sysio::slug_name token_code, uint64_t amount); - /// Operator-callable: cancel a previously-queued withdrawal before it /// flushes. The reserved amount rejoins the operator's `available()`. [[sysio::action]] @@ -304,7 +231,7 @@ namespace sysio { /// Read-only rollup of the operator's spendable balance for a given /// (chain, token_kind). Returns 0 if the operator is SLASHED / /// TERMINATED, or if no balance row exists. Otherwise returns - /// `balance - sum(active locks on uwrit) - sum(pending withdraws)`. + /// `balance - sum(pending withdraws)`. [[sysio::action, sysio::read_only]] uint64_t available(name account, sysio::slug_name chain_code, sysio::slug_name token_code); @@ -320,31 +247,9 @@ namespace sysio { [[sysio::action]] void processuw(name account, bool was_eligible, bool is_eligible); - /// Slash an operator. Permanent. Called by `sysio.chalg`. Routes the - /// **immediately slashable** portion (`balance - sum(active locks)`) to - /// the matching LP on each chain via OPERATOR_ACTION(SLASH) attestations. - /// The locked portion stays in opreg's balance and is slashed at lock- - /// release time by `sysio.uwrit::release` (deferred-slash). - /// - /// A depot-native shadow row is settled before the subtraction, so the yield it earned up to - /// the slash stays on the row, claimable with `claimyield`. Its locked remainder keeps earning - /// until `releaselock` performs the deferred slash, consistent with the deferred-slash - /// semantics: until then that shadow is still the operator's balance. [[sysio::action]] void slash(name account, std::string reason); - /// Called by `sysio.uwrit::release` when an underwriter lock resolves. - /// Opreg consults its own current status for the operator and routes - /// the released amount appropriately: - /// * SLASHED — decrement balance, emit OPERATOR_ACTION(SLASH) (deferred-slash). - /// * TERMINATED — decrement balance, emit WITHDRAW_REMIT (deferred-remit - /// to the operator's authex destination). - /// * else — no-op (balance was never decremented at lock time; - /// the freed amount naturally reappears in `available()` - /// the moment uwrit erases the lock row). - [[sysio::action]] - void releaselock(name account, sysio::slug_name chain_code, sysio::slug_name token_code, uint64_t amount); - /// Record per-batch-op delivery hit/miss for the rolling 24h buffer. /// Called inline from `sysio.epoch::advance` after each delivery cycle. /// Each write also sweeps up to `MAX_DELLOG_PRUNE_PER_WRITE` rows that @@ -360,22 +265,6 @@ namespace sysio { [[sysio::action]] void termcheck(name account); - /// Administratively terminate an operator. Status -> TERMINATED. Each - /// (chain, token_kind) balance is remitted to the operator's authex - /// destination via `OPERATOR_ACTION(WITHDRAW_REMIT)` attestations. - /// Locks remain alive — `sysio.uwrit::release` will deferred-remit - /// each lock at its natural release time. - /// - /// Earned shadow yield is settled and credited to the operator's WIRE claim alongside the - /// principal, but only the part `yieldpool` covers: this path is never-throw and cannot pull - /// from `sysio.liq`. The uncovered remainder STAYS on the row, and the terminated operator - /// collects it with `claimyield`, which pulls first. Each later `releaselock` on the TERMINATED - /// operator pays the same way. - /// - /// Claim window: banked yield never keeps a row from `prune` (or from the operator's own - /// re-registration), so whatever is still unclaimed when the row is erased is forfeited to the - /// registry. The `terminate_prune_delay_ms` window is therefore the time a terminated operator - /// has to call `claimyield` -- or have anyone call it for them, since it is permissionless. [[sysio::action]] void terminate(name account, std::string reason); @@ -451,20 +340,6 @@ namespace sysio { [[sysio::action]] void claimyield(name account, sysio::slug_name token_code); - /// Prune terminated operator rows, plus delivery-log rows that have aged - /// out of the rolling termination window. Permissionless. - /// - /// A terminated row is erased only once BOTH the prune delay has elapsed - /// AND the operator is completely settled — every balance bucket drained - /// and no `sysio.uwrit` lock still naming the account (WNS-01). - /// `terminate` deliberately retains the locked portion of each balance for - /// `releaselock` to settle later, and `releaselock` no-ops once the row is - /// gone; erasing on the delay alone therefore stranded that collateral, - /// and any caller could trigger it. - /// - /// Banked shadow yield is NOT part of "settled": it never delays the erase, and whatever a - /// terminated operator has not claimed with `claimyield` by then is forfeited to the registry. - /// The prune delay is its claim window (see `terminate`). [[sysio::action]] void prune(); @@ -472,18 +347,6 @@ namespace sysio { // Tables // ----------------------------------------------------------------------- - /// Per-(chain_code, token_code) aggregate balance row. The locked portion - /// is implied by `sysio.uwrit::locks` (consulted by `available()`); the - /// pending-withdraw portion is implied by this contract's - /// `withdraw_queue` (also consulted by `available()`). - /// - /// `shadow_yield` is meaningful only on a depot-native shadow row (`chain_code == - /// opp::wire::chain_code`, `token_code != opp::wire::token_code`), whose bonded shadow earns - /// WIRE yield on this registry's `sysio.liq` holder row; it stays zero on every other row. It - /// is the `opp::shadow::custody` position of this row's balance: settled at `sysio.liq`'s live - /// index for the symbol before every change to `balance` (a new row settles at balance 0, so it - /// starts at the current index and is never credited earlier distributions), so a bonded unit - /// earns exactly what a unit of the registry's holder row earns while it is bonded. struct balance_entry { sysio::slug_name chain_code; sysio::slug_name token_code; @@ -600,15 +463,6 @@ namespace sysio { (eligible_at_epoch)(requested_at_epoch)) }; - // Per plan §B.2 (mirrors sysio.uwrit::locks): split-index approach. - // Antelope KV secondary indexes use fixed-width integer keys; the - // 3-uint64 (account, chain_code, token_code) composite is computed on - // the row as `by_account_ck()` for cross-contract comparisons but is - // NOT a table-managed secondary index. Callers scan `byaccount` - // (uint64) and filter (chain_code, token_code) in memory — cheap - // because pending-withdraw counts per account are bounded by the number - // of its collateral buckets times - // MAX_OUTSTANDING_WITHDRAWS_PER_COLLATERAL_BUCKET. using wtdwqueue_t = sysio::kv::table<"wtdwqueue"_n, withdraw_key, withdraw_request, sysio::kv::index<"byeligible"_n, sysio::const_mem_fun>, @@ -645,48 +499,12 @@ namespace sysio { sysio::const_mem_fun> >; - /// Claimable depot-native collateral owed to an operator by a WIRE-chain remit: a withdraw - /// flush (`flushwtdw`), a deferred lock release on a TERMINATED operator, or the - /// termination payout itself. One row per `(account, token_code)`: WIRE and each shadow - /// LIQ symbol an operator bonded are owed, and claimed, separately (see `deposit`). - /// - /// All three are reachable from `sysio.epoch::advance` (flush by epoch, termination via - /// `termcheck`), which must never abort. A pushed `sysio.token::transfer` notifies the - /// operator, so an operator carrying a hostile notify handler could abort `advance` and halt - /// epoch advancement chain-wide -- and in the termination case would be blocking its own - /// removal, so the retry never converges. Crediting here and paying out from `claimremit` - /// puts the transfer under the operator's own authority instead. - /// - /// No expiry: this is returned collateral, held until claimed. - /// - /// The operator set is registered and bounded only CONCURRENTLY, not across this table's - /// lifetime — a terminated operator that never calls `claimremit` leaves a row billed to the - /// sysio RAM pool even after `prune` removes its operator record, so storage grows with - /// historical operators rather than with the live set. Accepted here for the same reason as - /// `sysio.system::payclaims`: forfeiting returned collateral is an economic decision, not a - /// RAM one. (Contrast `sysio.reserv::wireclaims`, whose recipient set is unbounded AND - /// caller-influenced, and which pays for its sweep with forfeiture.) - /// - /// The row therefore carries `expires_at_sec` and an expiry-ordered index NOW, even though - /// nothing reads them yet. Landing the schema is free before launch and expensive after it; - /// wiring the sweep is the part that needs a decision, so the two are deliberately - /// separated. `credit` maintains the stamp, so WIRE-339 inherits real age data rather than a - /// table that starts counting from the day the field is added. - /// - /// NOTHING EXPIRES TODAY: no `claimable::sweep_expired` call is wired against this table. - /// `REMIT_CLAIM_WINDOW_SEC` is the provisional window the stamp is computed from, not a - /// commitment — whether returned collateral should be forfeited at all is open in WIRE-339. - /// - /// Primary key: the owed operator and the depot-native token it is owed in. struct remitclaim_key { uint64_t account; sysio::slug_name token_code; SYSLIB_SERIALIZE(remitclaim_key, (account)(token_code)) }; - /// Provisional retention window used only to compute `remit_claim::expires_at_sec`. Mirrors - /// `sysio.reserv::WIRE_CLAIM_WINDOW_SEC` so the claimable tables age on one scale; revisited - /// when (and if) a sweep is wired. static constexpr uint32_t REMIT_CLAIM_WINDOW_SEC = 365 * 24 * 60 * 60; struct [[sysio::table("remitclaims")]] remit_claim { @@ -695,17 +513,6 @@ namespace sysio { uint64_t balance = 0; ///< Atomic units of `token_code` owed, not yet claimed. uint32_t expires_at_sec = 0; // recorded by `credit`; read by nothing yet (WIRE-339) - /// Expiry-major composite so the secondary index orders by expiry and a future retention - /// sweep can stop at the first live row. (Same shape as `sysio.reserv::wire_claim`.) - /// - /// The account tail breaks ties between operators, but NOT between one operator's - /// tokens: a remit that credits WIRE and a shadow token in the same second (termination - /// pays every depot-native row at once) gives both rows the same composite. The token is - /// not folded in because expiry, account and token need 160 bits and the index key is - /// 128. The tie is harmless: the host stores each index entry as `(composite, primary - /// key)` and keeps that pair unique, and the primary key carries the token, so tied rows - /// are distinct entries ordered by token; a sweep reads only the expiry prefix, so - /// visiting tied rows in either order changes nothing. uint128_t by_expiry() const { return (static_cast(expires_at_sec) << 64) | account.value; } diff --git a/contracts/sysio.opreg/src/sysio.opreg.cpp b/contracts/sysio.opreg/src/sysio.opreg.cpp index 7af73e6684..53ca4422ea 100644 --- a/contracts/sysio.opreg/src/sysio.opreg.cpp +++ b/contracts/sysio.opreg/src/sysio.opreg.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -24,7 +23,6 @@ using opp::types::OperatorStatus; using opp::types::AttestationType; using opp::attestations::OperatorAction; using opp::attestations::OperatorActionLog; -using opp::attestations::DepositRevert; namespace { @@ -58,10 +56,6 @@ constexpr std::string_view no_yield_owed_msg = "no yield owed"; constexpr std::string_view yield_not_covered_msg = "owed yield is rounding dust the WIRE received from sysio.liq does not cover; only later slack can cover it"; -/// Recent-actions reason `depositinle` records when it drops a credit naming the depot chain. -constexpr std::string_view depot_chain_credit_msg = - "depot-chain collateral is bonded through deposit, never credited from an outpost"; - /// Message of the `check` `sweepyield` raises for WIRE, which is not a shadow token. constexpr std::string_view wire_earns_no_yield_msg = "WIRE collateral earns no shadow yield"; @@ -85,7 +79,7 @@ bool earns_shadow_yield(sysio::slug_name chain_code, sysio::slug_name token_code /// Credit a WIRE-chain remit of `token_code` to the operator's claimable row instead of /// transferring it. /// -/// Every caller (withdraw flush, deferred lock release, termination payout) is reachable from +/// Every caller (withdraw flush and termination payout) is reachable from /// `sysio.epoch::advance`, which must never abort. A custody contract's `transfer` notifies the /// operator, and the chain runs notified receivers with no exception isolation, so a pushed remit /// would let an operator's notify handler abort `advance` and halt epoch advancement chain-wide. @@ -114,45 +108,6 @@ uint64_t current_time_ms() { return static_cast(current_time_point().sec_since_epoch()) * 1000; } -/// Compute the composite key matching `withdraw_request::by_account_ck` / -/// `sysio::uwrit::lock_entry::by_underwriter_ck`. Centralized so both the -/// indexer and the lookups stay in lockstep. -/// -/// Phase 6 layout: three uint64s — `account.value`, `chain_code.value`, -/// `token_code.value` — packed in that order into a 24-byte buffer and -/// hashed with sha256, producing a `checksum256`. The previous uint128 -/// composite layout (account<<64 | chain<<32 | token) is gone; the wider -/// slug_name values no longer fit in 32 bits each. -checksum256 make_account_chain_token_key(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - std::array buf{}; - uint64_t acc_v = account.value; - std::memcpy(buf.data() + 0, &acc_v, 8); - std::memcpy(buf.data() + 8, &chain_code.value, 8); - std::memcpy(buf.data() + 16, &token_code.value, 8); - return sysio::sha256(reinterpret_cast(buf.data()), buf.size()); -} - -/// Find the outpost id registered with sysio.chains for a given chain. Returns -/// `std::nullopt` if no matching chain row exists (the caller is responsible -/// for handling that case — typically by skipping the queueout for chains -/// without an outpost, e.g. WIRE-direct flows). -/// -/// After the cross-contract realignment: chain rows live in -/// `sysio.chains::chains` keyed by `code` (slug_name); the legacy -/// `sysio.epoch::outposts` table is gone. The "outpost id" returned here is -/// the chain's `code.value` (uint64) — callers that still expect a small -/// numeric id should use the slug_name value instead. -std::optional find_outpost_id_for_chain(sysio::slug_name chain_code) { - sysio::chains::chains_t chains_tbl(name{"sysio.chains"_n}); - sysio::chains::chain_key pk{chain_code}; - if (!chains_tbl.contains(pk)) return std::nullopt; - const auto row = chains_tbl.get(pk); - if (row.is_depot) return std::nullopt; // WIRE-direct flows don't queueout - return chain_code.value; -} - /// Resolve a `sysio::slug_name` chain identifier to its `ChainKind` enum by /// reading the `sysio.chains::chains` registry row. Returns `std::nullopt` /// when no chain row exists for the code — callers treat that as "chain @@ -213,46 +168,11 @@ void require_canonical_codes(const std::vector& v, } } -/// True iff `sysio.uwrit::locks` still holds ANY row for `account`, on any -/// `(chain_code, token_code)` pair. Existence-only, so it stops at the first row -/// instead of summing like `sum_locks_inline` — the settlement gate needs to know -/// whether deferred settlement is still outstanding, not how much. -bool has_active_locks(name account) { - uwrit::locks_t locks(opreg::UWRIT_ACCOUNT); - auto idx = locks.template get_index<"byuw"_n>(); - return idx.lower_bound(account.value) != idx.upper_bound(account.value); -} - -/// True iff a TERMINATED operator's collateral is completely settled — every -/// balance bucket drained AND no `sysio.uwrit` lock still naming the account. -/// -/// This is the precondition for erasing a terminated `operators` row, on EVERY -/// path that erases one — `prune` and the re-registration replace in -/// `regoperator` (WNS-01). `terminate_inline` remits only the immediately- -/// unlocked portion and deliberately LEAVES the locked remainder in the row, so -/// `releaselock` can emit its deferred WITHDRAW_REMIT when -/// `sysio.uwrit::chklocks` later releases each lock. `releaselock` settles only -/// SLASHED / TERMINATED rows and returns early when the row is missing, so -/// destroying or resetting it before that settlement lands permanently strands -/// the retained collateral: WIRE-side custody keeps it with no ledger entry to -/// release against, and the outpost keeps its escrow with no attestation that -/// could ever free it. -/// -/// Balance rows are checked by VALUE, not by `balances.empty()` — -/// `subtract_balance` zeroes a bucket in place and never erases the entry. Banked shadow yield does -/// NOT count: it never blocks the erase, and whatever is still unclaimed then is forfeited -- the -/// prune delay is the terminated operator's window to `claimyield` (see `terminate`). -/// -/// A row that never settles is never erased, and that is the intended outcome: -/// an unprunable row costs some RAM, while an erased one costs the operator -/// their bond. In correct operation the wait is bounded — `chklocks` releases -/// every lock at its wall-clock expiry, and each `releaselock` drains the -/// matching bucket, so the predicate goes true on its own. bool is_fully_settled(const opreg::operator_entry& op) { for (const auto& bal : op.balances) { if (bal.balance > 0) return false; } - return !has_active_locks(op.account); + return true; } } // anonymous namespace @@ -385,19 +305,9 @@ void opreg::regoperator(name account, auto existing = ops.get(op_pk); check(existing.status == OperatorStatus::OPERATOR_STATUS_TERMINATED, "operator already registered"); - // Re-registration REPLACES the terminated row, so it is an erase path and - // carries the same settlement precondition as `prune` (WNS-01). Without - // it, a terminated underwriter could re-register while its locks are still - // live: the retained balance row is replaced by a fresh healthy one, and - // when `chklocks` later fans out `releaselock` it sees a non-terminated - // operator and no-ops — stranding the old collateral exactly as pruning - // early would have. Re-registration stays blocked until settlement lands; - // `chklocks` releases every lock at its wall-clock expiry, so the wait is - // bounded. check(is_fully_settled(existing), "operator has unsettled collateral: a terminated operator may only " - "re-register once its balances are drained and no underwriting locks " - "remain"); + "re-register once its balances are drained"); ops.erase(op_pk); } @@ -444,40 +354,11 @@ void opreg::regoperator(name account, } // --------------------------------------------------------------------------- -// Internal helpers — balance / lock / withdraw rollup +// Internal helpers — balance / withdraw rollup // --------------------------------------------------------------------------- namespace { -/// The active lock total on `sysio.uwrit` for a given (op, chain, token) — -/// an O(1) read of uwrit's `locksums` rollup. Returns 0 when the operator -/// holds no live locks on that chain/token (the rollup erases a bucket's row -/// once it empties, so an absent row IS zero). -/// -/// This used to scan `sysio.uwrit::locks` through its `byuw` index and filter -/// `(chain_code, token_code)` in memory, on the stated assumption that -/// per-underwriter lock counts are "O(1)-ish in steady state so the scan is -/// cheap". That assumption does not hold: uwrit locks are held for the full -/// wall-clock challenge window and are never released by delivery, so a -/// bucket's live lock count is (settlement rate × lock duration). uwrit now -/// maintains the total at the THREE sites that can change it: -/// -/// * `try_select_winner` — ADDS, one lock per required leg, on a win. -/// * `chklocks` — DECREMENTS, releasing locks at expiry. -/// * `sweeplocks` — DECREMENTS, erasing the held locks of a -/// commitment whose underwriter-fault challenge was -/// UPHELD (WIRE-297), outside `chklocks`. -/// -/// That third path is the one this PR's stale-cache bug came from, so the -/// count is worth keeping exact here: this reader trusts the rollup -/// completely, and a bucket left positive after its last lock row is gone -/// suppresses that collateral forever. See `uwrit::lock_sum`. -uint64_t sum_locks_inline(name account, sysio::slug_name chain_code, sysio::slug_name token_code) { - uwrit::locksums_t sums(opreg::UWRIT_ACCOUNT); - uwrit::lock_sum_key pk{account, chain_code, token_code}; - return sums.contains(pk) ? sums.get(pk).amount : 0; -} - /// Sum the pending (not-yet-flushed) withdraws on this contract for a given /// (op, chain, token). Subtracted by `available()` so a queued withdraw /// effectively reserves the funds for its 2-epoch wait. @@ -522,7 +403,7 @@ bool has_terminal_status(OperatorStatus status) { } /// Compute available balance for a given (op, chain, token). The single -/// rollup formula: balance - sum(active locks) - sum(pending withdraws), +/// rollup formula: balance - sum(pending withdraws), /// gated by status. Slashed / terminated operators read as zero. uint64_t available_inline(const opreg::operator_entry& op, sysio::slug_name chain_code, sysio::slug_name token_code) { @@ -532,30 +413,23 @@ uint64_t available_inline(const opreg::operator_entry& op, const auto* bal = find_balance(op, chain_code, token_code); if (!bal) return 0; - uint64_t locked = sum_locks_inline(op.account, chain_code, token_code); uint64_t pending = sum_pending_withdraws(op.account, chain_code, token_code); - // Saturating: a wrap of locked+pending would understate `reserved` and - // overstate availability — the direction that admits an overcommit. The - // cap is unreachable for real amounts. - uint64_t reserved = opp::safe::add_sat_u64(locked, pending); - return bal->balance > reserved ? bal->balance - reserved : 0; + return bal->balance > pending ? bal->balance - pending : 0; } -/// Balance minus active locks (NOT pending withdraws). Used by `slash()` to -/// determine how much can be slashed immediately — pending withdraws of a +/// The full balance is slashable, including queued withdrawals. Withdrawals of a /// slashed operator are forfeit (silently dropped at flush time). uint64_t slashable_now(const opreg::operator_entry& op, sysio::slug_name chain_code, sysio::slug_name token_code) { const auto* bal = find_balance(op, chain_code, token_code); if (!bal) return 0; - uint64_t locked = sum_locks_inline(op.account, chain_code, token_code); - return bal->balance > locked ? bal->balance - locked : 0; + return bal->balance; } /// Check whether the operator's available balance on (chain_code, token_code) /// covers the role's minimum bond on that pair. /// -/// Bootstrapped operators are ACTIVE-by-fiat and bypass the per-outpost +/// Bootstrapped operators are ACTIVE-by-fiat and bypass the per-token /// bond check regardless of how `req_*_collat` is configured — they /// represent system-installed operators that the depot trusts without /// requiring collateral. Non-bootstrapped operators must satisfy every @@ -664,7 +538,7 @@ uint64_t take_yield(name self, opreg::balance_entry& b) { } /// Settle every shadow row the operator holds and take what each has earned, as far as each pool -/// covers, for the TERMINATED payouts (termination itself, then each later lock release). Returns +/// covers, for the TERMINATED payout. Returns /// the amount taken per row, for the caller to credit to the operator's WIRE claim alongside the /// principal; what a pool could not cover stays banked for `claimyield`. Never throws. std::vector take_all_earned_yield(name self, opreg::operator_entry& o) { @@ -842,12 +716,7 @@ opp::types::ChainAddress operator_chain_address(name account, sysio::slug_name c /// ready for either logging on the operator's row or queueing as an /// outbound OPERATOR_ACTION attestation. Pure — no side effects. /// -/// LP routing for the slashed funds is depot-side concern resolved via -/// `sysio.reserve::resolve_lp` at slash-handler time; outposts on receipt -/// only need to seize their share, so the attestation does not encode it. -/// The `OperatorActionLog.timestamp` covers the audit trail; once an -/// operator is slashed all subsequent OperatorActions are dropped+logged -/// as failures, so per-action epoch is redundant on the message itself. +/// This payload records a depot-local slash in the operator audit trail. OperatorAction build_slash_action(name account, OperatorType type, sysio::slug_name chain_code, @@ -867,69 +736,6 @@ OperatorAction build_slash_action(name account, return oa; } -/// Queue an OPERATOR_ACTION(SLASH) attestation outbound to the outpost -/// matching `chain_code`. No-op if the chain is WIRE (slashed funds stay on -/// the WIRE chain) or has no registered outpost. -void emit_slash_attestation(name self, const OperatorAction& slash_action) { - const sysio::slug_name chain_code{slash_action.chain_code}; - if (chain_code == opp::wire::chain_code) return; - auto resolved = find_outpost_id_for_chain(chain_code); - if (!resolved) return; // no outpost on this chain — nothing to slash through - - // `no_size{}` — raw protobuf bytes, no 4-byte zpp length prefix. The - // outpost decodes the attestation `data` field as a pure protobuf - // message; a size prefix would corrupt the first field tag. - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(slash_action); - - action( - permission_level{self, "active"_n}, - opreg::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(*resolved, - AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION, encoded) - ).send(); -} - -/// Queue a DEPOSIT_REVERT attestation outbound to the source outpost so -/// escrowed funds get refunded to the depositor (minus the outpost-side -/// gas penalty, computed locally on the outpost when the revert is -/// processed). Called by `opreg::depositinle` whenever validation rejects -/// an inbound DEPOSIT_REQUEST. -void emit_deposit_revert(name self, - sysio::slug_name source_chain_code, - const opp::types::ChainAddress& depositor, - sysio::slug_name token_code, - uint64_t amount, - const checksum256& original_message_id, - const std::string& reason) { - auto chain_code = find_outpost_id_for_chain(source_chain_code); - if (!chain_code) return; // no outpost on this chain — nothing to refund through - - opp::attestations::DepositRevert dr; - dr.depositor = depositor; - opp::types::TokenAmount ta; - ta.token_code = token_code.value; - ta.amount = zpp::bits::vint64_t{static_cast(amount)}; - dr.refund_amount = ta; - const auto& mh = original_message_id.extract_as_byte_array(); - dr.original_deposit_message_id.assign(mh.begin(), mh.end()); - dr.reason = reason; - dr.chain_code = source_chain_code.value; - - // `no_size{}` — see emit_slash_attestation for the rationale. - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(dr); - - action( - permission_level{self, "active"_n}, - opreg::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(*chain_code, - AttestationType::ATTESTATION_TYPE_DEPOSIT_REVERT, encoded) - ).send(); -} - /// Append an OperatorActionLog entry to the operator's `recent_actions` /// ring buffer (newest at the back). When the buffer is at MAX_RECENT_ACTIONS, /// drops the oldest (front) before appending. Caps `error_message` at @@ -965,43 +771,6 @@ void append_action_log(opreg::operators_t& ops, }); } -/// Encode + queue an OPERATOR_ACTION(WITHDRAW_REMIT) attestation to the -/// outpost matching `chain_code`. `op_address` carries the operator's authex- -/// linked chain pubkey so the outpost can derive the destination address. -void emit_withdraw_remit(name self, - name account, - OperatorType type, - sysio::slug_name chain_code, - sysio::slug_name token_code, - uint64_t amount, - uint64_t request_id) { - auto resolved = find_outpost_id_for_chain(chain_code); - if (!resolved) return; - - OperatorAction oa; - oa.action_type = OperatorAction::ACTION_TYPE_WITHDRAW_REMIT; - oa.op_address = operator_chain_address(account, chain_code); - oa.type = type; - opp::types::TokenAmount ta; - ta.token_code = token_code.value; - ta.amount = zpp::bits::vint64_t{static_cast(amount)}; - oa.amount = ta; - oa.request_id = request_id; - oa.chain_code = chain_code.value; - - // `no_size{}` — see emit_slash_attestation for the rationale. - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(oa); - - action( - permission_level{self, "active"_n}, - opreg::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(*resolved, - AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION, encoded) - ).send(); -} - } // anonymous namespace // --------------------------------------------------------------------------- @@ -1017,19 +786,12 @@ void reevaluate_eligibility(opreg::operators_t& ops, name self, name account); -/// Result of `try_enqueue_withdraw` — non-throwing variant for the -/// msgch-dispatched `withdrawinle` path so failures get logged on the -/// operator's row instead of reverting the inbound dispatch tx. struct enqueue_result { bool success; uint64_t request_id; // valid only when success == true std::string error_message; }; -/// Validate + insert a `wtdwqueue` row. Does NOT throw on validation -/// failure — returns the diagnostic in `enqueue_result`. Used by both -/// `withdrawinle` (msgch-dispatched, log-don't-revert) and `withdraw` -/// (operator-callable depot-native, also log-don't-revert). enqueue_result try_enqueue_withdraw(name account, sysio::slug_name chain_code, sysio::slug_name token_code, @@ -1158,10 +920,7 @@ OperatorAction build_deposit_action(const opp::types::ChainAddress& op_address, // registry cannot custody reverts, so a request that could never be paid out // is never queued. Every other validation failure DOES NOT revert — it // appends a failure entry to the operator's `recent_actions` ring buffer, -// matching the OPP-dispatched path (`withdrawinle`). The operator reads the -// outcome via WIRE JSON-RPC. Outpost-held collateral is withdrawn by calling -// the holding outpost's withdraw entry point — that path arrives at -// `withdrawinle` instead. +// The operator reads the recorded result through WIRE JSON-RPC. void opreg::withdraw(name account, sysio::slug_name token_code, uint64_t amount) { require_auth(account); check(resolve_depot_native_token(token_code).has_value(), unsupported_token_msg); @@ -1178,35 +937,6 @@ void opreg::withdraw(name account, sysio::slug_name token_code, uint64_t amount) } } -// --------------------------------------------------------------------------- -// withdrawinle — internal: outpost-driven withdraw request (msgch-inline) -// --------------------------------------------------------------------------- -// -// Inline-dispatched from `sysio.msgch::evalcons` for inbound -// OPERATOR_ACTION(WITHDRAW_REQUEST) attestations. Validation failures DO -// NOT revert (revert would kill the entire envelope's dispatch); the -// outcome is appended to the operator's `recent_actions` ring so they -// can read why their request was dropped via WIRE JSON-RPC. Escrowed -// funds stay in outpost custody on rejection — the operator re-issues -// once the underlying condition resolves. -void opreg::withdrawinle(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code, - uint64_t amount) { - require_auth(get_self()); - - operators_t ops(get_self()); - auto op_pk = operator_key{account.value}; - - auto result = try_enqueue_withdraw(account, chain_code, token_code, amount); - auto action = build_withdraw_request_action(account, chain_code, token_code, amount, - result.request_id); - append_action_log(ops, op_pk, action, result.success, std::move(result.error_message)); - if (result.success) { - reevaluate_eligibility(ops, op_pk, get_self(), account); - } -} - // --------------------------------------------------------------------------- // cancelwtdw — operator cancels a queued withdraw before it flushes // --------------------------------------------------------------------------- @@ -1315,27 +1045,8 @@ void opreg::deposit(name account, sysio::slug_name token_code, uint64_t amount) check(op.status != OperatorStatus::OPERATOR_STATUS_SLASHED && op.status != OperatorStatus::OPERATOR_STATUS_TERMINATED, "operator not in a deposit-eligible state"); - // Bootstrapped operators are ACTIVE by fiat and bypass `meets_role_min` entirely, so collateral - // credited to one can never affect its eligibility -- the deposit would be accepted into a - // balance that does nothing. `depositinle` already rejects them; this closes the depot-native - // path. There is deliberately no way to collateralise a bootstrap: an operator who wants a - // collateralised producer registers a new account. check(!op.is_bootstrapped, "bootstrapped operators cannot deposit collateral"); - // Credit collateral BEFORE the inbound custody transfer, with the cap check - // performed ATOMICALLY inside the same `modify` as the credit — reading the - // live row, not the `op` copy read above. This closes a reentrancy window on - // operator accounts that carry contract code: the custody contract's - // `transfer` notifies `from` (the operator), and that notification handler - // could re-enter `deposit`. A separate pre-read-then-check-then-credit could - // let two credits pass against the same stale balance and push the row past - // `asset::max_amount` — recreating the withdraw/terminate remit abort - // (`asset(balance, symbol)` aborts above the limit). Checking inside the - // modify makes check+credit indivisible, and crediting before the transfer - // means any re-entry observes the committed balance. A direct user deposit may - // legitimately `check()`-throw here (unlike the never-throw OPP `depositinle`); - // if the transfer below aborts (operator lacks the tokens) the whole - // transaction — including this credit — rolls back. (SEC-103; PR #449 review.) ops.modify(same_payer, op_pk, [&](auto& o) { check(amount <= MAX_COLLATERAL_AMOUNT && balance_of(o, opp::wire::chain_code, token_code) <= MAX_COLLATERAL_AMOUNT - amount, @@ -1356,124 +1067,6 @@ void opreg::deposit(name account, sysio::slug_name token_code, uint64_t amount) reevaluate_eligibility(ops, op_pk, get_self(), account); } -// --------------------------------------------------------------------------- -// depositinle — internal: outpost-driven collateral credit (msgch-inline) -// --------------------------------------------------------------------------- -// -// Inline-dispatched from `sysio.msgch::evalcons` for inbound -// OPERATOR_ACTION(DEPOSIT_REQUEST) attestations. Validation failures DO -// NOT revert — reverting from inside the inline dispatch would abort the -// entire envelope. Instead, the failure is logged on the operator's -// `recent_actions` ring (when an entry exists) and a `DEPOSIT_REVERT` -// attestation is queued outbound to the source outpost so the escrowed -// funds can be refunded to the depositor (minus the outpost-side gas -// penalty, computed locally on the outpost when the revert is processed). -// -// `actor_chain` is retained as `opp::types::ChainKind` per the -// ChainAddress flattening pattern — the depositor's source-chain -// `ChainAddress.kind` field is still ChainKind on the wire and is not -// part of the slug_name refactor. -void opreg::depositinle(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code, - uint64_t amount, - opp::types::ChainKind actor_chain, - std::vector actor_address, - checksum256 original_message_id) { - require_auth(get_self()); - - operators_t ops(get_self()); - auto op_pk = operator_key{account.value}; - - // Reconstruct the depositor's ChainAddress locally — the proto message - // type stays out of the ABI but is fine to use inside the contract for - // building OPERATOR_ACTION logs and DEPOSIT_REVERT correlation. - opp::types::ChainAddress actor; - actor.kind = actor_chain; - actor.address = std::move(actor_address); - - auto deposit_action = build_deposit_action(actor, chain_code, token_code, amount); - - // A credit naming the depot chain would create a depot-native row no custody transfer ever - // funded: depot-native collateral is bonded only through `deposit`, whose inbound transfer pays - // for it. `sysio.msgch` never dispatches such a credit today; refuse it here too. Dropped, not - // reverted -- there is no outpost holding anything to refund -- and never thrown, because this - // runs inside an envelope's dispatch. - if (chain_code == opp::wire::chain_code) { - append_action_log(ops, op_pk, deposit_action, false, std::string(depot_chain_credit_msg)); - return; - } - - // The balance map is keyed by (chain_code, token_code), so an uncanonical token - // code must not be persisted: rendering is total and will not complain, but the - // code can render a valid spelling that re-parses as a DIFFERENT code, aliasing - // one operator's balance onto another's key. The outpost has already taken - // custody, so refund rather than drop. - if (!token_code.is_canonical()) { - const std::string err = "token code has no canonical slug_name spelling"; - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, err); - append_action_log(ops, op_pk, deposit_action, false, err); - return; - } - if (amount == 0) { - const std::string err = "amount must be positive"; - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, err); - append_action_log(ops, op_pk, deposit_action, false, err); - return; - } - if (!ops.contains(op_pk)) { - // No entry to log to. The DEPOSIT_REVERT IS the audit record for the - // outpost — outpost emits a local refund event the depositor reads. - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, "operator not registered"); - return; - } - auto op = ops.get(op_pk); - if (op.status == OperatorStatus::OPERATOR_STATUS_SLASHED || - op.status == OperatorStatus::OPERATOR_STATUS_TERMINATED) { - const std::string err = "operator not in a deposit-eligible state"; - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, err); - append_action_log(ops, op_pk, deposit_action, false, err); - return; - } - // Bootstrapped operators are bonded by fiat — no deposit is ever - // permitted for them. Reject via DEPOSIT_REVERT (NOT a throw, per - // the no-throws-in-OPP-handlers rule — a throw here would halt the - // entire envelope's evalcons → chain stalls). The outpost refunds - // the depositor when it processes the revert. - if (op.is_bootstrapped) { - const std::string err = "bootstrapped operator cannot accept deposits"; - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, err); - append_action_log(ops, op_pk, deposit_action, false, err); - return; - } - // SEC-103 (WSA-028 follow-up): the credited collateral must stay within the - // asset magnitude range so the depot-native claim payout's `asset(balance, - // symbol)` can never abort — an abort on this OPP-inbound path would stall - // consensus. The msgch ingress gate already bounds a single `amount` to - // `asset::max_amount`; this additionally bounds the running sum. Fail closed - // by refunding via DEPOSIT_REVERT — never `check()`. - if (amount > MAX_COLLATERAL_AMOUNT || - balance_of(op, chain_code, token_code) > MAX_COLLATERAL_AMOUNT - amount) { - const std::string err = "deposit would exceed max collateral"; - emit_deposit_revert(get_self(), chain_code, actor, token_code, amount, - original_message_id, err); - append_action_log(ops, op_pk, deposit_action, false, err); - return; - } - - ops.modify(same_payer, op_pk, [&](auto& o) { - add_balance(o, chain_code, token_code, amount); - }); - append_action_log(ops, op_pk, deposit_action, true, ""); - - reevaluate_eligibility(ops, op_pk, get_self(), account); -} - // --------------------------------------------------------------------------- // flushwtdw — drain matured rows from the withdraw queue // --------------------------------------------------------------------------- @@ -1516,7 +1109,7 @@ void opreg::flushwtdw(uint32_t current_epoch) { auto op = ops.get(op_pk); if (op.status == OperatorStatus::OPERATOR_STATUS_SLASHED) { - // Slashed during the wait — funds went to the LP via the slash flow. + // Slashed during the wait; the operator no longer owns the collateral. append_action_log(ops, op_pk, remit_action, false, "operator slashed during withdraw-wait window"); queue.erase(wkey); @@ -1524,11 +1117,6 @@ void opreg::flushwtdw(uint32_t current_epoch) { } if (op.status == OperatorStatus::OPERATOR_STATUS_TERMINATED) { - // Terminated during the wait — terminate_inline already remitted the operator's full - // unlocked balance (which covered this queued amount), so the row is moot. Erase without - // subtracting; otherwise subtract_balance would underflow and abort this epoch-inline - // action, permanently stalling epoch advancement (the balance was reduced to sum(locks) - // by terminate_inline, leaving nothing to cover the matured withdraw). append_action_log(ops, op_pk, remit_action, false, "operator terminated during withdraw-wait window"); queue.erase(wkey); @@ -1558,14 +1146,8 @@ void opreg::flushwtdw(uint32_t current_epoch) { // transfer happens here, and the operator receives nothing until it calls `claimremit`. The // row's `token_code` is carried as-is, never resolved here. This path runs inline from // `sysio.epoch::advance`, where a pushed transfer would let the operator's notify handler - // abort epoch advancement chain-wide. For outpost chains: queue an - // OPERATOR_ACTION(WITHDRAW_REMIT) to the outpost so it can release the escrow on its end. - if (row.chain_code == opp::wire::chain_code) { - credit_remit_claim(get_self(), row.account, row.token_code, row.amount); - } else { - emit_withdraw_remit(get_self(), row.account, op.type, - row.chain_code, row.token_code, row.amount, row.request_id); - } + // abort epoch advancement chain-wide. + credit_remit_claim(get_self(), row.account, row.token_code, row.amount); append_action_log(ops, op_pk, remit_action, true, ""); // Remove the matured reservation before eligibility is recomputed. The @@ -1643,7 +1225,7 @@ void opreg::processuw(name account, bool was_eligible, bool is_eligible) { } // --------------------------------------------------------------------------- -// slash — punitive removal; routes unlocked funds to LP, defers locked +// slash — punitive removal of the depot collateral balance // --------------------------------------------------------------------------- void opreg::slash(name account, std::string reason) { require_auth(CHALG_ACCOUNT); @@ -1658,9 +1240,7 @@ void opreg::slash(name account, std::string reason) { auto now = current_time_ms(); - // Snapshot the slashable amounts per (chain_code, token_code) BEFORE - // marking SLASHED (the status flip would zero `slashable_now` via - // available()). + // Snapshot the full collateral balance for the slash and audit log. struct slash_pair { sysio::slug_name chain_code; sysio::slug_name token_code; uint64_t amount; }; std::vector to_slash; for (const auto& bal : op.balances) { @@ -1670,9 +1250,7 @@ void opreg::slash(name account, std::string reason) { } } - // Flip status + decrement balances by the slashable_now portion. - // Locked portion (== sum_locks) remains in `balance`; sysio.uwrit::release - // will deferred-slash it as each lock resolves. + // Mark SLASHED and remove each collateral balance; custody remains here. ops.modify(same_payer, op_pk, [&](auto& o) { o.status = OperatorStatus::OPERATOR_STATUS_SLASHED; o.updated_at = now; @@ -1689,99 +1267,12 @@ void opreg::slash(name account, std::string reason) { auto slash_action = build_slash_action(op.account, op.type, sp.chain_code, sp.token_code, sp.amount, reason); - emit_slash_attestation(get_self(), slash_action); append_action_log(ops, op_pk, slash_action, /*success*/ true, ""); } notify_producer_standing(get_self(), op); } -// --------------------------------------------------------------------------- -// releaselock — deferred-slash / deferred-remit / no-op on lock release -// --------------------------------------------------------------------------- -void opreg::releaselock(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code, - uint64_t amount) { - require_auth(UWRIT_ACCOUNT); - check(amount > 0, "amount must be positive"); - - operators_t ops(get_self()); - auto op_pk = operator_key{account.value}; - if (!ops.contains(op_pk)) return; - auto op = ops.get(op_pk); - - if (op.status != OperatorStatus::OPERATOR_STATUS_SLASHED && - op.status != OperatorStatus::OPERATOR_STATUS_TERMINATED) { - // Healthy underwriter: balance was never decremented at lock time. - // uwrit::release just erases the lock row; opreg has no work. - return; - } - - // SLASHED or TERMINATED — decrement opreg balance and emit the matching - // outbound attestation (deferred-slash to LP or deferred-remit to authex). - // - // Clamp the settled amount to the live balance bucket. In correct - // operation sum(active locks) <= balance for every (underwriter, - // chain_code, token_code) — the sysio.uwrit winner check reserves the - // aggregate per collateral bucket before writing locks — so `amount` - // never exceeds the remaining balance and this clamp is a no-op. - // It is the safety net for any residual/over-committed lock set: - // releaselock runs INLINE inside `sysio.uwrit::chklocks` at - // `sysio.epoch::advance`, and a `subtract_balance` underflow `check()` - // there would abort the advance and permanently stall epoch advancement - // chain-wide (`epoch-stall-is-fatal`). Settle — and attest — only what the - // balance can actually back. - uint64_t settle_amount = amount; - if (const auto* bal = find_balance(op, chain_code, token_code)) { - if (settle_amount > bal->balance) settle_amount = bal->balance; - } else { - settle_amount = 0; // no balance row for this bucket — nothing to settle - } - if (settle_amount == 0) { - // Bucket already fully drained (e.g. by prior releases of an - // over-committed set) — the caller has erased the lock row; emit no - // zero-value slash/remit attestation. - return; - } - - // A TERMINATED operator's shadow rows kept earning on their locked remainder since the - // termination payout; pay that yield with the principal, as the payout did -- the part the pool - // covers is credited, the rest stays banked for `claimyield` within the claim window. A SLASHED - // operator keeps it all on the row, claimable through `claimyield` (its locked remainder earned - // until this deferred slash). - std::vector yield_credits; - ops.modify(same_payer, op_pk, [&](auto& o) { - subtract_balance(o, chain_code, token_code, settle_amount); - if (op.status == OperatorStatus::OPERATOR_STATUS_TERMINATED) { - yield_credits = take_all_earned_yield(get_self(), o); - } - }); - - if (op.status == OperatorStatus::OPERATOR_STATUS_SLASHED) { - auto slash_action = build_slash_action(op.account, op.type, - chain_code, token_code, settle_amount, - /*reason*/ "deferred slash on lock release"); - emit_slash_attestation(get_self(), slash_action); - append_action_log(ops, op_pk, slash_action, /*success*/ true, ""); - } else { - // TERMINATED — for depot-native, CREDIT the operator's `remitclaims` row in this lock's token; - // it is paid only when it calls `claimremit`. The termination itself already committed in an earlier epoch, so a - // pushed transfer here could not undo it — what it WOULD do is abort this lock release, which - // `chklocks` runs inline from `sysio.epoch::advance`, stalling epoch advancement chain-wide - // and leaving the lock unreleased on every retry. (Aborting the termination itself is the - // `terminate_inline` case below.) Otherwise queue WITHDRAW_REMIT so the outpost can transfer - // to the authex destination. request_id == 0 (this remit isn't queued in wtdwqueue). - if (chain_code == opp::wire::chain_code) { - credit_remit_claim(get_self(), account, token_code, settle_amount); - } else { - emit_withdraw_remit(get_self(), op.account, op.type, - chain_code, token_code, settle_amount, /*request_id*/ 0); - } - credit_yield(get_self(), account, yield_credits); - } -} - // --------------------------------------------------------------------------- // terminate / termcheck / recorddel — administrative removal // --------------------------------------------------------------------------- @@ -1791,7 +1282,7 @@ namespace { /// Internal terminate body — used by both the operator-removal path /// (`termcheck` -> `terminate` inline) and the slashing-equivalent path for /// completeness. Marks status TERMINATED and remits each -/// (chain_code, token_code) balance back to the operator via WITHDRAW_REMIT. +/// depot-native token balance to a pull claim for the operator. void terminate_inline(name self, name account, const std::string& reason) { opreg::operators_t ops(self); auto op_pk = opreg::operator_key{account.value}; @@ -1807,20 +1298,14 @@ void terminate_inline(name self, name account, const std::string& reason) { std::vector to_remit; for (const auto& bal : op.balances) { uint64_t amt = slashable_now(op, bal.chain_code, bal.token_code); - // For termination we route the unlocked portion. The locked portion - // gets remitted at lock-release time by sysio.uwrit::release (deferred- - // remit, symmetric with deferred-slash). + // Termination returns the full remaining balance through pull claims. if (amt > 0) { to_remit.push_back({bal.chain_code, bal.token_code, amt}); } } - // Every shadow row is settled (the subtraction settles the remitted ones; the collection below - // settles the rest, e.g. a row whose balance is all locked) and its banked yield is paid with - // the principal to the WIRE claim row, up to what `yieldpool` covers; this path pushes nothing - // and so cannot pull more from sysio.liq. The uncovered rest stays banked for `claimyield` - // within the claim window (see `terminate`). Yield on a locked remainder keeps accruing until - // `releaselock` settles that remainder and pays it the same way. + // Settle earned shadow yield alongside principal, up to the yield pool balance. + // Uncovered yield stays banked for claimyield during the termination claim window. std::vector yield_credits; ops.modify(same_payer, op_pk, [&](auto& o) { o.status = OperatorStatus::OPERATOR_STATUS_TERMINATED; @@ -1832,25 +1317,10 @@ void terminate_inline(name self, name account, const std::string& reason) { yield_credits = take_all_earned_yield(self, o); }); - // Remit each (chain_code, token_code). For WIRE-chain: CREDIT `remitclaims` in the row's token, - // which the operator pulls with `claimremit` -- nothing is transferred from here. For outpost - // chains: queue WITHDRAW_REMIT. - // - // After each remit, append a WITHDRAW_REMIT entry to the operator's - // `recent_actions` ring buffer so the audit trail mirrors the - // operator-initiated withdraw flow (`flushwtdw` does the same). Without - // this entry, downstream consumers polling `operators[op].recent_actions` - // for proof of remit emission see only the prior DEPOSIT_REQUESTs and - // miss the termination payout — same semantic gap on TERMINATED ops as on - // a normal queued withdraw, only resolvable by querying msgch internals - // (which are transient — the rows drain on the next `buildenv`). + // Credit each token's pull claim and append its local withdrawal audit entry. + // No recipient notification can interrupt epoch-driven termination. for (const auto& rp : to_remit) { - if (rp.chain_code == opp::wire::chain_code) { - credit_remit_claim(self, account, rp.token_code, rp.amount); - } else { - emit_withdraw_remit(self, account, op.type, - rp.chain_code, rp.token_code, rp.amount, /*request_id*/ 0); - } + credit_remit_claim(self, account, rp.token_code, rp.amount); OperatorAction remit_action = build_withdraw_remit_action( account, rp.chain_code, rp.token_code, rp.amount, /*request_id*/ 0); append_action_log(ops, op_pk, remit_action, /*success*/ true, @@ -1871,7 +1341,7 @@ void opreg::terminate(name account, std::string reason) { // claimremit - pull depot-native collateral credited by a WIRE-chain remit, one token at a time. // -// The remit paths (withdraw flush, deferred lock release, termination payout) credit rather than +// The remit paths (withdraw flush and termination payout) credit rather than // transfer because every one of them is reachable from `sysio.epoch::advance`, which must never // abort. This is the only place such a balance becomes a transfer, and it carries the operator's // own authority, so a hostile transfer-notify handler blocks nothing but this caller's own claim. @@ -2077,25 +1547,8 @@ void opreg::prune() { operators_t ops(get_self()); auto status_idx = ops.get_index<"bystatus"_n>(); - // Erase a TERMINATED row only when BOTH gates pass: the prune delay has - // elapsed AND the operator's collateral is completely settled (WNS-01 — see - // `is_fully_settled`). The delay alone is not sufficient: `terminate_inline` - // intentionally retains the locked portion of every balance for - // `releaselock` to settle when `sysio.uwrit::chklocks` frees the lock, and - // `releaselock` no-ops the moment the operator row is gone. Since `prune` is - // permissionless, gating on the delay alone let ANY caller strand a - // terminated operator's locked collateral by cranking it first. - // - // The budget counts rows EXAMINED, not rows erased — same convention as - // `flushwtdw`'s `flushed` and `drainfwq`'s `drained`. Each candidate costs a - // `sysio.uwrit::locks` index probe whether or not it ends up erased, so - // counting only erases would let a long prefix of delay-elapsed but unsettled - // rows do unbounded work and blow this permissionless crank's CPU deadline. - // - // A capped scan means rows beyond the budget wait for a later crank. That - // converges rather than starving: every unsettled row is settled by - // `chklocks` at its locks' wall-clock expiry and then erased, so the - // unprunable prefix drains on its own. + // Wait for both the retention delay and complete principal/yield settlement. + // Count examined rows so unsettled entries cannot make this crank unbounded. uint32_t examined = 0; for (auto it = status_idx.lower_bound( magic_enum::enum_integer(OperatorStatus::OPERATOR_STATUS_TERMINATED)); diff --git a/contracts/sysio.opreg/sysio.opreg.abi b/contracts/sysio.opreg/sysio.opreg.abi index 49d1ac1c69..397c5339e9 100644 --- a/contracts/sysio.opreg/sysio.opreg.abi +++ b/contracts/sysio.opreg/sysio.opreg.abi @@ -266,40 +266,6 @@ } ] }, - { - "name": "depositinle", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - }, - { - "name": "actor_chain", - "type": "ChainKind" - }, - { - "name": "actor_address", - "type": "bytes" - }, - { - "name": "original_message_id", - "type": "checksum256" - } - ] - }, { "name": "flushwtdw", "base": "", @@ -539,28 +505,6 @@ } ] }, - { - "name": "releaselock", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - } - ] - }, { "name": "remit_claim", "base": "", @@ -773,28 +717,6 @@ } ] }, - { - "name": "withdrawinle", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - } - ] - }, { "name": "yield_pool", "base": "", @@ -854,11 +776,6 @@ "type": "deposit", "ricardian_contract": "" }, - { - "name": "depositinle", - "type": "depositinle", - "ricardian_contract": "" - }, { "name": "flushwtdw", "type": "flushwtdw", @@ -894,11 +811,6 @@ "type": "regoperator", "ricardian_contract": "" }, - { - "name": "releaselock", - "type": "releaselock", - "ricardian_contract": "" - }, { "name": "setconfig", "type": "setconfig", @@ -928,11 +840,6 @@ "name": "withdraw", "type": "withdraw", "ricardian_contract": "" - }, - { - "name": "withdrawinle", - "type": "withdrawinle", - "ricardian_contract": "" } ], "tables": [ diff --git a/contracts/sysio.opreg/sysio.opreg.wasm b/contracts/sysio.opreg/sysio.opreg.wasm index 2f57a38438539d4732283a062fe21853facf5ebc..df104527da2548e3501f6e4d5731f480bdb22ddc 100755 GIT binary patch literal 115210 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 literal 106138 zcmeFa4Y*$AS?9Yx-tTwsWRa#dhXh>jTiR|qpgA2(Vh5*LduRcv$F!4+VK{XNEm7E; zkB|gRYe;r&sJn`qsnnsCPSci_v=WU^%-~RpFw{+!h9~pRzmDW&_<=d|sxN9m;Jv#-h92Ix`rfi9t zJhxh>O&Z!qt|pg#JSO{8O~>lvC)(Byx6!t~?x;-lSzo=h9vXUOmDrU>)n%#Fmh$97 zvTI61<)i9RR)*>Ahw)Mu-SUp<6~nL`R()Hq=!IaYioB$!Dyxcm7ga%Wce(P;F?A(6MwYTlQK5AIfuD9>H;g)Nz z-L-4`&fQU>q}EN_Z+hE~w?|DqG_QT@Exd@z7rVCa_7`n(-E_GlW+i?54YzOK>Dl|Uw_o$NTW;Qc?F~2YirT7R zb-ioX+_Zhyu4{i_dz4yF$gG}p^w7Ef+Fd@Pt{x|Ocf;;$uG_wA_s(119>q#qp|tDv zj3-Wb#$CIw-F@4xsHc1{4f($RhMTXW0s3>@wL9M)_4Tgj*>B#yXSZiw6*Av=%MWhf zdCgmIx$WlbJg0`D)<#*gYu~o}hFfmF=DKTlUwh528{V-!noyP5&h6J;cTMeGG^ylo zVQ6LQ&h5Kz+j;Xfw_khXZQFf%%4T=$yx}G~cg^hfw?`{ftVOdw08m!y!3IYkcJ01p z=k_R5k^zRK+jh{6?a^6E>+jmmNN>3L#v5)9)d#4&jjoZ&CZg*juNt!P?GAmiraR== zzVq5$+oM(WmUeD840(yt&hkp?z%m3oYiyR^cFT3!qt)rb^e57&7e~Eb9QWeMX0Q3u zm%VIq@>}A`$u(*7e``)A$?CY#I=h!9Ns_LeOeXbj-Q+pFw3qZI)84t?x;mYl?4@bi z%j5XmK@tyN&i#3>c-bpoLY~RB=e{CsCTX7ZUX{G;73ZYsdFM@?KY7lpCwu=SO?nrs zP7|6)dape1!f%Tw&;9oGga7?CuX)+}fBK4x(sR=9NLIf*9j<%DhIOwwH%TYYU7f0z z)IOO`_VOg2j5n$`)WCHke`&H&|B{3!p7o=DahhI0BWh>zoXIriKZ>2}MH9M~@DuaQ z5R>>-s&mmlnovrQCRCKmx$7n;Uv=)}>O8TOl>SZj)Hzzvd;O?q`j7wfM9DzO{^FP} z(Tg-a_nhWANwdf8Ig{s-YtyDiGu{!eTGdSBC~mzh-5(co3sHZ6RGj>y>eBwUhIq#< zJKvr}y=xrVL9aCKUvtg%+ppbm&0E2cG-+PDW5CdG1rXNiIecJo^_qBc^ZA95C`#a-wf3i7`CZi%+*)RB= zN{(NhM@4q~FrFDE8+h{6V^o!DYD&pbcKp@AI~U%9&KTj`zQuHfD#s*^o?7n06;(ynZsyle3~|BI@_^sNbTV>{WhwgOQ4oWXXas7$NN=_Ja@FSDm{s%CF^v7CwV-iD zMY3FCx?Ezz6DI+CTGNJPBhSrp5kUd0IU8_y6q&XKpR#-uc4^ z_NY^R%d6?k6FN75!ZoraPkBeRc{JO9FDSmB^ypH-W;CDxB^wnDJ{R8#CgcLFfu#`& z_&A$koDCYyn>6Jm-XNMP_RS1yxdFf_bE|)MjO7M0r_9};ZQ!7J+6x@4LlewUg8?=N znziZ{7=~2gbvl}_v60#E0qW%p!bV&G$j*&}CSVk5<;`8oLYh&I_aud+fUi(llLzbu zX=<<)Xwnjpi_okO1rM72yxE_L<9*Z7pkWO7!cJe?%%eaU{t*rd_)z;yKH;{9Sd27yMNC`H4})44&*MxHQ_Uip>0Xlw!06mLyG z7Y*8~hrbSgUFI&y+tZu(4tw*IZ_o4ax+~XC$M4EJ(=l)YBdDv(JqfRyKQQd(4XRXH z-gpyr_Xi!}5~P21cw$z0cZZU;9=T)C)y*A)4n^A23(;04E6#iG&YK4YP5PR*cx=;B ztIzxWdx!im7~?7i?{?k>WcmF4!-;$%PwyX2-pt+Pjk8Ats1lg!SeNm0N%!5sn*(}i zhlde7Nu?QH4_f(z)zGCMn)jxIza@zOSLWTgXYt&xfBD-Dh2XQ_VSE#I|Jg5p`^}J) zt~bK=3`9}!+)w@G7Ef?Ho)PLvW~}%V(T%gJg7s8)xekrDH9Uv89Sq8aYKp zotge0r9Sky_{ras@MGHI;;YqNR|8};K0vCBXM!}T56=2bx(|v0F)R}NPJJghAspgJ`InLBra#0sPv5?<&Xgp&xIJ^?*rAz&0icMg?%{<8Us* zC?5Dvjj~W}nrxB{24V@P0BeyNYcilM9QdxA#~Ls|*^G=2zknedGZKbq%AS92pC2s! zCz?sT&y}KSA+~r!oJaQyvjI~C$9UI&|M-9Wle5B$X<_)>Fd@T`d=Q%w_T6#+Fd%JX zgtX1^am7Mfw1QIrh52dB0JKIJSgoS*86YD_3)ee!MIFbJXnJn$fc2gpSNHqw7(_;& z$^FRL5|0~&;V~khBGlAz06HRZ5e|@2B5)%hu;#?4Us{o6STuv_AQgL4#(H%O$RKm7 z0WtGPX-c8sFeBv*{3C<-3q^+B^B}{-nIglA=Rt;Srr%@<8}&cqdgyet5!ECrI;I0{ zNFwlD8f_8Z>Wu9C#BxY~?98CXkVSf&vP~roSl5p46_ubyJEP}CQuL(+CB@2};8Z-5 zy=uTz!4?HtvoZ4<3rfUbZCiND|yLHe?yu)hh_B>q+@-&Nmx1ysLJ=0Rjw04*n6Jspa zlf1Qd&{R3oOChp2Ya*A>XuLPPlGaX1Ww7zkWX0}7e|aHgDw$PpMDg+M=w!E2IXy$H z@~)A+7AW+ejpGKRDsIl>**p;lse5KXXS@|u1*+%_dc`f$I~ehQMvs!bC{P#a)ljn( z?;h%@e+9!~1_TvN$l3uqWiRjK-L=%%XHF8TFqJzYwIM*0(9%$n9Ol!`TL%1;F0xYS z%?`S}PxGE2ZaH8)6r9PMGkG@@j|+R<$YS2a;mm?|uyR1s#pkhWn+yM`g{ zQ(;KJhwgG{kmlXl(_uo}F~KMN7sY`b2EGbiLXM}I1S1by?T-PP88cmwPKbN3T3rfc z@u4xcqvd&x!Pwk_oU^LS<`z_qokSnf1n}_fWySx~^!y_wv;`0$K0E~mAvRz11}T34 zp;-Nf$=FyV!dVKISbS(9 zY{MzRaR#uk6{W$V<*;bxa59^gyj8)XDZ5t!EDR7i%QGI*LZED%VaTh#ZP%hkcMF zIzNpv^)EJ4<6*7)t?&N$+3<+j16=)L`PeXv04aZ8rZlArG!eQa-v1k+R7;6pjXd>_ z%2X@_tiZ|lqmL+G-k+X(aQ?{q7CwFAQr5_HrOzm{OC_56#qnsyo{OI+^JRJS$n=x# zef;gs``?jl*{9#P?91C%S%tsp6?Te4zZqqh=iTC{U37F7F#5k>6|Cg@#9JZn;y#&89Q&U{W5Yi9BZR`%TGGrU}37)j=?XXbJS znR}j@%kyQ%=Ant(nBEfuov9htBXe`dF)ayXYi6Kv-A&Iu^#1cVrk8Pdr{0qaa6{@Q z?tU$lTF*T%bH8MMLCXoyzXu)2=H!i6pp@+PMz+){+v=5d&QBVH38oPa z*RlB-Fh9oVC`u={aGBVcZ1o!65^5+;f?K?0*3_l0*V6S``u5i5RC7p$-CBij@tUqL zYkF#|rk>Z-D{InQnvZf#Fg5jRHC+#69ll_?P2-#hA^V=%J?e_UUbdz_-&g~0IUc2N ztEDdksXlZMljz@OjG*)+#OG;oJ9qM@AN%lwU&7K^v}G}cpLSs7C|(ywaF^-MJQfUW z-7`aegx~Z^NH|Q4p0f@nh2rZ!gVEVa7M-jMtBs8tOT)Xg{~Fj$ag3?V5JT^y#q}Gd z;7&jJ!*g>_YneoiF@~`JL&S`d8u$C}jgthH1;bizEL?2@L>H=!VdV5jzl<8z&`xKD zTNB2h&IQtvh1m2-&B`La1~EFNgzVclBzULt zHv#YCN!%R?l|?;jm|n-AQPpI-=5;!3c{TPf9W>I-1$I*Y#OzvhH*fGG6Dtoa3HW)_ zpd;}|HR_3s8F98{*e$;HyHWPKVUJXxAkxiHFj$s(_}R%xqq9&XQjYqOqleqM()nF=^{cYO&ZvL|su^wQ9vT*Y+oU~Vbl zBuUhNI!+s=&>HfXV=bxO|hQhAP1wRc&|RGGI|!zA;?`?PbvO`RN+V zK)>>IDwz@zS*@M1%0)^b*pw&c6h4pq8qazB&-x%N0kSs>*22+=ne5GHlMhiSBJ&3d zC1nXUmk%R#6q1tYh#a{{UtLS5r&W5VmOf$WbOKHZdi^P0>Jq&UB?fTgVvm37_YZ$; z|DXXElkX@k?!h32QrZw3Ylw}>iU1qqNBHUk#q_UX#M!^p%7-3J43SKCi{sJNyL6pE z^-3Vv;+?j7!ugU-4G~*+q0zId+mIY27xdMV-_uiAPw9mQ9A{h4F8=;&Vvp9Pf-F-i z(#ra0Q!Nj7CBvqm)68D2UL8s_%n$i64X>Pv>bx4QQvAdqjy)hxHON$hl<`xF^f<3( zrr|U2YMjZNjazFSum(26Wp%cO{Ly8$vTtZH1J@}2!xv3Ui!Fm33kj$La#QoJ;#+U; z|8*L-OQc81W+=YwF@zMamKh-3Exk1bUVfloZM(#j$86dq0{ex9XFl z6`;E(hW0;ayiYh-@xIh}z?#4Vz^Y|Uo;d0Il1!^o3LPchhMrDH#0}&;UpmMw{w=ySVCGPtfA+h)-{Jk|A+w zl(@c1)Ce^p8g-SpW|Wwdn0Ln5+-fB?7!4DMf8N()PI`AOHhzjmH1o(*7RBX)(x!A| z%S?UA1cXalW?C(oSVJvm%VbS$Zd@6Q5?MwTn;No>QU7O>aYg_V))~QwM8pVQFpY?0 zMI#h8B0(h2CF6uLXLe60TMV`V^-l=rcn}{b?_?gJ@i_Y%=bepg%GimiP&KkN9x-YV z<6*YuY-*7e(QAgGs9G-dI-P4I&NV9He5=KMAT;1X5Hlq;OIl!{0EsxYRM&*$>1@%w zE&J2h-ugS3Dob zf*I5{@e19wL*hK94YXNhFLbYH0Lz}eg)7BD3w6s!b|@Y8G{WOCyoRJ6eU9CQ-_1MO z_u)l%NFF4kz}-VideKLaCJvOT5dO2cpfK#B z`^6LK+*1wkn>AH2$QlC>Nr-${RlxJEqed}J@9=T-3-bNe=jgCX9*)OnWh%K&s-gH| zqxgA9yeBi~?&9bdB2D301mStR*Qq`!iWuND<;`q`!DK3VvZly+ru#@K>qI|joubP7 zP=^eQYE{x06qH?awqo+K*ALYCgOHR&F1N6OaH}DoK<(4@c(@+(EBwJ>aG$O$j1TDw zl<(2Czu3+->;r$jNnaZs_8e%HYd59~DWOiLRy!AHhPInpb2W=cZJ|u3o~m~$d4ej$ zENvZR<_(@o3at<#9D9b8;Db`j5lI*{J{$Xb?0ep3P|C?^o-2^-Ol1)>51Fut^pT zf4N@I>qpO1dY-DD)l9BsV#GA*mBi#GRrIJm@|$fx8;#=o`H_JO=zKQ8jRbJ!Qlz~E zs6e3wnd0i!7}IidzLa%3PG7btnle%4Wa4Ea%!ErQL=FfDkVp~4xiIl*UFV+ot3Ume zOOq#g26|7*i~WQ|K6K6R6X<4f@k}z4{eZn6JztL)Naz&4>4##BF_*h_?pZ8`9i8eqWPlKybN~PotO!7Nmh`Nk{u2ypOoctGm(+jp@$0 z1RbL4z%dk{gLZk5cd5A8lZ%V2x{Fjv5-nF?!B|XjBC?DpqN+KWPeUJAMV7bVXc7Ih zSUo!#7Kh8#`h+vCsK8Wf+_ZSY8+gL1TD~h!mxZ1w3q9?Ho(}CV*V40Pp#uRD#j{@M z+0(Q%A7)PSFpP|l>O>*cq6}vA65;11LwUkTkVuZ@!`C_zJcm@TSgRbHLCWxyX5aRD zp+SO^Y-9}#Ox7v0RQckDnb!Lu)kbrLU~3l@Ak zC#22@<3)UKPDDcrxQ5twZ_gg??o=f)X8zz+9GVE7F8LRFd}hvYaqHXo#T?0#_Td!qL(;cm*Um^CoixU`@bgw+!3d zn+zLvt{xB=oOiY&%bKt)6&5q|`Gs6w}xz=!D{!mxtr|)u64k0d-B_^6>uNpROTIW9XnfJd& zW?PJ9VyKet*5#o|f(DCemkFlP=DfcEIiE4n3@f|UXXb)112A4qnlw}CNV2|~ebLIE zS56I#s9Ij`DHl7%oXN>*Qr}os37Z|&>CKf2bfb6x)TtWum-hzk8b+}6<-zi$O5M#o zSUz@XDr*NI0#~o9~UoGB^#)# z|EF#*GK=cdjT*mWahn!Tp@>ujE#;5^`--&*1B|50oG>*jCE+qBvk+|>cN1H?$~lP; zm4PH=AdZ11Q29)nBV+$g;?~rIXB2Ytd3+PrGL%BY*<@ z;`G+7XQG?3pR+co-kJ)=FXjUgC(bd55!v(rko>$RMrj4oUBm|o+byix*aTAHih!Z$ z`Ytx4Uz>V|RM{Lz36H0(8-?1`x7w99bCVcJn{pTR|Bl^mtWpdq7f{73A^fn!=flv> z2q~9}T;U}w;H!V2*-&Qir_};W z*(_8|rS!w`tP80W^zs%uuX>TzZ!7gK9f>??GF8d4L&Qy2X=R6mp=AqjVEkvI99%x3 zQ@CP4g6x116lyCBm9<621F>id!q1ouq&ZL*;;pb1 zbic)_xcetf)ih3x+6a})jFG)ZJi-;yS}tS!HP!oDg`KWpzMMU7;HV)Dt8~}g5vm{j z%NQ*UG|kraKPvVhs$tnDLy**Ls0L1H8VD;t$mIpB{1hVUx;ZPqWSNzps;jU378Mw+ z{FJfc67seO;x<<6bZ!QaWmkTSYF&2arv}DWev1k%yYf?^v6bJVLd&lFRA_AFx2VvP zl^?U_4h>(Je4v%IMh=jg{YG00Mw*EE)|l80$>R-bND3+An51E#+&tcx$-c!Um=1czX*)|F9l zp9Fy($fy3Dr*iiXnH@&GA*;H!wLtNKrcpl^- zPsm&X0A0HOz1>M|*k^P)yso<`(Mg{X|S61pYRB)FN!Rpg=s)SL&U1^O9j@beWl)*Ub zw}ArYnL$y&0bTXl$_M^kZi|>79I32LvyRGRoBdd9Dk$7c4m7wDQfl6QF$7!C8wm#z*)wFtfODJR zWEm!ZqVWri&9v&zmYHflgwrqRvA@=6dkkRAzT?Z7K^b27h>#oYqsHfF3^aaOO z3ny=~Io(V?vlGTB!qjc7Fi+m3tvXE+J4ITs1AN}-FQ6nnO0r#8`e4t7p${c$j=(z@ z+)*q9({o4v;>ai5;dPj2*xg|XNBp~};98#-_eotT9zv1XZSP0Vx6vK^L&Utbbd|Hq z#M0}Drf9u>pd~&`huw@;mEHH$yp2Ot#vnR=>9Mo$`*7G>b!L#p^oWa0ezW4(7lO+k z#*DXJgD+dIz1K7>Fy7_gPVaZdek|TQR8)UsEBi*ZxMh&p?JPLd{|WUxj7a_GXoQ`z zw5l~pH@LyjHcqy{0VSePO0CdZx#bQy3z=Ld-`xOdlyO?K=jPp*o)L(G#dh|6(0ats3%#a2}orC#=RAum`%eA28(XcXQy)=1&03CNQh0+y>;L z4gw&1p=AzZYxaMr>|zLb78W`YTqYVAIenQ^*|Q00R(IEPfl%a*G7}EJ#C`pwu>rG5 zj#91PimN?8%^k6jz_i`yf3RjSJ5)EA-79LtL2xDpvtRi!5u`HHn|n`VFat|n+qZ>f zd0X0?B(BO@aiB<-2ZoK)7FqFI#5WgCK4nO3%u$`N)d zyVg&Etb$+}8GahO)+RS=NvW}ZaFe}2v(_P#2)}%W(?c`pjvtU&%ci0Dz?qn}{@1)s zz^n)*u}Q)n`AwU*?|^+lm%%M+tPeN6STU(__7a#?lnJ5a9M#m}^NRYkc!Cy(} zCD{|Xv<@G8@3*{(QgTr7yzuppNGu{haa0n)Y?9_Z$D`tWcAvN_@*Z3Gn_Ojwm@zok zL=CgG5=!D#8Y6qV)vEZCQKEFl9oaj0V=5ynB_}y5;>{k^cosGAzYC-52QAhfUhkqn zCR=>)N}{@93ov!xWha3@C?P+@_^2Z)8D|w&t%_f7;~f=toguqc!j1P^%0gn+^v?Bs zaakTpN87qZuCxoIWt?+#4r>Zh=l&RP3r54EOn|u##BL8mRbmu}w2oA;m##=sh3?{Q z3#|5TQ%4RXukTKh&GZmSooQOkKBI-5xD^b91w%0fe<)&cVrKNU2u88+Ai^tSw-T3{ zyN_B&f438N)X@bTZ>B1$V~0~~*d1+dbVL}Ii%QV@!DCUj#kQQ79pVrgU|q!`iVZUg zakX3a{~yZy^xsv*v2ma(Ht9^MDl!J|UW-?Q}E0Eedx{Q`AGLG;)do@luLj$lDB~Wu$Ly)YU>1 zWf>^?EP#n`!W`#4l+tM9K-0wwZX-i~?B)}BYbPFc zHjYAF#e2R;hfz0RO@p@Z6F81G5g*31V}k=qXlHvxk4=w^R)Xy%zC){#qbb8CZV}M# z)6pb#q$y=Dqqmm=D~tCS*l3;;4T;%;ea zDiilM>pZvhBu^nA%rvAa$thuKZp+sKALYDLV|Hv#fY}+b0$$V4ffJ&jCO;a*J2Q65 z!_w#2)R_G<5yX>PJ+LCMjRZUs1THybFSwz+=E&0x4JlB^H_+lqx_3}Grm_T=dRYaS z8tN-mVKQoJWX1B_>&x^i;?&a_v*({G&0vOr^MK^2Voq5Ydq$SdAt!g)CK0rCgQZ7E zmjSt(Z8!cjH6!qAIA{vtj@?K7@*Y0s);;5$prc-F^2>U^I*Y28dWH{ zw#}LOitB_S_`Bd*lRT|v(}c8c)|aw1WZww`l)sHeRm~P*WOMv1f{p1m)r_W5Q4QUP zYFuPCt1!l~JX6UIE#FpDR;?BE2y`p0YGS2Qh!%sW+(q1!$B0ITjl8EzdzOHRo9xgB zdec^PgbHT|Vsb#cCmX}IhRz{voD`Z+H7jw8M{O$}mDRaStf0A0&^XyZ-cXG&mF&Z| zT1a$M5cwGYyTuKyQnjP};`*T{sr6*If(>SDVEpJK^Jjf8Mh{`(U2qLVvcHGN>>gTJ zd)~NWZSfKE0rP+Rkw-e0uLUga>__Oi*5}q7`Xcw}&d+9~s={67NM@2DhGb;M&f=W^tmnnZ%qf-f0sWbetJc$Ga8pKFlE| zwnYXh*bL)Uo6j=;E_vDOqalZq@NT(hSU~i$f3f^o1fTrnUwr%}KKfkSz?;En z#EHf>@ZK;AKw*P1s|Qqts(+(x)YyQuW#g2)sAag)>Og&xRmJ+0lGaz;fm-a}T-u8i z+kilHL??vddBbQQ{;JG|XA(xs0Tj@NZJj>RbpPMUuqe{pf`kf>_d8o19Y_DHZ-YCg zRT5JCXv~~qYPIDNi}#_)6GB+`*Q8_4o>czqk=VD`r;0;Z z_+R~H-P6OW>=Zi&MxLIu^7N?dBeT80U*e;fZRG1h8+mP`pN-sxwjp^$P(TsHFbv0h zK;U51HuOg-i2NIGCKHvX#BL1WW&_i`B}>H5{Xbq3PC*Y=G^4nYqzx@W8W7W~Oya=d z!7YA*KEv@-;^PDJ!09ELO1l^krN;@=FSaCylf1|_iHpez(3LpJC8*m#bJ);{E8hBb%vh4yqNy=^%H-cZSW`E0~d356A#Q$Ss6I6R9)n$Uo>%kQ)=vWbEi< zMs+;5V*-(#RH?1M#>j|Q#%j#Sp&iwjB7gmez-Hd@d&*j9F(fUsIDx!^ws`Bvpf0z- z{x?q2Ee4DvM$2K=QzWsDvu2*{nqIy(`>W7(P;POveGAG*Y}}n<@t|48$tX;@a9<28 zS$HZc&+x@a!QvrkS*BwoE}{WZq7b3Y(j@Hc0bu7ER8l-rs)Hf3P#h=LW!d>Ev?1B7 zA`57*K;EvUtI~+1rc^CNc9;$X5+q5goi8cocSCOg9CSRpy9#4I6~iQ`mfup1Oqgd^ z_sU*h@4c?;eaT4g3z2*+WEfiLvKT?l|Oo3~1 zNq?)G4{;fms%b3E8o;^|#EVipV$oTD!E_0A6Lnep!xaJn$SKx;f1*%gB@V^0NtRHD zj%+5OzD6Qd`&oJ)iS6u0 z7{Ea09&rL^JFq$N5!-d96IWQaNXv)Xeec=3Y1w>T_RO>bGS~f837r=swb@3$wg%M( zbfrQaCBO%)cRZHq6H2i3iBLP^>8}GEsAj(qI*6^s&`J7g)c=G|t-$nn5YwZK1uab* zN^;8-X0{)9@HSf_PHL_7^QNU4C{cv(LZ4L>(tI-PfbTLy#i6j}ZnL9GDayf8*GkBs z5*L5-8w`*^drR6NLgk$dRm|{8;DBZRqFP0jM^Sob=xXpwChDnB8#x(gKWm&AW(52> zG)+{<>p8AL|M2r^m_5#?0ibiZv6*Nhilmr1`<6446!|d?G{A2& zcV;pQ$`v97!Xb|7Id(<=M4a%RL$UH07YeHU&tL~fSlO0Qeyhx~5B-D`v@*rBWIsuo zChV%Jy)E3K)vp<%o2$3&wUIFD95 z;>PiPvm!ITWIK=Qg(xg1U9eKD_10%=EPxj7Y7~Z{O%m6@Ipvq!FC3L67~_4_g-8co z=qq(vJw~qF1C=87owvzrBb?yH)3!|c^Dc*&_;$aRZ})Te8TAl!!K};sp)EwfJCPYz zE;Zf&Vw}G6mQBCs=RWq{FX7&W8rcW<=#Sp_OCEbN9}aH?9VuAzz4_D~Gxk_jBL~@G zki%;!vu{kxT39EmHK|DRG5s2M+s*MRAT_Q%A(?iiv1+s}H2fmkibokrI1%N%;wSda z6#FoFQq>=aAqt_UAXJj(KX15>`%NeGOVJ2*xG~gr_lv_=25umG8AClC9QE|d_>MW} z{B)krZyaT%Awz`(zUMJxXqk#J)KjaFKF__rOs|Zgo<3EkSH@6l=`bxFFY6RldZpZ9 z2h~|A>VfQ_o-R9R8#^d^IaA}`R*_cxI(e|Lr9}ELN^#80D?`o*hxStC?1IsCl;}c# zVrw3})HThtoA(e`ES)gg04Ke!HE`!ucHm8M44)YIZ5=CX6f4i(Yg29!%s-1cB4e*X;?!R-tANvH@l~El0nHQ;!nk_E0+P zihp-ywCcF&sWoEsphszW0w@kIWjB!JU2*KL7<1c8f@H_=cKEq2#qriiKc%UATiz7? z1o1{?B`yX65H|~#5AA5shE9DwB>hG&B#|WgeoBKBq$oC)Q)c`+S`*x-A&@lpAr z%62*3qt^}UWlbxq*QgFrYF(u7`j1PhKUwe#ca0uZ&Zu|hOVk=I9U^xK<|Qb5YS7#@X3~BiJe(4@2?9JAv;|{3tSwPXUL7t zfPfaKd!Cp)3(qs;odWs9e054jxYH%~$O1Ov>PZ`Mk-6G_D^ZC41S^0hvk!AzlI+oG z_Q&W&A|CquWNo{s1i6$UnfIY$DF@6rPo3;xSUU+wwFbx4p^;nUAlg*Lf$(7&y_fcO z8lrhT7Zwb~MYWno18PdRyG*i23bkro6dvuX3>=noa}Ry#&M*Au@4+WP6>y!aFuA>I zXo6y{+D?oJ(@4$DVH)1h?6Kx4aMLW!Z%A-^$XvxQ0a?E)l@49VwiQEDyM1xiG*(=# z&Ya$^5ARgTjkao2`p9HO@ht(%Mwkk^7^a%)1f+jmTCZDRK?=>(Ccofl^I43fJz%S? zBA~S1(JM_N>%QRj89Dw)midUOd>;Rw?ZvcKw&WlkWPrxa81MxKBMJI8s=Q5JCf^N6 zMhqL5n&a<9pEaY6S0Iv2wA7%bXU)7T9qE#GBmiB3?P6jdN{P~L+7JQO zOPp7gs3PRa?h*0~u*gGhZe17t!(MqD@kunz4x9jW4?(X_fWT4Modi?CH4nK#%V_&L zg*shOvJx<->K3ZUFva?iFQl>L1JhVg=*;LIUn|4D(I657kA2>gsRM5m$2F-K?3DQ7 zj#;ERvfMpea2p8_U^b>2b}X)?Qye9il>jw5+BjM~iqueOarQ%bB|vS_3Mr!CQc*{~ zc0Y!bq9|>TNzlOVOp2%nYP)m3hK;|Ewb0LCL0q;$%;vo25K9wM1eKL5Gg!Wn{wcZ; zB7wYl3;tQQ`Dqs6r)!?gWiP7v+F(3{h`B>nFCMse&w`17CqP(kG?W5{b@+@zjC5A{ zR*z1<6e-uyF*wqMUmUQZ(H8@2e1Eom-CF;EBBPdt_47^jc`vGU(YhV5Un#qQ$Fl+cNK=A*R>{0-E~My<9%^s zMe4s`NJGG2f;ItMVzty2D=V(O^S4uAx@ zs+TL3KNRPJV86=a%NovA;EpUR&KHG|fFCYQW@cc_+5x7_C!;bJKIt(<`8FZF^zH(5~BhAn~bt|UJkPmi$%6o;IfGCfEL$b;Hz74F))TIZNlxBbluL=Nu2QE|4~*{O8+I z^U~`yI0l-1Fb3B3nt<@_xxhS3D7D7lod@~pakW>nS{hHB{rlK>@|(ipX7nS>jyfp# z#t3eytNo9ji^GVa97lm|lhQ60n4&oQ5V9k0vm7!@KwGOeSEt$*E@+~Om<{}KCdpI~ zp6()xt+Wp-BHd6+SGK)waWOQj zFLX*qDmDc3Cbb<<_wZRr1w+D01(QN8T$Kuj&&ZZvY{W66WLxU$ASn`SCoCGIio*m3 zX|Zz9rTK&}XpPRvhBs___kjV+Ha{7>tAk_svF^x%Nvv?93!enjtG;#9RA6h(N0S|j zi4bWib_cJThM+0t9b?#Pqe?hU$;5P9d+YewGdt``V;gk&>&vv%mbO&1vkx==spO4C z4|9fQ2i4gUbSMgkXU!D$tw_s_f}Y-}4?oAZm&NQ%FMU-;{!Xxr8oFpNilpfd^z~LH z0|ubV3XM1VlBn=aL+s*$i>_eE-J*T_u*D2EEkwsMw>%?Xg`%%?Py?iInr3Y=#YfAi z#IM!sZccA|d4CoS8V_Vw?f3NUGENV@9JgX}WIFY4*WRyC9oHD|Up$VyFUPRHjz)$3 zB@Ihkv4(g#>tN{?@3;bi(dElQwHAb&W^7rmg<_Qfnz}R`{k(D5WkV1X4&G0MiW9F` zpCo$Tin~}^a+tIkq^R#etdGBqH2`#4G?1`n2@SNof!3KcpmW=Z#wa>Vdmbw0<51r~ z#jKc?B2RCGJZ#8+KIG|D$YYs>%!YHe9uqJe9DMr)0L{aeWZzi~A{Mj(I!!?bfAKhU z(o@eXz5}1v+N>H%V)z-srl)E_$?y z50DXG3@0Xh=MM;f+BXl4>m`flrpM~XI-&OA%zWT5@2LIW^R~}6j~>J!=D-q%$$-9_ zRX>v%+QTUbXlpEZvf^& z1NIHuW^tSNX|w1#+e`JSY|W4LJRRewp#h5ma(;(1BGy(2+__F562LHYR{r;wq6cKt zW>)Fw(Ur)Q#&mmr$X9amBt(Y>alNRk&@!-~D(Mfs7Xc3Ap*`ZMMSXzd&?&$n6#YjE z4oFrqRR|%Lu$Xl9`8Bi4jq#7p>~JFQ!9PU06C&MCJ|U6ZsgdsT7_ajUp<0o?7Ahms z?ZFl0_JnkSB?8oh^)h_&8&*Ix6rD4O3Mjqxa4cqZJMv!NpIu&rj3I|?v}3fg{76gW z7%rx5Lpwu>lD17UDa3rNTEcsmp>3;HddxorXut&*At>88QlQas_^_UEX4=$4_e1us_>?c5rWNoRm8D@+%W8ATvlNYR2L9kPS zU>`Dqtqs6y4t&?RS0e5O+aKp%LDkNe;a<#*Vi>>q+^Zy1RDB6RE^tfMDHG6?o`@6C z6G4T#2%cPsjA?P<$WuLmJi4$_m>6gul;Kz3^#0=B#|Z_=SbV_97FdArX&X5;EKl|k zxhC3j6&(B2C;$Ane|f*mwmd&L{~I53KXGn8gRZiSdjm^#I`~S2;871!xsj^<+f4IBh@xlvGg2yV1@4w+xgNE(D zA#B%fE_9c(r(Gp2u9yiaH4UZ#=a!xO+MVJx{N!?w*dz+0&;& zd?2m^TJrrjp-#cvGjWH@8K;)DC4Ixwxx;F?oeeGa#-ML@PFC6BirFoa|2Ne-mPo0z zzDdCn=M?NyhH*?|h*Qj}5^a-$RV<&cVj;#hDR@ofRjsy3f!}hIf>lw5ZsG3@UfWt= z4@X1RHYwmoiaa}kw+fLGp4z#ZEjMZloGo5SM$UhFOnLH{@3_M*3#kId-#N!~%-swt&6u5rz*)P#DcBPd_u>L3GhCrjgQ9r4W?>A1fd=#gORXQ#( ziWKe3*HYsVOfA+gO#B1fb+SbiSJMtwn)?;Qp`K$S?M$YKu|Q|GyI7AbpUPrhNlKmL`JM zl6vU>9M3Y}?6Kh_XsC!X*?NfmH;AyH$CG30dX0`bQHCm8*bCsLFO1g1e?SukT$KPX zG2In0-(Sn27ODf{7PqURyE!(I3zn&KR+h*NTYp*idL=qAxc;*4`633m^n4{_o-baM zp0A|ZF`)S3(({!RmFFvw9T495s|mbO15w5 z*N?=UHgDfYS@skhsw4c6?fi5X#{=uK^ho#u3=_yyPixtR;elnFqzt2`31TS#n6+Cw zIj0_R%4-;A_9dAhP%c2RKbdIhFsI)B#y)*F6l?XA_@p79UX&v=~PAagG>H@@!-Jq*5TwT!+4_ zWcT7(+5HSo!z&J@;!1?}nDa8Rjqe5zQlEz1hiK7S5-+q}{Uy!=UGsXLV=Tr?% zz3_%&n2>-dm2;IYTZS(&3tXz?RZ@&3Q}R`?em$%-&*LidJg&-LA~vK`!d8LOdcERm zeFb277_lYg^QY|kx)Dc7r|}$X8*%!x!4+hr!Ax)l+>T%Fv&wCj=$hvqdcGjui@O=}U6l11O<p9wJXI=)1x0tXZfve zj!LH;KJP=l5NXqc4y~HuOT{N%s-VNwu1daS43p;-#2~y7A6{iVC11rUwe5VB=CA2x zU^pkRmRPtU)t(H6xP72gD|NKvr8IYgml+zST9K9EzAuB;q4ew%Mx}b921!gO|4%N9 zXc{_`=&^#!5o7kvK{VnYtHG8b@pNy_)NpQ~rV2b;Y@Bo*z#K+%u1e0h)@~j z$Kz_?oRrGImU;2-pe~?%*>Mq`dzJ6Bqx`d!_kDA#v~Lc+*O7?%T4H-$?Wmlf3DO?Y zO)wv(-WYL*Y>eASmQs1s6g^fJlbPN7?VO*wfoA?Zt^eBFjiYZTj;7H>i~3l zfTGTk(qoEQ9Mt;lKlG^UX;SVW>J$iA+^Om`1(H0ETb|#JN87Ydmf^E`x@9urn}7sO zYkWC+Gne}JW<1Q5*tGS$W9>1;rD@;$!vOzh2UcPOP#6lkpya7WxzlD5E~B8F`u$eE z0l10R4XryiU^vt*KKBh+d==~O1|&P+OXDVON2olyuG{?yCkOwkAntRvbM zjc^oACat0o9vVd>NDN@a(dfz$gB{Sspo2=~4tThm<>_es-JvbPjdx7@RbCKrBD++M zo~=M^jAVpu4B1)s7Qa7lL$XbGQ)2lRn zkDHCnCxWkkiL=>iOp5j>gy2O~Yo3B`zH*S!+m!$S2MaJ!iYS0MgBo0dCoRAQhpga| z0T;fd3tTL-dAk$>1EvoqIDlq_X}2{T)tU=P*}#2S?jDzXfE!Y;#(Zzj@#PB#uZW5^|1$g|+1TpXd|S`(b@d<&ciO%fC>Xy1 z3a?%Y1+QCBn0yi7wTdSZrps0=@QM;c#V-WBRxy4%NkjRk_IMEIILlG}gdI@7Iqv!% zcF->!cdfCogVtEsL8#xRuV>(9*<@h{obKn-*R$!vK?&~;LPS1XOg2(Qx{FtnN z24NjD1`gKl2o2;Z13U{BX_9~w=PYJ2Q&7$bNQ!{Nm~|>3ty1n1NHF*nJPS)!Q2qVW-JPdajBAjb-t9w``Ns~F&dAYJK#bP^<4X}DpTCyh+T zk(jmvMr6)@vNK^nhQF(3iuC~_=9gjwmf4lql%VWRz=)NO5i8FGBeFhx7jZ8bI9DDg zkP0IdkToN4f)_{~ju%Xj|gWk#^tlF8fm zvv7UsTI^HkyQ`#VPD;@nYc9!4R9_rjcsllGjW8?6G%FIDhYi=4R4q(r-7*Sy(V2!J zSkI;ASz!>%NKN`HNH50}0^zf|IyK?rZYc@hp>MeZDjSs#CMKZSm0ZBGNiJ~hj0-_I zdSY?V;Ua@M7qs_27u2y8TmV3i3xP4Z;N?{T0%KGefiX(NiK-{G%5?IxaFt1o?-6ni{=F&8#P8S5R^dD$3}0{>;PfL6btn}^vsxcsN&9_BkQm*dkB$DY=L7mU(^JQK~(3=I?x1B`AhT-psF~Ps!Z{CCDT>P(k{KVOK_)i@9JH zQw_woSs&O)!_H!I@s;b^XXNCclkbb55XEDgdDHXL-f6xfrxQdJ;XqML$yQ5wdI;@8 zzlsj$4p#{qa^MsKAa-jkVc&}xQF+u`I)rO&NS7}$&?m*OM_R60`eqxCWmIM6zvKlS z^XjPK63#gKy9OZ{`Vfx!PZu~(q=|ja4jDblh>D{uie>l3mr^}&?2%e0by)j$dGVRA z5*i+I0SYh9JkRk_xB=EMndA;B&_brUa6QOXeLU>TYOG@zgKaQv%4zO7@5eT!wzDr= zfJmE92+H1c2F)YA^f@|w%-nn#-Xk2avmSvgM>B<^dPGp33Tfh(c7y8>D_)rnM+(mKF6R8d&5%g#s>+G%BLtCT!JM+$!Ye$a+qG%tZ{=H77Qky%_R)anW zWt^}fmt7_%%B;rq4wGi~;jzx9ZbJ6X66giM>8#CusQ4?eKm+`oHD*UWaafU^4yDG| zVWEAP$y9K}L*e?MCig)CdA)Cc#OXAea>aTJOTv^de}JsJ&mH~?cCy2tb$zegNkTsi z<%AZT!z^BzaS`ujn(a&|0bj0ZY}+~y26S%9JX^gB{z-ap0WNquqriPjM1XAze9aDs zVs@N!lJ%nUuvw^Zbk?lCF+XTyA$Kga5Gh0zOwT?%Pv!Ht7nigw2rv=;nj4&clEMZV zIv>(2&nR%M!tXo}fqqss5dio}g@vk-u6b$|=;!I{%k(PH&(o&}C<`T~N)M~Gbr@Up zaSZuZtHg*`%N;&dQG!~NImy$xvrs?zf+92i8=kIw*YuO^eU55a%O6&Sp#B58KEMKz zLFq^|OBAKsB$a78cOfKkJWJ>%3AAj-68ntmsq(jmT5)^Qf_8-J_weXs;zRXMfrUd} z(0t3t0S=z11^tOhd&nDKH~?SG%acmXg`hu>$Ao&V5kcWtl+SC;O+#Cg zH;SMA(kH(}e5or4eAHuB?)1zcWz`P$(M>+?!gx1uah)?$qg zv-H&~_lh^HcckiFx48Z$y~o`_q=enk44t&&5^F?q}fnhfsd3tOb%QkI5IlWN)d+7cW`K7f)X=^~m2;62{+5l_e?$Vr|`n zwzN>Tg;WWJxkJ9_!-ML-xht%rk}n+?2xsV{4@&(RiIxD?j>|R_vs6u;?PnJl~A0g%sky^_Kl&$BZL*Uz~UB-KmiLBRlGvZg>~g>3|)Rm0mESR`MH-jJ>pjK zjCc&il@MBJb5KZ%C(#ynx1$t4zHG*Eb+j?cD} z46t2X61J%;uIn=WTrxx^w5vD>%gq?l+DQh=CH|_-+J2He0@wosnuD;}x&hqa3{rA~ zPe;Mv$i$`x21uR3HrSy0jeNA$OWXT~ zKQ9CTEU6Z+R_URAG1c0_1H%AO=MvbpZUaP4n-aS4B^L7lcifcT8Mn?Z-)#XmC;oF8 zc!Y|Z-%!PaPN`$mW-jd9ZZUJVhqRhGn`cd!oj;)!3^Uhb=Gr!Mmf4OFK%baVR0pic zz@byYVVc4`EV~N%Uksymbv8uXjpFTb@F+rj>8u)f@MbNURRfNl3^4|E8KTE*?FvCZ zMYU~wC;uS8VM@(^fPG2d-)k9E$`d(8h{@spY~!6}{)PF{;hYi{= zK!xsLCTL8Y8o+$Wc8mA5cqMTrjafx{u=pa<0|gK7--Nt>QmV&hr|d|>`yfnyL49D0 zDxt{Ye+d4jVm(m$@ajrVab~oPXChJjodZl!VYmA?k&g(zt3{VJs?_1)?4osod?5sn zO&b-ujP+PjqwM@+e5Az?$1qOD&7~M;)&4^QV9g=`h_@4kg8`z(P>uB9H)qmgBRv>u zj>*Mne1y41dVsi)(i#;GaQ>Wg}5)BwNj{5Q63{0c-?|Q z=~l9er7DkA9G|-4dX&dl#kjyUYh#=(Ud-7}Q*n?dQXldV*07|0E*9lA%k3y zE972LMtLmms>CBasq}rm5b0|3hmX;sJb;E7xfi1dfk2wZ(xu;XX!On)8o2Zow}D-i zn#Z956K2hTZp8RtkhYk#csDszd#Cg1ErF3j2PzoB-W3WUB*{2`MnN9V<~_*60{VK3 z**wu%__$~Dp4dElMqp(5Dg}9TYoca(d5JHut00fALJ~yy&gKauQ%GMK>MjVC?^kN(x^u<82i)DxdCO-neDbRtb1S5c@BSnbt5O5UJ zR3n`6@Tet7VbF2H8HWzh6B1%I)^G70INZm%Q#p<*6k$ZCbpCNgP=AhI+# zO*+|3j>D;BQp>=JkOYy<%F~ezuvi2t^a??DrQs1V1|uF5A`(u;W7xFg^aCba)Io?b zj5Uv@x?UJ<78TtN3hES-B}(k%tP`8*_!e1|fhuFnp18_oq=>xi3No^txEhw^K6uC;7?GQS#1W*i16bcxt&M^QE8xBHztgN;HuziMjG<4S?X7i>iovd;= zZS?ZVEN|7g(em&NnhD*Gi(iKb8*5=lXuvwrXwa3`Icf$@(GZ8UNoxqWD_N|K0hcYG z%S>7+`aSRiBG|GV5n!zpvp~}}B4{}gw9c3a>{M)a;4ec2%uSQIVRcZ)a&S}owF8!G zhe(j5J~@_K3;`@h+GuhV^3mWz$j2lXkdXrz74ngBfx~vV5b~i!*nFD{As@Q1zEA<6 ztta5oDsv#`DEcxn{9qWnUvOKQ0<3#mFB07K5P*iScD7#VO_ z4;OM7Xn6O1?Z_`Y@Zql=;oujB>sy2pefkVEm#&75ia-`ljkgaLjK&>%q>GJPpVHFz zhQhf0PRC`Gah$mL1&T2DqM_e%ifxiv1%u!OzQwNO*@$vE11# zpWwRcb0m5?52m0)@L?bJ5!>J?dA^f}b&P@S{|zS(%Rs~8y0LYwj{lIO3G;BhU!|+N zI0(_0!5ZND~Eum8y+jY zatC<&RM}GH4zQNOXHz^6^Ax(now)-%-QCTqRPKNt?f^N>=mj{2r50m8j75x}4LJ^Q zb7_%B-Y~NaTlVZzIA-n4Ow2&qL8_Q8b4=c)!QO@8pTS>WI;Q93TvrWMBGWr9P+KAu zChn;fu5mDv z$~cOj!%MgYpZ_n!lxdCp6Ld^LVkNkNgS-JXLh)J)rLlr{Z@{TenO1i$j26mJGyL!t zaH;xno7=qxqPmF9ss z&%U0;5+Czlk=(|biWOL%gzs-^ng0x)JC5mLf!NAUCdARX#5pw!1486<22pvc355|9@9GBBD#B(Leg ziFO&jw%Ear!cjTZ&%~qd-Me5#BaWgt!aPv?JKIN#=Um=zG#O1-bjgL2GsjzE0F(8AX&Fy1;iLO(~t%5&ZkGQmMhI+LSV&R2#j z5eqWj9dnujS9Vdzrw=*&k9Mt(!hV8ly*CnFtzlvuK8y$);_R(b<4wZVQ-h9I>(6oKMicB~Gv+)r`SSwJ^b zEX!Re4RhP4VO?9$sr0pnI!MmUD=&i_bl5Ymd{0bx3^%Bg1+`>T*U*ZAlq0sjN|G9T z>*SM;OhTj5hi@)_x%#QbLmd>Vu!ay_+Bsu?zR!kcVv|Rum;U^Jn^fO+WNkyB70ZRl z=4MQ9NcAbiO~{DyONe}4vHl^%DX~BzEKm&>e+Q93gX;}Ip$bKiTqdVI6^y=t7%D(3 zrxnF?Pa#pN4A%3VMkoz5^4@mL<}M(h4M;pI{#$vM;X+G`|v4wsT>$LyVs}VCKR@ z@u9CMa7?d@dt%}5up2%wseHl-)*sYiOOj8=y0x_IGk#UyCEZcUBEELU2_A;Ql4GoJ zG)NZR5@+aeWYOSNr6eRDOBs+$SXF+k>@ubyiDx1dR_X66cVDr1zX^MsvD5qvK~p8gcfql%SScQv>a~ zc=44|*bbjT>q9loK_=@__HhNvC>S0@-sN93Tkjt|wF5(9MBV)g-D&V0P{ZN!!?FO% zM<)^zPh2A$_9PJ3l1%n_5^H8ls&G_a1fCQp^r}Ja$(N>L5#Uj8+{iHFAMlN9#d0@aRvYn9(QVN0vR2 z&&slB7!}I?mLui>3N>WEN8!Yp?en$Yu&IRUvKS#7T#$?wAT(l^pc6-EDo$w1^nM1Y zudAe(Mi@`vn$yH+fqEo-;>%I-imW&N<@bL6lbjSU>N0ZxEHe8wqOMpQk?ZagQE^s= z!BFNXW^y5RIOq@=qcNdB-;RL^^L;g4nS#R=n>~v9!||{zco&pbo{?mnTN_v@1h>`& z#^jX52YTTx&ZZUjwr#EL-l2Q;rZ!$6oc%fsTs9sKn1vJp4ASG%CobigM!iJ#S(ie_ zC!+qJ)sFO8Aa0=I6hvUB+WHi>>Il-yv4_8TA9j_H6aHYO*82t>mVKl1sx1Bta}vL~&!$zU=@tn0-H687ULpkZ-?#nQ%z^U{7CmMvaMGkbMzm1c=(f4vD@`<(d$ zO+K5~)QOIoEc<$)$Yck-m#I#Juq9nX!lGfq%)Ypn{h2RzUYAd&g z60+y;2#3{Pp@Ks#)QD(h(6wc~9=xEYFzz#S#R7RX3_zj&)9eX0k%>_V z6$Pj;d>!qrdo-B7lv{2O)c5-EVNNru9c?w5W>mA?i`q(7F?Jae1Z|;pQ`F$p@*vj1Z!>{BSB@M z_jI%kE9S&2G)AMSwuE^e3)xp$v(SrgtHw=b$(q?g>2PMRv9i#6evFZxK2@qe)>7CI z=6M!OYN;viP`zdADtG*NVOi+y?04Zl7NV|CN;K?|mEBZQcKfUd^_stl6ui!R084VM zTnhIBtFkXK8YFwNRdU=Ey(h2i_C0lhTKif!<`Yng+;ygGu?Z}-ZDs&U+srKSkaSRl zK$T|O%sk1qnOXnI!+1P~UzzQxvgC(aZJQZlk!@!DmfOs@J(LV>McfmSj5Y~HVY$Wf zySB{?R0S%+6FzXv z^Kq1$$xPv~9M)w@(SxM?MI?R4KwDdqYdxM8Cv7*sj0fvOlPkdEdvI0$wcp)$zoIw z8jzCjMSa-m#|6V@aN(g_bXAewDj;DwO@dc=n#wNkMW|}~jsCq3PlVsuZsgHDHMx#K zPff=dK$>uDiFZLm%9sN|z6>>vNt?yV`DoX}#vQcQ%F` zh;1Gk#W8RX@kzB;y7b4jOG1?#SU9A-8)R-250dk0lS8!+h?x2au~+&6Uo`aEq)ycx zK%9MDa3CI9W079UeBn_*lMJ2PM*W=AszDzQ`pi@y1yrMl-haNGuQ`3E-a|}V(B(Y! zGVXpYltMy^deC%q$^C*!&3zO!7F6VuHcmnf-UvQ2y!9R-fOADEj>QeMZQfVD%hgzU zwJfy2vP_j(*acj^UsHR(&fbFprYIj~#|z)E&D&S5EgIQxF$wA%8g3Z5)Y7@5e{tj! znzoH8l-ZbOmf#U~JrS-N4asXX2o4L!YZ7s5i~18bk2=4dRZ(385Nw*pChY~cbfm?LUhDS6bBPJO{<9kVVJPkk=RJ_ni! zxN~u-T=F4PDUnf4x!hg8lj*j4r()OF)Xp1S?L>7o`^SGt(iZBDnGX)8#=7%FK%JK< zpa#o1o>fM_9MgVGJ%X*xn^(=5wO)gG1LfqxI4x!L{ZhQm79cBfhL3YDxhQ%r(aAYF z_V)m6Mj#U%Tir4;SrCMLJAiL=_AkmDwF3m}4vW9fQ&-tPXzRDlf{D%Hnb>!Pb=_n} z$^lYL1BSENNjap2FY=5sHR^fFffC=G$Ctx`Lr5>Cus-@W4L@TaA|7qaQ=%!D1RKaf z)s}TG(f0tb8r)o(F=PR1!hJllK%1i79EahD3dCvZr(vMhKl?TKrDwz4E@K?*p#T|W zoPl448UWPq=-y3SH|ygKarPA2Znl!iiDcW?6oWj-hw_f&@-5y&+qR^&>up}WEo~J{ zP2HB3r?2;Om{SHBW&bzZ)qb~br9pQVC^&W5RE%FV`!772Y3Bn1Q|J(8MFTW79g`%4 zPvSyR-84lwp*KLpo_QHeZ(vULS0IPf5z|Xi#Qz_YR@#|tSOs&CV4;835jR{~0|>Jz zVV2C5Q^2ruSWnLCZ z&Ejq??y^s&XB!4VDZWJcpUvsXL|^RDJ)1$3LZ3aUS~w;~;VNb+4p9Q4fY`7&lZOX^NhJ{H_2B7qx-CT_ zim8a@dcR!d7xc=&cKEP5_s3c8NEFhQ>-}s9bdn9pjnJg|r$~QGEqz-peM>EUtEDSY zK(w-uNCOTqlM<|cNbl6x^c9t-bPpVnaJ)mjEgbl+{Xy*NfDIJS<{d+DB5z!YjRZ}` zddlxp#zT+8G9lc}>Nq~fW!2)0`2APvcE|r)-uXb; zRn_Ep~8pJAD_lVmb8$@A%)6a|I0KvDBrZMd1aNhX=h3^R8U z(xqikRBX{=)u*U^1+Z>Fk8;>g%LiPvDmUb!gP=wmME_g8}$wj@@ zQ{noAeD&|_Q@QehWw?BR!c8~WSleg?ty;-YV+5M>d@iTaA$A^o>lKpae#8?gs zxgZr}c;;Kd;n9n?-;7qnf?9FSz2@^l{LFUMoV!>xmK3N&)S@R257v#9MK2_cT^GP` z(FIb87wf{x#oCteV`jK6(A2e$st06fKv=G-_(PDZ(43`Cf#hv&iZPPzReqM#nkezJ zZ(R_08=LYL(xeKue&GBEDkjw^J{?=ieLh^2JS+cvP4Tdghk~HLc=U8%j+N+^RcZuU}cxBR65n9E-2F$$4wClhI}0cS7J-j$WC!Sqdb@`S;$Ci-UfX?kCC zt?6OYJFcn}+xJV?%YFSip~z?+A*w7nZ=9J!M87*sACcsvRBMC~E%ke_ANM6Lhbt-^ zRZ({R6N$>C-*G7(Uh z!i#mnE|6$^OPHW^n0q16-MWA^RC-44m>E$kPLQ zmJ?z(l_Aja6a?^WXqI$5g}_plTCOL2x+B{z?*T$Lp>!?U^m<7Vd(m2TX&2g7iT4rw zI5cP6VclGh7pwhtdDnL^j@fp3Xh`-E`qo0b<>5{9)nzJ`(0YAGX|p^Cpx7+$XZ#R? zvd!|a37CUGsViC=X`)byG*D@G z%o3+vQ?itz;CA10FEu2Jb5+^{*akdERX%oR+VQszSAiPC^Z^Tvz4Qdzg28RWfD7+R zoZb)v$|W6tAv8Vyy|8bislyiIm7Di{HwE!+AqoqFr^)saaccMd4Z1br zv5mcGmO%VR^ymQw&Pay8A3+8AA`e9|NLlyb_OO@vXOgm2rSTG)g*Oku_v~bs-p*;c zOFEy03Q^ICBmweXT0|fX5Pc)G-z?ctYj%Gk_m@|+!L&hR<(8+vrIkT+T8p8mP+8Jk zmXj9rBGxBalP*SFaf&CQ;m_RSP@?@>=parRH?s0u2`Iz3_wYxsEB8*&^mVVz(v~ziVcZt-xvTc#>17~2>;jo67oMDt|-)xV)+vN zdZ2?ufcXWLV6GiD9Ip5apRi$M%;G!3_)Eq3Y2@xSdz-<#&kG^zug^3G$}1z^VT!y- zNw{hek>#-iRS!gXN_?MrLv~N8)2?(AE>Cdskh(Km@|DLWcOI8kvBRa>^O#gaTCnQ| zJ-$mZ7v37-1@Z0n5%U@2t@k7mlcAZxfuz`z33s`vp@H0p;y1+r8^Jli$fPQtiy6r1 zps*iQ*6rk98XqZ?0OQ{!A)&sfSG$0Sg%(Yegz4>>O28tsidJi67*21^v;zAS)j>_! zr&NTAl{<$VS|5zcGHEO3gXm%og8}XPYa07w0QEvbYC=XsPMu{N=6lUmnh0 z>Z_aJ@1TO7u^<97d$v$b$YO!}Y{d_G+$mk)d_u>cE=|eW3e0-i0jPiJ_gxadVHE2fb3o4pOUtan}2J# zhq1ARjxK05PNy(03Bb-dU#qkOdm(huM`9ev+X!}9{c-LYk!hT3Ux$?Wb4Y4eKjg8) zq1pO@chgBM?Z3wJj`RyRWrdMayi3_ z9o_njcoPLMN}_G7LUo z`bM##j4KHPv#x9&XD!_C%Toy`@ikZ9Sm8KiRgj5xu>`iQfeQ2tUls?HW?HUE{oGmQ z_uMU|ikX(MMl;p{%hqU6tkW3QikJ;X5pADlu6ikX$g9ZzRQGw(@02Mk;6KVx0VXNH z-wa^ez1Wtc=1;&YW%!#vF(ogL0vpSs1Srhgg{Sd^zrFGXX%tc4z%5FN$E~uq(4d^* zYDl!+*#Lpua`!-RZ-?w(-$T95x)`|*$-!@*BEV^wzviQJzA{{8Z;Sk%uP!q;8kRxU zZ4$A?*|?2ahT zvgSSrERp##qfNaE;;%}iRnI||@JvQsuSp@_EkuwTI477R z;>lvoj=6A$w?{duFb=yIwyQhf>RDwjvARcEwP|3mT$?P6x^XzE8kbAgh+mF=m}p%y zO#p_JX&Ml2=L1pQ%@X(?FJmWa_T^DNZJ~49#+~6!YQyQ!@tro4r z!~KOj080=SCDL5b9dLiy9RMSyFkjKXgASmAC z5WvP}4&mL_RSp5_D0u|#gMeuPXII%HaGzQ=2N+|M5G^xGp>ued7xf6-a<_J0ci#f0 z)dT-Lx=TRJ!K5Vh|C=V2vBZ^`n300W`;uFLNHa=<0-YLqm|;Q8%_^0V1zV`ErIchd zIUAa*rp=l{fo&V6t20ifkBKw~bk-*KVH+eg-#k$(!K z%8Khu6FC+dAo%2UBMT=g@1!wn?>jKrr{vWjt9miya&f1UB_9={U0GiAG-sf#RuSCgPK{w+)Ii;jW53G_vJ0Waf-)BO675|HkE&<L0zZ1Fx7yvXh0f(r%;rx8YeIgKc482>Z0K*NME z;%|iXn`wjVOBc+3R3&CHq#S?KNG3t7I~_@(zkJnD2SzmIdm?5}MKh=}Oijp4)HBlA z@T1=Sg>pxuwSkg91%YOaddb#I!9_?BBL2M{jifTobnIZUzar&GJu((>KnO+VQ4>zc zNB~-ESbxwP_okh*y%j}vHRmgP~#{rD0n6ZLC`! zktlW~tPZ)+<5(S%4c)0Z+D|}DP#4KJ>}{jj&TktnT8n6*yZVDLbbHNU!#)~Wy zi{=}wrQfEh%{Tl9nsn9`h0XZ2dQbdq8$11q@6#=OUaoXH*gS{(ixhZjHv2(yTQ!s3 zhrc{HGkzZUY)9%a1pIx&$4lz}ZC(W^Z1iyFn8MC?7 zM&LUgWd21i^Y4boMy!d(#I|+4%&sQj#Lv~!ivd%4i?+DDK!8}fdz{N7mEN5{CY9FH zsVjD*k9^d8ib4yG-=}HM6C#?hENeQ}mx z>$t6MtBNV%?d=P7!}6LB{Th;RYu{0X+R&`YI1_LBvnHO_@)e{rVmN*fNh%9~I6h_w zXAi{x32JtaWR6v|Y(-|eS#}Fkx!=OU3nW;$oXQFai(e9G{!s{@m8B#5$?k$)E5p5$ zFPN#J?Q`HnjRIMgHJzC0C5i$t$ujIHRm3){Nr;JrmDS-LZuVSafL7%6#5o=My&sV^ z)=eD*KmiUqIcEhR$#mA{Z?JV4U+TKfU#MsUFZTK(O?R2!ijDosIS1pBM=G3s<~8Ugz)Uxlf%TL zbL0pcr+`FtUl-GNS2{HkK8k$hkGB~8uhaHqw$*8-t67oqLkupQ!{`A)3^6e(5$2TH zRB0H$Hw1O<%qpSj*S&keZ+7?ok7vn83ZwLRX>*&S%?once4(Swvmi@tV`PD3Z(JeW z#K&NQW((1D#Ef>4Z~vw#wx892G5fH$uPBW%R<>#`FkM7yMFI?HCQJ@9f7sxPJMAhY zYj_Cv$ooXvK6EQy3~%tBI{XkrS>$^SR=P3gc9Y*cAMFJgi90(M#Oik} zLeFDqW+gmU?5MXxTC^ClLxestbqM33Go8v#4D3u##2kdjpkFtX0}?UC$(XlgxI8?0 z8itE2_NfZ&OQ1*uEMq@v2?_Hl##J!OP7e52lmg;cJ&0@e!dFeD$A`izFhZgUx<2{o zDEnZ<@%~T%!t*QH$;C zX2Fmad>pNoe=nGgV*CklZZwUG#G6@PDcm?=Vi9uTARY{=zsZ{X2Q=Xp)^UFLxN%E5 z&G)f!OMZGG0Sfy=g5LVLFkf0dei$t9O>t3a8ICDw5}ST|^FqVudUS0RX_sJBo{~v+ zymDm-Fql`^UtZ^Vx|151EzX&oyOxu(P|+G8Csa`7TOz)W1NqPKsGCede>VrT$q60~ zDXwj5k#eT8B|sT}WY!lFBU}7~a-S?LF&vJ*{dgF42^)l@!{4K|T|c}yG5i{m_dhJ& zqyIQV_o>A@`j*f+K?}o03e0guEHnwTaD}Bix)1|?hk0goeCtBcnc?wJl%*bBO=|NJ zRwCVFCSWQppuo~R>?=6HLa9gh$N;qZlzKz}fsUd* z-J>t~jEv_%TjCX|N7wU1{xJB9=~fp?ks63z$OXGmHDE~_3@k<3mIz!$z)LHU?$Vb$ zGjD?2urk2i7qqy@N~BwGN+^v--vXo6b88Tp#(bAxO$FrTk8-2fJ{aI}wR*tJYLS!cqOAwvli|$tH+-RS z4ku*E!GUEsuLyMl3cp1dMPcsFNZ9HfM~k7E7T4&^j$z!*zI}y6!R;ArgrIXa+~?io zV=wZCv zCJuCkhe(_Em1)4<54LmO(uaJ`8Ywq zFY^B_wqEUHA# zFu#$D`iVZJ$7$$tI-@N?LN#ncz(o6D&f?~l#^MeYEbef|;@D7g0Ty?t%;L!IX2kgl z~M65IvoXSR6H!SR6}Ws2(_VKj7L(6Ag%mTYiZex7``ES8^mvL zp=6`72P&B$om*NC?;&|{Bj!EylI8FoS}=$AP&tPe-Pe7!7?p{OT79B2ec`CAoWna; zrZPhGO;mQ6@**>ltI-*Cl;{j<5a=Ii&f(SKQ9V0mS$bWMQJOv#si_*m{BuWJ)GIEa zk|5%Lh~L02B#msB7IT3tvQl?mlko7SbkKP|_$kfjz*BYZK=TxhwK@#3rQxFCL`lm_ukSlOjvQ<@h85VKu@#=$b0 zlvd`Gn%H(jjdn-F61{*R`B%Z|Ue1TmLmfaL35~E~sTj0w0*FauAMwh?Jp=$^W8XXo zu8Mk)}UKe~~UAwr!^W7j_nQh1;=}0$Iu{OFYZ`qvufqiT)%7uxo zpC(VT9do1*@{nzKRFHii7>GAVw(K#o)Kcn3S=c%^_wEn9|DJ0;aECJFmU)NWn;>Y0 zv7&{K72C%e2#ELaN6L+c2UgfyQYWI}^x<2aLczzWoRqjVZ6=^Nks`-8_9o18Olg`H~?#>LlETf*GY( z3n%K=OnTb>>b;&w&1<0`aw0mtH?)}`Zh9p;5ElIed%4U-ed*>bT+OQ3n=aqi zD-B!Nd0q4c90!Ic_b^HUOrBp&Kcg=BP~0F!IDJt zHl6*N#zo*T=bD&VC4L(N62D!{JAx?7q0n||t(>@3PHTNfHn^1{t+nFQT6e?JP_L!> zfsEx`6p3U|BO7dsHxykKNf6K=0&F-E`DnTrooG_)r;&|SWrHt2drX?qU2?7&>Go)p zH3=C?BS(w3R8r`sQi1fiG3}l~ia;7M7aJxp;MZ}%QS}^d>t49`a6OB6SQ}P{<}x@u zle{Tcd(xS&*0L_3kQn`|pyLYkfZ1jF;S8U3N#g~edcIr_?D=vonub(R?E!_nR|WCk z`Mw3E;B`>_!+e2RH@WJ0mbCR$jerlx0<*|M6=ZGY(@M3{JTe;#D5L!9Ya^E`ycoe( zX3GR$VPwGBQOO+2@no*`*=E4!WDbpUR+}=|9E~e6Ks276)#hpZoh>xpuIUnGUnh)d zQuI=nh{90=_=S{EG5qmu?2B$Fau#?I#>viWTM`}TeF{mAYeUCO3OOkq^|MX!KF(krB_I7dO4e)KQ1vU*J6D>|(3 zmA3T^2DZ%~j@3W_>qn@)$v-M>RM>^(Wseb?oFu|~t?l?&RK3JOnx){N8~m7=37ql& zRw>dFn@*!yM0Bt!mcFg+7-jsu4?qdDyT-;Nlv`q z1Z;?R$QGhnx_^S#4IMkn_U~FO+dp3>@V1*Se?s$j4+Z0kEECuVa&j+*Im;sT_F7`^ z>JX5fvL{_@BR zXJ9cyGcAVwca*r~sh)-9T3;*rr=)grtX8XU4hyR6f-1Yj_aU_|M{517NZH{YLaI7& zGwHNv(QrhVT_o2_VbN)?j-pH8wmO(o1Zkx^;@`0+DuKwbFEnLC+iydZ?pyMe$4KL@ z)KT!TBLe~KJI0!wg;J81x@PCND+m^Xpp@#2bJWCN^};4w3~Fb5^e5PxcFj6@E>RGF z-29e$(MA>hM^i|pinOI5e=fqUE{5CO5MVSF(z)u6p$Xk` z?{faR<*^m`5BDaOKWI$}L!*;eF@|iMg?shBq%hYO#==9vzIM>HL&HB98XjtkhFx~7 zG%AA-EOo#zNw_2(-jjCiPlv`$N_Wk^2O^|Hf%v%f628;9^^xHDN+##bbJ3{jGu9~< z7|1D?M9&6?bgtSkqtdtIFc=BV{JH4V_ech*?ufd1BCk3Js#7;8QQb&rSGRN|=8u4N zm5`sZA7z3{oW5czp!l<#-Y<6t32Ce0Qty!CATMvTgEp1rVr{8S^fT9{)6Y!%QJZwe zFDMIRJfocW0m$hICJcban3Rr{3bmJU&L{w5+(HW0LtZX-E32J1$6*^U53SSZyn8B4zGCjb(=AsMh8gH( zjcG&?Jv4g7kuraxYE6QGkOnJ(MbaM~bwms5q^5uY!I#^57t9ldhjY=lZA(BVA!V0I z^!0EjZ~leKYtmB$$R{M<#PvViEZ=C-gsO7~QBbFH2^pB(CLA%l?V9X~Kdm}Ucve5^ znRSK|>iDGI;*{|sQe#=6n#4@zy4h8T{b>Ca!;5dH2D`q!(c3YXS%YHuAEK(QG3Fq zPdpoyGL&bQcud6Wt#iMqyliX0_HRi?PnfWZ+2Td1Y~`-!o+?g`kY^OAi$g1{@cA5& zP7GVTpI{TqyTs-BT4&kY_;jXSwz#>HKIu6MQAX9LFeDWA+*!8_$t@7CZeh$H#!1~# zZJArl<4@$GfGBaZdtbKqyoqZHl*C1wwECO}{B4KVJka$NzRKfuKSP+MKSSLv0(Np& zx9o41{>^tyNwbpJ{Pq=VLeqKin<+ZyW5VD%qJUCEw8n$tgkQeEd`8*3%@kJKD*!dp z$NTL(V5S6yFyGqF0p*t%M5ULQedU*=OcyUX%Py~y;A#2As{F-j|J*hawx=TRmF{U4 zTIvLl*64T}WUxd?4t4Bq*F}HW;p4NL;6LjovIBBZyyF#>oI{8<#NAuNTZ}CGgTwBj zWXGC_9@uX*M@v;+C8N+uhERWvk2A{?@*H(I6rgETsPVw1pqy=qBcYnvkt40eseD;ZRZ&`QnBehdC# z9mSPxYviV(Yw0lC799;xTLWR+1TlN{Szw7M=I-f@oYVOzeU%3Vpd8z9RahdIMwu#H zKFQ+fDmI17p!rU{~~g zyRzNH!(o+%L$8Bz6_8)@!i(}ZA-pQ>w~Cl2pd<}}rJaXmZ;lgVI+}|lb+q9-@Z^M7$8@nem&(Lx(z?rTxi7dw&{Q}9h+KrKf!){Nf0 zVfEuhS`Owy*(IkPo>;o({bie?mFAg~r)^ouN2g<6t(07KSt0HzV`Fe!KD$frbROr0 zeRh9TPAbaX+`_Ej(h7PIjJI3B+kF=|VVYHJk8Hr4C)+~#!;Dol?Cg431KKs>m@K!? zc63n#+NS>5QHob;R}MwK-T$VAk{*yBeu-Af>d@?oG|fnShzEy%bgeu0*&U{Mxy4Rk zM!RuNM8$ht!doR>BFzQPv%wjo!@tjR_3)Q1CHMq3EpwS?+HxiHK@2CwD-gdw6O%|!f@J2qT`A=#_&V}qsv|b4OGrWagMbA z(}{dp+3JohltYW3Y|g>qgB5|uZWZK8QW7qsN}28~4tQ}=yAz

tu)b`u>4GoDM$+^-3iKvX9%N7&?wHhCHK2Z+h<4r`> zOhN_I1!|_gWRHmv$P>zg)a-@D+MARwE&0_aeB{ccVVfM(*=9*r5@CrGXx;8YJv3)P z7?nk7UnbL(wzLG9_Ae!w=B2n9{AOr6d0;+2Ow1pLnQPz(^eYi=S0u)_B#|k>=2-36 zcOX^;8ISlKlB-AXp%mw;PRtAwf;LWMLTAGQA2KiV@-w}l`MQ} z{R{H}SKuU&HsOXi(y#bf_8AbD#RvOcIkphzSYy9BYWmj_H9Zl?(Fxxq+GwVc7d3U( z1dex3;IbmzBdJPEs>wN_hdMh}vM+N^K!rTzr}G)AuW3|$a_E*CFujyE?$Yi#=2&Mc zhAF6xS5!@(SFoi?%zh+lGOupT2i@*b6_1He}j~59d#=N0^SfZgHTPsM2G)aO%n@J5}Il0 zr5x_H(okGKEHRb>Yxl(eSqlXPlU_2xpn-CrW1XU=f$}! zTF2J1`N5dF)~$bQv&Okf4jTlIL2H0)M;f>k@}~v#P2OEbN_PMdKcN^kTMcX{S7JWx za1ur5V?jDhR&#u@ScEwUOi*=2^La_F`WPjT4 zr~BtWj|d)66*r{EV}6JvF>7K|Mnsp~8h4B3d^MKGvL9tlNJx8PF^2w~%~+Zg;Cc_kUChlS2j zO$>!cbwI&s%~OVE+1685LS3(iN~nm=vCVX86`b}e_U$}NczL!lW$c$h58A6QYv=Rf zQ)uPeemea$T3~)N@RTGJ`>>>mscKxAXvQH*6U=^VVwvwo*-$@=dP*E&7|t0Di@Tzq zz+%n(T}Z!c=cM`5oPKS#s*lS7&hkM@JI_9XFxJSno2zpbu~~rZg#hJ=l)dM}lS{1r z=ZjST3nN?PaGUBU&>IAp@4GHQXvq&v+18h4`^+|FTa))i5`(h$MAPQS#^SZSC{~V& zXZr3iSm-p>>I<4QC7#AFt=I??mpyrV>PJv(Pf10g1h~19|5W6!F zm1vfMIDEWp?hQ*w;O;6CxUowPGfI;9hBYxal9CFn*`dD0G+?S0S=>p}EMTPB^tvgD z`!ojbFdnRBr72J;tyA!=I|?bVfP8mJ%D9*>uSCO5N!)?hm=pjgb8q_0J?Zd8XaYw| z;@$;bp({nl9OE}BkI;*Tso$l3YhP{aQkZtN%i)Fxm2!jxZ&u(TZRuGwEr z4)-3%#;?SOo-5I0u*!5zD*fzbvLpTBr@*5r%{)U9Ak|t*Ge{!;wZl?aDM|jTrK-A; zmVa%D)}~)uqBVPBiM4}FOrd~4v_F34C1ht6Hj;8-qx9>6rIEdzxGYsOQ6#vRdYvw$O-J(<9n z^Q9r;jgWH-G-%T8p;_sm;gUn64P!EBRyt^sGBmknye?2tj~n)uanP(h4ro?u;go`< z1vINm(5!aQ*f}v7Zq^jIQBe=g+6wFw|47psD_Cys;^15MyW@oSVhM-%~f zTc)R%AyDYzmjMK7VhM=9DuB2qa(cE_RbnVQJfzCA2h1T)Qd zhepeMl3%%6!lELR^RdVnR?>BQ;wu&4WMij!SbgS#Sf%;i81n@;l`%%gY^cy39JpFg zE(ROKf|}8%{F^?TCmO0D&1-kmj~k}f8itUrjMMR*EYNn8wWVJPNskq&A=yf!{MW@O zf4mK`ryb2eFteIRN@4F~p!d^*lLrH^m!b>waUr}>QMxh?!XR*OWU1>BKEdpfF=-xQ z00U*`zPoK3cAq5kFQMg<7ktYr;22nL+T=b(XOA+(_Xlj6&c2v;o7l?RfQEHW zQp`Fql)uq|ZLLyYF67Bp0!bPjrMCZNPVWVAu6EChspLI|C%lX~l{Jn<=|o-lBx z2a4aTZeQCXm6jM|H~chynzG@7YM1U$=gc}=kzSzR`U|HHdOf=i_nZ_(0Ss8UhU+~^ z!Bvg5CT1&hbnKcpR@4}z3d=C5?p;b#l_-3aZHW)&ZQVJatnxlN78r@u8AmXtV?V=e zu)Rc%lr?4&S*L@76DN@)>^G;a5E;)TGLlddnNQftTgVDrPLWAdI8%$Z0*10Wpi5+q z@~LcO5vG>O<;0!k$G*kpiy`sNg?tSaCNd*08rF>;F_WyCtTsX>B@1~fy-NbsMo7%I zB4+D*Cn}}&)1%dc>_+Yo1Sv*FifzCZwvFK`6uVwyBZlTt4zZYEKE&*A z*Fw4_B%oy=!@&!QgdkS5n-7PAvtnJ5O+>-YZ}?XB8@?qa!~I?wvHNeGn==0vTsPP4 z0>eeyficv)N3Rb_?ru7KqG@1DeKH2A9>D@4H}{PpU=WJ0Xc)Vic-~@DHb+h1_@%2b zhicJfqGq{3YqDKX&`F^IdkZWRb!+#Eg?4WBXzMc3MGYlem1=8J8Bo&^7BZA9AYm&~ zUCDNa(rXF>JttV3QMiVfPu;zpZl44=xu+pn=&)%w?_?{((M2I5Fd6DRiLZreLqJwi zBG2mLlrgQa6BlPE4sYC=lEwNi?Kq6RzKo*$bGx#i1}Zaqodt~g*qBKC$_mg$@!QOC zm4xxzD?P9+p`p7MhHU;=sjrfpsLV}vxxCBnNO!S*X0Lo*Nti9KuCaKZs=^EhL`FMp z10U@u(9yFDKA?23Rvh%&iE4Uog>l>;;x3P<&5 zKJ$DzrvUNTHYixm>l2U?fFpMGq zg;*NU9A7x4$Q*;JAbaljW#BnOC8YWCer>!7M+c+Hs|ADoANz*l9DxP5n1auP*?rCE(AlT0#ueq0_M(jsSgLz~8=R&|Jjd$NSBbhD^)D|4>WPp@eR$ zVy%r%3d%7}(>dYfs96Aaf^rurvn&7r5~`IU$Jeb{0i`SeWd$627Qpeh20tui0bJ*H zS~4bc1Jny|XD-r(>oq5@*J!!;e*OdR8&s^YRdZN?JNGk=+i z8U`s*Y<(o8mzXERZ>rSVU^>6Crt~2yvGgJ3A*nqx!%D+pHdtvmyqC;JTY<&hiSgfH zoyX1aREEl^U=ixI)6oRflqWY z=K6B$G=dX}y8^%B9Ltr+re(~lZ4 z?wT)tA}ICx-p{|~gLjX32K`+fK8?)YCfzT+mA zg|bEgIOx&)^!xl3T6s(upDx*pxw-dSMZVy5-+wUsZG$#9cgy>~_VpX@;lma7;SbaU z)$sl7W=>V<$#=3R|C-%Al->L!yLnkw_*>bNuVpts&2D~?-Fz>*dBAQ&DPZ)*yUt_@ zLKXc|YK?j_Rohjc z2}Z~EkB!!o9S4&?t4~h``)U_&ukELBZO3?h`_%O0=+jptH_$iOH`KRzec$@^>o=_5xW0e=ru75s2iFg+ z-@Ku3!}<*yHf-F`zhTpcfenKjhBj>8*tc>0#tj=cZtUNxVWBZ5-+!+B7sUG&nRgw0SciZl?Lo zRK1zcHuI<%4?Aj&k=@%Tr`q75x!~-?Xnp#?^jM=U&Um_SYy#Ya*x8Bt_R;!y?cnx( zZE7t_s{WD5iJAJytnj{l-`LDdz4&5mBKec`Ym-ec3YtHe8fo~V+c)&@&WFzz-@Lh8 z{=nG8=;VR6aO@hN+)*1(Mki@G(*&;6HiOl zC!@2CgUR${qt+Osm%()XBBMI`JU(7)*l)xB85#)8$#;5DGQQ)fYqQCWN zO6Ko&*JhGg#-q{DgmPfg^!S#fR`j?8&%WBhWO8C$m95L_HFktg9+M5 zMs}-|I+yjt25u%eFxJ>jD+j3PqS-nng%sC$-5s2qolaWI1hsu&r;*c8&H{3j=u?gm z|4dMQX?F`I^$Mb^C8KrwZe4wRY}XjPC7EH!>p^)m@-dvrhRc6c!g9uEt|E?clK*iX z_vdoonA}^Rpe*c6g9HW{^~s5`5h!?Oe0JCNiP}EeovM$6uwBK73dU??80oekWf0Xy zM(R_ItT(Vv*RABhyO^tuDJu?D|13k#_7 z*|T{0v5A@4ojb=y#_9}_rzTCoo#V4JyOTB3OrzPU1cKQo9(HIy7)(_`c8m$Ob{snSE)}2qqPPcSPei64FSgBA^cS@f?v#ecRi`Y^I&DO zQ&W@EDj;HRtV2K{Xv$bH{1gO|&*_|in-90@yyKA?NAu8J1 zWTL(w3L2r)P{e4mb9xe5FnmfxKsxTfbnEY(@vLW`dDe5Dd-gfM{|A3~?s@0`(FMS`;7yCUHFi^W z9H|LVYyKo%_s1_fJ)k?&wGlm`;*6?LPY(cZ@lN>DK<5?DxC;8#ZQf2LJJG1dOU0SM zdC6jocfyDyZ>MTdhrpd|1Pgb)pvDZUOtcr0D`$^Lb=X{x@m`bKHfQ05Mq0vL_I7&m zfG{^Ry8{(ugpM&R*;pInbUd>}&k!JUAb7!Z&VN>UUBQr7yo&Hy+BBAj?3<#N# zy%M - $ - $ - $ - $ - $ - $ - $ - $ - ) - - target_link_libraries(${target} - INTERFACE - magic_enum::magic_enum - ) - endforeach() -endif() diff --git a/contracts/sysio.reserv/include/sysio.reserv/sysio.reserv.hpp b/contracts/sysio.reserv/include/sysio.reserv/sysio.reserv.hpp deleted file mode 100644 index 5acf341aee..0000000000 --- a/contracts/sysio.reserv/include/sysio.reserv/sysio.reserv.hpp +++ /dev/null @@ -1,741 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace sysio { - - /** - * @brief sysio.reserv — reserve registry with create→match→ready handshake. - * - * Per the data-model refactor: - * - * - Reserve primary key is the triple `(chain_code, token_code, code)` - * (all codenames). Composite stored as `checksum256(chain || token || code)`. - * - * - `ReserveStatus` proto enum (`PENDING` / `ACTIVE` / `CANCELLED`) replaces - * the prior `active: bool`. - * - * - **Bootstrap path** (`current_epoch_index == 0`): `regreserve(...)` is - * priv-gated and inserts a row with `status=ACTIVE` inline. No `matchreserve` - * needed. - * - * - **Post-bootstrap path**: users call `create_reserve(...)` on outposts; the - * outpost queues a `RESERVE_CREATE` attestation; sysio.msgch dispatches - * `oncrtreserve(...)` which inserts a row with `status=PENDING`. Any WIRE - * account then calls `matchreserve(...)` putting up `requested_wire_amount` - * WIRE — `sysio.reserv` takes custody, status flips to `ACTIVE`, and a - * `RESERVE_READY` is queued back to the outpost. - * - * - **Cancel path**: creator calls `cancel_create_reserve(...)` on the outpost. - * `RESERVE_CREATE_CANCEL` flows; sysio.msgch dispatches `oncnclrsv(...)`. If - * `status == PENDING`, set `CANCELLED` + queue `RESERVE_CREATE_CANCELLED`. - * Else silent no-op (race lost — match landed first; - * feedback_opp_handlers_never_throw applies). - */ - class [[sysio::contract("sysio.reserv")]] reserve : public contract { - public: - using contract::contract; - - // Well-known accounts - static constexpr name MSGCH_ACCOUNT = "sysio.msgch"_n; - static constexpr name UWRIT_ACCOUNT = "sysio.uwrit"_n; - static constexpr name TOKEN_ACCOUNT = "sysio.token"_n; - static constexpr name EPOCH_ACCOUNT = "sysio.epoch"_n; - static constexpr name AUTHEX_ACCOUNT = "sysio.authex"_n; - static constexpr name CHAINS_ACCOUNT = "sysio.chains"_n; - /// The `sysio` account holds the WIRE emissions treasury; bootstrap - /// `regreserve` drains the reserve's WIRE backing from it. - static constexpr name TREASURY_ACCOUNT = "sysio"_n; - - // Bancor connector_weight is stored in basis points (10000 = 100%) and is - // the WIRE-side weight of the token/WIRE pool; the token side gets the - // remainder. weight = 5000 is the symmetric 50/50 case (pure constant - // product). The weighted swap curve lives in `sysio.opp.common/amm_math.hpp`. - // - // Max is 9999, not 10000: both pool-side weights must be positive. At cw = 10000 the - // token-side weight (10000 - cw) is 0, so amm_math::out_given_in returns 0 for every - // token<->WIRE swap — a permanently unswappable (dead) reserve. Capping below the total - // keeps the token side >= 1 bps. - static constexpr uint32_t MAX_CONNECTOR_WEIGHT_BPS = 9999; - static constexpr uint32_t DEFAULT_CONNECTOR_WEIGHT_BPS = 5000; - - // The WIRE token's decimal precision — the depot frame's reference side, - // and the cap for any source-token-side depot precision. WIRE custody is - // an `asset{"WIRE", 9}`. The WIRE/target side is always this value, so it - // is never carried per-reserve (only `source_token_precision` is stored); - // this constant is the write/validation precision for every reserve. - static constexpr uint32_t WIRE_PRECISION = 9; - - // Swap-fee split, stage 1. Every swap charges sysio.uwrit's `fee_bps` out - // of the WIRE leg; this contract gives half of the collected fee to the - // swap's WINNING UNDERWRITER (the party whose collateral lock let the swap - // settle) and puts the other half into a REWARDS POOL. The fee RATE - // (`fee_bps`) is owned by sysio.uwrit. - static constexpr uint32_t FEE_UNDERWRITER_SHARE_BPS = 5000; // 50% underwriter / 50% rewards pool - static constexpr uint32_t FEE_SPLIT_TOTAL_BPS = 10000; - - // Swap-fee split, stage 2 — the rewards pool's own division, GOVERNANCE - // CONFIGURABLE (`reserve_config.fee_emissions_share_bps`). This share of - // the pool is transferred to the `sysio` emissions treasury; the remainder - // accrues to `rewards_bucket` for batch operators via - // `sysio.system::payepoch`. - // - // Nothing seeds the `reservcfg` row: bootstrap does not write it, and every - // read goes through `get_or_default(reserve_config{})`, so the dial reads - // as zero until the self-authorized `setconfig` first persists a row. - // - // The DEFAULT IS ZERO: the whole pool is allocated to the batch-operator - // distribution and no part of a swap fee leaves this contract's custody at - // settlement. (`payepoch` then pays only eligible shares — see - // `drainrewards`.) A non-zero share re-opens a treasury inflow without - // touching the underwriter half. - static constexpr uint32_t DEFAULT_FEE_EMISSIONS_SHARE_BPS = 0; - - // Reserve OWNER fee bounds (WIRE-281). A reserve's owner fee is a second, - // independent fee on the WIRE leg — it is NOT a share of the network fee. - // - // 0 means "this reserve charges nothing" and is always legal (every - // reserve starts there, and an owner-less bootstrap reserve can never be - // anything else — `setrsvfee` requires the owner's authority). A reserve - // that DOES charge must land in [MIN_OWNER_FEE_BPS, MAX_OWNER_FEE_BPS]: - // below the floor the fee floors to zero on ordinary amounts and is just a - // misconfiguration, and the 99% ceiling keeps a positive remainder on the - // leg (Jonathan, 2026-08-04). - static constexpr uint32_t MIN_OWNER_FEE_BPS = 1; // 0.01% - static constexpr uint32_t MAX_OWNER_FEE_BPS = 9900; // 99% - - // Claimable-WIRE retention. `refundwire` / `paywire` credit an unbounded, caller-influenced - // set of accounts, and the rows bill to the sysio RAM pool, so an abandoned dust row would - // otherwise occupy system-paid RAM forever. A row not pulled within this window is swept and - // its balance returns to the emissions treasury. - // - // One year is deliberately far longer than any plausible claim latency: forfeiture is a RAM - // backstop, not an economic lever. (Contrast `sysio.dclaim::claim_window_sec`, which is - // governance-tunable because staker windows ARE policy.) Promote this to config if that ever - // changes; a constant keeps the swap-settlement surface unchanged in the meantime. - static constexpr uint32_t WIRE_CLAIM_WINDOW_SEC = 365 * 24 * 60 * 60; - - // Rows swept per credit. Bounds the on-write retention sweep so a settlement action reached - // from `sysio.epoch::advance` (via `drainfwq`) stays inside its CPU deadline, the same shape - // `sysio.opreg::prune_dellog` uses. - static constexpr uint32_t MAX_CLAIM_SWEEP_PER_CREDIT = 4; - - // Rows swept per epoch by `sweepclaims`, which `sysio.epoch::advance` inlines. Larger than - // the per-credit budget because this is the trigger that has to actually drain a backlog: - // the on-write sweep only fires while settlement traffic arrives, so if swaps stop, this is - // the only thing that revisits an aged-out row. Sized like the other advance-inlined bounds - // (`MAX_LOCK_RELEASE_PER_EPOCH`, `MAX_WTDW_FLUSH_PER_EPOCH`) to stay well inside advance's - // hard, uncatchable CPU deadline; an oversized backlog simply drains across later epochs. - static constexpr uint32_t MAX_CLAIM_SWEEP_PER_EPOCH = 32; - - // ----------------------------------------------------------------------- - // Actions - // ----------------------------------------------------------------------- - - /// Bootstrap-window only. Insert a reserve row directly with - /// `status=ACTIVE`. Priv-gated; rejects when `current_epoch_index > 0`. - /// - /// Custody: drains `initial_wire_amount` real WIRE from the `sysio` - /// emissions treasury into this contract's balance so the row's - /// `reserve_wire_amount` is backed from the moment it is usable. - /// `is_private` / `owner` let the bootstrap seed privately-owned - /// reserves directly (post-bootstrap creation derives ownership from - /// the authex-linked matcher instead). - [[sysio::action]] - void regreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - std::string name, - std::string description, - uint64_t initial_chain_amount, - uint64_t initial_wire_amount, - uint32_t source_token_precision, - uint32_t connector_weight_bps, - bool is_private, - sysio::name owner); - - /// Dispatched by sysio.msgch when a RESERVE_CREATE attestation arrives. - /// Inserts a row with `status=PENDING`. **NEVER throws** — - /// per feedback_opp_handlers_never_throw: duplicate / malformed records - /// are silently logged + skipped. - /// - /// `external_token_amount` arrives in the depot's canonical 9-decimal - /// frame — msgch unpacks it from the attestation's `ReserveAmount` - /// carrier; the outpost converted its chain-native escrow units at the - /// boundary (EVM `PrecisionLib.toDepot`, SVM `precision::to_depot`) - /// exactly like the swap paths. - /// - /// Create gating: the creator must be `authex`-linked to a WIRE - /// account. The creator's pubkey (EVM: `creator_pub_key`, 33-byte - /// compressed, outpost-verified against msg.sender; SVM: - /// `creator_chain_addr` already IS the 32-byte ed25519 key) is - /// resolved against `sysio.authex::links.bypubkey`. No link → the - /// request is rejected by queueing RESERVE_CREATE_CANCELLED back to - /// the outpost (which refunds the creator) and NO row is inserted. - [[sysio::action]] - void oncrtreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - std::string name, - std::string description, - uint64_t external_token_amount, - uint64_t requested_wire_amount, - uint32_t source_token_precision, - uint32_t connector_weight_bps, - opp::types::ChainKind creator_chain_kind, - std::vector creator_chain_addr, - bool is_private, - std::vector creator_pub_key); - - /// Auth = matcher. Takes REAL WIRE custody of `wire_amount` (must - /// exactly equal `reserve.requested_wire_amount`) via an inline - /// `sysio.token::transfer(matcher → sysio.reserv)`, flips status to - /// ACTIVE, records `owner = matcher`, queues RESERVE_READY outbound. - /// - /// Match gating: `matcher` MUST be the WIRE account `authex`-linked - /// to the reserve's creator — `links.bynamechain[(matcher, chain - /// kind)]`'s pubkey bytes must equal the creator pubkey recorded at - /// create time. The match IS the WIRE deposit; the matcher becomes - /// the reserve's owner (the ownership a private reserve's swap - /// gating compares against). - [[sysio::action]] - void matchreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - name matcher, - uint64_t wire_amount); - - /// Dispatched by sysio.msgch when a RESERVE_CREATE_CANCEL attestation - /// arrives. If `status==PENDING`, flip to CANCELLED + queue - /// RESERVE_CREATE_CANCELLED. Else: silent no-op (match won the race). - /// **NEVER throws.** - [[sysio::action]] - void oncnclrsv(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - opp::types::ChainKind creator_chain_kind, - std::vector creator_chain_addr); - - /// Read-only swap quote. Prices `from_amount` of the source reserve into - /// the destination reserve along the depot's live curve — the SAME - /// weighted-Bancor math (each reserve's `connector_weight_bps`) and the - /// SAME post-fee reduction that settlement uses, so the quote equals what a - /// swap would deliver. - /// - /// The fee priced here is the network fee (`sysio.uwrit::fee_bps`) PLUS - /// every participating non-WIRE leg's reserve `owner_fee_bps` — both sides - /// on a chain-to-chain swap, one side against a WIRE endpoint (a WIRE - /// endpoint has no reserve and charges no owner fee). Callers drive - /// ingestion and race-time variance checks off this value, so it must - /// account for every rate settlement will charge; a quote that priced only - /// `fee_bps` would drift from the books by each owner fee. - /// Handles WIRE endpoints: a WIRE source/destination skips that leg's - /// reserve (the depot IS the WIRE side). Returns 0 when a required reserve - /// is missing or not ACTIVE (callers treat 0 as "no quote"). - [[sysio::action, sysio::read_only]] - uint64_t swapquote(sysio::slug_name from_chain_code, - sysio::slug_name from_token_code, - sysio::slug_name from_reserve_code, - uint64_t from_amount, - sysio::slug_name to_chain_code, - sysio::slug_name to_token_code, - sysio::slug_name to_reserve_code); - - /// Read-only: current rewards-bucket WIRE balance (the batch-operator share - /// of collected swap fees, held in this contract's custody until - /// `drainrewards` sweeps it to the emissions treasury for distribution to - /// batch operators). - [[sysio::action, sysio::read_only]] - uint64_t rewardbal(); - - /// Auth = `sysio` (the emissions treasury / system account). Sweep `amount` - /// WIRE of accrued swap-fee rewards out of this contract's custody to the - /// `sysio` treasury, where `sysio.system::payepoch` allocates it EXCLUSIVELY - /// to the batch-operator distribution, on top of their emission share. - /// Producers are not paid out of swap fees — the fee compensates the parties - /// that carry an individual swap (the winning underwriter, whose share never - /// enters this bucket, and the batch operators that relay it). Allocated is - /// not the same as paid: payepoch pays only ELIGIBLE shares, and whatever it - /// skips (no groups at all, an EMPTY group holding positive epochs, - /// non-ACTIVE members, or the remainders of its two integer divisions) stays - /// in the treasury. Called - /// inline by payepoch with the amount it read from `rewardbal()`, so the - /// swept WIRE lands in the treasury before payepoch's payout transfers - /// execute (inline actions run depth-first, drain queued before payouts). - /// - /// Decrements `rewards_bucket.balance` by `amount`; `lifetime_accrued` - /// (an audit total) is left untouched. A non-positive `amount` is REJECTED - /// (throws — an internal sweep asking for <= 0 is a caller bug, not a - /// no-op), as is an `amount` exceeding the live balance. - [[sysio::action]] - void drainrewards(int64_t amount); - - /// Auth=sysio.uwrit. Inline-debit at SWAP_REMIT emit time. Asserts the - /// reserve is ACTIVE and balance is sufficient. - [[sysio::action]] - void debit(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - uint64_t amount); - - // onreject was removed: no SwapRejected attestation exists — every - // depot-initiated REMIT is paid by the destination outpost (no rejection, - // so no reserve-ledger reconciliation is needed). - - // onreward was removed: the STAKING_REWARD path credits the per-staker - // reward to sysio.dclaim directly (already WIRE-denominated), so there is - // no reserve leg and no reserve-side reward crediting. - - /// Auth=sysio.uwrit. Emit-time apply for a normal (outpost ↔ outpost) - /// swap, fired from `try_select_winner` BEFORE the SWAP_REMIT is queued so - /// every intervening quote prices post-swap. Computes the GROSS WIRE - /// intermediate internally from the pre-mutation source row on the weighted - /// curve (the source reserve's own `connector_weight_bps`) — - /// `w_gross = amm::token_to_wire(src.chain, src.wire, src.cw, src_amount)` - /// — takes the swap fee out of that WIRE leg, then: - /// src: chain += src_amount, wire -= w_gross - /// dst: wire += w_net, chain -= dst_amount (w_net = w_gross - fee) - /// `fee` is the TOTAL taken off that leg: BOTH reserves' `owner_fee_bps` - /// (each accrued to its own reserve row's `owner_fee_accrued`) PLUS the - /// network fee. Only the NETWORK component is split 50/50 to - /// `underwriter`'s claimable accrual / the rewards pool — and any - /// configured `fee_emissions_share_bps` of that pool is TRANSFERRED to the - /// `sysio` treasury rather than accruing to `rewards_bucket`. - /// Balances are checked BEFORE any mutation; a failed check - /// aborts the surrounding race-resolution transaction (no half-state). `Σ - /// reserve_wire_amount` drops by the whole fee; all of it stays in this - /// contract's custody as the three accruals EXCEPT that emissions share, - /// which is the only part that leaves. - /// - /// `underwriter` is the uwreq's winning underwriter, forwarded by - /// `sysio.uwrit::try_select_winner`. - [[sysio::action]] - void applyswap(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - uint64_t src_amount, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t dst_amount, - sysio::name underwriter); - - /// Auth=sysio.uwrit. Emit-time apply for a swap-FROM-WIRE (the depot - /// is the source; only the target outpost leg exists). The user's - /// escrowed WIRE (already held by this contract since `swapfromwire`) has - /// the swap fee taken out of it; only the post-fee remainder becomes the - /// target reserve's WIRE-side liquidity: - /// dst: wire += w_net, chain -= dst_amount (w_net = wire_in - fee) - /// `fee` is the TOTAL taken off the leg: the DESTINATION reserve's - /// `owner_fee_bps` (accrued to that row; there is no source reserve) PLUS - /// the network fee. Only the NETWORK component is split 50/50 to - /// `underwriter`'s claimable accrual / the rewards pool, and any configured - /// `fee_emissions_share_bps` of that pool is TRANSFERRED to the `sysio` - /// treasury. The escrowed `wire_in` splits into the new liquidity plus the - /// routed fee; every part stays in custody except that emissions share, so - /// custody balances once it is accounted for. - /// - /// `underwriter` is the uwreq's winning underwriter, forwarded by - /// `sysio.uwrit::try_select_winner`. - [[sysio::action]] - void applyfromwire(sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t wire_in, - uint64_t dst_amount, - sysio::name underwriter); - - /// Auth=sysio.uwrit. Settlement for a swap-TO-WIRE (the depot is the - /// target; only the source outpost leg exists). Pays the recipient exactly - /// `wire_out` REAL WIRE from custody, and charges the swap fee on the gross - /// weighted WIRE leg the source produces: - /// src: chain += src_amount, wire -= (wire_out + fee) - /// credit `wire_out` to the recipient's `wireclaims` row (pulled via `claimwire`) - /// `fee` is the TOTAL taken off the leg: the SOURCE reserve's - /// `owner_fee_bps` (accrued to that row; the recipient is paid in WIRE, so - /// there is no destination reserve) PLUS the network fee. Only the NETWORK - /// component is split 50/50 to `underwriter`'s claimable accrual / the - /// rewards pool, and any configured `fee_emissions_share_bps` of that pool - /// is TRANSFERRED to the `sysio` treasury. The source reserve keeps any - /// surplus when the user targeted below the post-fee quote. `Σ - /// reserve_wire_amount` drops by `wire_out + fee`; what leaves custody is - /// that emissions share alone — `wire_out` stays in custody as a claim. - /// - /// `underwriter` is the uwreq's winning underwriter, forwarded by - /// `sysio.uwrit::try_select_winner`. - /// - /// The payout is CREDITED, not transferred: this action is inlined from - /// `sysio.uwrit::try_select_winner` inside the never-throw - /// `deliver -> evalcons -> dispatch` chain, and `sysio.token::transfer` - /// notifies `recipient`. A recipient whose notify handler asserts (or burns - /// CPU) would otherwise abort the consensus-tipping delivery and stall - /// dispatch. The WIRE stays in this contract's custody until claimed. - [[sysio::action]] - void paywire(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - uint64_t src_amount, - sysio::name recipient, - uint64_t wire_out, - sysio::name underwriter); - - /// Auth=sysio.uwrit. Refund escrowed WIRE to a swap-FROM-WIRE user - /// whose queued request failed drain-time validation (reserve - /// missing / not ACTIVE / private / variance drift) or whose uwreq was - /// rejected. Touches no reserve row — the escrow was never credited to - /// any `reserve_wire_amount`. - /// - /// `revert_fee_bps` is the caller-fault revert fee: the recipient gets - /// `wire_amount` minus the fee, and the fee routes through the standard - /// `route_wire_fee` path. A revert has no winning underwriter — nobody - /// locked collateral for a swap that never settled — so the underwriter - /// share is zero and the WHOLE revert fee becomes the rewards POOL. That - /// pool is then split by `fee_emissions_share_bps` exactly like a - /// settlement fee's: under the default zero dial the entire revert fee - /// accrues to `rewards_bucket`, and with a configured dial that share is - /// transferred to the `sysio` emissions treasury instead. Pass 0 for - /// no-fault refunds (whole fee path no-ops). Callers keep it below 100% - /// (`sysio.uwrit::MAX_FEE_BPS`) so the post-fee refund stays positive. - /// - /// The refund is CREDITED to the recipient's `wireclaims` row, not - /// transferred. This action is inlined from the never-throw - /// `sysio.uwrit::drainfwq` drain, which itself runs inline from - /// `sysio.epoch::advance`; a pushed transfer notifies `recipient`, so a - /// hostile notify handler would abort `advance` and halt epoch - /// advancement chain-wide. Worse, the queue row erase would roll back - /// with it, making the block permanent and retried every epoch. - [[sysio::action]] - void refundwire(sysio::name recipient, - uint64_t wire_amount, - uint32_t revert_fee_bps); - - /// Auth = the claiming account. Pull the caller's credited WIRE - /// (swap-to-WIRE payouts and swap-from-WIRE refunds) out of this - /// contract's custody in a single transfer. - /// - /// This is the ONLY place a `wireclaims` balance becomes a transfer to the - /// claimant, and it carries the claimant's own authority — so a recipient - /// whose transfer-notify handler aborts blocks nothing but its own claim. - /// Throws when there is nothing to claim, which reaches only the caller. - /// - /// Also refuses a row past `expires_at_sec`. The sweep is bounded and - /// best-effort, so an expired row can wait many epochs for its turn; - /// without this check the retention deadline would mean "swept eventually" - /// rather than "claimable until", and a forfeit balance would still pay - /// out in the meantime. - [[sysio::action]] - void claimwire(sysio::name account); - - /// Auth = `sysio.epoch` or self. Erase up to `max_rows` `wireclaims` rows - /// whose one-year retention window has closed and push their total to the - /// emissions treasury. - /// - /// `sysio.epoch::advance` inlines this every epoch with - /// `MAX_CLAIM_SWEEP_PER_EPOCH`. That is what makes the retention deadline - /// real: `credit_wire_claim` also sweeps, but only as a side effect of a - /// later credit, so with settlement traffic stopped no action would ever - /// revisit an aged-out row — leaving an unbounded, system-funded table and - /// the WIRE it reserves outstanding indefinitely. - /// - /// Bounded and never-throwing past the auth gate for the usual reason: it - /// runs inline inside `advance`, where an abort stalls epoch progress - /// chain-wide. An oversized backlog drains across later epochs. - [[sysio::action]] - void sweepclaims(uint32_t max_rows); - - /// Auth = self (`sysio.reserv`). Set the contract's fee-routing config. - /// - /// * `fee_emissions_share_bps` — the share of each fee's REWARDS POOL - /// (the half left after the winning underwriter's cut) transferred to - /// the `sysio` emissions treasury; the remainder accrues to - /// `rewards_bucket` for batch operators. 0 (the default) allocates the - /// whole pool to the batch-operator distribution and keeps every fee - /// inside this contract's custody at settlement. Capped at - /// `FEE_SPLIT_TOTAL_BPS`. - [[sysio::action]] - void setconfig(uint32_t fee_emissions_share_bps); - - /// Auth = the reserve's `owner`. Set this reserve's owner fee — the - /// independent, per-reserve fee its liquidity earns on every swap that - /// draws from it (WIRE-281). `owner_fee_bps` is either 0 (charge nothing) - /// or in `[MIN_OWNER_FEE_BPS, MAX_OWNER_FEE_BPS]`. - /// - /// Requires an ACTIVE reserve with a resolved `owner`: a bootstrap-seeded - /// public reserve has no owner, so no account can authorize a fee on it - /// and none can be stranded with no claimant. Re-callable — the fee is a - /// live parameter, not a create-time constant. - [[sysio::action]] - void setrsvfee(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - uint32_t owner_fee_bps); - - /// Read-only: one reserve's unclaimed owner-fee WIRE balance, held in this - /// contract's custody until `claimrsvfee`. Zero for a reserve that has - /// never charged. - [[sysio::action, sysio::read_only]] - uint64_t rsvfeebal(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code); - - /// Auth = the reserve's `owner`. Pay out this reserve's entire accrued - /// owner fee as REAL WIRE from custody and zero the accrual; - /// `owner_fee_lifetime` (an audit total) is untouched. - /// - /// Owner-authenticated and self-serve, exactly like `claimuwfee` — the - /// depot never pushes these payouts. Throws when the reserve is missing, - /// has no owner, or has nothing accrued: a claim with nothing to pay is a - /// caller mistake, not a silent no-op. - [[sysio::action]] - void claimrsvfee(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code); - - /// Read-only: `underwriter`'s unclaimed swap-fee WIRE balance (the - /// underwriter half of every fee their winning commits settled), held in - /// this contract's custody until `claimuwfee`. Zero for an underwriter - /// with no accrual row. - [[sysio::action, sysio::read_only]] - uint64_t uwfeebal(sysio::name underwriter); - - /// Auth = `underwriter` (the earner). Pay out the caller's entire accrued - /// swap-fee balance as REAL WIRE from this contract's custody and zero the - /// accrual. `lifetime_claimed` (an audit total) accumulates instead. - /// - /// Owner-authenticated and self-serve — the depot never pushes these - /// payouts, so an underwriter claims on their own schedule and a dormant - /// underwriter costs the chain no per-epoch inline transfers. Throws when - /// the caller has no accrual row or a zero balance: a claim with nothing to - /// pay is a caller mistake, not a silent no-op. - [[sysio::action]] - void claimuwfee(sysio::name underwriter); - - // ----------------------------------------------------------------------- - // Tables - // ----------------------------------------------------------------------- - - /// Triple-slug_name primary key. Composite encoded as - /// `checksum256(chain_code || token_code || reserve_code)`. - struct reserve_key { - sysio::slug_name chain_code; - sysio::slug_name token_code; - sysio::slug_name reserve_code; - checksum256 primary_key() const { - std::array buf{}; - std::memcpy(buf.data() + 0, &chain_code.value, 8); - std::memcpy(buf.data() + 8, &token_code.value, 8); - std::memcpy(buf.data() + 16, &reserve_code.value, 8); - return sysio::sha256(reinterpret_cast(buf.data()), buf.size()); - } - SYSLIB_SERIALIZE(reserve_key, (chain_code)(token_code)(reserve_code)) - }; - - struct [[sysio::table("reserves")]] reserve_row { - sysio::slug_name chain_code; - sysio::slug_name token_code; - sysio::slug_name reserve_code; - std::string name; - std::string description; - opp::types::ReserveStatus status = opp::types::RESERVE_STATUS_UNKNOWN; - uint64_t reserve_chain_amount = 0; - uint64_t reserve_wire_amount = 0; - /// Depot-frame decimal precision of the source token side, recorded at - /// creation so the reserve is self-describing — nothing assumes a - /// precision. `source_token_precision = min(token native precision, 9)` - /// (the outpost downscales anything above the 9-dec frame cap at its - /// boundary). The WIRE/target side is always `WIRE_PRECISION` (9), so it - /// is not carried (there is no target_token_precision). The AMM curve is - /// precision-homogeneous, so this does NOT enter swap math — it exists - /// for unambiguous amount interpretation off the curve. - uint32_t source_token_precision = WIRE_PRECISION; - uint32_t connector_weight_bps = DEFAULT_CONNECTOR_WEIGHT_BPS; - opp::types::ChainAddress creator_addr; - uint64_t requested_wire_amount = 0; - uint64_t external_token_amount = 0; - uint64_t registered_at_ms = 0; - uint64_t activated_at_ms = 0; - uint64_t cancelled_at_ms = 0; - /// Private reserves only swap against counterpart reserves owned - /// by the same WIRE account (and are excluded from WIRE-endpoint - /// swaps entirely). Immutable after create. - bool is_private = false; - /// The WIRE account that matched (and therefore owns) this - /// reserve — the authex-linked account of the creator. Empty for - /// bootstrap-seeded public reserves unless `regreserve` named one. - sysio::name owner; - /// Canonical raw pubkey bytes of the creator (33-byte compressed - /// secp256k1 for EVM, 32-byte ed25519 for SVM), normalized at - /// create time. `matchreserve` compares the matcher's authex-link - /// key against this. - std::vector creator_pub_key; - - /// The owner's fee on this reserve's WIRE leg, in basis points — the - /// reserve's own revenue, INDEPENDENT of the network `fee_bps`. Every - /// swap that draws liquidity from this reserve pays it, so a - /// chain-to-chain swap between two fee-charging reserves pays both plus - /// the network fee. Set by the owner via `setrsvfee`; `0` (the default) - /// means the reserve charges nothing. See `MAX_OWNER_FEE_BPS`. - uint32_t owner_fee_bps = 0; - /// Unclaimed WIRE this reserve has earned from `owner_fee_bps`, held in - /// this contract's custody until the owner calls `claimrsvfee`. Part of - /// the custody invariant documented on `rewards_bucket`. - uint64_t owner_fee_accrued = 0; - /// Audit total: WIRE this reserve has earned from `owner_fee_bps`. - /// Monotonic — never decremented by a claim. - /// - /// SATURATES at `UINT64_MAX` (~18.45e9 WIRE at 9dp) rather than - /// wrapping. Unlike `owner_fee_accrued` — a balance, bounded by what is - /// actually in custody — this is an unbounded running total, so the - /// ceiling is reachable in principle: it needs roughly 370 turnovers of - /// the entire launch supply through THIS ONE reserve at a 5% owner fee - /// (~19 at the 99% maximum). Past that the counter stops advancing - /// while accrual and claims continue to work normally; only the audit - /// history is truncated, never a balance. - uint64_t owner_fee_lifetime = 0; - - uint128_t by_chain_token() const { - return (static_cast(chain_code.value) << 64) | token_code.value; - } - uint64_t by_status() const { return magic_enum::enum_integer(status); } - - SYSLIB_SERIALIZE(reserve_row, - (chain_code)(token_code)(reserve_code)(name)(description) - (status)(reserve_chain_amount)(reserve_wire_amount) - (source_token_precision)(connector_weight_bps) - (creator_addr)(requested_wire_amount)(external_token_amount) - (registered_at_ms)(activated_at_ms)(cancelled_at_ms) - (is_private)(owner)(creator_pub_key) - (owner_fee_bps)(owner_fee_accrued)(owner_fee_lifetime)) - }; - - using reserves_t = sysio::kv::table<"reserves"_n, reserve_key, reserve_row, - sysio::kv::index<"bychaintok"_n, sysio::const_mem_fun>, - sysio::kv::index<"bystatus"_n, sysio::const_mem_fun> - >; - - /// Singleton accumulator for the rewards (batch-operator) half of swap - /// fees. The WIRE stays in this contract's custody — it is NOT transferred - /// out — so the custody invariant is `token_balance == Σ - /// reserve_wire_amount + rewards.balance + Σ uw_fee_row.balance + - /// Σ reserve_row.owner_fee_accrued + in-flight escrow + - /// Σ wireclaims.balance`. A collected owner fee leaves - /// `reserve_wire_amount` but stays in this contract until `claimrsvfee`, - /// so it is a term in the equation, not an outflow; the last term likewise - /// covers settled-but-unclaimed payouts and refunds, whose WIRE stays in - /// custody until the recipient pulls it. `balance` is the portion - /// earmarked for distribution (swept by `drainrewards` and folded into - /// `sysio.system::payepoch`); `lifetime_accrued` is an audit total. (The - /// underwriter half of each fee accrues to `uwfees` instead and is - /// therefore not tracked here.) - /// - /// NOTE: sysio.system reads this row through a layout-compatible local - /// definition (a `[[sysio::table]]`-attributed struct cannot be shared - /// into sysio.system's translation unit — it corrupts that contract's - /// read-only-action return codegen). The cross-contract read is exercised - /// end-to-end by t5_emissions_tests/payepoch_folds_swap_fee_rewards, which - /// fails if the two layouts ever diverge. - struct [[sysio::table("rewardbkt")]] rewards_bucket { - uint64_t balance = 0; // claimable WIRE held for distribution - uint64_t lifetime_accrued = 0; // audit: total WIRE ever routed to rewards - SYSLIB_SERIALIZE(rewards_bucket, (balance)(lifetime_accrued)) - }; - using rewardbkt_t = sysio::kv::global<"rewardbkt"_n, rewards_bucket>; - - /// Claimable WIRE owed to an account by `paywire` (swap-to-WIRE settlement) or - /// `refundwire` (swap-from-WIRE revert). Both credit here instead of transferring, because - /// both are reached from never-throw paths where a recipient's transfer-notify handler could - /// otherwise abort the enclosing transaction — stalling consensus dispatch in `paywire`'s - /// case and halting epoch advancement chain-wide in `refundwire`'s. - /// - /// The backing WIRE never leaves this contract's custody at credit time, so it is a term of - /// the custody invariant above. - struct wireclaim_key { - uint64_t account; - SYSLIB_SERIALIZE(wireclaim_key, (account)) - }; - - struct [[sysio::table("wireclaims")]] wire_claim { - sysio::name account; - uint64_t balance = 0; // atomic WIRE units owed, not yet claimed - uint32_t expires_at_sec = 0; // swept back to the treasury once past - - /// Expiry-major composite so the secondary index orders by expiry and the retention sweep - /// can stop at the first live row. The account tail only breaks ties, keeping the key - /// unique when many rows share an expiry second. - uint128_t by_expiry() const { - return (static_cast(expires_at_sec) << 64) | account.value; - } - - SYSLIB_SERIALIZE(wire_claim, (account)(balance)(expires_at_sec)) - }; - - using wireclaims_t = sysio::kv::table<"wireclaims"_n, wireclaim_key, wire_claim, - sysio::kv::index<"byexpiry"_n, - sysio::const_mem_fun> - >; - - /// Key for `uwfees` — one row per earning underwriter account. - struct uw_fee_key { - sysio::name underwriter; - uint64_t primary_key() const { return underwriter.value; } - SYSLIB_SERIALIZE(uw_fee_key, (underwriter)) - }; - - /// Per-underwriter accrual of the underwriter half of swap fees. Credited - /// by `route_wire_fee` at settlement (`applyswap` / `applyfromwire` / - /// `paywire`) to the uwreq's winning underwriter, and drained by the - /// owner-authenticated `claimuwfee`. The WIRE never leaves this contract's - /// custody until a claim, so these balances are part of the custody - /// invariant documented on `rewards_bucket`. - /// - /// `balance` is unclaimed WIRE. `lifetime_accrued` / `lifetime_claimed` - /// are monotonic audit totals; a row is created on first accrual and - /// RETAINED at zero balance after a claim so the audit trail survives. - /// The two `lifetime_*` counters SATURATE at `UINT64_MAX` (~18.45e9 WIRE - /// at 9dp) rather than wrapping — see `reserve_row::owner_fee_lifetime` - /// for the reachability arithmetic. They are unbounded running totals, so - /// unlike `balance` (bounded by custody) the ceiling is reachable in - /// principle; past it the audit history is truncated while accrual and - /// `claimuwfee` continue to work normally. - struct [[sysio::table("uwfees")]] uw_fee_row { - sysio::name underwriter; - uint64_t balance = 0; // unclaimed WIRE held in custody - uint64_t lifetime_accrued = 0; // audit: WIRE accrued (saturating) - uint64_t lifetime_claimed = 0; // audit: WIRE paid out (saturating) - SYSLIB_SERIALIZE(uw_fee_row, (underwriter)(balance)(lifetime_accrued)(lifetime_claimed)) - }; - using uwfees_t = sysio::kv::table<"uwfees"_n, uw_fee_key, uw_fee_row>; - - /// Fee-routing configuration singleton. Holds only the governance dial - /// that stage 2 of the fee split consults — the stage-1 underwriter share - /// is the fixed `FEE_UNDERWRITER_SHARE_BPS` constant, and the fee RATE - /// itself lives on `sysio.uwrit::uwconfig`. - struct [[sysio::table("reservcfg")]] reserve_config { - /// Share of each fee's rewards pool routed to the `sysio` emissions - /// treasury; the remainder goes to `rewards_bucket`. Default 0. - uint32_t fee_emissions_share_bps = DEFAULT_FEE_EMISSIONS_SHARE_BPS; - SYSLIB_SERIALIZE(reserve_config, (fee_emissions_share_bps)) - }; - using reservcfg_t = sysio::kv::global<"reservcfg"_n, reserve_config>; - - private: - using ReserveStatus = opp::types::ReserveStatus; - using ChainKind = opp::types::ChainKind; - }; - -} // namespace sysio diff --git a/contracts/sysio.reserv/src/sysio.reserv.cpp b/contracts/sysio.reserv/src/sysio.reserv.cpp deleted file mode 100644 index 9efcdcbe85..0000000000 --- a/contracts/sysio.reserv/src/sysio.reserv.cpp +++ /dev/null @@ -1,1268 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -namespace sysio { - -namespace { - -// System-owned rows bill to the sysio RAM pool, not this contract account (privileged-contract -// model, as sysio.token uses): the account stays finite at code+abi size; growth draws from the pool. -constexpr name ram_payer = "sysio"_n; - -/// Canonical raw address widths for the creator-address domains accepted by -/// reserve creation. -constexpr std::size_t evm_creator_address_bytes = 20; -constexpr std::size_t svm_creator_address_bytes = 32; - -uint64_t current_time_ms() { - return static_cast(current_time_point().sec_since_epoch()) * 1000; -} - -uint32_t get_current_epoch_index() { - sysio::epoch::epochstate_t es(reserve::EPOCH_ACCOUNT); - if (!es.exists()) return 0; - return es.get().current_epoch_index; -} - -bool is_bootstrap_window() { - return get_current_epoch_index() == 0; -} - -/// Saturating uint64 credit for reserve balances / rewards-bucket counters. These accumulate from -/// operator-relayed external-chain amounts (no on-chain supply cap), and the credit sites run -/// inside the consensus dispatch chain (applyswap / applyfromwire / paywire inline from -/// uwrit::try_select_winner). A raw `+=` could wrap the uint64 and corrupt the -/// weighted-AMM curve and the `>=` sufficiency checks; cap at UINT64_MAX instead — never wrap, -/// never throw on the consensus path. Delegates to the shared -/// `sysio::opp::safe::add_sat_u64` so the never-wrap rule lives in one place. -/// -/// The cap is unreachable for any real BALANCE — a balance is bounded by what is -/// actually in custody. That reasoning does NOT carry to the monotonic -/// `lifetime_*` audit counters this helper also credits: those are unbounded -/// running totals and CAN saturate, which is documented at each field -/// (`reserve_row::owner_fee_lifetime`, `uw_fee_row::lifetime_*`). Saturation -/// there truncates audit history only — never a balance, never a payout. -inline void add_capped_u64(uint64_t& balance, uint64_t amt) { - balance = sysio::opp::safe::add_sat_u64(balance, amt); -} - -/// Live per-spoke swap fee (basis points) — owned by `sysio.uwrit`. Read fresh -/// so the read-only quote and settlement charge one and the same rate. Falls -/// back to the `uw_config` default when uwrit has not been configured yet. -uint32_t uwrit_fee_bps() { - sysio::uwrit::uwconfig_t cfg(reserve::UWRIT_ACCOUNT); - return cfg.get_or_default(sysio::uwrit::uw_config{}).fee_bps; -} - -/// Stage-2 governance dial: the share of each fee's REWARDS POOL routed to the -/// `sysio` emissions treasury. Read fresh per settlement so a `setconfig` takes -/// effect immediately; defaults to 0 (the whole pool is allocated to the -/// batch-operator distribution and no fee leaves custody) until configured. -uint32_t fee_emissions_share_bps(name self) { - reserve::reservcfg_t cfg(self); - return cfg.get_or_default(reserve::reserve_config{}).fee_emissions_share_bps; -} - -/// Credit a reserve's owner-fee revenue — the claimable balance AND the -/// monotonic audit total — from inside an open `modify` lambda. No-op at zero, -/// so a fee-free reserve costs nothing. -void accrue_owner_fee(reserve::reserve_row& row, uint64_t amount) { - if (amount == 0) return; - add_capped_u64(row.owner_fee_accrued, amount); - add_capped_u64(row.owner_fee_lifetime, amount); -} - -reserve::reserve_key make_key(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code) { - return reserve::reserve_key{chain_code, token_code, reserve_code}; -} - -/// Resolve a `chain_code` to its `ChainKind` via the `sysio.chains` -/// registry (mirrors `sysio.uwrit`'s helper of the same name). Returns -/// `std::nullopt` when the chain is unregistered. -std::optional chain_kind_for_code(sysio::slug_name chain_code) { - sysio::chains::chains_t tbl(reserve::CHAINS_ACCOUNT); - sysio::chains::chain_key pk{chain_code}; - if (!tbl.contains(pk)) return std::nullopt; - return tbl.get(pk).kind; -} - -/// Resolve the authoritative kind while soft-gating never-throw msgch handlers -/// before they can emit a queueout to an unregistered destination chain. -/// `sysio.msgch::queueout` fails loudly for direct callers, but dispatch -/// callbacks must log-and-skip instead of aborting the consensus-tipping -/// delivery transaction. -std::optional -registered_chain_kind_or_skip(sysio::slug_name chain_code, const char* handler) { - auto kind = chain_kind_for_code(chain_code); - if (kind.has_value()) return kind; - sysio::print(handler, ": chain_code is not registered; skipping\n"); - return std::nullopt; -} - -/// Validate the raw creator address against the authoritative registry kind. -/// User-created reserves originate on supported external outposts only: EVM -/// addresses are 20 bytes and SVM addresses are 32 bytes. Unknown/depot kinds -/// are not valid creator-address domains for this handler. -bool creator_address_matches_kind(opp::types::ChainKind kind, - const std::vector& address) { - using opp::types::ChainKind; - switch (kind) { - case ChainKind::CHAIN_KIND_UNKNOWN: - case ChainKind::CHAIN_KIND_WIRE: - return false; - case ChainKind::CHAIN_KIND_EVM: - return address.size() == evm_creator_address_bytes; - case ChainKind::CHAIN_KIND_SVM: - return address.size() == svm_creator_address_bytes; - } - - // Fail closed for an invalid numeric enum value while leaving the switch - // exhaustive, so adding a declared ChainKind produces a compiler warning. - return false; -} - -/// Reconstruct a `sysio::public_key` variant from the raw creator-key -/// bytes carried on a ReserveCreate attestation. EVM chains require the -/// 33-byte compressed secp256k1 key in `creator_pub_key` (the EM variant, -/// index 3); SVM chains use the 32-byte ed25519 key (ED variant, index 4), -/// falling back to `creator_addr.address` (which on Solana IS the signer -/// pubkey) when `creator_pub_key` was left empty. Returns `std::nullopt` -/// on any size/kind mismatch — callers treat that as "creator not linkable" -/// (never throw; this runs inside the msgch dispatch chain). -std::optional pubkey_from_raw(opp::types::ChainKind kind, - const std::vector& pub_bytes, - const std::vector& addr_bytes) { - using opp::types::ChainKind; - if (kind == ChainKind::CHAIN_KIND_EVM) { - if (pub_bytes.size() != 33) return std::nullopt; - using em_t = std::variant_alternative_t<3, sysio::public_key>; - em_t em{}; - std::memcpy(em.data(), pub_bytes.data(), pub_bytes.size()); - return sysio::public_key{std::in_place_index<3>, em}; - } - if (kind == ChainKind::CHAIN_KIND_SVM) { - const std::vector& src = pub_bytes.size() == 32 ? pub_bytes : addr_bytes; - if (src.size() != 32) return std::nullopt; - using ed_t = std::variant_alternative_t<4, sysio::public_key>; - ed_t ed{}; - std::memcpy(ed.data(), src.data(), src.size()); - return sysio::public_key{std::in_place_index<4>, ed}; - } - return std::nullopt; -} - -/// Encode + queue a depot→outpost attestation back to the reserve's owning -/// chain. Mirrors `sysio.uwrit::emit_swap_remit` / `emit_swap_revert`: -/// -/// * `zpp::bits::no_size{}` — raw protobuf bytes for the outpost decoder -/// (the default `zpp::bits::data_out` form prepends a 4-byte LE length -/// prefix that corrupts the first field tag on the receiving side). -/// * The destination `chain_code` is the reserve's `chain_code.value` -/// itself (per the convention recorded in `sysio.msgch.hpp`: -/// "the outpost id IS the chain's slug_name value"). -template -void queue_attestation_out(name self, - sysio::slug_name owning_chain, - opp::types::AttestationType attest_type, - const ProtoMessage& message) { - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(message); - - action( - permission_level{self, "active"_n}, - reserve::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(owning_chain.value, attest_type, encoded) - ).send(); -} - -/// Route the NETWORK COMPONENT of a collected WIRE swap fee to its three -/// destinations. The reserve-owner shares carried in `fee` are NOT routed here — -/// the settlement actions accrue those to their own reserve rows before calling -/// this, so this function only ever moves the network fee's own split: -/// * the underwriter share accrues to `underwriter`'s `uwfees` row, payable to -/// that account on its own `claimuwfee` call — stays in custody; -/// * the rewards share accrues to the singleton `rewards_bucket`, swept by -/// `drainrewards` into `sysio.system::payepoch` and paid to batch operators -/// — stays in custody; -/// * the emissions share (zero unless `reserve_config` sets one) is -/// TRANSFERRED to the `sysio` treasury — the only part that leaves custody. -/// No-op when there is no fee. -/// -/// At the default config the emissions share is 0, so everything THIS function -/// routes moves into two earmarked accumulators in the SAME custody and the call -/// never changes `token_balance`. With a non-zero share, custody drops by exactly -/// that share. Note the total `wire_fee::fee` is larger than what is routed here -/// whenever a participating reserve charges an owner fee: at the default dial the -/// whole fee ends up spread across the owner accrual(s) the caller already made, -/// `uwfees`, and `rewards_bucket` — not wholly into the two accumulators below. -/// -/// A fee with no winning underwriter (a revert refund) passes an unset -/// `underwriter` together with a zero underwriter share. Should a caller ever -/// pair an unset account with a non-zero share, the share falls through to the -/// rewards bucket rather than stranding WIRE in custody with no claimant. -void route_wire_fee(name self, const opp::amm::wire_fee& fee, name underwriter) { - uint64_t reward_share = fee.reward_share; - - if (fee.underwriter_share > 0 && underwriter.value != 0) { - reserve::uwfees_t uwf(self); - reserve::uw_fee_key key{underwriter}; - auto it = uwf.find(key); - if (it == uwf.end()) { - uwf.emplace(ram_payer, key, reserve::uw_fee_row{ - .underwriter = underwriter, - .balance = fee.underwriter_share, - .lifetime_accrued = fee.underwriter_share, - .lifetime_claimed = 0, - }); - } else { - uwf.modify(ram_payer, key, [&](auto& row) { - add_capped_u64(row.balance, fee.underwriter_share); - add_capped_u64(row.lifetime_accrued, fee.underwriter_share); - }); - } - } else { - reward_share += fee.underwriter_share; - } - - if (reward_share > 0) { - reserve::rewardbkt_t bkt(self); - auto rb = bkt.get_or_default(reserve::rewards_bucket{}); - add_capped_u64(rb.balance, reward_share); - add_capped_u64(rb.lifetime_accrued, reward_share); - bkt.set(rb, ram_payer); - } - - // The ONLY part of a fee that leaves this contract's custody, and only when - // governance has configured a non-zero emissions share. - if (fee.emissions_share > 0) { - action( - permission_level{self, "active"_n}, - reserve::TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(self, reserve::TREASURY_ACCOUNT, - asset(static_cast(fee.emissions_share), opp::wire::asset_symbol), - std::string("sysio.reserv::swap fee -> emissions")) - ).send(); - } -} - -/// Credit `amount` WIRE to `recipient`'s claimable row, then run the bounded retention sweep. -/// -/// Both callers (`paywire`, `refundwire`) sit on never-throw paths, so nothing here may abort: the -/// credit saturates rather than overflowing, and the sweep is bounded and non-throwing. -/// -/// Crediting instead of transferring is the whole point. `sysio.token::transfer` notifies the -/// recipient, and the chain runs notified receivers with no exception isolation, so a pushed payout -/// hands the recipient an abort switch over the enclosing transaction -- stalling consensus -/// dispatch from `paywire`, and halting epoch advancement chain-wide from `refundwire`. -/// -/// Reclaimed WIRE from expired rows is pushed to the emissions treasury. That push is safe for the -/// same reason `route_wire_fee`'s emissions share is: `sysio` is a system account under protocol -/// control, not a counterparty that can install a hostile notify handler. -/// THE retention sweep — one implementation behind both triggers, so the on-write path and the -/// epoch path can never drift on what "expired" means or where the reclaimed WIRE goes. -/// -/// Erases up to `max_rows` rows whose `expires_at_sec` has passed and pushes their total to the -/// emissions treasury. Never throws: both callers sit on never-throw paths (`credit_wire_claim` -/// runs under consensus dispatch and epoch advance; `sweepclaims` is inlined from `advance`). -/// -/// @return atomic WIRE units reclaimed. -uint64_t sweep_expired_wire_claims(name self, uint32_t now_sec, uint32_t max_rows) { - reserve::wireclaims_t claims(self); - auto by_expiry = claims.get_index<"byexpiry"_n>(); - const uint64_t reclaimed = sysio::opp::claimable::sweep_expired( - claims, by_expiry, - [](const reserve::wire_claim& r) { return reserve::wireclaim_key{r.account.value}; }, - now_sec, max_rows); - - if (reclaimed > 0) { - action( - permission_level{self, "active"_n}, - reserve::TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(self, reserve::TREASURY_ACCOUNT, - asset(static_cast(reclaimed), opp::wire::asset_symbol), - std::string("sysio.reserv::expired WIRE claim -> emissions")) - ).send(); - } - return reclaimed; -} - -void credit_wire_claim(name self, name recipient, uint64_t amount) { - if (amount == 0) return; - - const uint32_t now_sec = static_cast(current_time_point().sec_since_epoch()); - - reserve::wireclaims_t claims(self); - const auto pk = reserve::wireclaim_key{recipient.value}; - - // Settle an ALREADY-FORFEITED row before accumulating onto it. - // - // `credit` upserts: it adds to whatever balance is there and refreshes the stamp. If this - // recipient's row is past its window but still queued behind the bounded sweep, that would - // resurrect a balance `claimwire` has already been refusing -- the old amount becomes claimable - // again, and a trickle of small credits could keep a system-funded row (and its forfeited WIRE) - // alive indefinitely. Reclaiming first means a credit can only ever start a FRESH claim. - // - // Expiry exists so abandoned rows do not hold RAM forever; an account still being credited is - // not abandoned, so its new claim legitimately gets a full window. What that must not do is - // un-forfeit the balance the last window already closed on. - auto it = claims.find(pk); - if (it != claims.end() && it->expires_at_sec != 0 && now_sec >= it->expires_at_sec) { - const uint64_t forfeited = it->balance; - claims.erase(pk); - if (forfeited > 0) { - action( - permission_level{self, "active"_n}, - reserve::TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(self, reserve::TREASURY_ACCOUNT, - asset(static_cast(forfeited), opp::wire::asset_symbol), - std::string("sysio.reserv::expired WIRE claim -> emissions")) - ).send(); - } - } - - sysio::opp::claimable::credit( - claims, ram_payer, pk, - reserve::wire_claim{.account = recipient}, amount, - now_sec + reserve::WIRE_CLAIM_WINDOW_SEC); - - // On-write retention: sweep a bounded number of rows that aged out before adding more. Runs - // after the credit so the row just refreshed above is never the one collected. - // - // This is an OPPORTUNISTIC trigger, not the guarantee: it only fires while swap traffic keeps - // arriving. `sweepclaims`, inlined from `sysio.epoch::advance`, is what makes the retention - // deadline hold when settlement stops. - sweep_expired_wire_claims(self, now_sec, reserve::MAX_CLAIM_SWEEP_PER_CREDIT); -} - -} // namespace - -void reserve::regreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - std::string name, - std::string description, - uint64_t initial_chain_amount, - uint64_t initial_wire_amount, - uint32_t source_token_precision, - uint32_t connector_weight_bps, - bool is_private, - sysio::name owner) { - opp::require_privileged_self(); - sysio::check(is_bootstrap_window(), - "regreserve is bootstrap-window only; post-bootstrap reserves go through create_reserve"); - sysio::check(source_token_precision <= WIRE_PRECISION, - "source_token_precision exceeds the depot frame (9) — the outpost must downscale to min(native, 9)"); - sysio::check(connector_weight_bps > 0 && connector_weight_bps <= MAX_CONNECTOR_WEIGHT_BPS, - "connector_weight_bps must be in (0, 10000) — both pool-side weights must be positive"); - sysio::check(initial_chain_amount > 0 && initial_wire_amount > 0, - "bootstrap reserve must seed both chain_amount and wire_amount > 0"); - sysio::check(!is_private || owner != sysio::name{}, - "a private bootstrap reserve must name an owner"); - // The three codes form this row's COMPOSITE PRIMARY KEY and are rendered as strings by - // every reader -- refuse any that does not round-trip before the row becomes permanent. - opp::registry::check_codes({chain_code, token_code, reserve_code}, "sysio.reserv"); - // Both strings persist into a `sysio`-billed row -- bound them before emplace. - opp::registry::check_metadata(name, description, "sysio.reserv"); - - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - sysio::check(tbl.find(pk) == tbl.end(), "reserve already registered"); - - // Real-WIRE backing: drain the seed from the `sysio` emissions treasury - // into this contract's custody so `reserve_wire_amount` is never an - // unbacked accounting number. This contract is privileged, so the - // inline action may carry the treasury's authorization. - action( - permission_level{TREASURY_ACCOUNT, "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(TREASURY_ACCOUNT, get_self(), - asset(static_cast(initial_wire_amount), opp::wire::asset_symbol), - std::string("sysio.reserv::regreserve bootstrap WIRE backing")) - ).send(); - - const auto now = current_time_ms(); - tbl.emplace(ram_payer, pk, reserve_row{ - .chain_code = chain_code, - .token_code = token_code, - .reserve_code = reserve_code, - .name = std::move(name), - .description = std::move(description), - .status = opp::types::RESERVE_STATUS_ACTIVE, - .reserve_chain_amount = initial_chain_amount, - .reserve_wire_amount = initial_wire_amount, - .source_token_precision = source_token_precision, - .connector_weight_bps = connector_weight_bps, - .creator_addr = {}, - .requested_wire_amount = initial_wire_amount, - .external_token_amount = initial_chain_amount, - .registered_at_ms = now, - .activated_at_ms = now, - .cancelled_at_ms = 0, - .is_private = is_private, - .owner = owner, - .creator_pub_key = {}, - }); -} - -void reserve::oncrtreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - std::string name, - std::string description, - uint64_t external_token_amount, - uint64_t requested_wire_amount, - uint32_t source_token_precision, - uint32_t connector_weight_bps, - opp::types::ChainKind creator_chain_kind, - std::vector creator_chain_addr, - bool is_private, - std::vector creator_pub_key) { - require_auth(MSGCH_ACCOUNT); - const auto expected_chain_kind = - registered_chain_kind_or_skip(chain_code, "oncrtreserve"); - if (!expected_chain_kind.has_value()) return; - - // The reserve row is keyed by (chain, token, reserve) codes, so an unspellable one - // cannot be persisted. The creator's escrow is already in outpost custody, so refund - // rather than drop. No CANCELLED tombstone (unlike the rejections below): the row's - // key IS the unspellable triple, and a code with no canonical spelling can be neither squatted - // nor reclaimed, so the tombstone has nothing to protect. - if (!token_code.is_canonical() || !reserve_code.is_canonical()) { - sysio::print("oncrtreserve: rejecting with RESERVE_CREATE_CANCELLED " - "(token or reserve code has no canonical slug_name spelling)\n"); - opp::attestations::ReserveCreateCancelled cancelled; - cancelled.chain_code = chain_code.value; - cancelled.token_code = token_code.value; - cancelled.reserve_code = reserve_code.value; - queue_attestation_out(get_self(), chain_code, - opp::types::AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED, - cancelled); - return; - } - - // Soft-validate; silent skip per feedback_opp_handlers_never_throw. - if (connector_weight_bps == 0 || connector_weight_bps > MAX_CONNECTOR_WEIGHT_BPS) { - sysio::print("oncrtreserve: bad connector_weight_bps; skipping\n"); - return; - } - // An invalid amount — a zero deposit / requested amount, OR an inbound - // TokenAmount that sysio.msgch clamped to 0 because it was negative or out of - // asset range (WSA-028) — cannot create a reserve. It must NOT be silently - // dropped: the creator's outpost escrow still has to be released. Route it into - // the SAME cancel/refund path as an unlinked creator below (insert a CANCELLED - // row + queue RESERVE_CREATE_CANCELLED), idempotently. - const bool invalid_amount = (external_token_amount == 0 || requested_wire_amount == 0); - // `chain_code` is registry-owned and authoritative. The attestation also - // carries a creator kind, but that redundant, externally supplied value - // must not select a different public-key variant or produce a reserve that - // `matchreserve` can never match against the registered chain kind. - const bool creator_chain_kind_mismatch = creator_chain_kind != *expected_chain_kind; - const bool invalid_creator_address = - !creator_address_matches_kind(*expected_chain_kind, creator_chain_addr); - // Same `sysio`-billed metadata bound the privileged registrations enforce with - // `check_metadata`, asked the non-throwing way: this handler must never abort, so an - // over-bound string joins the reject/refund path below rather than reverting dispatch. - const bool oversized_metadata = opp::registry::metadata_exceeds_bounds(name, description); - // The outpost downscales to min(native, 9) at its boundary, so a - // source_token_precision above the depot frame means a malformed attestation. - if (source_token_precision > WIRE_PRECISION) { - sysio::print("oncrtreserve: source_token_precision exceeds depot frame (9); skipping\n"); - return; - } - - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - // Existence guard, status-aware. A live row (PENDING/ACTIVE) — or any - // non-CANCELLED row — is immutable here; skip idempotently. A CANCELLED - // row (a prior no-link rejection or an outpost-side cancel) is *reclaimable* - // by a later, properly-linked creator: fall through so the link probe below - // decides whether to overwrite it. Without this carve-out a CANCELLED row - // would permanently burn the (chain, token, reserve_code) identity for its - // rightful owner (namespace squatting), since no path ever erases a row. - auto existing = tbl.find(pk); - const bool reclaimable_cancelled = - existing != tbl.end() && - existing->status == opp::types::RESERVE_STATUS_CANCELLED; - if (existing != tbl.end() && !reclaimable_cancelled) { - sysio::print("oncrtreserve: reserve already exists; skipping\n"); - return; - } - - opp::types::ChainAddress creator; - creator.kind = *expected_chain_kind; - creator.address = std::move(creator_chain_addr); - - // Create gating: the creator must already be authex-linked to a WIRE - // account ("the only requirement to create a reserve"). Reconstruct the - // creator's key variant and probe `sysio.authex::links.bypubkey`. On - // any failure — a chain-kind mismatch, malformed address/key bytes, no - // link, an invalid amount, OR over-bound metadata — reject by inserting a - // CANCELLED row (for refund idempotency) and queueing - // RESERVE_CREATE_CANCELLED so the outpost refunds the creator's escrow. - // The CANCELLED row does NOT permanently burn the identity: a later, - // properly-linked creator reclaims it via the reclaim branch below - // (prevents namespace squatting). Never throws. - std::vector canonical_creator_key; - { - auto pk_variant = pubkey_from_raw(*expected_chain_kind, creator_pub_key, creator.address); - bool linked = false; - if (pk_variant) { - sysio::authex::links_t links(AUTHEX_ACCOUNT); - auto idx = links.get_index<"bypubkey"_n>(); - linked = idx.find(sysio::pubkey_to_checksum256(*pk_variant)) != idx.end(); - if (linked) { - canonical_creator_key = sysio::pubkey_to_bytes(*pk_variant); - } - } - if (creator_chain_kind_mismatch || invalid_creator_address || !linked || - invalid_amount || oversized_metadata) { - // A CANCELLED row already standing means this is a re-relay of the same - // rejected create (an unlinked squatter OR an invalid amount). Leave it - // and do NOT queue a second refund — the refund was queued when the row - // was first inserted, and the outpost refunds per (chain,token,reserve_code). - // The row stays reclaimable by a future linked creator with a valid amount. - if (reclaimable_cancelled) { - sysio::print("oncrtreserve: re-relay of an already-CANCELLED reserve; " - "leaving it (no double refund)\n"); - return; - } - sysio::print("oncrtreserve: rejecting with RESERVE_CREATE_CANCELLED " - "(creator chain-kind/address mismatch, invalid amount, over-bound " - "metadata, or unlinked / malformed creator key)\n"); - // Do not persist attacker-sized address bytes in the sysio-billed - // cancellation tombstone. The outpost refunds by the reserve triple, - // so a malformed creator address is unnecessary for that handshake. - if (invalid_creator_address) creator.address.clear(); - const auto now = current_time_ms(); - tbl.emplace(ram_payer, pk, reserve_row{ - .chain_code = chain_code, - .token_code = token_code, - .reserve_code = reserve_code, - // The tombstone is itself a `sysio`-billed row, so over-bound strings are NOT - // carried onto it — storing them verbatim would persist exactly the state the - // bound exists to prevent. Only THAT rejection reason substitutes the marker; a - // row rejected for an unlinked creator or an invalid amount keeps its (already - // in-bounds) metadata. The creator's originals stay in the inbound OPP envelope - // artifact either way. - .name = oversized_metadata - ? std::string(opp::registry::rejected_label) - : std::move(name), - .description = oversized_metadata ? std::string{} : std::move(description), - .status = opp::types::RESERVE_STATUS_CANCELLED, - .reserve_chain_amount = 0, - .reserve_wire_amount = 0, - .source_token_precision = source_token_precision, - .connector_weight_bps = connector_weight_bps, - .creator_addr = std::move(creator), - .requested_wire_amount = requested_wire_amount, - .external_token_amount = external_token_amount, - .registered_at_ms = now, - .activated_at_ms = 0, - .cancelled_at_ms = now, - .is_private = is_private, - .owner = {}, - .creator_pub_key = {}, - }); - opp::attestations::ReserveCreateCancelled cancelled; - cancelled.chain_code = chain_code.value; - cancelled.token_code = token_code.value; - cancelled.reserve_code = reserve_code.value; - queue_attestation_out(get_self(), chain_code, - opp::types::AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED, - cancelled); - return; - } - } - - const auto now = current_time_ms(); - reserve_row fresh{ - .chain_code = chain_code, - .token_code = token_code, - .reserve_code = reserve_code, - .name = std::move(name), - .description = std::move(description), - .status = opp::types::RESERVE_STATUS_PENDING, - .reserve_chain_amount = external_token_amount, - .reserve_wire_amount = 0, - .source_token_precision = source_token_precision, - .connector_weight_bps = connector_weight_bps, - .creator_addr = std::move(creator), - .requested_wire_amount = requested_wire_amount, - .external_token_amount = external_token_amount, - .registered_at_ms = now, - .activated_at_ms = 0, - .cancelled_at_ms = 0, - .is_private = is_private, - .owner = {}, - .creator_pub_key = std::move(canonical_creator_key), - }; - if (reclaimable_cancelled) { - // A properly-linked creator reclaims a previously-CANCELLED identity: - // overwrite the dead row in place (re-indexing `bystatus` - // CANCELLED → PENDING). Every field is reset to this create — this is - // what makes the slot non-squattable. - tbl.modify(ram_payer, pk, [&](auto& row) { row = std::move(fresh); }); - } else { - tbl.emplace(ram_payer, pk, std::move(fresh)); - } -} - -void reserve::matchreserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - name matcher, - uint64_t wire_amount) { - require_auth(matcher); - - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "matchreserve: reserve not found"); - sysio::check(it->status == opp::types::RESERVE_STATUS_PENDING, - "matchreserve: reserve is not PENDING"); - sysio::check(wire_amount == it->requested_wire_amount, - "matchreserve: wire_amount must equal requested_wire_amount exactly"); - - // Match gating: the matcher MUST be the WIRE account authex-linked to - // the reserve's creator. Resolve the matcher's link for the reserve - // chain's kind and compare its canonical key bytes against the creator - // pubkey normalized at create time. (recordlink permits one key → - // many accounts; ANY account linked to the creator's key may match — - // that is "the linked WIRE account".) - sysio::check(!it->creator_pub_key.empty(), - "matchreserve: reserve carries no creator pubkey (not matchable)"); - auto kind_opt = chain_kind_for_code(chain_code); - sysio::check(kind_opt.has_value(), - "matchreserve: reserve chain not registered in sysio.chains"); - { - sysio::authex::links_t links(AUTHEX_ACCOUNT); - auto idx = links.get_index<"bynamechain"_n>(); - auto lit = idx.find(sysio::to_namechain_key(matcher, *kind_opt)); - sysio::check(lit != idx.end(), - "matchreserve: matcher has no authex link for the reserve's chain"); - sysio::check(sysio::pubkey_to_bytes(lit->pub_key) == it->creator_pub_key, - "matchreserve: matcher is not the authex-linked account of the reserve's creator"); - } - - // The match IS a WIRE deposit: take REAL custody of the WIRE side so - // `reserve_wire_amount` is backed the moment the reserve activates. - action( - permission_level{matcher, "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(matcher, get_self(), - asset(static_cast(wire_amount), opp::wire::asset_symbol), - std::string("sysio.reserv::matchreserve WIRE escrow")) - ).send(); - - tbl.modify(ram_payer, pk, [&](auto& row) { - row.status = opp::types::RESERVE_STATUS_ACTIVE; - row.reserve_wire_amount = wire_amount; - row.activated_at_ms = current_time_ms(); - row.owner = matcher; - }); - - // Reserve is now ACTIVE on the depot. Notify the owning outpost so its - // local reserve record can flip to ACTIVE and become usable for swap - // routing. The destination `chain_code` is the reserve's `chain_code` - // (per the `sysio.msgch::queueout` convention — the outpost id is - // the chain slug_name's packed uint64 value). - opp::attestations::ReserveReady ready; - ready.chain_code = chain_code.value; - ready.token_code = token_code.value; - ready.reserve_code = reserve_code.value; - queue_attestation_out(get_self(), chain_code, - opp::types::AttestationType::ATTESTATION_TYPE_RESERVE_READY, - ready); -} - -void reserve::oncnclrsv(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - opp::types::ChainKind creator_chain_kind, - std::vector creator_chain_addr) { - require_auth(MSGCH_ACCOUNT); - const auto expected_chain_kind = - registered_chain_kind_or_skip(chain_code, "oncnclrsv"); - if (!expected_chain_kind.has_value()) return; - - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - auto it = tbl.find(pk); - if (it == tbl.end()) { - sysio::print("oncnclrsv: reserve not found; silently skipping\n"); - return; - } - - if (it->status != opp::types::RESERVE_STATUS_PENDING) { - sysio::print("oncnclrsv: status != PENDING; race lost, silent no-op\n"); - return; - } - - const bool addr_matches = - creator_chain_kind == *expected_chain_kind && - it->creator_addr.kind == *expected_chain_kind && - it->creator_addr.address == creator_chain_addr; - if (!addr_matches) { - sysio::print("oncnclrsv: creator_addr mismatch; silently skipping\n"); - return; - } - - tbl.modify(ram_payer, pk, [&](auto& row) { - row.status = opp::types::RESERVE_STATUS_CANCELLED; - row.cancelled_at_ms = current_time_ms(); - }); - - // Race won — depot accepted the cancel before any matchreserve. Notify - // the outpost so it refunds the creator's `external_token_amount`. The - // destination `chain_code` is the reserve's owning `chain_code`. Per - // `feedback_opp_handlers_never_throw.md` this handler still cannot - // throw; the early chain-registration guard and the remaining - // soft-validation checks must all pass before queueout is sent. - opp::attestations::ReserveCreateCancelled cancelled; - cancelled.chain_code = chain_code.value; - cancelled.token_code = token_code.value; - cancelled.reserve_code = reserve_code.value; - queue_attestation_out(get_self(), chain_code, - opp::types::AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED, - cancelled); -} - -uint64_t reserve::swapquote(sysio::slug_name from_chain_code, - sysio::slug_name from_token_code, - sysio::slug_name from_reserve_code, - uint64_t from_amount, - sysio::slug_name to_chain_code, - sysio::slug_name to_token_code, - sysio::slug_name to_reserve_code) { - if (from_amount == 0) return 0; - - const bool src_is_wire = opp::wire::is_native_asset(from_chain_code, from_token_code); - const bool dst_is_wire = opp::wire::is_native_asset(to_chain_code, to_token_code); - if (src_is_wire && dst_is_wire) return from_amount; // WIRE->WIRE is a plain transfer - - reserves_t tbl(get_self()); - - // Resolve only the non-WIRE side(s); a WIRE endpoint has no token/WIRE pool - // (the depot IS the WIRE side). Any required reserve missing or not ACTIVE - // yields a 0 quote. - uint64_t src_chain = 0, src_wire = 0; uint32_t src_cw = 0, src_fee_bps = 0; - if (!src_is_wire) { - auto it = tbl.find(make_key(from_chain_code, from_token_code, from_reserve_code)); - if (it == tbl.end() || it->status != opp::types::RESERVE_STATUS_ACTIVE) return 0; - src_chain = it->reserve_chain_amount; - src_wire = it->reserve_wire_amount; - src_cw = it->connector_weight_bps; - src_fee_bps = it->owner_fee_bps; - } - uint64_t dst_chain = 0, dst_wire = 0; uint32_t dst_cw = 0, dst_fee_bps = 0; - if (!dst_is_wire) { - auto it = tbl.find(make_key(to_chain_code, to_token_code, to_reserve_code)); - if (it == tbl.end() || it->status != opp::types::RESERVE_STATUS_ACTIVE) return 0; - dst_chain = it->reserve_chain_amount; - dst_wire = it->reserve_wire_amount; - dst_cw = it->connector_weight_bps; - dst_fee_bps = it->owner_fee_bps; - } - - // Each participating reserve's owner fee rides the quote alongside the - // network fee, so the quote prices exactly what settlement will charge. - return opp::amm::quote_swap(src_is_wire, src_chain, src_wire, src_cw, - dst_is_wire, dst_chain, dst_wire, dst_cw, - from_amount, uwrit_fee_bps(), - src_fee_bps, dst_fee_bps); -} - -uint64_t reserve::rewardbal() { - rewardbkt_t bkt(get_self()); - return bkt.get_or_default(rewards_bucket{}).balance; -} - -void reserve::drainrewards(int64_t amount) { - // Only the system treasury (where sysio.system::payepoch runs) may sweep the - // rewards bucket. The swept WIRE is allocated exclusively to the - // batch-operator distribution at the next pay-epoch — producers are not paid - // out of swap fees. payepoch pays only eligible shares; what it skips stays in - // the treasury. - require_auth(TREASURY_ACCOUNT); - - // Internal treasury sweep: a non-positive amount means the caller's - // integration logic is wrong (payepoch only calls with a positive amount), - // so fail loudly rather than silently no-op. - sysio::check(amount > 0, "drainrewards: amount must be positive"); - - const uint64_t req = static_cast(amount); - - rewardbkt_t bkt(get_self()); - auto rb = bkt.get_or_default(rewards_bucket{}); - sysio::check(req <= rb.balance, "drainrewards: amount exceeds rewards bucket balance"); - - // Drop the accounting balance first, then move the backing WIRE out of - // custody. lifetime_accrued is a cumulative audit total — never decremented. - rb.balance -= req; - bkt.set(rb, ram_payer); - - action( - permission_level{get_self(), "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(get_self(), TREASURY_ACCOUNT, - asset(static_cast(req), opp::wire::asset_symbol), - std::string("sysio.reserv::swap-fee rewards -> emissions payepoch")) - ).send(); -} - -void reserve::debit(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - uint64_t amount) { - require_auth(UWRIT_ACCOUNT); - sysio::check(amount > 0, "amount must be positive"); - - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "debit: reserve not found"); - sysio::check(it->status == opp::types::RESERVE_STATUS_ACTIVE, - "debit: reserve not ACTIVE"); - sysio::check(it->reserve_chain_amount >= amount, - "insufficient reserve_chain_amount for SWAP_REMIT debit"); - - tbl.modify(ram_payer, pk, [&](auto& row) { - row.reserve_chain_amount -= amount; - }); -} - -// onreject was removed — no SwapRejected attestation exists (every depot-initiated -// REMIT is paid by the destination outpost; reserves need no rejection reconciliation). - -// onreward was removed: the STAKING_REWARD path credits the per-staker reward to -// sysio.dclaim directly (already WIRE-denominated), so there is no reserve leg. - -// --------------------------------------------------------------------------- -// Emit-time swap settlement (auth = sysio.uwrit) -// -// All `check()`s below are defense-in-depth: `sysio.uwrit::try_select_winner` -// pre-validates the same conditions against its reserve mirror BEFORE -// sending these inline actions, so within the single race-resolution -// transaction they are unreachable. A check firing here therefore signals -// a depot bug (mirror drift), not a runtime condition — halting that -// transaction is the correct response. -// --------------------------------------------------------------------------- - -void reserve::applyswap(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - uint64_t src_amount, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t dst_amount, - sysio::name underwriter) { - require_auth(UWRIT_ACCOUNT); - sysio::check(src_amount > 0 && dst_amount > 0, "applyswap: amounts must be positive"); - - reserves_t tbl(get_self()); - auto src_pk = make_key(src_chain_code, src_token_code, src_reserve_code); - auto dst_pk = make_key(dst_chain_code, dst_token_code, dst_reserve_code); - auto src_it = tbl.find(src_pk); - sysio::check(src_it != tbl.end(), "applyswap: source reserve not found"); - sysio::check(src_it->status == opp::types::RESERVE_STATUS_ACTIVE, - "applyswap: source reserve not ACTIVE"); - auto dst_it = tbl.find(dst_pk); - sysio::check(dst_it != tbl.end(), "applyswap: destination reserve not found"); - sysio::check(dst_it->status == opp::types::RESERVE_STATUS_ACTIVE, - "applyswap: destination reserve not ACTIVE"); - - // The gross WIRE intermediate is derived from the PRE-mutation source row on - // the weighted curve (the source reserve's own `connector_weight_bps`) — the - // same definition `sysio.uwrit::swap_quote` uses, so the depot's books and - // its quotes share one curve. The fee is then taken OUT of this WIRE leg: the - // source side gives up the full gross WIRE and only `net` continues to the - // destination side. The TOTAL `fee` spreads across BOTH reserves' owner - // accruals (made in the modifies below), the winning underwriter's accrual, - // the batch-operator rewards bucket, and — only when - // `fee_emissions_share_bps` is set — a transfer of that share to `sysio`. - const uint64_t w_gross = opp::amm::token_to_wire(src_it->reserve_chain_amount, - src_it->reserve_wire_amount, - src_it->connector_weight_bps, - src_amount); - sysio::check(w_gross > 0, "applyswap: WIRE intermediate is zero"); - // BOTH reserves supply liquidity for this swap, so both charge their own - // owner fee on the same WIRE leg — on top of the network fee (WIRE-281). - const auto fee = opp::amm::split_wire_fee(w_gross, uwrit_fee_bps(), FEE_UNDERWRITER_SHARE_BPS, - fee_emissions_share_bps(get_self()), - src_it->owner_fee_bps, dst_it->owner_fee_bps); - // SEC-26 / WSA-042 settlement backstop: a zero post-fee WIRE leg credits no - // WIRE to the destination reserve below while still debiting its chain side - // — draining it at an arbitrary price. This is a LIVE path, not unreachable - // defense-in-depth: the caps bound each rate INDEPENDENTLY, so a network fee - // at `sysio.uwrit::MAX_FEE_BPS` (9999) plus either reserve's owner fee at - // MIN_OWNER_FEE_BPS (1) already totals 100%. Such a combination is an - // intentionally rejected configuration — refuse the swap here rather than - // settle it at an arbitrary price. - sysio::check(fee.net > 0, "applyswap: zero post-fee WIRE would credit no destination liquidity"); - sysio::check(src_it->reserve_wire_amount >= w_gross, - "applyswap: insufficient source reserve WIRE for intermediate"); - // WNS-02 settlement bound: the destination side may never give up more token - // than its own curve produces for the post-fee WIRE it is about to receive. - // `sysio.uwrit` derives `dst_amount` from exactly this expression (via - // `swap_quote` -> `opp::amm::quote_swap`) against the same pre-mutation rows - // in the same transaction, so equality holds and this cannot fire on the - // live path. It exists so the reserve is self-defending: `dst_amount` arrives - // as a caller-supplied parameter, and the vulnerability this replaces was - // precisely a caller-chosen amount being paid out verbatim. A reserve that - // trusts its caller for a payout size has no floor of its own. - const uint64_t curve_out = opp::amm::wire_to_token(dst_it->reserve_wire_amount, - dst_it->reserve_chain_amount, - dst_it->connector_weight_bps, - fee.net); - sysio::check(dst_amount <= curve_out, - "applyswap: destination amount exceeds the curve output for the post-fee WIRE"); - sysio::check(dst_it->reserve_chain_amount >= dst_amount, - "applyswap: insufficient destination reserve balance"); - - tbl.modify(ram_payer, src_pk, [&](auto& row) { - add_capped_u64(row.reserve_chain_amount, src_amount); - row.reserve_wire_amount -= w_gross; - accrue_owner_fee(row, fee.src_reserve_share); - }); - // Same-row swaps (identical triples) compose correctly: the second - // modify reads the post-first-modify state. The destination receives only - // the post-fee WIRE. - tbl.modify(ram_payer, dst_pk, [&](auto& row) { - add_capped_u64(row.reserve_wire_amount, fee.net); - row.reserve_chain_amount -= dst_amount; - accrue_owner_fee(row, fee.dst_reserve_share); - }); - - // Route the NETWORK component (the two owner shares already accrued to their - // reserve rows above): underwriter half to `uwfees`, rewards half to - // `rewards_bucket` — both custody-internal — except any configured - // `fee_emissions_share_bps`, the only part that leaves for the treasury. - route_wire_fee(get_self(), fee, underwriter); -} - -void reserve::applyfromwire(sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t wire_in, - uint64_t dst_amount, - sysio::name underwriter) { - require_auth(UWRIT_ACCOUNT); - sysio::check(wire_in > 0 && dst_amount > 0, "applyfromwire: amounts must be positive"); - - reserves_t tbl(get_self()); - auto pk = make_key(dst_chain_code, dst_token_code, dst_reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "applyfromwire: destination reserve not found"); - sysio::check(it->status == opp::types::RESERVE_STATUS_ACTIVE, - "applyfromwire: destination reserve not ACTIVE"); - sysio::check(it->reserve_chain_amount >= dst_amount, - "applyfromwire: insufficient destination reserve balance"); - - // Fee out of the user's escrowed input WIRE: only the post-fee remainder - // becomes destination-reserve liquidity. The TOTAL fee spreads across the - // DESTINATION reserve's owner accrual, the winning underwriter's accrual, and - // the rewards bucket. The full `wire_in` was escrowed in this contract at - // `swapfromwire` time; the ONLY part that leaves here is a configured - // `fee_emissions_share_bps` of the rewards pool. At the default-zero dial - // nothing leaves and custody balances as net -> Σwire, fee -> the accruals. - // The source is the depot's own WIRE — there is no source reserve — so only - // the DESTINATION reserve charges an owner fee here. - const auto fee = opp::amm::split_wire_fee(wire_in, uwrit_fee_bps(), FEE_UNDERWRITER_SHARE_BPS, - fee_emissions_share_bps(get_self()), - /*src_reserve_fee_bps*/ 0, it->owner_fee_bps); - // SEC-26 / WSA-042 settlement backstop — see applyswap. A zero post-fee WIRE - // leg would debit the destination reserve below while crediting zero WIRE. - // Reachable under valid configuration — the network fee at MAX_FEE_BPS (9999) - // plus this reserve's owner fee at MIN_OWNER_FEE_BPS (1) totals 100% — so - // this rejects a configured combination, not an impossible one. - sysio::check(fee.net > 0, "applyfromwire: zero post-fee WIRE would credit no destination liquidity"); - // WNS-02 settlement bound — see applyswap. The from-WIRE shape feeds the - // user's escrowed WIRE straight into the WIRE leg, so the curve output is - // `wire_to_token` of the post-fee remainder. - const uint64_t curve_out = opp::amm::wire_to_token(it->reserve_wire_amount, - it->reserve_chain_amount, - it->connector_weight_bps, - fee.net); - sysio::check(dst_amount <= curve_out, - "applyfromwire: destination amount exceeds the curve output for the post-fee WIRE"); - - tbl.modify(ram_payer, pk, [&](auto& row) { - add_capped_u64(row.reserve_wire_amount, fee.net); - row.reserve_chain_amount -= dst_amount; - accrue_owner_fee(row, fee.dst_reserve_share); - }); - - route_wire_fee(get_self(), fee, underwriter); -} - -void reserve::paywire(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - uint64_t src_amount, - sysio::name recipient, - uint64_t wire_out, - sysio::name underwriter) { - require_auth(UWRIT_ACCOUNT); - sysio::check(src_amount > 0 && wire_out > 0, "paywire: amounts must be positive"); - sysio::check(is_account(recipient), "paywire: recipient account does not exist"); - - reserves_t tbl(get_self()); - auto pk = make_key(src_chain_code, src_token_code, src_reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "paywire: source reserve not found"); - sysio::check(it->status == opp::types::RESERVE_STATUS_ACTIVE, - "paywire: source reserve not ACTIVE"); - - // Swap-to-WIRE: the recipient receives `wire_out`, and the fee is charged on - // the gross WIRE the source side produces — the same WIRE leg the quote - // priced. The source reserve gives up `wire_out + fee`. - const uint64_t w_gross = opp::amm::token_to_wire(it->reserve_chain_amount, - it->reserve_wire_amount, - it->connector_weight_bps, - src_amount); - sysio::check(w_gross > 0, "paywire: WIRE leg is zero"); - // The recipient is paid in WIRE — there is no destination reserve — so only - // the SOURCE reserve charges an owner fee here. - const auto fee = opp::amm::split_wire_fee(w_gross, uwrit_fee_bps(), FEE_UNDERWRITER_SHARE_BPS, - fee_emissions_share_bps(get_self()), - it->owner_fee_bps, /*dst_reserve_fee_bps*/ 0); - // WNS-02 settlement bound — see applyswap. For a WIRE destination the curve - // output IS the post-fee WIRE leg, so the payout may never exceed `fee.net`. - // `sysio.uwrit` passes exactly `fee.net` (its `swap_quote` returns the - // post-fee WIRE for a WIRE endpoint), so equality holds on the live path. - // `fee.net` now also nets out the source reserve owner's share, and - // `quote_swap` charges that share through this same `split_wire_fee`, so the - // quote and this bound still agree by construction. - sysio::check(wire_out <= fee.net, - "paywire: payout exceeds the post-fee WIRE the source leg produced"); - const uint64_t wire_leaving = wire_out + fee.fee; - sysio::check(it->reserve_wire_amount >= wire_leaving, - "paywire: insufficient source reserve WIRE for payout + fee"); - - tbl.modify(ram_payer, pk, [&](auto& row) { - add_capped_u64(row.reserve_chain_amount, src_amount); - row.reserve_wire_amount -= wire_leaving; - accrue_owner_fee(row, fee.src_reserve_share); - }); - - // The payout is credited, not pushed: this action is inlined from - // `sysio.uwrit::try_select_winner` inside the never-throw consensus dispatch chain, and a - // transfer would let `recipient`'s notify handler abort the whole delivery. - // - // The fee stays behind as three accruals — the source reserve's owner accrual (above), the - // underwriter accrual, and the rewards bucket — except any configured - // `fee_emissions_share_bps`, which `route_wire_fee` transfers to the treasury. - // - // Custody bookkeeping: `Σ reserve_wire_amount` drops by `wire_out + fee`, but only that - // emissions share actually leaves the contract's token balance. `wire_out` moves sideways - // into `Σ wireclaims.balance` and the rest of the fee into the accruals -- all still in - // custody, so the invariant balances. - credit_wire_claim(get_self(), recipient, wire_out); - route_wire_fee(get_self(), fee, underwriter); -} - -void reserve::refundwire(sysio::name recipient, - uint64_t wire_amount, - uint32_t revert_fee_bps) { - require_auth(UWRIT_ACCOUNT); - sysio::check(wire_amount > 0, "refundwire: amount must be positive"); - sysio::check(is_account(recipient), "refundwire: recipient account does not exist"); - - // Caller-fault revert fee, routed through the same path as a settlement fee. - // A revert has NO winning underwriter — no collateral was locked for a swap - // that never settled — so the underwriter share is zero and the whole revert - // fee becomes the rewards POOL. `fee_emissions_share_bps` then splits that - // pool exactly as it does a settlement fee's: the whole revert fee lands in - // the rewards bucket only under the default zero dial; a configured dial - // diverts that share to the emissions treasury. Zero bps (no-fault refund) - // makes both the split and the routing no-ops. - // - // Unlike `applyswap` / `applyfromwire`, the backstop below really IS - // unreachable here, and for a reason specific to this path: a refund charges - // NO reserve owner fee (the trailing rates are left at 0), so the total is - // the network fee alone, which `sysio.uwrit::setconfig` caps at MAX_FEE_BPS - // (9999). Floor division then leaves `net >= 1` for any positive amount. - // There is no second rate to stack on top and reach 100%. It must hold: this - // action is inlined from the never-throw `drainfwq` drain. - const auto fee = opp::amm::split_wire_fee(wire_amount, revert_fee_bps, - /*underwriter_share_bps*/ 0, - fee_emissions_share_bps(get_self())); - sysio::check(fee.net > 0, "refundwire: revert fee must be below 100%"); - - // Credited, not pushed. This runs inline from the never-throw `sysio.uwrit::drainfwq` drain, - // which itself runs inline from `sysio.epoch::advance`: a transfer here would let the refund - // recipient's notify handler abort `advance`, and because the drain's `q.erase()` rolls back - // with the transaction the offending queue row would survive and re-block every later epoch. - credit_wire_claim(get_self(), recipient, fee.net); - route_wire_fee(get_self(), fee, /*underwriter*/ name{}); -} - -void reserve::setconfig(uint32_t fee_emissions_share_bps) { - require_auth(get_self()); - sysio::check(fee_emissions_share_bps <= FEE_SPLIT_TOTAL_BPS, - "setconfig: fee_emissions_share_bps must be <= 10000 (100% of the rewards pool)"); - - reservcfg_t cfg(get_self()); - auto row = cfg.get_or_default(reserve_config{}); - row.fee_emissions_share_bps = fee_emissions_share_bps; - cfg.set(row, ram_payer); -} - -void reserve::setrsvfee(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code, - uint32_t owner_fee_bps) { - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "setrsvfee: reserve not found"); - sysio::check(it->status == opp::types::RESERVE_STATUS_ACTIVE, - "setrsvfee: reserve is not ACTIVE"); - // A bootstrap-seeded public reserve has no owner, so nobody can authorize a - // fee on it — which is exactly the guard that stops WIRE accruing with no - // claimant. `require_auth(name{})` would abort with a confusing message, so - // say why first. - sysio::check(it->owner.value != 0, "setrsvfee: reserve has no owner"); - require_auth(it->owner); - - // 0 disables the fee; anything else must clear the dust floor and stay below - // the 99% ceiling that keeps a positive remainder on the WIRE leg. - sysio::check(owner_fee_bps == 0 || - (owner_fee_bps >= MIN_OWNER_FEE_BPS && owner_fee_bps <= MAX_OWNER_FEE_BPS), - "setrsvfee: owner_fee_bps must be 0 or within [1, 9900]"); - - tbl.modify(ram_payer, pk, [&](auto& row) { row.owner_fee_bps = owner_fee_bps; }); -} - -uint64_t reserve::rsvfeebal(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code) { - reserves_t tbl(get_self()); - auto it = tbl.find(make_key(chain_code, token_code, reserve_code)); - return it == tbl.end() ? 0 : it->owner_fee_accrued; -} - -void reserve::claimrsvfee(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code) { - reserves_t tbl(get_self()); - auto pk = make_key(chain_code, token_code, reserve_code); - auto it = tbl.find(pk); - sysio::check(it != tbl.end(), "claimrsvfee: reserve not found"); - sysio::check(it->owner.value != 0, "claimrsvfee: reserve has no owner"); - require_auth(it->owner); - - const uint64_t amount = it->owner_fee_accrued; - const name owner = it->owner; - sysio::check(amount > 0, "claimrsvfee: no unclaimed balance"); - - // Zero the accounting balance first, then move the backing WIRE out of - // custody. `owner_fee_lifetime` is a cumulative audit total — never reduced. - tbl.modify(ram_payer, pk, [&](auto& row) { row.owner_fee_accrued = 0; }); - - action( - permission_level{get_self(), "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(get_self(), owner, - asset(static_cast(amount), opp::wire::asset_symbol), - std::string("sysio.reserv::reserve owner fee claim")) - ).send(); -} - -uint64_t reserve::uwfeebal(sysio::name underwriter) { - uwfees_t uwf(get_self()); - auto it = uwf.find(uw_fee_key{underwriter}); - return it == uwf.end() ? 0 : it->balance; -} - -void reserve::claimuwfee(sysio::name underwriter) { - // Only the earner may sweep their own accrual. There is no depot-side push: - // an underwriter claims when they choose, and a dormant one costs the chain - // nothing. - require_auth(underwriter); - - uwfees_t uwf(get_self()); - uw_fee_key key{underwriter}; - auto it = uwf.find(key); - sysio::check(it != uwf.end(), "claimuwfee: no accrued swap fees for this underwriter"); - - const uint64_t amount = it->balance; - sysio::check(amount > 0, "claimuwfee: no unclaimed balance"); - - // Zero the accounting balance first, then move the backing WIRE out of - // custody. The row is retained at zero so the lifetime audit totals survive. - uwf.modify(ram_payer, key, [&](auto& row) { - row.balance = 0; - add_capped_u64(row.lifetime_claimed, amount); - }); - - action( - permission_level{get_self(), "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(get_self(), underwriter, - asset(static_cast(amount), opp::wire::asset_symbol), - std::string("sysio.reserv::underwriter swap-fee claim")) - ).send(); -} - -void reserve::claimwire(sysio::name account) { - require_auth(account); - - reserve::wireclaims_t claims(get_self()); - const auto pk = reserve::wireclaim_key{account.value}; - - // Enforce the retention deadline HERE, not just in the sweep. The sweep is bounded and - // best-effort, so a row can outlive its expiry by an arbitrary number of epochs while it waits - // its turn; without this check the deadline would mean "swept eventually" rather than - // "claimable until". Refusing tells the caller the row is forfeit instead of paying a claim the - // retention policy already extinguished, and the sweep still reclaims the WIRE. - auto it = claims.find(pk); - if (it != claims.end() && it->expires_at_sec != 0) { - const uint32_t now_sec = static_cast(current_time_point().sec_since_epoch()); - sysio::check(now_sec < it->expires_at_sec, - "claimwire: this claim expired and is pending sweep to the treasury"); - } - - sysio::opp::claimable::pay_out( - claims, pk, get_self(), TOKEN_ACCOUNT, - account, opp::wire::asset_symbol, std::string("sysio.reserv::claimwire payout"), - "no claimable WIRE for this account"); -} - -void reserve::sweepclaims(uint32_t max_rows) { - // Two valid callers, mirroring `sysio.uwrit::chklocks`: - // * sysio.epoch::advance — inlined every epoch with MAX_CLAIM_SWEEP_PER_EPOCH. - // * sysio.reserv — manual drain with a caller-chosen budget. - sysio::check(has_auth(EPOCH_ACCOUNT) || has_auth(get_self()), - "sweepclaims requires sysio.epoch or sysio.reserv authority"); - if (max_rows == 0) return; - - sweep_expired_wire_claims(get_self(), - static_cast(current_time_point().sec_since_epoch()), - max_rows); -} - -} // namespace sysio diff --git a/contracts/sysio.reserv/sysio.reserv.abi b/contracts/sysio.reserv/sysio.reserv.abi deleted file mode 100644 index 1336ae2214..0000000000 --- a/contracts/sysio.reserv/sysio.reserv.abi +++ /dev/null @@ -1,909 +0,0 @@ -{ - "____comment": "This file was generated with sysio-abigen. DO NOT EDIT ", - "version": "sysio::abi/1.2", - "types": [], - "structs": [ - { - "name": "ChainAddress", - "base": "", - "fields": [ - { - "name": "kind", - "type": "ChainKind" - }, - { - "name": "address", - "type": "bytes" - } - ] - }, - { - "name": "applyfromwire", - "base": "", - "fields": [ - { - "name": "dst_chain_code", - "type": "slug_name" - }, - { - "name": "dst_token_code", - "type": "slug_name" - }, - { - "name": "dst_reserve_code", - "type": "slug_name" - }, - { - "name": "wire_in", - "type": "uint64" - }, - { - "name": "dst_amount", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "applyswap", - "base": "", - "fields": [ - { - "name": "src_chain_code", - "type": "slug_name" - }, - { - "name": "src_token_code", - "type": "slug_name" - }, - { - "name": "src_reserve_code", - "type": "slug_name" - }, - { - "name": "src_amount", - "type": "uint64" - }, - { - "name": "dst_chain_code", - "type": "slug_name" - }, - { - "name": "dst_token_code", - "type": "slug_name" - }, - { - "name": "dst_reserve_code", - "type": "slug_name" - }, - { - "name": "dst_amount", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "claimrsvfee", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - } - ] - }, - { - "name": "claimuwfee", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "claimwire", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - } - ] - }, - { - "name": "debit", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - } - ] - }, - { - "name": "drainrewards", - "base": "", - "fields": [ - { - "name": "amount", - "type": "int64" - } - ] - }, - { - "name": "matchreserve", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "matcher", - "type": "name" - }, - { - "name": "wire_amount", - "type": "uint64" - } - ] - }, - { - "name": "oncnclrsv", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "creator_chain_kind", - "type": "ChainKind" - }, - { - "name": "creator_chain_addr", - "type": "bytes" - } - ] - }, - { - "name": "oncrtreserve", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "description", - "type": "string" - }, - { - "name": "external_token_amount", - "type": "uint64" - }, - { - "name": "requested_wire_amount", - "type": "uint64" - }, - { - "name": "source_token_precision", - "type": "uint32" - }, - { - "name": "connector_weight_bps", - "type": "uint32" - }, - { - "name": "creator_chain_kind", - "type": "ChainKind" - }, - { - "name": "creator_chain_addr", - "type": "bytes" - }, - { - "name": "is_private", - "type": "bool" - }, - { - "name": "creator_pub_key", - "type": "bytes" - } - ] - }, - { - "name": "paywire", - "base": "", - "fields": [ - { - "name": "src_chain_code", - "type": "slug_name" - }, - { - "name": "src_token_code", - "type": "slug_name" - }, - { - "name": "src_reserve_code", - "type": "slug_name" - }, - { - "name": "src_amount", - "type": "uint64" - }, - { - "name": "recipient", - "type": "name" - }, - { - "name": "wire_out", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "refundwire", - "base": "", - "fields": [ - { - "name": "recipient", - "type": "name" - }, - { - "name": "wire_amount", - "type": "uint64" - }, - { - "name": "revert_fee_bps", - "type": "uint32" - } - ] - }, - { - "name": "regreserve", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "description", - "type": "string" - }, - { - "name": "initial_chain_amount", - "type": "uint64" - }, - { - "name": "initial_wire_amount", - "type": "uint64" - }, - { - "name": "source_token_precision", - "type": "uint32" - }, - { - "name": "connector_weight_bps", - "type": "uint32" - }, - { - "name": "is_private", - "type": "bool" - }, - { - "name": "owner", - "type": "name" - } - ] - }, - { - "name": "reserve_config", - "base": "", - "fields": [ - { - "name": "fee_emissions_share_bps", - "type": "uint32" - } - ] - }, - { - "name": "reserve_key", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - } - ] - }, - { - "name": "reserve_row", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "name", - "type": "string" - }, - { - "name": "description", - "type": "string" - }, - { - "name": "status", - "type": "ReserveStatus" - }, - { - "name": "reserve_chain_amount", - "type": "uint64" - }, - { - "name": "reserve_wire_amount", - "type": "uint64" - }, - { - "name": "source_token_precision", - "type": "uint32" - }, - { - "name": "connector_weight_bps", - "type": "uint32" - }, - { - "name": "creator_addr", - "type": "ChainAddress" - }, - { - "name": "requested_wire_amount", - "type": "uint64" - }, - { - "name": "external_token_amount", - "type": "uint64" - }, - { - "name": "registered_at_ms", - "type": "uint64" - }, - { - "name": "activated_at_ms", - "type": "uint64" - }, - { - "name": "cancelled_at_ms", - "type": "uint64" - }, - { - "name": "is_private", - "type": "bool" - }, - { - "name": "owner", - "type": "name" - }, - { - "name": "creator_pub_key", - "type": "bytes" - }, - { - "name": "owner_fee_bps", - "type": "uint32" - }, - { - "name": "owner_fee_accrued", - "type": "uint64" - }, - { - "name": "owner_fee_lifetime", - "type": "uint64" - } - ] - }, - { - "name": "rewardbal", - "base": "", - "fields": [] - }, - { - "name": "rewards_bucket", - "base": "", - "fields": [ - { - "name": "balance", - "type": "uint64" - }, - { - "name": "lifetime_accrued", - "type": "uint64" - } - ] - }, - { - "name": "rsvfeebal", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - } - ] - }, - { - "name": "setconfig", - "base": "", - "fields": [ - { - "name": "fee_emissions_share_bps", - "type": "uint32" - } - ] - }, - { - "name": "setrsvfee", - "base": "", - "fields": [ - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "owner_fee_bps", - "type": "uint32" - } - ] - }, - { - "name": "swapquote", - "base": "", - "fields": [ - { - "name": "from_chain_code", - "type": "slug_name" - }, - { - "name": "from_token_code", - "type": "slug_name" - }, - { - "name": "from_reserve_code", - "type": "slug_name" - }, - { - "name": "from_amount", - "type": "uint64" - }, - { - "name": "to_chain_code", - "type": "slug_name" - }, - { - "name": "to_token_code", - "type": "slug_name" - }, - { - "name": "to_reserve_code", - "type": "slug_name" - } - ] - }, - { - "name": "sweepclaims", - "base": "", - "fields": [ - { - "name": "max_rows", - "type": "uint32" - } - ] - }, - { - "name": "uw_fee_key", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "uw_fee_row", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - }, - { - "name": "balance", - "type": "uint64" - }, - { - "name": "lifetime_accrued", - "type": "uint64" - }, - { - "name": "lifetime_claimed", - "type": "uint64" - } - ] - }, - { - "name": "uwfeebal", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "wire_claim", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "balance", - "type": "uint64" - }, - { - "name": "expires_at_sec", - "type": "uint32" - } - ] - }, - { - "name": "wireclaim_key", - "base": "", - "fields": [ - { - "name": "account", - "type": "uint64" - } - ] - } - ], - "actions": [ - { - "name": "applyfromwire", - "type": "applyfromwire", - "ricardian_contract": "" - }, - { - "name": "applyswap", - "type": "applyswap", - "ricardian_contract": "" - }, - { - "name": "claimrsvfee", - "type": "claimrsvfee", - "ricardian_contract": "" - }, - { - "name": "claimuwfee", - "type": "claimuwfee", - "ricardian_contract": "" - }, - { - "name": "claimwire", - "type": "claimwire", - "ricardian_contract": "" - }, - { - "name": "debit", - "type": "debit", - "ricardian_contract": "" - }, - { - "name": "drainrewards", - "type": "drainrewards", - "ricardian_contract": "" - }, - { - "name": "matchreserve", - "type": "matchreserve", - "ricardian_contract": "" - }, - { - "name": "oncnclrsv", - "type": "oncnclrsv", - "ricardian_contract": "" - }, - { - "name": "oncrtreserve", - "type": "oncrtreserve", - "ricardian_contract": "" - }, - { - "name": "paywire", - "type": "paywire", - "ricardian_contract": "" - }, - { - "name": "refundwire", - "type": "refundwire", - "ricardian_contract": "" - }, - { - "name": "regreserve", - "type": "regreserve", - "ricardian_contract": "" - }, - { - "name": "rewardbal", - "type": "rewardbal", - "ricardian_contract": "" - }, - { - "name": "rsvfeebal", - "type": "rsvfeebal", - "ricardian_contract": "" - }, - { - "name": "setconfig", - "type": "setconfig", - "ricardian_contract": "" - }, - { - "name": "setrsvfee", - "type": "setrsvfee", - "ricardian_contract": "" - }, - { - "name": "swapquote", - "type": "swapquote", - "ricardian_contract": "" - }, - { - "name": "sweepclaims", - "type": "sweepclaims", - "ricardian_contract": "" - }, - { - "name": "uwfeebal", - "type": "uwfeebal", - "ricardian_contract": "" - } - ], - "tables": [ - { - "name": "reservcfg", - "type": "reserve_config", - "index_type": "i64", - "key_names": ["name"], - "key_types": ["name"], - "table_id": 11698 - }, - { - "name": "reserves", - "type": "reserve_row", - "index_type": "i64", - "key_names": ["chain_code","token_code","reserve_code"], - "key_types": ["slug_name","slug_name","slug_name"], - "table_id": 52863, - "secondary_indexes": [ - { - "name": "bychaintok", - "key_type": "uint128", - "table_id": 35544 - }, - { - "name": "bystatus", - "key_type": "uint64", - "table_id": 46081 - } - ] - }, - { - "name": "rewardbkt", - "type": "rewards_bucket", - "index_type": "i64", - "key_names": ["name"], - "key_types": ["name"], - "table_id": 15326 - }, - { - "name": "uwfees", - "type": "uw_fee_row", - "index_type": "i64", - "key_names": ["underwriter"], - "key_types": ["name"], - "table_id": 43959 - }, - { - "name": "wireclaims", - "type": "wire_claim", - "index_type": "i64", - "key_names": ["account"], - "key_types": ["uint64"], - "table_id": 22082, - "secondary_indexes": [ - { - "name": "byexpiry", - "key_type": "uint128", - "table_id": 59144 - } - ] - } - ], - "ricardian_clauses": [], - "variants": [], - "action_results": [ - { - "name": "rewardbal", - "result_type": "uint64" - }, - { - "name": "rsvfeebal", - "result_type": "uint64" - }, - { - "name": "swapquote", - "result_type": "uint64" - }, - { - "name": "uwfeebal", - "result_type": "uint64" - } - ], - "enums": [ - { - "name": "ChainKind", - "type": "int32", - "values": [ - { - "name": "CHAIN_KIND_UNKNOWN", - "value": 0 - }, - { - "name": "CHAIN_KIND_WIRE", - "value": 1 - }, - { - "name": "CHAIN_KIND_EVM", - "value": 2 - }, - { - "name": "CHAIN_KIND_SVM", - "value": 3 - } - ] - }, - { - "name": "ReserveStatus", - "type": "int32", - "values": [ - { - "name": "RESERVE_STATUS_UNKNOWN", - "value": 0 - }, - { - "name": "RESERVE_STATUS_PENDING", - "value": 1 - }, - { - "name": "RESERVE_STATUS_ACTIVE", - "value": 2 - }, - { - "name": "RESERVE_STATUS_CANCELLED", - "value": 3 - } - ] - } - ] -} \ No newline at end of file diff --git a/contracts/sysio.reserv/sysio.reserv.wasm b/contracts/sysio.reserv/sysio.reserv.wasm deleted file mode 100755 index 36074312f93e208ad600480eb466ed2c9d95c4d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89303 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 diff --git a/contracts/sysio.roa/sysio.roa.wasm b/contracts/sysio.roa/sysio.roa.wasm index 04e635895f34b990bbd9f74f390193e1fe9719b3..dc99f7e7333202073fd6a6399b7e8385eee53066 100755 GIT binary patch literal 76719 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 K&{+71vws71()yDC literal 53726 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 diff --git a/contracts/sysio.swap/sysio.swap.wasm b/contracts/sysio.swap/sysio.swap.wasm index 32220dab95887b4bfcad69b07511da5584f49690..0fdeb2efb3817bb3c8a1870a6e3c478d93f1410a 100755 GIT binary patch literal 59215 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

5frFj8IOix17d91&ZjbDuNlp42uw0 z9+8i#=3Wu?4(Bm`{Spg!!adQ=8u$QtIi!nPRZ7;}5yBq*z7-;E)q>8&6#<$x<3?#WUS#Ar2OW%HAZyDjFD~aW%UTx zmm|4ceSoLfsdE`!U=Tuwp|k>gb)td%f@HdtG(J`i_oRZDqKxgLX`U{bt7` zC}>MXw^=1Fd(KhD86%D{2(_|G*%V;ro9d(k&{l^fz&nsv*TK{dORyk(jzZvASJ&ti zUj|huPgzTBm)sg%e_ks|gD4tSl4_1eVis^#IlpaVk*zTUU1t%Q1>|I~DN+I~2aIPc zNo(|VC2a=I&RW(1Blg+vGMQLO8q`V>r8z`ED@h#g)N3V4{2_L7?8EIT7J@r0q?q$n zEYzEQ6r_R~g8HU1a{zPngXwM)fhUx1t9QgspF-Wq*RQQsgW})s7>vMJEc#;iGlUQ& zxp&`^WeizzJ|)1Vpfud%j?c{S5=Kv>cw~wRx!xR;dY5BKRbf=!!p-y31L^sy2Z{U+ zq$~hkZwvyx=|H7s&p?dxniF6Ttl06yM27D@_+3xl(QTlfXN*3my3be5*=@9eRI8lb zgCxq)t$(?*dr;2qNLI!P`GFr%65)$zjpV`}~}4wx_`?X1@8lH<0h;a~(Qi4xyWucUC6T zB{`QBZzyq3$Bn7kUO=i9Cs@3zB=akummG4L;Ay6_w8l?vx=Q^U!4p!i6{L!$Q$9xZ zc3@msc>jn0^MhX*jt{nJj!sRN?WZSM6{PjH7^eE0l z@WjZ+#b>Y2`PXOt^-L$*8OxSK0W3fXEOF5ZZmIfCQk*_bpDL+s{jo8$sP8=#dt~3C z?%5@EbTM+(AeVA3bLlG(gV}7Lp9pi~G;oIA}1o4U=9kUz! z!0E_-t3FY`-=^6tfA2(S# zK)}HYOS9p%DCBg;*Mc4k1B?$uND=IE1B}6>KAa~7UgLnW5Dy$(c{-G6kQiqTBR2qA^OP#vB(*D*D81_(wRFq$W5{F8c&TeUCb>)d1X zjv;J>n4m;25I}L`tDhgn4bZFiV}b|c?c%&@mOZZrQd2!hL1rFE*+ zt4*P-M?A=>a?xN#5vCK&s%{PkA1d0t6XsMUxh#|PaNu^w^Nh_|v0` zA&$+KYd1-CI5NFvg(ln9HPTRB#epVnhJB^Nj7e-9z``7_rk`8I-`gAieIMS%MUIR2 z8&Rb13uyU_i0Ynt%t`u}L>?4HfBIZ3=qbFudB9oW5@&drR>dy=?>)V=U4L8PR7H{(hmy%50+%PtKQ=KkYj!?->dppBs7xfk_ z-aFlR+3v;*3QFT;fl>@FsjR(vJ{~qfuPjb8cu{K(oF>z62{ZopQR#lbjo1iuq1`0u zeYx=}&{JmFv~v<_%^jPH(wgy!Q0|~h>?NyC>BOG;&y*aSc5K$I?7@!2lp7K)5^b1h zOk6FG0uAEr-N+VsE*n}UeS^b%^oC!RFhOD49r@U6WF^Y!ATv0G6&r~8f-B~kdZ-V$ zN&}Sj1z2skvoFAs$$ibQn##2zQx1M;uo|MiayEXeDu0H#ICNni!BRUoP_({wS2EQ7 zsr>rdg>u)ml!6I6nH~g;?gA=`k*crnau?kkSaj<3L40#yA-vZIrJDnNTZOmfn*$v* z$X(^;fD;CFSG_raKj@A%3l~B@bQgzY6E6fvw2U#F5lEgpH$!8H%b~X7ogAp4tm@iL zaBFT=Q@!I!lL;#eUA96sh1*fo41@}!ock`6aJvyHM!OM1Mn*bYIYpiZ=K5dBRhwuo z(u@#EF@L3ayJ}FP|EBc2zedQH&qUGd0v+iCu{C|6b_HlZ7u<{N(Ps{#`6Qlg!aONo zSOKjV`Ak_^{&Vz2(P)B1WQ9DkxKiv(ND^eFNu0GxrGx=gv8;{_c&&Z7V2)sj7W|_P z`352a2cIq#mC2C#qkIJbD;jDlK(}d^kJ<>{p#Hypr}^3@>Bt%ShqQjQkDit9CewM08oWIWudFOeMWmKfpr@8S-yt)!Dpce2t*Q~t zaR%(_T}6l9v@OD_h$5?Cn}TxPLE=$UuDo)}6|U`nxIfr?`bs#MC8 zJP8+dj!Mvmtw4sgAEDTr`(mr=a<4+s!g1A+Fv`h_F^qL9E+@Vy2DzkKzy+jI^Lq<9v0kE zw+W_i=c=Fu^@z<>L*SpK;{3BN#syCq!57VYOE&1z%@hYoba{B?nBtt#i^Kv;IR75@ zR_>{DjzIK5mj(6|ECHVt*hbu6V4GczZD16!4Ll{*UUbYR4#EOdctDdr>L9_U`eqiG zz-!uEq3ACJ#8tMKiXb}CIozQ@w}anGhuJtbxWjA*JIuC_eR$ok%M``fvT@tTtIiZV z+mG9XbvHxsR#2q2Gu!U6iwCYas;}QlPjN<|%yJ`pN@r@w+FP~E14dwhT(+pp7B|CR zmCOvkdznYOHQ-^U?wN$-V5v^q3m%4pW$25&-B{24xA}lsk{Do);PQY;ZVHp3 z_@KoUF%(>uY&VCM-)q50&*KvpOPHo^pZx#*wA(%)fQeNhT|vU!E{ED&Z%dND@+sv3 zERjo)yDnr<84oGhL7gXCRtIL5riz!mda>s61;AxeX;*9^1kjQ$$b`6&0$VoNilg_e zjoyD*#i3_Mw*BMy{r~Cs6<2N1d8>$xZ^yu044W`x<~blJo^2XDE`)>{a^(wOx*QK% zLk(u%{f<9@i}O+Un>M0Mjj*@eeY|VcX!9c+@u7j?MS>l8o{d1o59ys7mp-E;x<44* zCKxWyD3i4Zg`;JH!+pSDxhZ@Aq zx8^06g}PWv0eTtoCf0Q%j%O&xLk23ZGU+WrFXEenuedrb=*Z__FeAX9&5zl18 z9(tQ*lwm0C+41Y`;Hh9$%A6$Y7RQMr8f9)Q3SI}Q(ssE~K%d+SvN7lz`)Cbx7c-0} zBN)^zyIsmz9*7^47aQG!OQxUAm$9C+@6-u9svJf=bZ^&csMPLj#A+_)qI=rU5OwPd zLmlG;WEBt!&WZ^2L%Ws_ZLPSEM6YS=?1QakTUt1iFD>*9wh@cp`jW84N>)E?`88f= zsOo!3-=-Bxi|e5y3ySYyg_7ZKO~=~p$UWG|!s(tZZr0=#3~6~AukC|L7pm~ferBs< z`una<9D-;}Y;oKPt9xIHOr$D_L}ZXIS&hBqq0Hu@JUp0-LodL*jMDyw72-)e+9j4U zSBWpFF-}KzfSI0U@mu7x2xO`AkNC2+euY4I!KWhk2qiR-kP9>@T0xY8pG3s`CM`KK z+9HH=qgisdtcvE%_fZ%~8de3##JG}VVgSm#XFPe2o8trF(xIg2OnWG#FDw&m?@Uf_ z?e7U$Fgf*~bpj%*S(gmm`1=`mA|deWB44xo@5bt@W~H!0hcuFGc{y!n=@WIR6`{OT zA1POeFnobhE*r)$1%%l)(Rf6(=sBtI0?(bq`<;b$w~CXaeU+Xl4hc69#Pdluk(C74 zNP-1)$m107fC}CQQ9CO5vW%WdItqi%#(OV~$uE`Mn!KVdENqzxy^is9DtpUBcGo$o zmOiz|4`I;G~V#ri5!M=fFuWGb@kEwVm?M7!LyhH(6$jm@-HSoyuCGkI|cMie4*^ z5F^%Gan>mf!~9v$cDhsHVIw{{mS_ZO#82J|y@}{j-axcDx>l$QG7TCJvp>!>6Ihlx{Ofo?U&$x7udEN%-H$wZur`r{)0ASY9qR~%%ZMk$)aGXh) z+l&Fu*x<$S0JaPK!tdL}K>O@d4JE8uJ7xf~u^i^)LF86V^%7&mEsN^GEsMH4wgzYm zeb_i?N0F5L$scyUL7~O8yFFsObqWEiZ+XS|mw-cVrb3l0kJ)}6_O{Ze65q5>UvXdk z4@<@E4eB56PW|+aZCooh>k-?CjZAFg+BA5BmTzUYzvu(^DZS#zjbaG1L8my&KB0{1 zwZ3Jlf#Jqq(pL-c5|%Qo^o!114Tp=#)B(;frnJZw>U*oMF!v0Am(~piz>9$-3tBa% z=-2&F!c)?yv-}G29q;)~G#`#SqskrbVoScZ3Ot#X4xDUa?r3eAHE9?(=Ac#9nK6ul zcVaD8=VxyY)@D>-w@k3=uvyOejYq5cGQ2pRT@fo7)HdFfZEe~bb?eflR!Go8yuDEM z>T_(VS_|LZy|bM>&#M?Bp$ji=50DwMtc~M7CcZ$!pD`KKf4d-kR^jRI6-h?%(qxBB zEhZM_x0tv4xo7e)0YeTuQ9E7qEAM%it`Eb*qd_wiveLIsSm;2Iemamm3^=noUHqCI z>Ci-(g#Xo@Z~UQ^yT_1gF{n`Vh~f6d?H*%oSMwLEi(}!n9r&-?0qVLfYRak$>-|lf-%0FpX`Pa*DTwgY+DxNQ>3OR6<+B6p7h$m|7@% zXPCO68YBP!@N;jg!U+O0vmE*eOGzr59BT=mF##g0IFu%tQLX0C!l?V>uz098@<%c6 z1OsK`TU~DT>yJ9Z4&oK^rW5R66}(hwR*{HiBTKS$l@K#fBV^}Fk>P8*Oi8KS6(u2e zp`Tys>SqQ@*1Mi6)@<9(;HCw?-9ScTS8o{4*+Q7-5w&bKy$L|91G9`VF6@0)RR%_X z?c!|ZE+D#;%U|oLsFGt|9g19bR(Lw&sN^g()U;nyWwX$;l z7~ij{AY26aO@fS^xm@@dpEX&D@#F1`5X?R9ICQbl+9&I{ttSzV7&g z9%txxjz6HZ|E}s_()C@{PpCfzHDAo5Nsjw)D}U#$qpn9B6X7<_AOAIv*ivU@onx9x z?(i+hbH*|#!i&)=sx|7H_*DQDfbstW2LvLW5;?cyMPOm=}wsyk%LmAR4e7aUOMFIJWo87*If9 zM;I)Z(vN>SXhz0TD~>Su3%%%dghAw%beU}GIKlMea&WQ&V5dL&2OYJ^JlcIS!Foiv zlBG2WR}uC6JL`h~*n)LjJ<}NCEcbgim-KRu+lZ&4CzwSQVV(&R@Q^dEg_MR%G@B6Kc_+1|d4$=?m z1qYw&>~5ii(IYEORIC>*F5bu4ftX zN9mEPo@jQmj#xf@iC14#G?W*1UfcI`8L@v}_$-tTLw1fM+T-wHR+fO4@#_vM)q`+h z)YYh7^^K^E1(g$CvnsNwe`KFx!_dN6MEI@6^}2}PC|xjL026xi@9GlR0K*z%I$$C~ z>tS$tR>G_1FS`%DN`{cZw5rv@DatNowkeUqQBiTe){~V*3yBerbHEH=I(1BG zLeF;i0jty5UVO^O)AtKet1*C7u`fgwJ4cWF^eKZ@VmAX7-*@%W(2}`jibfzK&l?*7 zQo9iytWM$vot~ho(O3`B-@j8 z|CURxQwpe-!V!lepG|;yd*$~m^2V5#@NIL6B38UUl7Q2>K@tj*Xd@CP#B@sDn8AWT z@gFGvIUh@)5xm8!E2?tpu~C6!l%O=Bi2$WeEpTacf*2dlG=YqCZB=YIVM96hnGWkT zQ3;#|iVfggRPr4-G13Ti3KJ60SXpM}U<8?3Vj@ymWe8Q2rEG&Lvuyx9DIQjJ=xb@2 zpE~j7tbQr*B|(rQ=*-7Hr01;UKHt@av#wm0PhEfFi~oUg4nyakS$hB1zwu4V4O)w1 zlvB%l{Pk{sJ>ai1{@V7}d4HYu*WLblkH608>L{`N(Q|L$B)~Kn4I2%T4c89!nh1!k z_txvU=-25UrvCA;UjOlCB}lPV>JQZp%(Z8evE93;4@}J`d#2lS$<*|G(mpsoHy_+R zHomXDCz+p4#`f)--aR(oPG-jLY0m|F#_t>7(@ySsAbD4Nb~@NSHl;%Ivtv_pcT*h& z=Gy!24pgvr&6Dj1sB^YGw&wxY)?6?%Hm;JB?WvVuyh9-7pzR&Iubs>t*uB?f4#ubM z8{0R&C%N~)*wp;^`~!jN7C3j)r9Hv^19Jt)W~S%H=f|la&%LK@X;g-@UjLdwQ0FrD z!2Y|Y_a!s4?cL*Zw>U_qX>?Cws@{lKs=O z?PQ)D$&Ihx3X0~z*ByJu=Q`-&-oZDU%soKeH_Y8XHWOqJB?j?bpxVP4Oid?ujqMwo z+TBj3@0w`u&bs!%czfR-DDuGcfdc6aNC0RjyZ4Qa?+>Q$YtQbP9lL)jt7HX&TrGEl ztMRFOI%w`xGt+jjl*KYz1uLlc+UcpWd7()?gXry<`SzY*`u-_M*M|q@r+40U05;ff zthcJyo&Y)B9ZQbQ&9&!2vZxAjqL6%AU z*FgaiYm!dS?H!v%s&yKgWu)E*48>A8Hrog1cUn_B#~JdT%kq)9e|&0Z)`$FUkp~Nq zkL}rWNuX&7^tboy^mxnV3k8u;U0t~@$P;r&U*~a&oSS8^Ca-+mJKuW8Ti^J)w_I0* z(sky#=^52Hwl9&GdL`1TS4Zd>n3@A;hUT$c0{I~o+}(z056thKo<+{~8`g+4_zl`g zlJB0La^zbvj5B8I%A8RXX=q}E!gr6~b6{5dVzv1ZGzPESlT6=jBT{N37@dYKR)~h2 zXtzy)PP*;{O-FWg*z#3de)LtZ4yM}oXS66tea`|TdD5$rf7a0oH*QY8C%G|jthUp$ zTE;eTP^Zp5d5X-_qZT**O_(!s=Mhs-nncF$^b~4Pyp2L~oaBc?r-%7KvGF$72BCWl zcNv7LxI=;zNCL~_v|!mszN!Jaz8gejt=oUZ?#f}rq&PFTkyXJZQ>ah&{pe8?2GxmJ9m%G z?7VAcF6lzZ?tSAkJCWf~wJY6$KR>>ElI~>s#YP0WFQ<0KtHQx-`yT8yWa1u41J@}T zcGo2{VwQF?dtfS=1~^y+ESq#8-~EhZlz_Kql0EHtr1o6m`)C#o)Wf`>N^jd46<@I8 zdj8s|Urq;Vm*PqS6r5}ps5U=6o$Qh%bA8d8r zfm*|zipI+ou>1|Ly78ZX@2kh|+P$ZJ_dR>ZCnopppPHVzm*I2ZzWWb8@UCG0*gZ(D1K8OT zb+UU_nl>E?cH@t3y|JevzATCy@9x|^y{8>aPXXeA89)uvN6rQHqqW^)9@j?=T8A|7 zP$@H~(B1Bd*=X$WtyySSKOxYVTz7veVgZHz%6NbUHf!z)ZU{33&mA zMs7NEOrtz06ON#9?!eu5kMACrS!E_@a1X*J2txk!FLIY4_ScfXmi_e~FW(*vRD88n zf35ke=A8OB=q@&A)$Ndf9`@HY{<_v*8~(b^U)THV75=)xUti|0S296(-}#{a7WaIW lPu}RSFZb80{dJSSUgNLd<*(oEudnb|9BlQ{!j>TT{{Wm6310vJ literal 49661 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 eWR>8b*}Z-H=@k6;dP(9;U`8+^>&?s{8%JQb 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

xAXsMm*qN~jI6m;lzO{(!<(@Nb{<7O_ojIQ$k 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

W*NA2Wch(x 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 LzbAM4TV(7Xm#J(- literal 105651 zcmd4451d|CS?|05ynklqooRN`KZH!1?sqpSlQz+y(B@P;WX}a!1T|p9+Uq@Sr77*C zlr#yoX#NZkFoy%@1SlG?>VSm?Y$Zo35RS4XW9&*?cgS5TsdOYish zJZtaweP<>~0ng{&Y4h&2*Is+AXFboeo`37lj&9xc`Z$WBcsBi>TcYTecz<+DvY(%H zzkcI=o{IP162<#?N}{!YAJ6(z`)+9{k7w~dE4g2FkZPzT*QQ=jLbXy)vY&c+XCF;8 zx$NUM*{1?SR+69SmUc+0B!4^{Q7igi<*C!IDHrZYTbVWm_)l=C0bNz|zFUT=!yi@e z3J^iiws%!aYBgHTYR}p=Km||B;Sr>&m9Fr{aDYc|kJ6q2rS^q4Rc=6V(WB5=<fWr&I=O$S7TO}5yRl5G}zx~AX;y{sAEu&Mae%jigerE_m1w|cF!B{+_~-MTi>|* zj;Num*6X*ue%H3$QM0;v?T$D3O`9jLf5W}o{H}THkH2B(?kKWqcD-rWoo~4L)?K@{ zk(Jn+J9gfA_wHR$Y|mf&#+^I2-M#zf-FLozThvn4kUziqE-&8Mwe9ZPZoc#GyY9Su zo9B#ezxU=lci(*5wq3h-zTr)w9?#pg`_|oW+!eJ|PgYgEbK7gT-Ffe}onC#5lCPr| zLn<@kuid@vzTN)P1J+yZoQ3G>SxiIvM&zZxcyFW zw`&`4-FEBFH$@ld*^2NCNM29o&D*!VDO#onqk6FW=GRi??rq+MVb7|IsFM`A_tv}K zxXoW0B4^vqTX$`XGSx8#YC*$o+it$&)?IgagKZ<|8}D>3T0YQW6^)jsznp$FjoNY4 zZpZP83opE|9gnrgE{ex4PMen`&Ez@F<#D5xj;(y|s$~4q%T`~WcZ)05#O<-Qt5W{1 zeBPB4X(w5mq+{!@ymIWyB)RI!q@7SnnvBL*Uzu_pdro`Zigc{IK50K^EFN2tlDT}0 zG}jGdYe}d4q+=_{NWNpN&F{tEIhJmGev%~LNfYhm>33b3rb)VfJ(nxfWaE{~*RTEV zD`_lUown0PPRFQ@+-t_DYHaM91%Kp>jkS~H8Y?4r$O~|Kd0%sedZzO;986)An-` zN+x4GZq_YYLNN6IsdetDJ56G}yEeJvibgZu5wBR$OyekSU6SsP%jwyuvp*`2|D9dt zxU|2cA#w4Boo`B_k(-@s8OLedx%uWhw%xkp<{yV;(xiFojvaTsDf(PGd}SP!acB8^ zlJ_R}C+|ysCi&UqJ5DG6ce3#xlCLCRP5v?YT5=}&=Vau|Z)<%tZA97H&dxa9pKQ*f zv8arebxM9Gl4G~$QJLLa#8U+bK6bn39=*Lt^JuDEHdXGpr--gej^56TNtx}=qgUv4 z((#na=P`+H+~IM&IV$&Guczu>*6if4Sk$>Dj`v?d8`}$>T$7CQGVP8|BvG&WhiNyr zX3FTrT#wz(Ysr2a?gQ zyV5Uf{4&v*isOBgQMZw72F$d4Ci?!dh6sVUlmJ>pUPR zPBTpQCHv`PlSb16lhLco_lqWmfkvJvN8QZR>2AwqpxGCJl9X;EttgVmi?G7ukw>!iu?aA3_E5wTPk+04CaAU+z5!_EXlpFT6(hl&_@_>W;fkz~tb1Q54z zxgRMq)M+rBJ0}pp40#Q8y;P?60GU!7B1D#6-maIBH%06Z^O#2x08yDNm6GP7Ami(JKH7qG31Z>=ST||oa14iQA5L+UlB3-gmHAWeDWuI+mC4O$pz zxuN&s^3dnKO>6Zt2fnxJ%P+pGH2^^wBT$o0nScl6-#}|FTj|Baf8D5FQjgRhdey^$ z1m#&EDpM2K)QxaH@V$2rG+>Cb&J1?HYy=TKRq!4K=*?Z*;h(zoK|sBp^Pr{kR5OY9 zxh|c2=C{7|osyVw9zB$O*D@)T2s!%JZ+zr0{%U!6Fgg9(U;57JBB4M*IYyc?_U(NL z1`~RVMh~9mV83EG8xh_-3sS&EV+y1-LdW_lTF|k$Y^kEN(mO7PoKK?3>FEOoJrLLF zeS5o+jmzXApso10Q5qkk7@dajK{XV1yibY zF*8sdJvYpptwT^g)lifSfFczI{(pw?Z4XE03LdCR~|rIDwKk?q}Ko~QZ9 z%j^|OFhqHADsN8Z!{PO~w8ul)gdqS_hQt{!bq9LRu+*tp;_qPWPq&|XC zx(xTn_cG$*dx~_tXkZ3Y83PsMWSdqQwNimAq|p0-OA(;q+G&c;*SSs%=MXv)^ClPG zRXh9cLARG96{cy;!4L#k;P*Hb&G91Lp2uPhDDI%7;^9<|NcL~4*UjJ(P@-SXTTKQw zRCp%72d5xj#T!z`##Vv0)(->Sc}H}FHDnHjK7i29ABaPsK9*JahVyZWI=P5L+Rh;u z#d!jUv;&8h2II@_(NMeRrF3oN6-6?x%BU`3T%hHB5H(fP5Jqv@ZM$r@5p3Ck+ohIOnGW!oiqQicW-e9Q>-} zmGePgub(9&nusu$vIi^7hT4>^WEL3)3YOktMw8SU5K?HnesPqGzZsz0RhRKMcX4)& znD9dt%&N8~n~BzPq?_`OSIdiDHUQ|^ubm%t{=npm*iwf^E)ttBq@Vg zmQ2+$G;)wZA+pv+=unp-LO~2B4fL3FEbN|f7_C|7E1J1iZQWBCiekj8Lod9yY5`z_ zRo#~OMY>R9J&;?xJ>G35h|N3}`)h0#uoR+DS=+O86=*^jn8?%sBiYK6?;nQ?} ze0!0su?N|A;{ixDnqtJ*kS3&0RI?q$=3`NPWH8Z3BT!F`nM>Qyumc;?fsCNs#5p~! zb)eW^jZF6YfTsONtsWrL5oY5x;ZccSLD&P>Fn(|ZCSf>g-3TlM(W6b7?3( z4+P=zq3mV($cAXfcB{-e+T{UI?2+U1Pk8bO_2(ofRDJH%BVT6!@k7>2t1o+He_Jc5Z^T)l zY71>lwDE#`XixV2JUa=ZkfJI!UnRwvP_N}$q_r+-AVykJCJl;-K6-5#K59=N~IO0*R<(v(i2w%h2MUA3If$NFb+V}RTMD*~E zZKQ9iD%76+ASB_XC*opMO5C748Rw(s1oiQQk#I}RBH62G!1Tbc{l@#h>)Ldh2JK-1 z7Vh&-@8AM%?%+b^EdUV4LImqpMD z2rw5Nfu}RxB6$eT86s&ze+bTzlsKBAQadM7SDdr)_M(aAY^s$W0I>Rx*w|@qU~Gc| z3?n@3BGn+o_ffs*=yl0)n$X)wie`2LS_eLu@QZ9a@Yr0J?#L4bhkJEw@j5nn9dp#N zk>n=ofF?^<(+D`3p5DBtmsEhOSM3d6?MkoqoiDjI%}64snxJ5bYE4gvadd5ZG#0FP zphB5!xHdh;Z7$w}aY>ek<@g!zHOoU7N;f8(ClzV)zZdJ>Tl8}{o&=^1(UEw3&t42< zkGQK8q4d0=xOflZ%?|MdHG|CAmujRp#koxz>5K50^Rg&aZj`S=dNvj{Kat>SqaY7K zP1!-II&jc@D&s;dL6IpHnbLBao}}3a(0=}3o^FWl6Q6T^zSxTEM!B{m$s}HmuU~44 zO6I*NEb~IPo?>kplYVEmKMx^#T0R|3p+@Dzsp9ghrl{PfO4Wn@ho=S}J~8<4$^M5M zq8sWek<>`6Q00bbP5oef$X7pB*136|EAoM~J8IKWb~OIY6jOIU+*J}Z#U$hM{>P)N zMfVPV771NG%H3<|ox=X1-WXrPL5O;paxf$+5{O7c?__SqsDn9fV@=e7O zN*riVoiaYl#R3f3AFW5BG#JQkiN~hVMUT8jCV{c)yjYdW5K*w~H3QqNx*2fNCZ9PzQxrrqOi{Gug50Rq zrIt~?>(Z6ird#mX;T?XdjbEwhoAUO$^oRysdqXsx6b(hg$eK|WH^Y;$0yCN^87eT; zP~pm9o}fYZ;>nSHT?-ZCrC2y;gaUXVZH>aAytRiR+}uOFXLt`Yb@85}qbWb8_41*u zkDv}g_|`}Epj(@|Fp+4NEsyLih+v`7H)wzu5l02pL=o?;#TRpnsAZr<>VeL^7^5+h zWLt=L&}9TdB*h^baLFh}*1_w=CT+>|wix6FQID~ix0vG7!0(A9+6lr9*YTBmix_}{ zxRNZoF(2DfjN~JOt!?twa)IqF9-_sSB;lbp!c7@czJo%bZytkIQT5WGm*TkGbkTLBFkz{zPe6`tT_bdDwjU?-=C8B+$ERh0dh zo}P}xv{C2ZB?+sr=;ucFx`{z)>?S^v@haD)L#8T%^w7u$={Jh&Rx$`+UjiE{CbTl+ zq-sGDESiq*={#_OONi6R?_r({%S2#QGc|l|dP-N!rIT>l=B@y@qBn4s4GC^ zaUoCwqyUR^x@ld9dgjUu`i!o~n!~#C99?hNKd38J%)`D|29`ca8na+Ho1A0@aR0we zX1M|BeGFu1cZ_8J4hv!W#G@a0m$8-r%Wj5pi_kUe=;|TJ$Z`uEUEKR#-}@-g-%fU` z6z-xnj(@vtz_cqh#XMZ1kjpu%uEm}FrC}+p?8kNG8N-tY6G(4+N`N>Y)(N&1P=a@z@$7qw4lkg`HJ!l13r*vscOZO55DA#8wMDN9i&B2F`0b20W9@N^7hIkc zM1>f~9CmZ!>n6#TX@8C;?>k?Uh_A%D$QYxC27|h?ndGLa(8*18i#&PJxmz4MUyJw! z-jX%9eyW_eF0wFS2x2gZHTPdPNcMTUcrZR+i^!~I_F_6D(P6CugE~1uUS4%FuiNCw znsayZ__^B@_BKR$81-t%Z7DK=)@?^}55=CX4u@P}p)NbrE1K1rn|hg>>&*LlnbV=t zCB+T^+|et#qkex&FY`vvZ1rHmfi|;m(cn9^aPS2b%btAglEa+LrG%V2lZZ6rn=AmM zRQ5uNdzV4ekN@$n>~|s76FqVT$}GFG81X{xoZ=Wt%ScwtKxKJ}1ru$$e?S_-2jqxL z#_~Zi0z8r3#s#j(en_;NNFI!f5f?Z|REb_13D}xIriO=l@BjkKpY%yJYFJ68xBynE z2|g#<(9mf!)W4Xvv>F zThO@}zdMSy%?Tovi=nNUc09U~N91}iT@OaKV&{##N){5=z4kxFlQ0eTj^6=q|=+p6q=M znKm`9YRq-Uz@hNo&Z)Svs^+jB%?XoPbputSXgB(0!Y`R$B>9lG^xmAbM2JpAUAxb2 zhj`uzHAW*sFYK@hgNKW<8j~^07B|&zZR*qB+3RQp2xwY>(6l;?Q9!dw5eo$lEh1Pc zC(H6Ems8n9Uo{2`4&)sqP>*looN4rXJJ{E1=8&#?eJ0_30U6?#}W$@T_ zI$ZDbVmmzd4!_*u7hE${u~nCA68tjZ?FPSWrWH(j!%W)(d!Zll&}NfyI;CJ6%Ood~ znY7f@-wb_)m2i*_rSK9CR}9ILt?=NX`q5!M!Xc|49TtYbjf*w2vJ6Y)5>CwSup#f{wE|IjDO`}Ur$X|O(ykX9AXT~ zX8p%d`Cqvhhn~G4>b%g_bQ=QTx_TB_6UgeP=BzOho8uXRj<65%BYS_+`3Os@<>DT# zf@`AS5yBjz>r4o;F_tu`PLS}Ll9BV9yhAF@f$3Q`QubUNeK4!ICT{XtP(WF>Ef^l z@OH}w2$Y;cLjh*@Q0T1le>l{B+;p&~AZ&YJ0I z%cYxo??e1+eZ8L}s8sj<1Y|5vNJDt!O@$ zka3Xx@B2D_wAt73hY3xU52qr@8&{o;=3n_6r@6|}PkRTQvzX{Gdfi_t>!-SORFd86 z#F~j^ubh?ai`yF@JEKoj2XN^KanTI5!QoAH^$T$#4u`*I`zidGDEy+Dz-Q~~|M_|E zDkbTuy#ygvo{ggTc}U-ysOI+7bQLWX;WW0iT8y0H|B7gOR+Cc{#ExKG)D%Rg&<%BK zzV6l>!`7l`8n!^#80?lp2x2E6wM`$j>J~PyN;a%p7B5q`Ohi<%F+og7%>kGBz$+Et zOb#+HEAaECOY5WHG@gAMM2mz#Sr zy(M@hb{Trj?9*xIm)H;m!A<*Spm{C2`4sjFLYy4%eL&7Bjd;Ug+%t<{R=8fc)4){l)r{*L5rXRi zBFJ#&w2LhK3Fs06V+cmspTvs24%yOO5}Sh$9LuLc^V3UVZ9^R+ zi9wF%W2enqblO&-meKW{dytOW`~^0FySBQdtLQptuF!4!OFWo1d6kgGV~MIfJvjN| zw-(xbMe)W%B|no_ZXGXAKNU@_kD}~FXtAGUMr86Qp=zho`R*mKfDN^zL1pIDY}&ck z@ql?Mk1rAjV0b#r5M0XuSw@2Fg!lyp7^`3a9qTOnQ^;cCf?a%`!ZOm^;@dC>T>5_VmO#)RAR^d3WlxXf zfaR9JFZ5XgxuJJ*`Y%8B3%~Y`5B~{_UAAq)OQTL}w$m3y)W)0zW60O&+2%>Hn~64n zbTz{!%FX3xe_m4AMyv*ChDC@F!vv)`z9$2$jLM(=TibQ1!IRnav<5LWrutpw<@a{( z=-H=$hk@1yUIXx&8lVD?<>w87$1=BrOqVpZgpeN_s<>WOyMZLdulNU-O!_9Z20Ncm zdTL|3-KRFbF17J>$=U3Ev1HmpDV}VmvtR#^Yg83P=-ErBpM{#B3_|`xzS`?-YGQHa zi_`?oFQFy`vA|*N2QpZ)`WWr8tcB6x%RdIyiponxh}PLJCDtpw)z_=@OZe%wB>czw z@HyL$&&P1|%f}#BFfo@zr%XN>C@Kg1_700s&Z~7P13K@OEx29J2%9y8y{@uT4i1^tCtSh)N3q>|L&w}XujWaM5r|eT%P)_zo~_Wq?!|*@=Fy*K@5Hm^OP%9QANluXhD_yRCUHwW=YQ-XRu@dE)rts=* zh89~?GZnMGc9Gy7_nKur%A)Iowt&Uwr-#k*wmE7h(d+s#6u7}~szHJsVuIpSaU}NX zT7U!|2*rDM+0@HU!ZsgQHH%{5j@!ZND*l$My#*mT0YriOvd9N*`c z9p30IRDv7(V`L($=}XRj?JZo0Hs0VxHgiX9^QuktOvgqGUYup2gw`T5O>$e&Q4okJ z(rc1Cm;@kDCus{6jLb%wdL?2jdzGe7X3V|(K+5`Q^O~nq(}e?UWgg)f1V%quPV1h5 zeTu6!!)gS{6s;g)Q3SEo9YEFC5Y3C@(#^WDRY@^61Fz82Ik5+s2NRosw`Cx+8ol_) zth!)S<3$n}yGhj?wE!cD+iclcgvr`8tJI1&S^EJent_UKP!J_%4N?onRhCx4DC0oE zdRi?%V)@7Qnh&BSwjs1+r~==RE&CAjAA>u!s)Xb$N#GSnHJnRfr|Y8I9KxOtD)k3bXOfmlIzNrGe&79_rz-B+&p7x^bx3JdVnmRf6@(*`+=Fn}!znw$@;16GMUprvL47iAROD9wEka! zRk{G*6*VrqOX0!64y>mo%LZkSN7*l=n3{^0!&kpElnmpyD{4>pmXRhziY@4c4X(o) zC=<2wUXA=j@GuLhi>61dV@WkmX5g~kJGvWqk45A5#?f$P&Az=lqw6VoNG0z8LQ)=< zeXqN0@3^fy_BZDd^8vtw*=+q8fZy-51F9Ky6hH)WzD11 zh1zp!5jAt##8#)&6BOf~6k88Oa1~KFhB-@V8#Yf0mE0@2y}Uy*eI;|q+owoV2EG?_ zD}d)@&)&l53oK!T)-v-%-Yv-QDmiP4RZwCnCS6*aNFGnRu@{8y+2%+qFGl&t(p}i6 zIi(dm={binfM5fqi))Ojm$PNcA};1fZ~o!6+vMvk>Ie3*rLWwb_v~Xd{RmYZkSVU6VS}$aC^3i8w&gg(aDG#p<`~ z3|EOh=3xz_O7z*yz?%e#RYC5TmHwJk+i^KL8u!*5zh^>5E%&TUnGtWI`_Z`ZCz3?f zsr`NnT5!nHXF{XDO5)jXi%skh(_QIP!BQ6r>vmZ~(Zy8kVkX$qdx(-L(!=#4yW)Ml zoxq>H?++m(50QV6q^hxe`U{G~m5Po7XF;GKhb+5x;s+6W$aR*1fRX|0?-(O;(vD9y z6J28by8Vhey{Jf>4-@fWuQ%ob=zSG;_{I1OU{-^9>n8Q$|9j{`5{D_-Dl?J_R10)g zj%i8IQj{|M$V@V{@lK7T{?Tek>L08`R&tIQ0MGaznvqz@I9hnDT<0(tZ&0f6WZGP} z`%@p*Gnn9`iU;UVgPM)6qYHOBYRPNE0=TnOasXKq;Bb;HR_-D3dazg#aOw~g1!iL?J5`z_Qoxwut#s? zf)~Ao3yt5vg~nAe%4!oAVA#k77z7wF2rys}VCFPnj>#sp%7(bu(x_2QtMo9;F!bwTW5EuQlIRs~ok1ncvQF!#zEcTLrU{SksO*cwRowrb55ehfZ_ckYa0^^YA zr(%K_X~gfI#jND7F)Io96%qq#&L4q2ZIhCx<0>+$5Ny2|1Rzq2r#gXPLk6#TbPFbY zfb_(lOM&Y|BMZJh7dhx_V8o1!7tL7_jI#@VlI|w_lKW*PS`?LpwkY=p&z#0EGlWVj z|7>f)7p0ckzxP^jBR+|x#2__U@&3Lbz2b*B5*quT`@?rWG%)>hSfQ71cK+w*KIEZj zUPk*RQu@Z9Fof|Z=yIn|4uec*b9VeDSgT~4Nht>?&VD)d06dN$ksce@gbwN{r*g1C z#)oFoZ5|=iZ0E6%F<2*tHN2e2`M`LoCZ1mIp5W4)K-3?2-ho~P!!d0H7ICU)FU|e~ z=4plGRn)Ygbk?yV%xn63IX_5*s+>ydoV=E#Pl@*P1A@q6HiTtF9Llz?WmG06F2Ai9V#SZn97%{F%3D_qk;Yuw z{L^IP;{D6^HTE|ZxR@UeA;Qwaa|59iVniXR(hFV|o+pNqfoaB+gso2Fyom8De9v$V zj=^CivB6k>eJ~{140?!UInRl7cxN5`9_)nuveb*4G2OwQ@RcWCUNS9}ZRok4^e6&54eG;i4h)&}+2Z&CQ6i+_Il}E{wdSuX)7nQ7!+#vVB z1i%d5qhJSM#c@J64v{RWo8ME`8Xl{MM+EsXJCX|UW&uY)%2{QZds_yRat>LJs;&oN>lpQ3-hExj6%(WQ4mNM7(L^;CFVLjKx z!b%7~KPy9GM1s`d00nCQ#eSerx7e-E0uX#Wr?qxQmf3hdt^oD3lqj7vNEK| z%20@`obt6xk}FdisR91Kb~AkBv_8N-gH%1+weS zIl9g^y2ic78r|n=&=C$B5fiI78=^-!Du|HfoVPrl;iR`Nas8 zqPsQE^R8{v;_BP9y7}t-^1a>5)nBSy74~Qq;Nc47j4rf)1cvES*DBknMbMFZ()s1O z@9mE2uE|$*3xi8WDlbydV|&F~WgkiR)cMH0PPW z*S}qhhX>Q*auu3q>r1!SFu*OnUCU|>(Zz0lx$W2Lt^u+68hD1t!^L^-!gV!!o-wh( z0ei!K*mi8GhnMZ$1N!oO6`QtXiVF#3dLDbWhylV+@NScRIpg_ddx}*Q7^n2O$Y*R& zzV~;c>^ISgC0q%50zZiR9&NfPF_Cvn_WOj4hg`AGmc>4uM_E+~WM%+hq!DbUl!`)p zW9{+mGlA8Ppw7u;NIlGuV#Zq70;NpD8=u>dI>RhQJ*1M&aN*xS_@8WJ5ao`U{Wg@% zCYlSTGG%4I)AgxPJHVQ_b!~-237kUc8V0u3^b}1O41hXFFd%f^{qef5msTe4k zo_rMfFK8bH+G&Gx5wy%@0WIS!F`*HugA!D|7}bC%dGrUcL6+{frsSzfQpn`TH_>&8 zo$*{!)&?W!#Fiy9JBG~=RQ5%18q}-zUubyq_rAaTQzp=5*7Of|**3vUI;s&5s!p@{ z?${_Z-&f+~*{jta)HJt+6GnS8m1LI47dfI@xIWnA)G8Af8D_WX4BLAOk3HP%TM~Q} zi^laHPiFd`P(uinBi7E~`Xps4@(5dQBPsk$677*4j5SoC2ZWe!RUCIeG@M(ShI39s z)uo;uBEEFYrW~qT4zw<4L^0nXQETknpkYfo43=ytw~R3;gey7@5t@uN`%Zx|(E|W* za%lig8UPCSVrUrD@@GK8k$xm-V~33^DJf$!TI8@r8nq=Cv1zT4OeC#m)guqgo4}Uh zft)3(1}5{-$=#?a9@;%J+HHBCX*%ehhdafUni_3|&s(IztScY0RQ*JuS_L^Z5U#b3 zaYCGyYVkTs>@17`&1^9?y2bN!$%_f~9DZ1beKT&oh`c4tS6WKCW?Qm+#*`h}%=8or zvG-rjyMpz3_Ie*jGoW7C4QlClv&cDOrEi07zG|-|V^2)KW}JKQHnF$3EWa#Y#X&37 znn)Bp{ewCNjJ7poIH87Lk%@;)DKmaukYZ4QdWOl&JKp!@FLMN##*Ssw%cdAtq73GO zG2LO9PD)lH|6mw$GvrPCkkis0{;DC@kXQ#I*`%{7Evs0IG>+TNHOb?nxePVLLH66! zFI8O)xf7^kV=uitM;Nspahj|bCB}ibz;x>>sBEDRi zeKB^F9vU@$#}h~n6?1?qR_(>>6q0;coD`YpjZ8H8xQ0yL5b8xStCzH-pcJN)!L?D( z{>i=+(=}AGJ9)6lQF?X|IC_xQe8R%Bg*{UoIC)`maHP+SnNuF>QPkskusAsCAG3y# zDy&mnE>lq*qug9lboa_`to~Db2-FL+FULi`1&rs}g(AC@%kOY-l3v2jyMq4r*?Il= z$S;G+!i(E^^qw>}Yqm{o=>0wmZ+N^Y431{8_9i9Fz9V;y+jpyP%Gczp&A#K|e6zb+ zL?vr4cGg;bR^%!r_IB4=UV!Z*K?8($T33b5vO-s+yv$otX* z+wu{KRZSSQSGb+GTIL;go{V>9lUdxTDeVk&v5YV(*6yW^EAll+B5));!Mj)FYZ-qU z_Js^Wu2cs<&ub{c#Ky4B*Wy<(2_XxSR3sc=T@ka9Js$gu;{JtPaCYje?{LoQMLd8t zU-7_I3#|OW1#7p`m9@qNT;0lF3*E1R{E4w{zB*8TJtW`Yl;1!HP7{!W${^V(KMkQs ziyH>4{HycTT_f|E*r`v!K>hC!;HTp~`FB**Cr+2I&o|^(oT?ukE9LUwQ%csnu%|mpS#X2I2YDm6dO75Qr))NRqgfuO6<#%3tFg;Dcs$zUsy? z(SFt5E^I`~q%mb*J*H1*ET&~8;m%9bd?}imQ-kMLETHLXAK7cRib0L0T%D$CaGZ+0 z-6GI54>W~<-K(6Y;81&>^ELDkb?l>=d(wHafTmaA5v~=^j|G~}Rv0a=R`yY7dUZw9 z87cwKFx46#(N5DD(UfOP()3X5j4RJ=O&?AB(hF#MsHzFV6i(ACM2agannDu|)XQtn zt){7ZD4MPYZ8;XAw40D1E5!2f+;Y{0B_Jhh(lUprQd7-Z1I{$DOoEKhFGaeaip?lh zvEw+8_Fl1ClD|@#{jbpCYF??TmHV;^|2vfKuFe(4QGnvo{BpSw)%vCtAP|w&b0Y+1 zkRKQ*?oKRW>+}?^xe$x&nSihJ*fWSNcOlMW&j>L~TQhPKaxiyA_Q&X-0nBvG?{mix z*Ad0)9$~O)45e5SxRfkW)+#4v&WIRd>KNvu(mZIh0^~}#2^{=uE9vI1Kk1Xk_yh{0 zWjZp;suVG>g)K0np5TawFMzN>3rAevHOPl?Ns=UDxN8kq>TbC#b*-L~`M%ICf7sn} z>aLtq+T|3rM>Ug#OW{lK6gG1s+MS9Bph6INAjF>~2r4=ed~>rcC>r|Gd%&97$E8dm z7s5gy;rQ(_l<};7HP@XAd<-#=bWS#sVR*s1CT0UyZOd- ze5DH50z`6|1aK;96$9}LDxI<51c@UgF4O6ihzlY)kH|Yg{N$JEv`TW2oEzvzu9t2p zh;3Y^zSy6iREe>uYJC9jr685~X_bbn$H0}jR*P`OVk3)lia6o>P;~mF=vf;p}=xgqRP#QLMe?DU8Jm@wvBI)B*5L{E%yjDI;LgLt+s70$5fkJna8c|J}=76>-GnMOHhB&=L=H|@bt4zbt?tOV5g zr6CjxPf-9bRO<_k_ZGxqR~9V#r{m8$3|k$)1qv^9U4FSphweU#BCUOGRMbq;Z=C#iO;>|6A%CW7k|AMSqPHYebv4(5+*GxHb=u^2gqntT+ZTivNEK`HT+~E zqGAlp~5i9yo`EM0ldk$l@IWdyy<=O(0SUGeM0?V>6wJ;!x;|+d0!M ztQxPf80L-qxfYvHtR`O>lE)jlRGi-NfOUR}-jSXH0w%RxcUZK88Bq(a-f6AUuA25@|<58q`h$>q8nMZ_c6iCvkYf?BQIgH;9@lPOs z?>`lJDrUZu$;?(1^EK62p=uaNT9}vQ5bJszV_w&}cyEx=XmReDNyUhi+0`IltMhe8RwPtV4v5@9IPo4~|PA58ya5@ylQr^KU zTE;4~v+BNPm8ghV`d08I#dinv;&KZYq8X&Nc#3tQC~GtTKeCco>%o}#Q7QrrH<)Ig zU;!x$xH#VI`-M$y=Go+>2Z*kfp{ht{zR}?wV#~x!Oiv%;3lJOawwA6a9-X6ji;2c? zMoJZyjRh-hh5cTHWbG8VLPQFhMmE8?hWBX=iLzQhmnL+MQp>?6=&H9Xa@$cf>}EOepGW)+{sPvr0V1MAlihLvrvVWqdm%5ix&@ z2G6=3GOg-c1-kSGLzrEXY2S88Ff@_K>5R(zJxLh>$SOWM!)Ix9)nj{K?Xw~BD4B{q zu^|#0t4pN&+{_4lITR_32TVKuzB*~SSa_NQ%yOoMjxiWYX9WJF@2r3B`2XN z`gt$4F}&jP{nh@$#XBgexvELlc35+BjwBp%*CHC`7j171Ee zI+cA`NW{zis6b>e+Lc{=kEC7dCr1?Re5}=XY(b}Qz2sq8zZg{TEGzZU8n$>3oFU1% zFHh=QvgIMglFMn_5XoO-)dm)Y*&kvo)C5tmkx-RX$Z>9zIjcVmC@2 z8_E+)vZ893HTuCUiC>||cz46vSwfOH(oi>|UoF`=W%GKyd~n!~a0^HMQha7uPl*74 z6?KfQnILXSz#HX^zM@N{#hMChsFj(W)Fvt~rveYnpFMSWV6;^#y zC+#2Q08%PN7*(6&+2w}3yutEDq{=D96v={8yw)p(QAcMsM1oJ?Oe8GXuonz#Px9+Z zk0+cD_%X{mj`HqW>%lzpM4P?`_(!^ON!VPkm%+2hHazj?Q)1=ZVik zq)-$E>aUqVT5#GYt1iF`ItXD;hm#LIDieR--2Mk9mty6CthF#VAPXNl;ORO7eTBq#gQPpJAQ6#UB$gCtu6 z?%IvFRB`|LVC7;| z;se`Jrr#Wn6p8&-aYTz1e%KOWxZ%10FLt&j)C%VCw~CwAAZ}!8&D$o_W3i62=-=I7 z41$^nyBnM^Ku~XM=(AH)NYa6?mH0s#iOC8PzmO_&g-yb^!Su1208*%tHgLocN6Hli zY9hrpEK;9#Sir=REYjx_9D_&^>2u!CwAr=uTX9mIEXtaH>|3hB85lwWM&;l5xn_2d zudtUuR?}kgq0u; zRlB}x$lB^dmUo=r>;rH)y;QLjTuQjy7%aPqWIBQ6%Qwp(1o|(3)^>xot3v>Vfazok zCeRUUI(Mflb&jUvM?NwjSP?3@hLWB20!mCDPFP?lEjVjIPgh!=zQ>9ZHN8J@NQRC* z!@4rt_!DQhjadid!oao=R7rYVCF}8VmNOdkZK0|xmUNY5%UNYdD_H1k2@BK(79F-i zCxYR80>vD4{ygyw8TBcbLQ3|Pm|mgu?23(KS8SxO#RWLDxex8iFE7V-rS}JZ7g`9r z98rna1s_2^ooz*B$|b}1Gx0!ARUpa!QH>}T+Q(Ep$UsY3lt1bI;kqc^M?@$7)dqV3^?Da!c0=L8;%~Xk_Brvvs z(8?5WH2uhakzNAVhA3x4vPpJxbul8N&MVKM7-MbFN=342^PY0-bhf^oFROd1@dv34 zA;b&t;kWC^0GtkwPd4j6I$~akD#j1;)rUHsqH2zS1R)2FcBb_d*T6&>D9mdw$rEj* z@YkU3^R(J=GeT6@F&7Gi$%Xpa#Id4t$JvF{GeX$>+rYIZqnbyuoxL=KS){i%=*^R1H(RO}zOh$rknq#^0^hT@xE1~-EUTECrJbyt3WXSM} zvtLvetc9fe*EwBJ=k-cNpZ#>b7d!qH@$f^LF(gEzPvXIq<6uqS_!$gVcqI{~2+#$> zXYvi1)T9q`;`o8$A9RX;koDY}{;%?l7$2g~3}6s{Q>7%;C7L1Mhh_hsL7VlCBDK1CA9iDdr+eZyKTM4sx0Qz3ss>B4evX9bs62P znvdvJh_-2vXna8yy|+dzyJjl;6{%I@aAapFZ{vv%9eI>Q15d+~zAMOt17%IV`dN{> zUxqn#OT)~P1pVuvaAp|(OOo_3*a>|cK zJL^j|`v!h)O?A}}T80&h_`pitVspMq-3OOhU^9rSDzFI|W!Tv4o(w?AJKb`?vZv(i z5-7E^&PCHJZh@rC;atqG0T*D17O!VPH&a^M18oT$aZssfZ<25i97_PCDb@(>lmAg~Bu*rY9)HR$*(>V56_d@KEOAIAEG?_&Moy0|aq z#yG&=bbl-^Y|$@7H=OC#3`qJ)TyP6}l5$fCGphJe$z@JvkeuhvGGAyH1mwEjGV(<PZXp zDu?Qr)8mQfu$dGtW1nRm0{Uvqj!JVKhji8rlZkumtbeMR=MjCNFxFn1kG*z>)is^V zY57*!9aa~Mz}ornc=hH*3+v2V91Y`G(NY~%Q_eIkh*URWnGgEGP+os|gy9Up0>S2V zX!{BJ)`ORKnpy-Bmd8z>_|m_%O(VJwhNA6ak#-g;6+As7@ddM;hVz-N*QeL{t0Xs{ zi<0DSl&6R%4M}v{L{y`EihEBAqpC-$wlM#q^sz`tX<|GjNxjI&6MaW8**uR$KISZ!>Re@q$i>ga@ zB&t(cT1mhPp${|Qk{(u)?BfVu1xaWU%lar?#L)WB)nJaoG~b{RW>GV=`5$SDQkeMH zIfA2)wsf9TF{;josh&QrSjaUSRemX6_*FWzm=#1;BkqE;J#KcZa>-96U`c&*Q@^;=4X>W@Y>Kv(i>8LN)L%dKio~qcYjG= zNms%0!(Y;fmX8 zG|WP7Tfm)td<*3Uq}O7;ERqU~eAbuoX*M!g(HLblUt%auxz}S1YJcv$u&K&zl|9PcLhwvV*j5|tT3e0J$1fR#O*7{BBq=&e zW?YlwubTsAoU)#3aBQXyiKMlUo>Xg=$q}6fURzU!lxQig$O&G%IaG@&N5S?*l?JHt zDDI1NJxSWNovo^5x?z?6%%7^5xBp;DRcr+!8aZ7z^+}4z_Tj*1ZB-RAPMM=f zF7{okZ}Y~`UXM}z^q&PR@#WP$m`4g}(7)fbY{zbDuqH&oSX=+8*xlq7@G{tj{26;7 ztdnb|%Hu>>ewc;-O-0DS?NFsuzzrEX+&&C4<0kb6;~H4?ZA{o~3#nm^jPw>JLfh6) zbZR@ek?@SpTlMc`3;ol_zVNm``n&Jf#F}nvJ8*~Ddu^5I*?VpG)SC($02}t+h^)1y z&WrYM`vL}Y0-~f=2{{G-i*6M>mc7^HtTaBlE>qdQ^{VnZ`O!>P9!^&e_27Wcp7SRg zqFaKjr_ddqIowkf!{Mk<>k<$@`0oD>3cyZ3hasp%UU08|&|zW!D9mt{&N5mBr4xeA zau|#5Wx07$FoJN&K7Q53UsqqnwxO>-R&`Y(a6wnMglEdf&BKwyd|h?s*Jq?{0rX4IY1m9a4|pGbU!YsWaofx>0}O>>mY)Ym(*Y_bW}3IsHRZSpCGw_?x@`##}(cyB($#)IJ;@w3;Qar^k3 znX}q4iy47-kuj@e-nLsURG|I|TcL{0E5;pBPQE7}sXot*cNh!?oK^ff?<})Bo+iXDFd>u(R z1w}I3XJaw3Vuk*3ZhR@e*M<)kv%AASV5l7LKHuPGivbgn@H@+nL=?EeRYF+o0$-2| zjx%jFVDXEs1>I5mB^qlOadNrAC{WOL+=G2joqQ&o?)neXoDv9p@=kMJ(wn?3km*`B z>oMwI`g5~kw6a7E@Ev6f(Jtu-C1vh&rsm+}Q{Unj<6@IS*#~;x{rO+*eSI)ZTh+nZ zVOClG2;>zy&5^&H*=hlaOb&2kCm4iRQdWSl$D3W^!@3NY^bo835z`}OM`GIaJywcvjs$P%5$B&^iM!S9hPeZ{(at?B zNA*QMd?Ofu*2^IRAJEbKD&-aH^agDO*qkv&cj%I;&nbuj)hSkO7R-ssWf^o`vOOjmO1ht!Tm+0CE&1@hHDU}sXohPw%;arOuC&9ubgjU=N||?RYfra>@@;9i{^FcSVcbrd%FG> zHk3%R;|vgK?Q_qgEN*PT3ZRn83fSmIz(yGXvt|VR&%R@S=lTz$KB0&V-$v-`so7!h zFis3#{SMy`R^uo=D@vTjUYq6I1M6xl7-aOVMUG7?XdxEmDTBjv{M_eHFRpyi0o3&F ztOroLUWJVo984`$6mm(`$4pQ3pE5lGZyd%#2nCk@^zFwuWLhvv_X$@40p~wudd^On zelTum?k8qHj{6=lPFcdgDA~*yh#x9f?k-opvHT3eG%cU2^})8_s$on{jNu#UTPGt0 z7#dwTrQ1}8qq3!c-#1QJ1LGjt-i@e>D83cumr7&GIQ)h!!!( zs+UX0CY?VWCf3maM*U;F&LYj2QO4Uo+uRh}(gx;9=U_p!29<)waWd)7Bz`Cq%@x5@uVnnGr+`1Pr6BQmM-lUZG?wycGc6eOZ=%?kBNq!dAJ*|VBT8WFS~JbCw^q#L&?#I#LcR*r`k^|b%Vh=aVu=8KRAJw2Gh;tVGX>)fBCX;RtTHQv2jF2k&BPI*2 z*<#X~@5Au2q0qKK&}=M$gNU#;a+-1Ntx!-DLP1tt6$*k;8Ts#c@Du8bJ`ba*zsMV` zrv4(c=BaYp03|xY&PR1a)`2HwB-}@ifBfP$T7IE`l0)U89U{s1 z^{w65gZlQ3Y8o0L5JZ{d+P{{!dvPFkciJ2w3~DCGf9uop0}D8`il9_f1Lio_0*&Vf zf2^4#e%|&hlXX5}gX^_}4j^c?77sUWTBt~cj}71LcjIKUxVc&#sNyh?dFXQc z^xl15mS4O*eq~Z3w&cZb)X%h}llk{WbG&G2y`Q_s%p?=5)l>3^Zp_>Hkfar{8$O_* zNHmW(`(!w7G+u6uXUf~yx>zXGUW`bNh3Ysh07v@~P69Gnb#5;j(jr6KdyoP)S~OtT z2&>xNO$HVdAO_C=aB$il8KFf9?Stw*=n&e32BF2#C&qWzH=rSR1Z=kcYZzv46a2&q z)bnA;k33R<+0GWBk8KsoU!{#bge%O@)r5ME1Ra+AiJ0J)$nZYsds#!8-v)f|&djfW z`OrXxMrvfGz+HATe-RLnDl^+{#lLh>%pvt(-l6&--kn76T|O zxU!{f1A~@lVZl{H`M%&PZ?i$$6wQ#VvRSRG%7;4d==(;>e7{-xq|DMM&u^A$q{u8) zQ5v?_=AMOFN*T=3&z{>XU0nGhvy`FuuVt2Q>^Dm{J{z-CpRi|V5nZuuu)m6xDv(8| zK75?tN}V?=^?mVTKP@<2Fd@c)27*#~o$7sYG@QF*0tBgBdu3*4$y_~;SEeOY?x4Yo z*(dsZw14W8-Ansu#6rTl*k|FfJ%%G8Nv@(u@^95+`)e%1^LuQI8yk@0R8q}2RewprLz7X6+9iubi1G##f>CwFcI5L$}XQ;X`rwPPGS32-%P&cH<>ohu>d8Q6+r`(9Po5>2B0e9f`U_s z6(xo$DPwt+Jh?97*e!aFy-?FAd^WB^2c6unl^DW5>Lw|Z-Q{VHFQEK#&GM)E+Ra_6 zW#Bt=#F%W)9}&3!Fgm0;jv+hqLVkA3<~f+HH~UBQK7)~f!mOt@PwSriQ(RrYve0cl zWO8V?EqvqqiELZ-HEWEZ9l(HbGv^zSHMxmbN0c5*kZ`Eh9ZL7CoFZsiO*s%o_1w{& z-I0(>Qc4gfL$7fNt`3-2atvNAS^_w93!~J0t*RNDBeKu32M?Xk*Ht7lBIOy5 zIa*K3{v=#yb}5z3VbEZd5hxapmNP?+UBh=TD$hwDn76fw7*ZO~_IdcBo@Yg{J3OJX z<8gO|!;1ad1Hh)Xa(3;3>o6|`gu`fzMIj#B45ervCld06qPyvvIIAaFZ&$# zzVv|OUK!oZe1&`M2#^c&OU$6nFJdK(v59JAhc~Dqc$5?u?WK_w`6amP;6=E>UB4v1 z2zT9rcVy|1T3(eC z>5Vzt4e>|j)Z21ebLua`n0JG?nJ?3kmZGnECYL?Gu*YuZOu$^~@D^UdPVOHb4ul_c zdEX!l`XYr3y+q}PU#NQMNPeL$yWk4D7u={)Wgg(8JTwEl>TFBn-pY<=9flp+Y~>iU z7s{7rbWQi?^S(?LlFq@pRk= z4z3!OwACj^O@I-gw5UmWyl;(jZ;AGafrZVyJ8kO`{<+U$T)3-Bem-GHTU4hI61Uos zYWEFNK*l;UbGKj%kAQ2fAKxzT`kYLoocS3kDyEW9NAFCs=N8SrFaG6j3!n}C6K$%k z8>7=A^t|R?f=I+-xF-3r9BiU2HQw!k-X=QGr%Uy|JhQV_ko_%sw%YIiXFB&O=6hSR zcKRnYX4U6ehC-74H(MsV`8Td)PYb8L?fM#L^)7zNd|wY0c^QtXUI~T~m2$uZg8Z7^ zB1+ARRUd^iLYzBHvlv~KdA|@5b>~4u)p-q3mWrJN5!6;80n6$*l#zwi+v#GRHxI%> zK)CngZ)WE!!=sBplRedK zYj~XX+og-^4NiKyt0@nCAl#9RVD2Sn>ZBg(@E2Wm_=~R6DY|kJ0P*6y1r@N;wDbwX zP8aMC%_f@jEh;3N^Lnn7Tt;j?6(yWV4nAP3s3ebhe}?aMWfSVx;=;6 z>+`zvwv;YJBHW#Ssk?afR!O)6F_iuH*b-^>KVpDpkdIcIBpED=H%XrRSgicKUZ^A= zhV#dJ>m~kBE0;CBEHkBzkm9}|MjyVJ|9$i*f)y0VUGGD5>uaYg0Dox zSWK_dgiSvl&DQH^xoEW&g}M8%6NM2(s7!ILM)rRRw`|}FTTou$A9+-a(Su;kpbIC$ z1Lg7=URP8IDLjv-8c74MgAUDjw6*6`YK?Fn`zH}B&RXdJqxoU*um3<02kIqJ%nBRG z(I{9UfXpvi+vWM8bI{yPc}I=%_aa}#0Y?TGkq_Rb~yhxmAjkB^NG@i4uDnLjB)Xa!%Imc45yuS>h!q|sddnNzP znE4ki9IBXdYoQnCD8MYYj>t5Rh-pgv`4jlhpKw?KPb~K7PilzK*F53#&3eZcxaeEX z;fD9aJ3Nu~Z%;7!qU+Vn9i{CMD_L|cH0ZIsH1!^oi90rV`}{71zi8oyhI~Vy?iWQb zC_u|6HF;CCwBlT3F;cO>g>~4?@tkbNKPyX!1+(H@WVC4v%rl#^5Pof(GW`F*5ISQ! zP|w7^{brs)V>_x9ozN6w9;R^;i7O`9bYbhZE?O|DI6}G7J`+#Kq}oKvh%C}_jktv} z@Ua9ICWhYHTW`Hu=%sxv~(ShE6_(JD!aumH~1?rp&vfOm1O@SE@nD&Q#ECy zHeoZykv~{jKgdHq@6Ox!`op!uev+lU`gmW;zqZ83A!er)%dwUCXPco;!U#jzvm>>h zR9vv$Op`xMK+54Y2FDbwG794HCuJU<#0Qww^6Z9BOtgI!{q$e>3N-cqAXXv5X!ajJ z8=?iJ7X_c6WFQd8SjSNQ>6{DLGfqa8maVR9eo@TpLp)L!3r+bMA>$*!j~v9Ko+ zD;hrD?zS2bayC;^$IDK5nLqA>?wEGENimzY12omC6*rbVO_FWJ>L$NO4Oo^Af6{aW zhlY85Z~4ox-tGv+%oVU&RM+XG1PC4Gso#T1b|2r!0bt~|Ey-;9Lix|A2G3I9$YS?6 zZnL~=DC8P|oF?g3O+SEoHGo}C$NkQl?Gdw6bNEsYuObc77bZ{Jbd-_Mdzux{cOinZ%q0=SiftnRZ+ z$)GN_$!OYTETf1!R$F;L*zu&?K2=;kU6b^Fw&%t;phkAA|aqfCQ?wv5h*S+ zc*v4pf2aqzaMcrLFM~9p(2ZVviK$`3Dss8kNt$%{eM>C(?58{{xWKCDt_`t5fT*c) z6Q3DWXcC{!+eQKQ+Cw)_3TPVCbbshG`(edERo`D(bzLE*1zq3V*YlTH$AQMj z_I*GN7Z`!KY82^SopI(6*<}I@Fe|gT-uUgTmiE!1GkL*$;H5h>lqdIxfdOOMAl{*6 zgIZ@46NlMB4PFl|I3gtnv)9qIq@ytAFbXKG&&atrY`P1QNTVCXRLH$SNy#>CvEcAF z0~9;nBcVo%TgYE13-ZvPlafKbP=k-08k=h5RMHhVp$&kr11IDvgeIT~7(rVRs8vk_ zX(_C;ZH)fi76VFy6h@k`t|s(LdbzD7q7=mASk7zArj%V(n3Aa3Hx z_QB!;`p3uZunVp91+4P53TdMc8s1H&ie=l&hCu3+KdD7%ow7Og$YiaI}D zR{Ll^w!P3NCI-5|wvOR^MZn6iu+o5q255-<61xr3kN{GWuULYHRM^Q^$hqK)-#yMq z7jg#U=Pb4I4ROOsKH6OltfOGX&YOcfM@ zyEFZOHfxO#YLC*zqc_nqXx0idQAmg2{6Drzw7Z++(rM&JX zK-r$R9fUT_BoH_}zYl~ZX<#5MQ~3)e4W#enU$vo2ce%{u;ZomhG5NuG>zW8yz-ie6 z$siu=7Yu>Z#By+22B-B2hHT*ghWPD#d7yjS$C$=PUr&~l2*ypzr^;1JS14|(NQb4T)t6qakw0L zc35anvBd&WZFFh{3Et{zE+I(pVM*}TLJ6)trbzH4Z%Km3-4-XsNrGEx9mr@E_9VDN zs_mVKMZPTXEg=EB*m+=pqp2De4kEyi&~YSmo-Gm(0qU)bMZz_v#+nrpBo^ry0hbyH zK`v?n$&k==BoGY^YK+nq5*VG5AR_*#m~~$@O2Go7^dgPY&cac;e0y~n{4u> zD-2__NA6KXQs8;7Rof{Tqf69^Y?8!Jj76C}HF7s>PE*;Y1rfE4n%qcL=2oyGjoidy zMD8Y*cBKX>-87z;@C!DJ$$ShlqkJV!%n%rb3PDIU{@?b_2F|X#tn=rdJ8#KNnp3x~ zSyGoX*Ps)(fyw(M#Wg21w3R|%C{TA-+ezl8nPf6EotLIzshvVC1wlYW3k8V~psf_D z8wIhhfflz_&_Y3rMhJ?~BD#_7vMp5V_xC*ibMCn_ccw#A5k6}nbMAThzdirw|GfX7 zzfEN5Dh-w&j8SZ@$6G!js*e)3k9$UB3e3 zLm3XnthDeM8F&Q2XYD~N&_)a~x`h}`00qnl01~7EfP6g|W(YtW$BSr#!FGlhW2~Ot@WjyABJg+Y?Mg9O6utc?BhvI^yuNhxqyRw2R zl)dLTRz(x-O_X+pLva!Iu_S1Sxs-u>fBW`_|LAu(NyyZyqc`3C9ygNon-RCY?vyLO zi_@R}Li6!d^(PR3nBm}%MCmcjG}kB*#!Cv<>Ri)YF4c5-&_~xspKxsyuNkQV^~$Qz z!E}5=*Vrn7xp=c%<@@u%+zPM%vO@D6Yok6Hb};-|L=71PC5p(9O|cKhc}_)HnR}Gx zGtU=>BjI=jamrjtST@OgVTrLZZnCSGTMXytaWTEB$4m!3mLaG|R=DJ^R06x{02dq+ z{any*`?yd%~)GlU;*7#_7Waz^%BndTwPWWLh`$qk${$Fs>M$z@1?SsC#S1ze!eaw za%k}rZo{*{>q==yE;3(LIU=w&q+~|=t$2kL5PnNuLZwYT15)kP6^?GH$oJKD%Qc}3 zY)i(&NOkm7wzE4tx1TsZ@pU^S-?6^nXM{FZk#nfM!* zmNnJOdL#8L&yg>S)minbmW5B|9k0MxhhyA7+Ftlw?rn9*q{WNASq_;rs(ty%p&dwS zTUd*#EC$$ETo(d1lra)u7Tz;Ex3L`B25KtFK|MDDd%7j9NyTz&-~Kzlb^OCr>k!J}+-Fx^Ms9`T(nnzW`V0r6ugLdl-8W36INHA;Gbx%<>8iJqZ^8(cc- zUHIOaXUJy{Gex{Dk(&-~ZkP+1nDhTJgM}}@Q-#S{rK+lhFCUkT`|@863GAU>1T37u z&=e#VJ)#OU4?dGCMkHRowvLR?nPT(hs}o4wxD^dfrI!*3tXX8Z&}_+zPu0{3voyiX zs-&^pi~nE+BAt8jzhG^uD!nR*vtpsZjnjpr8-V6R4mDuMla`hC?5pgReY@dKK3_ zZyl_(km>=DV^ZH{XJ9b@M2B&vA7Lr{R*E zRtfchOr+j&WkOl=Ru)b0=FFmr9~39i>%cHntWb%d&m33VZ;U|h9Dae z<1XPG@f();)>K%e6rEeR^S$yq0m(1IW~|v;q<`7~8u25ML}}EQqX&9*gDo{TCPu z0MZCarDY?OLW;`v?zqy)mPc3vW7qCyV{A) zsslL$Y!6pHIOtk5cY4^zHKalcJB`eG0{6qTh#Z(!R(WHO>o^>J@@OSYdfq{y)vP&Z zIqjg68Us;Fkou0=W8H|U9nwngXJvGY)Qi+vh6anhK89MYFi}FHuuihQ@34MA&{5^P zGqiPyARRJZViLnVuUH$Ri2WUVtyF4re4dxVa452sEt~KnxM)HVo+>B7G0s9>RmVIE z5Iqz^Ze|tf+o#!<+g^A1URo=z>M)kAsxJlJy-I&&Qh&En%BgpBmU1e7ggQO(;!c;4V-!5y&V;9uPy1P@iyY<%*1MRU)%BvPP?7yT;^jEgyEtXVp>3& zX%|{Uf&C9v3@`aLqc|_xJZ!+`rzK2TekpX0pO$t2IH;mWAz~;7QT;S!0X!{a0X*$m zY!lb^o|a&OT)>X zbUmJ%1yEU2(`BkDJq@!bE>L9w)Kj0T9i=RQR)Y^Ds^iR{^RK9%hPTB3`$;-j{BGH4 zr3f)TPESjhnYEPpXQjDdVhi9T)5lP;dFELFLjrwe0klnM*x>RkfSt+$h-uop6SD5Z zLDH^Qzy@K2?8uroaf_jzh$q5{EhZOGB;Ik)lH)Ik$u=wcgC+hr^XM1_o}56&7n$l2 zqWR|hn1r{SD9d^?<`@|$7Y z#c^BAa9nJF-EO)|?n0p}Hc$;hs+k*3%qF{R0{R{@;mOBRd#GfR3Bw%}q<{gB`wAJ_MQzfBb(E+4{3?>hDb5!T!g?i7GbCjF7&xD7v9AcLL7%rIji|0(Sn4OwT(exIdah$8kVw&K zJFd>&h}6N2#AhUhS)ZbMuehrG3AM`LjO?IJ-0eZ@l(a>Yee!Rj<#4=$q%Z6 zFkvthZab1(^QEh%`O0;wf!NsjvnDbWv_~L^41MKCMi!vb;VagRehsuj&?=<;FkT3R zMuF(e&ib0{7JtP6P)O*r13O|Gik`Ey=Js3#sTTAy7^KOsupsRL)qVDq;F0V1z{B+W zI6Gll-OVuyi#ZFVuvq9m5N1}Yf|a@v)|ka^#{!fuUtva&U$A(DfK-T=bZJT_zg%T8 zN}=HUvi}Z%6q`0Xvp+oi4Ex6Rf#SU3jZM|9?5z39ogg!c4 z(N_y^+Jq%x0JYwT*vwl&@)qs(agnulUuUI9TuW9}I|EiCYrH-Ap}OVZH=R)rAVf13 zU(%8Mh%1t)7#zW#jbCLNATtW1TuJI{pk!O)?RpgYuQzB9cLfRbRo=WMm>i28M~RO&^rLD`uo_EuYjqRvrO$N2L({gF_Tg) zgM@|MepnRVdu#9P3m$okI@L7qs&Y-0yZQ+C-^i)wxb-C7TYL?(K7Q zNazu(MYV>VSY(@S$b3RY2^BFX>^9-1I+a}mkyiaLSe5)Z6^2C3UM&-)H7GD!KATep z(jni18N9Z!sM<0>B!-YY%9>$z5NdG5GUn!7R`-jr!3kLnbvBnPzPWW`A!<-$go|Qc zQgam>Fl6Z_MRx~Mq9FlHuF5}b(XbB#SW;QX;C@j44`Zy$r)AnkZ z3GNgL2d5%|YiHl=VE|+e1JuuHsx|~xQ5Tz-2Hmo4f)X_YqWMQ>$5L8Jl{~@3#`iTr=h%O;h}s# zAm*vk*jwiVfz#NxelN2bu_X9xU_J#lJM&qcGoRTE7v>|bGe^+KIgd#Z?Al3=E)1Ic zzsqYjA1!X`D(3#cX=l@;1()te=NpkP-4ovvBeL-^du}J2J$sC=?d*XEnzQ>B9^o;? z>Bw%`q_fiJO>UAkDcD!sR@8~jQdSg%Z>;lfvm`gG3UAxJp$dCbF0`kE~8zg}H3NV}*<}5Mt!dIR@3uwkpyQ%wP5r!04V z1}&{;4!05@;Z_R(5}yUd%GTW$0KNv3j%3oN?8)a6GYgG=K7b0E!4_G7mV(J==EE*j zED+;>2LuKi6^66qwK3+E1(9Fq5XLhOW zr)s!-Z=mh}nu|f80r~SR8HHvE!avv7grpS^guf`3ud^xbo4Y2o7+4c52CWGw1W*cV z!jwcg5UqySv(+v6YhauFqSZyE{9tnCNBKs}w8rz99^VjCiy0c}iVVpzyfQczE^FC( z6lgZq59@lCEj_;N1H$Hp&XCvDmlE?qk2($X58XDtN%o>JpI;C8cJ+)iZe~3PD2Tzx5 z4OJ*CGCDaPIz=~inZulthbxS@ofeylW{@nL|35?UBCcX}2y?4oQE>Q&4|*6*ax-Vd zZUIgBRx|Bc5Gw&mZ~)lE0%?217d@v}RO~sL#Ah3NnUe#fbP+RpRlT~(Dr0&GxT}Uk z#cPK>E31X3%#f2DmjoCLRz8lkJbPThfFM@LKFhB;+mqh$cM5a&Phm?|>?Cx4M!OJ6 zo^Fmzk2;Agh}7K`F(2SN0hUz@Ly41Hz&BRV+ij6nvoDzwJ%B0C#vYiNws_)kE?z>F z9f$zT@)ng}LQ|SAiHj&oVeTrwIJ0=MIy_hSI_U+76W6L<=}%2gm~a2oTwq|)YaWin z5W|+9@6BOq>ccQ)u=kY3H&zR>DC97IGi1hionLrJF(HlIOE^uK$d2IH4*~pVnrlgg zZ1d3S@b#JPzwRr(R$Xdoz%Tu@6rEk^$o8QSuJs zUE&5PRh8ic|#Qa21H)I&qs7k_9efjU^RA<{90TWt37RB@=kCB7PvM#jXiqo z#-wD78q<8f!k(|O8qIP`4QAKn3dEDcaj%OIEN`}>1dpV+C=bA!&8*p2h)h%+_h*>O z5^vZ4q;RaP!w_})g$_LDJ%OTzb}C{ve*CeW+4zM@7)y!HY^eypbS>>MVCyWyRG5_| zxI_V)P)gE$I(S4M`9@o%0Mj}#ttL^15_V@m<+nxJ$U+1axAMWVgbba#jMdPZHVOp7 zjuh2nj~O-{#97?>PC`({WpP_HNa)tJQn-}}jh@XWZ@z|#@O5e-9MSi{%!Y^5(1mLG z*XlDfVtrODfzPIEk;rVMI2LT;OoB`r5fd*MBpajJPs~wGfIe>Wxd9a=zdQ3}a$BvJ z$`D%&qrq^yt_)Y~0>2j5ex_I%1?%YvJjbywOK4EFu+eEzN9>|27O@Jy(p-2&_1kCj z8zFHDu>#d>+pEA~ENLzegp%oN`lHhf@}GU-SBbW_(tM}%N1BxWNM+XE^$KPn$czNQ z)re@>eUM2oeK9-MNr8e~C48UZ+FNCIJ2Fp3AW{6swE1g>pch!bB?gx)uXq*hZs&7v~ioRv|(3KybtJs z+PE#QT~^@mj0Wkz0f)b0J1(+-W)vkhWq;ZD!p?h1RSTvYY1q;SI%~wIYc&Bzf zh~}%H9f4?9L3O?g+V$X?)?gyjpiGM z2s=I9e}@a^)XW1?M3GSgUC}$>t$0LwoO1$OpHLFJ(!&4HGKF^B5o_b6#YwqTBH|xL zitu5}qk{-AYz3@Uw!UkH@;@yH2?fUNwNh!-!i!M@)MNDBE@lpS=pmwX&`O;Mpdu*T~S4R=j>e*}r zdaQrPxXOq{0wE39>R>5YFBmhs57lEf7A7B3axE|PVZlSM@WV@fTmf|8HrR+wu0 zM&~xkY|0K}mFlt2;_}=g=scjkO2~wDTbGYv~L|zyM~m^2-TY0&{2CH7^Mnl*i1WH0^yBC2ZA^z zfN16lZfylj+Ne#;m6cPMRND)zhQo0tTMHdU#0+4im+G-y#3*QvAruygkCX)H>nkk; zhgy=?xTps4F3EuU8Jjlf!z>u>;+-Q5#b&^$v&7M0O@E<}I#*WJh=?v5F7T+Z?^s3= zjueuXX^C0LgJ3)RVw+Mre0QwF*J5}WGVQ}16+6K}+dzub8SSJU?HFTI0X1uVGXPp* z6?qDUr|VEjY2!a1QM z99_5^0V#Vs1125zG<_kdUK%^|{7Igj5Dz_nRZ2>d#l+U4Gd!_hnaHW#5xl(g>NUyV7$zk`IEfnj5(# zRFC$5mcCiz58VRgUeylEI%wX3;8~?nk#&W8vtCJa$zgrk2%7Wid*Wsar<3L5(7wi^ z%f2SVd-4{~n*^}unB5a{z!wuXrJ7vC8AN~CrL3b$eSn?Gi@+n*?+y9HO#A`Sgh15 zRo~+3=PsmnTkHq7%qx;lU?}E`6c||fJ(hJP@{)Ph6>!gDkp)Q!(tfD9?G(etWnH%{ z{T{J}5d403I^>5gYz(_w(PNwYIDK$^rH!v=AqmQk#lh@!W^WyYe+3f@vgly;y&Cf| z^I<=xF~>+wSRQBmz#4Hb^Tk#l1r_99)hQryx603IaqZbj@qfCXyvZQDHacO<2|xP% zu}X6!rtK3_=@~{Kf=qFnn^> zbepj3vlR}kPt%zDhmX9$llI(MwG0;)3yOAW;i{!WHnOn{L$yWYY(1VVh~^ z>w&MI)ZPQDvme2RK1TA@t&JklG|eoN5?C!KECp739@QCXk%5KZSjCc$sg2waCPJ;k z4ygQBexkS4+hpAOhz${WoicMDCa3GM$m!6riuRy~6=_|uHAuoMJBv30S$e@juOyU0 z31lELzDv(Nset~pT6<*(l0eEjZL%$%g|nJB+PrX&4}_s-?l~K3j-YiVDrBoD@SB6fSp3FduC&~n7btY`bP>wC-@ zpG9lpJ70|S0|UsG^e&CJ>woA`aQ<8G{iPowt6jRJG%KB!18<2X5vFI9{;-us8RTJ4 zeW|v೬_^HF%^gD|!-*o4Wr9a1unwEM^yQ&aooGsI2mnFwaJ70N`PI$Ab@3pEW z;-GVM-Jm|%f0iK4Ke@m82^`*^Js6%LrGTm+OhlS5w=$!uU7$6Qh^;L2>(-?$5$?u!^xi-2&s~Juf(q@~egJGIwk9Ar} z9*k8e$%Cn1U|9!+gkvnb)wyVi%u+~}6omw{Rv#`gvH9f3KQ$ULTQ<{tWN-bdAnbzX zQ;;7;`;5pZ_rn36TpXO+T#GnJCAm0w0?KaniQ2l5cdWz7(lNvoj4$8?2=+K2242$2 z0Wo?zZtY|GLCi{Wra%bjTkR~5BjWOnypJ5x2UD?9OxJvNmdD{~UZ3nND!~I{TvUQ$ zW@owIR0CPGvd{QW6{3SORxxd$G2|qaR_kU{(gI)}lTWgK{G4CiLbVG5om%%Q%?D8Q zicYc0zYwDvFN`xIO-QJs8Fd2Zbn?SuEcBVUDIRL*p=tUr(O;rUB?B?_9YZjGwj*BD zs?($tHTB?9zH@hZiN6RLWUML72+A=`{XRVgwvnPsf{b-)fi9R;)f*BKVG)VcF1iao zJ(%Atwf$Kdc4Wq^<}Xc9l>7;Egn=u(MWnBdu;EI;z`lctMqwBXVlkET_ zGcM3`TNE*Zn<|A!D6L2=shaxbBEOu+9I`rSNAGZD7Sfq8!elaAUJ~VU1Cdsy8QvJp zLKdP=l`KS)MMq4IMC~$vAENK4g`s99;))1t&(m@~ow{Vdxp55inG%vJt;qfMqZf=a z8HNCcQ}esgs*pj}Z>|#3^PhBc7K6Uo-q*-J<$;KWz0eX4@L7!gWhbhjr2`gNVH$W_zP3#Ow}>*kKQV%+6jfeszbpwL?kw2 zSAvyvJUk|RB*c_Fi|%O>B#D)t2cZ0sF*qlV;RK3QEbSweV9cju%Aq9Ol@S7gxKF80 zz&qhF;yJ>hGF`?r2^2IkUAnRrcJv&n4p97*^twx#Z!0?nZdB@oiZWm;#}{4~{@4N( z(m6_a=LmGjGuymg$QJA$VHFr;Afo`-<)-XnI>Bny5O ziD==$YKx2MLAA=W@7TrkiH|GuqnBl=SUvaS$D5gL4xQs#bpm(HW#?^pqV$V1$u!h%nLnD?o)*xEy5oSa)^TlHLmR- zhNt9T4q}}SF{Dv*7T(#VwR-_V4iGh&-_?HrX$mjUPrg_&{%&DJv6 z2u;u)h*Oka&ZNb;(u=zBS8?r`_7XfNpu(jaJ@}Dj?~Ts_k9S5XDKrd8}}3N+Sxh-o463F3auJX}9e`fUaO|LH+w;Tw4iX z5*7h5E=dZCb?H^|*0?rZh>?Xni-e;kmimR3d?m{AWxtFI##w(Zy>f=eVuaVfDG9It zf$jjA*91w`0@HvPacRxCM2WR6FQASxF93kF@L+4W!Gmg*XWua|c=rQ=7f=@PT0Ky^MeqVR zKu*Ij@j>&+u!m;vq2;7*GCskrXs3}QD4O3wl1jgih|(Q_znVhuWvwtH-{21i>qVDiq@Ez?gpdI*fim z!}{xFV-yjoDv7w|$mf?HNI9iel#z0G={|WH{p24xaTIxgv`FjJz?UaHL(%EVnO5{+ zCMfLvrt~=JlIbwSo>mF-2jMKDKQ^|J^-8*ojo$Rv2||(*R?VIR9#Lychj0e+=Cpkv zb1a}(*VLUjdD!IPeu4E>ZF-O??(1Km6KDdZPJFf5iC>RBo@62of0KG(z@_=)H!u>hchS7g<&7nE*5p6@+wK0$fNS+9RvnYBm2#R`!KUp5|E z%X^AlQzz~yXWBP>;bm@0rZC?>{7Qk=g zmEIz|o5*#AOwe(p&^%}%RPPzIZ*$Gm%%awaV@9rdW1kv<)W-(o{flX;8dsiTTcU$S zTYo+mt%@-@35-SRz!7>XqGPue8$3?&)sPS4No-&Z4f`^kg%Oj38c%aAIa*3MSt$h#L^!*5t2p z-Y}?++`=7Z%s8u%-}tF<$uj#d&5*`zHZ8?ec<*SIO@FJmSk#gLv`}{iZ6mnS_E|;E zL=Fq!QfJK6JjE#%5iF*dL^5U{EK!bPiX~;h!&$F{1c;b0&Bb)cz$?;~PmekCpK0Hp{XaTI&vh1V!7C5!7Ntzg%H^Kpall%&B49F&Tl>h#)VquYDs3 zGJilVp;RyL4VIH2u`=FjhD9kr|X zp0F8MK?n79!U})R0-|KsJxDukt8+=nEhR{k$0> zEtMk)`Yz7#^8#6M_pfU{fL|orZ%j$CGH!;T@k;Wzn@iXtXh+4)ZgN(GV2lic$OuKv zNHTQ9lXgoU_d4b07k@wOCn9CnO=i!$TA4>$w?MaZ{J}fm5#WEW=)G2k88E;$juKE| zTMhfddrVjMo~gF?tQ7xX0nech`coqyVCy*{xMh=^X+3qf?4e?iqDp44>I35-7OCIi zeHJYWrmdb}({dL79b2-VC|R<$80-L?tHB0C|B1dlQ*|&JSg~(4!FY>THkkUkI{5s}I>g&+HiHGBr7dn-`ajaIdOL#sx8C zV0>D9%d7@gW68OUnrWmU5Mk|`6-74gBAGj@S%!(Q_Gy$*2|O#-hSj0gf#JD`(q4ONF$-$bc38Um&y;wZXzd%O0uVbYj!rmT2fqW3 zYk#kB{L!#SDd70)XW@9erc0Rpi-H)VCx%Ry4$qFlP-&-vYiM6~jrJe)00M!i3D&q} zAuI}yi!oKe|2BBcqR`%MEji(_tq~Dpo)vh!xTxCdcPXm&r3Gfe5ghR>&Zwf*RH<$U zwz53=-fIieV=N*l2;5MxbHQx?G{y2|x#i?(t=-m62EX$k-gev3Pd~izR7LRLzwc=l zNK7Duy21x#(V15;X>E9Mf=Q|F_5#W-lZBo3M507m&Bmcw=>~ea9FPN2D65Ks|XH+ z7!IjGHZVn9=GN(=RA%#QOMSuPG}kfwt=NyMbL(n` zC)y5UyFqExWAht4jEw^LnDx+HYBlLCCW#JWpckj|Ve2T`9wJrs#(%wgm`mKcWrp+%O;$8E5qd|7A_VGY6NnK*X$|@dWCR9h4jP)tA%&hI*6!|H1 ztUh_b!aJ(H)^pRU#a1N@8>iHv_bptDHyxH_f_~>*E5NNBG(>0KEsdDqW6<9e?$RKt zmhn}CDCyvy^{6@0;v+i}T+_@u1wRtN)xoyiS=~&lbFw0t!wravvw9yb2M){2`6->; z?$g>FFG}6q@nZUCUfbrR`dI9&KbTEur{$1dlxg*o!py`Ym8aWSB2!4&R?pl)*kH5$vY*jvZcnqIg$T(DF1PiABXh~+kgC<8N93}!rherc%%KO zgIGenS%w)7Eo)k4PA!Ds)JnfQu6CB9t_@Rr(Z5WAOQE;6H#}sR=q02YdlN))g;Cfl zG%D?pS-eMK*WDI&-D5G>eCqT!gk39Vg*jcgu)58Bt|d-hXd3H+4#?@D%E{{V6x-mS zL}l%eub4Kv)JB()k?d7?cJovrGcg!|)N+G1;YYlZ(x;yyV0JNDki5$dI<{Ew6LBmc zv6x*E777`O5n|`mhFKO_;|ZvKy8y_Zh%mNlF*bz{8~h>-ImNa&FZ^(`?yK|1*i0By z7-BKD7#=99pe-E=aC~MtrI{Y)0gkp!!uAb!&`}Llisv!V_ckqUEd%>OB%NpP(=vE> zz6|WuiToQa1AC%na3o&__C(9TerOrk4=sb+VyCZbqnio72KvM*yvJxReKF%?J32Hs zU0R?+CEW#LXndzFA^YxDK)isN$Uq@W3xu?iU9a;(iRSoph*xGy3@7W-68 zjbRvK%FhId_*CDRqsYIJ*wb2{w1o)>kmHS&=Qma0w{zqh-Y0;MN1#7qu_Cbrqg+^2=LoiJH6<&W8`d(^{04bCOU) z%Q=Zt%CIpFsV4h%UlZ!h=2w7?^;-%p4j+|jwwn0Jl2%e9HT~2qnPO4yGUFHY?EO`n zCXFMl)IyMRZV#|4jXhGlvF45HcwcXiYlCeDQn-_htC1tvt~6&g(rvr;g)0EDd%Sb@ zohXAna0a<>kU1h2x_R(vwE(K<#BYjBlxE>}Q5_lwcBj1dAugK6*>EXMPx!(H1mx`6 zI?M2Bghc9aVbQ?rKlzTQj4uGW{rGG=0NJjkaayNroQxMB+dV+G?`DzTnx%kI@;o@r z{pA!ypJ07upHK>-HSP&5?s^tD3EDlYbe#S zh}zN3)~vz*YIO4>O}d%+V4G`43qcexM{~nvU&hdkxruz+P=>pL#%07;D`$HfE#o^% z8@tVHpQe<-)O`O6*~+h~cdZgjL!FjJ)n)rvv_et@IdDJupViu?Hbvo<90nbVa)U-$ zE}C@J0uJOr2;?AuX7(DlEXQrvcx~Nx&^n9dqt);9xRCkn@~vmS*zW!L?DBTzON(9p z^^0))teorVw9TeS1~TCWlZed^lz*Y5uR6~rc!TVSnHF!5UNT2Y_?kO}%-P9u#pn6s zO`qpIv;0BUkGVbSpfjf3AQq8%NCxP_`}uKsKmTy9_j6HBpQ7t9x^~V>JCCR7^x<}{ z^YQ>I1zJm76===U6mDzQlK%w>Hj9Z^9ZzL+LAEpDS7JU=r`ZSez@D4^g46~6JUA5j zem;ldy;Vr3nkad`qS_l|#NY+oTHb|NCke5%)RT@Xx(D~RLnq96?m|WPDO{*Aqp8bQ zr7m~5IV^VM&@ON4EaKsSKHpiL`j|%LQVorEN`#%?!%0WvQKPS_BlmEQJeh~{4V^$a z$tYGy6}}}7QvP2W?~wcPi6`Fqz_~9maa<4n;I;2K`ssT=a$WPu6Hk2V*5Cd15j~;E z%*^2ryyX|~e&83byO_L-x+f>r(QiI_dMUhx4-t>izE zxBTII@75EmMYSG&fJ%vBvr&EHwqJjMN|#%u|CEpRSSWV!@```QXuo*$p6KG`XyaCH zX68@6{BdT&@HBZmAN>b4yc>V-fxpaKyfyDr0l1lO-1gZ=K3Nofl@H(GVg>ULzk1|> z$3OkqM`%-UBdgpa|7iD@ESIaYlKg@S>c{^v z`Bwhq#{9{*?TL?98STi>l|?%$NpG#TJWA@Rt?lH#YOVH{KiU;#GsmLZ^*wtAhesMi z>C{*{I5IM}dvL0ejt^ehn2d&ouO1$1q`MBKFK-#tKZ|vXHzqx-)|3LrNb^Ytsty{lt!@7;@Hm%#dZp*rXbz9f>uV1%* z{rU~-H?H5be)IY*>j&0v-O#^b-G=oWHf-3qVbg}q8@6m1*syhD|HgG2*KgdgapT5K z8#iyKCQylKm(flXUC_itXedHv=Mn>TLWw0ZO9Et>~6 zZ{5Rvv_zq_q(`ub49I+dbmz8{4e*ufO=PEHMu4h@f98SQTzDt%pg zlTI|IrYFkKkD{ahDMCt(q7R|ipTYI?XGhUWuD5U<;`;CV`NuUBooHM&-C!DrhtkRM z!QG9tan0UAy4ujUNBQ>Oah*IgIXw34$wQ+<+mPR*=w6=xAlJ`xUCwnW|Gvg=wbQvS ziiWw4ab3^#U0i>F>wD-|h3ju~-^um&xSq~c&tm>n`Pacey%XHGrRQ97;YI1%wP`dq zs-~my>0N@v&{$*A!7&W>?QNvH_YMw^rh5ht438Y5Z_L+!r2ORc`1r^nhO@iT7@D*X z2PY>RQ|ZLu=#`CVSL1RjyL@tLqA_@2TdInu#>Uc-!3kat4NeXEn{;q$s&QZ(aA-yF z?GUR$SWmkR`ebcizJGXhC>{1*=}lN0KQ`Igx5ntz%J@u)R)oS z6O994*;(nqvFVYa0ERhGm{sYa;l{{NlwE&sRC{I5+>(=f2ZzQE()eC_KQT6T03;e8 z89YS$lR~4>-SdO);KZ;|k`nC3M!h|Q0I?F*`Ql(Vj*lJ-{kqv4SD#0TIG=P`>_j*We^Q8Xp@Totlgef)`^4FQ07eF206l z4y&8duIb(T8_drxW^n2d^$!VIMn)Q7XF7O5Pba<99`Ibl6ZS^Y;b%qB<-+gMngsUK zjcq==v^bjX9-Ads_qf6lUU+h1{j1jccY-h}`&`dZS9)&Y#^tKQ9Pe6kjy~e92#t(;HxZY5L9GX3*5WY1{{1f$iT$4)d@q4<~rIxI(BeQQTqEx zm{(hmO_GV*;(oRW_fXNCrSH`Uu3bjPz_@veqR2!J@8NL!abbqus07%h44iZ}+tb@*&E=E}2=r-UHv9q)jp2&0Mx{#a|RnaXp*s zd56Xu6ITyUg5l2gL@~jE@y2dZ_M8^%1#%P9yQfkX2#_9)u4KVAfu56^A&A}>L~j;O z0QEzY>C3Y7FFpT7=a!4@9vsy)8e!I?7y(+a@a{2sK*#1`UJI6eh%v8b%*%h=`TT0G zu-@n*t^-_O!&Ut8R<1w9^;mdsNF$cf(&~Z1YlaU@AAt0ZUOBZlVxC~DV-td!k4Ms< zlgxB{qOp5eaI@uMQj9I>7KRi#X)EaAAhgmcB9BI>~HcxtcsQQqO<$;oMaNO~6#IiPoALTM_jF0W! z%T)qX_*AmGeZ7rkk`qDEpc3uU;EE0~w49dddBKk+Fc#eSU{t%IC&y(dLcxDDmgXuk z;+NM5>1z(ADfZ-~aVWqfy=3RbFWLFxOLv}g7C$f8nY}pMalua2d(MR~+S%-1fJD)+ zp#>Tf+*@eo0^bgfk591VOcFDmb++hAbF8HXjglMckB>~7d>2?Y9A#p#W$l#3&7uYY z6%kWQL~7L8VoDImX=bJP=5nhmIxu+U=}() zgVbY?3G&cY)AV*2zLU#+1%+u5ADhh9XbOFkP+6jl2Zkq2oQ;G@G{Go@s@e3^-pkgi z_jD>3#f($1hJ2VzRSP>Df<5KM3oIInt{fR-P_z5dTDVL^cTZ1Di0tp0R)xrY8lT3bLizLeB%?C7)Bab0)c$iMLmXLh0*WhZ+j_*xgS zq1otu{!Kw<_6(1*99~Z0uxwDvG#FO^x1~ETd*03$zgPqlHO6^bRMxvwy7c1nF1+N@ z=VupO(E8c7^m!Lva!&f93olJCddUSB>^w)WOA8`Qs(83xCB|Ecoe!A3}aCmYr-QI)JAeg{?k@^{JVNNLCM$*qT9ILC@C2=2S8v diff --git a/contracts/sysio.system/src/emissions.cpp b/contracts/sysio.system/src/emissions.cpp index c9f9040af8..9559066077 100644 --- a/contracts/sysio.system/src/emissions.cpp +++ b/contracts/sysio.system/src/emissions.cpp @@ -58,9 +58,6 @@ constexpr sysio::name GOVERNANCE_ACCOUNT = "sysio.gov"_n; constexpr sysio::name CAPEX_OPERATIONS_ACCOUNT = "sysio.ops"_n; constexpr sysio::name TOKEN_CONTRACT = "sysio.token"_n; constexpr sysio::name ROA_CONTRACT = "sysio.roa"_n; -// sysio.reserv holds the swap-fee rewards bucket that payepoch folds into the -// per-epoch batch-operator distribution. -constexpr sysio::name RESERV_CONTRACT = "sysio.reserv"_n; namespace memo { constexpr std::string_view capital = "T5 capital"; @@ -144,34 +141,6 @@ int64_t get_wire_balance(name account) { return acct_tbl.get(key).balance.amount; } -// Local layout-compatible view of sysio.reserv's rewards_bucket singleton. A -// [[sysio::table]]-attributed struct cannot be shared into sysio.system's -// translation unit -- doing so corrupts this contract's read-only-action return -// codegen (getpeerkeys) -- so the layout is mirrored here. The kv row is keyed -// by table name ("rewardbkt") + the reserv account scope, so this reads the -// exact bytes reserv wrote. MUST stay in lockstep with sysio.reserv.hpp's -// rewards_bucket (balance, lifetime_accrued); the cross-contract read is -// exercised end-to-end by t5_emissions_tests/payepoch_folds_swap_fee_rewards, -// which fails if the two layouts ever diverge. -struct reserv_rewards_bucket { - uint64_t balance = 0; - uint64_t lifetime_accrued = 0; - SYSLIB_SERIALIZE(reserv_rewards_bucket, (balance)(lifetime_accrued)) -}; -using reserv_rewardbkt_t = sysio::kv::global<"rewardbkt"_n, reserv_rewards_bucket>; - -// Read the live swap-fee rewards balance held in sysio.reserv's custody. -// Returns 0 when never accrued. Clamps to asset::max_amount because the value is -// later carried as a sysio::asset (drained + transferred as WIRE); the bucket is -// backed by real WIRE so the clamp is only a defensive guard against accounting -// drift constructing an out-of-range asset. -int64_t get_reserv_rewards_balance() { - reserv_rewardbkt_t bkt(RESERV_CONTRACT); - const uint64_t bal = bkt.get_or_default(reserv_rewards_bucket{}).balance; - constexpr uint64_t max_amt = static_cast(sysio::asset::max_amount); - return bal > max_amt ? sysio::asset::max_amount : static_cast(bal); -} - // --------------------------------------------------------------------------- // Payout helpers // --------------------------------------------------------------------------- @@ -826,7 +795,6 @@ void system_contract::payepoch(uint32_t epoch_index, batchepochs_t batch_history(get_self()); std::vector recorded_batch_groups; bool batch_history_complete = accrued_epochs > 0; - bool has_nonempty_batch_roster = false; int64_t recorded_epochs = 0; uint32_t batch_payout_credits = 0; uint64_t expected_epoch_index = state.period_start_epoch; @@ -863,8 +831,6 @@ void system_contract::payepoch(uint32_t epoch_index, batch_history_complete = false; } else { batch_payout_credits = static_cast(credits_with_group); - has_nonempty_batch_roster = - has_nonempty_batch_roster || !it->members.empty(); recorded_batch_groups.push_back(recorded_batch_group{ .members = it->members, .active_epochs = 1, @@ -880,58 +846,11 @@ void system_contract::payepoch(uint32_t epoch_index, && recorded_epochs == accrued_epochs && expected_epoch_index == static_cast(epoch_index) + 1; - // ----- Swap-fee rewards fold-in ----- - // The BATCH-OPERATOR half of collected swap fees accrues in sysio.reserv's - // rewards_bucket. The other half accrues per-underwriter in sysio.reserv and - // is drawn by that account's own `claimuwfee` — it never passes through this - // treasury. (When reserv's `fee_emissions_share_bps` dial is non-zero, that - // configured share of the batch-op half is transferred straight to this - // account at collection time and never enters the bucket; the dial defaults - // to zero, leaving the whole half here.) Fold the whole bucket into THIS - // period's batch-operator distribution so batch ops receive it alongside - // emissions, weighted identically by active-epoch count. - // - // Producers are NOT paid out of swap fees: the fee compensates the parties - // that carry an individual swap — the underwriter who locks collateral for it - // and the batch operators who relay it — while producers earn emissions for - // securing the chain. So `producer_bps` / `batch_op_bps` govern the emission - // `compute_amount` split only, and the entire drained fee pool goes to the - // batch-op distribution below. - // - // The fee WIRE lives in sysio.reserv's custody. Sweep it only when immutable - // roster history is complete and at least one roster can receive a share; - // otherwise leave the bucket in reserv so a later payable period can - // distribute it. When swept, drainrewards is queued FIRST - // (ahead of every payout transfer): inline actions execute depth-first, so the - // drain -- and the reserv->sysio transfer it queues -- run to completion before - // any sibling payout queued after it, landing the WIRE in this account's balance - // first. MUST remain ahead of the first send_wire_transfer below. - // - // Fees are funded by that transfer, NOT the T5 treasury, so fee payouts are - // tracked in `fee_paid` and excluded from total_distributed (which governs - // the emission curve). After a sweep, any amount skipped for an empty roster - // alongside a non-empty one, non-ACTIVE members, or integer-division - // remainders stays in this treasury. Incomplete or all-empty history leaves - // the entire bucket in reserv. - int64_t fee_batch_pool = 0; - if (batch_history_complete && has_nonempty_batch_roster) { - fee_batch_pool = get_reserv_rewards_balance(); - if (fee_batch_pool > 0) { - sysio::action( - {get_self(), "active"_n}, - RESERV_CONTRACT, - "drainrewards"_n, - std::make_tuple(fee_batch_pool) - ).send(); - } - } - // "paid" here means DISTRIBUTED -- credited to `payclaims` for producers / standbys / // batch operators, transferred for the category buckets. Both leave the treasury's // spendable position, which is what these counters feed. int64_t actual_paid = 0; // emission actually distributed (counts toward total_distributed) int64_t batch_emission_paid = 0; - int64_t fee_paid = 0; // swap-fee rewards actually distributed (does NOT count toward treasury) // ======================================================================= // Producer + standby pay. @@ -1128,18 +1047,13 @@ void system_contract::payepoch(uint32_t epoch_index, const int64_t members = static_cast(group.size()); const int64_t group_pool = static_cast( static_cast<__int128>(batch_pool) * active_epochs / accrued_epochs); - const int64_t fee_group_pool = static_cast( - static_cast<__int128>(fee_batch_pool) * active_epochs / accrued_epochs); const int64_t per_member = group_pool / members; - const int64_t fee_per_member = fee_group_pool / members; for (const auto& m : group) { if (!is_op_active(m, OperatorType::OPERATOR_TYPE_BATCH)) continue; - // One credit carries both the emission and the fee share. - credit_pay(get_self(), m, per_member + fee_per_member, memo::batch_op_reward); + credit_pay(get_self(), m, per_member, memo::batch_op_reward); actual_paid += per_member; batch_emission_paid += per_member; - fee_paid += fee_per_member; } }; @@ -1155,7 +1069,6 @@ void system_contract::payepoch(uint32_t epoch_index, } const int64_t batch_emission_retained = batch_pool - batch_emission_paid; - const int64_t batch_fee_retained = fee_batch_pool - fee_paid; // A pay period is the history lifetime. Clear only after an actual accrued // period: on the defensive zero-accrual path the history is preserved rather @@ -1240,10 +1153,10 @@ void system_contract::payepoch(uint32_t epoch_index, .compute_amount = compute_amount, .capex_amount = capex_amount, .governance_amount = governance_amount, - .fee_distributed = fee_paid, + .fee_distributed = 0, // Reserved legacy audit field. .batch_history_complete = batch_history_complete, .batch_emission_retained = batch_emission_retained, - .batch_fee_retained = batch_fee_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.wasm b/contracts/sysio.system/sysio.system.wasm index 0f21cb21fe378a5470aebb35305a3e2183ea33bb..78485cc2bfee401598b881fdfaff28603b17b723 100755 GIT binary patch literal 277387 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 Xx_FPX> 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

lg8zfn@OOn^)P7Z(93H76 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 RTV*mEjkURU@oS>!{{eAVOTz#F literal 202818 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 diff --git a/contracts/sysio.token/sysio.token.wasm b/contracts/sysio.token/sysio.token.wasm index dfecd815cad8ecf3d2f9f413f93b33899801e5af..054b06120a529c0364844aa027473bac23fbcc8a 100755 GIT binary patch literal 12623 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 C>F%)r literal 10465 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 diff --git a/contracts/sysio.tokens/sysio.tokens.wasm b/contracts/sysio.tokens/sysio.tokens.wasm index f69d2e89d80703fa2caf8d42ecc45ae123454617..446db5ad35876f9d9583934a54e7a48c2b5f2861 100755 GIT binary patch literal 33639 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 P#=Ju6HKOEAp7(zMh(SbH literal 30245 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 N{{)r7sb2_!{|_dCSnU7+ diff --git a/contracts/sysio.uwrit/CMakeLists.txt b/contracts/sysio.uwrit/CMakeLists.txt deleted file mode 100644 index 840bca2357..0000000000 --- a/contracts/sysio.uwrit/CMakeLists.txt +++ /dev/null @@ -1,50 +0,0 @@ -set(contract_name sysio.uwrit) -bootstrap_contract(${contract_name}) - -if(BUILD_SYSTEM_CONTRACTS) - find_cdt_magic_enum() - file(GLOB_RECURSE SOURCES src/*.cpp) - file(GLOB_RECURSE HEADERS include/*.hpp) - add_contract(${contract_name} ${contract_name} ${SOURCES}) - set(targets ${contract_name}) - - if("native-module" IN_LIST SYSIO_WASM_RUNTIMES) - list(APPEND targets ${contract_name}_native) - add_native_contract( - TARGET ${contract_name}_native - SOURCES ${SOURCES} - INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include - CONTRACT_CLASS "sysio::uwrit" - HEADERS ${HEADERS} - ABI_FILE ${CMAKE_BINARY_DIR}/contracts/${contract_name}/${contract_name}.abi - ) - endif() - - foreach(target ${targets}) - if(NOT TARGET ${target}) - message(WARNING "Target ${target} not found, skipping include directory setup") - continue() - endif() - - target_include_directories(${target} - PUBLIC - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - $ - ) - - target_link_libraries(${target} - INTERFACE - magic_enum::magic_enum - ) - endforeach() -endif() diff --git a/contracts/sysio.uwrit/README.md b/contracts/sysio.uwrit/README.md deleted file mode 100644 index 5a51475a5b..0000000000 --- a/contracts/sysio.uwrit/README.md +++ /dev/null @@ -1,101 +0,0 @@ -# sysio.uwrit - -Underwriting ledger and swap lifecycle contract. Owns the underwriter COMMIT -race, the collateral lock vector, the swap-from-WIRE escrow queue, and the fee -rate every swap is charged. - -## Responsibility - -- Ingests `SWAP_REQUEST` attestations from outposts and opens an underwrite - request (`uwreqs`) for each. -- Resolves the underwriter COMMIT race: verifies each candidate's signature - against their WIRE account permissions, re-runs the LP variance check, - re-checks available collateral, then picks a winner. -- Writes one collateral lock per required leg and holds it for the full - wall-clock challenge window. **Locks are never released by delivery** — on the - healthy path only `chklocks` sweeps them, once `expires_at_ms` passes. An - UPHELD underwriter-fault challenge ends the window early instead: `sweeplocks` - erases that commitment's locks where they stand, which can be well before - `expires_at_ms`. -- Settles the winning swap against `sysio.reserv` and queues the outbound - `SWAP_REMIT`. -- Escrows and drains swap-from-WIRE requests (`fwqueue`), charging a revert fee - on caller-fault drain failures. -- Owns the swap fee RATE (`uwconfig.fee_bps`). The fee is charged and - distributed by `sysio.reserv` — see "Fees" below. - -## Fees - -`uwconfig.fee_bps` (default 10 = 0.1%) is the **network fee**, taken out of the -**WIRE leg** of every swap. It is not the whole effective fee: each participating -non-WIRE leg's reserve independently charges its own `owner_fee_bps` -(`sysio.reserv::setrsvfee`, in `[MIN_OWNER_FEE_BPS, MAX_OWNER_FEE_BPS]` or zero). -A chain-to-chain swap therefore pays two owner fees plus the network fee; a swap -against a WIRE endpoint pays one plus the network fee. All of them come off the -same WIRE leg, so together they reduce what the recipient receives. - -`sysio.reserv` routes each part as follows — the owner fees to their reserves, -and the network fee through a **two-stage** split: - -| Part | Recipient | Path | -|---|---|---| -| Each reserve's `owner_fee_bps` | That **reserve's owner** | Accrues to the reserve row's `owner_fee_accrued`; drawn by `sysio.reserv::claimrsvfee` | -| 50% of the **network fee** | The swap's **winning underwriter** | Accrues to `sysio.reserv::uwfees`; drawn by that account's own `sysio.reserv::claimuwfee` | -| The other 50% — the **rewards pool** — less `fee_emissions_share_bps` | **Batch operators** | Accrues to `sysio.reserv::rewardbkt`; swept by `sysio.system::payepoch` into the batch-op distribution | -| `fee_emissions_share_bps` of the **rewards pool** | The `sysio` **emissions treasury** | Transferred out at settlement by `route_wire_fee` | - -The 50/50 network split is the fixed `sysio.reserv::FEE_UNDERWRITER_SHARE_BPS`; -the stage-2 share is the governance dial `reserve_config.fee_emissions_share_bps`, -which **defaults to zero**. At that default every part stays in `sysio.reserv`'s -WIRE custody until claimed or drained and no part of a swap fee reaches the -emissions treasury — but a non-zero dial diverts that share of the rewards pool -to the treasury at settlement, so the custody statement holds only at the -default. Producers are never paid out of swap fees at any setting. - -`uwconfig.fromwire_revert_fee_bps` is charged on the refunded escrow when a -queued from-WIRE swap reverts at drain for a cause the caller controls -(unpriceable target, variance tolerance exceeded). A revert has no winning -underwriter, so the whole revert fee becomes the rewards pool — reaching the -rewards bucket in full under the default zero `fee_emissions_share_bps`, less the -configured emissions share when that dial is set. Reverts caused -by system state changes after enqueue (reserve deactivated, flipped private, -chain deregistered) refund in full. - -## Tables - -| Table | Row type | Description | -|-------|----------|-------------| -| `uwconfig` | `uw_config` | Singleton: `fee_bps`, `collateral_lock_duration_ms`, `min_fromwire_amount`, `fromwire_revert_fee_bps`, `uwreq_pending_timeout_epochs`, `uwreq_retention_epochs` | -| `uwreqs` | `uw_request_t` | One row per swap intent — race state in `commits_by`, `winner`, lifecycle status, mirrored `variance_tolerance_bps`. Retained for `uwreq_retention_epochs` after ANY terminal transition — `COMPLETED` (once the last collateral lock is gone — normally when `chklocks` sweeps it at expiry, but earlier when `sweeplocks` erases it on an UPHELD challenge; both run the same `finalize_settled_uwreqs` tail. The reserve settlement itself already happened at winner selection, which is what made the row CONFIRMED), `REJECTED` (immediate failure via `reject_and_refund`), or `EXPIRED` (pending timeout, same path) — then erased by `pruneuwreqs` | -| `locks` | `lock_entry` | Flat per-leg lock vector — the AUTHORITY for locked collateral. `sysio.opreg::available()` does not scan it; it reads the `locksums` cache below. The `byexpire` secondary index lets `chklocks` sweep expired locks oldest-first, up to its per-epoch budget | -| `locksums` | `lock_sum` | Materialized Σ `lock_entry.amount` per `(underwriter, chain_code, token_code)` bucket — the "locked" half of `sysio.opreg::available()`, read O(1) instead of scanning `locks`. Written by exactly three paths, all in this contract: `try_select_winner` ADDS (on a win), `chklocks` DECREMENTS (healthy release at expiry), and `sweeplocks` DECREMENTS (erasing a commitment's held locks on an UPHELD challenge). A bucket's row is erased once its total reaches zero, so an absent row reads as zero. Any new erase path must decrement too — the rollup is authoritative for `available()`, so a bucket left positive after its last row is gone suppresses that collateral permanently | -| `fwqueue` | `fromwire_q` | Escrowed swap-from-WIRE requests awaiting drain. `byepoch` secondary index | -| `uwcounters` | `uw_counters` | Monotonic id allocators (uwreq ids, lock ids) | - -## Actions - -| Action | Auth | Description | -|--------|------|-------------| -| `setconfig` | `sysio.uwrit` | Set the fee rate, lock duration, from-WIRE floor, revert fee, and uwreq lifecycle windows | -| `createuwreq` | `sysio.msgch` | Open an underwrite request from an inbound `SWAP_REQUEST` attestation | -| `rcrdcommit` | `sysio.msgch` | Record an underwriter's per-leg `UNDERWRITE_INTENT_COMMIT` bytes; resolves the race once both legs are present | -| `swapfromwire` | `user` | Escrow WIRE and enqueue a swap-FROM-WIRE request | -| `drainfwq` | `sysio.epoch` or self | Drain the from-WIRE queue: settle what prices, revert the rest (charging the revert fee on caller-fault causes) | -| `chklocks` | `sysio.epoch` or self | Sweep collateral locks whose wall-clock window has expired, oldest-first, EXAMINING at most `max_rows` rows per call (`advance` passes `MAX_LOCK_RELEASE_PER_EPOCH`) — the budget counts rows examined rather than locks released, so held locks and the challenge pokes they generate are bounded by it too; an oversized expiry burst drains across later epochs rather than aborting `advance` | -| `pruneuwreqs` | `sysio.epoch` or self | Expire timed-out PENDING uwreqs and erase terminal rows past their retention window | -| `holdlocks` | `sysio.chalg` | Mark a commitment's winning locks as held by an OPEN underwriter-fault challenge (WIRE-297); held locks are skipped by `chklocks` instead of released | -| `freelocks` | `sysio.chalg` | Clear the hold after a REJECTED or LAPSED challenge, so the next `chklocks` releases the locks normally | -| `sweeplocks` | `sysio.chalg` | Erase a commitment's held locks after an UPHELD challenge — the underwriter is already SLASHED, so each `releaselock` takes its deferred-slash branch. Decrements `locksums` like `chklocks` does | -| `sumlocks` | read-only | Sum an underwriter's active locks for a `(chain, token)` bucket — the lock half of `sysio.opreg::available()` | - -## Dependencies - -- Receives inbound attestations via `sysio.msgch`; queues outbound `SWAP_REMIT` - / `SWAP_REVERT` through it. -- Settles against `sysio.reserv` (`applyswap` / `applyfromwire` / `paywire` / - `refundwire`), forwarding the winning underwriter so the fee accrues to them. -- Reads collateral from `sysio.opreg`; its `locksums` rollup — the O(1) cache - over the authoritative `locks` table — is the lock half of that contract's - `available()`. -- Lock sweeping and queue draining are inlined from `sysio.epoch::advance`. -- The off-chain counterpart is `wire-sysio/plugins/underwriter_plugin/`. diff --git a/contracts/sysio.uwrit/include/sysio.uwrit/sysio.uwrit.hpp b/contracts/sysio.uwrit/include/sysio.uwrit/sysio.uwrit.hpp deleted file mode 100644 index f8b85cccae..0000000000 --- a/contracts/sysio.uwrit/include/sysio.uwrit/sysio.uwrit.hpp +++ /dev/null @@ -1,1007 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sysio { - - /** - * @brief sysio.uwrit — underwriter race resolver + flat lock vector. - * - * Per the data-model refactor (`load-context-and-follow-smooth-flame.md` - * §3.13, §4.5, §4.6): - * - * - opreg owns the bond ledger (per-(operator, chain_code, token_code) aggregate - * balance). uwrit owns the **lock vector** — one row per leg of every - * in-flight UWREQ, plus `locksums` — the per-(underwriter, chain_code, - * token_code) materialized total of those rows. `locks` is the - * authority; `locksums` is the O(1) read cache, and it is what opreg's - * `available()` reads cross-contract to subtract active locks from the - * operator's spendable balance. - * - * - Identity has been rekeyed onto `sysio::slug_name` (uint64). Each - * `lock_entry` carries `(chain_code, token_code, reserve_code)`; the - * `reserve_code` records which specific reserve this leg is bound to - * so a slash-to-reserve hop on a same-(chain, token) pair with - * multiple reserves can route unambiguously. `uw_request_t` carries - * `src_*` and `dst_*` slug_name triples for the same reason. - * - * - The per-underwriter composite lock key can no longer fit in a - * `uint128_t` (3 × uint64 = 192 bits), so per the plan's B.2 design the - * table carries only uint64 secondary indexes and the composite lives - * outside them: - * * `compose_account_chain_token_ck` — `checksum256(account || - * chain_code || token_code)`, computed on the row - * by `lock_entry::by_underwriter_ck()` and used - * as the `locksums` primary key. NOT a - * table-managed secondary index. - * * `byuw` — uint64 split-index keyed on `underwriter.value` - * for cheap per-operator scans (in-memory filter - * on chain_code / token_code / reserve_code). - * * `byuwreq`, `byexpire` — uint64 indexes on `uwreq_id` and - * `expires_at_ms`; the latter is the window - * `chklocks` sweeps. - * - * - On `UNDERWRITE_INTENT_COMMIT` arrival (one per outpost; underwriters - * call `commit(...)` JSON-RPC on each side), `record_commit` registers - * the per-leg arrival in `uwreqs.commits_by`. When BOTH legs land for - * the same underwriter, `try_select_winner` checks the underwriter's - * `sysio.opreg::available(...)` for each chain; if both legs are - * covered, the underwriter wins, two rows are pushed onto `locks`, and - * a `REMIT` is queued to the destination outpost. - * - * - opreg.balance is **not** mutated when a lock is added — the lock - * simply reduces what `available()` rolls up. When a lock releases: - * * SLASHED underwriter — opreg::releaselock decrements balance - * and emits SLASH_OPERATOR (deferred-slash). - * * TERMINATED underwriter — opreg::releaselock decrements balance - * and emits WITHDRAW_REMIT to authex - * destination (deferred-remit). - * * Healthy underwriter — no opreg call; freed amount naturally - * reappears in `available()` once the lock - * row is erased. - */ - class [[sysio::contract("sysio.uwrit")]] uwrit : public contract { - public: - using contract::contract; - - // Well-known accounts - static constexpr name EPOCH_ACCOUNT = "sysio.epoch"_n; - static constexpr name MSGCH_ACCOUNT = "sysio.msgch"_n; - static constexpr name AUTHEX_ACCOUNT = "sysio.authex"_n; - static constexpr name OPREG_ACCOUNT = "sysio.opreg"_n; - static constexpr name CHAINS_ACCOUNT = "sysio.chains"_n; - static constexpr name CHALG_ACCOUNT = "sysio.chalg"_n; - static constexpr name RESERVE_ACCOUNT = "sysio.reserv"_n; - static constexpr name TOKEN_ACCOUNT = "sysio.token"_n; - - // Default number of epochs a TERMINAL (COMPLETED / REJECTED / EXPIRED) - // UWREQ row lives before `pruneuwreqs` erases it. 10 epochs matches the - // bootstrap doc's "losers retained 10 epochs for debugging" requirement. - // Live value is `uwconfig.uwreq_retention_epochs` (retunable via - // `setconfig`); this constant is only the fresh-chain default. - static constexpr uint32_t DEFAULT_UWREQ_RETENTION_EPOCHS = 10; - - // Default number of epochs a PENDING UWREQ may wait for its underwriter - // race to resolve before `pruneuwreqs` expires it (refund/revert + flip - // to UNDERWRITE_REQUEST_STATUS_EXPIRED). A pending request only resolves - // if underwriter commits arrive — an external, optional event — so - // without this deadline an abandoned request is indistinguishable from a - // slow one forever and its row (plus the user's escrowed/deposited - // funds) lingers indefinitely (SEC-129 / WSA-223). Live value is - // `uwconfig.uwreq_pending_timeout_epochs` (retunable via `setconfig`). - static constexpr uint32_t DEFAULT_UWREQ_PENDING_TIMEOUT_EPOCHS = 10; - - // Ceiling `setconfig` accepts for BOTH epoch-count lifecycle knobs - // (pending timeout + retention). Prevents a misconfigured value near - // UINT32_MAX from wrapping the `current_epoch + knob` deadline stamp to - // a tiny epoch index (instant expiry/prune). One million epochs is ~2 - // years at the 60s test cadence and far beyond any real retention need, - // while `current_epoch + 1'000'000` stays astronomically below the - // uint32 wrap point for any reachable epoch index. - static constexpr uint32_t MAX_UWREQ_LIFECYCLE_EPOCHS = 1'000'000; - - // Rows `sysio.epoch::advance` passes to `pruneuwreqs` each epoch. Sized - // like MAX_FWQ_DRAIN_PER_EPOCH (and for the same reason): the sweep runs - // inline inside advance's hard, uncatchable transaction CPU deadline, so - // per-epoch work must be bounded; a backlog simply drains across - // subsequent epochs. - static constexpr uint32_t MAX_UWREQ_PRUNE_PER_EPOCH = 32; - - // Locks `sysio.epoch::advance` passes to `chklocks` each epoch. Sized - // like MAX_UWREQ_PRUNE_PER_EPOCH / MAX_FWQ_DRAIN_PER_EPOCH, and for the - // same reason — but the exposure here is sharper than either, because - // lock EXPIRY is inherently bursty. Every lock is stamped - // `now + collateral_lock_duration_ms` at creation, so a burst of - // settlements inside one epoch produces a burst of expiries inside one - // epoch, exactly one lock-duration later. Sustained swap traffic - // therefore presents `chklocks` with a whole epoch's settlements at - // once, and per expired lock the sweep does an inline - // `opreg::releaselock` dispatch plus an erase — all inside advance's - // hard, uncatchable transaction CPU deadline. - // - // Unbounded, a large enough expiry burst aborts `advance`; and because - // those same locks are still expired at the next advance, it aborts - // identically every epoch thereafter — a PERMANENT chain-wide epoch - // stall rather than a transient one. Bounded, an oversized burst is - // just release latency that drains across subsequent epochs, which is - // harmless: the challenge window has already closed, so a lock freed an - // epoch or two late costs only a brief overstatement of the - // underwriter's reserved collateral. - static constexpr uint32_t MAX_LOCK_RELEASE_PER_EPOCH = 32; - - // ── UWREQ row-growth rails (SEC-129 / WSA-223) ───────────────────────── - // Every uwreqs `modify` re-serializes the whole row inside the - // never-throw evalcons / advance dispatch surfaces, so per-field byte - // caps + a total-row guard keep the serialized row from approaching - // database value limits (where a later consensus-dispatched modify - // would abort and stall the chain). All are protocol backstops sized - // generously above legitimate traffic — violations log + skip, never - // check(). - - // Per-candidate cap on `uw_request_t.commits_by`. rcrdcommit already - // gates entries on ACTIVE underwriters; this bounds the row even if the - // ACTIVE roster grows large. - static constexpr uint32_t MAX_UWREQ_CANDIDATES = 32; - - // Per-leg cap on a stored verbatim `UnderwriteIntentCommit` payload - // (`commit_entry.source_uic_bytes` / `dest_uic_bytes`). A real UIC is a - // handful of ids + codes + a signature `verify_uic_signature` already - // bounds to 1024 bytes — 2 KiB is ~2x the largest legitimate encoding. - static constexpr uint32_t MAX_UIC_LEG_BYTES = 2048; - - // Cap on the stored inbound SwapRequest payload - // (`uw_request_t.attestation_inbound_data`) at createuwreq. - static constexpr uint32_t MAX_ATTESTATION_INBOUND_DATA_BYTES = 4096; - - // Cap on `SwapRequest.source_tx_id` (largest real id today is a 64-byte - // Solana signature; 2x headroom for future chains). - static constexpr uint32_t MAX_SOURCE_TX_ID_BYTES = 128; - - // Cap on the depositor address bytes (`SwapRequest.actor.address`). - // Mirrors swapfromwire's existing 64-byte `recipient_addr` cap — the - // platform's notion of a maximum chain address. - static constexpr uint32_t MAX_DEPOSITOR_BYTES = 64; - - // Belt-and-braces ceiling on the projected serialized uwreqs row before - // any growing `modify`. Structural worst case under the caps above - // (32 candidates x 2 x 2 KiB legs + 4 KiB attestation + fixed fields) - // is ~135 KiB, comfortably inside this guard; the guard exists so no - // future field addition can silently push a row toward chain KV value - // limits. - static constexpr uint32_t MAX_UWREQ_ROW_BYTES = 262'144; - - // Safety rails on the depot-originated swap-from-WIRE queue (SEC-77 / - // WSA-165). `swapfromwire` is public and escrows only the caller's WIRE, - // so without bounds a caller could split real WIRE into an unbounded - // number of system-paid `fwqueue` rows and force `drainfwq` to process - // them all inside one `sysio.epoch::advance` transaction. That - // transaction's CPU budget (~150 ms) is a hard, uncatchable deadline, so - // an oversized queue would abort every advance and permanently stall - // epoch progress chain-wide. - // - // MAX_FWQ_DRAIN_PER_EPOCH bounds the rows drained per advance; undrained - // rows stay queued (escrow safe in reserve custody) and drain a later - // epoch, converting a potential halt into bounded drain latency. - // Ingress is already throttled economically — every `swapfromwire` is a - // `require_auth(user)` transaction that bills the caller CPU/NET and - // escrows real WIRE per row — so the drain bound alone is the liveness - // rail; there is no per-caller row cap. Conservatively sized to stay well - // under the transaction CPU ceiling shared with chklocks / buildenv / - // emissions; raise it (contract upgrade) only if legitimate from-WIRE - // throughput approaches the bound. - static constexpr uint32_t MAX_FWQ_DRAIN_PER_EPOCH = 32; - - // Economic ingress rails on `swapfromwire`, complementing the drain - // bound above. Both are live config (`uwconfig`) retunable via - // `setconfig` without a contract upgrade; the values here are only the - // defaults a fresh chain starts from. - // - // DEFAULT_MIN_FROMWIRE_AMOUNT floors the escrow per queued row (5 WIRE - // at 9 decimals). Dust rows are what made queue spam nearly free: the - // escrow comes back in full at drain, so 1-atomic rows could hold drain - // slots while locking no meaningful capital — and sat below the Bancor - // kernel's pricing floor, guaranteeing zero-quote refunds. With the - // floor, sustaining a D-row backlog keeps D x floor WIRE locked for the - // full queue latency the spammer themselves creates. - // - // DEFAULT_FROMWIRE_REVERT_FEE_BPS is charged on the refunded escrow - // when a queued row reverts at drain for a cause the caller controls - // (unpriceable target / variance tolerance exceeded). The fee is routed - // through the same rewards/emissions split as settlement fees - // (`opp::amm::split_wire_fee` + `sysio.reserv::route_wire_fee`), so - // revert churn pays the system instead of recycling for free. Reverts - // caused by system state changes after enqueue (reserve deactivated, - // flipped private, chain deregistered) refund in full — the caller did - // nothing wrong. Successful swaps are unaffected; they already pay - // `fee_bps` at settlement. - // - // 500 bps (5%) is the launch default (Jonathan, 2026-08-04), replacing the - // 0.1% placeholder that merely mirrored `fee_bps`. It has to be large - // enough that cycling escrows through the queue is not free. It stays - // governance-tunable via `setconfig` (capped at MAX_FEE_BPS), so this is a - // starting point, not a commitment. - static constexpr uint64_t DEFAULT_MIN_FROMWIRE_AMOUNT = 5'000'000'000; // 5 WIRE @ 9 decimals - static constexpr uint32_t DEFAULT_FROMWIRE_REVERT_FEE_BPS = 500; // 5% - - // Maximum accepted swap fee, in basis points. A 100% fee (10000 bps) - // would zero the post-fee WIRE leg of every swap (`net == 0` in - // `opp::amm::split_wire_fee`), which let a from-WIRE or token-to-token - // swap debit destination reserve liquidity while crediting zero WIRE — - // draining the reserve at an arbitrary price (SEC-26 / WSA-042). Any fee - // below 100% leaves a positive remainder for every positive input, so the - // cap is 9999 (mirrors `sysio.reserv::MAX_CONNECTOR_WEIGHT_BPS`). - // - // This bounds THIS rate only. Reserve owner fees are charged off the same - // WIRE leg and capped independently, so the TOTAL can still reach 100% — - // 9999 here plus one owner fee at `sysio.reserv::MIN_OWNER_FEE_BPS` (1) - // does exactly that. The settlement paths' `net > 0` checks are therefore - // live rejections of a configured combination, not dead defense-in-depth; - // see `opp::amm::split_wire_fee`. - static constexpr uint32_t MAX_FEE_BPS = 9999; - - // Upper bound on the collateral lock duration. try_select_winner locks a - // winning underwriter's collateral until `now_ms + collateral_lock_duration_ms`; - // an unbounded duration near UINT64_MAX wraps that sum to a past timestamp, - // releasing the lock immediately. 365 days is far beyond any real settlement - // window yet leaves ~18 orders of magnitude of headroom below the wrap point. - static constexpr uint64_t MAX_COLLATERAL_LOCK_DURATION_MS = - 365ull * 24ull * 60ull * 60ull * 1000ull; - - // ----------------------------------------------------------------------- - // Actions - // ----------------------------------------------------------------------- - - /// Set underwriting fee + lock config. Fields: - /// * `fee_bps` — the NETWORK swap fee charged by the depot per spoke, - /// taken out of the WIRE leg of every swap (so the ETH/SOL the - /// recipient can receive is reduced). It is NOT the whole effective - /// fee: each participating non-WIRE leg's reserve independently charges - /// its own `owner_fee_bps` off the same leg (`sysio.reserv::setrsvfee`). - /// `sysio.reserv` splits the NETWORK component 50/50 between the swap's - /// winning underwriter (claimable via `sysio.reserv::claimuwfee`) and a - /// rewards pool (see `sysio.reserv::FEE_UNDERWRITER_SHARE_BPS`). That - /// pool then splits again by `reserve_config.fee_emissions_share_bps`: - /// that share is transferred to the `sysio` emissions treasury and the - /// remainder accrues to the rewards bucket, which - /// `sysio.system::payepoch` allocates to the batch-operator - /// distribution (paying only eligible shares; the rest stays in the - /// treasury). The dial defaults to zero, so by default the whole pool - /// is allocated to batch operators and no part of a fee leaves - /// `sysio.reserv`'s custody at settlement. - /// * `collateral_lock_duration_ms` — wall-clock milliseconds after - /// `lock_entry.created_at_ms` that the lock auto-expires (swept by - /// `sysio.epoch::advance -> chklocks`). This is the challenge - /// window: collateral stays locked for its full duration — it is - /// never released by delivery. Default 43,200,000 (12 hours); - /// test clusters shorten it via this action. - /// * `min_fromwire_amount` — minimum WIRE escrow (9-decimal atomic - /// units) `swapfromwire` accepts; rows below the floor are rejected - /// at enqueue. Must be positive. Default 5 WIRE. - /// * `fromwire_revert_fee_bps` — fee on the refunded escrow when a - /// queued from-WIRE swap reverts at drain for a caller-controlled - /// cause (see `drainfwq`); routed exactly like the settlement fee. - /// Capped at MAX_FEE_BPS so the post-fee refund stays positive. - /// * `uwreq_pending_timeout_epochs` — epochs a PENDING uwreq may wait - /// for its race to resolve before `pruneuwreqs` expires it - /// (refund/revert + EXPIRED). 1..MAX_UWREQ_LIFECYCLE_EPOCHS. - /// * `uwreq_retention_epochs` — epochs a terminal (COMPLETED / - /// REJECTED / EXPIRED) uwreq row is retained for audit before - /// `pruneuwreqs` erases it. 1..MAX_UWREQ_LIFECYCLE_EPOCHS. - [[sysio::action]] - void setconfig(uint32_t fee_bps, - uint64_t collateral_lock_duration_ms, - uint64_t min_fromwire_amount, - uint32_t fromwire_revert_fee_bps, - uint32_t uwreq_pending_timeout_epochs, - uint32_t uwreq_retention_epochs); - - /// Called inline from `sysio.msgch::dispatch` when a SWAP attestation - /// arrives. Decodes the SwapRequest, prices the swap on the depot's live - /// curve (`sysio.reserve::swapquote`'s kernel), runs the - /// variance-tolerance check, and either: - /// * creates an OPEN UWREQ whose `dst_amount` is that QUOTE — never the - /// caller's `target_amount` (WNS-02) — or - /// * emits a SWAP_REVERT back to `chain_code` and skips UWREQ creation - /// when the gap between `target_amount` and the depot's current quote - /// exceeds `target_tolerance_bps` of that quote, or when every - /// required reserve is ACTIVE yet the curve cannot price the swap. - /// - /// `chain_code` is the source outpost the SWAP came from — needed so - /// the SWAP_REVERT routes back to the user's depositing outpost on - /// variance failure. - [[sysio::action]] - void createuwreq(uint64_t attestation_id, - opp::types::AttestationType type, - uint64_t chain_code, - std::vector data); - - /// Called inline from `sysio.msgch::dispatch` when an - /// UNDERWRITE_INTENT_COMMIT attestation arrives. Pre-validates the - /// claimed underwriter's fixed-size recoverable signature before - /// changing candidate evidence. Invalid claims are logged and ignored: - /// they cannot replace stored bytes, change status/reason, or refresh - /// arrival timestamps. A valid replay of an already-recorded leg never - /// replaces the stored bytes: candidate legs are write-once. Once both - /// legs are stored, an exact replay revalidates the preserved evidence - /// and retries winner selection against current mutable depot state. - /// When both legs - /// land for the same underwriter, runs `try_select_winner` to resolve the - /// race. For a dual-outpost request it revalidates only the older stored - /// leg so a WIRE permission-key change between arrivals cannot authorize - /// stale evidence; the just-verified incoming leg is not recovered twice. - /// A complete candidate is evaluated immediately. Recoverable conditions - /// (temporarily unavailable collateral, identity links, or reserves) keep - /// its evidence on the PENDING row until an outpost replays one of the - /// exact stored UICs; malformed or no-longer-authorized stored evidence is - /// durably disqualified. Replayed UIC bytes never replace stored legs. - /// - /// `(from_chain_code, from_token_code, reserve_code)` together identify - /// which leg of the swap this UIC covers. Same-chain swaps with - /// multiple reserves of the same `(chain, token)` need all three - /// codes to disambiguate src vs dst. - /// - /// `uic_bytes` is the raw zpp_bits-encoded `UnderwriteIntentCommit` - /// payload — the action signature carries bytes, not the proto - /// message itself, per `feedback_no_proto_messages_in_actions.md`. - [[sysio::action]] - void rcrdcommit(uint64_t uwreq_id, - name underwriter, - uint64_t chain_code, - sysio::slug_name from_chain_code, - sysio::slug_name from_token_code, - sysio::slug_name reserve_code, - std::vector uic_bytes); - - /// Sweep all `locks` rows whose `expires_at_ms` has elapsed. Inlined - /// from `sysio.epoch::advance` (as one of its FIRST steps — freshly - /// freed collateral must be visible to the same advance's withdraw - /// flushing); can also be invoked by `sysio.uwrit` itself for manual - /// cleanup. The `byexpire` secondary index walks rows in ascending - /// expiry, so the loop stops at the first unexpired row. - /// - /// For every expired lock the sweep inlines `opreg::releaselock` - /// (deferred-slash for SLASHED underwriters, deferred-remit for - /// TERMINATED, no-op for healthy) and erases the row. When the last - /// lock of a CONFIRMED uwreq is swept, the uwreq flips to COMPLETED - /// (delivery itself is implicit — there is no SWAP_REMIT ack; the - /// lock window expiring IS the settlement horizon). - /// - /// This sweep is the only HEALTHY lock-release path: locks are a - /// wall-clock challenge window (12h default) and are never released by - /// delivery. It is not the only path that ERASES a lock — `sweeplocks` - /// does too, on an UPHELD challenge (see `lock_sum`, whose rollup both - /// must maintain). - /// - /// EXCEPTION (WIRE-297): a lock whose `challenge_id` is non-zero — an - /// underwriter-fault challenge is OPEN against its commitment — is NOT - /// released at expiry. The sweep skips it and instead pokes - /// `sysio.chalg::chkuwchal`, whose resolution either sweeps the locks - /// with the underwriter slashed (`sweeplocks`) or clears the hold - /// (`freelocks`) so the NEXT sweep releases them normally. The epoch - /// tick is thereby the challenge system's only cadence. - /// - /// Budget-bounded, mirroring `pruneuwreqs` / `drainfwq`: walks the - /// `byexpire` index in ascending `expires_at_ms` and EXAMINES at most - /// `max_rows` rows (`max_rows == 0` is a no-op). Inlined from - /// `sysio.epoch::advance` with `MAX_LOCK_RELEASE_PER_EPOCH`; also - /// invocable by `sysio.uwrit` itself with a caller-chosen budget for a - /// manual backlog drain. NEVER throws past the auth gate: it runs inline - /// inside `advance`, where an abort stalls epoch progress chain-wide. - /// - /// The budget counts rows EXAMINED, not locks released — held locks and - /// the challenge pokes they generate cost real work too. Bounding only - /// releases would let an arbitrary number of held rows be scanned and an - /// arbitrary number of distinct challenges fan out an inline - /// `chkuwchal` each, which is exactly the unbounded `advance` work this - /// bound exists to remove. `open_challenges` is therefore bounded by - /// `max_rows` as a consequence of the same counter. - /// - /// Ascending-expiry order makes the bound a FIFO drain, so an oversized - /// burst simply spreads across subsequent epochs. Held locks are the one - /// thing that can sit at the head of that queue without leaving it: they - /// are skipped, not erased, so while more than `max_rows` challenges are - /// open the rows behind them wait. That is bounded and self-clearing - /// rather than a stall — each sweep pokes the challenges it can see, and - /// a resolved challenge removes its locks (`sweeplocks`) or clears their - /// hold (`freelocks`), letting the window advance on the next tick. - - [[sysio::action]] - void chklocks(uint32_t max_rows); - - /// Mark the winning underwriter's locks for `uwreq_id` as held by the OPEN underwriter- - /// fault challenge `chal_id` (WIRE-297). Auth: `sysio.chalg`, inlined from `openuwchal`. - /// This is the AUTHORITATIVE liveness validation for a filing — every lock must still be - /// inside its window and free of any other challenge, and at least one lock must exist — - /// so a stale challenge aborts whole here, bond escrow included. - [[sysio::action]] - void holdlocks(uint64_t uwreq_id, name underwriter, uint64_t chal_id); - - /// Clear the challenge hold on the underwriter's locks for `uwreq_id` — the challenge was - /// REJECTED by the council or LAPSED at the window's end. The locks then release on their - /// next normal `chklocks` sweep: a healthy release, no collateral moves. Auth: - /// `sysio.chalg`. Deliberately a silent no-op when nothing is held: it runs inline from - /// the epoch tick (`chklocks` -> `chkuwchal` -> here), where an abort would stall epoch - /// advancement chain-wide. - [[sysio::action]] - void freelocks(uint64_t uwreq_id, name underwriter); - - /// Release + erase the underwriter's locks for `uwreq_id` after an UPHELD challenge. The - /// underwriter is already SLASHED (`chkuwchal` slashes before sweeping — inline actions - /// run depth-first in send order), so every inlined `opreg::releaselock` takes its - /// deferred-slash branch: the locked collateral is debited and the outbound SLASH - /// attestation queued. Ends with the same COMPLETED-flip / evidence-clear / retention - /// tail `chklocks` runs when a uwreq's last lock leaves. Auth: `sysio.chalg`; silent - /// no-op when no locks remain (same never-throw-inside-advance reasoning as `freelocks`). - [[sysio::action]] - void sweeplocks(uint64_t uwreq_id, name underwriter); - - /// Bounded UWREQ lifecycle sweep (SEC-129 / WSA-223). Inlined from - /// `sysio.epoch::advance` each epoch with `MAX_UWREQ_PRUNE_PER_EPOCH`; - /// also invocable by `sysio.uwrit` itself with a caller-chosen budget - /// for manual backlog drains. Walks the `byexpire` index over rows whose - /// `expires_at_epoch` is non-zero and has elapsed, handling at most - /// `max_rows` rows (`max_rows == 0` is a no-op): - /// - /// * PENDING past its deadline — the underwriter race never resolved - /// inside `uwconfig.uwreq_pending_timeout_epochs`. Refund the source - /// side (SWAP_REVERT to the source outpost, or a full `refundwire` - /// of the from-WIRE escrow), flip the row to EXPIRED, clear its - /// heavy payloads, and re-stamp `expires_at_epoch` for the terminal - /// retention window. - /// * COMPLETED / REJECTED / EXPIRED past retention — erase the row. - /// * CONFIRMED never appears: winner selection zeroes the deadline - /// (the wall-clock lock window owns the row until whichever eraser - /// takes its last lock terminalizes it — `chklocks` at expiry, or - /// `sweeplocks` earlier on an UPHELD challenge). - /// - /// NEVER throws past the auth gate — it runs inline inside `advance`, - /// where an abort stalls epoch progress chain-wide. There are no timers - /// anywhere in this lifecycle: `expires_at_epoch` is passive row data, - /// and this action — a trigger fired by the epoch machinery — is the - /// only thing that ever evaluates it. - [[sysio::action]] - void pruneuwreqs(uint32_t max_rows); - - /// Swap FROM WIRE (the depot is the source chain). `user` escrows - /// `wire_amount` REAL WIRE into `sysio.reserv` custody NOW and the - /// request is QUEUED — no uwreq is created in this transaction. The - /// next `sysio.epoch::advance` drains the queue (`drainfwq`): the - /// target reserve + variance are re-validated authoritatively and a - /// PENDING uwreq with `src = (WIRE, WIRE)` is created for the normal - /// single-leg underwriter race (target leg only). - /// - /// `wire_amount` must meet the configured `min_fromwire_amount` floor - /// (default 5 WIRE) — the floor prices queue slots in locked escrow so - /// dust rows cannot hold the drain hostage for free. Drain-time reverts - /// the caller controls (unpriceable target, variance exceeded) refund - /// the escrow minus `fromwire_revert_fee_bps`; reverts caused by system - /// state changes after enqueue refund in full. - /// - /// `recipient_kind` / `recipient_addr` name the payout address on the - /// target chain (flattened ChainAddress — proto messages never appear - /// in action ABIs). The target reserve must be public: private - /// reserves are excluded from WIRE-endpoint swaps. - [[sysio::action]] - void swapfromwire(name user, - uint64_t wire_amount, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t target_amount, - uint32_t target_tolerance_bps, - opp::types::ChainKind recipient_kind, - std::vector recipient_addr); - - /// Drain the swap-from-WIRE queue. Inlined from `sysio.epoch::advance` - /// each epoch (after the roster attestations are queued, before - /// `buildenv`). Per row: re-validate the target reserve (exists, - /// ACTIVE, not private) and the variance tolerance against the live - /// quote — on failure, refund the user's escrowed WIRE via - /// `reserv::refundwire` (minus `fromwire_revert_fee_bps` when the - /// revert cause is caller-controlled) and drop the row; on success, - /// emplace the - /// PENDING uwreq (id = the queue row's depot-origin id) carrying a - /// synthetic SwapRequest payload so the settlement tail - /// (`emit_swap_remit`) can decode the recipient. NEVER throws. - [[sysio::action]] - void drainfwq(); - - /// Read-only rollup of an underwriter's active lock total on a given - /// `(chain_code, token_code)`. Used by off-chain consumers + (eventually) - /// other contracts that don't rely on opreg's mirror. - [[sysio::action, sysio::read_only]] - uint64_t sumlocks(name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code); - - // ----------------------------------------------------------------------- - // Tables - // ----------------------------------------------------------------------- - - /// Auto-incrementing id-keyed primary key used by `uwreqs`. - struct id_key { - uint64_t id; - uint64_t primary_key() const { return id; } - SYSLIB_SERIALIZE(id_key, (id)) - }; - - /// Per-leg lock row. Rows are pushed by `try_select_winner` and erased - /// by `chklocks` (natural expiry) or `sweeplocks` (an UPHELD - /// underwriter-fault challenge's slash-sweep, before expiry). Each of - /// those erase sites must also decrement `locksums` — see `lock_sum`. - /// - /// The `(underwriter, chain_code, token_code)` triple is the collateral - /// bucket a row rolls up into. 3 × uint64 = 192 bits exceeds - /// `uint128_t`, so the composite is hashed into a `checksum256` via - /// `by_underwriter_ck` — the same digest `locksums` uses to address - /// that bucket's materialized total, which is the O(1) surface opreg's - /// `available()` actually reads cross-contract. A separate - /// `by_underwriter` split-index (uint64 keyed on `underwriter.value`) - /// provides the cheap per-operator scan path for consumers that filter - /// on chain / token / reserve in-memory (per plan §B.2). - /// - /// `reserve_code` records which specific reserve this leg covers; on - /// a slash, the outpost routes seized collateral to that reserve via - /// `ReserveAmount`, even when multiple reserves exist for the same - /// `(chain_code, token_code)` pair. - /// The `(account, chain_code, token_code)` collateral-bucket digest: - /// the three uint64 identities packed little-endian into 24 bytes and - /// hashed. 3 × uint64 = 192 bits does not fit `uint128_t`, so the triple - /// is hashed to land in a `checksum256`. - /// - /// SINGLE SOURCE for that encoding, and it must stay that way. - /// `lock_entry::by_underwriter_ck()` says which bucket a lock row - /// belongs to; `lock_sum_key::primary_key()` addresses that bucket's - /// materialized total. If the two derivations ever diverged, the rollup - /// would be keyed differently from the rows it summarizes and every - /// reader would silently observe zero locked — collateral already - /// committed to a live lock would look spendable. Both call this, so - /// they cannot diverge. - static checksum256 compose_account_chain_token_ck(name account, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - std::array buf{}; - uint64_t acc_v = account.value; - std::memcpy(buf.data() + 0, &acc_v, 8); - std::memcpy(buf.data() + 8, &chain_code.value, 8); - std::memcpy(buf.data() + 16, &token_code.value, 8); - return sysio::sha256(reinterpret_cast(buf.data()), buf.size()); - } - - struct lock_key { - uint64_t lock_id; - uint64_t primary_key() const { return lock_id; } - SYSLIB_SERIALIZE(lock_key, (lock_id)) - }; - - struct [[sysio::table("locks")]] lock_entry { - uint64_t lock_id = 0; - uint64_t uwreq_id = 0; - name underwriter; - sysio::slug_name chain_code; - sysio::slug_name token_code; - sysio::slug_name reserve_code; - uint64_t amount = 0; - uint64_t created_at_ms = 0; - /// `created_at_ms + uwconfig.collateral_lock_duration_ms`, computed - /// at insert time in `try_select_winner`. Wall-clock — the lock is - /// the 12h challenge window and is independent of epoch cadence - /// (epochs can stretch when `advance` gate-blocks). Indexed via - /// `byexpire` so `chklocks` sweeps expired locks in ascending order. - uint64_t expires_at_ms = 0; - - /// Which collateral bucket this lock belongs to — see - /// `compose_account_chain_token_ck`, the single source of that - /// encoding, shared with `lock_sum_key::primary_key()`. - checksum256 by_underwriter_ck() const { - return compose_account_chain_token_ck(underwriter, chain_code, token_code); - } - /// Non-zero while an underwriter-fault challenge (the `sysio.chalg::uwchals` row id) is - /// OPEN against this lock's commitment (WIRE-297). A held lock is NOT released at - /// `expires_at_ms`: `chklocks` skips it and instead pokes the challenge's tally crank, - /// so the collateral stays at risk until the council resolves or the challenge lapses. - /// Stamped by `holdlocks`, cleared by `freelocks` (reject/lapse); an UPHELD challenge - /// erases the row through `sweeplocks` instead. - uint64_t challenge_id = 0; - - /// Split-index for cheap per-operator scans (plan §B.2). Callers - /// pull all rows for a given underwriter and filter on - /// chain_code / token_code / reserve_code in memory. - uint64_t by_underwriter() const { return underwriter.value; } - uint64_t by_uwreq() const { return uwreq_id; } - uint64_t by_expires_at_ms() const { return expires_at_ms; } - - SYSLIB_SERIALIZE(lock_entry, - (lock_id)(uwreq_id)(underwriter)(chain_code)(token_code)(reserve_code) - (amount)(created_at_ms)(expires_at_ms)(challenge_id)) - }; - - // Per plan §B.2: split-index approach — keep only uint64 secondary - // indexes. `by_underwriter_ck` (checksum256) is computed on the row - // when needed for cross-contract composite comparisons, but is NOT a - // table-managed secondary index (Antelope KV's secondary-index - // templates expect fixed-width integer keys). - // - // This table is the AUTHORITY for locked collateral — one row per live - // lock leg, and the only thing that can settle what is actually locked. - // `locksums` is the O(1) read cache derived from it, one row per - // (underwriter, chain_code, token_code) bucket; `sysio.opreg`'s - // `available()` reads THAT cross-contract, not this table. - // - // It used to scan `byuw` (uint64) and filter (chain_code, token_code) - // in memory, on the assumption that underwriters hold O(1) concurrent - // locks. That assumption is false: locks are held for the full - // wall-clock challenge window and are never released by delivery, so a - // bucket's live count is (settlement rate × lock duration). See - // `lock_sum` for the three sites that maintain the cache, and why a - // missed one is permanent rather than merely stale. - using locks_t = sysio::kv::table<"locks"_n, lock_key, lock_entry, - sysio::kv::index<"byuw"_n, - sysio::const_mem_fun>, - sysio::kv::index<"byuwreq"_n, - sysio::const_mem_fun>, - sysio::kv::index<"byexpire"_n, - sysio::const_mem_fun> - >; - - /// Primary key of `locksums`: one (underwriter, chain_code, token_code) - /// collateral bucket, addressed by the SAME digest - /// `lock_entry::by_underwriter_ck()` uses to say which bucket a lock row - /// belongs to — both call `compose_account_chain_token_ck`. - struct lock_sum_key { - name underwriter; - sysio::slug_name chain_code; - sysio::slug_name token_code; - checksum256 primary_key() const { - return compose_account_chain_token_ck(underwriter, chain_code, token_code); - } - SYSLIB_SERIALIZE(lock_sum_key, (underwriter)(chain_code)(token_code)) - }; - - /// Materialized Σ `lock_entry.amount` for one (underwriter, chain_code, - /// token_code) bucket — the "locked" half of `sysio.opreg::available()`. - /// - /// A CACHE of the `locks` table with exactly ONE writer: every code path - /// that can change a bucket's total lives in this contract. There are - /// THREE, and any new one inherits the same obligation — nothing - /// structural enforces it: - /// - /// * `try_select_winner` — ADDS, one lock per required leg, on a win. - /// * `chklocks` — DECREMENTS, releasing locks at expiry. - /// * `sweeplocks` — DECREMENTS, erasing the held locks of a commitment - /// whose underwriter-fault challenge was UPHELD (WIRE-297). This one - /// runs OUTSIDE `chklocks`, which is exactly why it was missed once: - /// this block previously said `chklocks` was the sole erase path, and - /// `sweeplocks` erased rows without decrementing. - /// - /// Getting that wrong is permanent and silent rather than merely stale: - /// the rollup is authoritative for `sysio.opreg::available()`, so a bucket - /// left positive after its last row is gone suppresses that collateral - /// forever — nothing decrements it again, because the rows that would - /// have are already erased. - /// - /// A row is erased when its total reaches zero, so an absent row reads as - /// zero and the table holds only live buckets. - /// - /// It exists because the derivation it replaces does not scale. Both - /// `sum_locks_inline` rollups (here and in sysio.opreg) previously - /// walked every lock row an underwriter held, each documenting the - /// assumption that "per-underwriter lock counts are O(1)-ish so the scan - /// is cheap". That is false under sustained swap traffic: locks are held - /// for the full wall-clock challenge window - /// (`collateral_lock_duration_ms`, 12h default) and are NEVER released - /// by delivery, so a bucket's live lock count is - /// (settlement rate × lock duration) — unbounded within the window. The - /// scan ran per candidate inside `try_select_winner` (up to - /// MAX_UWREQ_CANDIDATES of them per uwreq), i.e. inside the same - /// consensus-dispatch CPU budget whose overrun stalls the chain. - /// - /// `sumlocks` reads this rollup, so it stays the cheap external answer - /// to "how much of this bucket is locked"; the authoritative recompute - /// is the `locks` table itself, which the contract tests scan and - /// compare against this total. - struct [[sysio::table("locksums")]] lock_sum { - name underwriter; - sysio::slug_name chain_code; - sysio::slug_name token_code; - uint64_t amount = 0; - SYSLIB_SERIALIZE(lock_sum, (underwriter)(chain_code)(token_code)(amount)) - }; - - using locksums_t = sysio::kv::table<"locksums"_n, lock_sum_key, lock_sum>; - - /// Per-underwriter race entry inside an UWREQ row. Tracks when each - /// leg of a dual-COMMIT pair arrived so `try_select_winner` can - /// resolve the race deterministically. Each leg's COMMIT is an - /// independent attestation with its own chain_code + uw_ext_chain_addr - /// (signed external-chain signer metadata for that leg) + signature over - /// the whole UIC. The depot stores the full UIC bytes per leg so - /// `try_select_winner` can reconstruct the signed digest verbatim and - /// verify a canonical fixed-size recoverable K1, R1, EM, or ED signature - /// against the underwriter's WIRE account `active` or `owner` permission. - /// - /// `commit_entry` does NOT carry codenames — the per-leg - /// `(chain_code, token_code, reserve_code)` identity is on the - /// surrounding `uw_request_t::src_*` / `dst_*` fields; the - /// commit_entry slot is solely a race-tracker. - struct commit_entry { - name underwriter; - /// Source-leg COMMIT. `source_uic_bytes` is the verbatim zpp_bits - /// serialization of the `UnderwriteIntentCommit` proto received from - /// the source-side outpost; the bytes include the underwriter's - /// signature in the `signature` field. Empty until the source-leg - /// arrives. - uint64_t source_received_at_ms = 0; - uint64_t source_outpost_id = 0; - std::vector source_uic_bytes; - /// Destination-leg COMMIT. Same shape, populated when the dest-side - /// outpost's relay arrives. - uint64_t dest_received_at_ms = 0; - uint64_t dest_outpost_id = 0; - std::vector dest_uic_bytes; - /// Race outcome — INTENT_SUBMITTED (initial/retryable), - /// INTENT_CONFIRMED (winner), DISQUALIFIED (durably invalid stored - /// evidence, such as a signature invalidated by key rotation), or - /// RELEASED (clean loser, retained for audit). A new matching - /// `rcrdcommit` cannot rewrite or re-arm a DISQUALIFIED entry. An - /// exact replay can re-evaluate only an INTENT_SUBMITTED entry. The - /// reused protobuf enum also contains SLASHED, but commit entries - /// never write that value; economic slash state belongs to lock and - /// operator settlement. - opp::types::UnderwriteStatus status = opp::types::UNDERWRITE_STATUS_INTENT_SUBMITTED; - std::string reason; - - SYSLIB_SERIALIZE(commit_entry, - (underwriter) - (source_received_at_ms)(source_outpost_id)(source_uic_bytes) - (dest_received_at_ms)(dest_outpost_id)(dest_uic_bytes) - (status)(reason)) - }; - - /// UWREQ row — one per inbound SWAP attestation. Tracks the swap's - /// src/dst pairs, the underwriter race, and the eventual settlement. - /// - /// Each side of the swap carries a full `(chain_code, token_code, - /// reserve_code)` triple per the data-model refactor: identity - /// is slug_name-keyed throughout, and `reserve_code` lets a same- - /// `(chain, token)` swap target a specific reserve when multiple - /// reserves exist for that pair. - struct [[sysio::table("uwreqs")]] uw_request_t { - uint64_t id; - opp::types::AttestationType type; - opp::types::UnderwriteRequestStatus status; - - /// Src / dst of the cross-chain swap. Populated by `createuwreq` - /// from the decoded SwapRequest. Used by `try_select_winner` to - /// validate per-leg bond coverage. - /// - /// `dst_amount` IS the AMM quote — the destination amount the - /// underwriter must deliver and the amount `sysio.reserv` debits at - /// settlement. It is **never** the caller's `SwapRequest.target_amount` - /// (WNS-02): the target is an unauthenticated expectation carried over - /// OPP, and paying it out let a caller name any figure and drain the - /// destination reserve. `createuwreq` / `drainfwq` seed it with the - /// quote at ingestion; `try_select_winner` re-prices on the live curve - /// and overwrites it with the price the books actually move at, after - /// checking the drift against `variance_tolerance_bps`. - sysio::slug_name src_chain_code; - sysio::slug_name src_token_code; - sysio::slug_name src_reserve_code; - uint64_t src_amount = 0; - sysio::slug_name dst_chain_code; - sysio::slug_name dst_token_code; - sysio::slug_name dst_reserve_code; - uint64_t dst_amount = 0; - /// The destination amount the caller ASKED for — `SwapRequest.target_amount` - /// (outpost-originated) or `fromwire_q::target_amount` (swap-from-WIRE). - /// - /// This is an expectation, never an instruction: it is the fixed - /// reference the slippage check measures against, and it is NEVER paid - /// out. Settlement uses `dst_amount` (the AMM quote) exclusively. - /// - /// It is retained on the row precisely so `try_select_winner` can - /// compare the LIVE settlement quote against the user's ORIGINAL bound. - /// Comparing instead against the previous quote (what `dst_amount` - /// holds before the race re-prices) would compound the tolerance across - /// the two checkpoints — a 10% tolerance accepting a 91 quote at - /// ingestion and an 83 quote at settlement, 17% below a target of 100 — - /// and would skip the bound entirely on a row created while no LP was - /// provisioned (`dst_amount == 0`). - uint64_t target_amount = 0; - /// Variance tolerance the user accepted at SWAP_REQUEST time, in - /// basis points (50 = 0.5%). The allowance it produces is a fraction - /// of the **AMM quote**, never of the user's `target_amount` (WNS-02) - /// — see `variance_allowance` in the implementation — and is clamped - /// to 100%. The depot's createuwreq path validates the ingestion quote - /// against it; `try_select_winner` re-validates the live quote at - /// race-resolution time so drift between ingestion and race doesn't - /// silently move the settlement price past what the user accepted. - uint32_t variance_tolerance_bps = 0; - - /// Source-chain id of the deposit transaction that funded this - /// swap (ETH: 32-byte tx hash; SOL: 64-byte signature). Used by - /// the off-chain underwriter plugin's `verify_source_deposit` - /// step to confirm a real on-chain deposit backs the swap - /// before committing collateral. `createuwreq` rejects any - /// SwapRequest with an empty `source_tx_id` (emits SwapRevert - /// for refund) — every outpost must populate this field at - /// swap-emit time. - std::vector source_tx_id; - - /// Depositor's address on the source chain (decoded from - /// `SwapRequest.actor.address`). ETH = 20 bytes (left-padded in - /// 32-byte ABI slots when matched); SOL = 32-byte Ed25519 - /// pubkey. The underwriter plugin matches this against the - /// `tx.from` (ETH) / fee-payer (SOL) of the source-deposit tx - /// during verification. - std::vector depositor; - - /// Race state. - std::vector commits_by; - name winner; - uint64_t committed_at_ms = 0; - uint64_t settled_at_ms = 0; - /// The row's lifecycle deadline, evaluated (lazily, at trigger time) - /// by `pruneuwreqs` via the `byexpire` index. Semantics by status: - /// * PENDING — `creation_epoch + uwconfig.uwreq_pending_timeout_epochs`; - /// past it the race is abandoned (refund + EXPIRED). - /// * CONFIRMED — 0 (no deadline): the wall-clock lock window owns - /// the row; `chklocks` always terminalizes it. - /// * terminal — `terminal_epoch + uwconfig.uwreq_retention_epochs`; - /// past it the row is erased. - /// 0 means "no deadline, never swept" (same zero-sentinel predicate - /// as `sysio.dclaim::flushexpired`). - uint32_t expires_at_epoch = 0; - - /// Inbound attestation payload (zpp_bits-encoded protobuf). - std::vector attestation_inbound_data; - - /// Outbound attestation payload reserved for future flows where - /// uwrit emits its own response (e.g. underwriter intent acks). - /// Empty until that flow lands. - std::vector attestation_outbound_data; - - /// Non-zero while an underwriter-fault challenge (the `sysio.chalg::uwchals` row id) - /// is OPEN against this request's commitment — stamped by `holdlocks`, cleared by - /// `freelocks`, alongside the identical marker on each held lock. - /// - /// `pruneuwreqs` refuses to erase a row carrying one. The lock marker alone is not - /// enough: the locks hold the COLLATERAL, but this row holds the EVIDENCE a challenge - /// adjudicates against — `attestation_inbound_data` (the stored SwapRequest), - /// `source_tx_id`, and `commits_by` with the per-leg UIC bytes. Terminal rows are - /// erased at `terminal_epoch + uwreq_retention_epochs`, which is far shorter than the - /// challenge window, so without this a challenge could outlive the record it exists - /// to adjudicate and resolve against nothing. - uint64_t challenge_id = 0; - - uint64_t by_status() const { return magic_enum::enum_integer(status); } - uint64_t by_winner() const { return winner.value; } - uint64_t by_expires_at_epoch() const { return expires_at_epoch; } - - SYSLIB_SERIALIZE(uw_request_t, - (id)(type)(status) - (src_chain_code)(src_token_code)(src_reserve_code)(src_amount) - (dst_chain_code)(dst_token_code)(dst_reserve_code)(dst_amount) - (target_amount)(variance_tolerance_bps)(source_tx_id)(depositor) - (commits_by)(winner)(committed_at_ms)(settled_at_ms)(expires_at_epoch) - (attestation_inbound_data)(attestation_outbound_data)(challenge_id)) - }; - - using uwreqs_t = sysio::kv::table<"uwreqs"_n, id_key, uw_request_t, - sysio::kv::index<"bystatus"_n, - sysio::const_mem_fun>, - sysio::kv::index<"bywinner"_n, - sysio::const_mem_fun>, - sysio::kv::index<"byexpire"_n, - sysio::const_mem_fun> - >; - - /// Singleton holding the next-issued `lock_id` + the depot-origin - /// swap-from-WIRE sequence. Keeps both auto-increments monotonic - /// across action calls. - struct [[sysio::table("uwcounters")]] uw_counters { - uint64_t next_lock_id = 1; - /// Sequence for depot-originated (swap-from-WIRE) uwreq ids. The - /// issued id is `0x8000000000000000 | seq` — the high bit - /// partitions the depot-origin id space away from inbound - /// attestation ids (msgch's `mint_att_id` counts monotonically - /// from 1 and can never reach 2^63), so `fwqueue` row ids double - /// as collision-free uwreq ids. - uint64_t next_fromwire_seq = 0; - SYSLIB_SERIALIZE(uw_counters, (next_lock_id)(next_fromwire_seq)) - }; - - using uwcounters_t = sysio::kv::global<"uwcounters"_n, uw_counters>; - - /// Queued swap-from-WIRE request. Written by `swapfromwire` (which - /// escrows the user's WIRE in the same transaction), drained by - /// `drainfwq` on the next `sysio.epoch::advance`. The row id is the - /// depot-origin uwreq id the drained request will be created under. - struct fw_key { - uint64_t id; - uint64_t primary_key() const { return id; } - SYSLIB_SERIALIZE(fw_key, (id)) - }; - - struct [[sysio::table("fwqueue")]] fromwire_q { - uint64_t id = 0; - name user; - uint64_t wire_amount = 0; - sysio::slug_name dst_chain_code; - sysio::slug_name dst_token_code; - sysio::slug_name dst_reserve_code; - uint64_t target_amount = 0; - uint32_t variance_tolerance_bps = 0; - opp::types::ChainKind recipient_kind = opp::types::CHAIN_KIND_UNKNOWN; - std::vector recipient_addr; - uint32_t created_at_epoch = 0; - - uint64_t by_epoch() const { return created_at_epoch; } - - SYSLIB_SERIALIZE(fromwire_q, - (id)(user)(wire_amount) - (dst_chain_code)(dst_token_code)(dst_reserve_code) - (target_amount)(variance_tolerance_bps) - (recipient_kind)(recipient_addr)(created_at_epoch)) - }; - - using fwqueue_t = sysio::kv::table<"fwqueue"_n, fw_key, fromwire_q, - sysio::kv::index<"byepoch"_n, - sysio::const_mem_fun> - >; - - /// Fee + lock-duration configuration singleton. `fee_bps` is the per-spoke - /// NETWORK swap fee, charged out of the WIRE leg — the reserve owner fees - /// charged alongside it live on their own reserve rows - /// (`sysio.reserv::reserve::owner_fee_bps`), not here. No fee-DISTRIBUTION - /// shares live here either: the underwriter/rewards split of the network - /// fee is fixed in `sysio.reserv` (FEE_UNDERWRITER_SHARE_BPS), and the - /// rewards pool's own emissions share is that contract's governance dial - /// (`reserve_config.fee_emissions_share_bps`). - struct [[sysio::table("uwconfig")]] uw_config { - uint32_t fee_bps = 10; // 0.1% per spoke - /// Wall-clock collateral lock duration — the challenge window. - /// Locks are NEVER released by delivery; they expire this many ms - /// after creation and are swept by `chklocks` at epoch advance. An - /// UPHELD challenge cuts the window short — `sweeplocks` erases the - /// commitment's locks where they stand. Default 12 hours. - uint64_t collateral_lock_duration_ms = 43'200'000; - /// Minimum WIRE escrow accepted by `swapfromwire` (atomic units, - /// 9 decimals) — the queue-slot price floor. - uint64_t min_fromwire_amount = DEFAULT_MIN_FROMWIRE_AMOUNT; - /// Fee (bps of the escrow) on caller-controlled drain-time reverts, - /// routed like the settlement fee. <= MAX_FEE_BPS. - uint32_t fromwire_revert_fee_bps = DEFAULT_FROMWIRE_REVERT_FEE_BPS; - /// Epochs a PENDING uwreq may wait for its underwriter race before - /// `pruneuwreqs` expires it (refund/revert + EXPIRED). The deadline - /// is stamped per row at creation; retuning is not retroactive. - uint32_t uwreq_pending_timeout_epochs = DEFAULT_UWREQ_PENDING_TIMEOUT_EPOCHS; - /// Epochs a terminal (COMPLETED / REJECTED / EXPIRED) uwreq row is - /// retained for audit before `pruneuwreqs` erases it. Stamped per - /// row at the terminal transition; retuning is not retroactive. - uint32_t uwreq_retention_epochs = DEFAULT_UWREQ_RETENTION_EPOCHS; - SYSLIB_SERIALIZE(uw_config, (fee_bps)(collateral_lock_duration_ms) - (min_fromwire_amount)(fromwire_revert_fee_bps) - (uwreq_pending_timeout_epochs)(uwreq_retention_epochs)) - }; - - using uwconfig_t = sysio::kv::global<"uwconfig"_n, uw_config>; - - private: - - using UnderwriteRequestStatus = opp::types::UnderwriteRequestStatus; - using UnderwriteStatus = opp::types::UnderwriteStatus; - using ChainKind = opp::types::ChainKind; - using AttestationType = opp::types::AttestationType; - }; - -} // namespace sysio diff --git a/contracts/sysio.uwrit/src/sysio.uwrit.cpp b/contracts/sysio.uwrit/src/sysio.uwrit.cpp deleted file mode 100644 index ef84b1b387..0000000000 --- a/contracts/sysio.uwrit/src/sysio.uwrit.cpp +++ /dev/null @@ -1,2783 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace sysio { - -using opp::types::AttestationType; -using opp::types::UnderwriteRequestStatus; -using opp::types::UnderwriteStatus; -using opp::types::OperatorStatus; -using opp::types::OperatorType; -using opp::types::ReserveStatus; -using opp::types::ChainKind; -using opp::attestations::SwapRequest; - -namespace { - -// System-owned rows bill to the sysio RAM pool, not this contract account (privileged-contract -// model, as sysio.token uses): the account stays finite at code+abi size; growth draws from the pool. -constexpr name ram_payer = "sysio"_n; - -/// `reserv::refundwire` fee argument for full refunds — refunds whose cause -/// the caller does not control (system state changed after enqueue, uwreq -/// rejected at resolution) forfeit nothing. -constexpr uint32_t REFUND_FEE_EXEMPT_BPS = 0; - -/// High bit partitions depot-originated (swap-from-WIRE) uwreq ids from -/// inbound attestation ids — msgch's `mint_att_id` counts monotonically -/// from 1 and can never reach 2^63, so the two id spaces are disjoint. -constexpr uint64_t DEPOT_ORIGIN_ID_BASE = 0x8000000000000000ULL; - -/// Fixed-size, recoverable `sysio::signature` variants accepted at the UIC -/// protocol boundary. WebAuthn is deliberately excluded because its variable -/// fields require a hostile-byte parser; BLS is fixed-size but not recoverable. -/// The enum values are the packed variant tags and `sysio::signature` indices. -enum class uic_signature_variant : uint8_t { - k1 = 0, - r1 = 1, - wa = 2, - em = 3, - ed = 4, - bls = 5, -}; - -static_assert(std::variant_size_v == 6); -static_assert(std::is_same_v, - sysio::webauthn_signature>); -static_assert(std::is_same_v, - sysio::ed_signature>); -static_assert(std::is_same_v, - sysio::bls_signature>); -// K1, R1, and EM intentionally share ecc_signature as their alternative type, -// so type-identity assertions cannot detect a permutation of those indices. -// sysio.dispatch_tests pins their concrete packed tags and exercises real -// contract recovery/permission authorization for all four accepted variants. - -constexpr size_t K1_SIGNATURE_VARIANT_INDEX = - magic_enum::enum_integer(uic_signature_variant::k1); -constexpr size_t R1_SIGNATURE_VARIANT_INDEX = - magic_enum::enum_integer(uic_signature_variant::r1); -constexpr size_t EM_SIGNATURE_VARIANT_INDEX = - magic_enum::enum_integer(uic_signature_variant::em); -constexpr size_t ED_SIGNATURE_VARIANT_INDEX = - magic_enum::enum_integer(uic_signature_variant::ed); - -/// Every accepted variant index is encoded as one varuint byte. -constexpr size_t PACKED_SIGNATURE_TAG_SIZE = 1; - -/// Exact packed sizes of the one-byte variant tag plus each fixed signature -/// body. K1, R1, and EM share the 65-byte ECC representation; ED embeds its -/// public key in a 96-byte body. -constexpr size_t PACKED_ECC_SIGNATURE_SIZE = - PACKED_SIGNATURE_TAG_SIZE + std::tuple_size_v; -constexpr size_t PACKED_ED_SIGNATURE_SIZE = - PACKED_SIGNATURE_TAG_SIZE + std::tuple_size_v; - -/// Stable action-trace marker for malformed or unauthorized UIC signatures. -constexpr const char* UIC_SIGNATURE_REJECTED_LOG_PREFIX = "UIC_SIGNATURE_REJECTED"; - -/// Prefix used in the durable commit-entry rejection reason. -constexpr const char* UIC_SIGNATURE_REJECTED_REASON_PREFIX = - "invalid underwrite-intent-commit "; - -/// Separates the rejected leg from its typed verification result. -constexpr const char* UIC_SIGNATURE_REJECTED_REASON_SEPARATOR = " signature: "; - -/// Fixed serialized-size allowance for a NEW `commit_entry`'s non-vector -/// fields (underwriter name, two timestamp/outpost-id pairs, status, empty -/// reason + the vectors' length prefixes) in rcrdcommit's projected-row-size -/// guard. The true packed size is ~50 bytes; 128 over-counts on purpose — -/// the guard may only ever be tighter than reality, never looser. -constexpr size_t COMMIT_ENTRY_PACK_ALLOWANCE_BYTES = 128; - -uint64_t current_time_ms() { - return static_cast(current_time_point().sec_since_epoch()) * 1000; -} - -uint32_t get_current_epoch() { - sysio::epoch::epochstate_t es(uwrit::EPOCH_ACCOUNT); - if (!es.exists()) return 0; - return es.get().current_epoch_index; -} - -/// Sum the underwriter's pending withdraws on opreg for the given -/// `(chain_code, token_code)`. Per the split-index design: -/// `opreg::wtdwqueue_t` exposes only uint64 secondary indexes. The `byaccount` -/// index keys on `account.value`; rows are filtered on `(chain_code, -/// token_code)` in memory. Per-account pending-withdraw counts are O(1)-ish -/// so the scan is cheap. -uint64_t opreg_pending_withdraws(name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - opreg::wtdwqueue_t queue(uwrit::OPREG_ACCOUNT); - auto idx = queue.template get_index<"byaccount"_n>(); - - uint64_t total = 0; - auto it = idx.lower_bound(underwriter.value); - auto end = idx.upper_bound(underwriter.value); - for (; it != end; ++it) { - if (it->chain_code != chain_code || it->token_code != token_code) continue; - // Saturating: amounts are uncapped uint64 (external-chain values); a - // wrapped subtotal would understate `reserved` and overstate availability. - total = opp::safe::add_sat_u64(total, it->amount); - } - return total; -} - -/// This contract's active lock total for the given -/// `(underwriter, chain_code, token_code)` — an O(1) read of the `locksums` -/// rollup maintained by `add_locked_total` / `sub_locked_total`. An absent row -/// means the bucket holds no live locks. -/// -/// This used to walk every lock row the underwriter held, on the assumption -/// that per-underwriter lock counts stay small. They do not: locks are held -/// for the whole wall-clock challenge window and are never released by -/// delivery, so the count is (settlement rate × lock duration). See the -/// `lock_sum` docs for why that mattered on this particular call path. -uint64_t sum_locks_inline(name self, - name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - uwrit::locksums_t sums(self); - uwrit::lock_sum_key pk{underwriter, chain_code, token_code}; - return sums.contains(pk) ? sums.get(pk).amount : 0; -} - -/// Add `amount` to the `(underwriter, chain_code, token_code)` bucket, -/// creating the row when the bucket was empty. Called once per lock row -/// written by `try_select_winner`. -/// -/// Saturating, matching the scan it replaced: amounts are uncapped uint64 -/// external-chain values, and a wrapped total would UNDERSTATE what is -/// reserved and so OVERSTATE availability — the one direction that lets an -/// overcommit through. Saturation instead overstates `locked`, which fails -/// closed (a swap is refused, never over-collateralized). -void add_locked_total(name self, name underwriter, sysio::slug_name chain_code, - sysio::slug_name token_code, uint64_t amount) { - uwrit::locksums_t sums(self); - uwrit::lock_sum_key pk{underwriter, chain_code, token_code}; - if (sums.contains(pk)) { - sums.modify(ram_payer, pk, [&](auto& row) { - row.amount = opp::safe::add_sat_u64(row.amount, amount); - }); - return; - } - sums.emplace(ram_payer, pk, uwrit::lock_sum{ - .underwriter = underwriter, - .chain_code = chain_code, - .token_code = token_code, - .amount = amount, - }); -} - -/// Subtract `amount` from the bucket, erasing the row once it reaches zero so -/// the table holds only live buckets. -/// -/// Clamped at zero rather than wrapping. A wrap here would leave a colossal -/// `locked` on the bucket and zero the underwriter's `available()` for the -/// rest of the chain's life — and this runs inside `chklocks`, which is inline -/// in `sysio.epoch::advance` and must never throw. Clamping keeps a -/// hypothetical accounting slip local and self-healing instead. -void sub_locked_total(name self, name underwriter, sysio::slug_name chain_code, - sysio::slug_name token_code, uint64_t amount) { - uwrit::locksums_t sums(self); - uwrit::lock_sum_key pk{underwriter, chain_code, token_code}; - if (!sums.contains(pk)) return; - const uint64_t current = sums.get(pk).amount; - const uint64_t next = current > amount ? current - amount : 0; - if (next == 0) { - sums.erase(pk); - return; - } - sums.modify(ram_payer, pk, [&](auto& row) { row.amount = next; }); -} - -/// Look up an underwriter's balance on opreg for the given -/// `(chain_code, token_code)`. Returns the raw stored balance — caller -/// subtracts active locks + pending withdraws to get the spendable amount. -uint64_t opreg_balance(name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code, - OperatorStatus& out_status) { - opreg::operators_t ops(uwrit::OPREG_ACCOUNT); - opreg::operator_key op_pk{underwriter.value}; - if (!ops.contains(op_pk)) { - out_status = OperatorStatus::OPERATOR_STATUS_UNKNOWN; - return 0; - } - auto op = ops.get(op_pk); - out_status = op.status; - for (const auto& b : op.balances) { - if (b.chain_code == chain_code && b.token_code == token_code) { - return b.balance; - } - } - return 0; -} - -/// Compute the underwriter's spendable balance on -/// `(chain_code, token_code)`. Mirrors the sysio.opreg::available() formula: -/// balance - sum(active locks here in uwrit) - sum(pending withdraws on opreg) -/// gated by status (SLASHED / TERMINATED -> 0). -uint64_t available_via_mirrors(name self, - name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - OperatorStatus status; - uint64_t balance = opreg_balance(underwriter, chain_code, token_code, status); - if (status == OperatorStatus::OPERATOR_STATUS_SLASHED || - status == OperatorStatus::OPERATOR_STATUS_TERMINATED) { - return 0; - } - uint64_t locked = sum_locks_inline(self, underwriter, chain_code, token_code); - uint64_t pending = opreg_pending_withdraws(underwriter, chain_code, token_code); - // Saturating: a wrap of locked+pending would understate `reserved` and - // overstate availability — the exact direction that lets an overcommit - // through. The cap is unreachable for real amounts. - uint64_t reserved = opp::safe::add_sat_u64(locked, pending); - return balance > reserved ? balance - reserved : 0; -} - -/// True iff `candidate` is currently an ACTIVE underwriter on opreg: an -/// operator row that EXISTS, is typed `OPERATOR_TYPE_UNDERWRITER`, AND is in -/// `OPERATOR_STATUS_ACTIVE`. This is the authorization the race resolver -/// requires before a candidate may win and drive value settlement. -/// -/// opreg's eligibility model registers every non-bootstrapped operator as -/// `UNKNOWN`, forbids bootstrapping an underwriter active at registration, and -/// only promotes an underwriter to `ACTIVE` through `opreg::processuw` once it -/// clears the role's `req_uw_collat` minimum. The balance mirror -/// (`available_via_mirrors`) zeroes only SLASHED / TERMINATED rows and ignores -/// `op.type`, so on its own it would admit a non-underwriter (PRODUCER / BATCH -/// / CHALLENGER) or a pre-activation underwriter (UNKNOWN / WARMUP / COOLDOWN) -/// that merely holds enough mirrored balance. Reads the opreg `operators` row -/// directly — the authoritative type/status source, mirrored nowhere else in -/// uwrit. -bool is_active_underwriter(name candidate) { - opreg::operators_t ops(uwrit::OPREG_ACCOUNT); - opreg::operator_key op_pk{candidate.value}; - if (!ops.contains(op_pk)) return false; - const auto op = ops.get(op_pk); - return op.type == OperatorType::OPERATOR_TYPE_UNDERWRITER - && op.status == OperatorStatus::OPERATOR_STATUS_ACTIVE; -} - -/// Live `uwconfig` snapshot (defaults when `setconfig` has never run). -/// `self` is the uwrit contract account (where the singleton lives). -uwrit::uw_config read_config(name self) { - uwrit::uwconfig_t cfg(self); - return cfg.get_or_default(uwrit::uw_config{}); -} - -/// Live per-spoke swap fee (basis points) from `uwconfig`, read fresh so the -/// ingestion variance check, the race-time recheck, and settlement all charge -/// one rate. -uint32_t current_fee_bps(name self) { - return read_config(self).fee_bps; -} - -/// Find a reserve by its triple key, returning the row pointer-equivalent -/// optional. Mirrors sysio.reserv's primary-key access. Returns -/// `std::nullopt` when no such reserve exists (the variance check then -/// treats the quote as 0 — implicit skip). -std::optional find_reserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code) { - reserve::reserves_t reserves(uwrit::RESERVE_ACCOUNT); - reserve::reserve_key pk{chain_code, token_code, reserve_code}; - if (!reserves.contains(pk)) return std::nullopt; - return reserves.get(pk); -} - -/// Quote `src_amount` of (src_chain, src_token, src_reserve) into -/// (dst_chain, dst_token, dst_reserve) along the depot's live curve. Mirrors -/// `sysio.reserv::swapquote` exactly — the shared weighted-Bancor kernel (each -/// reserve's `connector_weight_bps`) and the SAME post-fee reduction: the -/// network `fee_bps` PLUS each participating reserve's own `owner_fee_bps`, all -/// out of the WIRE leg — so the variance check at SWAP_REQUEST receipt time -/// matches what settlement will deliver, without an inline action call into -/// reserv. Returns 0 if any required reserve is missing or not ACTIVE (caller -/// treats 0 as "no quote available, skip variance check"). -/// -/// A WIRE endpoint skips that leg's reserve (the depot IS the WIRE side); -/// WIRE->WIRE is a plain `src_amount` passthrough. -uint64_t swap_quote(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t src_amount, - uint32_t fee_bps) { - if (src_amount == 0) return 0; - const bool src_is_wire = opp::wire::is_native_asset(src_chain_code, src_token_code); - const bool dst_is_wire = opp::wire::is_native_asset(dst_chain_code, dst_token_code); - if (src_is_wire && dst_is_wire) return src_amount; - - auto active = [](std::optional&& r) - -> std::optional { - if (!r || r->status != ReserveStatus::RESERVE_STATUS_ACTIVE) return std::nullopt; - return r; - }; - - uint64_t sc = 0, sw = 0; uint32_t scw = 0, sfee = 0; - if (!src_is_wire) { - auto r = active(find_reserve(src_chain_code, src_token_code, src_reserve_code)); - if (!r) return 0; - sc = r->reserve_chain_amount; sw = r->reserve_wire_amount; scw = r->connector_weight_bps; - sfee = r->owner_fee_bps; - } - uint64_t dc = 0, dw = 0; uint32_t dcw = 0, dfee = 0; - if (!dst_is_wire) { - auto r = active(find_reserve(dst_chain_code, dst_token_code, dst_reserve_code)); - if (!r) return 0; - dc = r->reserve_chain_amount; dw = r->reserve_wire_amount; dcw = r->connector_weight_bps; - dfee = r->owner_fee_bps; - } - return opp::amm::quote_swap(src_is_wire, sc, sw, scw, - dst_is_wire, dc, dw, dcw, - src_amount, fee_bps, sfee, dfee); -} - -/// True iff every NON-WIRE leg of a swap has an ACTIVE reserve row. Lets callers -/// distinguish a zero `swap_quote` caused by an unprovisioned / not-ACTIVE -/// reserve (dev & smoke clusters — the variance check is intentionally skipped) -/// from one caused by a *degenerate but ACTIVE* reserve: a side drained to zero, -/// extreme connector weights, or a WIRE leg too small for the weighted-Bancor -/// kernel to price (it floors the output to 0). In that case a zero quote is a -/// real pricing failure and the caller MUST fail closed — otherwise the -/// user-supplied `target_amount` bypasses the variance check and settlement -/// over-debits the reserve (WSA-041). A WIRE endpoint has no reserve and is not -/// required (the depot IS the WIRE side), mirroring `swap_quote`. -bool required_reserves_active(sysio::slug_name src_chain_code, - sysio::slug_name src_token_code, - sysio::slug_name src_reserve_code, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code) { - auto leg_active = [](sysio::slug_name c, sysio::slug_name t, sysio::slug_name r) { - auto row = find_reserve(c, t, r); - return row && row->status == ReserveStatus::RESERVE_STATUS_ACTIVE; - }; - if (!opp::wire::is_native_asset(src_chain_code, src_token_code) - && !leg_active(src_chain_code, src_token_code, src_reserve_code)) return false; - if (!opp::wire::is_native_asset(dst_chain_code, dst_token_code) - && !leg_active(dst_chain_code, dst_token_code, dst_reserve_code)) return false; - return true; -} - -/// Slippage allowance for a variance check, in absolute destination units: -/// `tolerance_bps` of the **AMM quote**. -/// -/// The reference is deliberately the quote and NEVER the caller-supplied -/// `target_amount` (WNS-02). A target crosses the OPP boundary unauthenticated, -/// so deriving the allowance from it made the bound scale with the very number -/// it was meant to constrain: `|quote - target| <= target * tolerance_bps / 10000` -/// holds for ANY target above the quote once `tolerance_bps` reaches 10000, which -/// is what let a caller name an arbitrary destination amount and have it settle. -/// Measured against the quote, the check means what it says — "the price I got is -/// within N% of the price I expected". -/// -/// `tolerance_bps` is clamped to `BPS_TOTAL` (100%): a wider tolerance is -/// meaningless (100% already admits every target from zero to twice the quote) -/// and only inflates the allowance. Computed in `uint128_t` so the product cannot -/// overflow for any (quote, tolerance) pair. -uint128_t variance_allowance(uint64_t quote, uint32_t tolerance_bps) { - const uint32_t bounded_bps = tolerance_bps > opp::amm::BPS_TOTAL ? opp::amm::BPS_TOTAL - : tolerance_bps; - return (static_cast(quote) * bounded_bps) / opp::amm::BPS_TOTAL; -} - -/// Encode + queue a SWAP_REVERT attestation back to the source outpost when -/// the variance check fails. The outpost matches the original SWAP_REQUEST -/// via `original_swap_message_id` (low 8 bytes carry the depot's -/// attestation_id; see msgch's SWAP_REMIT dispatch for the matching decode -/// convention). -/// -/// The slug_name pair `(source_chain_code, source_reserve_code)` is included -/// so the outpost can locate the matching local reserve when refunding. -void emit_swap_revert(name self, - uint64_t chain_code, - uint64_t attestation_id, - const opp::attestations::SwapRequest& sr, - sysio::slug_name source_chain_code, - sysio::slug_name source_reserve_code, - const std::string& reason) { - opp::attestations::SwapRevert rev; - rev.original_swap_message_id.assign(32, 0); - for (size_t i = 0; i < 8; ++i) { - rev.original_swap_message_id[i] = static_cast((attestation_id >> (i * 8)) & 0xff); - } - rev.depositor = sr.actor; - rev.refund_amount = sr.source_amount; - rev.reason = reason; - rev.source_chain_code = source_chain_code.value; - rev.source_reserve_code = source_reserve_code.value; - - // `no_size{}` — raw protobuf bytes for the outpost decoder; the default - // `zpp::bits::data_out` form prepends a 4-byte LE length prefix that - // corrupts the first field tag on the receiving side. - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(rev); - - action( - permission_level{self, "active"_n}, - uwrit::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(chain_code, - opp::types::AttestationType::ATTESTATION_TYPE_SWAP_REVERT, encoded) - ).send(); -} - -/// Resolve a `sysio::slug_name` chain identifier to its `ChainKind` by -/// reading the `sysio.chains::chains` registry row. Returns `std::nullopt` -/// when no chain row exists for the code — callers treat that as "no -/// outpost for this chain, skip the queueout". -std::optional chain_kind_for_code(sysio::slug_name chain_code) { - sysio::chains::chains_t tbl(uwrit::CHAINS_ACCOUNT); - sysio::chains::chain_key pk{chain_code}; - if (!tbl.contains(pk)) return std::nullopt; - return tbl.get(pk).kind; -} - -/// Look up the depot's outpost id for `chain_code` via the chains registry. -/// Returns `std::nullopt` when no chain row is registered (per -/// `feedback_no_zero_sentinels` — outpost id 0 is a real id, so 0 must not -/// double as "missing"). -/// -/// After the cross-contract realignment: chain rows live in -/// `sysio.chains::chains` keyed by `code` (slug_name); the legacy -/// `sysio.epoch::outposts` table is gone. The "outpost id" returned here is -/// the chain's `code.value` (uint64). The depot-self row is filtered out so -/// WIRE-direct flows skip queueouts cleanly. -std::optional find_outpost_id_for_chain(sysio::slug_name chain_code) { - sysio::chains::chains_t chains_tbl(uwrit::CHAINS_ACCOUNT); - sysio::chains::chain_key pk{chain_code}; - if (!chains_tbl.contains(pk)) return std::nullopt; - const auto row = chains_tbl.get(pk); - if (row.is_depot) return std::nullopt; // WIRE-direct flows don't queueout - return chain_code.value; -} - -/// True iff `chain_code` is a REGISTERED chain row flagged `is_depot`. -/// An unregistered chain is NOT the depot — `createuwreq` rejects swaps on -/// unregistered chains outright rather than mistaking them for WIRE legs -/// (which would silently waive that leg's signature/bond/lock). -bool leg_is_depot(sysio::slug_name chain_code) { - sysio::chains::chains_t chains_tbl(uwrit::CHAINS_ACCOUNT); - sysio::chains::chain_key pk{chain_code}; - if (!chains_tbl.contains(pk)) return false; - return chains_tbl.get(pk).is_depot; -} - -/// True iff the chain is registered AND active in sysio.chains. -bool chain_registered_active(sysio::slug_name chain_code) { - sysio::chains::chains_t chains_tbl(uwrit::CHAINS_ACCOUNT); - sysio::chains::chain_key pk{chain_code}; - if (!chains_tbl.contains(pk)) return false; - return chains_tbl.get(pk).active; -} - -/// The depot's own chain code (the singleton `is_depot` row in -/// sysio.chains — `code = "WIRE"` by protocol convention). Scans the -/// registry, which holds a handful of rows. Returns nullopt before the -/// depot row is registered (pre-bootstrap). -std::optional depot_chain_code() { - sysio::chains::chains_t tbl(uwrit::CHAINS_ACCOUNT); - for (auto it = tbl.begin(); it != tbl.end(); ++it) { - if (it->is_depot) return it->code; - } - return std::nullopt; -} - -/// Find a reserve and require ACTIVE status — the shape every settlement -/// pre-check needs. Returns nullopt when missing or not ACTIVE. -std::optional find_active_reserve(sysio::slug_name chain_code, - sysio::slug_name token_code, - sysio::slug_name reserve_code) { - auto row = find_reserve(chain_code, token_code, reserve_code); - if (!row) return std::nullopt; - if (row->status != ReserveStatus::RESERVE_STATUS_ACTIVE) return std::nullopt; - return row; -} - -/// Parse a WIRE account name from its string-spelling bytes (the canonical -/// `ChainAddress.address` encoding for CHAIN_KIND_WIRE). Delegates to the shared -/// `sysio::opp::safe::parse_wire_account_name`, which validates charset, length, -/// and final-symbol bounds BEFORE constructing the `name`, so this never throws -/// inside the evalcons dispatch chain. The full CDT name domain is accepted — -/// including a legitimate 13-byte name whose final symbol fits the 4-bit final -/// slot. An empty address is not a valid principal here. -std::optional parse_wire_name(const std::vector& bytes) { - return sysio::opp::safe::parse_wire_account_name(std::string_view{bytes.data(), bytes.size()}); -} - -/// A WIRE account name as its string-spelling bytes — the inverse of -/// `parse_wire_name`, used to stamp WIRE-side principals into -/// ChainAddress-shaped fields (depositor, actor). -std::vector wire_name_bytes(name n) { - std::string s = n.to_string(); - return {s.begin(), s.end()}; -} - -/// Disposition of a pre-settlement attempt to build the outbound SWAP_REMIT -/// for a winning candidate. Lets `try_select_winner` unwind a race -/// non-throwing instead of letting a `check()` abort the evalcons dispatch -/// chain and stall OPP consensus chain-wide. -enum class swap_remit_disp { - ok, ///< envelope built — proceed to settle + `queue_swap_remit` - terminal, ///< no underwriter can EVER remit this uwreq — REJECT + refund - disqualified, ///< THIS candidate cannot remit — skip it, leave uwreq PENDING -}; - -/// Pre-validate + build (but do NOT send) the outbound SWAP_REMIT envelope -/// for `candidate` winning `req`, mutating no state. -/// -/// **Non-throwing.** The former `emit_swap_remit` ran AFTER the caller's -/// `reserv::applyswap` / `applyfromwire` reserve mutation and resolved the -/// recipient / destination outpost / underwriter identity with `check()`. -/// Inside the synchronous evalcons → dispatch → rcrdcommit → try_select_winner -/// chain, any such throw aborts consensus application for the whole outpost -/// envelope and stalls OPP epoch advancement chain-wide. This function instead -/// reports a `swap_remit_disp` so the caller can disqualify the candidate (or -/// reject the uwreq) cleanly. It is called BEFORE any lock / CONFIRMED / -/// reserve write; `queue_swap_remit` (below) only ships the pre-built envelope, -/// AFTER the reserve books have moved (so every intervening quote prices the -/// post-swap books). -/// -/// On `ok`, `dst_outpost_id` + `encoded` carry the ready-to-send envelope. -/// Failure classification: -/// * stored-request decode / dst outpost / dst chain-kind — uwreq-wide and -/// identical for every candidate ⇒ `terminal` (REJECT + refund/revert). -/// * winner's destination-chain authex link / pubkey — candidate-specific -/// (another underwriter may hold a valid link) ⇒ `disqualified`. -swap_remit_disp try_build_swap_remit(const uwrit::uw_request_t& req, - name candidate, - uint64_t& dst_outpost_id, - std::vector& encoded) { - // Decode the stored SwapRequest for its `recipient` (the row keeps only - // chain/kind/amount summaries). Same bytes for every candidate ⇒ terminal. - opp::attestations::SwapRequest sr; - { - auto in = zpp::bits::in{ - std::span{req.attestation_inbound_data.data(), - req.attestation_inbound_data.size()}, - zpp::bits::no_size{}}; - if (in(sr) != zpp::bits::errc{}) return swap_remit_disp::terminal; - } - - // Destination outpost id + ChainKind from the `sysio.chains` registry — - // chain-level config; if unresolved, no winner can ever remit ⇒ terminal. - auto dst_outpost_opt = find_outpost_id_for_chain(req.dst_chain_code); - if (!dst_outpost_opt) return swap_remit_disp::terminal; - auto dst_kind_opt = chain_kind_for_code(req.dst_chain_code); - if (!dst_kind_opt) return swap_remit_disp::terminal; - const ChainKind dst_kind = *dst_kind_opt; - - // Resolve the winning underwriter's destination-chain pubkey from - // `sysio.authex::links` (`bynamechain`) so the SwapRemit carries the - // underwriter's auditable destination-chain settlement key. This is not - // the UIC's signed transaction-signer metadata: on EVM the remit carries a - // 33-byte compressed EM key while `uw_ext_chain_addr` carries the 20-byte - // address, so the two fields are deliberately not byte-compared. - // A winner without a dst-chain link cannot ship a SwapRemit with a - // populated underwriter, but this is candidate-specific: disqualify them - // so the race can resolve for another underwriter (NOT terminal). - sysio::authex::links_t links(uwrit::AUTHEX_ACCOUNT); - auto idx = links.get_index<"bynamechain"_n>(); - auto it = idx.find(sysio::to_namechain_key(candidate, dst_kind)); - if (it == idx.end()) return swap_remit_disp::disqualified; - std::vector uw_addr = sysio::pubkey_to_bytes(it->pub_key); - if (uw_addr.empty()) return swap_remit_disp::disqualified; - - // All identities resolved — build the envelope. - opp::attestations::SwapRemit remit; - remit.recipient = sr.recipient; - // FORCE recipient.kind to the dst chain's actual ChainKind. The ETH - // outpost ships SwapRequest with `recipient.kind = CHAIN_KIND_UNKNOWN` - // ("depot routes by chain_code, outposts decode by their own kind"), but - // the SOL off-chain cranker (`extract_inbound_recipient_pubkeys`) forwards - // the recipient pubkey only when `kind == CHAIN_KIND_SVM`; UNKNOWN → dropped - // → on-chain `handle_swap_remit` rejects "recipient not in - // remaining_accounts". Per the project rule against 0-as-sentinel enums. - remit.recipient.kind = dst_kind; - remit.amount = opp::types::TokenAmount{ - .token_code = req.dst_token_code.value, - .amount = static_cast(req.dst_amount), - }; - // `original_message_id` low 8 bytes encode uwreq_id; the reflected - // SWAP_REMIT envelope back to msgch's dispatch uses this for the - // release-trigger decode (see sysio.msgch.cpp's SWAP_REMIT case). - remit.original_message_id.assign(32, 0); - for (size_t i = 0; i < 8; ++i) { - remit.original_message_id[i] = - static_cast((req.id >> (i * 8)) & 0xff); - } - remit.chain_code = req.dst_chain_code.value; - remit.reserve_code = req.dst_reserve_code.value; - remit.underwriter.kind = dst_kind; - remit.underwriter.address = std::move(uw_addr); - remit.unlock_timestamp = 0; - - // `no_size{}` — see emit_swap_revert for the rationale. - encoded.clear(); - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - (void)out(remit); - dst_outpost_id = *dst_outpost_opt; - return swap_remit_disp::ok; -} - -/// Queue a pre-built SWAP_REMIT envelope (from `try_build_swap_remit`) to the -/// destination outpost. Sent by `try_select_winner` AFTER the reserve mutation -/// in the same transaction, so every intervening quote prices the post-swap -/// books. The destination outpost's ReserveManager (ETH) / reserve PDA (SOL) -/// pays the recipient inline via `_handleSwapRemit` / `handle_swap_remit`. -/// Non-throwing. -void queue_swap_remit(name self, uint64_t dst_outpost_id, - const std::vector& encoded) { - action( - permission_level{self, "active"_n}, - uwrit::MSGCH_ACCOUNT, "queueout"_n, - std::make_tuple(dst_outpost_id, - opp::types::AttestationType::ATTESTATION_TYPE_SWAP_REMIT, encoded) - ).send(); -} - -/// Typed outcome of verifying one required UIC leg. -enum class uic_signature_result { - valid, - empty_uic, - malformed_uic, - non_canonical_uic, - missing_signature, - invalid_signature_length, - unsupported_signature_type, - non_canonical_signature, - digest_encoding_failed, - recovery_failed, - unauthorized_key, -}; - -/// Identifies which required UIC leg failed verification. -enum class uic_leg { - source, - destination, -}; - -/// True only when one incoming side already carries complete stored evidence. -bool required_uic_leg_populated(const uwrit::commit_entry& entry, uic_leg leg) { - return leg == uic_leg::source - ? !entry.source_uic_bytes.empty() && entry.source_received_at_ms != 0 - : !entry.dest_uic_bytes.empty() && entry.dest_received_at_ms != 0; -} - -/// True when every outpost-backed side has complete stored UIC evidence. -bool required_uic_legs_complete(const uwrit::uw_request_t& request, - const uwrit::commit_entry& entry) { - const bool source_complete = leg_is_depot(request.src_chain_code) || - required_uic_leg_populated(entry, uic_leg::source); - const bool destination_complete = leg_is_depot(request.dst_chain_code) || - required_uic_leg_populated(entry, uic_leg::destination); - return source_complete && destination_complete; -} - -/** - * @brief Validate and construct one fixed-size ECDSA UIC signature variant. - * @tparam VariantIndex Exact `sysio::signature` alternative to construct. - * @param packed Packed bytes including the one-byte variant tag. - * @param curve Curve order governing `r` and `s`. - * @param recovery_position Position of the compact recovery byte. - * @param recovery_encoding Required numeric recovery-byte representation. - * @param parsed_sig Destination signature variant. - * @return Typed length/canonicality outcome, or `valid` after construction. - */ -template -uic_signature_result parse_uic_ecc_signature( - std::span packed, - opp::uic_ecdsa_curve curve, - opp::uic_ecdsa_recovery_position recovery_position, - opp::uic_ecdsa_recovery_encoding recovery_encoding, - sysio::signature& parsed_sig) { - if (packed.size() != PACKED_ECC_SIGNATURE_SIZE) { - return uic_signature_result::invalid_signature_length; - } - const auto body = packed.subspan(PACKED_SIGNATURE_TAG_SIZE); - if (!opp::is_canonical_uic_ecdsa_signature( - body, curve, recovery_position, recovery_encoding)) { - return uic_signature_result::non_canonical_signature; - } - - sysio::ecc_signature compact_sig{}; - std::copy_n(body.begin(), compact_sig.size(), compact_sig.begin()); - parsed_sig.emplace(std::move(compact_sig)); - return uic_signature_result::valid; -} - -/// Verify the embedded signature in `uic_bytes` was produced by a key on -/// EITHER the `underwriter` account's `active` OR `owner` permission, over -/// the digest `sha256(serialize(uic_with_signature_blanked))`. -/// -/// The accepted wire shapes are the fixed-size recoverable variants already -/// supported by WIRE providers: K1, R1, and EM (one tag byte plus a 65-byte ECC -/// body), and ED (one tag byte plus a 96-byte body). Each body is copied into -/// its known variant directly instead of routing attacker-controlled bytes -/// through the generic variant deserializer, whose fixed-array underrun calls -/// `check()` and would abort the enclosing evalcons transaction. Variable-size -/// WebAuthn and unrecoverable BLS signatures are rejected before construction. -/// -/// Per `feedback_opp_handlers_never_throw.md`, every attacker-controlled -/// failure is returned as a typed result. Other permissions (custom names) -/// are intentionally not checked: the plugin is pinned to `active` or -/// `owner`, and accepting a custom permission would create an unconfigured -/// authorization surface. -uic_signature_result verify_uic_signature(name underwriter, - const std::vector& uic_bytes) { - if (uic_bytes.empty()) return uic_signature_result::empty_uic; - - // Decode the UIC payload. - opp::attestations::UnderwriteIntentCommit uic; - { - auto in = zpp::bits::in{ - std::span{uic_bytes.data(), uic_bytes.size()}, - zpp::bits::no_size{}}; - if (in(uic) != zpp::bits::errc{}) return uic_signature_result::malformed_uic; - } - - // Require the exact canonical protobuf encoding before hashing or recovery. - // zpp decode normalizes duplicate/default/unknown-field representations; - // re-encoding the complete message (with its original signature intact) - // makes every alternate byte sequence fail closed without throwing. - std::vector canonical_uic; - auto canonical_out = zpp::bits::out{canonical_uic, zpp::bits::no_size{}}; - if (canonical_out(uic) != zpp::bits::errc{}) { - return uic_signature_result::digest_encoding_failed; - } - if (canonical_uic != uic_bytes) { - return uic_signature_result::non_canonical_uic; - } - - // Validate the exact fixed-size shape before doing any hashing or - // constructing the signature variant. This both bounds work on malformed - // inputs and makes the former generic-deserializer underrun unreachable. - const std::vector sig_bytes_view{uic.signature.begin(), uic.signature.end()}; - if (sig_bytes_view.empty()) return uic_signature_result::missing_signature; - const auto signature_variant = magic_enum::enum_cast( - static_cast(sig_bytes_view.front())); - if (!signature_variant) { - return uic_signature_result::unsupported_signature_type; - } - - sysio::signature parsed_sig; - switch (*signature_variant) { - case uic_signature_variant::k1: - if (const auto result = parse_uic_ecc_signature( - sig_bytes_view, opp::uic_ecdsa_curve::secp256k1, - opp::uic_ecdsa_recovery_position::prefix, - opp::uic_ecdsa_recovery_encoding::compact, parsed_sig); - result != uic_signature_result::valid) return result; - break; - case uic_signature_variant::r1: - if (const auto result = parse_uic_ecc_signature( - sig_bytes_view, opp::uic_ecdsa_curve::p256, - opp::uic_ecdsa_recovery_position::prefix, - opp::uic_ecdsa_recovery_encoding::compact, parsed_sig); - result != uic_signature_result::valid) return result; - break; - case uic_signature_variant::em: - if (const auto result = parse_uic_ecc_signature( - sig_bytes_view, opp::uic_ecdsa_curve::secp256k1, - opp::uic_ecdsa_recovery_position::suffix, - opp::uic_ecdsa_recovery_encoding::ethereum, parsed_sig); - result != uic_signature_result::valid) return result; - break; - case uic_signature_variant::ed: { - if (sig_bytes_view.size() != PACKED_ED_SIGNATURE_SIZE) { - return uic_signature_result::invalid_signature_length; - } - sysio::ed_signature compact_sig{}; - std::copy_n(sig_bytes_view.begin() + PACKED_SIGNATURE_TAG_SIZE, - compact_sig.size(), compact_sig.begin()); - parsed_sig.emplace(std::move(compact_sig)); - break; - } - case uic_signature_variant::wa: - case uic_signature_variant::bls: - default: - return uic_signature_result::unsupported_signature_type; - } - - // Blank the signature and recompute the signed digest. - uic.signature.clear(); - - std::vector blanked; - auto out = zpp::bits::out{blanked, zpp::bits::no_size{}}; - if (out(uic) != zpp::bits::errc{}) { - return uic_signature_result::digest_encoding_failed; - } - - sysio::checksum256 digest = - sysio::sha256(blanked.data(), blanked.size()); - - // Recover the public key through the rc-returning intrinsic. Exact - // fixed-size construction above makes the subjective variable-size - // signature guard unreachable, while bad recovery bytes or curve values - // return nullopt. - auto recovered_opt = sysio::try_recover_key(digest, parsed_sig); - if (!recovered_opt) return uic_signature_result::recovery_failed; - const sysio::public_key& recovered = *recovered_opt; - - // Only `owner` and `active` permissions are considered. The - // underwriter_plugin's signature_provider_manager_plugin config is - // pinned to one of those two (see plugin docs); accepting a custom - // permission would open a separate authorization surface that nothing - // else on the platform validates. - constexpr sysio::name OWNER_PERM = "owner"_n; - constexpr sysio::name ACTIVE_PERM = "active"_n; - for (auto perm : { ACTIVE_PERM, OWNER_PERM }) { - auto rec_opt = sysio::get_permission(underwriter, perm); - if (!rec_opt) continue; - for (const auto& kw : rec_opt->auth.keys) { - // A UIC carries exactly one signature. A direct key therefore - // authorizes it only when that key's weight alone reaches this - // permission's threshold; mere membership in a multisig authority - // is not sufficient. - if (kw.key == recovered && kw.weight >= rec_opt->auth.threshold) { - return uic_signature_result::valid; - } - } - } - return uic_signature_result::unauthorized_key; -} - -/// Create the durable rejection reason from typed leg and verification values. -std::string create_uic_signature_rejection_reason(uic_leg leg, - uic_signature_result result) { - const auto leg_name = magic_enum::enum_name(leg); - const auto result_name = magic_enum::enum_name(result); - - std::string reason{UIC_SIGNATURE_REJECTED_REASON_PREFIX}; - reason.append(leg_name.data(), leg_name.size()); - reason.append(UIC_SIGNATURE_REJECTED_REASON_SEPARATOR); - reason.append(result_name.data(), result_name.size()); - return reason; -} - -/// Allocate a fresh `lock_id` from the uwcounters singleton. -uint64_t next_lock_id(name self) { - uwrit::uwcounters_t ctr_tbl(self); - auto ctr = ctr_tbl.get_or_default(uwrit::uw_counters{}); - uint64_t id = ctr.next_lock_id; - ctr.next_lock_id = id + 1; - ctr_tbl.set(ctr, ram_payer); - return id; -} - -/// Allocate a depot-origin (swap-from-WIRE) id: the high-bit-tagged -/// sequence that doubles as both the fwqueue row id and the eventual -/// uwreq id. Disjoint from msgch's inbound attestation-id space. -uint64_t next_fromwire_id(name self) { - uwrit::uwcounters_t ctr_tbl(self); - auto ctr = ctr_tbl.get_or_default(uwrit::uw_counters{}); - uint64_t id = DEPOT_ORIGIN_ID_BASE | ctr.next_fromwire_seq; - ctr.next_fromwire_seq += 1; - ctr_tbl.set(ctr, ram_payer); - return id; -} - -} // anonymous namespace - -// --------------------------------------------------------------------------- -// setconfig -// --------------------------------------------------------------------------- -void uwrit::setconfig(uint32_t fee_bps, - uint64_t collateral_lock_duration_ms, - uint64_t min_fromwire_amount, - uint32_t fromwire_revert_fee_bps, - uint32_t uwreq_pending_timeout_epochs, - uint32_t uwreq_retention_epochs) { - require_auth(get_self()); - // Reject a 100% (or higher) fee: it zeroes the post-fee WIRE leg - // (`net == 0`), which let a swap debit destination reserve liquidity while - // crediting zero WIRE (SEC-26 / WSA-042). MAX_FEE_BPS == 9999 keeps the - // remainder positive for every positive input — for THIS rate alone. Reserve - // owner fees stack on the same leg under their own cap, so the total can - // still reach 100%; `sysio.reserv`'s settlement `net > 0` checks reject that - // configured combination. - check(fee_bps <= MAX_FEE_BPS, - "fee_bps must be below 10000 (100%): a 100% fee zeroes the post-fee WIRE leg"); - check(collateral_lock_duration_ms > 0, - "collateral_lock_duration_ms must be positive"); - // Reject a duration so large that `now_ms + collateral_lock_duration_ms` - // would wrap past UINT64_MAX and yield a lock that expires in the past, - // releasing collateral the instant it is placed. - check(collateral_lock_duration_ms <= MAX_COLLATERAL_LOCK_DURATION_MS, - "collateral_lock_duration_ms exceeds the one-year ceiling"); - // A zero floor would reopen free dust rows in the swap-from-WIRE queue — - // the floor is the queue-slot price (see DEFAULT_MIN_FROMWIRE_AMOUNT). - check(min_fromwire_amount > 0, "min_fromwire_amount must be positive"); - // Same 100% rationale as fee_bps, and on this path it is also a liveness - // rail: a 100% revert fee would zero the post-fee refund transfer inside - // the never-throw drainfwq drain (`refundwire` rejects zero transfers). - check(fromwire_revert_fee_bps <= MAX_FEE_BPS, - "fromwire_revert_fee_bps must be below 10000 (100%): a 100% revert fee zeroes the refund"); - // A zero pending timeout would expire every uwreq the epoch it is created — - // no race could ever resolve; a zero retention would erase terminal rows - // before the audit window they exist for. The shared ceiling prevents a - // near-UINT32_MAX knob from wrapping the `current_epoch + knob` deadline - // stamp to a tiny epoch index (instant expiry). - check(uwreq_pending_timeout_epochs > 0, - "uwreq_pending_timeout_epochs must be positive"); - check(uwreq_pending_timeout_epochs <= MAX_UWREQ_LIFECYCLE_EPOCHS, - "uwreq_pending_timeout_epochs exceeds the lifecycle ceiling"); - check(uwreq_retention_epochs > 0, - "uwreq_retention_epochs must be positive"); - check(uwreq_retention_epochs <= MAX_UWREQ_LIFECYCLE_EPOCHS, - "uwreq_retention_epochs exceeds the lifecycle ceiling"); - - uwconfig_t cfg_tbl(get_self()); - uw_config cfg = cfg_tbl.get_or_default(uw_config{}); - cfg.fee_bps = fee_bps; - cfg.collateral_lock_duration_ms = collateral_lock_duration_ms; - cfg.min_fromwire_amount = min_fromwire_amount; - cfg.fromwire_revert_fee_bps = fromwire_revert_fee_bps; - cfg.uwreq_pending_timeout_epochs = uwreq_pending_timeout_epochs; - cfg.uwreq_retention_epochs = uwreq_retention_epochs; - cfg_tbl.set(cfg, ram_payer); -} - -// --------------------------------------------------------------------------- -// createuwreq — called inline from sysio.msgch when SWAP arrives -// --------------------------------------------------------------------------- -void uwrit::createuwreq(uint64_t attestation_id, - opp::types::AttestationType type, - uint64_t chain_code, - std::vector data) { - require_auth(MSGCH_ACCOUNT); - - uwreqs_t reqs(get_self()); - auto pk = id_key{attestation_id}; - // Duplicate-delivery is the protocol's normal idempotency case — every - // batch op re-relays the same envelope on each cron tick until the - // depot advances the epoch, so the second, third, ... batch op's - // `deliver → evalcons → dispatch → createuwreq` call lands on a row - // that's already present. Per `feedback_opp_handlers_never_throw.md` - // a `check()` here halts `evalcons` and stalls consensus across the - // chain. Silently no-op the duplicate and let the relay continue. - if (reqs.contains(pk)) { - sysio::print("createuwreq: uwreq ", attestation_id, - " already exists, skipping idempotent re-delivery\n"); - return; - } - - // Only SWAP_REQUEST attestations create UWREQs — msgch's dispatch routes - // other types directly to their handlers, not through createuwreq. A - // non-SWAP type can only arrive from a future/buggy dispatcher; per - // `feedback_opp_handlers_never_throw.md` a check() here would halt evalcons - // and stall consensus, so log + skip instead of asserting. - if (type != AttestationType::ATTESTATION_TYPE_SWAP_REQUEST) { - sysio::print("createuwreq: non-SWAP_REQUEST attestation ", attestation_id, - " routed to createuwreq, skipping\n"); - return; - } - - // Decode the operator-supplied SwapRequest. A malformed payload cannot be - // refunded (emit_swap_revert needs the decoded actor / source_amount to - // tell the outpost which deposit to reverse — exactly what failed to - // decode), and a check() here would abort the consensus-tipping delivery - // before any uwreq row exists, stalling epoch advancement chain-wide. Log + - // skip: no row is created, so no state is corrupted and the relay - // continues. Per `feedback_opp_handlers_never_throw.md`. - SwapRequest sr; - { - auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}}; - if (in(sr) != zpp::bits::errc{}) { - sysio::print("createuwreq: failed to decode SwapRequest for attestation ", - attestation_id, ", skipping (undecodable — cannot revert)\n"); - return; - } - } - - // One config snapshot for the whole ingestion: the variance quote and the - // PENDING deadline stamp below must read one consistent config. - const uw_config cfg = read_config(get_self()); - - // Pull the slug_name triples + amounts out of the decoded SwapRequest. - // The source token's code lives on the TokenAmount; the source chain - // and source reserve are top-level fields. Destination has all three - // top-level. - const sysio::slug_name src_chain_code{sr.source_chain_code}; - const sysio::slug_name src_token_code{sr.source_amount.token_code}; - const sysio::slug_name src_reserve_code{sr.source_reserve_code}; - const sysio::slug_name dst_chain_code{sr.target_chain_code}; - const sysio::slug_name dst_token_code{sr.target_token_code}; - const sysio::slug_name dst_reserve_code{sr.target_reserve_code}; - // WSA-028: source_amount is signed on the wire. Gate it through the shared - // fail-closed parser; a non-positive or out-of-range value yields nullopt and - // is reverted (refund on the proven outpost) by the positivity guard below, - // never wrapped into a huge src_amount that would corrupt the swap quote and - // reserve settlement. Never-throw: revert, do not check(). - const std::optional src_amount_opt = - opp::safe::to_depot_amount(static_cast(sr.source_amount.amount)); - - // WSA-005 source-chain binding. `chain_code` is the source outpost `sysio.msgch::dispatch` - // proved via `deliver` (an active, non-depot `sysio.chains` row); `sr.source_chain_code` is the - // payload's self-asserted source. They MUST be identical — a SwapRequest about a deposit on - // chain X is always relayed by outpost X. A divergence means an envelope proven from outpost A is - // claiming the swap was funded on a different chain B; settling it would lock/draw against B's - // source reserve while the user's funds are escrowed on A — a cross-chain settlement forgery. - // Refund on the PROVEN outpost (A, == `chain_code`, where the deposit actually sits) and create - // no uwreq. Never throw: we are inside the evalcons dispatch chain, where a `check()` stalls - // consensus chain-wide (`feedback_opp_handlers_never_throw`). Checked before the amount/registry - // guards because provenance is the most fundamental precondition. - if (sr.source_chain_code != chain_code) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - sysio::slug_name{chain_code}, src_reserve_code, - "SwapRequest rejected: source chain does not match the proven " - "delivering outpost (cross-chain provenance mismatch)"); - return; - } - - // The four payload-controlled codes must have a canonical string spelling, because a - // uwreq row IS created from them below on a path that no registry lookup gates: the - // zero-quote guard fails closed only when `required_reserves_active` holds, so a - // MISSING reserve (the unprovisioned-LP case) falls through to `reqs.emplace` with - // these codes stored verbatim. Rendering is total, so an uncanonical code does not - // make the row unreadable — it makes it WRONG: the rendered text either fails - // validation on the way back, or re-parses as a DIFFERENT reserve than the uwreq was - // created from. Refund rather than drop: the user's deposit is escrowed on the source - // outpost, so a silent skip would strand it. Never `check()` — we are inside the - // evalcons dispatch chain (`feedback_opp_handlers_never_throw`). - // - // The two CHAIN codes are deliberately absent: `src_chain_code` was just proven equal - // to the delivering outpost's `chain_code`, and `dst_chain_code` must pass - // `chain_registered_active` below. Both therefore name a `sysio.chains` row, and - // `sysio.chains::regchain` refuses a code with no canonical spelling — so the registry itself - // carries that guarantee. - if (!src_token_code.is_canonical() || !src_reserve_code.is_canonical() || - !dst_token_code.is_canonical() || !dst_reserve_code.is_canonical()) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: a token or reserve code has no canonical " - "slug_name spelling"); - return; - } - - // An exact source/destination reserve identity has only one outpost leg. - // Admitting it would leave the ordinary two-leg request waiting forever - // for a second distinct commitment. Refund on the proven source outpost - // and create no UWREQ; same-asset cross-chain swaps and different reserve - // codes remain distinct triples and continue through the normal path. - if (src_chain_code == dst_chain_code && - src_token_code == dst_token_code && - src_reserve_code == dst_reserve_code) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: source and destination reserve " - "identities must differ"); - return; - } - - // Structural guards — refund via SwapRevert, never throw (we are inside - // the evalcons dispatch chain). Zero amounts are rejected up front so - // every downstream lock/settlement amount is provably positive (a - // zero-amount lock would trip `opreg::releaselock`'s amount check from - // inside `chklocks` at epoch advance — a consensus stall). - if (!src_amount_opt || sr.target_amount == 0) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: source and target amounts must be positive"); - return; - } - const uint64_t src_amount = *src_amount_opt; - if (!chain_registered_active(src_chain_code) || !chain_registered_active(dst_chain_code)) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: unregistered or inactive chain"); - return; - } - const bool src_depot = leg_is_depot(src_chain_code); - const bool dst_depot = leg_is_depot(dst_chain_code); - if (src_depot && dst_depot) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: WIRE->WIRE swap is degenerate — " - "use sysio.token::transfer"); - return; - } - if (src_depot) { - // Swap-from-WIRE originates ON the depot (swapfromwire -> drainfwq); - // an outpost claiming WIRE as its source chain is bogus. - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: swap-from-WIRE cannot originate " - "from an outpost"); - return; - } - - // Hard-fail any SwapRequest without a populated `source_tx_id`. The - // off-chain underwriter verify path uses this id to confirm a real - // on-chain deposit backs the swap before committing collateral; a - // SwapRequest without it can't be verified, and an outpost is - // required to populate the field at swap-emit time. Per - // `feedback_opp_handlers_never_throw.md` we cannot `check()`/throw - // here (we're inside the evalcons dispatch chain — a throw stalls - // consensus); instead emit a SwapRevert back to the source outpost so - // the user's deposit is refunded and the run continues. - if (sr.source_tx_id.empty()) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: source_tx_id is required " - "(no SwapRequest may be emitted without a " - "populated source-chain transaction id)"); - return; - } - - // Row-growth rails (SEC-129 / WSA-223): the row stores these payloads - // verbatim and every later `modify` re-serializes the whole row inside the - // never-throw dispatch surfaces, so oversized inputs are refused at the - // door. All caps sit far above legitimate traffic; a violation is a - // malformed/hostile outpost payload — refund it like every other - // structural rejection, never check(). - if (data.size() > MAX_ATTESTATION_INBOUND_DATA_BYTES || - sr.source_tx_id.size() > MAX_SOURCE_TX_ID_BYTES || - sr.actor.address.size() > MAX_DEPOSITOR_BYTES) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: payload exceeds a size cap " - "(attestation data / source_tx_id / depositor address)"); - return; - } - - // Privacy gate — a private reserve only swaps against a counterpart - // reserve owned by the same WIRE account (the authex-linked matcher - // recorded at match time), and is excluded from WIRE-endpoint swaps - // entirely. Ownership is immutable while a reserve is ACTIVE, so this - // ingestion-time gate needs no race-time recheck. - { - const auto src_r = find_reserve(src_chain_code, src_token_code, src_reserve_code); - if (dst_depot) { - // Swap-to-WIRE: only the source reserve exists — it must be public. - if (src_r && src_r->is_private) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: private reserves are " - "excluded from WIRE-endpoint swaps"); - return; - } - } else { - const auto dst_r = find_reserve(dst_chain_code, dst_token_code, dst_reserve_code); - const bool src_priv = src_r && src_r->is_private; - const bool dst_priv = dst_r && dst_r->is_private; - if (src_priv || dst_priv) { - const bool same_owner = src_r && dst_r - && src_r->owner != name{} - && src_r->owner == dst_r->owner; - if (!same_owner) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "SwapRequest rejected: private reserve pairing " - "violation — counterpart reserve must be owned " - "by the same WIRE account"); - return; - } - } - } - } - - // Price the swap on the depot's live curve. This quote — NOT the caller's - // `target_amount` — is what the row carries as `dst_amount` (WNS-02). - // `target_amount` is the caller's *expectation*, an unauthenticated number - // that arrives over OPP, while `dst_amount` is paid out verbatim by - // `sysio.reserv::applyswap` / `applyfromwire` / `paywire`. Settling on the - // caller's figure let a small source deposit name an arbitrary destination - // amount and drain the destination reserve. - const uint64_t current_quote = swap_quote(src_chain_code, src_token_code, src_reserve_code, - dst_chain_code, dst_token_code, dst_reserve_code, - src_amount, cfg.fee_bps); - // Zero quote with every required reserve ACTIVE = an unpriceable / degenerate - // reserve (a side drained to zero, extreme connector weights, or a leg too - // small for the weighted-Bancor kernel to price). Fail closed (WSA-041) — - // otherwise the row would be created with `dst_amount == 0`, which - // `applyswap` and its siblings assert against, aborting inside evalcons. - // The gate is no longer nested under `target_amount != 0`: it protects - // `dst_amount`, which has nothing to do with the caller's target, so it must - // not depend on the positive-amounts gate above to be reached. - // - // A zero quote from a MISSING / not-ACTIVE reserve is the dev & smoke cluster - // case: no LP is provisioned, so the row is created (the underwriter race - // still runs) but cannot settle until a reserve exists — `try_select_winner` - // re-quotes and only ever settles against ACTIVE reserves. - if (current_quote == 0 && - required_reserves_active(src_chain_code, src_token_code, src_reserve_code, - dst_chain_code, dst_token_code, dst_reserve_code)) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "unpriceable reserve: zero quote from an ACTIVE reserve " - "(degenerate weights/balances) — fail closed"); - return; // no UWREQ created - } - // Variance tolerance — the caller's slippage bound on the quote they will be - // given, measured as a fraction of that quote (see `variance_allowance`). - // A zero `target_amount` was already refused by the positive-amounts gate - // above, so that term is defensive; the live skip is `current_quote == 0`, - // the unprovisioned-LP case, where there is no price to compare against. - if (sr.target_amount != 0 && current_quote != 0) { - const uint64_t target = sr.target_amount; - const uint64_t diff = current_quote > target ? current_quote - target - : target - current_quote; - const uint128_t allowed = variance_allowance(current_quote, sr.target_tolerance_bps); - if (static_cast(diff) > allowed) { - emit_swap_revert(get_self(), chain_code, attestation_id, sr, - src_chain_code, src_reserve_code, - "variance exceeded tolerance: target=" + std::to_string(target) - + " current=" + std::to_string(current_quote) - + " tolerance_bps=" + std::to_string(sr.target_tolerance_bps)); - return; // no UWREQ created - } - } - - reqs.emplace(ram_payer, pk, uw_request_t{ - .id = attestation_id, - .type = type, - .status = UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING, - .src_chain_code = src_chain_code, - .src_token_code = src_token_code, - .src_reserve_code = src_reserve_code, - .src_amount = src_amount, - .dst_chain_code = dst_chain_code, - .dst_token_code = dst_token_code, - .dst_reserve_code = dst_reserve_code, - // The AMM quote, never `sr.target_amount` (WNS-02). Re-quoted and - // overwritten with the live price at race resolution before anything - // settles against it. - .dst_amount = current_quote, - // The caller's expectation, retained as the FIXED reference the - // race-time slippage check measures against. Never paid out. - .target_amount = sr.target_amount, - .variance_tolerance_bps = sr.target_tolerance_bps, - .source_tx_id = sr.source_tx_id, - .depositor = sr.actor.address, - .commits_by = {}, - .winner = name{}, - .committed_at_ms = 0, - .settled_at_ms = 0, - // The PENDING deadline (SEC-129 / WSA-223): if no underwriter wins the - // race within this many epochs, `pruneuwreqs` expires the row and - // refunds the source side. No trigger ever announces "no winner is - // coming" — commits are external and optional — so the deadline is what - // converts a later epoch advance into the recovery trigger. - .expires_at_epoch = get_current_epoch() + cfg.uwreq_pending_timeout_epochs, - .attestation_inbound_data = std::move(data), - .attestation_outbound_data = {}, - }); -} - -// --------------------------------------------------------------------------- -// Internal: try_select_winner — race resolver -// --------------------------------------------------------------------------- - -namespace { - -/// Helper: find or create the commit_entry for `underwriter` inside an -/// uw_request_t. Returns iterator-like reference into the in-place vector. -uwrit::commit_entry* find_or_create_commit(uwrit::uw_request_t& req, name underwriter) { - for (auto& c : req.commits_by) { - if (c.underwriter == underwriter) return &c; - } - req.commits_by.push_back(uwrit::commit_entry{ - .underwriter = underwriter, - }); - return &req.commits_by.back(); -} - -/// Mark a single candidate's commit_entry as race-disqualified and leave the -/// uwreq PENDING so the race can still resolve for another valid underwriter. -/// Non-throwing. -/// -/// Uses the dedicated `UNDERWRITE_STATUS_DISQUALIFIED` status — a -/// candidate-specific, durable pre-settlement invalidity (for example, an -/// older stored UIC invalidated by permission-key rotation or a missing -/// destination-authex identity link). It is -/// deliberately distinct from `UNDERWRITE_STATUS_SLASHED` (an economic -/// punishment that actually burned collateral) and from -/// `UNDERWRITE_STATUS_RELEASED` (a clean race loser whose commit was valid): -/// conflating the three would mislead downstream indexing and audits. A -/// disqualified entry is durable for this request: later records naming the -/// candidate cannot rewrite its evidence or re-arm it for winner selection. -/// Raw UIC payloads are cleared because terminal candidates have no remaining -/// signature-validation reader; timestamps, outpost ids, status, and reason -/// remain as the compact audit record. -void disqualify_candidate(uwrit::uwreqs_t& reqs, const uwrit::id_key& pk, - name candidate, const std::string& reason) { - reqs.modify(same_payer, pk, [&](auto& r) { - auto* c = find_or_create_commit(r, candidate); - c->status = UnderwriteStatus::UNDERWRITE_STATUS_DISQUALIFIED; - c->reason = reason; - c->source_uic_bytes.clear(); - c->dest_uic_bytes.clear(); - }); -} - -/// Emit the stable rejection marker for an invalid claimed candidate UIC. -/// `chain_code` is the provenance-bound slug supplied by msgch, not an outpost -/// table id. Logging is the only effect when pre-storage validation fails. -void log_uic_signature_rejection(uint64_t uwreq_id, - name candidate, - uic_leg leg, - uint64_t chain_code, - uic_signature_result result) { - const std::string leg_name{magic_enum::enum_name(leg)}; - const std::string result_name{magic_enum::enum_name(result)}; - sysio::print(UIC_SIGNATURE_REJECTED_LOG_PREFIX, - ": uwreq=", uwreq_id, - ", claimed_underwriter=", candidate, - ", leg=", leg_name, - ", chain_code=", chain_code, - ", reason=", result_name, "\n"); -} - -/// Revalidate an older stored leg when its matching leg arrives. This protects -/// the race from WIRE permission-key rotation between leg arrivals: a key that -/// authorized the first leg may no longer be authorized when the pair becomes -/// eligible to win. Returning false is a normal consensus-path outcome. -bool validate_candidate_uic(uwrit::uwreqs_t& reqs, - const uwrit::id_key& pk, - uint64_t uwreq_id, - name candidate, - uic_leg leg, - uint64_t chain_code, - const std::vector& uic_bytes) { - const auto result = verify_uic_signature(candidate, uic_bytes); - if (result == uic_signature_result::valid) return true; - - const auto reason = create_uic_signature_rejection_reason(leg, result); - log_uic_signature_rejection(uwreq_id, candidate, leg, chain_code, result); - disqualify_candidate(reqs, pk, candidate, reason); - return false; -} - -/// Terminally close a uwreq that can never settle: refund the source side and -/// mark the row with `terminal_status` (REJECTED for active rejections — the -/// default; EXPIRED when `pruneuwreqs` abandons a PENDING race past its -/// deadline), releasing any in-flight commits. **Non-throwing** — safe inside -/// the evalcons dispatch chain (a `check()` here would stall OPP consensus -/// chain-wide). The refund routes by source-leg kind: -/// * outpost source (`src_needed`): best-effort SWAP_REVERT back to the -/// source outpost (requires the stored SwapRequest to decode; a depot -/// source has no outpost to route to, so `find_outpost_id_for_chain` -/// returns nullopt and the revert is skipped). -/// * depot source (from-WIRE): refund the escrowed WIRE to the -/// depositor via `reserv::refundwire`. -/// The terminal modify also stamps the retention deadline -/// (`uwconfig.uwreq_retention_epochs`) and clears the row's heavy payloads -/// (inbound attestation copy + every stored UIC byte blob) — they were only -/// needed to reach this decision; the compact audit metadata (ids, codes, -/// amounts, statuses, reasons, timestamps) is what retention keeps. -void reject_and_refund(name self, uwrit::uwreqs_t& reqs, const uwrit::id_key& pk, - const uwrit::uw_request_t& req, bool src_needed, - const std::string& revert_reason, - const std::string& commit_reason, - UnderwriteRequestStatus terminal_status = - UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_REJECTED) { - if (src_needed) { - opp::attestations::SwapRequest sr; - auto in = zpp::bits::in{ - std::span{req.attestation_inbound_data.data(), - req.attestation_inbound_data.size()}, - zpp::bits::no_size{}}; - if (in(sr) == zpp::bits::errc{}) { - if (auto src_outpost_opt = find_outpost_id_for_chain(req.src_chain_code)) { - emit_swap_revert(self, *src_outpost_opt, req.id, sr, - req.src_chain_code, req.src_reserve_code, revert_reason); - } - } - } else if (auto user = parse_wire_name(req.depositor)) { - // Swap-from-WIRE: the escrowed WIRE was never credited to a reserve — - // refund it directly (there is no source outpost to route a revert to). - // Full refund: uwreq-stage rejection (race loss, post-drain market - // movement, expiry) is not a caller-controlled revert cause. - action( - permission_level{self, "active"_n}, - uwrit::RESERVE_ACCOUNT, "refundwire"_n, - std::make_tuple(*user, req.src_amount, REFUND_FEE_EXEMPT_BPS) - ).send(); - } else { - sysio::print("reject_and_refund: cannot parse from-WIRE depositor for " - "refund on uwreq ", req.id, "\n"); - } - reqs.modify(same_payer, pk, [&](auto& r) { - r.status = terminal_status; - r.settled_at_ms = current_time_ms(); - r.expires_at_epoch = get_current_epoch() + read_config(self).uwreq_retention_epochs; - for (auto& c : r.commits_by) { - if (c.status == UnderwriteStatus::UNDERWRITE_STATUS_INTENT_SUBMITTED) { - c.status = UnderwriteStatus::UNDERWRITE_STATUS_RELEASED; - c.reason = commit_reason; - } - // Terminal compaction (SEC-129 / WSA-223): the raw UIC blobs were - // only needed to resolve the race; retention keeps the compact - // audit metadata, not the payload bytes. - c.source_uic_bytes.clear(); - c.dest_uic_bytes.clear(); - } - // The refund above already decoded the snapshot's copy; the stored - // bytes have no remaining reader on a terminal row. - r.attestation_inbound_data.clear(); - }); -} - -/// Resolve the race once every REQUIRED leg of a (uwreq, underwriter) pair -/// has arrived. A leg is required iff its chain is an outpost; a depot -/// (WIRE) leg needs no UIC, no bond, and no lock — single-leg swaps -/// (to/from WIRE) therefore resolve on their one outpost commit. On a win: -/// verify the required legs' signatures + bond, pre-validate reserve -/// liquidity against the local mirror AND pre-build the outbound SWAP_REMIT -/// envelope (so both the inline reserv settlement actions and the remit are -/// unreachable-failure by construction — nothing past the CONFIRMED write can -/// `check()`-abort and stall evalcons), push one lock per required leg (a 12h -/// wall-clock challenge window — never released by delivery: `chklocks` sweeps -/// it at expiry, or `sweeplocks` erases it earlier on an UPHELD challenge), -/// mark CONFIRMED, then settle: -/// * normal — reserv::applyswap + SWAP_REMIT to the dst outpost -/// * from-WIRE — reserv::applyfromwire + SWAP_REMIT to the dst outpost -/// * to-WIRE — reserv::paywire (REAL WIRE to the recipient; no remit) -/// Stale/invalid evidence disqualifies a candidate durably. Candidate-local -/// eligibility, collateral, and identity-link gaps, plus mutable reserve -/// availability, remain PENDING until an outpost replays an exact stored UIC. -/// Every failure remains non-throwing inside the enclosing consensus dispatch. -void try_select_winner(name self, uint64_t uwreq_id, name candidate, - std::optional just_verified_leg) { - uwrit::uwreqs_t reqs(self); - auto pk = uwrit::id_key{uwreq_id}; - if (!reqs.contains(pk)) return; - auto req = reqs.get(pk); - if (req.status != UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING) return; - - const bool src_needed = !leg_is_depot(req.src_chain_code); - const bool dst_needed = !leg_is_depot(req.dst_chain_code); - - // The resolver only operates on one complete, live candidate. Keeping this - // invariant here prevents consensus dispatch from reaching costly - // permission recovery with incomplete or terminal evidence. - const uwrit::commit_entry* ce_ptr = nullptr; - for (const auto& c : req.commits_by) { - if (c.underwriter == candidate) { ce_ptr = &c; break; } - } - if (!ce_ptr || - ce_ptr->status != UnderwriteStatus::UNDERWRITE_STATUS_INTENT_SUBMITTED || - !required_uic_legs_complete(req, *ce_ptr)) { - return; - } - - // ── Underwriter eligibility — role + activation gate ───────────────── - // Mirrored bond availability and UIC signature recovery alone do NOT - // establish that `candidate` is an authorized underwriter. opreg registers - // every non-bootstrapped operator as UNKNOWN, forbids bootstrapping an - // underwriter active, and only flips an underwriter to ACTIVE through - // `opreg::processuw` once it clears `req_uw_collat`. The balance mirror - // (`available_via_mirrors`) zeroes only SLASHED / TERMINATED and ignores - // `op.type`, so without this gate a non-underwriter (PRODUCER / BATCH / - // CHALLENGER) or a not-yet-active underwriter (UNKNOWN / WARMUP / COOLDOWN) - // carrying enough mirrored balance and a valid UIC signature would be - // selected as the winner, consume lock capacity, and settle against the - // reserves. Require an ACTIVE UNDERWRITER before any signature work, lock, - // CONFIRMED write, or reserve mutation. This status can change through the - // ordinary opreg lifecycle, so retain valid UIC evidence and let an exact - // external replay retry after it becomes eligible rather than permanently - // disqualifying it. This resolver runs inside the evalcons dispatch chain, - // so it must remain non-throwing (`feedback_opp_handlers_never_throw`). - if (!is_active_underwriter(candidate)) { - sysio::print("try_select_winner: candidate ", candidate, - " is not an ACTIVE underwriter for uwreq ", uwreq_id, - ", leaving valid commit retryable\n"); - return; - } - - // ── T7: permission freshness ────────────────────────────────────────── - // `rcrdcommit` passes its incoming leg after verifying it immediately, so - // only the older stored opposite leg needs another recovery against current - // owner/active keys before locks or settlement. - if (src_needed && just_verified_leg != std::optional{uic_leg::source} && - !validate_candidate_uic(reqs, pk, uwreq_id, candidate, uic_leg::source, - ce_ptr->source_outpost_id, - ce_ptr->source_uic_bytes)) { - return; - } - if (dst_needed && just_verified_leg != std::optional{uic_leg::destination} && - !validate_candidate_uic(reqs, pk, uwreq_id, candidate, - uic_leg::destination, ce_ptr->dest_outpost_id, - ce_ptr->dest_uic_bytes)) { - return; - } - - // ── Settlement quote — re-price on the live curve ──────────────────── - // The books move in this transaction, so the price they move at is the price - // computed HERE, not the one quoted at ingestion. Settling on a stale quote - // would over-debit the destination reserve by exactly the drift the LP took - // between ingestion and the race. - // - // Only the quote is computed at this point — every decision it feeds is made - // below, AFTER the candidate has proved its bond. The quote must come first - // regardless, because it is what sizes the obligation the bond has to cover. - const uint64_t settle_quote = swap_quote( - req.src_chain_code, req.src_token_code, req.src_reserve_code, - req.dst_chain_code, req.dst_token_code, req.dst_reserve_code, - req.src_amount, current_fee_bps(self)); - if (settle_quote != 0) { - // Every downstream read of `req.dst_amount` — bond coverage below, the - // reserve pre-checks, the lock amount, the SWAP_REMIT payload, and the - // reserv settlement actions — now uses the live price, and the CONFIRMED - // write persists it as the amount the winner owes. When the curve cannot - // price the swap the stored ingestion quote stands in for the bond check; - // the request is terminated (or skipped) below before it could settle. - req.dst_amount = settle_quote; - } - - // ── Bond availability — required legs, aggregated per collateral bucket ─ - // Underwriter collateral is held and rolled up by (underwriter, - // chain_code, token_code) — NOT by reserve_code (see `available_via_mirrors` - // / `opreg::available`). A same-(chain, token) swap between two reserves - // (distinct `reserve_code`, a shape `rcrdcommit` explicitly routes) draws - // BOTH required legs against the SAME balance bucket. Checking each leg - // independently would let an underwriter whose balance covers each single - // leg but not their sum win and write two locks that overcommit the one - // balance: balance 150 wins `src_amount=100` + `dst_amount=100` because - // 150>=100 holds per leg, yet the aggregate 200 is uncovered, and - // the deferred slash/remit cleanup (`opreg::releaselock` inside `chklocks`) - // then has to draw 200 from a 150 balance. Require availability to cover - // the AGGREGATE required amount of every leg that shares a collateral - // bucket. The sum is computed in uint128: `src + dst` can exceed uint64, - // and a balance is itself uint64, so an overflowing aggregate must read as - // genuinely insufficient — NOT saturate to UINT64_MAX (which a UINT64_MAX - // available balance would spuriously satisfy) and NOT wrap to a small - // passing value. Collateral can be replenished or freed by lock expiry, so - // insufficient coverage keeps this valid candidate replayable while the - // uwreq remains PENDING. This resolver is non-throwing - // (`feedback_opp_handlers_never_throw`). - const bool same_bucket = src_needed && dst_needed - && req.src_chain_code == req.dst_chain_code - && req.src_token_code == req.dst_token_code; - if (same_bucket) { - // One opreg balance funds both legs — it must cover their sum. uint128 - // keeps the comparison honest when src+dst exceeds uint64 (no uint64 - // balance can cover it, so availability is insufficient by construction). - const uint64_t avail = available_via_mirrors(self, candidate, - req.src_chain_code, req.src_token_code); - const uint128_t need = static_cast(req.src_amount) + req.dst_amount; - if (static_cast(avail) < need) { - const std::string required = need > std::numeric_limits::max() - ? "greater than uint64 maximum" - : std::to_string(static_cast(need)); - sysio::print("try_select_winner: insufficient available collateral for uwreq ", - uwreq_id, " candidate ", candidate, " in bucket (", - req.src_chain_code.to_string(), ", ", req.src_token_code.to_string(), - "): required=", required, ", available=", avail, - ", leaving valid commit retryable\n"); - return; - } - } else { - // Distinct buckets (or a single required leg) — check each independently. - const uint64_t src_avail = src_needed - ? available_via_mirrors(self, candidate, req.src_chain_code, req.src_token_code) : 0; - const uint64_t dst_avail = dst_needed - ? available_via_mirrors(self, candidate, req.dst_chain_code, req.dst_token_code) : 0; - if (src_needed && src_avail < req.src_amount) { - sysio::print("try_select_winner: insufficient source collateral for uwreq ", - uwreq_id, " candidate ", candidate, - ", leaving valid commit retryable\n"); - return; - } - if (dst_needed && dst_avail < req.dst_amount) { - sysio::print("try_select_winner: insufficient destination collateral for uwreq ", - uwreq_id, " candidate ", candidate, - ", leaving valid commit retryable\n"); - return; - } - } - - // ── Candidate-specific remit eligibility ───────────────────────────── - // Build the outbound remit before any request-global verdict. A missing - // destination-chain authex link belongs to this candidate. It can be added - // without changing the request or UIC evidence, so an exact external replay - // can retry after the link is provisioned rather than permanently - // disqualifying the candidate. - // Request-wide build failures remain terminal, and every outcome here is - // still after the authoritative bond gate above. - uint64_t remit_dst_outpost_id = 0; - std::vector remit_encoded; - if (dst_needed) { - switch (try_build_swap_remit(req, candidate, remit_dst_outpost_id, remit_encoded)) { - case swap_remit_disp::ok: - break; - case swap_remit_disp::disqualified: - sysio::print("try_select_winner: candidate ", candidate, - " has no destination authex link for uwreq ", uwreq_id, - ", leaving valid commit retryable\n"); - return; - case swap_remit_disp::terminal: - reject_and_refund(self, reqs, pk, req, src_needed, - "swap unremittable at race resolution: destination outpost / " - "chain-kind unresolved or stored request undecodable", - "uwreq rejected: destination unremittable at race resolution"); - return; - } - } - - // ── Request-level terminal decisions ───────────────────────────────── - // Deliberately placed AFTER every candidate-specific gate (eligibility, - // signature, bond, destination identity). These outcomes REFUND the user - // and close the request for good, so they must not be reachable by a - // candidate that was never going to win: an ACTIVE underwriter can clear - // the role minimum yet be under-bonded for this particular swap, and - // letting it terminally close a healthy request during transient drift - // would hand any such operator a denial-of-service on other people's swaps. - // A candidate that reaches this point is eligible, remittable, and bonded - // for the currently known obligation, so every remaining verdict belongs - // to the request itself. - if (settle_quote == 0) { - if (required_reserves_active(req.src_chain_code, req.src_token_code, req.src_reserve_code, - req.dst_chain_code, req.dst_token_code, req.dst_reserve_code)) { - // Every required reserve is ACTIVE yet the curve cannot price the swap - // — a drained side, extreme connector weights, or a leg below the - // kernel's pricing floor. Terminal: fail closed rather than settle at a - // price the AMM does not have (WSA-041). - reject_and_refund(self, reqs, pk, req, src_needed, - "unpriceable reserve: zero quote from an ACTIVE reserve at race resolution", - "uwreq reverted at race resolution (unpriceable reserve)"); - return; - } - // A required reserve may be provisioned or activated while the UWREQ is - // still PENDING. Keep the candidate's valid evidence so a later exact - // UIC replay can re-evaluate the live route. - sysio::print("try_select_winner: required reserve missing or not ACTIVE for uwreq ", - uwreq_id, ", leaving request PENDING\n"); - return; - } - // Slippage — the live settlement quote against the user's ORIGINAL - // `target_amount`, never against the previous quote. Measuring drift from - // `dst_amount` (the ingestion quote) would compound the tolerance across the - // two checkpoints: a 10% bound would accept a 91 quote at ingestion and then - // an 83 quote at settlement, delivering 17% below a target of 100. Anchoring - // on the fixed target means the bound the user agreed to is the bound they - // get, whatever path the price took. `target_amount` is guaranteed positive - // by `createuwreq` / `swapfromwire`, so the guard is defensive. - // Non-throwing throughout (`feedback_opp_handlers_never_throw`). - if (req.target_amount != 0) { - const uint64_t diff = settle_quote > req.target_amount - ? settle_quote - req.target_amount - : req.target_amount - settle_quote; - const uint128_t allowed = variance_allowance(settle_quote, req.variance_tolerance_bps); - if (static_cast(diff) > allowed) { - reject_and_refund(self, reqs, pk, req, src_needed, - "variance exceeded tolerance at race resolution: " - "target=" + std::to_string(req.target_amount) - + " current=" + std::to_string(settle_quote) - + " tolerance_bps=" + std::to_string(req.variance_tolerance_bps), - "uwreq reverted at race resolution (variance drift)"); - return; - } - } - - // ── Settlement pre-checks against the local reserve mirror ─────────── - // The reserv-side settlement actions (`applyswap` / `applyfromwire` / - // `paywire`) `check()`-abort on violation; an abort inside this - // dispatch chain stalls consensus, so every condition is pre-validated - // here — making the inline actions unreachable-failure by construction - // (no other reserve mutation can interleave within this transaction). - // A live reserve shortfall is shared mutable state. It may recover through - // a reserve top-up or a different settlement, so keep the request PENDING - // rather than letting a momentary shortfall permanently refund it. - name towire_recipient{}; - if (!dst_needed) { - // Swap-to-WIRE. Terminal first: a malformed recipient can never - // become valid — revert to the source outpost + REJECT. - opp::attestations::SwapRequest sr0; - { - auto in = zpp::bits::in{ - std::span{req.attestation_inbound_data.data(), - req.attestation_inbound_data.size()}, - zpp::bits::no_size{}}; - if (in(sr0) != zpp::bits::errc{}) { - sysio::print("try_select_winner: cannot decode stored SwapRequest " - "for uwreq ", uwreq_id, "\n"); - return; - } - } - std::vector recipient_bytes{sr0.recipient.address.begin(), - sr0.recipient.address.end()}; - auto rcpt = parse_wire_name(recipient_bytes); - if (!rcpt || !is_account(*rcpt)) { - // A malformed recipient can never become valid (terminal). to-WIRE - // always has an outpost source leg, so this routes a SWAP_REVERT - // back to it. - reject_and_refund(self, reqs, pk, req, src_needed, - "swap-to-WIRE rejected: recipient is not a valid WIRE account", - "uwreq rejected: invalid WIRE recipient"); - return; - } - towire_recipient = *rcpt; - // Terminal: the to-WIRE payout is sent as `asset(dst_amount, WIRE)` by - // reserv::paywire, and an amount past asset::max_amount (2^62-1) has no - // WIRE-asset representation — it would (a) wrap the `dst_amount + - // to_wire_fee` sufficiency guard below and (b) abort paywire's asset() - // constructor mid-evalcons, stalling consensus. Since `dst_amount` is the - // AMM quote (WNS-02), it is bounded by the source reserve's WIRE side, - // itself an asset-bounded balance — so this is unreachable - // defense-in-depth rather than a live path. Kept because the consequence - // of it ever becoming reachable is a chain-wide consensus stall; it must - // stay AHEAD of any CONFIRMED / lock / reserve write. - if (req.dst_amount > static_cast(asset::max_amount)) { - reject_and_refund(self, reqs, pk, req, src_needed, - "swap-to-WIRE rejected: quoted amount exceeds the maximum " - "representable WIRE asset", - "uwreq rejected: to-WIRE quote exceeds asset max_amount"); - return; - } - auto src_r = find_active_reserve(req.src_chain_code, req.src_token_code, req.src_reserve_code); - // paywire gives up `dst_amount` (to the recipient) + the fee (on the gross - // WIRE leg) out of the source reserve's WIRE; pre-validate that exact sum. - // `dst_amount` is bounded <= asset::max_amount above and `to_wire_fee` - // derives from the (asset-bounded) reserve WIRE, so the sum cannot wrap - // uint64 — but add saturating so the guard is provably overflow-free at - // the settlement boundary regardless of inputs. - const uint64_t w_gross = src_r - ? opp::amm::token_to_wire(src_r->reserve_chain_amount, src_r->reserve_wire_amount, - src_r->connector_weight_bps, req.src_amount) - : 0; - // The TOTAL fee `paywire` will charge on this leg: the network fee plus - // the source reserve's own owner fee (swap-to-WIRE has no destination - // reserve). Only `.fee` is read, and the total is independent of how the - // network fee sub-divides, so the emissions share is irrelevant here. - const uint64_t to_wire_fee = opp::amm::split_wire_fee( - w_gross, current_fee_bps(self), reserve::FEE_UNDERWRITER_SHARE_BPS, - /*emissions_share_bps*/ 0, - src_r ? src_r->owner_fee_bps : 0, /*dst_reserve_fee_bps*/ 0).fee; - const uint64_t wire_needed = opp::safe::add_sat_u64(req.dst_amount, to_wire_fee); - if (!src_r || w_gross == 0 || src_r->reserve_wire_amount < wire_needed) { - sysio::print("try_select_winner: insufficient source-reserve WIRE for uwreq ", - uwreq_id, ", leaving request PENDING\n"); - return; - } - } else if (!src_needed) { - // Swap-from-WIRE. drainfwq validated at queue-drain; re-validate the - // live state (privacy is immutable, but liquidity can drift). - auto dst_r = find_active_reserve(req.dst_chain_code, req.dst_token_code, req.dst_reserve_code); - if (!dst_r || dst_r->is_private || dst_r->reserve_chain_amount < req.dst_amount) { - sysio::print("try_select_winner: destination reserve cannot settle uwreq ", - uwreq_id, ", leaving request PENDING\n"); - return; - } - } else { - // Normal outpost↔outpost swap — both rows must cover the four-leg - // apply (the WIRE intermediate is derived from the same pre-mutation - // source row `applyswap` will read). - auto src_r = find_active_reserve(req.src_chain_code, req.src_token_code, req.src_reserve_code); - auto dst_r = find_active_reserve(req.dst_chain_code, req.dst_token_code, req.dst_reserve_code); - // applyswap debits the gross weighted WIRE intermediate from the source - // (the fee is taken from it before the net reaches dst); pre-validate the - // same conditions applyswap will check. - const uint64_t w_gross = src_r - ? opp::amm::token_to_wire(src_r->reserve_chain_amount, src_r->reserve_wire_amount, - src_r->connector_weight_bps, req.src_amount) - : 0; - if (!src_r || !dst_r || w_gross == 0 || - src_r->reserve_wire_amount < w_gross || - dst_r->reserve_chain_amount < req.dst_amount) { - sysio::print("try_select_winner: insufficient reserve liquidity for uwreq ", - uwreq_id, ", leaving request PENDING\n"); - return; - } - } - - // Winner — push one lock per REQUIRED leg + mark uwreq CONFIRMED. - // Each lock_entry carries the matching leg's full slug_name triple - // (`chain_code, token_code, reserve_code`) so a future slash routes - // unambiguously back to the originating reserve. Locks are a - // wall-clock challenge window (12h default): they are NEVER released - // by delivery — `chklocks` (epoch advance) sweeps them after - // `expires_at_ms` on the healthy path, and `sweeplocks` erases them - // ahead of it when a challenge against the commitment is UPHELD. - const uint64_t now_ms_v = current_time_ms(); - uwrit::uwconfig_t uwcfg_tbl(self); - auto uwcfg = uwcfg_tbl.get_or_default(uwrit::uw_config{}); - const uint64_t expires_ms = now_ms_v + uwcfg.collateral_lock_duration_ms; - - uwrit::locks_t locks(self); - - if (src_needed) { - uint64_t src_lock_id = next_lock_id(self); - locks.emplace(ram_payer, uwrit::lock_key{src_lock_id}, uwrit::lock_entry{ - .lock_id = src_lock_id, - .uwreq_id = uwreq_id, - .underwriter = candidate, - .chain_code = req.src_chain_code, - .token_code = req.src_token_code, - .reserve_code = req.src_reserve_code, - .amount = req.src_amount, - .created_at_ms = now_ms_v, - .expires_at_ms = expires_ms, - }); - add_locked_total(self, candidate, req.src_chain_code, req.src_token_code, - req.src_amount); - } - - if (dst_needed) { - uint64_t dst_lock_id = next_lock_id(self); - locks.emplace(ram_payer, uwrit::lock_key{dst_lock_id}, uwrit::lock_entry{ - .lock_id = dst_lock_id, - .uwreq_id = uwreq_id, - .underwriter = candidate, - .chain_code = req.dst_chain_code, - .token_code = req.dst_token_code, - .reserve_code = req.dst_reserve_code, - .amount = req.dst_amount, - .created_at_ms = now_ms_v, - .expires_at_ms = expires_ms, - }); - add_locked_total(self, candidate, req.dst_chain_code, req.dst_token_code, - req.dst_amount); - } - - reqs.modify(same_payer, pk, [&](auto& r) { - r.status = UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_CONFIRMED; - r.winner = candidate; - r.committed_at_ms = current_time_ms(); - // The live quote this race settled at — the amount the winner owes on the - // destination chain, and the amount the locks and the SWAP_REMIT carry. - // Persisting it keeps the row's record of the swap equal to what the - // reserve books actually moved. - r.dst_amount = req.dst_amount; - // Deadline cleared (SEC-129 / WSA-223): a CONFIRMED row is owned by the - // wall-clock lock window — `chklocks` is guaranteed to terminalize it - // once the locks expire, so a second (epoch-count) deadline would just - // race the first. 0 excludes the row from the `pruneuwreqs` sweep. - r.expires_at_epoch = 0; - // Mark the winner's commit_entry CONFIRMED, others RELEASED (loser). - for (auto& c : r.commits_by) { - if (c.underwriter == candidate) { - c.status = UnderwriteStatus::UNDERWRITE_STATUS_INTENT_CONFIRMED; - } else if (c.status == UnderwriteStatus::UNDERWRITE_STATUS_INTENT_SUBMITTED) { - // Eligible loser — promote to RELEASED for retention/debugging. - c.status = UnderwriteStatus::UNDERWRITE_STATUS_RELEASED; - c.reason = "lost the COMMIT race"; - } - // Race resolved — non-winner UIC blobs have no remaining reader. - // The winner's bytes stay through the lock window as - // the challenge-evidence trail; `chklocks` clears them at COMPLETED. - if (c.underwriter != candidate) { - c.source_uic_bytes.clear(); - c.dest_uic_bytes.clear(); - } - } - }); - - // ── Settlement — reserve books move NOW, before any remit leaves ───── - // All reserve mutations are queued ahead of the SWAP_REMIT queueout in - // this same transaction, so every quote between emit and delivery - // prices the post-swap books. There is no failure path past this - // point: everything was verified above, and transport is - // consensus-gated (delivery either reaches consensus or nothing - // happens — the envelope dispute process covers divergence). Success - // is implicit; locks expire on their own 12h window via `chklocks`. - if (!dst_needed) { - // Swap-to-WIRE: pay the recipient REAL WIRE from reserve custody. - // The depot itself is the payer — no outbound remit, no ack. - action( - permission_level{self, "active"_n}, - uwrit::RESERVE_ACCOUNT, "paywire"_n, - std::make_tuple(req.src_chain_code, req.src_token_code, req.src_reserve_code, - req.src_amount, towire_recipient, req.dst_amount, candidate) - ).send(); - } else if (!src_needed) { - // Swap-from-WIRE: the escrowed WIRE becomes dst-reserve liquidity, - // then the normal remit tail to the destination outpost. - action( - permission_level{self, "active"_n}, - uwrit::RESERVE_ACCOUNT, "applyfromwire"_n, - std::make_tuple(req.dst_chain_code, req.dst_token_code, req.dst_reserve_code, - req.src_amount, req.dst_amount, candidate) - ).send(); - queue_swap_remit(self, remit_dst_outpost_id, remit_encoded); - } else { - // Normal swap: emit-time four-leg apply, then the remit tail. - action( - permission_level{self, "active"_n}, - uwrit::RESERVE_ACCOUNT, "applyswap"_n, - std::make_tuple(req.src_chain_code, req.src_token_code, req.src_reserve_code, - req.src_amount, - req.dst_chain_code, req.dst_token_code, req.dst_reserve_code, - req.dst_amount, candidate) - ).send(); - queue_swap_remit(self, remit_dst_outpost_id, remit_encoded); - } -} - -} // anonymous namespace - -// --------------------------------------------------------------------------- -// rcrdcommit — record a per-leg COMMIT arrival -// --------------------------------------------------------------------------- -// -// The leg-classification logic now uses the slug_name triple -// `(from_chain_code, from_token_code, reserve_code)` to disambiguate src -// vs dst. The triple is required because two reserves of the same token -// can coexist on the same chain — same-(chain, token) swaps fall back to -// reserve_code as the tiebreaker. -void uwrit::rcrdcommit(uint64_t uwreq_id, - name underwriter, - uint64_t chain_code, - sysio::slug_name from_chain_code, - sysio::slug_name from_token_code, - sysio::slug_name reserve_code, - std::vector uic_bytes) { - require_auth(MSGCH_ACCOUNT); - - uwreqs_t reqs(get_self()); - auto pk = id_key{uwreq_id}; - // Dispatched-from-msgch handlers MUST NOT throw — a check() halts - // evalcons (`feedback_opp_handlers_never_throw.md`). Silently no-op - // on unknown uwreq_id or wrong status. - if (!reqs.contains(pk)) { - sysio::print("rcrdcommit: unknown uwreq ", uwreq_id, ", skipping\n"); - return; - } - auto req_snapshot = reqs.get(pk); - if (req_snapshot.status != UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, - " not in PENDING (status=", - magic_enum::enum_integer(req_snapshot.status), "), skipping\n"); - return; - } - - // Classify the UIC's leg BEFORE touching commits_by. `src_*`/`dst_*` are set once at - // createuwreq and never change here, so the snapshot's values are authoritative. Route by the - // full `(chain_code, token_code, reserve_code)` triple so same-chain swaps with multiple - // reserves on the same (chain, token) pair land in the correct per-leg slot. - const bool is_source = (from_chain_code == req_snapshot.src_chain_code - && from_token_code == req_snapshot.src_token_code - && reserve_code == req_snapshot.src_reserve_code); - const bool is_dest = (from_chain_code == req_snapshot.dst_chain_code - && from_token_code == req_snapshot.dst_token_code - && reserve_code == req_snapshot.dst_reserve_code); - // A UIC whose leg matches neither the source nor the destination is not bound to this request. - // Drop it with no mutation so a stream of unmatched commits cannot grow commits_by or re-arm a - // disqualified entry. Fail closed, never check(). - if (!is_source && !is_dest) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, - " UIC leg matches neither source nor destination, skipping\n"); - return; - } - - // Only a registered ACTIVE underwriter can win this race — try_select_winner enforces the same - // gate, so an entry for - // any other account is pure dead weight. Refuse to create one: otherwise a matched-leg UIC could - // append one commit_entry per attacker-chosen valid-but-unregistered account name and bloat the - // row. Fail closed, no mutation, never check(). - if (!is_active_underwriter(underwriter)) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " underwriter ", underwriter, - " is not an ACTIVE underwriter, skipping\n"); - return; - } - - // Reject a new name at the roster cap before protobuf decoding, hashing, or - // recovery. Existing candidates can still provide their missing required leg. - const auto existing_it = - std::find_if(req_snapshot.commits_by.begin(), req_snapshot.commits_by.end(), - [&](const commit_entry& c) { return c.underwriter == underwriter; }); - const bool existing_entry = existing_it != req_snapshot.commits_by.end(); - if (!existing_entry && req_snapshot.commits_by.size() >= MAX_UWREQ_CANDIDATES) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " already carries ", - req_snapshot.commits_by.size(), - " candidates (cap reached), skipping new candidate\n"); - return; - } - - const auto incoming_leg = is_source ? uic_leg::source : uic_leg::destination; - - // Disqualification is durable for this request. Once winner-time checks - // reject a candidate, no replay, malformed record, unauthorized record, or - // even newly valid replacement may rewrite the evidence/timestamps or - // re-arm that candidate. Another underwriter can still win the open race. - if (existing_entry && - existing_it->status == UnderwriteStatus::UNDERWRITE_STATUS_DISQUALIFIED) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " candidate ", underwriter, - " is already DISQUALIFIED, skipping\n"); - return; - } - - // A candidate leg is write-once. Outpost delivery is at-least-once, so a - // conflicting duplicate is never replacement evidence. A byte-identical - // replay of a complete candidate is the explicit external trigger for a - // fresh winner-selection pass: it does not mutate evidence or timestamps, - // and `std::nullopt` makes the resolver revalidate every stored leg against - // current permissions. - if (existing_entry && required_uic_leg_populated(*existing_it, incoming_leg)) { - const auto& stored_bytes = is_source - ? existing_it->source_uic_bytes - : existing_it->dest_uic_bytes; - if (stored_bytes != uic_bytes) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " candidate ", underwriter, - " received conflicting duplicate ", - is_source ? "source" : "destination", " UIC, skipping\n"); - return; - } - if (required_uic_legs_complete(req_snapshot, *existing_it)) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " candidate ", underwriter, - " received exact stored UIC replay, re-evaluating\n"); - try_select_winner(get_self(), uwreq_id, underwriter, std::nullopt); - return; - } - sysio::print("rcrdcommit: uwreq ", uwreq_id, " candidate ", underwriter, - " already carries a ", - is_source ? "source" : "destination", - " UIC, skipping duplicate\n"); - return; - } - - // Reject oversized blobs before protobuf decoding, hashing, or recovery. - // Candidate evidence/status/reason/timestamps remain untouched. - if (uic_bytes.size() > MAX_UIC_LEG_BYTES) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " UIC payload of ", - uic_bytes.size(), " bytes exceeds the per-leg cap, skipping\n"); - return; - } - - // Validate the claimed underwriter's signature before changing candidate - // evidence/status/reason/timestamps. - // Current outposts bind the canonical UIC's claimed identity and external - // address to their authenticated caller, while the depot remains - // authoritative for binding `uw_account` to current WIRE permission keys. - // An invalid claim must never overwrite an honest - // candidate's previously stored leg, re-arm its status, or create a - // competitor-visible entry. Log and ignore it; later - // records in the same envelope continue dispatching. - const auto signature_result = verify_uic_signature(underwriter, uic_bytes); - if (signature_result != uic_signature_result::valid) { - log_uic_signature_rejection(uwreq_id, underwriter, incoming_leg, - chain_code, signature_result); - return; - } - - // Remaining row-growth rails (SEC-129 / WSA-223) — every rail fails closed - // with no mutation, never check() (we are inside the evalcons dispatch - // chain). The candidate roster is capped so the ACTIVE-underwriter population - // bounds scan cost but not row size… - // …and the projected whole-row size is guarded so THIS modify — and every - // later consensus-dispatched one — stays clear of chain KV value limits. - // Conservative projection: current packed row + the incoming leg bytes + - // a fixed allowance for a new entry's non-vector fields. - const size_t projected_row_bytes = pack_size(req_snapshot) - + uic_bytes.size() - + (existing_entry ? 0 : COMMIT_ENTRY_PACK_ALLOWANCE_BYTES); - if (projected_row_bytes > MAX_UWREQ_ROW_BYTES) { - sysio::print("rcrdcommit: uwreq ", uwreq_id, " projected row size ", - projected_row_bytes, " bytes exceeds the row cap, skipping\n"); - return; - } - - reqs.modify(same_payer, pk, [&](auto& r) { - auto* c = find_or_create_commit(r, underwriter); - const uint64_t admitted_at_ms = current_time_ms(); - if (is_source) { - c->source_received_at_ms = admitted_at_ms; - c->source_outpost_id = chain_code; - c->source_uic_bytes = uic_bytes; - } else if (is_dest) { - c->dest_received_at_ms = admitted_at_ms; - c->dest_outpost_id = chain_code; - c->dest_uic_bytes = uic_bytes; - } - }); - - // Re-read after modify — try_select_winner needs the latest commit_entry. - // A leg is required iff its chain is an outpost; a depot (WIRE) leg is - // satisfied by construction (no outpost exists to send a UIC for it), - // so single-leg swaps arm the race on their one real commit. - auto refreshed = reqs.get(pk); - for (const auto& c : refreshed.commits_by) { - if (c.underwriter != underwriter) continue; - if (required_uic_legs_complete(refreshed, c)) { - try_select_winner(get_self(), uwreq_id, underwriter, incoming_leg); - } - break; - } -} - -// --------------------------------------------------------------------------- -// swapfromwire — queue a depot-originated swap (WIRE is the source) -// --------------------------------------------------------------------------- -// -// User transaction (require_auth(user)) — reverting on validation failure -// is the correct mode here, exactly like opreg::deposit. The WIRE escrow -// and the queue row land atomically; the uwreq itself is NOT created in -// this transaction (that would short-circuit ledger consistency) — the -// next sysio.epoch::advance drains the queue via drainfwq. -void uwrit::swapfromwire(name user, - uint64_t wire_amount, - sysio::slug_name dst_chain_code, - sysio::slug_name dst_token_code, - sysio::slug_name dst_reserve_code, - uint64_t target_amount, - uint32_t target_tolerance_bps, - opp::types::ChainKind recipient_kind, - std::vector recipient_addr) { - require_auth(user); - check(wire_amount > 0, "swapfromwire: wire_amount must be positive"); - // Queue-slot price floor (default 5 WIRE, retunable via setconfig): the - // escrow of a failed row is refunded, so without a floor dust rows could - // hold drain slots while locking no meaningful capital — and sit below the - // Bancor kernel's pricing floor, guaranteeing zero-quote reverts. - check(wire_amount >= read_config(get_self()).min_fromwire_amount, - "swapfromwire: wire_amount below the configured minimum"); - check(target_amount > 0, "swapfromwire: target_amount must be positive"); - check(!recipient_addr.empty() && recipient_addr.size() <= 64, - "swapfromwire: recipient address must be 1..64 bytes"); - check(chain_registered_active(dst_chain_code), - "swapfromwire: target chain not registered or not active"); - check(!leg_is_depot(dst_chain_code), - "swapfromwire: target must be an outpost chain (WIRE->WIRE is just " - "a token transfer)"); - { - auto kind_opt = chain_kind_for_code(dst_chain_code); - check(kind_opt.has_value() && *kind_opt == recipient_kind, - "swapfromwire: recipient_kind does not match the target chain's kind"); - } - - // Advisory reserve checks for UX — drainfwq re-validates authoritatively - // at the epoch boundary (state may change in between; drain refunds). - { - auto r = find_reserve(dst_chain_code, dst_token_code, dst_reserve_code); - check(r.has_value(), "swapfromwire: target reserve not found"); - check(r->status == ReserveStatus::RESERVE_STATUS_ACTIVE, - "swapfromwire: target reserve not ACTIVE"); - check(!r->is_private, - "swapfromwire: private reserves are excluded from WIRE-endpoint swaps"); - } - - // Escrow REAL WIRE into reserve custody NOW. The queue row records the - // claim; until drainfwq either creates the uwreq (escrow becomes - // reserve liquidity at race win) or refunds, this sits as the - // in-flight-escrow term of the custody invariant. - action( - permission_level{user, "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(user, RESERVE_ACCOUNT, - asset(static_cast(wire_amount), opp::wire::asset_symbol), - std::string("sysio.uwrit::swapfromwire escrow")) - ).send(); - - uint64_t qid = next_fromwire_id(get_self()); - fwqueue_t q(get_self()); - q.emplace(ram_payer, fw_key{qid}, fromwire_q{ - .id = qid, - .user = user, - .wire_amount = wire_amount, - .dst_chain_code = dst_chain_code, - .dst_token_code = dst_token_code, - .dst_reserve_code = dst_reserve_code, - .target_amount = target_amount, - .variance_tolerance_bps = target_tolerance_bps, - .recipient_kind = recipient_kind, - .recipient_addr = std::move(recipient_addr), - .created_at_epoch = get_current_epoch(), - }); -} - -// --------------------------------------------------------------------------- -// drainfwq — epoch-boundary drain of the swap-from-WIRE queue -// --------------------------------------------------------------------------- -// -// Inlined from sysio.epoch::advance. NEVER throws: every reachable -// `check()` in the reserv actions it inlines (`refundwire`) is -// pre-guaranteed (wire_amount > 0 enforced at swapfromwire; the user -// account existed at escrow time and accounts are not deletable; the -// post-fee refund stays positive because setconfig caps the revert fee -// below 100%). Validation failures refund + drop; only validated rows -// become PENDING uwreqs. -// -// Pre-guaranteeing `refundwire`'s own `check()`s is necessary but was NOT -// sufficient while the refund was a pushed `sysio.token::transfer`. A transfer -// notifies its recipient, and the chain executes notified receivers with no -// exception isolation, so the refund recipient -- not this contract -- decided -// whether `advance` committed. A recipient asserting in its notify handler (or -// merely burning CPU) aborted the whole transaction, and because the `q.erase()` -// below rolls back with it, the offending row survived and re-blocked every -// later epoch: a permanent chain-wide epoch halt bought for one -// `min_fromwire_amount` escrow. `refundwire` now credits a claimable balance and -// transfers nothing, so no counterparty sits inside this drain's abort surface. -void uwrit::drainfwq() { - check(has_auth(EPOCH_ACCOUNT) || has_auth(get_self()), - "drainfwq requires sysio.epoch or sysio.uwrit authority"); - - fwqueue_t q(get_self()); - uwreqs_t reqs(get_self()); - const auto depot_code = depot_chain_code(); - // One config snapshot for the whole drain: quotes and revert fees inside a - // single action must charge one rate. - const uw_config cfg = read_config(get_self()); - - // Bounded FIFO drain (SEC-77): process at most MAX_FWQ_DRAIN_PER_EPOCH rows - // per advance, oldest first (primary-key = id order). Every branch of the - // loop body erases the front row, so re-reading `q.begin()` each iteration - // walks the queue forward and the next advance resumes where this one - // stopped; undrained rows keep their WIRE escrow in reserve custody and - // drain a later epoch. Bounding this loop is what keeps `advance` inside its - // hard, uncatchable CPU deadline: an unbounded drain over an attacker- - // inflated queue would abort every advance and stall epoch progress chain- - // wide. `drained` is incremented before any of the per-row `continue` - // branches, so it counts every attempt (refund-and-drop included). Reading - // only the front row (not a full-table pre-copy) also bounds the scan cost. - uint32_t drained = 0; - while (drained < MAX_FWQ_DRAIN_PER_EPOCH) { - auto head = q.begin(); - if (head == q.end()) break; - const auto row = *head; - ++drained; - - // Fault taxonomy for the revert fee: system-caused failures (the chain - // registry or reserve state changed AFTER swapfromwire validated the - // row) refund in full via REFUND_FEE_EXEMPT_BPS; failures the caller's - // own parameters produce (unpriceable escrow, missed variance - // tolerance) forfeit `fromwire_revert_fee_bps` so revert churn cannot - // recycle for free. - auto refund_and_drop = [&](const char* why, uint32_t revert_fee_bps) { - sysio::print("drainfwq: ", why, " — refunding queued swap ", row.id, "\n"); - action( - permission_level{get_self(), "active"_n}, - RESERVE_ACCOUNT, "refundwire"_n, - std::make_tuple(row.user, row.wire_amount, revert_fee_bps) - ).send(); - q.erase(fw_key{row.id}); - }; - - if (!depot_code) { - refund_and_drop("depot chain not registered", REFUND_FEE_EXEMPT_BPS); - continue; - } - auto r = find_reserve(row.dst_chain_code, row.dst_token_code, row.dst_reserve_code); - if (!r || r->status != ReserveStatus::RESERVE_STATUS_ACTIVE) { - refund_and_drop("target reserve missing or not ACTIVE", REFUND_FEE_EXEMPT_BPS); - continue; - } - if (r->is_private) { - refund_and_drop("target reserve is private (excluded from WIRE-endpoint swaps)", - REFUND_FEE_EXEMPT_BPS); - continue; - } - // Authoritative variance check. The target reserve is confirmed ACTIVE - // above, so a zero quote here is NOT an unprovisioned-LP skip — it is a - // degenerate / unpriceable ACTIVE reserve (token side drained to zero, - // extreme connector weights, or a WIRE escrow too small for the - // weighted-Bancor kernel to price). Fail closed and refund: otherwise a - // caller-chosen target_amount bypasses variance and drains the reserve's - // token side for a negligible WIRE escrow (WSA-041). - const uint64_t quote = swap_quote(*depot_code, opp::wire::token_code, - opp::wire::token_code, - row.dst_chain_code, row.dst_token_code, - row.dst_reserve_code, row.wire_amount, - cfg.fee_bps); - if (quote == 0) { - refund_and_drop("unpriceable target reserve: zero quote from an ACTIVE reserve", - cfg.fromwire_revert_fee_bps); - continue; - } - { - // `target_amount > 0` is guaranteed by swapfromwire. The allowance is a - // fraction of the AMM `quote`, never of the user's target (WNS-02) — - // see `variance_allowance`. - const uint64_t diff = quote > row.target_amount - ? quote - row.target_amount - : row.target_amount - quote; - const uint128_t allowed = variance_allowance(quote, row.variance_tolerance_bps); - if (static_cast(diff) > allowed) { - refund_and_drop("variance exceeded tolerance at drain", - cfg.fromwire_revert_fee_bps); - continue; - } - } - - // Synthetic SwapRequest payload — the settlement tail - // (`try_build_swap_remit`) decodes `recipient` from the stored bytes - // exactly as it does for outpost-originated swaps. - opp::attestations::SwapRequest sr; - sr.actor.kind = ChainKind::CHAIN_KIND_WIRE; - { - auto user_bytes = wire_name_bytes(row.user); - sr.actor.address.assign(user_bytes.begin(), user_bytes.end()); - } - sr.source_amount.token_code = opp::wire::token_code.value; - sr.source_amount.amount = static_cast(row.wire_amount); - sr.source_chain_code = depot_code->value; - sr.source_reserve_code = opp::wire::token_code.value; // sentinel — no WIRE-side reserve - sr.target_chain_code = row.dst_chain_code.value; - sr.target_token_code = row.dst_token_code.value; - sr.target_reserve_code = row.dst_reserve_code.value; - sr.recipient.kind = row.recipient_kind; - sr.recipient.address.assign(row.recipient_addr.begin(), row.recipient_addr.end()); - sr.target_amount = row.target_amount; - sr.target_tolerance_bps = row.variance_tolerance_bps; - sr.target_timestamp_ms = 0; - // Synthetic but non-empty source_tx_id (8-byte LE row id) so the - // uwreq row shape matches outpost-originated swaps. - std::vector stx(8); - for (size_t i = 0; i < 8; ++i) { - stx[i] = static_cast((row.id >> (i * 8)) & 0xff); - } - sr.source_tx_id.assign(stx.begin(), stx.end()); - - std::vector encoded; - auto out = zpp::bits::out{encoded, zpp::bits::no_size{}}; - if (out(sr) != zpp::bits::errc{}) { - // Internal encode failure — not a caller-controlled revert cause. - refund_and_drop("failed to encode synthetic SwapRequest", REFUND_FEE_EXEMPT_BPS); - continue; - } - - auto pk = id_key{row.id}; - if (reqs.contains(pk)) { - // Idempotency backstop — the id space makes this unreachable. - q.erase(fw_key{row.id}); - continue; - } - reqs.emplace(ram_payer, pk, uw_request_t{ - .id = row.id, - .type = AttestationType::ATTESTATION_TYPE_SWAP_REQUEST, - .status = UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING, - .src_chain_code = *depot_code, - .src_token_code = opp::wire::token_code, - .src_reserve_code = opp::wire::token_code, - .src_amount = row.wire_amount, - .dst_chain_code = row.dst_chain_code, - .dst_token_code = row.dst_token_code, - .dst_reserve_code = row.dst_reserve_code, - // The AMM quote, never the user's `target_amount` (WNS-02) — same - // contract as `createuwreq`; re-quoted at race resolution. - .dst_amount = quote, - // The user's expectation, retained as the FIXED slippage reference. - .target_amount = row.target_amount, - .variance_tolerance_bps = row.variance_tolerance_bps, - .source_tx_id = std::move(stx), - .depositor = wire_name_bytes(row.user), - .commits_by = {}, - .winner = name{}, - .committed_at_ms = 0, - .settled_at_ms = 0, - // Same PENDING deadline as createuwreq (SEC-129 / WSA-223): a - // from-WIRE race that never resolves is expired by `pruneuwreqs`, - // which refunds the escrow in full via `refundwire`. - .expires_at_epoch = get_current_epoch() + cfg.uwreq_pending_timeout_epochs, - .attestation_inbound_data = std::move(encoded), - .attestation_outbound_data = {}, - }); - q.erase(fw_key{row.id}); - } -} - -// --------------------------------------------------------------------------- -// sumlocks — read-only helper -// --------------------------------------------------------------------------- -uint64_t uwrit::sumlocks(name underwriter, - sysio::slug_name chain_code, - sysio::slug_name token_code) { - return sum_locks_inline(get_self(), underwriter, chain_code, token_code); -} - -// --------------------------------------------------------------------------- -// chklocks — epoch-boundary sweep of expired locks (the only HEALTHY release -// path; `sweeplocks` also erases, on an UPHELD challenge) -// --------------------------------------------------------------------------- -// -// Locks are a wall-clock challenge window (12h default via uwconfig) and -// are never released by delivery. This sweep: -// 1. erases every lock whose `expires_at_ms` has elapsed, -// 2. inlines `opreg::releaselock` per lock — the deferred-slash -// (SLASHED) / deferred-remit (TERMINATED) / no-op (healthy) hop that -// settles the underwriter's bond as the challenge window closes, -// 3. flips a CONFIRMED uwreq to COMPLETED once its last lock is swept -// (delivery success is implicit — there is no SWAP_REMIT ack). -// -// Runs as one of the FIRST steps of sysio.epoch::advance so freshly freed -// collateral is visible to the same advance's withdraw flushing. NEVER -// throws on reachable state: lock amounts are provably positive (enforced -// at uwreq/queue creation), so opreg::releaselock's amount>0 check cannot -// fire; and releaselock clamps its balance subtraction to the live bucket, -// so even a residual over-committed lock set cannot underflow + abort the -// advance (defence-in-depth — the winner-selection aggregate bond check -// already prevents the over-commit at lock-creation time). -namespace { - -/// COMPLETED flip for uwreqs whose LAST lock just left the table — a CONFIRMED -/// uwreq with no remaining locks has exited its challenge window. The flip -/// stamps the retention deadline (`pruneuwreqs` erases the row once it -/// elapses) and clears the remaining heavy payloads: with the window closed, -/// the winner's UIC evidence and the inbound attestation copy have no -/// remaining reader — retention keeps the compact audit metadata only -/// (SEC-129 / WSA-223). Shared by `chklocks` (natural expiry) and -/// `sweeplocks` (an UPHELD underwriter challenge's slash-sweep). -void finalize_settled_uwreqs(name self, uwrit::locks_t& locks, - const std::vector& affected) { - uwrit::uwreqs_t reqs(self); - auto byuwreq = locks.get_index<"byuwreq"_n>(); - const uint32_t now_ep = get_current_epoch(); - const uint32_t retention_epochs = read_config(self).uwreq_retention_epochs; - for (uint64_t id : affected) { - auto lit = byuwreq.lower_bound(id); - if (lit != byuwreq.end() && lit->uwreq_id == id) continue; // locks remain - auto pk = uwrit::id_key{id}; - if (!reqs.contains(pk)) continue; - auto r = reqs.get(pk); - if (r.status != UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_CONFIRMED) continue; - reqs.modify(same_payer, pk, [&](auto& row) { - row.status = UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_COMPLETED; - row.settled_at_ms = current_time_ms(); - row.expires_at_epoch = now_ep + retention_epochs; - row.attestation_inbound_data.clear(); - for (auto& c : row.commits_by) { - c.source_uic_bytes.clear(); - c.dest_uic_bytes.clear(); - } - }); - } -} - -} // anonymous namespace - -void uwrit::chklocks(uint32_t max_rows) { - // Two valid callers, mirroring pruneuwreqs / drainfwq: - // * sysio.epoch::advance — inlined at every epoch boundary with - // MAX_LOCK_RELEASE_PER_EPOCH. - // * sysio.uwrit — manual cleanup invocation with a caller-chosen budget, - // e.g. from a migration. - check(has_auth(EPOCH_ACCOUNT) || has_auth(get_self()), - "chklocks requires sysio.epoch or sysio.uwrit authority"); - if (max_rows == 0) return; - - const uint64_t now_ms = current_time_ms(); - locks_t locks(get_self()); - auto idx = locks.get_index<"byexpire"_n>(); - - // Walk in ascending `expires_at_ms` and collect full copies while - // expired — we erase in a second pass (an erase invalidates the index - // cursor) and need every field for the releaselock fan-out. - // - // A lock HELD by an open underwriter-fault challenge (WIRE-297) is not - // released at expiry: its collateral stays at risk until the council - // resolves or the challenge lapses. Skip it and poke the challenge's - // tally crank instead — this poke is the challenge system's only - // cadence (the chain is not paused during a challenge, so the epoch - // tick keeps arriving; envelope disputes need a standalone crank - // precisely because theirs does not). - // - // Bounded to `max_rows`, for the same reason as MAX_UWREQ_PRUNE_PER_EPOCH - // and MAX_FWQ_DRAIN_PER_EPOCH: the per-lock work here is an inline - // `opreg::releaselock` dispatch plus an erase, and it runs inside - // advance's hard, uncatchable transaction CPU deadline. Expiries arrive - // in BURSTS — every lock is stamped `now + collateral_lock_duration_ms` - // at creation, so one epoch's settlements all fall due in one epoch a - // lock-duration later — and an unbounded sweep of such a burst would - // abort `advance`. Those locks would still be expired at the next - // advance, so it would abort identically every epoch thereafter: a - // permanent chain-wide epoch stall. Ascending expiry order makes the - // bound a FIFO drain; an oversized burst simply spreads across - // subsequent epochs. - // - // The counter is EXAMINED ROWS, not released locks. A held lock is skipped - // rather than released, so counting only releases would let an unbounded - // number of held rows be scanned AND let every distinct challenge among - // them fan out its own inline `chkuwchal` — reintroducing precisely the - // unbounded advance work this bound removes. Counting the row when it is - // examined bounds the scan, the release fan-out and the challenge poke - // fan-out with one budget (`open_challenges.size() <= max_rows` follows). - std::vector expired; - std::vector open_challenges; - uint32_t examined = 0; - for (auto it = idx.begin(); - it != idx.end() && it->expires_at_ms <= now_ms && examined < max_rows; - ++it) { - ++examined; - if (it->challenge_id != 0) { - if (std::find(open_challenges.begin(), open_challenges.end(), - it->challenge_id) == open_challenges.end()) { - open_challenges.push_back(it->challenge_id); - } - continue; - } - expired.push_back(*it); - } - - // Poke each held challenge: quorum reached → it resolves (slash-and-sweep - // or reject-and-free); no quorum → it LAPSES with a bond refund, and the - // freed locks release on the NEXT sweep. Resolution clears the hold in the - // same transaction tree, so a resolved challenge is never re-poked. - for (uint64_t chal_id : open_challenges) { - action( - permission_level{get_self(), "active"_n}, - CHALG_ACCOUNT, "chkuwchal"_n, - std::make_tuple(chal_id) - ).send(); - } - - if (expired.empty()) return; - - std::vector affected; - for (const auto& l : expired) { - action( - permission_level{get_self(), "active"_n}, - OPREG_ACCOUNT, "releaselock"_n, - std::make_tuple(l.underwriter, l.chain_code, l.token_code, l.amount) - ).send(); - locks.erase(lock_key{l.lock_id}); - sub_locked_total(get_self(), l.underwriter, l.chain_code, l.token_code, l.amount); - if (std::find(affected.begin(), affected.end(), l.uwreq_id) == affected.end()) { - affected.push_back(l.uwreq_id); - } - } - - finalize_settled_uwreqs(get_self(), locks, affected); -} - -// --------------------------------------------------------------------------- -// holdlocks / freelocks / sweeplocks — underwriter-challenge lock control -// (WIRE-297; auth = sysio.chalg for all three) -// --------------------------------------------------------------------------- - -void uwrit::holdlocks(uint64_t uwreq_id, name underwriter, uint64_t chal_id) { - require_auth(CHALG_ACCOUNT); - check(chal_id != 0, "holdlocks: challenge id must be non-zero"); - - const uint64_t now_ms = current_time_ms(); - locks_t locks(get_self()); - auto byuwreq = locks.get_index<"byuwreq"_n>(); - - // Authoritative liveness validation for a challenge filing — throwing here - // aborts the WHOLE openuwchal, bond escrow included. Collect first, modify - // second (a modify invalidates the index cursor). - std::vector to_hold; - for (auto it = byuwreq.lower_bound(uwreq_id); - it != byuwreq.end() && it->uwreq_id == uwreq_id; ++it) { - if (it->underwriter != underwriter) continue; - check(it->challenge_id == 0, "holdlocks: lock is already under challenge"); - check(now_ms < it->expires_at_ms, "holdlocks: the lock window has closed"); - to_hold.push_back(it->lock_id); - } - check(!to_hold.empty(), "holdlocks: no locks found for this commitment"); - - for (uint64_t lock_id : to_hold) { - locks.modify(same_payer, lock_key{lock_id}, [&](auto& row) { - row.challenge_id = chal_id; - }); - } - - // Mark the REQUEST too, not just its locks. The locks hold the collateral; - // this row holds the evidence the challenge adjudicates against, and its - // retention window is far shorter than the challenge window — see - // `pruneuwreqs`, which refuses to erase a marked row. - uwreqs_t reqs(get_self()); - auto req_it = reqs.find(id_key{uwreq_id}); - check(req_it != reqs.end(), "holdlocks: uwreq not found"); - reqs.modify(same_payer, id_key{uwreq_id}, [&](auto& row) { - row.challenge_id = chal_id; - }); -} - -void uwrit::freelocks(uint64_t uwreq_id, name underwriter) { - require_auth(CHALG_ACCOUNT); - - locks_t locks(get_self()); - auto byuwreq = locks.get_index<"byuwreq"_n>(); - - // Deliberately soft past the auth gate: this runs inline from the epoch - // tick (`chklocks` → `chkuwchal` → here) on a REJECTED or LAPSED - // challenge, where an abort would stall epoch advancement chain-wide. - std::vector to_free; - for (auto it = byuwreq.lower_bound(uwreq_id); - it != byuwreq.end() && it->uwreq_id == uwreq_id; ++it) { - if (it->underwriter != underwriter || it->challenge_id == 0) continue; - to_free.push_back(it->lock_id); - } - - for (uint64_t lock_id : to_free) { - locks.modify(same_payer, lock_key{lock_id}, [&](auto& row) { - row.challenge_id = 0; - }); - } - - // Release the request's own marker so the retention sweep can reclaim it - // again. Soft like the loop above — this runs inline from the epoch tick, - // where an abort would stall advancement chain-wide. - uwreqs_t reqs(get_self()); - auto req_it = reqs.find(id_key{uwreq_id}); - if (req_it != reqs.end() && req_it->challenge_id != 0) { - reqs.modify(same_payer, id_key{uwreq_id}, [&](auto& row) { - row.challenge_id = 0; - }); - } -} - -void uwrit::sweeplocks(uint64_t uwreq_id, name underwriter) { - require_auth(CHALG_ACCOUNT); - - locks_t locks(get_self()); - auto byuwreq = locks.get_index<"byuwreq"_n>(); - - // Collect the underwriter's locks for this commitment (all held by - // construction on an UPHELD challenge). Soft when empty — same - // never-throw-inside-advance reasoning as `freelocks`. - std::vector held; - for (auto it = byuwreq.lower_bound(uwreq_id); - it != byuwreq.end() && it->uwreq_id == uwreq_id; ++it) { - if (it->underwriter == underwriter) held.push_back(*it); - } - if (held.empty()) return; - - // The underwriter is already SLASHED (`chkuwchal` slashes before sweeping), - // so each releaselock takes its deferred-slash branch: the locked - // collateral is debited from opreg and the outbound SLASH attestation - // queued for the outpost's slash-to-reserve hop. - // This is the THIRD lock-erasure path (with `chklocks` and, historically, - // nothing else), so it carries the same obligation: the `locksums` rollup is - // authoritative for `available()` and must never outlive the rows it - // summarizes. Erasing here without decrementing would leave the bucket - // permanently positive after its last lock is gone — `sumlocks` would report - // phantom locked collateral forever, and a re-registering operator would find - // that collateral unusable even though nothing holds it. - std::vector affected; - for (const auto& l : held) { - action( - permission_level{get_self(), "active"_n}, - OPREG_ACCOUNT, "releaselock"_n, - std::make_tuple(l.underwriter, l.chain_code, l.token_code, l.amount) - ).send(); - locks.erase(lock_key{l.lock_id}); - sub_locked_total(get_self(), l.underwriter, l.chain_code, l.token_code, l.amount); - if (std::find(affected.begin(), affected.end(), l.uwreq_id) == affected.end()) { - affected.push_back(l.uwreq_id); - } - } - - // Release the request's challenge marker. The UPHELD path ERASES the locks - // rather than clearing their markers, so this is the ONLY place the row's - // own marker comes off on that branch — without it `pruneuwreqs` would skip - // the row forever and the retention sweep could never reclaim it. - { - uwreqs_t reqs(get_self()); - auto req_it = reqs.find(id_key{uwreq_id}); - if (req_it != reqs.end() && req_it->challenge_id != 0) { - reqs.modify(same_payer, id_key{uwreq_id}, [&](auto& row) { - row.challenge_id = 0; - }); - } - } - - finalize_settled_uwreqs(get_self(), locks, affected); -} - -// --------------------------------------------------------------------------- -// pruneuwreqs — bounded UWREQ lifecycle sweep (SEC-129 / WSA-223) -// --------------------------------------------------------------------------- -// -// The enforcement half of `uw_request_t.expires_at_epoch`: every deadline is -// stamped by the lifecycle writes (createuwreq / drainfwq / reject_and_refund -// / chklocks) and evaluated only HERE, lazily, when the epoch machinery -// triggers this action. Nothing is scheduled; a row past its deadline is -// inert data until the sweep fires. NEVER throws past the auth gate: it runs -// inline inside `sysio.epoch::advance`, where an abort stalls epoch progress -// chain-wide. The per-call `max_rows` budget keeps the sweep inside advance's -// transaction CPU deadline (same rationale as MAX_FWQ_DRAIN_PER_EPOCH); a -// backlog drains across subsequent epochs. -void uwrit::pruneuwreqs(uint32_t max_rows) { - // Two valid callers, mirroring chklocks / drainfwq: - // * sysio.epoch::advance — inlined each epoch with MAX_UWREQ_PRUNE_PER_EPOCH. - // * sysio.uwrit — manual backlog drain with a caller-chosen budget. - check(has_auth(EPOCH_ACCOUNT) || has_auth(get_self()), - "pruneuwreqs requires sysio.epoch or sysio.uwrit authority"); - if (max_rows == 0) return; - - const uint32_t now_ep = get_current_epoch(); - uwreqs_t reqs(get_self()); - auto idx = reqs.get_index<"byexpire"_n>(); - - // Collect-then-act, bounded to `max_rows` copies: a modify/erase - // invalidates the index cursor (same two-pass shape as chklocks), and the - // bounded copy keeps the scan cost budgeted. `lower_bound(1)` skips the - // `expires_at_epoch == 0` population — rows with no deadline (CONFIRMED, - // owned by the wall-clock lock window) are never swept. - std::vector due; - for (auto it = idx.lower_bound(uint64_t{1}); - it != idx.end() && it->expires_at_epoch <= now_ep && due.size() < max_rows; - ++it) { - due.push_back(*it); - } - if (due.empty()) return; - - for (const auto& req : due) { - auto pk = id_key{req.id}; - // An OPEN challenge pins the row regardless of status or retention: this - // row carries the evidence the challenge adjudicates against - // (`attestation_inbound_data`, `source_tx_id`, `commits_by`), and the - // terminal retention window is far shorter than the challenge window. - // `freelocks` clears the marker on reject/lapse and `sweeplocks` on an - // upheld verdict, so the row rejoins the sweep once the challenge is done. - if (req.challenge_id != 0) { - sysio::print("pruneuwreqs: uwreq ", req.id, " held by challenge ", - req.challenge_id, ", skipping\n"); - continue; - } - switch (req.status) { - case UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING: { - // The race never resolved inside the pending timeout — abandon - // it: refund the source side (SWAP_REVERT to the source outpost, - // or the full from-WIRE escrow via refundwire — expiry is not a - // caller-controlled revert cause), flip to EXPIRED, and start the - // terminal retention window. PENDING rows hold no locks (locks - // are first written in the same modify that flips CONFIRMED), so - // there is nothing to release. - const bool src_needed = !leg_is_depot(req.src_chain_code); - reject_and_refund(get_self(), reqs, pk, req, src_needed, - "underwrite request expired: no underwriter resolved the race " - "within the pending timeout", - "uwreq expired: pending deadline elapsed before the race resolved", - UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_EXPIRED); - break; - } - case UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_REJECTED: - case UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_COMPLETED: - case UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_EXPIRED: - // Terminal row past its retention window — remove it. - reqs.erase(pk); - break; - default: - // CONFIRMED (or a future status) with a live deadline should be - // unreachable — winner selection zeroes the deadline. Defensive - // skip + log; never throw inside advance. - sysio::print("pruneuwreqs: uwreq ", req.id, " in status ", - magic_enum::enum_integer(req.status), - " unexpectedly carries an elapsed deadline, skipping\n"); - break; - } - } -} - -} // namespace sysio diff --git a/contracts/sysio.uwrit/sysio.uwrit.abi b/contracts/sysio.uwrit/sysio.uwrit.abi deleted file mode 100644 index 1c17ba33a7..0000000000 --- a/contracts/sysio.uwrit/sysio.uwrit.abi +++ /dev/null @@ -1,940 +0,0 @@ -{ - "____comment": "This file was generated with sysio-abigen. DO NOT EDIT ", - "version": "sysio::abi/1.2", - "types": [], - "structs": [ - { - "name": "chklocks", - "base": "", - "fields": [ - { - "name": "max_rows", - "type": "uint32" - } - ] - }, - { - "name": "commit_entry", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - }, - { - "name": "source_received_at_ms", - "type": "uint64" - }, - { - "name": "source_outpost_id", - "type": "uint64" - }, - { - "name": "source_uic_bytes", - "type": "bytes" - }, - { - "name": "dest_received_at_ms", - "type": "uint64" - }, - { - "name": "dest_outpost_id", - "type": "uint64" - }, - { - "name": "dest_uic_bytes", - "type": "bytes" - }, - { - "name": "status", - "type": "UnderwriteStatus" - }, - { - "name": "reason", - "type": "string" - } - ] - }, - { - "name": "createuwreq", - "base": "", - "fields": [ - { - "name": "attestation_id", - "type": "uint64" - }, - { - "name": "type", - "type": "AttestationType" - }, - { - "name": "chain_code", - "type": "uint64" - }, - { - "name": "data", - "type": "bytes" - } - ] - }, - { - "name": "drainfwq", - "base": "", - "fields": [] - }, - { - "name": "freelocks", - "base": "", - "fields": [ - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "fromwire_q", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "user", - "type": "name" - }, - { - "name": "wire_amount", - "type": "uint64" - }, - { - "name": "dst_chain_code", - "type": "slug_name" - }, - { - "name": "dst_token_code", - "type": "slug_name" - }, - { - "name": "dst_reserve_code", - "type": "slug_name" - }, - { - "name": "target_amount", - "type": "uint64" - }, - { - "name": "variance_tolerance_bps", - "type": "uint32" - }, - { - "name": "recipient_kind", - "type": "ChainKind" - }, - { - "name": "recipient_addr", - "type": "bytes" - }, - { - "name": "created_at_epoch", - "type": "uint32" - } - ] - }, - { - "name": "fw_key", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - } - ] - }, - { - "name": "holdlocks", - "base": "", - "fields": [ - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chal_id", - "type": "uint64" - } - ] - }, - { - "name": "id_key", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - } - ] - }, - { - "name": "lock_entry", - "base": "", - "fields": [ - { - "name": "lock_id", - "type": "uint64" - }, - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - }, - { - "name": "created_at_ms", - "type": "uint64" - }, - { - "name": "expires_at_ms", - "type": "uint64" - }, - { - "name": "challenge_id", - "type": "uint64" - } - ] - }, - { - "name": "lock_key", - "base": "", - "fields": [ - { - "name": "lock_id", - "type": "uint64" - } - ] - }, - { - "name": "lock_sum", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - }, - { - "name": "amount", - "type": "uint64" - } - ] - }, - { - "name": "lock_sum_key", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - } - ] - }, - { - "name": "pruneuwreqs", - "base": "", - "fields": [ - { - "name": "max_rows", - "type": "uint32" - } - ] - }, - { - "name": "rcrdcommit", - "base": "", - "fields": [ - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chain_code", - "type": "uint64" - }, - { - "name": "from_chain_code", - "type": "slug_name" - }, - { - "name": "from_token_code", - "type": "slug_name" - }, - { - "name": "reserve_code", - "type": "slug_name" - }, - { - "name": "uic_bytes", - "type": "bytes" - } - ] - }, - { - "name": "setconfig", - "base": "", - "fields": [ - { - "name": "fee_bps", - "type": "uint32" - }, - { - "name": "collateral_lock_duration_ms", - "type": "uint64" - }, - { - "name": "min_fromwire_amount", - "type": "uint64" - }, - { - "name": "fromwire_revert_fee_bps", - "type": "uint32" - }, - { - "name": "uwreq_pending_timeout_epochs", - "type": "uint32" - }, - { - "name": "uwreq_retention_epochs", - "type": "uint32" - } - ] - }, - { - "name": "sumlocks", - "base": "", - "fields": [ - { - "name": "underwriter", - "type": "name" - }, - { - "name": "chain_code", - "type": "slug_name" - }, - { - "name": "token_code", - "type": "slug_name" - } - ] - }, - { - "name": "swapfromwire", - "base": "", - "fields": [ - { - "name": "user", - "type": "name" - }, - { - "name": "wire_amount", - "type": "uint64" - }, - { - "name": "dst_chain_code", - "type": "slug_name" - }, - { - "name": "dst_token_code", - "type": "slug_name" - }, - { - "name": "dst_reserve_code", - "type": "slug_name" - }, - { - "name": "target_amount", - "type": "uint64" - }, - { - "name": "target_tolerance_bps", - "type": "uint32" - }, - { - "name": "recipient_kind", - "type": "ChainKind" - }, - { - "name": "recipient_addr", - "type": "bytes" - } - ] - }, - { - "name": "sweeplocks", - "base": "", - "fields": [ - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, - { - "name": "uw_config", - "base": "", - "fields": [ - { - "name": "fee_bps", - "type": "uint32" - }, - { - "name": "collateral_lock_duration_ms", - "type": "uint64" - }, - { - "name": "min_fromwire_amount", - "type": "uint64" - }, - { - "name": "fromwire_revert_fee_bps", - "type": "uint32" - }, - { - "name": "uwreq_pending_timeout_epochs", - "type": "uint32" - }, - { - "name": "uwreq_retention_epochs", - "type": "uint32" - } - ] - }, - { - "name": "uw_counters", - "base": "", - "fields": [ - { - "name": "next_lock_id", - "type": "uint64" - }, - { - "name": "next_fromwire_seq", - "type": "uint64" - } - ] - }, - { - "name": "uw_request_t", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "type", - "type": "AttestationType" - }, - { - "name": "status", - "type": "UnderwriteRequestStatus" - }, - { - "name": "src_chain_code", - "type": "slug_name" - }, - { - "name": "src_token_code", - "type": "slug_name" - }, - { - "name": "src_reserve_code", - "type": "slug_name" - }, - { - "name": "src_amount", - "type": "uint64" - }, - { - "name": "dst_chain_code", - "type": "slug_name" - }, - { - "name": "dst_token_code", - "type": "slug_name" - }, - { - "name": "dst_reserve_code", - "type": "slug_name" - }, - { - "name": "dst_amount", - "type": "uint64" - }, - { - "name": "target_amount", - "type": "uint64" - }, - { - "name": "variance_tolerance_bps", - "type": "uint32" - }, - { - "name": "source_tx_id", - "type": "bytes" - }, - { - "name": "depositor", - "type": "bytes" - }, - { - "name": "commits_by", - "type": "commit_entry[]" - }, - { - "name": "winner", - "type": "name" - }, - { - "name": "committed_at_ms", - "type": "uint64" - }, - { - "name": "settled_at_ms", - "type": "uint64" - }, - { - "name": "expires_at_epoch", - "type": "uint32" - }, - { - "name": "attestation_inbound_data", - "type": "bytes" - }, - { - "name": "attestation_outbound_data", - "type": "bytes" - }, - { - "name": "challenge_id", - "type": "uint64" - } - ] - } - ], - "actions": [ - { - "name": "chklocks", - "type": "chklocks", - "ricardian_contract": "" - }, - { - "name": "createuwreq", - "type": "createuwreq", - "ricardian_contract": "" - }, - { - "name": "drainfwq", - "type": "drainfwq", - "ricardian_contract": "" - }, - { - "name": "freelocks", - "type": "freelocks", - "ricardian_contract": "" - }, - { - "name": "holdlocks", - "type": "holdlocks", - "ricardian_contract": "" - }, - { - "name": "pruneuwreqs", - "type": "pruneuwreqs", - "ricardian_contract": "" - }, - { - "name": "rcrdcommit", - "type": "rcrdcommit", - "ricardian_contract": "" - }, - { - "name": "setconfig", - "type": "setconfig", - "ricardian_contract": "" - }, - { - "name": "sumlocks", - "type": "sumlocks", - "ricardian_contract": "" - }, - { - "name": "swapfromwire", - "type": "swapfromwire", - "ricardian_contract": "" - }, - { - "name": "sweeplocks", - "type": "sweeplocks", - "ricardian_contract": "" - } - ], - "tables": [ - { - "name": "fwqueue", - "type": "fromwire_q", - "index_type": "i64", - "key_names": ["id"], - "key_types": ["uint64"], - "table_id": 19551, - "secondary_indexes": [ - { - "name": "byepoch", - "key_type": "uint64", - "table_id": 10478 - } - ] - }, - { - "name": "locks", - "type": "lock_entry", - "index_type": "i64", - "key_names": ["lock_id"], - "key_types": ["uint64"], - "table_id": 31311, - "secondary_indexes": [ - { - "name": "byuw", - "key_type": "uint64", - "table_id": 10723 - }, - { - "name": "byuwreq", - "key_type": "uint64", - "table_id": 30520 - }, - { - "name": "byexpire", - "key_type": "uint64", - "table_id": 53377 - } - ] - }, - { - "name": "locksums", - "type": "lock_sum", - "index_type": "i64", - "key_names": ["underwriter","chain_code","token_code"], - "key_types": ["name","slug_name","slug_name"], - "table_id": 57617 - }, - { - "name": "uwconfig", - "type": "uw_config", - "index_type": "i64", - "key_names": ["name"], - "key_types": ["name"], - "table_id": 7727 - }, - { - "name": "uwcounters", - "type": "uw_counters", - "index_type": "i64", - "key_names": ["name"], - "key_types": ["name"], - "table_id": 5169 - }, - { - "name": "uwreqs", - "type": "uw_request_t", - "index_type": "i64", - "key_names": ["id"], - "key_types": ["uint64"], - "table_id": 20885, - "secondary_indexes": [ - { - "name": "bystatus", - "key_type": "uint64", - "table_id": 60695 - }, - { - "name": "bywinner", - "key_type": "uint64", - "table_id": 29561 - }, - { - "name": "byexpire", - "key_type": "uint64", - "table_id": 36295 - } - ] - } - ], - "ricardian_clauses": [], - "variants": [], - "action_results": [ - { - "name": "sumlocks", - "result_type": "uint64" - } - ], - "enums": [ - { - "name": "AttestationType", - "type": "int32", - "values": [ - { - "name": "ATTESTATION_TYPE_UNSPECIFIED", - "value": 0 - }, - { - "name": "ATTESTATION_TYPE_OPERATOR_ACTION", - "value": 2001 - }, - { - "name": "ATTESTATION_TYPE_STAKE", - "value": 3001 - }, - { - "name": "ATTESTATION_TYPE_UNSTAKE", - "value": 3002 - }, - { - "name": "ATTESTATION_TYPE_PRETOKEN_PURCHASE", - "value": 3004 - }, - { - "name": "ATTESTATION_TYPE_PRETOKEN_YIELD", - "value": 3006 - }, - { - "name": "ATTESTATION_TYPE_RESERVE_BALANCE_SHEET", - "value": 43520 - }, - { - "name": "ATTESTATION_TYPE_STAKE_UPDATE", - "value": 60928 - }, - { - "name": "ATTESTATION_TYPE_WIRE_TOKEN_PURCHASE", - "value": 60930 - }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_RESPONSE", - "value": 60932 - }, - { - "name": "ATTESTATION_TYPE_SWAP_REQUEST", - "value": 60934 - }, - { - "name": "ATTESTATION_TYPE_SWAP_REMIT", - "value": 60944 - }, - { - "name": "ATTESTATION_TYPE_CHALLENGE_REQUEST", - "value": 60945 - }, - { - "name": "ATTESTATION_TYPE_OPERATORS", - "value": 60947 - }, - { - "name": "ATTESTATION_TYPE_BATCH_OPERATOR_GROUPS", - "value": 60943 - }, - { - "name": "ATTESTATION_TYPE_NODE_OWNER_REG", - "value": 60949 - }, - { - "name": "ATTESTATION_TYPE_STAKING_REWARD", - "value": 60950 - }, - { - "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", - "value": 60958 - }, - { - "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 - }, - { - "name": "ATTESTATION_TYPE_LIQ_YIELD", - "value": 60964 - }, - { - "name": "ATTESTATION_TYPE_DESYNDICATE_LIQ", - "value": 60965 - } - ] - }, - { - "name": "ChainKind", - "type": "int32", - "values": [ - { - "name": "CHAIN_KIND_UNKNOWN", - "value": 0 - }, - { - "name": "CHAIN_KIND_WIRE", - "value": 1 - }, - { - "name": "CHAIN_KIND_EVM", - "value": 2 - }, - { - "name": "CHAIN_KIND_SVM", - "value": 3 - } - ] - }, - { - "name": "UnderwriteRequestStatus", - "type": "int32", - "values": [ - { - "name": "UNDERWRITE_REQUEST_STATUS_PENDING", - "value": 0 - }, - { - "name": "UNDERWRITE_REQUEST_STATUS_CONFIRMED", - "value": 1 - }, - { - "name": "UNDERWRITE_REQUEST_STATUS_REJECTED", - "value": 2 - }, - { - "name": "UNDERWRITE_REQUEST_STATUS_COMPLETED", - "value": 3 - }, - { - "name": "UNDERWRITE_REQUEST_STATUS_EXPIRED", - "value": 4 - } - ] - }, - { - "name": "UnderwriteStatus", - "type": "int32", - "values": [ - { - "name": "UNDERWRITE_STATUS_INTENT_CREATED", - "value": 0 - }, - { - "name": "UNDERWRITE_STATUS_INTENT_SUBMITTED", - "value": 1 - }, - { - "name": "UNDERWRITE_STATUS_INTENT_CONFIRMED", - "value": 2 - }, - { - "name": "UNDERWRITE_STATUS_READY", - "value": 3 - }, - { - "name": "UNDERWRITE_STATUS_RELEASED", - "value": 5 - }, - { - "name": "UNDERWRITE_STATUS_SLASHED", - "value": 10 - }, - { - "name": "UNDERWRITE_STATUS_DISQUALIFIED", - "value": 11 - } - ] - } - ] -} \ No newline at end of file diff --git a/contracts/sysio.uwrit/sysio.uwrit.wasm b/contracts/sysio.uwrit/sysio.uwrit.wasm deleted file mode 100755 index fae6467f3dcbd86be940b7bf6eb6ea4e0b176a15..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 161033 zcmeFa51bvxRp;A(?!TEk_fE@}gYbBv??q_GSv!#-k-f&j?%6oD3G6C7uh-;dpJg0@ zJ)_9_XX9jLX&ehPEaoA=fC3H-V6c){ARuA&Lh_h^Lj>^>1)QMedBXq-PY^@2yH1`6 zA%5TAsp`IcXYP!~PBb|CS^c@)U0q#u&Z%=wojP@@D!6_3Tf!g+!tbwKwa|0{a3H~duf!k+X~Q9iB#|0#su{L}qg8de7H zDbL*oR0T=wx8?Nf0L)uunemb$y)uA)0v7g^ma70ew4^DSlY zmrwNCa;R`#H2BpYp;mLZl%wqWI8?F~9&8!WclxhaBsWw5f5-+B2B^I>xa^LC^kyHK zTYS^|NRH;i}>mky|&AxAI?kU(Z zR3sab?OQ<*{YdTQ@z(A4j_iKx?z`^3?e^Wfx9-{##7e5({@r(TALzdJmaT8uy>(Ae z(La2R`)crYwON??%K8Ww%hO7b7xS^bKkM!t?qqo>fYP*Y0cj3*mc+TJ-dU@ z%CU!g?s6YB+^gHRx!RLE?%B0#>-Igj?b>?B*1PWAx+|#bn88owt9}t2YIa6+wwWzw5TCt#1v6EoJxC z?Qg#AuI=0I+P>A*-njF2N)2Rx9;Av>+ZKY=to@I?mf5fxo3CKR5@)W zz4^ZWd>8n{ZFhekb$&N+zBw4NTK$XdTkqTBzF*^tEaxyP&P)uYuZm>aMMO?%B2dwtH{ic8|wQ&GEf7$EdQ_jON>2 z0~nG(f6or;*%~CukONP?x7@z#tv!-mdOq9b0$3<*wbZ+4kGYC|;lp!w&TxEj)Se07rnamL76>dd=pZ9dg!?4N|u5x_$T7 z;KCkyp1*SRc5OA@U8@Xh%3Kd$y?JYJQT&DYY#cPhpxF$=m%Z$3zNT5JwA<^#cKd6i z%Egs-+#ajM7d69nSgl9xOX4Wb|Fm`U&)Q$t{`z($u2e2+r_G_EPP6+BQMBSq?y}Z34x;Sb#+Z&o2HvEgrF5l4DkVKWZ z^NJ{nUb%i_bVc)8?PeTNOFL?gZHVIy z(PeEeV`EL-Z~T{Gn>U^2rYLMS$G-WS<0#q?ZD@|g05R4MFN&^ixB1t;+Wp}ggDQV0 z(Eb!x(qlf;PZ;yE=`$#Q%)hZ1)I!k-asYYrTcT!r{dykSJo06@X_G55jG}M++G{pI zTC$0Fe5u=Z81gF8|KJg*Q_<@$<`dB0syd5 z^o>6K)^0qCeuWdE0GwZoPfSZQl(8#!>b59XqzYHTe0;&@Ew*h0RyKGyd85 z=i=G;=i^_9|7-lI_|M|`_|N0Nh;R9Kw|>_f|Mxfj`~PFh?ce?Scg63HzvDge6Y-Jw zSK_1buf@L}KN){G{=N7k@$vZg<5TgcgrC$k+pyTPXDtUishWKSBrUIlO; zPTB`)$XzFF@;!sxn~I+B0U4O)QNF$4}whu!Gf?x!3OoL{Pl9E@r z%VqAeu{jxr`zC@;CAtnk#gg9_mEYnMPN}4K9@{(F#jc751u%7E~4asvc6QR5f#;14e0O z##KfAs%i!*yE;@=sHzSi1N(T0C(@a2H4SyC?Mve{+P62Yq#?gbeV#v~4|c`|Zz&#Bmi4o7Z*)4w?&D9%JteP6U6a#pD{J}?n{XZ8*; zfDyZrMoJMy(|Ed5GcrUpf-B+{UQ{->!X}^BCY}#Eb(O>a2LFd>@hGiNT(`G7Jj3_( z86K{^Wo#mRFl|hPGzol1Swq~DaP7>2?oe8xNTsEfH&Ax7(-@(N;KeukC#cA~`;~O_ zk^4JCy1Bp8;FJ2qTyQgd5vIcrrPTwSDzK(C9_!RpYw~{o-Y!2NLzzuS?|NE?Wa-S~ z-H~)8jUVr}wsY6oHg!aV%84n!hQOaoyzhSA9MD7EKlJEHD%J41Q%gszgdyNiyH_3g zYohp5C?jM;G6C2@7Vo^?a1JgHkZ&>vQ=@!?WOhUMG7WhpK?$;GP+~kNvEmYG9He3I zWC#Aw{sY1;Im{~Dpa#ejAw7k*~X#@lvSX24} zmgXJB0%`Nd=p*)%9^BB0%Lt`Nb|C!tkP4piK)eSNthz>>KI-~D~Lq0ExGE7T1gqz?B&2Los%Zl)CwtaijC#Vs`SsZy2k5||nK z5cMXIFpM(@+1S>pLPnG(nC%`&X|U{vBu0fnU+G3Q9<=Mk@pU;dSRUuTV^jKrKNO+NUxJ(JG#4O9l0}02yIgdg!GKYB)X#CZ?wk z81z6~-0!=;6PSEPkE48ZKCWcO$5=*%pvH~|kRuWo@Bk?}16Mo)t4&<*_W|0er5`APaXoMRsXqIbOT2W84Mq)EY!V+mlqmiL{oxB z5+B~nP-Q2D0~wt^?<&c)P@(z7Fsy)8wml7}(nutv?8y$mxLPo4DWcIC&hD0t490(j z&9D9xz$wq5d8aTyV&%#}<#cRqw zgefLgP#M;$u1&wk_p6RfW|yEDXe-y6-3p^S3WHh@C&?~09+b^xM9JGkIVjvU!X(Ih zzA?hkB98AtM2*VDI3!-o3C258tG=}iit1Q5o=QV8Jbdgxi6xn_hSz+!`kqKo-LMwaQ_MI7WLLhUGUd)y`t`KUOA)~~}Qcb|((gybR)sXGo~0G38@ zNZoTt-8iIP;*k1M9OC(5Xb>C3>Cn`2oKSb1;2QkbrGXp`++bsoI4;u&CLZP`=p!^O zX0{+eNP93SSxjWP6;IYe!C2eEoKq^x+7?y~oroS&1MsjZFZinl@?XfQErN*g z(J3ehsrhWU6Z0P;WNTm73Qa}QIm^J3ijPf5oQQNQH?+*~La#$LhHGWM*SN%+h|#2} zKj|--_%0c}Z%x|hz}RHOcoWq_?n5#AyL;5l1rybce}@JbAfwfOZCx2FhBgu~l!Uak zxWJm0Qu<`gbw{QsAcO0E?5Rp^)hWZBTD|;N5Ph^YrHK^~#j}o{E}Ib(lH3bL=zEZO zv;zvARA?GQFkfPQXSV|NlG}?1c`G#-dau?}Hm(TVYe# zeSqb@xHJf^v@aU&3ydY2j`6m<9ZqjVSu(kvzF*eK=VhJX9ZjdYo+8P&EJ=pwX-#``#99a+?NM#xr<` z@oTr5-{9QK5HNNu1xvS6Xr6p0dflpQelQNJ`_k}=U{pS=D}pv8=F*bcryC5viK}q= zO(eKBuL#14#zO~m6CLm=ah_7iQ>uAN?LfE2Jta)*&;0Yx2M->gDDKZb|Ml+nOV8K2 zsrka{`KOJ%u%SFYUz2~jE-&nB^OTG8l(9VJ5~bwG(X0cIOW>3L zrxDgAB(w2Uw?-q^%F~&(eG0yEzyW5T2zQf&K#qA;8M%M&CU|0L*L3gWf z-tKPo%>(XM-%Q)>b7Eef2x=FCv_W&?ZS!aFU1c|jEm=Bn=jTMoLFb6aM0+3qLp4WW{jmqALM?W?#ZY0 zaqjt?J6Pnvfh&WVUd@lVTfzPlx|OT{n&8LXtqAaA?p6f&QFki>9OO28j-homxtY5Y z^or{yQOj_edrI;rC7zUrp6dy&%FFLGeME{~Wh&Mujz`Z$-9}m;kIpiPm{MNcchgZa z+8xUFf1AW#YT{MyVunDSY7tS*62Eoe&}^ET0Z%^HkTzPpo1OW6eVfnGE_#>hGtb+>fOek zw6+VEeIu(&{S9l_lm$1U^(mVHxqxSk^wV-<7;af9QhYi)?J!y59a1^Qx5~OuAR87dK*w%OF=Hi}qwsJ{ic9uqMiLp(Na=Dvj@D`SeI^-#p^KVWPxUB*v3L z14>LW!hITY)=?*PN6pVOt4!dI%~85u7|O^Xfq~GsW{t>av87u%Vt)8oT_HM{$*NnR zQ+1*~137AHYjpFbXr0U*5ZDx5LiHF60ufKfp6odX7#}OgCFB_43VYz9S`*U;lb^|y zbzLS$07ATIelK-q9|_HTq(b$P@@qwDP0eX_ChjtlhOdsc7?jwhk2?kxoY<80J-E>Y z5jCt$>AM4Dj#hu~hmE~G8Zx>rVJxgc5kS%xi|N4%CJ=@qgol3w_}9_H8h)|xoByyi zmVa*)u;M&^+>T0|<9=*0(nNmR=$jZpgHLAC7T%)RStx+{e1sTjP4YQ}M6#0!d zcjMgMFt)<)Av|^8yzuL;c_q}RJIt0wZ80(C-6xQ-pi=fBOjT7Bj9H>l>vo$+Cyjqy ze(;BjvrPDTY~XL|V+MU`K67j(1_q89tP;#_97nPy!5_AOjAq%M{~GQ&-#CaFGvqV5 zjVs+m@*|80KOV-4^^3}57CGibU>Ju+6QwE`%(S2rH>R}_x2hAjQCi(749Atl&40&; zQ+x&&A{xY6nAG(MZDxBF7VMRxnqBqXaH?DV*F6a?isUu@Hsb3F!sDj)a$^o`sq^1d z=dp%)$8H}E@dmk&5)GQ5u>K*>Ff2Dr!(JT}q*oK+iW@+GO-~LayoSBdTxHpS2x$p> z(z0|}kbY4ksw_APX(6B_bmKtR7|pBuy_l*kVic46F5@YbzVn`^vGXH>pIWood@SZ$ zC@>8d)I|dHXJRmG63BJuXFrsi4&V}PrUT60^}EDX(9dRns+%;Lc;MudAN(9K4>B3( z7LCEOcA3E@wo66-`uSVaIJ^W#x z^HY|ywjd|Hv#_`_-b~j#yeUetDxZ6BdU}>f7wox-=~F*-;({ybA-H|!skIsmSw40D zWWTSJeMc|*okjL7F5j(O#$}M5#1za;LVHTKj%DK#9K6}sj$9Zfzm^MfcpaDc%J?cS zg!*jcg6DXgQe;r7K-uQg({t}Xc|-C&Bus}gEq*HMsgGHj7G_nx`DfnuM>GGf!+6`N zj_eRdGaj1wnt(0|Lth%fML5VjGVzBpUDq%P9ypS+U%ke-mv1v#5@T4lG|nWm#jMes z*(*~cQTb+V*n$*voeu=-Px;EdxsWNOPW@VpbwvOf&!tb5= z-Cr_}0V@rl&SFe@891X`82>bLQGim-&M<*e%}&=Qle;9yKYlz&;K8JkJ+3<&RY2v3 zo{?iK`!B`Y1HbRy^6h-hJJ_n(vD&2SlXGXfcYC_p8$~fX*c6?VajStHS3Mv1&vX8I z)L#$#>#V;X^4Ej@I^(ZrtFFA$Fb&EMhXt34Oz7;H9&l82SbT-265X~bdQ1=49J5@r zlQqq8K?q>2?9wcg` zVBXbud?DtY`oxQxwcCbSKe~#TR-Wh%+bR%$10< zO@Mpi08%ZvLyynY@MLp6SrckdK+Iq%1Bo!s@>Ex{1R(q24UMe_NlV+g^1n9KX z03&6X&on|Stxcg++GvaRO=I1b6b3Tfk_s=WBYH3j($<2ZjE@68hT&_&8N7q8H8L6wWcPaNcVQ zCoCzP0%a+jwo^DXRiDBEQMI{mj8(#HMwrc%F!SQ=Ff;XZ6cjya(-sE2J&B*W5sFSp zGL^q-N^)92V;%FGD@khaQ<8^kBZ5zs$7-nU`vB!ER(ua2tcN~;W@{sgiCry#b^ywm zK7fW60CcD}QdvcS?gXIIeE?xP^ik+wO;OLQMxiZgtUiDktMvgiQ!8}LoN4OW=sfo# zw*r%fl)2Hjt>w*?Wjc|UcE_<9Tr|8jrEUEN&F&RvZoV$=H*RVT8|fu2j`U85)n}Yr zy)U%7`n$J7^pmo>)NRn=Hx3SF0=!+F_ z@(`T7aL84d16V*b>Sv|G~tF%OTK zs{i!%PYOzD53k8qPdzy`ae5_kW}>LEMOv4EB%#x&-5jdh)vL_ZY!t}dSq<_K3#A^c zb=O=OAGP~Kwax`Db(YmxhkcI2+|O2FpV2hdLNt|_;xnQGycgW9;ZDwpAO3?Nxw{)B zJ8Xp%QC-)!7zr>hlWeqJ_0o765TtSW;27~z7DCL(u6KL5cRQ!hX&(pXFO_(m9C3ReE{XtfLAmx7uSmPG2~A6*V9LuS+e+j zTuokRbl{>Icv+zF&Zo{2no8#xuG+)!y@m%fkeAjB}97iG#G_2#bqt#?j&%iLp-sQm|9I@oNJUGqz7AGT1QcYgK zA3d?C(lD1Q>srYKms6<(R{YH3W{N)O|1F z(8Q{bko_24`}5R_^zvu_F`EZz4!u<+LhZ7fo+ikiZ1lc=KvCk*K;RU3-7J zHeGYS?rs@-V{*7OYm;x|?ksb54Rs?VG3oXGKMIl? zY4~CR3l^u=wZvyb@3PUjS%#Uw)#}%5#O5Lxi<|S=7x`zD^V&h>%)mqDiC(gtd0<;( zu*qR#5vqOq!&+u#-R{Pj|`%$~EBr!@#yYa}zzXO8h8{(8A+zz?hXS9|@{9^;= zGi20Y>9oJjyKD4xC3&0K)CZaCu9o$-L{icDvVLeIeDzP;rw?K&-Kv~zD%plr_K3%VB9ip-N~4)wbtMPM*(cF;|r zB6JXgxjq*-g$_vWhxw$F`lc&?;uncsahcFQ4@XI}gZ`%!%|jx}H#ENxg*6t@k24d6 zQ`Y^pC<^9K6`PlJJM@0&cnbvRXow?MZ#}w>X(Sb1o76l}!=kL}ii+zp6k-}((TwaF zp!hf@j~OB}`CmgvptzX|r9QqSEnm~9FoQH!QGtqL9UP^YDko;{+tFdj8nNV3W7K+C z5R$LHC!5y7uu7hrcr~AwQFbqHMBX-emoe=l=P=8KehsoYxfUw}~|_a%FPKsX1Q zEYsX7LG%vJDZ>;b6+lH01UYOa2V0MPG-y5?mBZf4w6~a=Mz^AX2NaNxLZ=4I5OXTq zdK}9|)HahsqAo#b-ww$Yx&|l>_|p*(E?3Mv%iO9I#(&m8XzL4_#jY_<`KtNiog1Ba z`5a!ha-f;Il>!7!DNA$|-bV{5;?4oIHdRkBGMOBe;XwNmlSkZKH_%j~1&^(@W*_OY z0RxCgM@5WWv;=^se;xcUPK0s(sW|aV%(3o|2=Q7%6KHXBNfV%EY8XMvu9$P`f+G`1?1Oo&(uT_@12Qj_ zdq5i~_fpvxs)HjFmpo)0$`ZgxHx%ll+6|6Oe1prsR0Yi7_dclUgO~dcmihvf+xw!f zFRsYHSn3n4(-x0d?EAxR{0Yq=3 z0Ha7TP2*`)gg~X-`w%EJkPm@E2`L|58C`BIW#8P(eq)gx?WBBHnZm-^7s-%}tQE{}kgS~bH1_F_#VW^cYKOG>Y_id(%EdQ>ZGEK*ecS2Sszg!Ja1 zRon~^Y@ZU8EuFvi^B{%Vv}njF$!mha2=t?&leB4vW8^( z4`tWL^k?YRa>X#$YQ-H4>Frb=D6Df|W+_*~gZ8PY@n|#Q_;d~}R$BxF%G1FQrLBp^ z-fnARPMalBvjUJXb>0+RNASMldI*mosRBK1aFXn|ZH)+;7M@MDW$c2d^*oGc?^_ki zCVLgIv^SN3rL`Rt`|Sp4^xEh;{S`9Y*uHQl-IA#hG`U+ zp9nbxQ?n+gNzjU!OcYIKV2QU`JnOywEA^`43m4LE6|ySB7Do?thLJuhiy@4yUep`y z_~O3MG6I%Vy`Huh%If!ahaptc9Cemi;E+*M3kD9Mb3k>Yhz~|x8WNnkO{fRZ?7hS? z3+CjZP7_3BW7xWgI32kOJ1;&m5$~e~*vyG1hfC{mBE8FKU==WJtVQgUhlW~EudHCJ zhnG{1C_~xXUXOJ$gmF}=Ko+8iHK>B@0;4iT2^syowmU?aoUQRrAfJCeu@_er9iFq4ABBTxoilH44X;wme zFlbwrE+L{w9=QhUtr7#SKyURJ&@zhw`>1K+oimt8w!%{TBzqt$m}J9X7Y{B_43_I5 zE0x;&OGSSzW?L&&F_~8CLO^LZDT=r(4&g#^7^vT@T)4~ltbS>{qtnVouczizJS|D_ zwzE((yGQD?nGHKN7ny#}i;v3s*%#kYe`2wr^r_RKHvVhb#!*V9n?V_x-u3-V(f!iT z+(HM^Vo^p{!CzYbBQ8}}O+UQ=Ql}DlDN|tNh10^6<_OCeC3Dc?zQME*Z@x5XfuySU z6e~y(ER@*cffC!8>bBDnw1y$A+TB__8ex2|{)t)QZHket9q*N=4mE*P{)A2KO0#j6n=szwix2G-r(NLXkF( zGk(ZL+Ipgqg{5ji>)@W~d=UmlaD4_Hz*mI)|JD5b_8fG6Uy+%_||Sg5+_8N8&vQ0oav9ru!YFqEj4I&)Lr zdBd2}ilww<^!6z&jVIJFiT2~NjAUS|7vNb=Ly+2o)OG*35rLL4$eEF^^j z)?bP`3m!3T#kdBpiMbGrJ;H@vIg{iAyE{LX{m;}>>@qJsd zl&(<~Nm|c-<{6xw4aQpx<53cB@i%Rryj4RJxkrw_@7;g>2fw-h$~e_?z6gXxPFa$? zqB~?Blc9*i2eVl|>u?{JjRynzjLR2*=4>;<44yKLsE35KZolaX7YcMw;F;UYH!A-nAG@UcJq>w{YNW6lT) zyfVJ8fKQ>Zp}ZQ&_uE!-f|!INIs1)24w8o`=JMbQkJ7Wj{@H*0IiZcT+!7)~re1a%I@a4|4-ky`0|SATmJ&Ui zV-9i<{Ck1W_r3>0&;x@4TDf0(9!Y^)EAmNkVY#8SqGkN?=$ zyn%MeZ+F_}hPGhQz0#&FF4xX3BYIqd=@4NbxCPavYvEao|81T}`JbqGP#z|m_VWpG zdG5iUrKPkrg|2f_-wjWav@srS%p#TAu$5Dd`X@FJBe_z-<7m@c=LgHvj<4003NpZec9L ztW_Ds^4GrWA$B8%8_Q$XBtI0o{x#1jZq4rnHvMcPo7R>cX-zvbT1PR5wA5USfl<*W z3Qg;^iUO3_Hp5f82059Rt%C9DAy1Q|-%Aw<0=g!Wss6pgRadd=@j%gMHq(z39F zg;5g)0Dh=>hwWt`aBTAhjbs$~(~Z%e-O8Af<>b{4abwB9>e7W38?T~B^S~5ye*uTIxn;J~{EeG{Tm%xH#k%vUKE|k$Ge(cn! zmGA}G`kLuxN+-4r zh=>RA#DeSPZl@SR5p+?k!RrlM6>VV!+H6rl0Tu0yO8$;Sj2j7-$l1`Ik|dEsbYk>M zk&-NHM+oa2pN>ilpN$d_jg`3tRc!S+Nuo;CsG@z{B+dm@XcNUgSQ{-9=a5qcz|7xT zuC^e-L!oV-g9XlMEw;5rxi0AkHt6B(79qm$8isJY-+1;Iw0jP4F)nf_yNv>jnwkkD z)tXy8T$xOMjm>T&hB#OSHy{uW2s52|%P`XXE?(7@>8%y27b zC%Qxjq~vadM>-x>(6m zZa*avsT0H$p{i^*#%u!_Lp_ow4cV3&(aIwHXxZPMVw#lNbY_kGYJtR+3QaW-H%njI z%zpjT)Ql7nfurqHDB8R=;VC68(lZK?mE^f*>Gb_D1S~ zHy^i=gpQdAd08_82H8)ZvJr-40@XYbDhfidafFXeo6@J(pf90nZ`(KV^1TnG(E)|L zkx>CVM$t5S(IH^({0zJf5!7uA4gq^Gc8oTi8{IbA!XSHJ1U0c-@3Rghb{$CN(zu~v zpr*pnh%l1{nz|%nSS^X2AYMavsYoPyrh~I*M&R~*m3FsspD?7iq>V*VN);QgjK}$Y zjy}TPa^p&PDo^3YrO5iVJlwbx=#Hp{GMl9#pPRfHgc^y zm$z<;rb#~NoAwx85#royTi+_ykV(-bd0Q8)woVkr$6~FzUJvX2!*L@%zeQfxi2N2Q z#s)ne@D0wscp7?b-b7r04RI)y!MVak0}bJMsG)y zXDR#^DbPA3uBCWJN>J1uO~{L0HiE@uIXH+AwoXj9ml~9}C%YFwnk< zHoO-PD9Dl|YkV{N3H?xu2hG3yU(F0@{^xGy=HIWWDN@>c^!>3Sre|?yjI39G-iq)i z;LzX_T#tQV#&)CAqMLWUKZ!`L=Eu@dwoarU+i=`Yr=j~+K3k(fp-kJ!9WkOIR7#vx z8rfovk&MAfI>O>OMKSQs^^Z6i=+9dW@*6iFNnWiC)7l9(x*7Fr-?)66$5gR9u8q$L zU2q>xT=S6o)@~*^9gm@pEdc=?)fN-7g9pa`q^?lDc7=8uwhjaunC}Gyxs{2M#O%Xi zN6=~5aP2J}oFS}<>EOcH%nFg#L^wmjwGSPTSA_>$1=zktJ;~ngXd2#pWG~K@(GEh! z`jZRBWC9jh8l4NXYbXyC4Vumcs+wvoK`7}zi(Epg>ELIEkQbn=x{U&?@RlwcIVOA4 z3wal?fk-;4dRwku5i*{5&BQf&b&jzV(#9 zXd=EP<>w8MCGF)JeGvP1b}#fEv7Lp9zcf#aHD6-Q1h$=qY+o7}Bp&J}1yK{VuOSN6 zIPT9iZ<@FmPAd^yE<_LFX^`r7C|w$#h$gO?Nn_DBoX}y8G#@FDgN7}N-?6tFrU~Tb zq`waHK{EBsSOwU*XmY|lOa^JFYQ)XiQAe#gQHwmDT5DC^MXKrXO2Eb%IIpy7g>>ZGz4kp2DLiNe)$}?6lr-0^j?OLN+9>5 z5>D`*yv$yy4Er?@tok|kF@9~DZsEtYenUmihuuWiIa4E|5I5odf*}oWfgj-5{9EoR z(*dy;`BGbw29SALXyUVZYz*F!DzpI)JK{*=;q=7^=p2DSYiiHHX6Vi|pHq4mh{A{E zH9|pv0bQ{`4VY>Rv>g(yL48_vy2-Cj8$QG? zqG`E$q2)-R@pia(jy)7_&{J|PNA@k*KlpoDDVEwUw`K3 z-}?!CF8Fz4XlygS7(a?Qqg%kot3W^nKRO>T720AqY%Uxm_`aiN_75LpBy3-14Hmk| z(v|;?F;S&)cZlchLy--YBita( zu|NQfM`lL`$;ZvJXKR{*<}G$elXI7v*FUtWSaR!RAajhWLpA4{kUA`^l0T9Y)po|U zFEw2Z5#{l;g@8fx-#ao>%#PfHj?ca4K^H(7P7#30-%CUmIPfd^I}K=U$SFr*(ER(R zTZ%Rx+vJ~Qg^l`Ru1v6=)KwF%m?(LqMuOrL1?TimO~L%DZu#seox6U~Y4c$s*UC=j;8%|7Hx$xdKRKK;ie zOKSd>#Glamm5ezo{tv5q8Q5tB?ofgr#r8bv7Tdh`1_EsR}4~xg>3mzj^*2y^eN%>aF@V@V9!Fv+OfA{Gi`6*j)V^fCR zrVG>*&W5*U`A$dHmLu|{0})H|!<2jo#}MRB!#B`N4{6BGY07de(2pv65 z7DDq^9102U0rDYYT(Tym^#T5CpU46K;3tCQ9WJlv;6_aT!*zgj ziD1^}O!-oxE&{i~#>*j79$9EJ!N57r0vuM3D7#K4CeQe0?GiAA1BD;F7Ql5CN`YLgArqU|r#M>+FhCX}L70)t143>1xq;0_>vI2c@`F6BcSE7+kh2FttU#4m{Oo~*`LIC^`Tc+5%lXMa z36h@;;R8N(5Y>~P%f+VzCt-?29L*{x0N{;D4GwGWA;}x{{pPL*Zaz8FgO&uSdvEeC<@a# z$6TNXFCQ*mGMewbq`Fh=h~Z{j$#ycz%Xo&dR8fYCf2w$OqW_f^xiii5g^-?w&iF*= zV{Yi2AV8G-lG?g*d2qbnN6c&7fC=gabU3UA8^-k5|o?J;`B|?tD+$J=QzXb%_n_mg{Jmx_Dy}=oiZSg{>_nhxf%W~ zP>IxAQF78kq4}uIiJBaC9e+8^nD$*^s%bj~@iBpDCW_`U-fa`-;pM?~e%c^EtV5l1 zoW-vrXQWF-^{}?unQL-3%esP6%E(0IP8Vi|m9H6uh3E;>OANgQc)uFF>EXe9!HeD( zcN6M>_Kx^Z_b#d>$!_zd0T0oFWzBEP$nlV4Abcl_-dy&Lz3f+!-88@JDBL_79e}bU zW8k`TX+WO5K5p!8@~PB} z9yHrxK-RN6CzB(vvK3QXR7|EB&7}*%i6B(#5;|9kYG9u*1QFklHo)O}xQ21KT5wnUA9EY}GGwY@q^YRs z8vZP7N%xlhP}Io33SUV{pTyj;O=lp;eBE^)B(sr!k$lK$iOYi%u_Z!& zIu7=&f#oJJ)cCK$#PT^ojChV>5Qd3!-oppw_p=CtD5>|hH+vOokaDQ@M%l=pe%7(4 zn6ihVAU|pyYK?Xz_c53I7yfpc++sBe_G#KiR?8`_JV(z|tqqJnfzL~{c$s_cGFQpi z?j`v(P35TW$trmLTo|;*d{Dz3v z#Q5|SZ=&d>mg{*E@?c3$DkB@Y3SKd&erN2L^8&cfsjB;=N3aWhYOiqcn|Fh6{q9sw1zOn@%o}Q2hE2)CCWzT5AQjA-vuDAJ zsAb2VV!%QpP@dBWl5>)uphL@*%6o~t^2v*oip#x(3OZIQWUf?5Dfv(@nIC z@uW*cuVBuSXc8o$*I(Oz5K5|fXOK@behC$bCZ#MyqQkvg*E0DFvQ872rbnWVO{p;5 zGpa&tf&y&O;1jqUs zwu#aM119Kb8!V-3B=yE&@GR4D$WuJYTn+k+g~@BV3LQ=Wxrke-CUU*Z^OPZ@UW^8% z!@|xmTJiWy9XC|{brjunJ2$yA6YYc#Xh?spLD{@HXD}%NZd3F$RMJu#hPnhMUdk0U ztbXjYS=&ezTS88@K!CGjsy2Adn`iNrDU`t{d>|t?wJvlgd=T*IPxvs9Rq%Yh3)n(m z#sCS1nA>n4sFcBjE+EC!R|il)9{`{`;iI1{zR>ePoOuz4!LX|v;%pP(o;ZNiBpi^( z12GN`uG6tlV?C8v!BWHwhH`_5%L|C8ARMBQQEafmEenIp`AC|@L5#19=`UkG(+F&Ipg>`*qi862 zS_%WjtRU*LIyzzMh%d?rJ{^PiIn~ig8&j%MsiQ$`X~X+tj^?PNm!XcJIirKJIx198 zgdK>uWeuJ1ae?}^K7}KF2PI4@rFW&^RF*(5DV&qBDI6mQ3g;jve1M}AGvm{o&Z`4etzk{Ptzn&bpTuk6Nt?zvChcjyPCumgw3*2VOIlY=1I!BOS=St1 zN!qBkq$E#7T3x$p5FHXkINbQn)wYedOAsB8w9t3eAUY_B@b;A;vaw(ZqPa-U^i_js zMi9;PK}79+diz);KmDpfG%bjx`yfI&_d#?tl4E)`dlC(C&YD|SdQr9wW8-slBromG zV+R;1G{O|OsTe4F&c5TOmIbdEGaStOHm`@yt>PD2Uj1F%)86uuf~CPxwDLb=a2Irc z8HShhm{kkI>#+G!R}G*o05snRkO?q`S8fT;u1GU0w|Wyt#FO8r_zy#G98OSwva z|G%3bk{ohjKlDHJ)X05NjodHF?6r557T#0rOy`xf*&}K@Rx3ic<~XU@JkU#4d&DXd zDysJR7geo8QBxDn+py3{oBSu@_sm}oQD$OITrT3QnFvrZ6ax zNHQ`!M|6WZQSH|`QLV6UL!qUVZ~oMj%!HEJXLxw}oT&sHs3bJzqLTdQ`j&A}5t*4g zVy!*RsmqdY*PxY&{mRXxhjl9{5$-$&d$m|vep&_ORhaO0KyYio?B!LPXf4TT;#uG8-J`~4p+td7x3pn~NNVGYi zX0o}%2bwpP<)Mvo&oCCAcSjN6nh04a za~^%Fvhid^_6bvIF#6nq*X+08($NPePS^K!St1#!L4_P5^8q&{M;R$obE?j8u3Kc9 z-JjrI;3$2XBZma0?q|3c{JKBL{aNk>2WJ=9T%jaYbHi!^*%B$_MvozWKW=JTZ;HF_4@ed<SGAd^KjvC*BbbRVy#JZUKKtp9yzkY#7d&(7sUBdKE%^{39@aoMRjA3o z-wEB&*5ZE$!zsSW;S}HGaEfno-YUZ>ymB~&m*5n>!fTp4-3ynTw}gxMZ6Gd4*ZYn= z+r#B|m&ZkeVoF+RJ4rtE=S3n88C&Q-#+y&`WI`dC(GC| zo`#Rk)g#EZQCBAoBF`L5IbUyPGygr;bsxdj_;9Z4=tIcQkhI#hGtu#rlD`UTxH&9> zt}ubD-!&f=HS=B7furdiUOpCyg5!^Jn*!<9+CBa3}L zht^avocUtcqy9SUujo~!M+dm7VeQ^OPM8$vRO7F z9^yKq4T&9$?0(x39%o%HZ6xI2r+9o?E~uSA3>S3FBf;#@QS)1|=C>GFY8w~p>lH?2 z+wuuABacXqL-~syPG$l5dueo*IFe}!H`j{`SZm-==5uWgWid?-hu&{WoR*ziuOsUN z&Qp+sIhKRDuE`K1mx^by$9`v`?h2rGPJ-4FU_T0oBCqHmu(h1_KGBK+kWq}4bvg=^ z{bJ6tF%nRy1D&g=|6etLb^wZeD?NZ{;XZ(lRivC(4WK&#MQ)THK(JmPKu0Ss8agf} zA&sxpcxtxSG12KhcCCpN+4exbU2RUlIG;UW&PG=UG>@1X=PPppsBRODf_HtP=YfN_ zP?OvYC(6pOP=RiC z4dr)J%bypD4g@#5X5=dH_Jx)t=_`f3 z`pdZz_VRe=*aMN5q=&sE&6p%<+m&3B3XCO5EOYee_DK?WN|Jn2*yLK?pyv;zrB(A7@iY_ZEDIPdmwmG|N*yxXYS|WcaqRb0bj)qnF}F?B z#PKznyUQtUdDl$lCA#K~0E`c%CuvBkNt#`O&Z{aXdDdZ*8TgEs=<=bnNtUW$11lPe zdBZ}okS%F$*w2EWgg(-Fb$}71LRkBzB<+i9D~?QRza>0tb}U=3vG-^*fNFq9iZ-L0 z?c00bkw4$~o1&hLJ6Nf7!IN~8#&Qfz#5Kk&fCxYcShfLLa%=~D$FUt84$QF~bz8X# z11#l^=wO*csyWPWu7uK8b*y zbPRo`g3!12Z`6)ZNxq76gj{=B(HslnbT>UjMVqoMXas}-M>jN7W0^#>-I+h_l~f@i z;Y7sVp+9BlebKb*mr?ZjX+4d`b!JD$eCLiN54-LYw}O=AVlq{=sMIJ_b!Y!n`zhp= z+=50h3UMrwJI!gKCV}DAx_$J4|N9HZUL4T6p7iI5J`zWmLcRxrcl#{6g``2FfCgf0 z_MX&=9qCckksdHdNtOpw$kYmJEippW?L{Q=Y+*tV zzpXQa4WE9+c77_$r7K%}2Ss})RY3pzq>52HUPqRlw94q?SQ2iuOfTRSR>wwcrcQe2 z>x??BT0^VSdzH`Ep#?dQg}o@s=j)8J<|#I{YPm~7njwFwnSk5jZmbuzB zv1E&te(AxaUrKsVyDwj?%DvipNs6VZEyU7j((k$$D3)~egTxXdmBex|h4S9M5KG^~ z_o*;rrJVl8ruBt=lML%6`X=aar-bDmEvQ)c`^TGX|=1rW!`D zBvkVY>}U|M;R{a;Y6!phPAXZ2wJnO$X%{K|z3FvU&{JwqT+^YI!zW2>3|IIw#tRuTtfeZfR z59}MQV9cr@DX6kPhjzkI9lvH;0$jqv(TO=UwqzRt@B>*Sej+tP(Rpp<0iG({f6m8p z{bsl^XLW$Va@-g{2YiS>6%=6D}O_@&R@Ck3^s2 zNQ4(V;w|byJC#Sgs%PPdmwF^|g3^z8`@imuc$fQni4kvUw6%f}uSQ#?5wAvD0H*Fo zWLrGcUohgWM>b$Dz{ft79qC5AGB427Hq0#dfwZLBt8BbOk-QHy3Be#<>4;Z{;o5%Q z3qWCc)387>8x_YXYco?j;*1T+`bNBqL8&Vm54#Q+S!*3Cz}MjsvO*H`dhY4oH&PxO&E!W5c#Ut+{t zpkY3=;j(EhA#skh#UtLu6f(8K0hWbnT|V{}1p1;yq{vNomAOM}|HbG~)fTb@~F`X!<1Qx1~qCi>Yb)1b$mQ z;w>oU$`;>2(Q}M=RY7SW;rn0nZ=zL~8A!a?5%2IyM!W?oZlF;X%Yg%o#bh$+=^OEu z%alfZNs8sUN4y0Zxof*O;_Zp$;t}s+3gx|hA(o{PZ&AtOZjVa(wU?}g#ii%n-k=e0 zx%3r}c$Z+*^E~1$Kz)HD-o*sT8FJ8wx2OejfAte&;D~o=f(#t-epOJ!Gk!7RN6JC*v+dT8`AAg*qQV(K~l><3v zG_JD|YB7upC|3&@t6C?Jd{hy-6nQMt1mvNH%xo+3cE%J>*xcA_C>He7@&y0=0M^hJ&96~HFQm{J2M9#n*{w&rVddyIi&T zKSH;0;2eA8Rh6R1{*7FSLcB_IwVdG-nW3G9nrO!Llf@9{^K3~Uku?$3*%X;Ot~x6S zZkSB|GrP>$XDlvopD|M3lRcS#)+jz}G_w=`$VoB(NZBxP_FwJhs>$p*ZldfT>~VZD z`@t_M1D+MJ9OdH*P_*zLN;)cdJbF9MXu~EEq?ZWQY!|1h;(rL-{prXK2eVCj-S&Qx z9U2}7)|=#|Wh+Dt(+Sxca4Hy+%oFt)Il<&t;RGyWTzz{dvAkwtrO`RRBbrq~yDSm1 zWA{EAci9zKPb@g^LBm;nfV2JuK*OOaR*ntfPv>5N#H%eJ8;l-Z>p zF4DOJNMbJa?F_;4$NjewOWT6-V_|1F*}QNmOnG`%tST90;|=*2O)Ij^tK^3vs{@$x4d`2@Y3e`0#f9;~0JB>PdF^5>xo;_PAZw>p&jUE;Jn zo<1R%fL1Azu0(59!#yGNiDb2j--BHFtR(WPR0VA5cgF8SZbfumPsKjuh#{Kx8zk*SgLhrE2Y zczLG$5|!x|29Sj%bUE1NEP&0+CyJLx%P&Q%`tnNy-V-)Y#=YIsPhjplS)!eu5`F6w`8N9sZuTS>~}>{LZlu=#S;_N4?&vfNGeUzL`Fj(u>33w15^}Do1>A z@L}DcS*#Wu1#*>zmzBvw58i+A!Z`EjsFhsc~`y|U~;D(KLiWsmv$A=@#YLTb+T ztGTSQ{gicttjGO5CcgG|%>M3kZjCv6@+bf5Vlo{3e2~1B8#WegC`CBCrB$=64b0~0 zEJ}7qJ)1@UTe7!1EXC}OJXKvcoAfECEgW&_&h8lU>9RXqy6lc&*&QSPmFy1ps%Lk& z!en>2H$A(!*>(Rm;mS z*7BFr<(KUE>*^&7+otAV)AcX-{sYC!x$?__TgoDCXaRP&j;!UuPS0dm&j_1S-(`&3*-`X16!xgha`v&rEOwipqkT^> zbLVy&)%Jo^;z;YOMx6i}qi6O5)94hyB$NAgvf->*uL!kdJ^5_d@kkzv$b=Q3A?8s= zS7kaJ)_O!kp1Eu8{YF%e!P=~Jh9pepD?=~gbH0-}WOTSZXmsnl1XTTc?E2dEh7V$d zKC{9kQXN4_KMa|eV%SOMA$P$Ak^EUmpf6~%JV85FLnM~=OJ`m=q=wVAMV{_$=2ICA94P+4H|8JSC?f>B0Vz)+?PU#FWa|i>apGI|~zH~ch zD_Y@GFznr)VeYFjDNfM~vd+``dORsE;Q54qKJKq9fpv`KuZR70)?Zl)Ywxw>Tjfmq zE1}KwmPT4c6e|c(kX)VWK}c=KWxNumVhd)U)B~J97s`g$U$=A?Trl0S_>l2z9$&q- zsQv&~v7iuQYgmz%22!qk>8wMKPH~(PnR;<9L_I#qMhk9*PE|{1`&Q_nPGC`O4SQT0 zL-RydEf$lUup|wy9~iO*m7tWP0PHs-oM)h+rL(+a@wP(fa~uX;@z4z38WzvZO|8Uj z4O=Z%Wg5sTSkA%MX}?F|S|C>1sqwg?0z6s8@2tw4=MWS=0Kn4Luq09(WbuWb2jZZ= z^8E}QakjaQDvdaRRCQa!o~m>qIW)7yWH`vg43;8xFqGRYCL2|-ltJ9azopn5qcr@Z zJsfB%>09xi+UJE~=GGpwN9e$IKV?=Ga-u8kK99R^R;X!&=h{JwkNF(KBkGKbV@4%T z#n!N^K4*yD`pN@O^2bl zW~BxaV?NUe9D6r~QfZ?t+N`i^NnxPBTG!j?pr$dZwCiz`JH99*$ZSP&Zgn*4_PnD~ zsiQ$`IW6?24L!~tvc;u%qhY`V@}zolT&iaA0of4<#7rC51B^ znZhx0pm65diy9n<`2Y|~Tf-KJ`$9_!=PQMNZ@Wf^z2EouxUdHzuW#nOzWIRZ8`MXk zZwid1Z6IY8K3eE_l90Mm)92GETFqyc-6hZsxs0d&wNHdnI?ksfD| z$8T=bn4Va+2YG4t8drgf`SNn*LaSqTE#GI{)Ed@+*I2>XH_oVF!hHoj+d_-0ziWHS zTU_cgXm3E#bgSD<(5OCQo4vpZn3B1@HDhzzs|L?F@R);BTp)N%c1!R~uSg?nd|`ty zGYjUz^KAd5)RvZc*W20Ww0@prhXnQscu8A7>p+eb0%mxa?Sryd0U!H~JC?M4{MgAG z12w+|j@g8RZ67u1s@p!8tMW|d$orkDorS=d$_=&HD!>~2IopwBNz+IBc0f4<`k&tP zK^HNkFYuaVQ<1z;Q4SdWXVf7%J9TNx$FgYbocc`*oc3m3Wn=O)*twSnr#Ztx)-xNf z>CC^4$shA{dGL%&Ruqh6MB~d7i0Z_N_Z<1;54+P5otAQkeLg9Th1b!%8m%YTf=XrD zP(6E+lFgBkoQ)K!g1t=zv(feP_hnBKkIVx8{4XibnYT%caH>&U)qFuM>m;w}R2fQO zvubgEEuX4!ONFwItzywjw%(~`oykr@fBj;=WO2=ltdj-zDaT^7E|@DL^(soIn{?V% zAgG;H;66?&oQ4`%oCzmISdrm$?-ti+x1Ogz<-c%EfV172g1x8gI_h*g+(?yOO8S7km}CAQhZ_xkqt%}>Y*R+1Tt!ps8|K1(JO7qFVWmQU8`t7K zjp!I5%CV&EK{`64RjqEhQ93%&*xMbQnA5B@+^2${^#yNat3&|SN7re`OfdpG0l=Nc z3UYuEi*12P7HSHPK;NeGn%pXG~*mi&w$))st*IPkId z$54nnMRQ!-R$WKH}XZ*1obPRL^D$TQ1}Uk{txt_=yJ^0rOzTINL4mpazniIk9% zM9S}1Ecx%j*_xu)ZTmRuL<>ZsG|A(9-TG)vt>uy8Mlc35jPr}N{tF7z;)(8yE3uP+ zwJCxUF?;}i8)5)yi5ObJZZt4r;HS*DuxJ@EEVC`DLKG)6Qwv6m+`lGgV$j!Uo)~^q zthSJk1IGsEAhW5SEjL)6CK_&<&{HKMA6=JifMtq>4z1osA==lgukvK7%a#IDwzE)5 z3!*gAK=mfe)k{!a;&%v1iuF=LnA+2p+aW}aCR&NQ3t7!mSF9#njqU?jZ2)!2>^BJM zF@QNQ^R~Bj>h!by+@ye^K5nvr@q(NBNaBhEbPfS`^7}%{th)tLCR=?V!o1~WQ7yMD zsK5Ylui+kbn+;U?rasdJs{5q6Ly6} zkg`jY$;^}tL^!zw5fU{Ti=SRvL_iqvA8PrNQ+kD5!m=ztYH2y+BDo}nWH?Q{T;fQZ zL`-clW3ZfDT5w5Sd0X_5P=h`##u$Kj9b~rMlT4&{U?)(L%oTJ3T1;B%1iWOT?@pQS zpC6?A)wO|{lPuc~r?e-_5_&FYfQ#Kq$(~#pS6Q>?@^38FKneLZZ>~fgAJ!-TRwDXp zP)GP>)4-G(?d?3;r&u|yo^akKlKRyzyELKaX=d|?O81NE5D1TAzE#r_G_ieE7=IMH z-`coO=1i02q<$ev3|Z?gB(*C{5|)$NN@S5+KdI{rNu8@&i*SMt&oU{wK!wqabqoL` zgucqwL@r#~>4>mMjaxIen1VED`;hl#q849VnYI#gVaFBpY$OmrSI33^Or2R^nT-JI z?_?(BD@7p)rYFB=mJpU!u};)jJ6_1z7_0+WySGHNPgwi{(LRcl7KkpvYM#pHi6YW& z5|&|6fu$@aEV9i97)roj78a2e0kWNl%7)S+nwarui$cAjR9+N{gcLg2*-FVkolF=S zO4#dUvy~Xf^p=D=;w`G$R)e;+8Wdsetp;WJFDQF!3BIO7mi;tHAAQ+|d7oUE9Op|x zO@x_W=@;Qewzr8eEHy4GpALAMN!I~|dgWl#>{-LQLua8$@=ajE$vj z5DmOnZi85EXM^+)j*una6X!b2WJ;~j*IfwALEVMhQ3nFB59ktR*h|j>Sx5p=N2<1u z1S1Pcz@8|BNnk4qA;H0~8WNb>MjEC~kVjhOLg0cf5B$y$wcWxlZ8VwIHQ-^Otqr^+7>ZO-4{!Q>v3J3}ks(m{9k7)AQYpS6NT&CO z@w47&_{5M7AIPzIanO|aS-JIV$fiZmn{SIjRp0tG`AGNMMlw{S#d-?0Vp2R=YqJUh z*^Ss|Ba7}}?>t{Iri)*~#l$Al;QSWhbDGjEaG5K=k;k)m!&wgIUBUC@S9mQ52ku4v z{_JJ(upLA|kkyICRJXAng1Wz{ML0IHquOgm5m$|DCYp2(tAUQ=- z4?@-E0?ddQ8|HvnJ>VTXSf^2kMWKdPXDEuQ$t@w1ME5qb16*5Ng$l7&ZEo>!E7vF- zQDIzWh;Wh)fu#rWyAz^i`jf{;>YCKhBFiDgTCH(Gb!%)l!Pm1P`v zoOZH&?%NeKyPNsDfC2}#JBLA9yNi+3?m7&T(58lkHW|;+?r#A;mV+_3ae+AI0tu3| z=W5ZK$R>oOMC(c7V<3PrqJ#gC9mBn9B$^RK3Tx_-2odcg(LpS8nq`$2Ut}#6G^jB1S*Ru zmj-9ai}}QHD2XBYY}atJhFB{+;!m*$QktN@%IfGS5NHChPaQoKgZDYr(Fr>zK$S`z z4Pwjt08Ph_&;fwsDB1{;k4Hztk~S(dj|;UjeG`o&i}a1u9sCnZ-#{DFZ#m>8eRCo< zePh%>-<;qSZcvn71_T#sW#lUG_Jx-8%~uNh9aO4_q#S#NcIMavkykft6K+k+Ni|>= zmefswvDA$sFCDsl>IOX3;=U=w2djYCouH(!r5rIY+t#db9-Kc-*n=mmoZ@)7csf+4 z+4UY?GvUB+TQ%|D0PbI+P@nXl4P`>E8bG%Kiq4(w0Yv3}0G+X8%2o}a&48lF zrXE1U3jjJD%B)^BfNm5heE=aSeH5AxW#F$GK(gQz*3_erI^c3Ad(zfguVxQ&70?iv zl-qkc3IS1Dw*Po(_Zb_(Wup+vyA0EgE9f$Ed&?Qv&gxQkac<4N(BA6r*xpMIDV-+8 zN^^5%D+}NwvdK|>)Z#W_1bXM(+B#v&B8)Rv1D+I6(lR{5V$2de$2D+p-RCk2S((h1 z@MDzoZ2hdGHiD6Qyjwdb@2Uw1OkjQRT=>%FWBPgp8}<0uAYTC=4=-!_(6BGe^wBtU z)lDC8RYX#b{I(Wgd%F+cj<E zokH1QYpB2Z+}Zd3%}3wQUQu(XuHIRxoY?48S$t4?k6>3z<~iY%Ik!vWaYb)s386`q z^7c}UYP6$|Ksf;@3Z&BzN6*9^Nk4KcFNqv2+0!H$Udd@X^?FB7zW!i~=u^`ck zK^C`9icVSg2_RuFp$aK-Rh{sR>>ocKYEu!*uTsgc8t9rRlJKzDEmyZ< z9CP{Q>Y6t4+?HuSnL+>{h5#;c;u#`L#i5B`Gu~Ux-pD?%;M3vWrD&kzn_kb(AYye|+Utk%nGbh{>9tetNoj*d zhTftaOT?(y28)V{ZA7dog_^18L`&N&EP@N|C95bE#ocdpmyvt+f3Lcbqk8 zYDUWS7J}rx~W0QC76B@2Mx*OYh0KZ<{oJ(tFO8m-NqzUAlIyhw<|hj`q>W z_H8%eIl*`Kvc0`$x8^^8u0sQQMVK<@I$Tg)cx@0nud&yG2!ysRpd7fPp{b4 zGrKbWN&ffz%ODAq`f(_6>(VZ}J=>E#Bs~A7-H^5xKe}R7BUL;3^$Ps^(tEB~3<2CG zm$B#ASu5UL6&dHe^fv8x%UDNm>@2^W;ru8e)e0&=YlLQ?^z^Fe7+k7uC71PFWJyRFdr3 z&9=jG?RzhmfyGb1X{%!N9-la~cTn+8&kM7cD)+fSO@Nd|kP=t+POtj;Z!e=bmXZMT z72C7RXf69O08-SY&bYA)l3j1a7o&kdvb=&;-bL}TNy;+NoHjI^UIXHqS*GPCP0OC$ zz04+-9X@$$(7c%;@Ht*q9{4Kp0W-QPOx%=Rx_#T8Vz~ph4PaP(q!6nBhuy0Aox)Dd z=k^<}R}_CtitreR5$gN)R#g&`PJ}RB8IQ(E^_ovg1W28(##U^no%iMchn4XRu!ypo zy9RW$F3r8K6+{Q5vtFwL<{8(6&(zX}7=&8E6S_tj>g{E(0vXtmx(s+|B(`V-0k$bN zGKkK6+PRdJUkP+j!59Wsgojc$#yhYfsCj#Gy}d~xRxJ))`TgggPDlZubfMMx*=8Ik3lS*>aI<_FRzwag zR8ifdF^)7TamS< z;UYN;6q6!C0>wySw{wi>JVMJbbwnEJNPb*luc+%t$}dYYG4OBHmNqjXk(S8|{S{O^ z9SC}Q1uahpEH{LlNqxccZ+q~I?4@h%^I`;rUEvZm1OS8NI#QmezRVJ!!;+5U$X*^- zk3yQgTYq$fpuFXyfp`k|;cM+@`Bxk>7+s_Fg}V+Cog$wBgIPUk_5`H_h2tvSoJ5kX z(em*>Y2{C#bc~d<7NhKSW7#qbHUqo)M~>u3P!o%s))1OK<|4c!B(l3KP*#NeR*$_)9-N{; z<|6!UsepU7Rjglj^^J?y+@z<*5rir9h9_G2#YDui+N3NHQcEh7SN3C88R4H~qM?X? zM+7LD8~wNRPsNbKchd#1%oa2dcs651j%%TgdqkQJ9qUJ?lu(Swssunm0IOzI z%3H0Y7PLzrDteP~0kA^95i3-B&Lm#~BFF*bvEE$D<-@E4z-s|Ve zCT&ouvz!n9&fEXF22bz24meq=!5PwLMocvd7TE>q6m4u_3FotN2$>A_381~DXX0@t ztNK9T%Bhg0^z-PR#TYZp@K2P^wN295GG}|8XMJv-)Z9Glt^R{roiCXTxD2s|LIlD0 zq{-+!MGbrzhcEJIb1_AcU>-Ax%)D?xKGLP{p}k7;L+(X&!V-kIq`VgA2vA`tKqT&$ zu#uVySrwm=IhUzei%IcEpwqi>Sw-Y8#$LRZBT`R3iTr3!UHgsk4HV%FAzNLTh%QK1 z=uybTBs~$+gmodWyQW@&sIg1ovkBIsf=}iLQd)+}PUsH{!brGQ!vrN2qSr)?L|Bs| zCT?tTTDz71NUL*WmW&BVM_453#um4DWz=L~_^9sUD#xVHXymQ)TS@uvX5@fSW{RrV zDti|CQ$BP^JnG;mFhIni+a%~rWN!>0waN`IloYKJkWn*8%77RP(_k#FQosL1_!P(Y zSVZ8r`bHM0Pq&?^uv_iMbaaenFK#qTgPKU|U93&bJsFzgu6c>-T+pUZnc`Mi7|Wqr zR&&X*p!&8pJNxyYTO6aH^Jx87QrhCzr{yOwy=lw?QQ3tnRKYh!E3ec#!- z!T{}ON8VK|c!ZO7{!xfuiK$KBqK+xG*2XL@uO(HA7^`_xhAo>WB~=xo=T>4J62hjm z)kX86zp1(e=ScNt`5TE=>G`Oop1(2VwB-vq|cL9Vg5B!pBoh-jYt zehWhS+Hpoki=@1bYGe9jBYmBv`%w1Z08JH_JZ@Zag!vzp#PSDN^KDN?hIm?6;l|X#3FS>Y< zCFR@x473E*yfBrBMvUj*W9E3v0SWUxDuDLR%6m=dD#xjjtr&_ePOdUVLdf6EjWnc5 z5QP}hSBR9H+KM1Xmaj>*mY@&$_e&m?yBllh0$pMdg+&t^H4!F6f(l;sEHpt%30?To zSlY&74`tnp^?f^l4b`SHXR?-Sdoj5t~K*~Xw>QM3xp9$Y)IbmK&_`N#e6($0fe`hJ70~z5y zY@(K*-@1tW0*!jQ7+1JmKK}ThHTr5xUV9t`WltccQ{b0A8=B*@on=aYNc>=xh-}{H^Fn8C@=A)FwKAFySRC zqyTG6Pv%b2P)I4uH2V4^Ybs@S54NwbCl4Itui}B#^GY5VNG|7r%~KQ45E*GfGt)eP<1FCM|3AopcU^a@GZThB!639bh=h8HRlvT#{8TI6I6`!%z zK;k8aG&Av30Maq@f&R>gnwbl7lk-~}D>idps7=ggvPvzgRGkoj5bkskL5gJyqW5mX z(!yZ`Nwu5u{OhaNCtEKyVsY2Ep(;Y2@h&tN9V1-I=sK9=;q7ATiwp5})!9%wj}D*O=T@PQJ4IePEoG=Wqi&_@;r`N=&xT zemlEydrNvsyPFh)ywLpA>ueEbx zR+f=+l*th~u6dELHtqb>qWIXvt#6zvGX8BXZ~W^(i4ZnMn0d*th=*@zmx3&aA*wNp z3@GVJf7xiWI~RDxbIP3tu(KD)Da>>`f1_sK!Thm*xl9fSZT@LbNdK0beU}s1iH-xx zXnW6FjOw2$ipEsryuj}N04@$NY&ViH>4_saB!F1ymbgcW{GJWJ*&t(HYT~({-{o=8 zGAVU?!RHuDxsM$hc6C@imSyiusb*Yx`R7%L($Gk7VI(CTL1jet!}DAFrCE?PO8+=f z86^!w?`WGa1xlH$oB5I*1B&fFSzCHKxCK$E<;k$hX7yt|QnbDewwy)0HTTPHuz zNPZ}Ivr+eAs^5%;^-T>+6dS)+8t9? zd2mbwyMM&xqZU^!_P2b@{JWfSB;~RG%t!h%AL!3~sF}&AFw!^-$|KF3^3MLx?(WOH ztv_>L%#;fJ7?UM{Ap7TM>AZlUM1CyMW~c!4KlBLI|9Xr<$+J4!alX(nL!3T2<|XB= z#0_H8zhoy7uMNh`@<9A4Z{?}N+7jg0>Iz<27!s-RW1$ZGUFLW{J)}?ian?~=Orzxn zqnP@Fd^D^4&l5+d1HsqQYADX24jqvsObVMixFD$0H=|o|+eRYhiFcirBsFl7%-_w4 z@G#X;p`z`Otq6DDdpI15U=Jf zpR20zf+uW6d*(;_GRGtX?mzVAv*S~3^|z>$M`&F!5c6w$XBvAB9X|0nx=yr^tm-se zl!U!097nEK_V-0=@yjXr5Jwkxzt|-A3S*&?thZB=OgyF3)_8HI9_Y#Gt03HGzKY-J zWo!R+Ngt4V_&=XqDwiD}(1$)h*au)fM`1~$2gCD#J|=yC_`NSY#T`^`Z+Pwr&)M+2 z&a-#=<0^k#>5t33%9q-&^dsIW$GEXlu=6d)ZF1br9z${#iqQi5;Bz->Sj zwqPJH)DtE{QhehFtrvJR)4I=vWPN)*ju=S~m3o5LS>E(M3rEpF)aI=i!5AE#me4sR z+@9ut!H}^C+Q0=EeY1ye_Jwcu^38q;6JjyjfH=0w?pZLHvs6cBgF6b}vUrD?7{3Xp z_+@eY@JQJR}($PTBi4kx+eaE0j@0w>vkDwvp8!OBjtGDa*M_%cQ;T#m0CDj86%DptyR8p4#V6p&W(+v4<1@G^F7Z#7_>Kv?Q{;ngxXu0(L@ zo~>%N7^c!o_2(rh4}q6eP@34$TUDGL;uEd%poObq+>q2ltVwpzHu3u!fMZ{7qBy&X zTUZa>>RU+#y{BAhcQs?57y!wngJc4btS;CouRt;(kO&e6l2s0pNkFp7Kw^a_4J3S~ z(1m9kJUF%^h}b284x)fI0U(^%6#~gfsVV496sr`xYQn<4oD?p|ek(=GR|2E%*`4C- z?j$k=&oRn2NdaId~!kS;L=2sP~)%>cHG`~tXa1Izg zQ>?;T4LLxxwN6Zmi%)o{GzPv|Q2UOcULP<8!eTOD*Gdl#DCB4iL#uiayR<9|6r;14 zw}`=wRb8Mb&-B(jwQAS6L4JO<6I4q8nU$g13GeO15_`K6@+K^U1YX`vhu2QjE^4P|BMs|>Gd zJ|XE`$&7lEhHIj@%9eK|imRO{t^p5L+w@!E5O%O{@@YFvy0L-;Dzdc2x^AX4BFm&- zHEhyX7wa_XtHPwO_DNs8#7SS(9De_#LnzJ`>NDr(%mBz4I&2I?Kv^xI)Tj`kvJfg5 zR3a*fJJckH?o*ZUB2t~zP99b-5k63zB9Moj!{7rV2tMGMEs9Bp&*T!}vl@f}4^|s` z41o{80Ytz8L^}r%5hhdUwA%1ybrVEdn%&jKLLgdKPe#!x+Q`M zLbHJ}uUQ;S46hGT9ml^lKx!SdssSlsHSP!tL283a4~G=vAXAXq5FoYAA+>IaklHX) zT2wb6wPz@#))yNDDJY&nYP~~h{SqMskuHGLy2T;I@CJc(nq%TRfCb-Z0!t*a%XzB> z(5g`B;lN@jWC~c_0Ic;6to2I-R(GcSsCG_lG<~FRebE(^dQYw1o=aPzrh60jvuIG( z^*ee6)i>t2@NO3;@Amo*;A4&@bq(}-gI9~V6&KmktKHB6lncGpLqyuo>L0Z-mwJo#J|HXhOCP3qgZYTm_Z znHSK(bs}$2{IJ2E8)nDGnH2-zllna+jUskqPS|(ZSe&aVkLzxu<|HjP_Rq;M zi3amP!JKrz7XWM+2E3vI9)leYKSW)@Z-c>aLj!(VtX+Z46g>eF-B<8iZ}9tG8g}@d zE409=^IVOJ%V*a+?B=U_0d(LIa_@SHZv~+5Z72%GKi$v}-xh%&0?2x)dh3gGX|hwC ztC1r}o?EaKD*1tgN(Iu_Z8JOSFMwrWQu@ zIz_dRxjiIGB~5Fe(e-&13wg0V7*$^>s*Xm}Pt_-VMa);UP}jdxidKI&CmqtJk?iGgAxgv)#E{A%Px<+Q>^2GPpR^V*1U!X;o$WF z(V!=JupOWx#4}ZU1Qk>fqCu-Yc0QFyD8C6Fc&VxgXL(lgfTrq!&xJlfG@vr=eIh^_ zvfBpZXFuPv4My7xN-fH|%)709(ls}G=Xb0Xef0s( z^0vz*hX$M<)d!2AQkUQYUMu2vXoXG^=gdlEeh!jKXG4nN#BR={Rvje1ZRQRF8GFS= z+hiGzY1>)CHTZ}WN&a^ksHr7`Q%Jt@1^M6OL;TiEdGeCdIYaF%$!AKaxf4gNa;lq< zIyIyG7H>uMhRAK|g*A079P zaF4e3hYk3N_z~WV{?QZu5nkkcG`yufijpG-q(ey@S?5K>JSo|$1Xb`zyGZ2YqF&}M zELgQ@aqIJZpDV)xp(=&!MI~#p* zaSIb}+R;uN)ns@9;ULM0VLCFYBaf8BgXYC+dt;REj`JZe2qUze;A!uN3@Guh zNTTq?Xw~|>n}Gbp#lXu{Vh#V1DA64-abQ7jlm=TN&OnUXbYQdXSFIcF=}-c_$Tx%J z<^)?iYiftv&Uy-a>{KTwJd3P4)!B}xIu%X`994I%Q*Am0_n+4(JUdCY?pdcg9YcAJ zHhdRirwXx`7`qT@i@vqsjGt>_c`zY16Dc#1ikUQVgVm^Mz@UKtuF#@$F_rZZ6qX*W z>944Tmn{E!Lgf-t;|kUJv1)}@>+IYFZ_sIXF+@CbA6oXA%mcD)!S~PYydzOph*M9u zgYiA6b{-fy~Y76B5M7Z z!zY|(VIVcsq23eu$AFdr+|M(sl0MgEvf;z|H*0Uv_oT^)v-^}r86tXL^G5cPPsus$qHP{_8l^Wv%+9MF>^d1~(Y*i@ z=$PiW;A|?IXQy;PPw{M;1Bmj2qfIf8@SBi(Y$E8O;7ehW;X93$luLZnZ81sbQ5Idm z0yII!U*OH__>kDFuZcG+00(nLWE~5mpvlNzWhidHStWS(exE$D-Ff#FF))wVHN)60 z?rV3cUILMXYcUj^n&1v}X(^jUIb9*NXi1C%h0p`$Xh{6_(M`D~NX#5XMbd#Wi|j;4 zM>?0sHr4l(q%?Hdm|mjzHb7#~*If~ABnTK|v|)6Zo7Bev60}|zUHdl=Ovu5^0Xrop z*J?uIc~JSa+iX~D9+zM9kW>Zuu_L8csyb+-xKT{Q7jvBFQ}=id<5|q(`4_(+k7xQL zVn|RJIfemcoUU1-CML}4H7#f5p!|de$aJ3INil96^WVq>_8Zr+%tFbi9y5iPPjI^P zZxb3o0MNNo(s72?n)E1X9&e`CzRa=^l9}(cd>A>DFkUPWT{f)OF%~q8KAq0^r|LXF zn}v|mje)&o-Q||COyb8QNv|UYOClJv+?xBb({zf4!$Tac5sz2227664eioZ9m(xdZ za)CW9bd2i)>XMSifgjGrmDwwM%fym9rId~IN3maXMyX6+N!%OV+tCU9UQL})GzShx zF2fP_u>=oRS{SEw>$glqD ztHSOI|EOytcsRk4Ry8TSJr`CG;Q00re0c?67 zD?+p-h|&R;PX2eC`}8#fifL=LV2xyiIRa+*PDd%#){4%%`cl!G=0m>H;;VK=&~n$r zf!Y>euG2WQGZE$uG_xVIWH>*`WQ)Fcm$7UVG|(0@EB=zg=mIsP+U&dc9ru6jz#l*- zbZ~k0z$fF`vCT#^kc=VsAh_v0%iUxY^`7NA`bI0lNor$y(S3NQ0VQ3;)TjC114F^U zbRD|=5+8?^;GmrAIiycF(l2t@Y0WYq9oaR9dP;E{AnVI8)B~P~gq>#tN;ih_j_};* zghnJr!nTFUdPCy?-$^qhomUx1A6KpR(`)}`ViRej(;tGPGY}g? z{w>N*r5hu?O+}2EGdln({GsGHShFO`YqZ0#?>r~G`q-Gxg!+zE z)ORZ+s%|0@UuIYR-x3Dl!>dT|7REQ6^lo+1JLM!}qf?A6gM^oAMGinT5sWF-eqJOO z+YAqty^U;$?p7LGi0E!Lq8msA@oX*H83-)_QmcRCjFACx?KqoC6VKLs86~h4G}Tx^ zVlu{+wfJBPPJe98)ag*UK$l!d`=iVb=vjE%1Adm2eq+fQl#vWMvhp?W&P<$*&VcuIHSjzX|h9UvrVSR9o)1r=@0#8luNUn^6d$<8q~s#98B zF)gZ7X<1Zh(FBS7E+E7s`LRPR@_%6YS;Pu01>UfNS;+`4#K9D@N$(^CwCsd=YLUIa zEv8|>O*yDCCB;g!+tN`vu%J!&AtV1S$Ug-S*pb((klbr~#tJH=0W8cdn?BQ$vHiQa zlOWkvN*`nQ>8BRQ<4Nyp3U$XojhrEW|q>BO9N^LmOZ2)W8~Bt zThj0OO+A;U00Fw2g{w1_DY3DWn&h}=0u;enHWdRH&y#4b<0sWzLtF(Ny43_Bj$h$g z(E_?~aVj;6+@Ti#N_~v?`Cj=vjqlb4H9m=B#DS67na=&Be;-vg7U`g-rU{gSV|p5x z`Ih1OoPwg(<(m531F5$$dkT?Hk?hcc>bR%J;ZuEaPnkkOuZ0GBeGw&=}m)(wtE>4}l(=8T;{&DXwc24q*b4oE<&XZ)bu8xhB4 zUgtPID^1HH2Pu4w0?-}DIsi@NkX}|EKyKPuIH)pGu$QoqJ!cyxva> z>8kVekgoSLLi!`T%S^2L^?qhZ=V*WTtdOpEoz7uEHZik|&F*|4tS6(&%GnGl5or@I z&8+JLD?7h+)QA>|$mtxIhQu>LOpma1uYGq2wbb^FHSA`p;m@rJzydb-v?E87&G?2C zB8kp>HoKt7KEn|pPUSWrP6oMTYxwu={ayJK_>C2&nh&qiU7ZTpSuP=!^K>fVPz8j#&>&<9qIq?pL8zO!qqL*b%+})(KMPL6KI~T}zPN@n zX`@mCvN^X#z+GKe5JbGBWdItpU9Aqg&#U^gZ?C?a=+D?&WzhC*%NsS`j_&JCk#lo1 zXRqf}HyDtAbXnuehotbJ^pSqJnSR969T5*CjW6zH1y^m|+05DRIVj3iG}oPt4|i29 zy{wUXRim~OjkERM@&nZGGD9kvK_@z)AS0$;F=q#si;I;*lrw1)nKyfJj;onEO7?-ID+GLm$$=D+?fbL3#xb$)}Jf47=8c zwHKt97wRr+A^#OLw%orssxL{?JaY2cg)jet7H^Tsj~<$U%#8tV>>p!<7El}$x_wL6 ztsdC)+CjJ}elEp~gA0mKbs+zvuva=rm#JT4yhs~~7a8PIi?0OCls>HzHnwRKc@5AS zB~&X+%0);(KeCueAWJB7PGgllS4nOOt;B>T``=)#L&%^EbOY@!zT|r8{Jm=uwx2Qw z>KHRq16*A~*yIHIV;`&U0eTpN9eQoY4{o;px~PW=1N-~3d==R8Pf^WawbW{cU6kZ% zf^ptT)3DHlF{4sCGM$N!shG6mYg9V2O6kZdzlW?+Ix?vm@LJ)tu4Wf3B|)p@{3h0p z$af4!cz!FFwvkEOh{^!vU{&B~t#DYcSs1x=4v>Ndn56-og9%yDr*p7fZFsx;tYlmg z%w8lI+pUlpf0la_qA|&$k7YL3=#C#YGc|0E^{AoaHpRL$28nN#_HK{swEm-dV`TVY zsaSdpRmAPld?!hdzmF=?NJ-du)$%ZLGqmOM zhy@+w73Rur2lZMoIo+bJMQmk`DZZWT(qm>BtQ9r8JB2_4SgZ?0I~=b})1#GXn!v*{ zj$o9VN+;E3e?u~uP71t=7|b8~McB_N;MyDA(d+E!5!-5l@pmfIG|}#eIVBx~3K1FY zBg9Di2q}~2gaSek4@*)IBKcH(YVAAQomRwE_67 z2nz5&Ci?@L#=$W{meeJesTiPP>0Sn-a2NaqQ~A3WsW+J3*om^8DzaJS20WQhZv%Ql z=DlE^ghe6Q7*x{|@l^~6U8Pd*b~@u|&INZW;Vn{qPBg%{0{H1{M7+m7QSI_a^dib6 zNRq-9GGMo)`m)2%F)q6!dZkOVxUuL)9$#EXY-~BP!c-TLIGj!a@uYfH6c2`_t{$mV z5y)fNx%%~B{R-`j1z_Frbzl8@Tm8DXe%({QqLjqXPDBc7H`T;cs9AhPL5{B#Wwsr| zm|Brz+hL5UksLEe&ug)AL_>w%d@A}R3R$oCKabeMEpY?rcO>x?TOSV)Yf2Jg+f*+} z?@ZSH%@T(~iRewK$!zA|>iPcV?q<%OX3j)2T2DQ_tlog3KE0q%xQ5Qq*mHyaBD z4>`QW93ktGWvIuoGw80N6mN89dH5Ra3hBI&4$;zbM6Wy{(m(cjxb;44jOT3ho(V}M4$l2SLR89^1Ikhi>%cc7UE zady>UtWiSus5QUllC`Sl;{$p0=VoSKP2ZGiu#)$h-n1kurR1|dE9FTd+p;4C3XY1#vW@SNZ7t1|0hr&no4P&pydscN%Kx z)rv(&3Sj;^G%eVN>5{pk4mFFMjlw6brcCnJrzkejujIQLs%w_dzJ|INPTw*MJH{yO zBFICVl-r_kfNO4K%;rn%!a=L7#DJFKgE@!A zcT7DV8NO-zF|`r{TGG#BYNeGa8h4diX=N=;X0d483Tk+xcNTK|6TYLNo8#X;L2bC> z-~Xb3g9Hcn!?&CJ;TzAaHAo{Y^EF6Zj9gd54r*cmx}xj~$AhkDrVMmN6@yTdBPljB z6%o^NR1q;KyNZ~VK__fybjEZ-{?IDt8kF&%(aF=vosD!i&2lLvs!$sO`bhf)>S))7Wg6Ww`9_1XdQf%H=x|suwnu5x z=&WVe=&Z;(O2QbW(Tx_qC83vMv`?e6kb4S3=~HAHoz2Q=uF=`V1dR^+iSz03(GN%- zqIonr_GaY!GU6JY4L}fbD7d~x_YG6f*D}+d3|n}YTp9E=b|4U`EIa6HhG*z&hES+c zHc>(W>1&pVHWsj}N(BqI124xp)7N5)R4;ukq9Z^1^fj;D^ffCI^floT!x;^xS)o4q zT10*HHDDk0wW@0LHB~2lEvxl4CDl|Jh(?voHeZ~!VUbD#nZu}4zgD!GHtN)hR%7O` zQ!82xeCyPTRnV^ux+mvfxp2K{F9NvPC zTx|GKscYm#%#ct~hC5R2!BS zJU);|KhVD7hbTzR56MLP3YpTrs!Vncy@zUm@m zp?i@fW4d&&m?c5`DdV?**oWO(L{X8mevJ6m9jm%Qwlh=YVh@ahM@24lUW#0mPnESI zNALS;k|I}?wB~~%7iy9s*O%YVD@>7N6ex23feNF@1$N|$+_zUH9htND#SM80BYXSI z*^bA1z~>;0%<9#|3_Jd7n2cT9gb@iUK^W2PkD%%hVdM~p1XS(>`}Y}eQE zfO1)Rm?@VK^sw(utjPFod(%_(AY^O%?19Qv^#H~LXfP~_!G_a`JSV$aGI4%!wBL`H?NI4)CcVw661%z+$Xks(@0ZuO$rdM!qOiMb z?!1ieaIkmU$49VHRJ+-8%^YelYl-y-9$?NQCZHplTh+Bh7E?%uG3O_7V5JWj@xdK2 zbzpDUfpBhXLJp$9i(H^9>%Hykp=`Ft~_b3;6e@x_R=n#=27Va{CiDYO_Y# z6Ktm~LZr9**kKgLs?2>V%hEcih8t@O$ko#QLC#i$#esMq-CCDRh;{TEBE#;rJ%>mj zT`c;p46D`X+YSrKu!imn%CLF2gFP zDZ?tqWmx45$gszdVfV#GU4~UDmtjTk^tH>dN_QC+mTTIWzJuo8uEE^yK2t8QlGxB1 z7nEVC(OYQ9u(Vh2AcSfO8g4Xcm<+p2#^_}(bD5-Rix191-NrrxF}CZgL*867uaaTw zT>_l(nkBT_CBufVX9x1Yl|9R2=FQ9dn5U6s93>#`1+>FTj zHY3p7n>DF>{UQcRrKA9wa6XCnz_|b~Cl(=`uoVqCcb03S)wsBuU-vCZ2_b6SZ7}<^ zd%>QB2_h18zPikfaR`k%L77f3e8)AzK@cV)89# zdB6o!EX9nG>MAg9MKs3AC56bxa<o%CPF-c^_fh$_rqgh8Yh^!W)Y`FxJXOn6 zzuA$qCmX5uoD0u`;dvlD_lM`c@VqTN_l74F(Y~Jz&+Gi>PJiHOr^c@I$L0Ru{yZgK z;*Zczj-)s#$CF9IEZxSd4W62()Bc}M1-Mm`oMZ4_dB^d3M3OPj2PEs7+9n}=2Ns&E zyW=U<+t#pMM3IU)L*2(*{2~YHUgt2DI%fhEz~t32c>!|%>__DXto-<&bIz)KA1AkdS*L#jMvSpjCb~O*`bZ16`vl2qjD^9s z`71yMqX4$77#K7eS+z`F!MiM3pQq-ZL;KK5DnIl^{U8`l{sB$0LQ~YCJ0?W}#$%Gr z>OtI5bw;+X+ywKzcC?*iXBVUg(yH%&|HW2l^Tsc=@>{Hv-~?a@mdjM;oBYk`8bwMn z;2c2}1%IQ5)1Th;;BsA2s=hKpRMFbO=|S>HyX(QbMmRD`o%ye%EC{p}Q$;I8oOC># zq!NiFV>!HXEPso(3=JUpTWOyYgq|-F$J=q+)BOE3?r9!?+GU&;*Y%((bx(@sPAsq9*c6!Lp5vVZ>u*sRX@|EEc%e-x23%#f4iMYPf9j} zH09>OCdXDymxfj)CguHD)R4;g*w=Yd-rJWCg4OwaeKU49=d_&q=_{$b4f|_C*lM(d ziMGyHhua{LQH?}Y1A*ND_};{~Xyq|a0f~Bc*Q{a>BqrhcVJ7=2!OkuUM5I%;q`9;q z$r0<#Qfdi~YlSSOmfRA}$*dd<6KasuW(>mHldxt7!0e|=9;D>K#Nu0M?Lh4M<6HbX zYfHwSImN2c^cOas98#*6EWcd}4>P+zi4j2XFRmkic;@X@=DuboP-4insB%@Tf*??X zRa_DCFn!l~)Bpmo`K|yRjR9;SR>%KF@5VZpabGi?kUz55kaX2!ekq5*YzEpe)hHEFwMG}^HyvrA_=3y=wcD#$!zJp}?J^y&u9SK2-48xi| zw*i?RZrztnY=pU``_&+B$0O9fvq6E(rmLE^sjsE(vQV^I@IA1jYXAiqh2NYxR{|4k zhtG8MKBU|6b4%y)Agi4UI_tm^R`R=a7^?wlht-liKi+{v)aUu}-Mw`89hjPt$OL+K z_AyheKs!yUdBZ)?SHl0Y4hCRb*e8F7R%qwr?KHa~d$r&KF)*l!quYs6OXK{y&M<_d zxOT{Z$oYPp$u2bSr9fM7woAp0*|A3W#8mA~sbxYMEE_(HT#YB5f0Ihe)~9e1t4FgY z0^MuWqddIxtaGYI*|DCg0tkFv0X(W%0ePWWeXh*-xp|ALNv@V7i@$d0Lw$-J@Qxa1 zp$}|0akj$u^+tJxJt$QtG!9rRmX z?S&z!tMJ~?aluoWu#Et*`>e39W_IpvXNjU3X)4sF$?ye3SQ9f1-ms9?Kj(AUqY?QzAMXNRA-61a&WHG-p+24S(H9II`|?QRoDULGea`20erV^8Nn}u5 z^~WS=?hfk-?NA+7$iYX5q;Kh_)x$iM zvnki)4g$~sH=7C=@D$A_fcQ2Y@^e78J}F}>lzzAfXf92TjCQDuZ>Q2LE=YG#142T? zWCBRWbt#$6Fjf&_(<`jT*@o2DD*L3q!fL$AuBJ{Dn3Cmhi-SG+SfxG~6QZih>?*ow zD>8ovy1V+bORF!@mC~_4Pt+gpwU4W11I?1)78jJ!h6+nro7BS>RmuTNxvg4NHmF@T zC>B=(Ob01wY`zB5?Z`6s3e@}RC4Xmhe6S_o0MzZEkBc6f8CC2=*Q9L(1jtKHd|fPY z$W}UE1+9$Y!hRZ>O8^TiPDEMqrT~JF7@^5HA~f%(Pz}xf34<|d-|%)Hec1fU8RQRgg#$`ua7w*Wsam2sIF|`H^dAuR8VA$N`LZ8ZGs>^kZPI%AE+Xz} zpxr+yL~P5N*D^#zf`t>$M?{m7q@O(Fh!~Bupxk^!1l1xcg6|b73Vk_}$x0rT+VXoT zL1c9_%p;2w#^KDc*0^YS7R1-S<#p+_7KFqL{z6h3x71_u5aS9VK2V_>#mqo)6N^b- zpWoUH%vjK(uJH?c$g(Vn_%D?}5~ei-$&0p+78SCrSv)K<%Y?)>GufhOWIoWJiP~Fd zK9e`t|D~u#db1kmEcYaw1 z$dqVY0)%R7OV*Q!_F)Rm&ywTm_JLyo^yJUGVoPbLErczd`@}-8NbN z`%sk+=$T@PAKR+0x6R~tSmDhq+ve=N%={8GJB7~@Unl2nv&G>8)VS z6-qLH52J>*ac(yMf9Q3MG}#UjPwWpG2Zl#1@B!!3xQMvHc~#>x(0NAQURZL71_%n0Bv1$ zM?=Wf?qmx&w-7=!x<7N|n-x8GV)3+)4`Fc-`L1*1qcbAN{MF{LA)h4BE30JabHGV2 zR^h0MU0M}m+16hRm%Ma^u__HU2+1N!j_AxI7%W8IQsr)mh&(lb$h#baL1ZvkWJc~S za!cf9NByDKhSHnVF+=H1)~LCIQLFI!R}xTU5yaloKy0>@7DjB?Cy0H&tZE=80ZX4L zs(#))qCUlF!K00gcB7`MScnd9T!0Q2`8PD|Vf5A_pnfxq;@M_Skr@d);1Gdj4e%tG zkmE`*pX&$@KnL(Z=rB$d$Sh3#R{f#IP1#ibi|GPGcUE&OjD4;q^rG!gi!pDTH-&Ua zQz#@ST)hGg)XN2`?1{{)k93f~ZW%hS3{E2~@;59rLe?2L`ai)4-?;b)|3_JcH!m~{ zTZIn>s&7*KGeGstgwQW?pc9zPv74tB-F2XzY!7(ERwUVf!?$Y;g}R1f^SFeUuDFCM zy}T-2F^lkZ#U;XT1lS;8dNpDfziBc|zkuVp3ko>9ApxS1!z3I=xGCGIKMZ1kMGz%b z^3NsoR{`6i=<_EhVG&D~eK4~;6SB`c6BmApXO71}U@EntnJq3HwV4u86=W-xrmtVI*gk-!CgF>|5J&HI6Xuk8Y3(y!skI; z0$P{WLAp}g5hb7MH3oM{)$C{@riVZNZ7|${}W)g^1)^_(O2|2 z)jpR8E5EBvKC(?)8;|K>laKV~CSOl=v~op!=P)%YT5_NZC;BEn8@~!_>b4 zb-{-%HDE_WZ@AZk)K@y@Jayqm1_6w^0Qp=@UBEsPF3*YaD+NTB)N_F$nuD6Zzq%R*vmAhh3~H)}*e$>ZaHQG9xRQkIb6rA<#Im`8r(}XNP)i5f~XS zxg~rFUuRR2t#m%0ueVmcQaJR4Kh0iFOU1V-B#Vh;xp_-)#gjIr1=9d)yQiZ|yWK}?r_<+Bm^ETV0Fy~9{1##mbik&wlZd8tZ z0Inf1gqar0`%N)w_}#DSiRkfRJ=f_8drZsIXY!92Q^e4II`^VHuf-e~?XT0%u=Gt< zqp8f)UXoaLQ#?rL*o7={W7kt8tlI>(g?kteL`3A4IL^-ZDUxc=eS z#ACujomxG%a_kcm4>72XlsoY%v@b@=-;1V?!|Lad{9FgSRYq4TJ1HPF9?f2HIPOJ>-N z2}W4+5aq!?wET3&2&@wD84O(;4^s?Aw6%bgF+kpY4KW3HeZoYjg%m2~{ae1UEyy$Y&@-VIdX?~x3bF!+NtXim8K6$%@G%twk zVjr>NJYROfRh?n^b{7*=2`@qgc22ZEmj9l~F9OW#?NUA}+?NC!VR@&Dh|&Z*VJ6Q^ zC`Zf}LF&stzjaeR9QOP4yo*MU>M}#;7gB&uzD37S;dZ*VxBzbmvE)gU2 z+%|Dc#P~ouU&NSnjz3@0C<0^VN)U08!KJvdk{vLEw#xHD>5aonZ>~yb978ZsV}E#Z z{J?&5{J`cOi-`T64bSWRClp+Q!Bzga(jS-dc)jBB{0(=NT;gf;RRxh`L=HCd*q6*G zg;h|A7YK?F50?P?B?FfSe!rFnFvXhCKAdet4g$xBuYF4k`UWeQ6EkVDf?rdDj4Q@& z;Hds0bBr+}xGsA~lmTI>n3qT5i`#)nIT2GZd{d?e(xZwDvLk_$!dO1M9{ftYW|oRK z%ErHE;jqC=7r;awH9e2+oZ_zEJJR}=jeUMI8AovM`p|Db^zM5fL{KWn0&hyt(XBA) zhm$~(b;Q4h8Vycq%lH4T$Wyf4Vtp(eRgO1vUk>~2VsnlM;*5&XL7{9W$Z zZb`zT8%8rn%AATob35zIoh0RTkXi|KFJ7Dfb86BKi%p5D#fm0MCz5Z<%xh(3qC1I< z8Eo3?0ho4_E3WRfb+atZ6zv`Pq4W%`G2Ypc<%OESj1_WYSLOCHsBnoD4Yy#^Z?LY@S>iZ+j*naIZz-<)+xT60A|wMZyy!{+GC zimQu+*#j%&F`8->B67kY^tM9#zIYG&^{53&^x6o4!)wIgHCjmUEph(8ODD$wifq&v zB?Zh(M|gw?{3DtKM|kd%F@wj0Ee`RFGN~-;k8@Qx{W6+&@_sPqX28Q*^WS6vJnScX zBpu45UI%)nSs|Jf8>e?xjv24)WpQ$|9r+)}$<3HtMyEw@ciS*COS(6w8nRGg$aOY` zC*D=Tk)NW5KKYVkbMDPKnk;_WArs!1;>=5r)u2gAHQ{y}iF%D+i=|5DBpo^0<}dYW zgf*BxVI;un5Wxe6FJy^%?Zt>pa#{k<A9u5o#9Wq2h-64tpTxWX)k@YySy2oL2J!k-k)!JL5R zlB&2GvVrE{I8obb9QObZY*d9SA_I@|WN{d!FYf1wQFT6n;or2(!=J8F0SFPHmJ+%E~* zkuGfr>JjtB;J3O;mDv3%4ULPe$(XgSp=@=&;-a9*(mG-dw`Ezff)#3`eDtIS@K1b3 zT+>T}3ij18tcND?H670{ht@8rL-GZ3&!;OL@~E*DiQ9L%HdznZT;EuhrV$cuN23x= zm*f-%yr@z*zr(P4Gg7k>O(~=s4W;lDyCUc{x+0eEXFGoqo?to<1QbX(eG$5f_K3_F zOE-eLqa1x@{x_Bk!YdCMJ@H*9!)>x^84~^{DOjkcP<#EJ6>Rf2N-*V4KRQQ1v`2JbR=5m=+~z7C4#7^8ZhkKCA2kf02 z6A;N|e<>J33DdQa)cy=il{xsc3+dHNJSDKv+C4o*P@$*7bC~(p>}^u@KljY)*wbM( zz(Z}}oU&`~zge1^bxfSv35Xsr98?3=Em)ENxgq8pAP4l)TN--S6086GBGrGvsuw|+ zU;WQ6py5Dv`c#}RMk%;Y#W|rXl{FAs6fT$}cJ9$d_W74E6~y5%+ai9QpO}X-4?Btw z!=~CHAVdN?1TZ3#VA0{tAJ(F3T}h%_b3QzP2E!!z(SAw(ui_XQlKiM{sib2UMg*?5 zG@?PuY*RFf6~WLOWc|9(+mN(#*AlcRU}@5%`kw!Vg+wsnhZv7Lpwk4{bKU4Y_wPBg zPiC~t0o~{h3PDJ2x#RxvS~L1`+Jgw?UlH|h=a0bZ^S_+0?!44d9l7MHN-n`3=0rXP zJ8SA6H@qP7%7EfgDhehjZjps|I`__}0*?wj1aL509Ms?r{bsEjRRD~EDv%=>L$YmZ z3;LeYkk7YhpQmeLEWKG6rZPVfhC@RvBaHIBA^Ebd*!EGY^KO$vjd`Xl!W2^jxAY); z>vL=u9AF?Bi%+1QvT*fKS*T?tdFJ+4$y5q2T0rCiD{pc*)mERS(r46BRLMQ$zG zk^TogdTCdsMwF2~bC_;v3sg!Hpi$-eJzu-5ph48Te|%_+(@TJ@PS?=5<0J~On6iby zltBpk!z73bW%*Y@J|nnJl>}pHwciO!NKf| zhUl3g`cN^!+PPGI@%DDPd?C%KP9w+c?54G}tu5HD3I*08mtWxsW7{-LEa=Gn`2w3TVjYeff46*O9%pN8K+ z9(ffpT){&kG-#1d)4}>yX@f~pjt(;_z@M8&1!LFLWR}rr99x=EVYG^R`JFW?To|~l z0(=%Sd7G(Gpl5`LMb8m7lj^=SJWOR7?`)0ViKh2|2i0EW!=JG_+;mMNQcn2ep=}1} z0jNqQfAb#-jQ8ZLyAh7(ht_o7)!574*SxNgO==61E@HsZK}|5o#rxoXHhRQ|-zb); zC_EtG?TcL3$O&7gY+7>F>!NpSfWq;U$@pen0q8O~5J(wAbhzo=-0Qr!jG`!H@kjj@ z@9)r#aLkQuWQ*OZxHl7?hG4L~Epn^kR$DzruB{(X>{Go|(Jur2&=RKyFd(tKQwABE zhVq>_6eNC0>38St*`K#11p^X7^#ucx)=gk4IaK60Kk*6|z&56cd%Ev&d46lw`THdE zjXG5I!4tt+tI%gVFCd~}OV@w47h1!R@$>M1GaBA5_q|o?KHd+XZ(zT{FJ9GBDMa}# zFptNU5oUG8Em~#Q7^n*EOPoJk0n|kqwMe6>NXtv2D;vXbk+_T(t{{J#jQtFgO`gs^ zsb=M|$7wR17B@z$C{Kfd@ym|rl7y9{1On-FHeKHIO-h#U`j%K_TNa?Q$S>64*o^%` zlI0(E!^Nj%>Gbw9wr|kJ*Trj#k$bq#6Ft}cx(Ihj_dDfBzR5(D|DXy@43HjigH`#Q zi-{OblMFKHTp7d21ur%mwrax?gP~DQ{}4*HJk?{-^v6@<)+M0Owy0&QJY{52N%MPo z;|UQIZgeh(r-W%AA%Wcoi^RUht{i3}ezE(lvOPj&*||I;XY_A8zkdt+cvM7 zvkEYZ{HMCL#Sb8C%|E80XoB61K=az8JKECafnWwi&__R<;ckEo0&ZO4fQeoy>O6P` zI*K!wn@?I}@OW^if2;hyy7C`iv~uqETw;HJudecQ3H@Ty-!D}fMEGqzF|fTM>ygZu z?EHaV(XZ7jy<(||uTy%pqL?jW;@0{V?ph^_`B*zg;>%H9-iZ7tcQB4L@PbzBFOTtZ zv$EtyV_(6u{Y6eQmLjJa`y5u|U&(34>LCaVWop^y{g2&ocE9X%bWz#o=NYF-9IpNZ zMF}$Izk@7P{hpNg1B5KD;AZ`(Tz;(@a}IWo4LKaCn3l*zI-VX~Jp>*Eq2Ec<6z_*d z^ID;zr_$xj{loSPG5<=~0Iy7s!_!J{bj((2BFvj)jiuO$L0d_N*5ss;HHG99R1xyx zZmtNYB3D}X)gmW>tT`RkjLk7XZ=|5rDQctot=87IzP7e1IXbR@2}4_>L=6hHcZqz) zY-5XlfGVMf1YyPEL0e`@;F}>L?MYx*!#n%j^tSn( z^~6SJ85{A%x$6t_K!H<$u2Hy|4N|)Yvz>3X6qUfU!GV>n5!We~|V#AuT}pnueZg?jC^9-WH{#_1rMXM3&0K3gpM1AulE z*}M~S64MZ=%?Sa>sQFCRjm8Ge-=;Al-YoiXvaw~TXjL}%)yE~*8VSA+*_r-|4a`9u z=k#(PHKh<6{?n^Fd`6~P3+`d5fp3q{1^&W%tXQh43;chuFd!C8}xk40r<#ls~Jmr7ch`2n0LO$cB@sn%@VZB9REpIdm}65g}L zPke9|PH)*-YgaMAuPv6X&6gDx0QI>0ZW8rIlTw9C{e4W2_ z_?Ku&*Yrlhm;AW47^%JlKM1s};Kc77jVtN^rzMuKq8`#pPHjaU4EU=;; zk{%dW)G=F8e8r3{w4%6yAk?Q7r7vtnJ=AWjsC~d(_-`u;=kUH2b&T?wdu?_DT&tt8 zrj9bhv~Uy}&{$R6cVKB|q-8pmysX~yDUUHpeIl4v5AxZ3S09li1Kg~~n1lyJZwI+8 zfQ@MB2$z0nEr^~8KdFsncR_kLVKDvY1?m3A`<=X(cM`YLbd}p+H!b&Ns1Ey~TSJ1G za*4z4R1ytkSQo&8xAT#WxFwxit*E|?vgxk!Za59FJ~Q*cZi~b)oDU1pK*ccBV1pH~ zF<8MICj(v0<)S#n<3L%?Hb|aJ9H3t$I&6S#4G`EPf3$iwL}7r2DD>zXA{4}9;~lV5 zD3|stm#o?7bYV|;^>3zKh=T@DF-C=yQUfvvE3VW#7*^!?zu3=Y!I;eo32Af@vay*h zDjgz#*)kEfdCi*=bY6m5+x^|8`@|9U;F8k5b+SMd4ot~Z^3dZxcZA2T`!JjMs5YBp zo0DXbL(?JD^yxx4^AJeo-kp|RQMYtj2)>!0bWO#+--vyGhs42E0my79Y+X}#()^uf zE!r%Ga^sL5loF1<1D!x#Z*=1Jx)bkCUB1(q26LxpfN3c~o)K(~Tgp=JHkTW1+j+%I zxzYER-G}7lbO!_lV#xiy(Lqk04TE z+nkLzs+aJp1^Xsk*C``xG z@l2sK{g;$ric!lhiSDu zM|*xs!Xy%tWc^SQ_7Y1nG0#@y`|9rR4&8UpLZ=C(>2!!e7czB1TbTTQBc%Np%dlAA zL+HS!wC$!CEXtsrS7g~f+=(q zDaQ|()7I?qv9I#Dv5=|_)`}QE92F+R@<97U7zkX(P|@u+*ibJFR8trXnbKd?jwd@) zm$0B1I`W=-Oo<+9Q=W;ljptZ*sBMu-8w8m3(#l^fZ=Y6YRPURy(O3-D8vHh1mI(~Y z*+MdNdYZOOMM_0o*XRZqO)UKz;0jVY#1_c0qT)_EDr!uqvtubuWohyh+Y-I4+WPc- zw5rDBq&5{v1*hq$h@1UP;$(~B81Ey%!$#q4>ZC{NipDk-DR51k;JD`zfH&2cH;Nmz zj5uo5h%r`VlerbKq?TAm?Q~=_3)2=)M+g#I)kb0dep@eQ(tbzH#}S#0;%S#gFJZ=v zgR5DaA>+oiB-J-V8h0k59!4iDrS(ne#tS*G>Hu1-yMne6c8OU~?W>nE!6PYC^Ax98 zM6jCTBgtTM4Im)RAS(bDVt%k>G|km?1oLq9JDQWzV83O$wD>)LX~-Vx(kwT=W!C%a zHWAp45qeuKBdFDgZcPZo(|6FWZ(13R*eIp2Xpop$$8b?JuE1I|1lC40~ zDve%xg;lRcS~{9^pC1Htoz}Yrt1T03Dciwltr2&ODT$g?u6WVfE(k1Yrt^9!Sd?Vb z<&|AXM#1gqwn;`@(QZQSsP^T6)$e}Gg5~J!55~Oh$@QEaY>Do*U{)%Jjn4Mu*gU#; z?Tzdq{ld>6l)QjKFZkiLb5oJ-MquLHpH6GAza=)u;_dv13!X!_vCvO)Y@A4p_Q<0RTPFgB)0Pp)jL%`8>&+<2^OIVoO{$R(@%A}*O4n9z7En^b)-_= z9#v;WZ$|TucG+nw`L>wK^07j?dwLA|J1Lf4dIu@+J0 zP)n##wft^XOQ>3{%&V{*?JV5U-#@@2a_9UMt_Dci9l9nISity%w3%JNj*q?=1+i6=9df;))$$yie zXxK2wEyai*x|Z1zGc=8`~2>%Y3$L-W>Vl@;a#wlP=8bL(TX(zlvCrF1G zTPjNns6vd#{DBO#aU5r&?q7n>>15xjYX~N1>cgs%vK>~MO@^qdI!IGPeNdUB;Dh>` zI#b=}glZGme*oJKUV~$l{om_T1`lTa=dTy&QJc<~8QOaS&97Zk|5S(vml*HCGE66T z*VMKqZ%28RRGG=0yw30rnc!n#S%|=nh?hbzn+Le(@5m)=m7r!{-^|vlzOuQUkiZ_sxH}u^-#WV~EY;{&!M_VZZqg?>wtUwxdmC z^VT7G3*psg@IuS~43a*8AWL#Vj(rsM7;>&zB*j>kiKwD5Zdq+k@72%)m^q?|h?P&U zoq_qRlP}3YfdlfC9_LsP)5E=jq{CKO9x*+H95ubqkp}`5?-hJVi^+&%(_i5IiAP3* zHgRVIC1AZ;WU0s*5*_{XgA)9fom+bAs3_!^Q9pg4Uv{_mGN+fiu!9|lv{)vS%0t2W z&Ny*1q&js8Y4Un$I9q1EKS4DH`K=|jf$Bi(uqveX(1}r_sUl}6hyTG5k^b6NkBy)6 zstk;~DCv2|Anijt6B7HUvk>l(C;CeNBxUy*&I2F`NaJLku{*a6N@>StqLksH(|o_g z)1?E;u1M2nTy)ORrvoPvZs{HqI$ve)^i{geDh=A8*~uE(o;pM`Sjf_0nr3HZ^J59m z0Q^<+p~k+JqeF-~qWR9ImkzK#NU_qwWO1R3oozRK53t(?w`TAlpbbBh+6GAn#P)_3 zD)w4v%318X_bN-O|X?=X9b6&`n=_zs^~^%i8rDf8FUWb6hpZ-!?Qm~FX^*ho>&G(M`b=L>tIjB`Ze9OJ<_ zM~X2)3($-KcgAdsUY}55nR?64%)a)U9CYkJ9A6&;tnLTk#k9ajEa z(;Ho@&&eTZGp!Y)Yc0@H0>ew_0c`C>rzI>>qUuN^6Wo~|Yl8hl)sgORHS}zV!v&F>Ymkdb7AR6xc8ve;kAc($>Y=R$tf& zI)5~?EdZkh5a_r8Zr~iUg)Iz56dB3?DznRTqjxp>ihEb*U(Y(vK@1+OFxC8IYn7~p zBj=Q`v)#EmQwU|b3Tnzt5yDVx9y|8{coA?3svxczC)x_63?hXP$r3*qB4{pJ4h~0( znB~V!Qb+SaMs{y61j}0zGgw=-RB2v?J>Ll?1m+HCnO1g@?YPId061r}Z;e7i6_**y zks4KS3i_sQZzalR(Y7y7`k6_}%n9Gi59n{Q&aU8qZUAui9fOcyw)UQA4iX_H#dwtU z1{79rm=6a5%UK{W2@2iMmyInNVwRb*$jnKw6Tmt@Wr3^dhTzLm7x`A7(8B(EX3=l> zQD}fYzl(7l9zb0!?F7)Y>5Q<21fP#2&IgMPAwwou5HJRZG&dUVQ5Mg_B)P`~rCN}+gJCJWp=EQ5vZoZ+%9CLMX? zmCW8egc~tvLm+(aMW_WSN#xqKF`UEm2k>>yP26z>bBhc&9!!p16GfDU8J4a5+|He- z1uf|&@<^Y5EpIs`*=C5LKn9nf(nN!TCSj1ZZqc}@V4HK?a*I{BMW3dvVIRzi=63Lp zvx2Oa*a%(>@fAdkuUJ47yqPhur{Zl4t958JWetqTx(jwCL{qjaI;Aq|Ixg$g@zDOT zmY%TwQ!}?l_(xCpN8Ez6U`u-pJ^1KQ;v-9;9kxdD6Kol@I*%-O`QeEBtZDY}-_K0( z77Z)4R`zl#bWSffbN%T?`#id3xrUB8lC?w`bS@gh_1nLKS}x>FW6t7MpR9WN!YJa+ zS;LuzPESD;5uMA!{z@5ja1UrEw!T|WW!v{^9|WCEz!rBzZ@AH1FMRjm%v#V?iU7I% zd)ELGXvy{Te>V8PAimr8bYF4aArJhv*t*fa2#(6~IF99!;B)E~*~F~|aFm}yD}pdV z)*M!wH4K-qwMA~TCkL#lSb}{t8MdHwp#kajw$%9$`~V+plW0J^v^eGEy4XIEuf6>( z1MM6Aj_pVP0ovbEx4$j6pPJ;kwDv>fbKq{!*z9&#c?Fplc?B$YS8PPtX7xG9_%eUo zH*OW3x(1qElgi+OB~Rexqh|giO-rBRlr%FvUTyfe4n{N(f2rP^s~rc#mnID^B4x%H z>M;7I#0iO@yPymO9EEH!A@NjEMm}qXG7`+j@C|EVUW^7# zmsQlr8DWiLZ&pYI%0h3_ewr0}gGd?(USI4CslL`59#wC&?qY8yy*CESNV=-nP&KrH zTM>IR`80aNRzGY*JN{K~!WKVZ4_o}A-3{2pEq-95q5(GA;5b5@<-v1dgrSo%yZ!5<-QWwD9W+B})Q9;;dtZcLIeW-nQT&e+2S(@XU!z*gEW zYlb0)g7nRKaRDz5SBH6Y+ zQ0-;0b@CooWQ2wIR zo4+W{5YgBR16qFp-2S4|n?_A^dh-tTe1Ck=>CInDpMC(ACB`_rQ6dYdhsLf4UM96I zWVbCLhN`>))p(&8XB?U8i*fea_8Go&*%#x?jv1N-U2HeTaw_dmV9s$nAmZ&Ym8|Jb z=fz+v6gp0}J#C5s9rJ4-v>E01<^W_S)6y}m=Qn9WG>u#>@x>)<6Echg1kJ^Ec?7?(%@faR>KGQQkf=Ax z%=-6vev1R0P?@)V; z1sL-Bb{9G+x`949-q!{8Lg(LyCr>Jfl@!wIJd+eQagr1&BIY=BG0v|l98#QLPpk7@ zHz3v?uZixlWz}iTO=EV~Phgx4u>=BPHzFw>E~^08X6VMY z!4|W*R%OpkUt%K4*FUMx(6?1W-c^)0@2Yk<@A>b2ArBh&!a;hLvb<&hi{?1KvasVT zYui3$ZNE{BY1>TxCM|7IYdbHA#}qk3XY7=22mD6nM|=-7Z8?tyc{Un9jw6oGsk6ii zUci>~X!EEcJc7b}wTLap0{vNivgPOtu$kb(&7+1e3vkCo{<|w?ugSVf$gJz)-I8Np z2TRVC2GwJLr(wuJ28v@L2b&Gu6Gw}NoGW12eTE!L(f3~#LzdM>>t+s#N_HQ+%0!IB z4+^Rt7c=A~q{i)dJJO;=I6j4Zy0$Ilgrj{RRp^Wj_=>74){H~pijy?$<~VB_OlJX{ zq-oma>?RV3X_c=@DsoZ@#f(cL%+dr|m4x0!K?|dQT89lz!nN=N0M$gw;wC=!C&a%WnZHg*M~ zUAhf3Pp8W-pc}-JNN)C(*x{`1I%%HJMJq6clR2iYh)nEb`*}B;PJRf7_zoG5c#<;Q zO)C&@D!xiCfBP|ojl+5U8qz2BT!L9x~S~~>j$I{ zC_ookc3Ote#muN*YILy_RrJCkozhUnDSo-n=}>MbnwJ1SCyH>p&nKy(4#7o4m8zcH zEpS2yPBLeoc+BTaC28Nj(wq^d#?J?rZUV2oN`EbYH&;f3Qug zU+f#}G42nBP2WN*c3fD$|B;RJre<~7V(@-*?5 z6evPlgf;nF#g5$ncBY=Os;bDz6h4fqcBbBLKZG5X4(ogv~b4dmsJNc?;X0kVg)Xsu@+>KzV4v4U~tP8z@IjM?GdY z!Y#Cc@(>eTT76==`oeh7L(L5ou5^k__jZ33w`8>+H9Wc?eL&AFejnKGkwoZO9zm;X z@ZH0tM#f8)GhVi3D#m*_fgJX6UW!4^zODKr=antPIWIbAgY#ll=;yps>C0qsun5QT!fFIaniP&JdB^gS|kV#T3DZ5uY9=0tiG0pL ztT~PDdK=xneWN=_Su==4No6X%6D8?h=2HR=Eyf-~J7W*^Q4O>^!30@5>CT+j9Ym4) zDHcT$$#HiY;WM*Nm$_8Z0os}aA%r9v>70!;Dd}W{ERDr>RbdU;0|{-qTegNEgNj6+ zEWgF>j-d9PJ#+e|exZQ&fYj#8lq!5B6=PPtE7LE8fv23^HoP=g_~ac>_AzY!i%=hD5OIXry`1>z?Vp=iGbGJ&*64JDNR@^bjVGW2gTY zdKj;cRBFxYej`1E!%X!MzqLnZLu$ic8pL0wkM$5|)=Cez{2%EdF9R-+^^l?WmW}if zvrIG*cF@;EN(LB|pJ41YkLXJ4A#bF|o*K;p5>MmZXP9g$rm{2#K6Rr;14SD3lL#-ro8G6Z(?RF9?! zf|n{*dLe0?^wPtK8IqB>?;|ev`rt$QyOof@}2C8_fEG?=ut1@&(T&Pf!LnSF7 znyeKV3xVip5f8&szn@Lf zoA;bKbH~@7d+Pp=?mxu;MwD)x`R^zH{_{WBgBy6h`plE39(mx~FFtT=7T=)6=hYv4 z;_i>$eaER|b6Zggg{1DnADQ3>t1mEJ(r@+j=f3vD_pX@5uZeEnbK>bgyW^*S^H0b3 z&u!hpRQL0rrr6Ar!E=oI;4e;|diL8Q9(MJw)U;-|p0Ah_w}&1Rvj~is#Z%A!H;f#l7-{03sd>-4Fo_RrG4#n`h+n;0A0DzwXV*an=`2~^1+yL=SL?X(!% z*)nWLT6*Z|hU0jq5lX#lv}GVHd&#z>KHt-iO5YR7YwAHzeP~4F^Z7!)m@nna`AWW; zujT9c$wIzRC=?5&Lb*^WR1392y)aqK7YoH=u~aMED|0<*__`@= z$9CI7Y{I5*MmrDfRcKq#UX3>F{`q(|g7zA;QM56%*P;!(zZK73hxU3j4ebqRZ$yih zeR-tmOOgbwOm;nUVV%`kMrZ_~FO80FgrV$oL#4^fzRhZk9i@$ut|i0;;QL!>7ouH+ zb}`zU&^or;Z24Yi+4g1A=y(x?pXfqgjQtM2#QuiOSD-YCVy*6AFC)xKEwqaXLf*~L z?no~Rhc0)+-r1dW?3OfprX%}XIYgox6Pb`~D4DdO^93HnaB$b^0ZKje4Ken|BXR{A z8*JBuMZ4R@Ds(uFPS;~4_GQkJ4)oCPjfw$ouIJ_s9G<$SIX69fX!^iG-oGGZMaCdL z#Gtk^LzRxPhFR*uEEphs)`4ZCJ137s5(?1rV0ciDKJ%1~K8`&g3S%Ck`tj;t=%(Si zUIfSw_CRS(=ztLl3;jHzC^v!*+GcgF7IijuDy*$}#xx0(hd9PBOuA$Xn>+ zrtbwoPEmHbo8f|0#}1f$V#tUF{PQAMVlBQ5q`!pGX`u@SjZM?`T-!7pJ#eCS(=|F$ z54zG}E8cJohHGJ-L1?>5^a*!tB1|u|u{Jga%O(#tWOlJUh*xO%a5=iNqjfBH{mDeG zFWcn!d~7<_h}oFrzLbcEj7nZN_b&Fm0rp$B1FWPiU3f+qKka4|Kn%UW_I9xJ8ja95 z++c%sfK84}J;z*QW$=8vP2!O{J<<*Ag4)d?^HS1ihAU0mqDjG(Mer?_K8~HruD@d- zGQc}^Y^bNDFWcb)I3w6}y>3J<-ikFX))$^V2?nviwuY%n*XTK(Vd;r!8#>1i6A~KF z+5!1aeP=vEJlma+Ap1PB8Owq)1Ve}M?In^4`iQBod>23AH9~3`=!TCat(Z<^Vg4*J z-YB@Zu4luy(q08aMAuE3vW|BR+b8o`vOUOy*3CvrK3e)+!zW8s){uB?Z`sALz%1nV z1F{3&O2agXNJK7%FeTf2Oxoa(t{;iuvidtbwfEr6wbOm>;~B+TmUep~40g^N4w-H+ z!E+)Pq??WxNJ}gkzRgyxwgB@9JqKEe-i_Emb_?B%d>=N3cm1b?Lv{-S42hFf&QT2Kka;QFsUmwFk`)Nd&Of1mM;{mc{y{6&&wJ$yk{CC`Q0eDrw;-i~sUf_mfQ4wften7l0ReQ`ov0%Q$yM+e3c?0EWB2T?O;9=Z zRDIM|*s-HpR)Si0fH-iIW)6lO)>RHX>(eDpCJg~Gt8sR5Di%TKZ>Cft3E4@^nOMWK zKmX>UO+ol<7!k)(1`F>9bn3Q-N8BTEf;4|+`-7rdr<=0JHSE^7-XZ~)DLhj=(#YI&^EI;L6{4Rl<4)5 z#LE=lg6CLF*B}`>j&$1+xS?+*1i?A8&vc~zj-AvAdsro!1~FcJY2v)Dyns0##!Df*GSd5>qoB2Ltd#6GL&fwvmhPD z2&q`G+ezGFC8fkcu5<~u@!ZqL48T2y!UjpeH-O#=xgo03=Qx$q<5W&1cqyyov4s>0 z;2cQF0U83Yd5JtlrYvF7JP2Z_i>N|T8qOb5Sm|;S;`HFORjhe z)7R9Vz5AxG*#DlH_x|DguDt5%Yi8#T96WUG;p?uyAzvt#%9U!ZKG_sVzsd5jm7zy_ zjQ1!d0oEEiq8`+(pOJ|EJEekH6#yK`4t*U74D9RUe>9c5;l>H5yaCD59WW+6fE1`a zwG2_E&jMr`l-~6j11k`Nd~UJ{CM}?LfU=YoB-u}o`u?_MgL??yV3>>G9yneohA&;r zORDZPRn?)P6IkPgQ{E$n$(k}|3;?Af9pQ3>xcytZj;-VcF{&ivWrB`3@^wHT{ z-ubZq2J`iQ`U~f}liIHE)(gM?(DmAXd|-9?U7y^pxs}o{@7sNm_T*O|z2U_-k7>`p z<}Lqx{I)IH+y)%zd{!_Wtcc zJNV2^b2}~)+Ue--;96ee*k)_+Lgj z=HLG7wU1nfYq;w;$c1*p>-K_NXoo)hR*(zr%mMUD%no*-lecNx5_2ejEl;_A(I7@Dy}2CIxyxnH&Y<2PPWVRkA$^m&g?zUVAaymGBPyvd>vOuN~o? zqXN6|h_cp3HeE`p2gtqQnrt`6@Q*kskI#5Wr4a;|C=yZhiK|p`KuK#+*(si58c@35 zDEmEoLH$`!nG8>gBFJ8Q6sWM6T}{4+83CnH&P?w3gtG`lXS%Zff#=0s3&UORd& z_;uF)D1f*7<*cip-CMrh3j0gSE+sA;#l^@tCp-W}nak89V~ZSR44w-;RdY%=wx1s) zAutK3g9Qyv>{BY+Rff%-`YeWx!9@>jpQ?IilgilSVV_;v4>x4hRznAfyM0L|HPK`N zs*aMdGN>>4k2tAI6d)_D0T-eSJD2DH4@e zq*zEmvbvVYH3fn>_(sSj7rRh_47-ggA=2B1PM$x%OP!U}aHUwWPBlS%3ePC~d>&Ui zGx;K}bS82NS2_#%R($;tu5?!MKe)ab*EhaXonKsrE5)NJT10i-0UFZ{le-jc?6=U^Was~H)2km#!bhNjky$$X6(9Q{P zsoMSu0V|ON0!ahJ2pw7&1gTuUk5!m~bPSFWl93L3Ak~c)VdI+xca0%3>OEcQBQZdI z4jYa`Lrd&nq;AZcmTa{b?3)*zj_Y-g_(2#gEwA*Bip8ZyLm?C#YpAFLaYjf*>Cmwc zlGC#d1}QldU2PhIy+T}{5~zMEfV=nVE3ce6$Wc(>Ed30m$X9y*=(8_AaL>h``N#{8 zeDm*qWp10Ldzsfur@r*VXCHjeiCusDy_;_PZgpzv7#^;Ip04!zQEAeAhLX{>x8yUa@eAenR{{h_|7=9qk=x<7g9Tm!bUu8sWMe z+77gxXcxR&h&WXwP+~d;PUT=Y{RGjc+bFp4Q;OhC%H8P`G&Jzs-2z3s=7E|0S4|x} zG&hYCuJ=ywJvhA&R@ip!5Or}Kl|1Dc*PJ&xD7Y)+RlKQM?5k?f>&$zO&Wfg#9w`JX zyFvIcDX%mD1gSo*&K3#e0X{^O(-1}xDo8Ye8ga5pr;iLF2{xjVj+2t~;3GW*AlIup S(*bnsNb#M5u~%uA3-Q0?a(l7> diff --git a/contracts/sysio.wrap/sysio.wrap.wasm b/contracts/sysio.wrap/sysio.wrap.wasm index bb767c7b05ad12c058d86e67adf87af10c8686c1..2c7a443ea6eff451c54f12a7fb98c35f143983aa 100755 GIT binary patch literal 6814 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 diff --git a/contracts/tests/CMakeLists.txt b/contracts/tests/CMakeLists.txt index f71d95ea88..ca096b547b 100644 --- a/contracts/tests/CMakeLists.txt +++ b/contracts/tests/CMakeLists.txt @@ -92,16 +92,16 @@ add_contract_suite(amm_math_tests amm_math_tests.cpp ACCOUNTS sysio.opp.common) add_contract_suite(council_math_tests council_math_tests.cpp ACCOUNTS sysio.councl) add_contract_suite(sysio_emissions_tests emissions_tests.cpp ACCOUNTS sysio.dclaim sysio.epoch sysio.msig sysio.opreg - sysio.reserv sysio.system sysio.token sysio.uwrit) + sysio.system sysio.token ) add_contract_suite(t5_emissions_tests emissions_tests.cpp ACCOUNTS sysio.dclaim sysio.epoch sysio.msig sysio.opreg - sysio.reserv sysio.system sysio.token sysio.uwrit) + sysio.system sysio.token ) add_contract_suite(sysio_producer_eligibility_tests emissions_tests.cpp ACCOUNTS sysio.dclaim sysio.epoch sysio.msig sysio.opreg - sysio.reserv sysio.system sysio.token sysio.uwrit) -add_contract_suite(sysio_producer_score_tests emissions_tests.cpp ACCOUNTS + sysio.system sysio.token ) +add_contract_suite(sysio_producer_score_tests emissions_tests.cpp ACCOUNTS sysio.chains sysio.tokens sysio.liq sysio.synd sysio.dclaim sysio.epoch sysio.msig sysio.opreg - sysio.reserv sysio.system sysio.token sysio.uwrit) + sysio.system sysio.token ) add_contract_suite(getpeerkeys_tests getpeerkeys_tests.cpp ACCOUNTS sysio.msig sysio.system sysio.token) add_contract_suite(safe_ops_tests safe_ops_tests.cpp ACCOUNTS sysio.opp.common) @@ -120,15 +120,15 @@ add_contract_suite(sysio_councl_tests sysio.councl_tests.cpp ACCOUNTS add_contract_suite(sysio_dclaim_tests sysio.dclaim_tests.cpp ACCOUNTS sysio.dclaim) add_contract_suite(sysio_dispatch_tests sysio.dispatch_tests.cpp ACCOUNTS sysio.andon sysio.authex sysio.bond sysio.chains sysio.chalg sysio.epoch - sysio.msgch sysio.opreg sysio.opp.common sysio.reserv - sysio.synd sysio.system sysio.token sysio.uwrit) + sysio.msgch sysio.opreg sysio.opp.common + sysio.synd sysio.system sysio.token ) add_contract_suite(sysio_dispute_tests sysio.dispute_tests.cpp ACCOUNTS sysio.chains sysio.chalg sysio.epoch sysio.msgch sysio.msig sysio.opreg sysio.roa sysio.system - sysio.token sysio.uwrit) + sysio.token ) add_contract_suite(sysio_epoch_flushwtdw_tests sysio.epoch_flushwtdw_tests.cpp ACCOUNTS sysio.chains sysio.epoch sysio.msgch sysio.opreg - sysio.system sysio.token sysio.uwrit) + sysio.system sysio.token ) add_contract_suite(sysio_epoch_tests sysio.epoch_tests.cpp ACCOUNTS sysio.chains sysio.epoch) add_contract_suite(sysio_system_finalizer_key_tests sysio.finalizer_key_tests.cpp ACCOUNTS sysio.msig sysio.opreg sysio.system sysio.token) @@ -140,7 +140,7 @@ add_contract_suite(sysio_liq_tests sysio.liq_tests.cpp ACCOUNTS sysio.andon add_contract_suite(sysio_msgch_chain_tests sysio.msgch_chain_tests.cpp ACCOUNTS sysio.chains sysio.chalg sysio.epoch sysio.msgch sysio.opreg sysio.roa sysio.system sysio.token - sysio.uwrit) + ) add_contract_suite(sysio_msgch_tests sysio.msgch_tests.cpp ACCOUNTS sysio.chains sysio.epoch sysio.msgch) add_contract_suite(sysio_msgch_envlog_tests sysio.msgch_tests.cpp ACCOUNTS @@ -149,9 +149,6 @@ add_contract_suite(sysio_msig_tests sysio.msig_tests.cpp ACCOUNTS sysio.msig sysio.system sysio.token test.sendinline) add_contract_suite(sysio_opreg_tests sysio.opreg_tests.cpp ACCOUNTS sysio.epoch sysio.opreg sysio.token) -add_contract_suite(sysio_reserve_tests sysio.reserv_tests.cpp ACCOUNTS - sysio.authex sysio.chains sysio.msgch sysio.opp.common - sysio.reserv sysio.token) add_contract_suite(sysio_roa_tests sysio.roa_tests.cpp ACCOUNTS sysio.authex sysio.msig sysio.roa sysio.system sysio.token test.noop test.sendinline) @@ -171,7 +168,6 @@ add_contract_suite(sysio_system_tests sysio.system_tests.cpp ACCOUNTS add_contract_suite(sysio_token_tests sysio.token_tests.cpp ACCOUNTS sysio.msig sysio.system sysio.token) add_contract_suite(sysio_tokens_tests sysio.tokens_tests.cpp ACCOUNTS sysio.tokens) -add_contract_suite(sysio_uwrit_tests sysio.uwrit_tests.cpp ACCOUNTS sysio.uwrit) add_contract_suite(sysio_wrap_tests sysio.wrap_tests.cpp ACCOUNTS sysio.msig sysio.wrap) add_contract_suite(twap_tests twap_tests.cpp ACCOUNTS sysio.opp.common) get_property(_registered_suites GLOBAL PROPERTY CONTRACT_REGISTERED_SUITES) diff --git a/contracts/tests/contracts.hpp.in b/contracts/tests/contracts.hpp.in index 2506f42ba1..202e506231 100644 --- a/contracts/tests/contracts.hpp.in +++ b/contracts/tests/contracts.hpp.in @@ -24,14 +24,10 @@ struct contracts { static std::vector opreg_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.opreg/sysio.opreg.abi"); } static std::vector msgch_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.msgch/sysio.msgch.wasm"); } static std::vector msgch_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.msgch/sysio.msgch.abi"); } - static std::vector uwrit_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.uwrit/sysio.uwrit.wasm"); } - static std::vector uwrit_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.uwrit/sysio.uwrit.abi"); } static std::vector chalg_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.chalg/sysio.chalg.wasm"); } static std::vector chalg_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.chalg/sysio.chalg.abi"); } static std::vector councl_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.councl/sysio.councl.wasm"); } static std::vector councl_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.councl/sysio.councl.abi"); } - static std::vector reserve_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.reserv/sysio.reserv.wasm"); } - static std::vector reserve_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.reserv/sysio.reserv.abi"); } static std::vector dclaim_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.dclaim/sysio.dclaim.wasm"); } static std::vector dclaim_abi() { return read_abi("${CMAKE_BINARY_DIR}/contracts/sysio.dclaim/sysio.dclaim.abi"); } static std::vector chains_wasm() { return read_wasm("${CMAKE_BINARY_DIR}/contracts/sysio.chains/sysio.chains.wasm"); } diff --git a/contracts/tests/depot_flow_coverage.json b/contracts/tests/depot_flow_coverage.json index d1e7bf2e19..dd2ab96db0 100644 --- a/contracts/tests/depot_flow_coverage.json +++ b/contracts/tests/depot_flow_coverage.json @@ -1,6 +1,6 @@ { - "tools_branch": "feature/syndication-underwriting", - "tools_head": "686f09cdaf0463873a4d6c80c72bd47cc96a0f1d", + "tools_branch": "feature/deprecated-cleanup", + "tools_head": "ed871bd7fa072ec273b896e0ab247072427cd37d", "flows": [ { "name": "flow-batch-operator-slashing", diff --git a/contracts/tests/emissions_tests.cpp b/contracts/tests/emissions_tests.cpp index 8184971450..186f3cf4cc 100644 --- a/contracts/tests/emissions_tests.cpp +++ b/contracts/tests/emissions_tests.cpp @@ -38,6 +38,7 @@ #include #include "finalizer_test_keys.hpp" +#include "contract_test_support.hpp" #include #include @@ -59,7 +60,6 @@ static constexpr account_name OPREG = "sysio.opreg"_n; static constexpr account_name EPOCH = "sysio.epoch"_n; static constexpr account_name CHALG = "sysio.chalg"_n; static constexpr account_name MSGCH = "sysio.msgch"_n; -static constexpr account_name UWRIT = "sysio.uwrit"_n; // Keep these in sync with contracts/sysio.system/src/emissions.cpp static constexpr uint32_t SECONDS_PER_MONTH = 30u * 24u * 60u * 60u; @@ -356,16 +356,16 @@ class sysio_emissions_tester : public tester { // // Under ROA (active via the base tester), accounts need explicit ROA // RAM policies before set_code can succeed for a large contract. - create_accounts({ OPREG, EPOCH, CHALG, MSGCH, UWRIT }); + create_accounts({ OPREG, EPOCH, CHALG, MSGCH }); produce_blocks(1); // OPREG, EPOCH and UWRIT 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. - for (auto acct : { OPREG, EPOCH, CHALG, MSGCH, UWRIT }) { + for (auto acct : { OPREG, EPOCH, CHALG, MSGCH }) { if (get_roa_policy(acct, "nodedaddy"_n).is_null()) { - const bool has_code = acct == OPREG || acct == EPOCH || acct == UWRIT; + const bool has_code = acct == OPREG || acct == EPOCH; auto tr = addpolicy_ram_only("nodedaddy"_n, acct, asset::from_string(has_code ? "500.0000 SYS" : "100.0000 SYS")); BOOST_REQUIRE( tr ); @@ -382,12 +382,6 @@ class sysio_emissions_tester : public tester { set_abi ( EPOCH, contracts::epoch_abi().data() ); set_privileged( EPOCH ); - // sysio.epoch::advance unconditionally inlines sysio.uwrit::chklocks, - // so uwrit must be deployed for advance_epoch_state() to succeed. - set_code( UWRIT, contracts::uwrit_wasm() ); - set_abi ( UWRIT, contracts::uwrit_abi().data() ); - set_privileged( UWRIT ); - produce_blocks(1); { @@ -547,95 +541,6 @@ class sysio_emissions_tester : public tester { produce_blocks(1); } - /// Deploy the real sysio.reserv contract (privileged) so the swap-fee - /// fold-in test can seed its rewards bucket via a swap. Mirrors - /// deploy_dclaim_for_signing's account + ROA-policy + code pattern. - void deploy_reserv() { - const account_name RESERV = "sysio.reserv"_n; - if (!control->db().find(RESERV)) { - create_accounts({ RESERV }); - produce_blocks(1); - } - if (get_roa_policy(RESERV, "nodedaddy"_n).is_null()) { - auto tr = addpolicy_ram_only("nodedaddy"_n, RESERV, asset::from_string("500.0000 SYS")); - BOOST_REQUIRE( tr ); - BOOST_REQUIRE( !tr->except ); - produce_blocks(1); - } - set_code( RESERV, contracts::reserve_wasm() ); - set_abi ( RESERV, contracts::reserve_abi().data() ); - set_privileged( RESERV ); - produce_blocks(1); - } - - /// Current balance of sysio.reserv's batch-operator rewards bucket. - /// - /// Requires deploy_reserv(). A missing bucket is reported as zero. - int64_t reserv_reward_balance() { - const account_name RESERV = "sysio.reserv"_n; - auto data = get_row_by_account(RESERV, RESERV, "rewardbkt"_n, "rewardbkt"_n); - if (data.empty()) return 0; - - const auto* meta = control->find_account_metadata(RESERV); - BOOST_REQUIRE(meta != nullptr); - abi_def def; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(meta->abi, def), true); - abi_serializer reserv_ser; - reserv_ser.set_abi(def, abi_serializer::create_yield_function(abi_serializer_max_time)); - auto bucket = reserv_ser.binary_to_variant( - "rewards_bucket", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - return static_cast(bucket["balance"].as_uint64()); - } - - /// Deploy sysio.reserv and seed its batch-operator rewards bucket with a - /// real bootstrap-window swap fee, returning the exact accrued balance. - int64_t seed_reserv_reward_bucket() { - const account_name RESERV = "sysio.reserv"_n; - const account_name UWRIT = "sysio.uwrit"_n; - deploy_reserv(); - - BOOST_REQUIRE_EQUAL(success(), push_reserv_action(RESERV, "regreserve"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH")("reserve_code", "PRIMARY") - ("name", "eth")("description", "") - ("initial_chain_amount", 1'000'000'000'000ULL)("initial_wire_amount", 1'000'000'000'000ULL) - ("source_token_precision", 9u)("connector_weight_bps", 5000u)("is_private", false)("owner", name{}))); - BOOST_REQUIRE_EQUAL(success(), push_reserv_action(RESERV, "regreserve"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL")("reserve_code", "PRIMARY") - ("name", "sol")("description", "") - ("initial_chain_amount", 1'000'000'000'000ULL)("initial_wire_amount", 1'000'000'000'000ULL) - ("source_token_precision", 9u)("connector_weight_bps", 5000u)("is_private", false)("owner", name{}))); - BOOST_REQUIRE_EQUAL(success(), push_reserv_action(UWRIT, "applyswap"_n, mvo() - ("src_chain_code", "ETH")("src_token_code", "ETH")("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA")("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL)("underwriter", name{}))); - - const int64_t balance = reserv_reward_balance(); - BOOST_REQUIRE_GT(balance, 0); - return balance; - } - - /// `sysio.reserv::wireclaims` balance owed to `acc`, or 0 when there is no row. - /// Requires deploy_reserv(). Credited by paywire / refundwire, drained by claimwire, and - /// reclaimed to the treasury by the retention sweep sysio.epoch::advance inlines. - uint64_t wire_claimable( account_name acc ) { - const account_name RESERV = "sysio.reserv"_n; - auto data = get_row_by_account(RESERV, RESERV, "wireclaims"_n, acc); - if (data.empty()) return 0u; - - const auto* meta = control->find_account_metadata( RESERV ); - BOOST_REQUIRE( meta != nullptr ); - abi_def def; - BOOST_REQUIRE_EQUAL( abi_serializer::to_abi(meta->abi, def), true ); - abi_serializer reserv_ser; - reserv_ser.set_abi( def, abi_serializer::create_yield_function(abi_serializer_max_time) ); - - return reserv_ser.binary_to_variant("wire_claim", data, - abi_serializer::create_yield_function(abi_serializer_max_time))["balance"].as_uint64(); - } - // ----------------------------- // sysio.system action helpers // ----------------------------- @@ -1435,17 +1340,6 @@ class sysio_emissions_tester : public tester { return push_epoch_action(signer, "advance"_n, mvo()); } - // Push an action against the (separately deployed) sysio.reserv contract. - // Only the swap-fee fold-in test deploys reserv and uses this. - action_result push_reserv_action(account_name signer, action_name act, const variant_object& data) { - try { - base_tester::push_action("sysio.reserv"_n, act, signer, data); - return success(); - } catch (const fc::exception& ex) { - return error(ex.top_message()); - } - } - // Convenience: set epoch config only. Genesis advance is deferred so each // test can decide whether to initt5 (and thus pass the emissions gate) or // exercise gate-block behavior. Under the new model, the first @@ -2684,7 +2578,6 @@ BOOST_FIXTURE_TEST_CASE( payepoch_recovers_from_incomplete_batch_roster_history, const account_name BATCH_OP = "batchopa"_n; constexpr int64_t period_emission = 10'000; create_t5_holding_accounts(); - const int64_t fee_total = seed_reserv_reward_bucket(); BOOST_REQUIRE_EQUAL(success(), setemitcfg_defaults(config::system_account_name)); BOOST_REQUIRE_EQUAL(success(), initt5(config::system_account_name, tpsec(head_secs()))); BOOST_REQUIRE_EQUAL(success(), @@ -2710,7 +2603,6 @@ BOOST_FIXTURE_TEST_CASE( payepoch_recovers_from_incomplete_batch_roster_history, 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()); - BOOST_REQUIRE_EQUAL(fee_total, reserv_reward_balance()); // 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 @@ -2732,8 +2624,7 @@ BOOST_FIXTURE_TEST_CASE( payepoch_recovers_from_incomplete_batch_roster_history, auto recovery_log = get_epoch_log(2); BOOST_REQUIRE(recovery_log["batch_history_complete"].as_bool()); - BOOST_REQUIRE_EQUAL(fee_total, recovery_log["fee_distributed"].as()); - BOOST_REQUIRE_EQUAL(int64_t(0), reserv_reward_balance()); + BOOST_REQUIRE_EQUAL(compute - producer_pool, get_wire_balance_paid(BATCH_OP).get_amount()); } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( payepoch_seeds_initial_roster_history_at_activation_epoch, sysio_emissions_tester ) try { @@ -4292,148 +4183,6 @@ BOOST_FIXTURE_TEST_CASE( single_active_producer_paid_per_block, sysio_emissions_ BOOST_REQUIRE_LT( got, test_active_pool(compute) ); } FC_LOG_AND_RETHROW() -// Swap-fee rewards (sysio.reserv rewards_bucket) are folded into payepoch's -// BATCH-OPERATOR distribution only: batch ops receive them on top of their -// emission share, and producers receive none of them (producer_bps / -// batch_op_bps govern the emission split alone). End-to-end: deploy reserv, -// seed the bucket with a real swap fee, advance to the (cadence-1) pay-epoch, -// and verify the single producer is paid its emission producer_pool and NOTHING -// more, the all-empty roster leaves the bucket in reserv, and the fee is NOT -// counted against the emission treasury. -BOOST_FIXTURE_TEST_CASE( payepoch_folds_swap_fee_rewards, sysio_emissions_tester ) try { - create_t5_holding_accounts(); - const int64_t fee_total = seed_reserv_reward_bucket(); - - // --- Single full-round producer; advance to the cadence-1 pay-epoch --- - setup_producers(1); - wait_for_producer_schedule(); - produce_complete_cycles(1, 2); - - const uint32_t start = head_secs() - ONE_EPOCH - 1; - BOOST_REQUIRE_EQUAL( success(), initt5( config::system_account_name, tpsec(start) ) ); - - const int64_t t5_before = get_t5_state()["total_distributed"].as(); - const uint32_t blocks = unpaid_blocks_of("producera"_n); - const int64_t bal_before = get_wire_balance_paid("producera"_n).get_amount(); - - // Must NOT overdraw: payepoch queues the reserv->sysio drain ahead of the - // payout transfers, so the swept fee lands in sysio's balance first. - BOOST_REQUIRE_EQUAL( success(), advance_epoch_state() ); - - const int64_t got = get_wire_balance_paid("producera"_n).get_amount() - bal_before; - - auto log = get_epoch_log(1); - const int64_t compute = log["compute_amount"].as(); - const int64_t capex = log["capex_amount"].as(); - const int64_t gov = log["governance_amount"].as(); - const int64_t producer_pool = test_split_bps(compute, PRODUCER_BPS); - const int64_t batch_pool = compute - producer_pool; - - // The producer received its emission share and NOTHING MORE. Swap fees pay - // the parties that carry an individual swap — the winning underwriter and the - // batch operators that relay it — never producers, who earn emissions for - // securing the chain. (The share is the per-block rate times its blocks; the - // slots it did not fill and the standby slice stay in the treasury.) - BOOST_REQUIRE_EQUAL( got, test_block_pay(test_active_pool(compute), blocks, - test_nominal_slots(T_EPOCH_SECS)) ); - BOOST_REQUIRE_LT( got, producer_pool ); - - // Nothing was distributed out of the fee: this fixture has no non-empty - // rotation group, so the bucket stays in reserv for a future payable period. - // This is the NEGATIVE case — a batch operator actually receiving the fee is - // `payepoch_pays_swap_fee_to_active_batch_operator` below. - BOOST_REQUIRE_EQUAL( log["fee_distributed"].as(), 0 ); - BOOST_REQUIRE(log["batch_history_complete"].as_bool()); - BOOST_REQUIRE_EQUAL(log["batch_emission_retained"].as(), batch_pool); - BOOST_REQUIRE_EQUAL(log["batch_fee_retained"].as(), int64_t(0)); - - BOOST_REQUIRE_EQUAL( reserv_reward_balance(), fee_total ); - - // total_distributed counts emission only (the producer's block pay + capex + - // gov, with the unfilled slots, the standby slice and the empty batch group's - // share staying in treasury) -- the fee is NOT charged against the emission - // curve. - const int64_t t5_after = get_t5_state()["total_distributed"].as(); - BOOST_REQUIRE_EQUAL( t5_after - t5_before, got + capex + gov ); -} FC_LOG_AND_RETHROW() - -// The POSITIVE counterpart: a swap fee actually reaching an ACTIVE batch -// operator's balance. `payepoch_folds_swap_fee_rewards` proves that an all-empty -// history preserves reserv custody; this positive case proves a non-empty, -// eligible roster drains and distributes the bucket. -// -// Scaled to a ONE-member rotation (operators_per_epoch = batch_op_groups = 1) so -// the arithmetic is exact rather than a proportional bound: with one group active -// for the single epoch of a cadence-1 period, that member's slice is the entire -// batch pool and the entire fee pool. Asserts the recipient's balance delta and -// the exact positive `epochlog.fee_distributed`. -BOOST_FIXTURE_TEST_CASE( payepoch_pays_swap_fee_to_active_batch_operator, sysio_emissions_tester ) try { - const account_name BATCH_OP = "batchopa"_n; - - create_t5_holding_accounts(); - const int64_t fee_total = seed_reserv_reward_bucket(); - - // --- A one-member rotation group, ACTIVE in opreg --- - // Bootstrapped so the ACTIVE flip bypasses the collateral gate (see - // .claude/rules/bootstrapped-operator-invariants.md); payepoch's own filter is - // `is_op_active(member, OPERATOR_TYPE_BATCH)`, which this satisfies. - create_accounts( { BATCH_OP }, false, false, false, true ); // include_ram_gift - BOOST_REQUIRE_EQUAL( success(), - register_operator( BATCH_OP, OperatorType::OPERATOR_TYPE_BATCH, /*is_bootstrapped*/true ) ); - - // operators_per_epoch = batch_op_groups = 1 -> batch_operator_minimum_active - // is 1, so this single operator satisfies schbatchgps and fills the whole - // window: one group, one member, paid every epoch. - BOOST_REQUIRE_EQUAL( success(), init_epoch_state(60, /*operators_per_epoch*/1, - /*batch_op_groups_count*/1) ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( success(), push_epoch_action(EPOCH, "schbatchgps"_n, mvo()) ); - - setup_producers(1); - wait_for_producer_schedule(); - produce_complete_cycles(1, 2); - - const uint32_t start = head_secs() - ONE_EPOCH - 1; - BOOST_REQUIRE_EQUAL( success(), initt5( config::system_account_name, tpsec(start) ) ); - - const int64_t t5_before = get_t5_state()["total_distributed"].as(); - const uint32_t producer_blocks = unpaid_blocks_of("producera"_n); - const int64_t bal_before = get_wire_balance(BATCH_OP).get_amount(); - - BOOST_REQUIRE_EQUAL( success(), advance_epoch_state() ); - - // --- The payout --- - auto log = get_epoch_log(1); - const int64_t compute = log["compute_amount"].as(); - const int64_t producer_pool = test_split_bps(compute, PRODUCER_BPS); - const int64_t batch_pool = compute - producer_pool; - - // One group, active for the single epoch of a cadence-1 period, one member: - // the member's slice is the whole batch pool AND the whole fee pool. The - // emission and fee shares ride ONE credit, so the balance delta after the - // claim is the sum. - const int64_t got = get_wire_balance_paid(BATCH_OP).get_amount() - bal_before; - BOOST_REQUIRE_EQUAL( got, batch_pool + fee_total ); - - // The fee reached a real recipient — the assertion the negative case cannot - // make. Exact value, not merely positive: a fee that leaked into the producer - // pool or was double-counted would still be > 0 here. - BOOST_REQUIRE_EQUAL( log["fee_distributed"].as(), fee_total ); - BOOST_REQUIRE(log["batch_history_complete"].as_bool()); - BOOST_REQUIRE_EQUAL(log["batch_emission_retained"].as(), int64_t(0)); - BOOST_REQUIRE_EQUAL(log["batch_fee_retained"].as(), int64_t(0)); - - // Bucket swept, and the fee is NOT charged against the emission curve — - // total_distributed moves by the EMISSION only, excluding fee_total. - BOOST_REQUIRE_EQUAL( reserv_reward_balance(), 0 ); - const int64_t capex = log["capex_amount"].as(); - const int64_t gov = log["governance_amount"].as(); - const int64_t t5_after = get_t5_state()["total_distributed"].as(); - const int64_t producer_pay = test_block_pay(test_active_pool(compute), producer_blocks, - test_nominal_slots(T_EPOCH_SECS)); - BOOST_REQUIRE_EQUAL( t5_after - t5_before, producer_pay + batch_pool + capex + gov ); -} FC_LOG_AND_RETHROW() - // Lowering pay_cadence_epochs MID-PERIOD must not multiply the payout. // `accrueepoch` increments one batch_group_epochs slot per epoch unconditionally, // while `setemitcfg` may change pay_cadence_epochs at any time. Normalizing by @@ -4447,12 +4196,11 @@ BOOST_FIXTURE_TEST_CASE( payepoch_pays_swap_fee_to_active_batch_operator, sysio_ // 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. -BOOST_FIXTURE_TEST_CASE( cadence_drop_midperiod_does_not_multiply_batch_fee_payout, +BOOST_FIXTURE_TEST_CASE( cadence_drop_midperiod_does_not_multiply_batch_payout, sysio_emissions_tester ) try { const account_name BATCH_OP = "batchopb"_n; create_t5_holding_accounts(); - const int64_t fee_total = seed_reserv_reward_bucket(); create_accounts( { BATCH_OP }, false, false, false, true ); BOOST_REQUIRE_EQUAL( success(), @@ -4492,12 +4240,11 @@ BOOST_FIXTURE_TEST_CASE( cadence_drop_midperiod_does_not_multiply_batch_fee_payo // The single group holds BOTH accrued epochs, so normalizing by the accrued // total (2) gives it the whole pool exactly once -- not twice. const int64_t got = get_wire_balance_paid(BATCH_OP).get_amount() - bal_before; - BOOST_REQUIRE_EQUAL( got, batch_pool + fee_total ); + 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(), fee_total ); - BOOST_REQUIRE_EQUAL( reserv_reward_balance(), 0 ); + 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. @@ -5028,121 +4775,6 @@ BOOST_FIXTURE_TEST_CASE( advance_gate_blocks_on_insufficient_treasury_balance, s BOOST_REQUIRE_EQUAL( get_t5_state()["last_epoch_index"].as(), 0u ); } FC_LOG_AND_RETHROW() -// A balance-blocked epoch reclaims forfeited WIRE and unblocks ITSELF, with no manual sweep and -// no top-up. This pins the placement of the `sysio.reserv::sweepclaims` inline in advance: it sits -// BEFORE the emissions gate, so a BALANCE_INSUFFICIENT epoch still queues the reclaim on its way -// out. Move that call below the gate (where the other maintenance sweeps live) and the epoch -// returns without ever reclaiming the WIRE that covers its own shortfall -- every retry repeating -// it, with `sweepclaims` taking only epoch/reserv authority so no keeper can break the cycle. -// -// The two-attempt shape is the guarantee, not an artifact: `action.send()` QUEUES the inline, so -// this advance's gate has already read the treasury by the time the reclaim executes. The first -// attempt therefore records the block AND performs the reclaim; the next chkcons retry sees the -// larger balance and advances. -BOOST_FIXTURE_TEST_CASE( expired_wire_claims_unblock_a_balance_blocked_epoch, sysio_emissions_tester ) try { - const account_name RESERV = "sysio.reserv"_n; - - create_t5_holding_accounts(); - deploy_reserv(); - - // Move real WIRE into reserv custody so a claim has backing. regreserve is bootstrap-window - // only, which holds here: current_epoch_index is still 0. - constexpr uint64_t RESERVE_SEED = 1'000'000'000'000ULL; - BOOST_REQUIRE_EQUAL( success(), push_reserv_action(RESERV, "regreserve"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH")("reserve_code", "PRIMARY") - ("name", "eth")("description", "") - ("initial_chain_amount", RESERVE_SEED)("initial_wire_amount", RESERVE_SEED) - ("source_token_precision", 9u)("connector_weight_bps", 5000u)("is_private", false)("owner", name{}) ) ); - - // Fund the escrow the refund gives back, SEPARATELY from the reserve's booked liquidity. - // `regreserve` books RESERVE_SEED into `reserve_wire_amount`, and `refundwire` credits a claim - // without debiting that row — in production it is reached only after `swapfromwire` has already - // deposited the user's in-flight escrow on top. Skipping that deposit would make the sweep hand - // the treasury registered reserve liquidity instead of forfeited escrow, so the test would - // unblock emissions by breaking reserv's custody invariant rather than by reclaiming a claim. - constexpr uint64_t FORFEIT = 100'000'000'000ULL; - base_tester::push_action( - TOKEN, "transfer"_n, - vector{{ config::system_account_name, "active"_n }}, - mvo()("from", config::system_account_name) - ("to", RESERV) - ("quantity", asset(static_cast(FORFEIT), WIRE_SYMBOL)) - ("memo", "in-flight swap-from-WIRE escrow the refund returns") - ); - produce_blocks(1); - BOOST_REQUIRE_EQUAL( RESERVE_SEED + FORFEIT, - static_cast(get_wire_balance(RESERV).get_amount()) ); - - // A swap-from-WIRE refund credits a claimable balance that nobody ever pulls. Custody now - // reads `reserve_wire_amount (RESERVE_SEED) + Σ wireclaims (FORFEIT)`. - create_user_accounts({ "lapseduser"_n }); - BOOST_REQUIRE_EQUAL( success(), push_reserv_action(UWRIT, "refundwire"_n, mvo() - ("recipient", "lapseduser") - ("wire_amount", FORFEIT) - ("revert_fee_bps", 0)) ); - BOOST_REQUIRE_EQUAL( FORFEIT, wire_claimable("lapseduser"_n) ); - - // Age past the one-year window with NO further credit, so `credit_wire_claim`'s opportunistic - // sweep never fires and the epoch-driven one is the only thing that can collect the row. - produce_block(); - produce_block(fc::days(366)); - produce_blocks(2); - BOOST_REQUIRE_EQUAL( FORFEIT, wire_claimable("lapseduser"_n) ); - - const uint32_t start = head_secs() - ONE_EPOCH - 1; - BOOST_REQUIRE_EQUAL( success(), initt5( config::system_account_name, tpsec(start) ) ); - - // Epoch 1's emission is the annual initial scaled to the fixture's 60s epoch; it is also the - // period total, since nothing has accrued yet. - const int64_t period_emission = test_scale_annual_to_epoch(ANNUAL_INITIAL_EMISSION, 60); - BOOST_REQUIRE_GT( period_emission, static_cast(FORFEIT) ); - - // Leave the treasury short by EXACTLY the forfeited claim. - create_user_accounts({ "lapsedrain"_n }); - const int64_t balance = get_wire_balance(config::system_account_name).get_amount(); - const int64_t target = period_emission - static_cast(FORFEIT); - BOOST_REQUIRE_GT( balance, target ); - base_tester::push_action( - TOKEN, "transfer"_n, - vector{{ config::system_account_name, "active"_n }}, - mvo()("from", config::system_account_name) - ("to", "lapsedrain"_n) - ("quantity", asset(balance - target, WIRE_SYMBOL)) - ("memo", "leave the treasury one forfeited claim short of the epoch emission") - ); - produce_blocks(1); - - // FIRST attempt: blocked on the balance the gate could see, and the queued reclaim still runs. - produce_blocks(130); - BOOST_REQUIRE_EQUAL( success(), advance_epoch_state(EPOCH) ); - - auto blocked = get_blocklog_row(1u); - BOOST_REQUIRE( !blocked.is_null() ); - BOOST_REQUIRE_EQUAL( blocked["reason"].as_string(), "EMISSIONS_BLOCK_REASON_BALANCE_INSUFFICIENT" ); - BOOST_REQUIRE_EQUAL( blocked["attempted_emission"].as(), period_emission ); - BOOST_REQUIRE_EQUAL( get_t5_state()["last_epoch_index"].as(), 0u ); - - // The forfeited claim is gone and its WIRE is in the treasury -- reclaimed by the same - // transaction that recorded the block. - BOOST_REQUIRE_EQUAL( 0u, wire_claimable("lapseduser"_n) ); - BOOST_REQUIRE_EQUAL( period_emission, get_wire_balance(config::system_account_name).get_amount() ); - - // What moved was the ESCROW, not the reserve. Custody is back to exactly the booked - // `reserve_wire_amount`, so the epoch unblocked itself on forfeited value rather than on - // registered liquidity. - BOOST_REQUIRE_EQUAL( RESERVE_SEED, static_cast(get_wire_balance(RESERV).get_amount()) ); - - // SECOND attempt: no manual sweepclaims, no funding -- the epoch advances on its own. - produce_blocks(130); - BOOST_REQUIRE_EQUAL( success(), advance_epoch_state(EPOCH) ); - - auto est = get_epoch_state_row(); - BOOST_REQUIRE( !est.is_null() ); - BOOST_REQUIRE_EQUAL( est["current_epoch_index"].as(), 1u ); - BOOST_REQUIRE( get_blocklog_row(1u).is_null() ); - BOOST_REQUIRE_EQUAL( get_t5_state()["last_epoch_index"].as(), 1u ); -} FC_LOG_AND_RETHROW() - BOOST_FIXTURE_TEST_CASE( roa_forcereg_inlines_addnodeowner_happy_path, sysio_emissions_tester ) try { // Happy path: with emitcfg present (fixture default), sysio.roa::forcereg // fires an inline sysio.system::addnodeowner which writes a nodedist row. @@ -6049,12 +5681,12 @@ struct producer_score_tester : public producer_eligibility_tester { /// Chain/token pair the collateral helpers use. Any pair works -- opreg stores slug names /// opaquely -- so these name a plausible outpost rather than carrying meaning. - static constexpr std::string_view collateral_chain = "ETH"; - static constexpr std::string_view collateral_token = "ETH"; + static constexpr std::string_view collateral_chain = "WIRE"; + static constexpr std::string_view collateral_token = "WIRE"; /// A second pair, for the "minimum across pairs" case. - static constexpr std::string_view second_chain = "SOL"; - static constexpr std::string_view second_token = "SOL"; + static constexpr std::string_view second_chain = "WIRE"; + static constexpr std::string_view second_token = "NTA"; /// The minimum bond every collateral test measures its ratios against. static constexpr uint64_t base_min_bond = 1'000'000; @@ -6109,17 +5741,65 @@ struct producer_score_tester : public producer_eligibility_tester { /// This is the seam the score hangs off: a credit runs `reevaluate_eligibility`, which /// dispatches `processprod` for producers on EVERY balance change, whose notification /// sysio.system turns into a rescore. + bool native_shadow_ready = false; + + void setup_score_shadow() { + if (native_shadow_ready) return; + const auto chains = "sysio.chains"_n; + const auto tokens = "sysio.tokens"_n; + const auto synd = "sysio.synd"_n; + const auto liq = "sysio.liq"_n; + for (auto account : {chains, tokens, synd}) { + if (!control->db().find(account)) { + create_accounts({account}, false, false, false, true); + produce_blocks(1); + } + if (account != synd && get_roa_policy(account, "nodedaddy"_n).is_null()) { + const auto trace = addpolicy_ram_only("nodedaddy"_n, account, asset::from_string("100.0000 SYS")); + BOOST_REQUIRE(trace && !trace->except); + produce_blocks(1); + } + } + set_code(chains, contracts::chains_wasm()); + set_abi(chains, contracts::chains_abi().data()); + set_privileged(chains); + set_code(tokens, contracts::tokens_wasm()); + set_abi(tokens, contracts::tokens_abi().data()); + set_privileged(tokens); + deploy_liq_for_signing(); + const std::vector address(20, '\x31'); + base_tester::push_action(chains, "regchain"_n, chains, + mvo()("kind", ChainKind::CHAIN_KIND_EVM)("code", "EC1")("external_chain_id", 1) + ("name", "External Chain 1")("description", "") + ("outpost", sysio_system::test_support::no_outpost_mvo())); + base_tester::push_action(tokens, "regtoken"_n, tokens, + mvo()("kind", TokenKind::TOKEN_KIND_LIQ)("code", "NTA")("symbol_name", "NTA") + ("description", "Native Token 1")("precision", 9) + ("address", mvo()("kind", ChainKind::CHAIN_KIND_EVM)("address", address))); + base_tester::push_action(tokens, "regctok"_n, tokens, + mvo()("chain_code", "EC1")("token_code", "NTA")("contract_addr", address)("is_native", false)); + base_tester::push_action(liq, "create"_n, liq, + mvo()("sym", "9,NTA")("chain_code", "EC1")("token_code", "NTA")); + native_shadow_ready = true; + } + + /// Fund and bond real depot-native collateral, driving the production rescore notification. action_result credit_collateral(account_name account, uint64_t amount, std::string_view chain = collateral_chain, std::string_view token = collateral_token) { - return push_opreg_action(OPREG, "depositinle"_n, mvo() - ("account", account) - ("chain_code", chain) - ("token_code", token) - ("amount", amount) - ("actor_chain", ChainKind::CHAIN_KIND_EVM) - ("actor_address", std::vector(20, '\x06')) - ("original_message_id", fc::sha256())); + BOOST_REQUIRE_EQUAL(chain, "WIRE"); + if (token == "WIRE") { + BOOST_REQUIRE_EQUAL(success(), push_token_action(config::system_account_name, "transfer"_n, + mvo()("from", "sysio")("to", account) + ("quantity", asset(static_cast(amount), WIRE_SYMBOL))("memo", "bond funding"))); + } else { + BOOST_REQUIRE_EQUAL(token, "NTA"); + setup_score_shadow(); + base_tester::push_action("sysio.liq"_n, "mint"_n, "sysio.synd"_n, + mvo()("to", account)("token_code", token)("amount", amount)); + } + return push_opreg_action(account, "deposit"_n, + mvo()("account", account)("token_code", token)("amount", amount)); } /// Push `setscorecfg`. Defaults mirror the contract's own so a test names only the weight it diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 10e9570ae4..20f3c5fa07 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -23,8 +23,6 @@ // 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 #include #include @@ -72,37 +70,6 @@ constexpr uint32_t PROTOBUF_FIELD_TAG_SHIFT = 3u; /// or above any outstanding shadow a dispatch test can reach. constexpr uint64_t CUSTODY_COVERING_ANY_SUPPLY = static_cast(sysio::chain::asset::max_amount); -/** Append one unsigned protobuf varint to a hostile-wire-format fixture. */ -void append_proto_varint(std::vector& out, uint64_t value) { - do { - uint8_t byte = static_cast(value & PROTOBUF_VARINT_PAYLOAD_MASK); - value >>= PROTOBUF_VARINT_PAYLOAD_BITS; - if (value != 0) byte |= PROTOBUF_VARINT_CONTINUATION_BIT; - out.push_back(static_cast(byte)); - } while (value != 0); -} - -/** Append one complete protobuf varint field as an explicit key/value pair. */ -void append_varint_field(std::vector& out, uint32_t field_number, - uint64_t value) { - constexpr uint32_t VARINT_WIRE_TYPE = 0; - append_proto_varint(out, (static_cast(field_number) - << PROTOBUF_FIELD_TAG_SHIFT) - | VARINT_WIRE_TYPE); - append_proto_varint(out, value); -} - -/** Append one length-delimited protobuf field without a generated schema. */ -void append_length_delimited_field(std::vector& out, uint32_t field_number, - std::span payload) { - constexpr uint32_t LENGTH_DELIMITED_WIRE_TYPE = 2; - append_proto_varint(out, (static_cast(field_number) - << PROTOBUF_FIELD_TAG_SHIFT) - | LENGTH_DELIMITED_WIRE_TYPE); - append_proto_varint(out, payload.size()); - out.insert(out.end(), payload.begin(), payload.end()); -} - /// 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, @@ -278,42 +245,6 @@ std::string encode_node_owner_registration( return out; } -/// Encode a SwapRequest attestation payload — the bytes `createuwreq` decodes. -std::string encode_swap_request( - sysio::opp::types::ChainKind actor_kind, - const std::vector& actor_addr, - uint64_t src_chain_code_v, uint64_t src_token_code_v, uint64_t src_reserve_code_v, - int64_t src_amount, - uint64_t dst_chain_code_v, uint64_t dst_token_code_v, uint64_t dst_reserve_code_v, - uint64_t target_amount, uint32_t tolerance_bps, - sysio::opp::types::ChainKind recipient_kind, - const std::vector& recipient_addr) -{ - sysio::opp::attestations::SwapRequest sr; - auto* actor = sr.mutable_actor(); - actor->set_kind(actor_kind); - actor->set_address(actor_addr.data(), actor_addr.size()); - auto* amt = sr.mutable_source_amount(); - amt->set_token_code(src_token_code_v); - amt->set_amount(src_amount); - sr.set_source_chain_code(src_chain_code_v); - sr.set_source_reserve_code(src_reserve_code_v); - sr.set_target_chain_code(dst_chain_code_v); - sr.set_target_token_code(dst_token_code_v); - sr.set_target_reserve_code(dst_reserve_code_v); - sr.set_target_amount(target_amount); - sr.set_target_tolerance_bps(tolerance_bps); - auto* rcpt = sr.mutable_recipient(); - rcpt->set_kind(recipient_kind); - rcpt->set_address(recipient_addr.data(), recipient_addr.size()); - const std::vector tx_id{'\x01', '\x02', '\x03', '\x04'}; - sr.set_source_tx_id(tx_id.data(), tx_id.size()); - - std::string out; - sr.SerializeToString(&out); - return out; -} - /// Encode a SyndicateLIQ attestation payload: the emitting outpost, the syndicating user's /// native pubkey (kind + bytes), the liq TokenAmount, the per-outpost sequence and the outpost's /// live custody total. @@ -396,20 +327,9 @@ class sysio_dispatch_tester : public tester { public: static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; static constexpr auto OPREG_ACCOUNT = "sysio.opreg"_n; - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; static constexpr auto EPOCH_ACCOUNT = "sysio.epoch"_n; - static constexpr auto RESERV_ACCOUNT = "sysio.reserv"_n; static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; - /// `sysio.uwrit::uw_config::fee_bps`'s default (0.1% per spoke) — the rate in - /// force whenever a case does not push its own `uwrit::setconfig`. Tests that - /// re-derive a swap quote must charge the same fee the contract did. - static constexpr uint32_t kDefaultUwritFeeBps = 10; - - /// Mirrors `sysio.uwrit::MAX_LOCK_RELEASE_PER_EPOCH` — the per-epoch `chklocks` budget - /// `sysio.epoch::advance` passes. Tests that sweep "as production would" use this; tests - /// exercising the bound itself pass their own smaller value. - static constexpr uint32_t kMaxLockReleasePerEpoch = 32; static constexpr auto TOKEN_ACCOUNT = "sysio.token"_n; static constexpr auto AUTHEX_ACCOUNT = "sysio.authex"_n; static constexpr auto DCLAIM_ACCOUNT = "sysio.dclaim"_n; @@ -427,8 +347,8 @@ class sysio_dispatch_tester : public tester { produce_blocks(2); create_accounts({ - MSGCH_ACCOUNT, OPREG_ACCOUNT, UWRIT_ACCOUNT, EPOCH_ACCOUNT, - RESERV_ACCOUNT, CHALG_ACCOUNT, TOKEN_ACCOUNT, CHAINS_ACCOUNT, + MSGCH_ACCOUNT, OPREG_ACCOUNT, EPOCH_ACCOUNT, + CHALG_ACCOUNT, TOKEN_ACCOUNT, CHAINS_ACCOUNT, DCLAIM_ACCOUNT, BATCHOP, UWRIT_OP }); // CLAIM_ACCOUNT with NO roa policy (include_roa_policy=false) so regnodeowner exercises the @@ -442,9 +362,7 @@ class sysio_dispatch_tester : public tester { deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi); deploy(OPREG_ACCOUNT, contracts::opreg_wasm(), contracts::opreg_abi(), opreg_abi); - deploy(UWRIT_ACCOUNT, contracts::uwrit_wasm(), contracts::uwrit_abi(), uwrit_abi); deploy(EPOCH_ACCOUNT, contracts::epoch_wasm(), contracts::epoch_abi(), epoch_abi); - deploy(RESERV_ACCOUNT, contracts::reserve_wasm(), contracts::reserve_abi(), reserv_abi); deploy(AUTHEX_ACCOUNT, contracts::authex_wasm(), contracts::authex_abi(), authex_abi); deploy(DCLAIM_ACCOUNT, contracts::dclaim_wasm(), contracts::dclaim_abi(), dclaim_abi); deploy(CHAINS_ACCOUNT, contracts::chains_wasm(), contracts::chains_abi(), chains_abi); @@ -689,22 +607,6 @@ class sysio_dispatch_tester : public tester { return fc::variant(); } - /// Find an operator's balance entry for a (chain_code, token_code) pair. - fc::variant find_balance(const fc::variant& op, - std::string_view chain_code, - std::string_view token_code) { - const auto chain_v = fc::slug_name{chain_code}.value; - const auto token_v = fc::slug_name{token_code}.value; - const auto& arr = op["balances"].get_array(); - for (const auto& b : arr) { - if (b["chain_code"].as().value == chain_v && - b["token_code"].as().value == token_v) { - return b; - } - } - return fc::variant(); - } - // Read sysio.roa's kv tables (scoped by network_gen). `nodeowners` proves registration; // `nodeownerreg` is the audit row (status / reject_reason). fc::variant get_nodeowner(name acc) { @@ -742,641 +644,7 @@ class sysio_dispatch_tester : public tester { type, data, abi_serializer::create_yield_function(abi_serializer_max_time)); } - // ── uwrit swap-race helpers (direct msgch-auth action calls) ── - - action_result depositinle_credit(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - // depositinle does require_auth(get_self()); sign as opreg for a direct call. - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "depositinle"_n, mvo() - ("account", account.to_string()) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount) - ("actor_chain", ChainKind::CHAIN_KIND_EVM) - ("actor_address", std::vector(20, '\x06')) - ("original_message_id", fc::sha256())); - } - - action_result createuwreq_direct(uint64_t attestation_id, uint64_t chain_code_v, - const std::string& swap_request_bytes) { - return push(UWRIT_ACCOUNT, uwrit_abi, MSGCH_ACCOUNT, "createuwreq"_n, mvo() - ("attestation_id", attestation_id) - ("type", AttestationType::ATTESTATION_TYPE_SWAP_REQUEST) - ("chain_code", chain_code_v) - ("data", std::vector(swap_request_bytes.begin(), - swap_request_bytes.end()))); - } - - action_result rcrdcommit_direct(uint64_t uwreq_id, name underwriter, - uint64_t outpost_chain_code, - std::string_view from_chain, std::string_view from_token, - std::string_view reserve, const std::vector& uic_bytes) { - return push(UWRIT_ACCOUNT, uwrit_abi, MSGCH_ACCOUNT, "rcrdcommit"_n, mvo() - ("uwreq_id", uwreq_id) - ("underwriter", underwriter.to_string()) - ("chain_code", outpost_chain_code) - ("from_chain_code", from_chain) - ("from_token_code", from_token) - ("reserve_code", reserve) - ("uic_bytes", uic_bytes)); - } - - /** Push `rcrdcommit` and retain its trace for exact rejection-log assertions. */ - transaction_trace_ptr rcrdcommit_trace( - uint64_t uwreq_id, name underwriter, uint64_t outpost_chain_code, - std::string_view from_chain, std::string_view from_token, - std::string_view reserve, const std::vector& uic_bytes) { - return push_trace(UWRIT_ACCOUNT, uwrit_abi, MSGCH_ACCOUNT, "rcrdcommit"_n, mvo() - ("uwreq_id", uwreq_id) - ("underwriter", underwriter.to_string()) - ("chain_code", outpost_chain_code) - ("from_chain_code", from_chain) - ("from_token_code", from_token) - ("reserve_code", reserve) - ("uic_bytes", uic_bytes)); - } - - /// Create the common UnderwriteIntentCommit fields used by signed, - /// malformed-signature, and malformed-account test payloads. - sysio::opp::attestations::UnderwriteIntentCommit create_uic( - std::string_view underwriter, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v) { - sysio::opp::attestations::UnderwriteIntentCommit uic; - uic.mutable_uw_account()->set_name( - std::string{underwriter.data(), underwriter.size()}); - uic.mutable_uw_ext_chain_addr()->set_kind(sysio::opp::types::CHAIN_KIND_EVM); - uic.mutable_uw_ext_chain_addr()->set_address(std::string(20, '\x01')); - uic.set_uw_request_id(uwreq_id); - uic.set_token_code(token_code_v); - uic.set_chain_code(chain_code_v); - uic.set_reserve_code(reserve_code_v); - return uic; - } - - /// Serialize one UnderwriteIntentCommit into the raw bytes relayed by an - /// outpost and stored by `sysio.uwrit::rcrdcommit`. - std::vector serialize_uic( - const sysio::opp::attestations::UnderwriteIntentCommit& uic) { - std::string full; - BOOST_REQUIRE(uic.SerializeToString(&full)); - return std::vector(full.begin(), full.end()); - } - - /// Blank, digest, sign, and serialize an already-shaped UIC. - std::vector sign_uic( - sysio::opp::attestations::UnderwriteIntentCommit uic, - const fc::crypto::private_key& signing_key) { - uic.clear_signature(); - const auto blanked = serialize_uic(uic); - const auto digest = fc::sha256::hash(blanked.data(), blanked.size()); - - const auto sig = signing_key.sign(digest); - const auto sig_bytes = fc::raw::pack(sig); - uic.set_signature(sig_bytes.data(), sig_bytes.size()); - return serialize_uic(uic); - } - - /// Create a UIC claiming `claimed_underwriter` and signed by an explicit - /// WIRE private key. Tests use this to exercise every supported fixed-size - /// signature variant and to model forged account claims. - std::vector create_signed_uic_with_key( - name claimed_underwriter, - const fc::crypto::private_key& signing_key, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v) { - auto uic = create_uic(claimed_underwriter.to_string(), uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - return sign_uic(std::move(uic), signing_key); - } - - /// Produce a valid K1-signed UIC with an exact serialized size. Padding is - /// carried in the external-chain address, which is part of the signed digest - /// but otherwise irrelevant to rcrdcommit's row-growth boundary. - std::vector create_signed_uic_of_size( - name underwriter, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v, - size_t target_size) { - const auto size_probe_digest = fc::sha256::hash(std::string{ - "wire.sysio.dispatch_tests.sized_uic_signature"}); - const size_t packed_k1_signature_size = fc::raw::pack( - get_private_key(underwriter, "active").sign(size_probe_digest)).size(); - auto uic = create_uic(underwriter.to_string(), uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - - for (size_t padding_size = 0; padding_size < target_size; ++padding_size) { - uic.mutable_uw_ext_chain_addr()->set_address( - std::string(padding_size, '\x01')); - uic.set_signature(std::string(packed_k1_signature_size, '\0')); - const auto shaped_size = uic.ByteSizeLong(); - if (shaped_size < target_size) continue; - BOOST_REQUIRE_EQUAL(target_size, shaped_size); - - const auto signed_uic = sign_uic( - std::move(uic), get_private_key(underwriter, "active")); - BOOST_REQUIRE_EQUAL(target_size, signed_uic.size()); - return signed_uic; - } - - BOOST_FAIL("could not construct an exact-size signed UIC"); - return {}; - } - - /// Create a UIC claiming `claimed_underwriter` but signed by - /// `signing_account`. Separating these accounts lets tests model a real - /// protocol forgery: dispatch derives the candidate from the claimed - /// account, while signature recovery must reject an unrelated signing key. - std::vector create_signed_uic_as(name claimed_underwriter, - name signing_account, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v) { - return create_signed_uic_with_key( - claimed_underwriter, get_private_key(signing_account, "active"), - uwreq_id, chain_code_v, token_code_v, reserve_code_v); - } - - /// Create and sign an UnderwriteIntentCommit so it passes verification: - /// serialize with signature empty (matching the contract's - /// blank-then-rehash), hash the raw bytes, sign with the claimed - /// underwriter's active key, embed the packed signature, and serialize. - std::vector create_signed_uic(name underwriter, uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, uint64_t reserve_code_v) { - return create_signed_uic_as(underwriter, underwriter, uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - } - - /// Create a protobuf-valid UIC carrying caller-supplied packed signature - /// bytes. This reaches the depot's inner-signature verifier while allowing - /// tests to exercise truncated, oversized, and unsupported variant shapes. - std::vector create_uic_with_signature( - name underwriter, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v, - const std::vector& signature) { - auto uic = create_uic(underwriter.to_string(), uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - if (!signature.empty()) { - uic.set_signature(signature.data(), signature.size()); - } - return serialize_uic(uic); - } - - /** Return the one-byte fc variant tag for a packed UIC signature type. */ - static char packed_uic_signature_tag(fc::crypto::signature::sig_type type) { - return static_cast(magic_enum::enum_integer(type)); - } - - /** Produce a real signer-generated packed shape for a supported key type. */ - static std::vector create_packed_uic_signature( - fc::crypto::private_key::key_type type) { - const auto digest = fc::sha256::hash(std::string{ - "wire.sysio.dispatch_tests.packed_uic_signature"}); - return fc::raw::pack(fc::crypto::private_key::generate(type).sign(digest)); - } - - /** Produce a complete serialized WebAuthn variant with structured fields. */ - static std::vector create_packed_webauthn_uic_signature() { - fc::crypto::webauthn::signature webauthn{ - fc::crypto::r1::compact_signature{}, - std::vector{1, 2, 3}, - R"({"type":"webauthn.get"})"}; - return fc::raw::pack(fc::crypto::signature{ - fc::crypto::signature::storage_type{std::move(webauthn)}}); - } - /** Produce a complete serialized fixed-size variant for a shim type. */ - template - static std::vector create_default_packed_uic_signature() { - return fc::raw::pack(fc::crypto::signature{ - fc::crypto::signature::storage_type{ - std::in_place_type}}); - } - - /** Replace only the fc variant tag while preserving a real packed size. */ - static std::vector retag_packed_uic_signature( - std::vector packed, - fc::crypto::signature::sig_type type) { - BOOST_REQUIRE(!packed.empty()); - packed.front() = packed_uic_signature_tag(type); - return packed; - } - - /// Produce a correctly tagged fixed-size signature that is one byte short. - /// Its size comes from fc's real signer/serializer rather than a duplicated - /// protocol constant. - std::vector create_truncated_uic_signature( - fc::crypto::private_key::key_type type) { - auto packed = create_packed_uic_signature(type); - BOOST_REQUIRE_GT(packed.size(), 1u); - packed.pop_back(); - return packed; - } - - /// Preserve fc's serialized variant tag/size while replacing the signature - /// body with invalid recovery data. - std::vector create_invalid_uic_signature( - fc::crypto::private_key::key_type type) { - auto packed = create_packed_uic_signature(type); - BOOST_REQUIRE_GT(packed.size(), 1u); - std::fill(std::next(packed.begin()), packed.end(), '\0'); - return packed; - } - - /** Produce a correctly tagged fixed-size signature that is one byte long. */ - std::vector create_overlength_uic_signature( - fc::crypto::private_key::key_type type) { - auto packed = create_packed_uic_signature(type); - packed.push_back('\0'); - return packed; - } - - /** Corrupt only the Ed25519 signature suffix, preserving its embedded key. */ - std::vector create_corrupted_ed_uic_signature() { - auto packed = create_packed_uic_signature( - fc::crypto::private_key::key_type::ed); - BOOST_REQUIRE(!packed.empty()); - packed.back() = static_cast(static_cast(packed.back()) ^ 1u); - return packed; - } - - /** - * Convert a legitimate low-s UIC signature into its valid high-s alternate - * (`s' = n - s`, recovery parity flipped) without changing signed bytes. - */ - std::vector create_high_s_signed_uic( - name underwriter, - const fc::crypto::private_key& signing_key, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v) { - using key_type = fc::crypto::private_key::key_type; - auto uic = create_uic(underwriter.to_string(), uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - const auto blanked = serialize_uic(uic); - const auto digest = fc::sha256::hash(blanked.data(), blanked.size()); - auto packed = fc::raw::pack(signing_key.sign(digest)); - auto high_s = sysio::opp::test::create_high_s_alternate( - std::move(packed), signing_key.type()); - BOOST_REQUIRE(high_s.has_value()); - packed = std::move(*high_s); - - // Prove this is a valid alternate representation, not random corruption. - // libfc's R1 recovery entrypoint itself enforces low-s before recovery, - // so the algebraic n-s/parity construction is the executable proof for - // R1; K1/EM can additionally recover through their host shims. - const auto alternate = fc::raw::unpack(packed); - if (signing_key.type() != key_type::r1) { - BOOST_REQUIRE(fc::crypto::public_key::recover(alternate, digest) - == signing_key.get_public_key()); - } - - uic.set_signature(packed.data(), packed.size()); - return serialize_uic(uic); - } - - /** - * Replace a legitimate UIC signature recovery byte with libfc's equivalent - * noncanonical alias without changing the recovered key. - */ - std::vector create_recovery_alias_signed_uic( - name underwriter, - const fc::crypto::private_key& signing_key, - uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, - uint64_t reserve_code_v) { - auto uic = create_uic(underwriter.to_string(), uwreq_id, chain_code_v, - token_code_v, reserve_code_v); - const auto blanked = serialize_uic(uic); - const auto digest = fc::sha256::hash(blanked.data(), blanked.size()); - auto alias = sysio::opp::test::create_recovery_alias( - fc::raw::pack(signing_key.sign(digest)), signing_key.type()); - BOOST_REQUIRE(alias.has_value()); - - const auto alternate = fc::raw::unpack(*alias); - BOOST_REQUIRE(fc::crypto::public_key::recover(alternate, digest) - == signing_key.get_public_key()); - - uic.set_signature(alias->data(), alias->size()); - return serialize_uic(uic); - } - - /// Create an UnderwriteIntentCommit carrying an arbitrary raw `uw_account.name` - /// string — including names that are not constructible as a `sysio::name` - /// (uppercase, hyphen, over-long). No signature is embedded: the dispatch path - /// validates the account name and drops a malformed UIC before rcrdcommit or - /// signature verification is ever reached, so a valid signature is unnecessary. - std::vector create_uic_raw_name(const std::string& raw_name, uint64_t uwreq_id, - uint64_t chain_code_v, - uint64_t token_code_v, uint64_t reserve_code_v) { - return serialize_uic( - create_uic(raw_name, uwreq_id, chain_code_v, token_code_v, reserve_code_v)); - } - - fc::variant get_uwreq(uint64_t id) { - auto data = get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwreqs"_n, id); - return data.empty() ? fc::variant() : uwrit_abi.binary_to_variant( - "uw_request_t", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Next attestation id `sysio.msgch::mint_att_id` will return: the `attseq` singleton's - /// `next`, or 1 before the row is materialised. Outbound queueouts share the same sequence - /// (e.g. reserve registration emits RESERVE_READY), so a test that needs the ids an inbound - /// envelope's attestations will mint must read the counter rather than assume it starts at 1. - uint64_t next_att_id() { - auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, "attseq"_n, 0); - if (data.empty()) return 1; - auto v = msgch_abi.binary_to_variant("att_seq_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - return v["next"].as_uint64(); - } - - /// Read a collateral lock row by lock_id (uwrit `locks` KV table). lock_ids - /// are allocated from 1 (uwcounters default), so the first swap's source + - /// destination locks are ids 1 and 2. - fc::variant get_lock(uint64_t lock_id) { - auto data = get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "locks"_n, lock_id); - return data.empty() ? fc::variant() : uwrit_abi.binary_to_variant( - "lock_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Drive sysio.opreg::slash (CHALG-authorized economic punishment). Flips the - /// operator to SLASHED and immediately debits the unlocked (slashable-now) - /// portion of each balance; the locked portion is settled later by - /// releaselock as each lock's challenge window closes. - action_result slash_op(name account, const std::string& reason) { - return push(OPREG_ACCOUNT, opreg_abi, CHALG_ACCOUNT, "slash"_n, mvo() - ("account", account.to_string()) - ("reason", reason)); - } - - /// The `locksums` rollup total for one (underwriter, chain, token) bucket. - /// `locksums` is keyed by a checksum256 over the triple, so this walks the - /// KV table and filters in memory — same shape as `get_reserve`. An absent - /// row IS zero: the rollup erases a bucket once it empties. - uint64_t get_lock_sum(name underwriter, 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; - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("locksums"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(UWRIT_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == UWRIT_ACCOUNT - && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = uwrit_abi.binary_to_variant("lock_sum", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["underwriter"].as_string() == underwriter.to_string() && - row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token) { - return row["amount"].as_uint64(); - } - } catch (...) { - // Not a lock_sum row — skip. - } - } - return 0; - } - - /// Recompute the same bucket total from the AUTHORITATIVE `locks` rows. - /// This is the oracle the materialized rollup is asserted against — it is - /// the derivation `sum_locks_inline` used to perform inline before the - /// rollup replaced it. - uint64_t scan_lock_total(name underwriter, 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; - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("locks"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - uint64_t total = 0; - auto itr = kv_idx.lower_bound(boost::make_tuple(UWRIT_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == UWRIT_ACCOUNT - && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = uwrit_abi.binary_to_variant("lock_entry", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["underwriter"].as_string() == underwriter.to_string() && - row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token) { - total += row["amount"].as_uint64(); - } - } catch (...) { - // Not a lock_entry row — skip. - } - } - return total; - } - - /// Direct `sysio.uwrit::chklocks` call with an explicit budget - /// (self-authorized). `advance` inlines it with MAX_LOCK_RELEASE_PER_EPOCH; - /// the tests call it directly to exercise the budget itself without an - /// intervening advance sweeping the whole backlog first. - action_result chklocks_direct(uint32_t max_rows) { - return push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "chklocks"_n, mvo() - ("max_rows", max_rows)); - } - - /// Direct sysio.opreg::releaselock call (UWRIT-authorized). In production - /// this is fanned out one-per-lock by sysio.uwrit::chklocks at epoch advance; - /// the tests call it directly to exercise the deferred-slash settlement math. - action_result releaselock_direct(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - return push(OPREG_ACCOUNT, opreg_abi, UWRIT_ACCOUNT, "releaselock"_n, mvo() - ("account", account.to_string()) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount)); - } - - /// Administratively terminate an operator (`sysio.opreg::terminate`, - /// self-authorized). Remits the unlocked portion of every balance and leaves - /// the locked remainder for `releaselock` — the state WNS-01's prune gate - /// must protect. - action_result terminate_op(name account, const std::string& reason) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "terminate"_n, mvo() - ("account", account.to_string()) - ("reason", reason)); - } - - /// The permissionless `sysio.opreg::prune` crank. - action_result opreg_prune() { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "prune"_n, mvo()); - } - - /// Walk `sysio.reserv::reserves` and return the row matching the slug_name - /// triple. The table is KV-keyed by a checksum256, which `get_row_by_id` - /// (uint64 keys only) cannot reach — same scan `sysio.reserv_tests` uses. - fc::variant find_reserve(std::string_view chain_code, - std::string_view token_code, - std::string_view reserve_code) { - const auto target_chain = fc::slug_name{chain_code}.value; - const auto target_token = fc::slug_name{token_code}.value; - const auto target_reserve = fc::slug_name{reserve_code}.value; - - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("reserves"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(RESERV_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == RESERV_ACCOUNT - && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = reserv_abi.binary_to_variant("reserve_row", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token && - row["reserve_code"].as().value == target_reserve) { - return row; - } - } catch (...) { - // Not a reserve_row — skip. - } - } - return fc::variant(); - } - - /// The depot's own quote for a swap, computed with the SAME kernel the - /// contract uses (`sysio.uwrit::swap_quote` -> `opp::amm::quote_swap`) over - /// the live reserve rows. Tests assert settlement against this rather than - /// against a hardcoded number, so they pin the INVARIANT ("dst_amount is the - /// curve's output") instead of one fixture's arithmetic. - uint64_t expected_quote(std::string_view src_chain, std::string_view src_token, - std::string_view src_reserve, uint64_t src_amount, - std::string_view dst_chain, std::string_view dst_token, - std::string_view dst_reserve, uint32_t fee_bps) { - const auto src = find_reserve(src_chain, src_token, src_reserve); - const auto dst = find_reserve(dst_chain, dst_token, dst_reserve); - BOOST_REQUIRE(!src.is_null()); - BOOST_REQUIRE(!dst.is_null()); - return sysio::opp::amm::quote_swap( - /*src_is_wire*/ false, - src["reserve_chain_amount"].as_uint64(), src["reserve_wire_amount"].as_uint64(), - static_cast(src["connector_weight_bps"].as_uint64()), - /*dst_is_wire*/ false, - dst["reserve_chain_amount"].as_uint64(), dst["reserve_wire_amount"].as_uint64(), - static_cast(dst["connector_weight_bps"].as_uint64()), - src_amount, fee_bps); - } - - /// Register one ACTIVE reserve with ample balanced liquidity (1e12 / 1e12, - /// 50% connector weight). Shared by the bootstrap pair below and by the - /// same-(chain, token) multi-reserve swap tests, which add a second reserve - /// on an already-registered (chain, token) pair. - action_result regreserve_active(std::string_view c, std::string_view t, std::string_view r) { - return regreserve_active_amounts(c, t, r, /*chain_amount*/ 1'000'000'000'000ull, - /*wire_amount*/ 1'000'000'000'000ull); - } - - /// `regreserve_active` with explicit pool depths, for a test that needs a deliberately - /// IMBALANCED reserve. At the 50% connector weight the curve is pure constant product - /// (`out = wire * amount / (chain + amount)`), so a deep token side over a shallow WIRE side - /// floors a small conversion to zero — the "dust bucket" the bond quote has to survive. - action_result regreserve_active_amounts(std::string_view c, std::string_view t, - std::string_view r, - uint64_t chain_amount, uint64_t wire_amount) { - return push(RESERV_ACCOUNT, reserv_abi, RESERV_ACCOUNT, "regreserve"_n, mvo() - ("chain_code", c) - ("token_code", t) - ("reserve_code", r) - ("name", std::string(c)) - ("description", std::string{}) - ("initial_chain_amount", chain_amount) - ("initial_wire_amount", wire_amount) - ("source_token_precision", uint32_t{9}) - ("connector_weight_bps", uint32_t{5000}) - ("is_private", false) - ("owner", "")); - } - - /// Drain chain-side reserve liquidity through the same UWRIT-authorized - /// action used by emitted swap settlement. Tests use this after request - /// creation to model liquidity changing while UICs are in flight. - action_result debit_reserve_chain(std::string_view c, std::string_view t, - std::string_view r, uint64_t amount) { - return push(RESERV_ACCOUNT, reserv_abi, UWRIT_ACCOUNT, "debit"_n, mvo() - ("chain_code", c) - ("token_code", t) - ("reserve_code", r) - ("amount", amount)); - } - - /// Deploy sysio.token, issue a WIRE supply to the treasury, and seed two - /// ACTIVE bootstrap reserves (ETH/ETH and SOLANA/SOL) with ample balanced - /// liquidity so try_select_winner's reserve-liquidity gate passes and the - /// race reaches try_build_swap_remit. Bootstrap window is open (epoch 0: - /// bootstrap_for_dispatch's advance() gate-blocks on missing emissions). - void setup_wire_token_and_reserves() { - deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, TOKEN_ACCOUNT, "create"_n, mvo() - ("issuer", "sysio")("maximum_supply", "1000000000.000000000 WIRE"))); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "issue"_n, mvo()("to", "sysio")("quantity", "1000000000.000000000 WIRE")("memo", "seed"))); - - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "PRIMARY")); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("SOLANA", "SOL", "PRIMARY")); - } - - /// Register the WIRE depot chain (`is_depot = (kind == CHAIN_KIND_WIRE)`), so a - /// swap whose destination leg is `WIRE` is treated as a to-WIRE swap (depot - /// destination, no destination UIC, settled inline via reserv::paywire). - void register_wire_depot() { - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_WIRE) - ("code", "WIRE") - ("external_chain_id", 0) - ("name", std::string("wire-depot")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - } - - /// Register SOLANA, seed ACTIVE ETH+SOLANA reserves, credit UWRIT_OP collateral, - /// and create a PENDING ETH->SOLANA uwreq under `att_id` with source leg - /// (ETH, ETH, PRIMARY) and destination leg (SOLANA, SOL, PRIMARY). Shared by the - /// UIC leg-binding and account-name-validation cases below. `bootstrap_for_dispatch` - /// must have been called first. - void setup_eth_to_sol_uwreq(uint64_t att_id) { - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, sol_chain, sol_token, primary, 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, /*proven=*/ eth, sr)); - BOOST_REQUIRE(!get_uwreq(att_id).is_null()); - } // ── SEC-129 / WSA-223: real epoch-aging helpers ────────────────────────── // @@ -1448,7 +716,7 @@ class sysio_dispatch_tester : public tester { /// Make `sysio.epoch::advance` genuinely advance: everything /// `deploy_system_with_emitcfg` sets up, plus the t5 state the gate needs /// past its STATE_UNINITIALIZED check. Requires - /// setup_wire_token_and_reserves() first (payepoch pays WIRE out of sysio's + /// setup_wire_token() first (payepoch pays WIRE out of sysio's /// token balance). void enable_epoch_advancement() { deploy_system_with_emitcfg(); @@ -1620,7 +888,15 @@ class sysio_dispatch_tester : public tester { return console; } - abi_serializer msgch_abi, opreg_abi, uwrit_abi, epoch_abi, reserv_abi, authex_abi, dclaim_abi, + void setup_wire_token() { + deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi); + BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, TOKEN_ACCOUNT, "create"_n, mvo() + ("issuer", "sysio")("maximum_supply", "1000000000.000000000 WIRE"))); + BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, + "issue"_n, mvo()("to", "sysio")("quantity", "1000000000.000000000 WIRE")("memo", "seed"))); + } + + abi_serializer msgch_abi, opreg_abi, epoch_abi, authex_abi, dclaim_abi, chains_abi, roa_abi, token_abi, tokens_abi, liq_abi, synd_abi; std::vector uwrit_op_eth_pubkey; @@ -1630,675 +906,116 @@ class sysio_dispatch_tester : public tester { BOOST_AUTO_TEST_SUITE(sysio_dispatch_tests) -BOOST_FIXTURE_TEST_CASE(dispatch_routes_deposit_to_opreg, sysio_dispatch_tester) { try { +BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispatch_tester) { try { bootstrap_for_dispatch(); - constexpr int64_t DEPOSIT_AMOUNT = 1'000'000; const auto eth_code = fc::slug_name{"ETH"}.value; - - auto operator_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - /*chain_code_v=*/ eth_code, - /*token_code_v=*/ eth_code, - DEPOSIT_AMOUNT); - auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - operator_payload); + sysio::opp::types::ATTESTATION_TYPE_STAKE, + std::string{}); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); auto op = get_operator(UWRIT_OP); BOOST_REQUIRE(!op.is_null()); - auto bal = find_balance(op, "ETH", "ETH"); - BOOST_REQUIRE(!bal.is_null()); - BOOST_REQUIRE_EQUAL(static_cast(DEPOSIT_AMOUNT), - bal["balance"].as_uint64()); + const auto& balances = op["balances"].get_array(); + BOOST_REQUIRE_EQUAL(0u, balances.size()); } FC_LOG_AND_RETHROW() } -// A payload `token_code` with no canonical spelling is DROPPED, never stored. -// -// `chain_code` is proven -- source_chain_binding_ok binds it to the delivering -// outpost -- but `token_code` rides the FORGEABLE payload and reaches slug_name -// through the non-validating raw constructor. Rendering is total, so persisting one -// leaves a balance row whose rendered code either fails validation on the way back, or -// silently re-parses as a DIFFERENT code -- aliasing one operator's balance onto -// another's key. -// So the dispatcher drops the attestation -- a check() here would halt -// evalcons and stall consensus (feedback_opp_handlers_never_throw). -// A DEPOSIT_REQUEST arrives only after the outpost has taken custody, so an -// unspellable token code must be REFUNDED rather than dropped: a drop leaves the -// depositor with no credit on the depot and no DEPOSIT_REVERT to release the escrow. -// The balance map is keyed by the code, so the credit itself must still not persist. -BOOST_FIXTURE_TEST_CASE(dispatch_refunds_uncanonical_deposit_token_code, sysio_dispatch_tester) { try { +// A second `deliver` from the SAME operator for the same outpost+epoch must REVERT, not land as a +// recorded no-op: a reverted transaction is never included in a block and bills no CPU/NET, whereas +// the previous soft print-and-return shape charged the operator and consumed block space for zero +// state change. Matching deliveries from DISTINCT operators are not duplicates -- they are the +// consensus tally itself (covered by the dispute/consensus suites). +BOOST_FIXTURE_TEST_CASE(deliver_duplicate_from_same_operator_reverts, sysio_dispatch_tester) { try { bootstrap_for_dispatch(); const auto eth_code = fc::slug_name{"ETH"}.value; - // Any value below the leading symbol's floor decodes to "" and packs back to - // 0, so it is not a code and has no spelling. - constexpr uint64_t uncanonical_token = 7; - BOOST_REQUIRE(!fc::slug_name{uncanonical_token}.is_canonical()); - - const auto before = get_operator(UWRIT_OP); - const size_t balances_before = before.is_null() ? 0 : before["balances"].get_array().size(); - - auto payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - /*chain_code_v=*/ eth_code, - /*token_code_v=*/ uncanonical_token, - 1'000'000); - auto envelope = encode_envelope_with_one_attestation( current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - payload); + sysio::opp::types::ATTESTATION_TYPE_STAKE, + std::string{}); - // The delivery SUCCEEDS -- the rejection happens inside dispatch, never as a revert - // of the envelope (a throw here would halt evalcons and stall consensus). BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); - - // Nothing was persisted, so no stored row can later render as the wrong code... - const auto after = get_operator(UWRIT_OP); - const size_t balances_after = after.is_null() ? 0 : after["balances"].get_array().size(); - BOOST_CHECK_EQUAL(balances_before, balances_after); - - // ...and the escrow is released: observe the OUTBOUND revert, not merely the - // absent credit. Without it the depositor's funds sit in outpost custody forever. - BOOST_REQUIRE(!find_queued_attestation( - "ATTESTATION_TYPE_DEPOSIT_REVERT", - sysio::opp::types::ATTESTATION_TYPE_DEPOSIT_REVERT).is_null()); + // Cross a block boundary so the re-submission is a distinct transaction — + // an identical push in the same block is rejected as tx_duplicate before + // the contract runs, which would mask the guard under test. + produce_blocks(); + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("operator already delivered for this outpost+epoch"), + deliver(/*chain_code=*/eth_code, envelope)); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(dispatch_routes_withdraw_request_to_opreg, sysio_dispatch_tester) { try { +// The inbound `deliver` boundary enforces the same 32 KiB protocol envelope cap the outbound +// `buildenv` packer obeys (and that the Ethereum/Solana outposts enforce on their side): a +// decodable current-epoch envelope one byte over the cap must revert before anything is hashed +// or stored. Without the contract-level cap, the generic chain ceilings (~512 KiB inline-action, +// 256 KiB KV value) would admit inbound envelopes WIRE's own packer could never emit. +BOOST_FIXTURE_TEST_CASE(deliver_oversized_envelope_reverts, sysio_dispatch_tester) { try { bootstrap_for_dispatch(); - constexpr int64_t INITIAL_DEPOSIT = 5'000'000; - constexpr int64_t WITHDRAW_AMOUNT = 2'000'000; const auto eth_code = fc::slug_name{"ETH"}.value; + auto oversized = encode_envelope_padded_to(current_epoch(), MAX_ENVELOPE_BYTES + 1); - // The depot dedups per-(batch_op, chain_code, epoch) — a second `deliver` - // from the same batch op in the same epoch reverts as a duplicate. To - // exercise both dispatch branches in one test, both attestations ride a - // single envelope. - auto deposit_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - eth_code, eth_code, - INITIAL_DEPOSIT); - - auto wtdw_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_WITHDRAW_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - eth_code, eth_code, - WITHDRAW_AMOUNT); - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, - encode_envelope_with_attestations(current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - {deposit_payload, wtdw_payload}))); - - auto row = get_wtdw(/*request_id=*/1); - BOOST_REQUIRE(!row.is_null()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), row["account"].as_string()); - BOOST_REQUIRE_EQUAL(static_cast(WITHDRAW_AMOUNT), - row["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(eth_code, row["chain_code"].as().value); - BOOST_REQUIRE_EQUAL(eth_code, row["token_code"].as().value); + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("inbound envelope exceeds MAX_ENVELOPE_BYTES"), + deliver(/*chain_code=*/eth_code, oversized)); + // Reverted before the emplace: no envelope row landed. + BOOST_REQUIRE(get_envelope(1).is_null()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(dispatch_silently_drops_out_of_scope_types, sysio_dispatch_tester) { try { +// Boundary companion: an envelope exactly AT the cap is accepted and stored (and, with this +// suite's single-operator group, immediately reaches consensus and dispatches). Guards against +// an off-by-one regression turning the cap check exclusive. +BOOST_FIXTURE_TEST_CASE(deliver_envelope_at_size_cap_succeeds, sysio_dispatch_tester) { try { bootstrap_for_dispatch(); const auto eth_code = fc::slug_name{"ETH"}.value; - auto envelope = encode_envelope_with_one_attestation( - current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, - std::string{}); - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); + auto boundary = encode_envelope_padded_to(current_epoch(), MAX_ENVELOPE_BYTES); - auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - const auto& balances = op["balances"].get_array(); - BOOST_REQUIRE_EQUAL(0u, balances.size()); + BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, boundary)); + auto row = get_envelope(1); + BOOST_REQUIRE(!row.is_null()); + BOOST_REQUIRE_EQUAL(BATCHOP.to_string(), row["batch_op_name"].as_string()); + BOOST_REQUIRE_EQUAL(current_epoch(), row["epoch_index"].as()); } FC_LOG_AND_RETHROW() } -// ───────────────────────────── WSA-005: inbound source-chain binding ───────────────────────────── -// -// A consensus envelope is delivered for exactly ONE proven source outpost (the `deliver` chain_code, -// validated against `sysio.chains`). Every value-bearing attestation it carries embeds its own chain -// identifier; that identifier MUST equal the proven outpost. These tests drive a payload chain that -// diverges from the proven outpost and assert the depot applies NO value-bearing effect — and never -// throws (a throw inside the evalcons dispatch chain stalls consensus chain-wide). - -// OPERATOR_ACTION: a DEPOSIT_REQUEST and a WITHDRAW_REQUEST proven-delivered from the ETH outpost but -// whose payloads claim a different active chain (SOLANA) are both dropped — no operator collateral is -// credited and no withdraw is queued. The matched-chain control is `dispatch_routes_deposit_to_opreg` -// / `dispatch_routes_withdraw_request_to_opreg` above, which DO credit/queue. -BOOST_FIXTURE_TEST_CASE(operator_action_mismatched_source_chain_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH outpost + UWRIT_OP (EVM authex link) +// NodeOwnerRegistration: msgch decodes the attestation and inline-sends sysio.roa::newnameduser then +// nodeownreg. CLAIM_ACCOUNT pre-exists with a single-key active, so newnameduser no-ops and the +// claim's matching wire key drives nodeownreg's existing-account path to CONFIRMED (registers the +// owner and inline-records the depositor's ETH link in sysio.authex). Exercises the full dispatch: +// proto decode (account name + WireKey + ETH key) -> routing -> both roa actions -> recordlink. +BOOST_FIXTURE_TEST_CASE(dispatch_routes_node_owner_reg_to_roa, sysio_dispatch_tester) { try { + // Node-owner NFT deposits originate on the Ethereum outpost (code "ETHEREUM", matching the launch + // bootstrap config and msgch's NODE_OWNER_SRC_CHAIN). Bootstrap it as the scheduled source outpost + // so the delivery below reaches consensus and dispatches. + bootstrap_for_dispatch("ETHEREUM"); - const auto eth_code = fc::slug_name{"ETH"}.value; - const auto sol_code = fc::slug_name{"SOLANA"}.value; - constexpr int64_t DEPOSIT_AMOUNT = 1'000'000; - constexpr int64_t WITHDRAW_AMOUNT = 400'000; + const auto eth_code = fc::slug_name{"ETHEREUM"}.value; + // The claim must carry CLAIM_ACCOUNT's own active key so nodeownreg's active_key_matches passes. + auto wire_key = k1_pubkey_bytes(get_public_key(CLAIM_ACCOUNT, "active")); + // BAR supplies the depositor's ETH key as an uncompressed 65-byte point. + auto eth_pub = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em).get_public_key(); + auto eth_bytes = em_uncompressed_pubkey_bytes(eth_pub); + auto eth_address = fc::crypto::ethereum::address_to_bytes(eth_pub); - // SOLANA is a real, active outpost, so the ONLY thing wrong with the payloads below is that they - // were proven-delivered from ETH rather than SOLANA — the exact WSA-005 forgery. - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); + // Seed a pre-link reward at the key-derived address, then deliberately put a conflicting address + // in the redundant actor field. Dispatch must derive from actor_pub_key and sweep the real row. + const std::vector native_address(eth_address.begin(), eth_address.end()); + BOOST_REQUIRE_EQUAL(success(), push(DCLAIM_ACCOUNT, dclaim_abi, MSGCH_ACCOUNT, "onreward"_n, mvo() + ("chain_code", eth_code)("staker_wire_account", std::string{}) + ("reward_chain", ChainKind::CHAIN_KIND_EVM)("staker_native_addr", native_address) + ("reward_amount", uint64_t{4321})("reward_epoch_index", uint32_t{7}) + ("external_epoch_ref", uint64_t{100})("share_bps", uint32_t{10000}))); + BOOST_REQUIRE(!get_dclaim_row("unmapped"_n, "unmapped_token", 1).is_null()); + std::fill(eth_address.begin(), eth_address.end(), uint8_t{0xA5}); - auto deposit_sol = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, - /*chain_code_v=*/ sol_code, /*token_code_v=*/ sol_code, DEPOSIT_AMOUNT); - auto withdraw_sol = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_WITHDRAW_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, - /*chain_code_v=*/ sol_code, /*token_code_v=*/ sol_code, WITHDRAW_AMOUNT); - - // Proven outpost = ETH; both payloads claim SOLANA. deliver() must SUCCEED (the binding drops the - // attestations inside dispatch — it must not abort the envelope). - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, - encode_envelope_with_attestations(current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, {deposit_sol, withdraw_sol}))); - - auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_REQUIRE_EQUAL(0u, op["balances"].get_array().size()); // no collateral on any chain - BOOST_REQUIRE(get_wtdw(/*request_id=*/1).is_null()); // no withdraw queued -} FC_LOG_AND_RETHROW() } - -// SWAP_REQUEST: a swap whose `source_chain_code` does not match the proven delivering outpost must be -// refunded (SwapRevert) and create NO uwreq — settling it would draw against the named source reserve -// while the user's deposit sits on a different chain. Same SwapRequest delivered from its real source -// outpost is the control: it creates the uwreq. -BOOST_FIXTURE_TEST_CASE(swap_request_mismatched_source_chain_is_refunded, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH source outpost - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH/PRIMARY + SOLANA/SOL/PRIMARY reserves - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - // A fully valid ETH->SOLANA swap (source leg = ETH). - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - - // Mismatch: proven delivering outpost = SOLANA, but source_chain_code = ETH -> refund, no uwreq. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9001, /*proven=*/ sol_chain, sr)); - BOOST_REQUIRE(get_uwreq(9001).is_null()); - - // Control: same SwapRequest proven-delivered from its real source outpost (ETH) -> uwreq created. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9002, /*proven=*/ eth, sr)); - BOOST_REQUIRE(!get_uwreq(9002).is_null()); -} FC_LOG_AND_RETHROW() } - -// A SwapRequest's token/reserve codes are payload-controlled and reach `slug_name` -// through the non-validating raw constructor. Nothing downstream gates them: the -// zero-quote guard fails closed only when `required_reserves_active` holds, so a code -// naming NO reserve leaves the quote at zero, skips that guard, and reaches -// `reqs.emplace`. Rendering is total, so the row is not unreadable — it is WRONG: its -// rendered code either fails validation on the way back, or re-parses as a DIFFERENT -// reserve than the one it was created from. The request must be -// REFUNDED rather than dropped: the user's deposit is escrowed on the source outpost. -BOOST_FIXTURE_TEST_CASE(swap_request_uncanonical_code_is_refunded, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH source outpost - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - // Below the leading symbol's floor: decodes to "" and packs back to 0, so it is not a - // code and does not round-trip. The TARGET CHAIN stays valid — the point is that a - // registered chain does not imply a canonical token/reserve code. - constexpr uint64_t uncanonical = 7; - BOOST_REQUIRE(!fc::slug_name{uncanonical}.is_canonical()); - - const auto bad_target_token = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, uncanonical, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - - // Delivery SUCCEEDS (refund path, never a throw) and NO row is persisted. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9101, /*proven=*/ eth, bad_target_token)); - BOOST_REQUIRE(get_uwreq(9101).is_null()); - - // Same for an unspellable RESERVE code on the source leg. - const auto bad_source_reserve = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, uncanonical, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9102, /*proven=*/ eth, bad_source_reserve)); - BOOST_REQUIRE(get_uwreq(9102).is_null()); - - // Control: every code spellable -> the uwreq is created, so the guard rejects the - // unspellable code rather than the shape of the request. - const auto good = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9103, /*proven=*/ eth, good)); - BOOST_REQUIRE(!get_uwreq(9103).is_null()); -} FC_LOG_AND_RETHROW() } - -// An exact `(chain, token, reserve)` self-route has only one outpost leg and -// can never produce the two distinct commitments an ordinary external-token -// swap requires. Depot ingestion must therefore refund it without creating a -// UWREQ. The cross-chain control proves same-asset-style routing remains -// admissible whenever the endpoint triple is not identical. -BOOST_FIXTURE_TEST_CASE(swap_request_identical_reserve_identity_is_refunded, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - const auto self_route = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - eth, eth, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9051, /*proven=*/ eth, self_route)); - BOOST_REQUIRE(get_uwreq(9051).is_null()); - - const auto cross_chain = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(/*att_id*/ 9052, /*proven=*/ eth, cross_chain)); - BOOST_REQUIRE(!get_uwreq(9052).is_null()); -} FC_LOG_AND_RETHROW() } - -// UNDERWRITE_INTENT_COMMIT: the same signed dest-leg (SOLANA) commit, delivered through the FULL -// deliver->evalcons->apply_consensus->dispatch path, is recorded only when its proven outpost matches -// `uic.chain_code`. Delivered from ETH it is dropped (no commit); delivered from SOLANA it lands. -BOOST_FIXTURE_TEST_CASE(underwrite_commit_mismatched_source_chain_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 9100; - - // Create the uwreq (ETH source, SOLANA dest) via the proven ETH outpost. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, sol_chain, sol_token, primary, 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, /*proven=*/ eth, sr)); - BOOST_REQUIRE(!get_uwreq(ATT_ID).is_null()); - - // One signed dest-leg (SOLANA) UIC, wrapped in an envelope. The outpost it is proven-delivered - // from is the ONLY thing that varies between the two deliveries below. - const auto uic_sol = create_signed_uic(UWRIT_OP, ATT_ID, - /*chain_code*/ sol_chain, sol_token, primary); - const auto uic_env = encode_envelope_with_one_attestation( - current_epoch(), sysio::opp::types::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - std::string(uic_sol.begin(), uic_sol.end())); - - auto dest_committed = [&]() { - auto req = get_uwreq(ATT_ID); - for (const auto& c : req["commits_by"].get_array()) - if (c["underwriter"].as_string() == UWRIT_OP.to_string() && - c["dest_received_at_ms"].as_uint64() != 0) - return true; - return false; - }; - - // Mismatch: a SOLANA-leg commit proven-delivered from the ETH outpost is dropped — no commit. - BOOST_REQUIRE_EQUAL(success(), deliver(/*proven=*/ eth, uic_env)); - BOOST_REQUIRE(!dest_committed()); - - // Control: the SAME commit proven-delivered from the SOLANA outpost is recorded. - BOOST_REQUIRE_EQUAL(success(), deliver(/*proven=*/ sol_chain, uic_env)); - BOOST_REQUIRE(dest_committed()); -} FC_LOG_AND_RETHROW() } - -// SEC-13/WSA-027: two chains of the SAME VM family (ETH + a second EVM chain) -// must be disambiguated by EXACT chain_code at the depot — never collapsed onto -// one ChainKind. A commit for the SECOND EVM chain's leg, proven-delivered from -// the FIRST EVM chain's outpost, is dropped (its chain_code != the proven -// outpost); delivered from the second EVM outpost it lands. This is the -// same-family analogue of underwrite_commit_mismatched_source_chain_is_dropped -// and pins the two-same-kind-chain invariant WSA-027 is about. -BOOST_FIXTURE_TEST_CASE(underwrite_commit_two_evm_chains_route_per_chain, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH (EVM) source outpost - // A SECOND active EVM chain — same VM family, distinct chain_code. - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_EVM)("code", "POLYGON") - ("external_chain_id", 137)("name", std::string("polygon-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - // SOLANA is registered only because the shared reserve-setup helper seeds a - // SOLANA/SOL reserve; it is otherwise unused by this two-EVM scenario. - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("POLYGON", "POL", "PRIMARY")); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "POLYGON", "POL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto polygon = fc::slug_name{"POLYGON"}.value; - const auto pol_tok = fc::slug_name{"POL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 9200; - - // ETH -> POLYGON swap (both EVM). Proven source outpost = ETH. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, polygon, pol_tok, primary, 100, - 5000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0c')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, /*proven=*/ eth, sr)); - BOOST_REQUIRE(!get_uwreq(ATT_ID).is_null()); - - // One signed dest-leg (POLYGON) UIC; the proven delivering outpost is the - // only thing that varies between the two deliveries below. - const auto uic_pol = create_signed_uic(UWRIT_OP, ATT_ID, /*chain_code*/ polygon, pol_tok, primary); - const auto uic_env = encode_envelope_with_one_attestation( - current_epoch(), sysio::opp::types::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - std::string(uic_pol.begin(), uic_pol.end())); - - auto dest_committed = [&]() { - auto req = get_uwreq(ATT_ID); - for (const auto& c : req["commits_by"].get_array()) - if (c["underwriter"].as_string() == UWRIT_OP.to_string() && - c["dest_received_at_ms"].as_uint64() != 0) - return true; - return false; - }; - - // Mismatch: a POLYGON-leg commit proven-delivered from the ETH outpost (same - // VM family, different chain_code) is dropped — exact chain_code != proven ETH. - BOOST_REQUIRE_EQUAL(success(), deliver(/*proven=*/ eth, uic_env)); - BOOST_REQUIRE(!dest_committed()); - - // Control: the SAME commit proven-delivered from the POLYGON outpost lands. - BOOST_REQUIRE_EQUAL(success(), deliver(/*proven=*/ polygon, uic_env)); - BOOST_REQUIRE(dest_committed()); -} FC_LOG_AND_RETHROW() } - -// Malformed UIC bytes and decode-clean UICs whose `uw_account.name` is not a -// canonical constructible account name must be logged and dropped without -// throwing. A valid UIC later in the same envelope must still be dispatched. -BOOST_FIXTURE_TEST_CASE(underwrite_commit_early_rejections_log_and_continue, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9200; - setup_eth_to_sol_uwreq(ATT_ID); - - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - const auto uic_upper = create_uic_raw_name("BADNAME", ATT_ID, sol_chain, sol_token, primary); - const auto uic_hyphen = create_uic_raw_name("bad-name", ATT_ID, sol_chain, sol_token, primary); - const auto uic_long = create_uic_raw_name("abcdefghijklmn", ATT_ID, sol_chain, sol_token, primary); - const auto uic_alias = create_uic_raw_name("underwriter.", ATT_ID, sol_chain, sol_token, primary); - const auto valid_uic = create_signed_uic( - UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - const auto env = encode_envelope_with_attestations( - current_epoch(), sysio::opp::types::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - {std::string(1, '\x0a'), - std::string(uic_upper.begin(), uic_upper.end()), - std::string(uic_hyphen.begin(), uic_hyphen.end()), - std::string(uic_long.begin(), uic_long.end()), - std::string(uic_alias.begin(), uic_alias.end()), - std::string(valid_uic.begin(), valid_uic.end())}); - - const auto trace = deliver_trace(/*proven=*/ sol_chain, env); - BOOST_REQUIRE(trace != nullptr); - BOOST_REQUIRE(!trace->except); - const auto console = all_console(trace); - BOOST_CHECK_NE(std::string::npos, - console.find("UIC_DISPATCH_REJECTED: chain_code=")); - BOOST_CHECK_NE(std::string::npos, console.find("reason=malformed_uic")); - BOOST_CHECK_NE(std::string::npos, console.find("reason=invalid_wire_account")); - - const auto commits = get_uwreq(ATT_ID)["commits_by"].get_array(); - BOOST_REQUIRE_EQUAL(1u, commits.size()); - BOOST_CHECK_EQUAL(UWRIT_OP.to_string(), commits.front()["underwriter"].as_string()); - BOOST_CHECK(!commits.front()["dest_uic_bytes"].as_string().empty()); -} FC_LOG_AND_RETHROW() } - -// A UIC whose (chain_code, token_code, reserve_code) triple matches neither the source nor the -// destination leg of the pending request must leave commits_by untouched — no inert commit_entry, -// no mutation. rcrdcommit is msgch-authorized; call it directly. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_unmatched_leg_leaves_commits_empty, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9300; - setup_eth_to_sol_uwreq(ATT_ID); // src=(ETH,ETH,PRIMARY) dst=(SOLANA,SOL,PRIMARY) - - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - const auto uic = create_signed_uic(UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - - // (SOLANA, ETH, PRIMARY): source-chain differs from the dest leg, token differs from the source - // leg — matches neither. - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, sol_chain, "SOLANA", "ETH", "PRIMARY", uic)); - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); -} FC_LOG_AND_RETHROW() } - -// Repeating unmatched commits with distinct underwriter names must not grow the row — the -// pre-mutation leg guard is what closes the storage/scan-cost bloat vector. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_unmatched_distinct_underwriters_do_not_grow_row, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9301; - setup_eth_to_sol_uwreq(ATT_ID); - - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - const auto uic = create_signed_uic(UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - - for (name uw : {"uwtwo"_n, "uwthree"_n, "uwfour"_n}) { - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, uw, sol_chain, "SOLANA", "ETH", "PRIMARY", uic)); - } - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); -} FC_LOG_AND_RETHROW() } - -// Control for the leg-branch refactor: a UIC that DOES match the source leg still records a -// commit_entry with the source arrival slot armed. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_matched_source_leg_is_recorded, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9302; - setup_eth_to_sol_uwreq(ATT_ID); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - const auto uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - - // (ETH, ETH, PRIMARY) is the source leg. - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", uic)); - - const auto commits = get_uwreq(ATT_ID)["commits_by"].get_array(); - BOOST_REQUIRE_EQUAL(1u, commits.size()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), commits[0]["underwriter"].as_string()); - BOOST_REQUIRE(commits[0]["source_received_at_ms"].as_uint64() != 0); -} FC_LOG_AND_RETHROW() } - -// Even on a matched leg, a UIC whose account name is a valid `name` but NOT a registered ACTIVE -// underwriter must not create a commit_entry: only active underwriters can win, so gating entry -// creation on activation bounds commits_by to the legitimate racer set and blocks the matched-leg -// name-varying bloat vector (varying valid account names would otherwise append one entry per name). -BOOST_FIXTURE_TEST_CASE(rcrdcommit_matched_leg_non_underwriter_names_do_not_grow_row, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9303; - setup_eth_to_sol_uwreq(ATT_ID); // src=(ETH,ETH,PRIMARY) - - const auto eth = fc::slug_name{"ETH"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - // These are real accounts with valid active keys, deliberately omitted - // from the operator roster. That isolates the ACTIVE-underwriter gate from - // account-nonexistence and malformed-key shortcuts. - create_accounts({"alice"_n, "bob"_n, "carol"_n}); - produce_blocks(); - - // (ETH, ETH, PRIMARY) IS the source leg, so these clear the leg-binding guard; each name is a - // real keyed account but none is a registered underwriter, so all are dropped before mutation. - for (name uw : {"alice"_n, "bob"_n, "carol"_n}) { - const auto uic = create_signed_uic(uw, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, uw, eth, "ETH", "ETH", "PRIMARY", uic)); - } - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); -} FC_LOG_AND_RETHROW() } - -// A second `deliver` from the SAME operator for the same outpost+epoch must REVERT, not land as a -// recorded no-op: a reverted transaction is never included in a block and bills no CPU/NET, whereas -// the previous soft print-and-return shape charged the operator and consumed block space for zero -// state change. Matching deliveries from DISTINCT operators are not duplicates -- they are the -// consensus tally itself (covered by the dispute/consensus suites). -BOOST_FIXTURE_TEST_CASE(deliver_duplicate_from_same_operator_reverts, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const auto eth_code = fc::slug_name{"ETH"}.value; - auto envelope = encode_envelope_with_one_attestation( - current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_STAKE, - std::string{}); - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); - // Cross a block boundary so the re-submission is a distinct transaction — - // an identical push in the same block is rejected as tx_duplicate before - // the contract runs, which would mask the guard under test. - produce_blocks(); - BOOST_REQUIRE_EQUAL( - wasm_assert_msg("operator already delivered for this outpost+epoch"), - deliver(/*chain_code=*/eth_code, envelope)); -} FC_LOG_AND_RETHROW() } - -// The inbound `deliver` boundary enforces the same 32 KiB protocol envelope cap the outbound -// `buildenv` packer obeys (and that the Ethereum/Solana outposts enforce on their side): a -// decodable current-epoch envelope one byte over the cap must revert before anything is hashed -// or stored. Without the contract-level cap, the generic chain ceilings (~512 KiB inline-action, -// 256 KiB KV value) would admit inbound envelopes WIRE's own packer could never emit. -BOOST_FIXTURE_TEST_CASE(deliver_oversized_envelope_reverts, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const auto eth_code = fc::slug_name{"ETH"}.value; - auto oversized = encode_envelope_padded_to(current_epoch(), MAX_ENVELOPE_BYTES + 1); - - BOOST_REQUIRE_EQUAL( - wasm_assert_msg("inbound envelope exceeds MAX_ENVELOPE_BYTES"), - deliver(/*chain_code=*/eth_code, oversized)); - // Reverted before the emplace: no envelope row landed. - BOOST_REQUIRE(get_envelope(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Boundary companion: an envelope exactly AT the cap is accepted and stored (and, with this -// suite's single-operator group, immediately reaches consensus and dispatches). Guards against -// an off-by-one regression turning the cap check exclusive. -BOOST_FIXTURE_TEST_CASE(deliver_envelope_at_size_cap_succeeds, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const auto eth_code = fc::slug_name{"ETH"}.value; - auto boundary = encode_envelope_padded_to(current_epoch(), MAX_ENVELOPE_BYTES); - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, boundary)); - auto row = get_envelope(1); - BOOST_REQUIRE(!row.is_null()); - BOOST_REQUIRE_EQUAL(BATCHOP.to_string(), row["batch_op_name"].as_string()); - BOOST_REQUIRE_EQUAL(current_epoch(), row["epoch_index"].as()); -} FC_LOG_AND_RETHROW() } - -// NodeOwnerRegistration: msgch decodes the attestation and inline-sends sysio.roa::newnameduser then -// nodeownreg. CLAIM_ACCOUNT pre-exists with a single-key active, so newnameduser no-ops and the -// claim's matching wire key drives nodeownreg's existing-account path to CONFIRMED (registers the -// owner and inline-records the depositor's ETH link in sysio.authex). Exercises the full dispatch: -// proto decode (account name + WireKey + ETH key) -> routing -> both roa actions -> recordlink. -BOOST_FIXTURE_TEST_CASE(dispatch_routes_node_owner_reg_to_roa, sysio_dispatch_tester) { try { - // Node-owner NFT deposits originate on the Ethereum outpost (code "ETHEREUM", matching the launch - // bootstrap config and msgch's NODE_OWNER_SRC_CHAIN). Bootstrap it as the scheduled source outpost - // so the delivery below reaches consensus and dispatches. - bootstrap_for_dispatch("ETHEREUM"); - - const auto eth_code = fc::slug_name{"ETHEREUM"}.value; - // The claim must carry CLAIM_ACCOUNT's own active key so nodeownreg's active_key_matches passes. - auto wire_key = k1_pubkey_bytes(get_public_key(CLAIM_ACCOUNT, "active")); - // BAR supplies the depositor's ETH key as an uncompressed 65-byte point. - auto eth_pub = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em).get_public_key(); - auto eth_bytes = em_uncompressed_pubkey_bytes(eth_pub); - auto eth_address = fc::crypto::ethereum::address_to_bytes(eth_pub); - - // Seed a pre-link reward at the key-derived address, then deliberately put a conflicting address - // in the redundant actor field. Dispatch must derive from actor_pub_key and sweep the real row. - const std::vector native_address(eth_address.begin(), eth_address.end()); - BOOST_REQUIRE_EQUAL(success(), push(DCLAIM_ACCOUNT, dclaim_abi, MSGCH_ACCOUNT, "onreward"_n, mvo() - ("chain_code", eth_code)("staker_wire_account", std::string{}) - ("reward_chain", ChainKind::CHAIN_KIND_EVM)("staker_native_addr", native_address) - ("reward_amount", uint64_t{4321})("reward_epoch_index", uint32_t{7}) - ("external_epoch_ref", uint64_t{100})("share_bps", uint32_t{10000}))); - BOOST_REQUIRE(!get_dclaim_row("unmapped"_n, "unmapped_token", 1).is_null()); - std::fill(eth_address.begin(), eth_address.end(), uint8_t{0xA5}); - - auto payload = encode_node_owner_registration( - CLAIM_ACCOUNT.to_string(), /*tier=*/2, - sysio::opp::types::WIRE_KEY_TYPE_K1, wire_key, eth_bytes, eth_address); - auto envelope = encode_envelope_with_one_attestation( - current_epoch(), sysio::opp::types::ATTESTATION_TYPE_NODE_OWNER_REG, payload); + auto payload = encode_node_owner_registration( + CLAIM_ACCOUNT.to_string(), /*tier=*/2, + sysio::opp::types::WIRE_KEY_TYPE_K1, wire_key, eth_bytes, eth_address); + auto envelope = encode_envelope_with_one_attestation( + current_epoch(), sysio::opp::types::ATTESTATION_TYPE_NODE_OWNER_REG, payload); BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); @@ -2321,7 +1038,7 @@ BOOST_FIXTURE_TEST_CASE(dispatch_node_owner_tier_cap_preserves_epoch_progress, s namespace owners = sysio_system::test_support::nodeowners; namespace audit = sysio_system::test_support::nodeownerreg; bootstrap_for_dispatch("ETHEREUM"); - setup_wire_token_and_reserves(); + setup_wire_token(); enable_epoch_advancement(); owners::fill_tier1(*this, roa_abi, owners::tier1_cap - 1); @@ -2564,4575 +1281,254 @@ class node_owner_claim_tester : public sysio_dispatch_tester { } /// Every permission of `account`, for before/after comparison. - permission_set permissions_of(name account) const { - permission_set out; - const auto& db = control->db(); - const auto& idx = db.get_index(); - for (auto it = idx.lower_bound(boost::make_tuple(account)); it != idx.end() && it->owner == account; ++it) { - const name parent = it->parent._id == 0 ? name{} : db.get(it->parent).name; - out.emplace(it->name, std::make_pair(parent, it->auth.to_authority())); - } - return out; - } -}; - -// A tier-2/3 claim can take the name auth.msg and deploy code there, so an auth.msg::onlinkauth notification is -// attacker-controlled. It must reach sysio as an ordinary notification and change nothing: not sysio's owner, active, -// auth.ext or any other permission, and not a user's. -BOOST_FIXTURE_TEST_CASE(claimed_auth_msg_notification_changes_no_permission, node_owner_claim_tester) { try { - bootstrap_for_dispatch(NODE_OWNER_SOURCE_CHAIN); - create_account(PAYER_ACCOUNT); - deploy_system_contract(); - - claim_node_owner(AUTH_MSG_ACCOUNT, get_public_key(AUTH_MSG_ACCOUNT, "active")); - const auto audit = get_nodeownerreg(AUTH_MSG_ACCOUNT); - BOOST_REQUIRE(!audit.is_null()); - BOOST_REQUIRE_EQUAL(audit["status"].as(), sysio_system::test_support::nodeownerreg::status_confirmed); - issue_own_policy(AUTH_MSG_ACCOUNT); - set_code(AUTH_MSG_ACCOUNT, notify_system_account_wasm()); - produce_block(); - - const auto replacement_key = get_public_key(REPLACEMENT_KEY_NAME, "active"); - const auto sysio_before = permissions_of(config::system_account_name); - const auto user_before = permissions_of(CLAIM_ACCOUNT); - for (const auto target : {config::system_account_name, CLAIM_ACCOUNT}) { - for (const auto permission : {config::owner_name, config::active_name, AUTH_EXT_PERMISSION, OTHER_PERMISSION}) { - BOOST_CHECK_MESSAGE(notify_onlinkauth(target, permission, replacement_key), - "onlinkauth for " << target.to_string() << "@" << permission.to_string() - << " did not commit as a plain notification"); - } - } - BOOST_CHECK_MESSAGE(permissions_of(config::system_account_name) == sysio_before, "sysio permissions changed"); - BOOST_CHECK_MESSAGE(permissions_of(CLAIM_ACCOUNT) == user_before, "claimacct permissions changed"); -} FC_LOG_AND_RETHROW() } - -// No node owner may claim a name under the reserved sysio. prefix: the depot's claim is rejected as NAME_INVALID and -// no account is created. -BOOST_FIXTURE_TEST_CASE(node_owner_claim_rejects_reserved_system_name, node_owner_claim_tester) { try { - bootstrap_for_dispatch(NODE_OWNER_SOURCE_CHAIN); - deploy_system_contract(); - - claim_node_owner(RESERVED_SYSTEM_NAME, get_public_key(RESERVED_SYSTEM_NAME, "active")); - - const auto audit = get_nodeownerreg(RESERVED_SYSTEM_NAME); - BOOST_REQUIRE(!audit.is_null()); - BOOST_CHECK_EQUAL(audit["status"].as(), sysio_system::test_support::nodeownerreg::status_rejected); - BOOST_CHECK_EQUAL(audit["reason"].as(), sysio_system::test_support::nodeownerreg::reason_name_invalid); - BOOST_CHECK(get_nodeowner(RESERVED_SYSTEM_NAME).is_null()); - BOOST_CHECK(!account_exists(RESERVED_SYSTEM_NAME)); -} FC_LOG_AND_RETHROW() } - -/// Regression: a non-advancing advance() must not permanently strand the epoch. -/// -/// When every active outpost has reached consensus and the wall clock has passed, chkcons triggers -/// sysio.epoch::advance. advance can legally return WITHOUT bumping the epoch when emissions are not -/// ready -- its emissions gate records a block and returns gracefully, it does not throw. The earlier -/// chkcons cleared per-outpost consensus_reached BEFORE calling advance, so that graceful return -/// committed the cleared state and nothing re-armed it (apply_consensus does not re-fire for an -/// already-complete delivery set) -- permanently stalling advancement even once emissions later became -/// ready. -/// -/// This fixture never deploys sysio.system, so the emissions gate always blocks (CONFIG_MISSING): -/// exactly the non-advancing path. After consensus, each chkcons must re-attempt advance, which the -/// gate records as blocklog.retry_count. Pre-fix, the second chkcons bailed at the consensus gate -/// (retry_count would stay 1) and the epoch was stuck forever. -BOOST_FIXTURE_TEST_CASE(chkcons_survives_non_advancing_advance, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - const auto eth_code = fc::slug_name{"ETH"}.value; - - // This fixture deploys no sysio.system, so even the genesis advance in bootstrap gate-blocked; the - // chain sits at `epoch0`. advance() always targets the next epoch, whose blocklog row counts gate - // re-attempts. - const uint32_t epoch0 = current_epoch(); - const uint32_t target = epoch0 + 1; - - // operators_per_epoch == 1, so a single delivery is Option-A unanimous consensus; apply_consensus - // records the outpcons row for ETH at epoch0. - auto operator_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - /*chain_code_v=*/ eth_code, - /*token_code_v=*/ eth_code, - /*amount=*/ 1'000'000); - auto envelope = encode_envelope_with_one_attestation( - epoch0, - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - operator_payload); - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); - produce_blocks(); // land the deliver in its own block (this fixture's push does not) - - auto retry_count = [&]() -> uint32_t { - auto bl = get_blocklog(target); - return bl.is_null() ? 0u : bl["retry_count"].as(); - }; - const uint32_t rc0 = retry_count(); - - // Pass the wall clock (epoch_duration_sec == 60) so chkcons will fire advance, then refresh the head - // so the subsequent push is stamped against the post-skip time and does not expire. - produce_block(fc::seconds(120)); - produce_blocks(); - - // First chkcons fires advance, which gate-blocks on missing emissions and returns without advancing. - // The epoch is unchanged and the gate bumps retry_count -- confirming advance was actually attempted. - BOOST_REQUIRE_EQUAL(success(), chkcons()); - produce_blocks(); - BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); - const uint32_t rc1 = retry_count(); - BOOST_REQUIRE_EQUAL(rc1, rc0 + 1); - - // REGRESSION: the per-outpost consensus signal must survive the non-advancing advance, so the second - // chkcons re-attempts advance (retry_count -> rc1 + 1). Pre-fix, chkcons cleared consensus_reached - // before calling advance, so this second call bailed at the consensus gate and never re-attempted -- - // retry_count would stay at rc1 and the epoch would be permanently stranded. - BOOST_REQUIRE_EQUAL(success(), chkcons()); - produce_blocks(); - BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); - BOOST_REQUIRE_EQUAL(retry_count(), rc1 + 1); -} FC_LOG_AND_RETHROW() } - -// ═════════════════════════════════════════════════════════════════════════ -// msgch::bootstrap emissions guards. Missing emissions config is a bootstrap DEFECT, not an -// operational state: without these guards bootstrap's inline genesis advance would gate-block -// SILENTLY (the emissions gate never throws, by design) and the misconfiguration would surface -// only as "epoch stuck at 0". The three cases below cover both guards independently plus the -// fully-configured pass-through. advance()'s own soft block-and-retry behavior for the ECONOMIC -// gate reasons is covered above (chkcons_survives_non_advancing_advance) and in emissions_tests. -// ═════════════════════════════════════════════════════════════════════════ - -// Guard 1 — emitcfg missing. This fixture deploys no sysio.system at all (setemitcfg / initt5 -// never ran), which is exactly the never-configured deployment shape. -BOOST_FIXTURE_TEST_CASE(bootstrap_requires_emissions_config, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL( - wasm_assert_msg("msgch::bootstrap: emissions config missing -- sysio.system::setemitcfg must run before bootstrap"), - push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); -} FC_LOG_AND_RETHROW() } - -// Guard 2 — emitcfg present, t5state missing (the half-configured deployment: setemitcfg ran, -// initt5 was forgotten). Proves the SECOND check is independently reachable and reads t5state, -// not emitcfg again: guard 1 passes here, so a wrong table/account lookup or a shadowed copy of -// the first check would surface as the WRONG diagnostic (or no throw at all). -BOOST_FIXTURE_TEST_CASE(bootstrap_requires_emissions_state, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - deploy_system_with_emitcfg(); // setemitcfg only -- initt5 deliberately not run - BOOST_REQUIRE_EQUAL( - wasm_assert_msg("msgch::bootstrap: emissions state uninitialized -- sysio.system::initt5 must run before bootstrap"), - push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); -} FC_LOG_AND_RETHROW() } - -// Both guards pass — the genesis 0 -> 1 advance actually happens. Guards that always threw (or a -// gate that never passes) would look identical to guards 1 and 2 above, so this is the case that -// pins them as guards rather than a permanent bootstrap block. -BOOST_FIXTURE_TEST_CASE(bootstrap_advances_genesis_epoch_when_emissions_configured, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); // payepoch pays WIRE out of sysio's token balance - enable_epoch_advancement(); // setemitcfg + initt5 - - // bootstrap_for_dispatch's own advance ran while emissions were unconfigured, so it - // gate-blocked and left a blocklog row for the target epoch. - BOOST_REQUIRE_EQUAL(current_epoch(), 0u); - BOOST_REQUIRE(!get_blocklog(1).is_null()); - - BOOST_REQUIRE_EQUAL(success(), - push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); - produce_blocks(); - - // The inline genesis advance passed the gate: epoch 0 -> 1, and the gate cleared the stale - // blocklog row on its way through. - BOOST_REQUIRE_EQUAL(current_epoch(), 1u); - BOOST_REQUIRE(get_blocklog(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// A forged/invalid delivery cannot strand the epoch. SEC-102's semantic-header check runs at -// INGRESS (msgch::deliver's inbound_envelope_valid gate), so a forged envelope reverts on delivery -// and records no envelope row -- it can never reach the consensus tally and leave a phantom -// consensus_reached with no outpcons row. A subsequent VALID delivery reaches consensus normally and -// chkcons proceeds to ATTEMPT advance (retry_count bumps) rather than waiting forever for a consensus -// row that an accepted-then-soft-dropped invalid winner would have left missing. Drives the -// production chkcons gate, not a direct epoch::advance. -BOOST_FIXTURE_TEST_CASE(forged_delivery_does_not_strand_chkcons, sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - const auto eth_code = fc::slug_name{"ETH"}.value; - const uint32_t epoch0 = current_epoch(); - const uint32_t target = epoch0 + 1; - - auto operator_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, - uwrit_op_eth_pubkey, - /*chain_code_v=*/ eth_code, - /*token_code_v=*/ eth_code, - /*amount=*/ 1'000'000); - const auto valid = encode_envelope_with_one_attestation( - epoch0, - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - operator_payload); - - // A forged copy: corrupt payload_checksum so the semantic header no longer recomputes. - std::vector forged; - { - sysio::opp::Envelope env; - BOOST_REQUIRE(env.ParseFromArray(valid.data(), static_cast(valid.size()))); - auto* h = env.mutable_messages(0)->mutable_header(); - std::string c = h->payload_checksum(); - c[0] ^= 0x01; - h->set_payload_checksum(c); - forged.resize(env.ByteSizeLong()); - env.SerializeToArray(forged.data(), static_cast(forged.size())); - } - - // Forged delivery is rejected at ingress -- nothing recorded, no phantom consensus. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: delivered envelope failed inbound-chain or " - "semantic-header validation"), - deliver(eth_code, forged)); - produce_blocks(); - - auto retry_count = [&]() -> uint32_t { - auto bl = get_blocklog(target); - return bl.is_null() ? 0u : bl["retry_count"].as(); - }; - - // The valid delivery (operators_per_epoch == 1 => Option-A unanimous) reaches consensus normally. - BOOST_REQUIRE_EQUAL(success(), deliver(eth_code, valid)); - produce_blocks(); - const uint32_t rc0 = retry_count(); - - // Pass the wall clock so chkcons fires advance; confirm it ATTEMPTED advance (retry_count bumps), - // i.e. it was NOT stranded waiting on a missing consensus row. Advance itself gate-blocks on - // emissions (this fixture deploys no sysio.system), exactly as in chkcons_survives_non_advancing_advance. - produce_block(fc::seconds(120)); - produce_blocks(); - BOOST_REQUIRE_EQUAL(success(), chkcons()); - produce_blocks(); - BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); - BOOST_REQUIRE_EQUAL(retry_count(), rc0 + 1); -} FC_LOG_AND_RETHROW() } - -// A complete, fully bonded candidate lacking a destination-chain authex link -// keeps its valid UIC evidence while the request remains PENDING. Once the -// operator fixes the link, an exact external UIC replay settles from the -// preserved bytes without replacing the evidence. -BOOST_FIXTURE_TEST_CASE(swap_missing_dst_authex_recovers_after_exact_uic_replay, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // registers ETH + UWRIT_OP (EVM link only) - - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 5000; - constexpr uint64_t AMOUNT = 1'000'000'000; - - setup_wire_token_and_reserves(); - - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", AMOUNT)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", AMOUNT)); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, static_cast(AMOUNT), - sol_chain, sol_token, primary, AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_SVM, - std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, sol_chain, "SOLANA", "SOL", "PRIMARY", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - BOOST_REQUIRE(!req["attestation_inbound_data"].as_string().empty()); - bool found = false; - for (const auto& c : req["commits_by"].get_array()) { - if (c["underwriter"].as_string() == UWRIT_OP.to_string()) { - found = true; - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - c["status"].as_string()); - BOOST_REQUIRE(!c["source_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(!c["dest_uic_bytes"].as_string().empty()); - } - } - BOOST_REQUIRE(found); - BOOST_REQUIRE(get_lock(1).is_null()); - - const auto solana_link_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::ed).get_public_key(); - const auto solana_link_raw = solana_link_key.get().serialize(); - BOOST_REQUIRE_EQUAL(success(), push( - AUTHEX_ACCOUNT, authex_abi, AUTHEX_ACCOUNT, "recordlink"_n, mvo() - ("account", UWRIT_OP) - ("chain_kind", ChainKind::CHAIN_KIND_SVM) - ("pub_key", solana_link_key) - ("native_address", std::vector(solana_link_raw.begin(), solana_link_raw.end())))); - produce_block(); - - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, sol_chain, "SOLANA", "SOL", "PRIMARY", dst_uic)); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(ATT_ID)["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), get_uwreq(ATT_ID)["winner"].as_string()); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - -} FC_LOG_AND_RETHROW() } - -// [P1] WSA-041: a zero AMM quote from ACTIVE reserves must FAIL CLOSED, not skip -// the variance check. createuwreq overloaded a zero `swap_quote` to mean "no LP -// provisioned, skip variance" — but a zero quote also arises from a degenerate -// ACTIVE reserve (extreme connector weights, a drained side, or — as here — a -// `src_amount` so small the weighted-Bancor kernel floors the output to 0). With -// variance skipped, the caller-supplied target_amount flowed straight into the -// uwreq and on to settlement, letting a ~0 input debit an arbitrary amount. -// Post-fix: when every required reserve is ACTIVE, a zero quote emits SWAP_REVERT -// and creates NO uwreq. (try_select_winner and drainfwq carry the same guard.) -BOOST_FIXTURE_TEST_CASE(swap_zero_quote_from_active_reserve_fails_closed, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // registers ETH - - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 6100; - - // Seeds ETH/ETH and SOLANA/SOL ACTIVE reserves at 1e12/1e12. - setup_wire_token_and_reserves(); - - // src_amount = 1 against a 1e12 reserve floors token_to_wire to 0, so the - // chained quote is 0 — but both reserves are ACTIVE. The (huge) target must - // NOT slip through on a skipped variance check. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 1, - sol_chain, sol_token, primary, /*target_amount*/ 900'000'000'000ull, - /*tolerance_bps*/ 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - // createuwreq never throws (it emits SWAP_REVERT and returns). - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - // Fail closed: no uwreq was created from the unpriceable, ACTIVE-reserve swap. - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -/// WIRE-375: a foreign-chain token named WIRE is still an outpost asset. With -/// no ACTIVE `(ETH, WIRE, PRIMARY)` reserve, the ingress quote must remain -/// unavailable instead of treating both legs as native WIRE and pricing at par. -/// Keeping the zero-quote PENDING row also pins the existing unprovisioned-LP -/// behavior and proves `required_reserves_active` uses the same chain-aware test. -BOOST_FIXTURE_TEST_CASE(swap_foreign_wire_codename_is_not_native, - sysio_dispatch_tester) { try { - constexpr uint64_t ATT_ID = 6101; - constexpr int64_t SOURCE_AMOUNT = 2'000'000'000; - bootstrap_for_dispatch(); - register_wire_depot(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, wire, primary, SOURCE_AMOUNT, - wire, wire, wire, static_cast(SOURCE_AMOUNT), - /*tolerance_bps=*/10'000, ChainKind::CHAIN_KIND_WIRE, - std::vector{}); - - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL(0u, req["dst_amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-02 (CertiK "Wire Network - Sysio Audit 1", Critical): a swap must -// settle on the AMM QUOTE, never on the caller's `target_amount`. -// -// The attack: `createuwreq` stored `dst_amount = sr.target_amount` — a number -// the caller puts in the SwapRequest — and the only guard computed the allowed -// deviation as a percentage of that SAME caller-supplied target -// (`allowed = target * target_tolerance_bps / 10000`). At 10000 bps the check -// reduces to `|quote - target| <= target`, which every target above the quote -// satisfies. `try_select_winner` then locked and `sysio.reserv::applyswap` paid -// out `req.dst_amount` verbatim, so a negligible source deposit drained the -// destination reserve. -// -// Post-fix, both halves are closed and this pins each: -// (a) the allowance is a fraction of the QUOTE, so the drain request is -// refused outright — no uwreq exists to settle; -// (b) even for a target the tolerance legitimately admits, the row carries -// the quote, so the settlement amount is not caller-controlled at all. -BOOST_FIXTURE_TEST_CASE(swap_settles_on_amm_quote_not_caller_target, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_eth_to_sol_uwreq(/*att_id*/ 9500); // registers SOLANA, reserves, collateral - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr int64_t SRC_AMOUNT = 1'000'000; - - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "SOLANA", "SOL", "PRIMARY", kDefaultUwritFeeBps); - BOOST_REQUIRE(quote > 0); - - auto submit = [&](uint64_t att_id, uint64_t target, uint32_t tolerance_bps) { - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - sol_chain, sol_token, primary, target, - tolerance_bps, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - }; - - // (a) THE EXPLOIT, verbatim: a tiny source deposit naming most of the - // destination reserve, with the 100% tolerance that used to make the check - // pass unconditionally. Refused at ingestion — no uwreq, nothing to settle. - constexpr uint64_t DRAIN_TARGET = 900'000'000'000ull; // 90% of the 1e12 reserve - submit(/*att_id*/ 9501, DRAIN_TARGET, /*tolerance_bps*/ 10'000); - BOOST_REQUIRE(get_uwreq(9501).is_null()); - - // A tolerance ABOVE 100% must not buy the caller anything either — the - // allowance is clamped, so the same drain is still refused. - submit(/*att_id*/ 9502, DRAIN_TARGET, /*tolerance_bps*/ 1'000'000); - BOOST_REQUIRE(get_uwreq(9502).is_null()); - - // (b) A target the tolerance genuinely admits (1.5x the quote at 100% - // tolerance: |quote - target| = 0.5*quote <= quote). The request is accepted - // — and the row still carries the QUOTE, not the inflated target. - const uint64_t inflated_target = quote + quote / 2; - submit(/*att_id*/ 9503, inflated_target, /*tolerance_bps*/ 10'000); - const auto accepted = get_uwreq(9503); - BOOST_REQUIRE(!accepted.is_null()); - BOOST_REQUIRE_EQUAL(quote, accepted["dst_amount"].as_uint64()); - BOOST_REQUIRE_LT(accepted["dst_amount"].as_uint64(), inflated_target); -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-02, settlement half: the destination reserve is debited by the AMM -// quote and by nothing else. The row-level assertion above proves `dst_amount` -// is the quote; this drives the full race to CONFIRMED and reads the reserve -// books, so the guarantee is pinned where the funds actually move -// (`sysio.reserv::applyswap`), which is where the drain happened. -BOOST_FIXTURE_TEST_CASE(swap_settlement_debits_destination_reserve_by_the_quote, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - // Both legs on ETH (PRIMARY -> SECOND): UWRIT_OP's only authex link is EVM, - // so an ETH destination is what lets the race actually reach settlement. - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9601; - constexpr int64_t SRC_AMOUNT = 1'000'000; - - // One bucket funds both legs, so it must cover src + quote. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - BOOST_REQUIRE(quote > 0); - - const uint64_t dst_chain_before = - find_reserve("ETH", "ETH", "SECOND")["reserve_chain_amount"].as_uint64(); - const uint64_t src_chain_before = - find_reserve("ETH", "ETH", "PRIMARY")["reserve_chain_amount"].as_uint64(); - - // Target 20% above the quote — inside a 100% tolerance, so the race resolves - // and the ONLY thing that can decide the payout is the curve. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, /*target_amount*/ quote + quote / 5, - /*tolerance_bps*/ 10'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), req["winner"].as_string()); - // The CONFIRMED row records the price the books moved at. - BOOST_REQUIRE_EQUAL(quote, req["dst_amount"].as_uint64()); - - // Destination reserve gave up exactly the quote; the source reserve took in - // exactly the source amount. Neither is the caller's target. - const uint64_t dst_chain_after = - find_reserve("ETH", "ETH", "SECOND")["reserve_chain_amount"].as_uint64(); - const uint64_t src_chain_after = - find_reserve("ETH", "ETH", "PRIMARY")["reserve_chain_amount"].as_uint64(); - BOOST_REQUIRE_EQUAL(dst_chain_before - quote, dst_chain_after); - BOOST_REQUIRE_EQUAL(src_chain_before + static_cast(SRC_AMOUNT), src_chain_after); - - // The destination lock — the collateral the winner has at risk — is sized to - // the delivered amount, so the challenge window covers the real obligation. - BOOST_REQUIRE_EQUAL(quote, get_lock(2)["amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// [P0] Review follow-up (PR #550): the slippage bound must NOT compound across -// the two checkpoints. The row's `dst_amount` is re-priced at race resolution, -// so measuring race-time drift against it would compare the settlement quote -// with the PREVIOUS quote instead of with what the user actually asked for — -// letting the price walk one tolerance-step per checkpoint. The reviewer's -// example: target 100 at 10% accepts an ingestion quote of 91 (9 <= 9.1) and -// then a settlement quote of 83 (8 <= 8.3), delivering 17% below target. -// -// `target_amount` is retained on the row precisely so this check anchors on the -// user's ORIGINAL bound, whatever path the price took. -BOOST_FIXTURE_TEST_CASE(swap_slippage_bound_does_not_compound_across_checkpoints, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9800; - constexpr int64_t SRC_AMOUNT = 1'000'000; - constexpr uint32_t TOLERANCE_BPS = 1'000; // 10% - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - - // Target ~9% above the ingestion quote — inside the 10% bound, so the request - // is accepted and the row records the quote. - const uint64_t ingestion_quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - BOOST_REQUIRE(ingestion_quote > 0); - const uint64_t target = ingestion_quote + (ingestion_quote * 9 / 100); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, target, - TOLERANCE_BPS, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL(ingestion_quote, req["dst_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(target, req["target_amount"].as_uint64()); - } - - // Move the destination reserve so the price walks a second step down: debit - // 7% of its token side (UWRIT-authorized, the same primitive settlement uses). - BOOST_REQUIRE_EQUAL(success(), push(RESERV_ACCOUNT, reserv_abi, UWRIT_ACCOUNT, "debit"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "SECOND") - ("amount", uint64_t{70'000'000'000}))); - - // Pin the scenario: the drift is inside tolerance of the PREVIOUS quote (so - // the compounding rule would have let it settle) but outside tolerance of the - // user's TARGET (so the anchored rule must reject). If the fixture's - // arithmetic ever drifts out of this window the test says so here, rather - // than silently asserting nothing. - const uint64_t settle_quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - const uint64_t allowed = settle_quote * TOLERANCE_BPS / 10'000; - BOOST_REQUIRE_LE(ingestion_quote - settle_quote, allowed); // old rule: would pass - BOOST_REQUIRE_GT(target - settle_quote, allowed); // new rule: must reject - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_REJECTED", req["status"].as_string()); - bool drift = false; - for (const auto& c : req["commits_by"].get_array()) { - if (c["underwriter"].as_string() == UWRIT_OP.to_string()) - drift = c["reason"].as_string().find("variance drift") != std::string::npos; - } - BOOST_REQUIRE(drift); -} FC_LOG_AND_RETHROW() } - -// [P1] Review follow-up (PR #550): an under-bonded candidate must not be able to -// terminally close someone else's swap. The variance / unpriceable verdicts -// refund the user and end the request, so they run only after a candidate has -// proved it could actually settle. An ACTIVE underwriter can clear the role -// minimum yet lack collateral for a particular swap; if it could trigger a -// terminal refund during transient drift, any such operator would hold a -// denial-of-service over other people's swaps. -BOOST_FIXTURE_TEST_CASE(swap_underbonded_candidate_cannot_terminally_reject, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9850; - constexpr int64_t SRC_AMOUNT = 1'000'000; - constexpr uint32_t TOLERANCE_BPS = 1'000; // 10% - - // Clears the 1-unit role minimum (so the candidate is an ACTIVE underwriter) - // but is nowhere near the src + dst aggregate this swap needs. - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 10)); - - const uint64_t ingestion_quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, ingestion_quote, - TOLERANCE_BPS, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - // Drift the price far outside the tolerance — the request IS terminally - // doomed, but this candidate must not be the one to close it. - BOOST_REQUIRE_EQUAL(success(), push(RESERV_ACCOUNT, reserv_abi, UWRIT_ACCOUNT, "debit"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "SECOND") - ("amount", uint64_t{500'000'000'000}))); - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - // Still PENDING (not REJECTED): the valid candidate was under-bonded, so - // the request remains retryable after collateral changes. - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - bool bond_retryable = false; - for (const auto& c : req["commits_by"].get_array()) { - if (c["underwriter"].as_string() == UWRIT_OP.to_string()) { - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", c["status"].as_string()); - bond_retryable = c["reason"].as_string().empty() - && !c["source_uic_bytes"].as_string().empty() - && !c["dest_uic_bytes"].as_string().empty(); - } - } - BOOST_REQUIRE(bond_retryable); -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-01 (CertiK "Wire Network - Sysio Audit 1", Critical): `opreg::prune` -// must not erase a TERMINATED operator whose collateral has not settled. -// -// `terminate` remits only the immediately-unlocked portion and deliberately -// LEAVES the locked remainder on the row, because `releaselock` settles it when -// `chklocks` frees each lock. `prune` gated on `terminated_at` + the prune delay -// alone, so any caller — it is permissionless — could erase the row first; -// `releaselock` then returns at its missing-operator check and the retained -// collateral is stranded with no attestation that could ever release it. -// `chklocks` releases at most `max_rows` locks per call, and the remainder -// drains on the next sweep. -// -// Lock expiry is inherently BURSTY: every lock is stamped -// `now + collateral_lock_duration_ms` when its race is won, so a burst of -// settlements inside one epoch produces a burst of expiries inside one epoch, -// exactly one lock-duration later. Each expired lock costs an inline -// `opreg::releaselock` dispatch plus an erase, all inside `advance`'s hard, -// uncatchable transaction CPU deadline — so an UNBOUNDED sweep of a large -// enough burst aborts `advance`, and because those same locks are still -// expired at the next advance it aborts identically forever: a permanent -// chain-wide epoch stall, not a transient one. The budget converts that into -// bounded release latency, which is harmless because the challenge window has -// already closed by the time a lock is swept. -BOOST_FIXTURE_TEST_CASE(chklocks_budget_bounds_the_sweep_and_backlog_drains, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - // A 2-minute lock window, so the locks can expire inside the test. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", kDefaultUwritFeeBps)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 0) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9750; - constexpr int64_t SRC_AMOUNT = 1'000'000; - - // Both legs on ETH — the shape whose race reaches CONFIRMED and writes a - // lock per required leg, so one won race yields the two locks this bound - // is exercised against. - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - enable_epoch_advancement(); - - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - BOOST_REQUIRE(quote > 0); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, /*target_amount*/ quote, - /*tolerance_bps*/ 10'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(ATT_ID)["status"].as_string()); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - - // Both legs are ETH/ETH, so they share one collateral bucket: the rollup - // carries their sum, and agrees with the authoritative scan of the lock - // rows it materializes. - BOOST_REQUIRE_EQUAL(uint64_t(SRC_AMOUNT) + quote, - get_lock_sum(UWRIT_OP, "ETH", "ETH")); - BOOST_REQUIRE_EQUAL(scan_lock_total(UWRIT_OP, "ETH", "ETH"), - get_lock_sum(UWRIT_OP, "ETH", "ETH")); - - // Let the lock window elapse WITHOUT an advance, so both locks are due and - // nothing has swept them yet (advance would run the full budget inline). - produce_blocks(260); // > 120s at 0.5s/block - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - - // Zero budget is a no-op even with expired locks waiting. - BOOST_REQUIRE_EQUAL(success(), chklocks_direct(0)); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - - // Budget of one releases EXACTLY one lock. Both were written by the same - // transaction and so share an `expires_at_ms`; the bound is what is under - // test here, not the tie-break between equal expiries. - BOOST_REQUIRE_EQUAL(success(), chklocks_direct(1)); - BOOST_REQUIRE(get_lock(1).is_null() != get_lock(2).is_null()); - - // The rollup decremented by exactly the released leg and still matches the - // surviving lock rows — a partial sweep leaves the two consistent. - BOOST_REQUIRE_EQUAL(scan_lock_total(UWRIT_OP, "ETH", "ETH"), - get_lock_sum(UWRIT_OP, "ETH", "ETH")); - BOOST_REQUIRE_GT(get_lock_sum(UWRIT_OP, "ETH", "ETH"), 0u); - - // A block between the two sweeps: the second call carries identical action - // data and the same signer, so without a fresh TAPOS reference block the - // two pushes hash to the same transaction id and the second is rejected as - // a duplicate. In production each sweep rides its own epoch's advance. - produce_blocks(); - - // The remainder drains on the next sweep — an oversized burst costs release - // latency, never a stalled advance. - BOOST_REQUIRE_EQUAL(success(), chklocks_direct(1)); - BOOST_REQUIRE(get_lock(1).is_null()); - BOOST_REQUIRE(get_lock(2).is_null()); - - // Bucket emptied: the rollup row is ERASED, so it reads as zero and the - // table is left holding only live buckets. - BOOST_REQUIRE_EQUAL(0u, get_lock_sum(UWRIT_OP, "ETH", "ETH")); - BOOST_REQUIRE_EQUAL(0u, scan_lock_total(UWRIT_OP, "ETH", "ETH")); - - // The releaselock fan-out still ran for both, benignly: UWRIT_OP is healthy - // (neither SLASHED nor TERMINATED), so each release is a no-op on the - // ledger and the bond is left whole — the lock rows disappearing is what - // restores `available()`. - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", op["status"].as_string()); - uint64_t total = 0; - for (const auto& b : op["balances"].get_array()) total += b["balance"].as_uint64(); - BOOST_REQUIRE_EQUAL(uint64_t{1'000'000'000}, total); - } -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(prune_keeps_terminated_operator_until_collateral_settles, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - // A 1s prune delay (vs the fixture's 10min) so the delay gate is satisfied - // within the test — isolating the SETTLEMENT gate as the only thing holding - // the row. The underwriter minimum is re-declared verbatim: setconfig - // replaces the whole config. - BOOST_REQUIRE_EQUAL(success(), - opreg_setconfig_collat(fc::variants{chain_min_bond_mvo("ETH", "ETH", 1)}, - fc::variants{}, /*prune_delay_ms*/ 1'000)); - // A 2-minute lock window, so `chklocks` can expire it inside the test. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", kDefaultUwritFeeBps)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 0) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9700; - constexpr int64_t SRC_AMOUNT = 1'000'000; - - // Both legs on ETH — UWRIT_OP is authex-linked on EVM only, so this is the - // shape whose race actually reaches CONFIRMED and writes locks. - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - enable_epoch_advancement(); - - // Win a race so UWRIT_OP holds live locks on both legs. - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - BOOST_REQUIRE(quote > 0); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, /*target_amount*/ quote, - /*tolerance_bps*/ 10'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - BOOST_REQUIRE(!get_uwreq(ATT_ID).is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(ATT_ID)["status"].as_string()); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - - // Terminate: the unlocked portion is remitted, the locked portion stays on - // the row for `releaselock`. - BOOST_REQUIRE_EQUAL(success(), terminate_op(UWRIT_OP, "audit-test termination")); - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_TERMINATED", op["status"].as_string()); - uint64_t retained = 0; - for (const auto& b : op["balances"].get_array()) retained += b["balance"].as_uint64(); - BOOST_REQUIRE_GT(retained, 0u); // exactly the locked collateral - } - - // Past the prune delay, with locks still live: the row MUST survive. This is - // the vulnerability — pre-fix the delay alone was sufficient to erase it. - produce_blocks(4); // > 1s at 0.5s/block - BOOST_REQUIRE_EQUAL(success(), opreg_prune()); - BOOST_REQUIRE(!get_operator(UWRIT_OP).is_null()); - - // Let the 2-minute lock window elapse. Each advance runs `chklocks` inline, - // which frees the expired locks and fans out `opreg::releaselock` — the - // deferred remit that drains the retained balance. - age_one_epoch(); - age_one_epoch(); - age_one_epoch(); - BOOST_REQUIRE(get_lock(1).is_null()); - BOOST_REQUIRE(get_lock(2).is_null()); - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - for (const auto& b : op["balances"].get_array()) - BOOST_REQUIRE_EQUAL(0u, b["balance"].as_uint64()); - } - - // Settled — now the row is genuinely disposable and prune erases it. - BOOST_REQUIRE_EQUAL(success(), opreg_prune()); - BOOST_REQUIRE(get_operator(UWRIT_OP).is_null()); -} FC_LOG_AND_RETHROW() } - -// [P0] Review follow-up (PR #550): `prune` is not the only path that destroys a -// TERMINATED row — `regoperator` REPLACES one to allow re-registration, and it -// strands collateral the same way. A terminated underwriter re-registering while -// its locks are live would swap the retained balance row for a fresh healthy -// one; `chklocks` then fans out `releaselock`, which settles only SLASHED / -// TERMINATED operators, sees a healthy row, and no-ops. Every terminated-row -// erase path carries the settlement precondition. -BOOST_FIXTURE_TEST_CASE(reregistration_blocked_until_collateral_settles, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", kDefaultUwritFeeBps)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 0) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9900; - constexpr int64_t SRC_AMOUNT = 1'000'000; - - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - enable_epoch_advancement(); - - // Win a race so the operator holds live locks. - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", SRC_AMOUNT, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, quote, - /*tolerance_bps*/ 10'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - BOOST_REQUIRE(!get_lock(1).is_null()); - - BOOST_REQUIRE_EQUAL(success(), terminate_op(UWRIT_OP, "audit-test termination")); - - auto reregister = [&]() { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "regoperator"_n, mvo() - ("account", UWRIT_OP.to_string()) - ("type", OperatorType::OPERATOR_TYPE_UNDERWRITER) - ("is_bootstrapped", false)); - }; - - // Locks live + balance retained: re-registration must be refused, so the - // terminated row (and the collateral it accounts for) survives intact. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: operator has unsettled collateral: a terminated " - "operator may only re-register once its balances are drained and no underwriting " - "locks remain"), - reregister()); - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_TERMINATED", op["status"].as_string()); - uint64_t retained = 0; - for (const auto& b : op["balances"].get_array()) retained += b["balance"].as_uint64(); - BOOST_REQUIRE_GT(retained, 0u); - } - - // Let the locks expire; `chklocks` -> `releaselock` drains the balance. - age_one_epoch(); - age_one_epoch(); - age_one_epoch(); - BOOST_REQUIRE(get_lock(1).is_null()); - - // Settled — re-registration is allowed again, with a clean row. - BOOST_REQUIRE_EQUAL(success(), reregister()); - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_REQUIRE(op["status"].as_string() != "OPERATOR_STATUS_TERMINATED"); -} FC_LOG_AND_RETHROW() } - -// [P1] regression (r3444213199) + WNS-02: an oversized to-WIRE target_amount -// must never reach settlement. Originally the target WAS the settlement amount, -// so a value near UINT64_MAX wrapped `dst_amount + to_wire_fee`, slipped into -// paywire, and aborted its `asset(static_cast(dst_amount))` inside -// evalcons — a chain-wide consensus stall — which the downstream -// `asset::max_amount` guard was added to catch. -// -// With settlement priced by the curve (WNS-02) the row never carries the target -// at all, and the variance check refuses these at INGRESS: the allowance is a -// fraction of the quote, so a target orders of magnitude above it fails for any -// tolerance. That is strictly stronger than the old outcome (no uwreq is created -// at all, versus one created then terminally rejected after a commit), and it -// makes the downstream asset-bound guard unreachable defense-in-depth — kept in -// the contract, but no longer constructible from a SwapRequest. -BOOST_FIXTURE_TEST_CASE(swap_to_wire_oversized_target_is_refused_at_ingress, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH source outpost + UWRIT_OP (EVM link) - register_wire_depot(); // WIRE depot => to-WIRE destination - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH/PRIMARY source reserve w/ WIRE - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - // Large enough that the source leg prices well above the kernel's floor, so - // the refusal is the variance check and not the unpriceable-reserve gate. - constexpr int64_t SRC_AMOUNT = 1'000'000; - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000'000})); - - // A valid existing WIRE recipient, so nothing earlier in the path rejects. - const std::string rs = UWRIT_OP.to_string(); - const std::vector rcpt(rs.begin(), rs.end()); - - constexpr uint64_t ASSET_MAX = (uint64_t{1} << 62) - 1; - auto run = [&](uint64_t att_id, uint64_t target) { - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - wire, wire, primary, target, - // 100x the meaningful maximum: `variance_allowance` clamps it to 100%, - // so even the widest tolerance a caller can name cannot admit a target - // this far from the quote. Pre-fix, this tolerance let it straight in. - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_WIRE, rcpt); - // createuwreq never throws — it emits SWAP_REVERT and returns. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - BOOST_REQUIRE(get_uwreq(att_id).is_null()); - }; - - run(7001, ASSET_MAX + 1); // target == asset::max_amount + 1 (boundary) - run(7002, uint64_t{1} << 63); // high bit set (negative as int64) - run(7003, ~uint64_t{0}); // UINT64_MAX — wrapped dst_amount + fee pre-fix -} FC_LOG_AND_RETHROW() } - -// WIRE-291: every protobuf-valid UIC whose embedded signature is malformed or -// unsupported must be ignored before storage without aborting rcrdcommit. -// The table covers all packed tags 0..5: fixed-size K1/R1/EM/ED truncations, -// unsupported WebAuthn/BLS shapes, and invalid K1 recovery math. These cases -// use real PENDING requests and an ACTIVE, bonded underwriter, so each action -// reaches pre-storage verification rather than an earlier guard. -BOOST_FIXTURE_TEST_CASE(swap_malformed_signature_shapes_are_ignored_before_storage, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); // to-WIRE: a single (source) required leg - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH/PRIMARY source reserve w/ WIRE - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'210; - using key_type = fc::crypto::private_key::key_type; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const std::vector> cases{ - {}, - create_truncated_uic_signature(key_type::k1), - create_truncated_uic_signature(key_type::r1), - create_packed_webauthn_uic_signature(), - create_truncated_uic_signature(key_type::em), - create_truncated_uic_signature(key_type::ed), - create_default_packed_uic_signature(), - create_invalid_uic_signature(key_type::k1), - }; - - const std::string recipient = UWRIT_OP.to_string(); - const std::vector recipient_bytes(recipient.begin(), recipient.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - /*tolerance_bps*/ 1'000'000, - ChainKind::CHAIN_KIND_WIRE, recipient_bytes); - - for (size_t i = 0; i < cases.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - - const auto uic = create_uic_with_signature( - UWRIT_OP, att_id, eth, eth, primary, cases[i]); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(att_id, UWRIT_OP, eth, - "ETH", "ETH", "PRIMARY", uic)); - - const auto req = get_uwreq(att_id); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - req["status"].as_string()); - BOOST_REQUIRE(req["commits_by"].get_array().empty()); - } - - // No malformed candidate reached lock creation or settlement. - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Every structural rejection class is stable and operator-visible. These -// payloads all reach signature verification on a real PENDING request; each -// action succeeds at the consensus level, logs its exact typed reason, and -// leaves the candidate roster untouched. -BOOST_FIXTURE_TEST_CASE(swap_signature_rejection_taxonomy_is_logged_and_non_throwing, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'250; - using key_type = fc::crypto::private_key::key_type; - using sig_type = fc::crypto::signature::sig_type; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - const std::string recipient = UWRIT_OP.to_string(); - const std::vector recipient_bytes(recipient.begin(), recipient.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, - 1'000'000, ChainKind::CHAIN_KIND_WIRE, recipient_bytes); - - struct rejection_case { - std::vector payload; - std::string_view reason; - }; - const auto ecc_sized_shape = create_packed_uic_signature(key_type::k1); - const auto ed_sized_shape = create_packed_uic_signature(key_type::ed); - const std::vector cases{ - {{}, "empty_uic"}, - {{'\x0a'}, "malformed_uic"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 2, eth, eth, primary, {}), - "missing_signature"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 3, eth, eth, primary, - create_truncated_uic_signature(key_type::k1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 4, eth, eth, primary, - create_truncated_uic_signature(key_type::r1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 5, eth, eth, primary, - create_truncated_uic_signature(key_type::em)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 6, eth, eth, primary, - create_truncated_uic_signature(key_type::ed)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 7, eth, eth, primary, - create_overlength_uic_signature(key_type::k1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 8, eth, eth, primary, - create_overlength_uic_signature(key_type::r1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 9, eth, eth, primary, - create_overlength_uic_signature(key_type::em)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 10, eth, eth, primary, - create_overlength_uic_signature(key_type::ed)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 11, eth, eth, primary, - retag_packed_uic_signature( - ecc_sized_shape, sig_type::ed)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 12, eth, eth, primary, - retag_packed_uic_signature( - ed_sized_shape, sig_type::k1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 13, eth, eth, primary, - retag_packed_uic_signature( - ed_sized_shape, sig_type::r1)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 14, eth, eth, primary, - retag_packed_uic_signature( - ed_sized_shape, sig_type::em)), - "invalid_signature_length"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 15, eth, eth, primary, - create_packed_webauthn_uic_signature()), - "unsupported_signature_type"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 16, eth, eth, primary, - create_default_packed_uic_signature< - fc::crypto::bls::signature_shim>()), - "unsupported_signature_type"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 17, eth, eth, primary, - create_corrupted_ed_uic_signature()), - "recovery_failed"}, - {create_uic_with_signature(UWRIT_OP, ATT_ID_BASE + 18, eth, eth, primary, - create_invalid_uic_signature(key_type::k1)), - "non_canonical_signature"}, - }; - - for (size_t i = 0; i < cases.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - auto payload = cases[i].payload; - // Payload-bearing cases were shaped with their final attestation id. - // The two raw malformed cases contain no signed/id-dependent content. - const auto trace = rcrdcommit_trace( - att_id, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", payload); - BOOST_REQUIRE(trace != nullptr); - BOOST_REQUIRE(!trace->except); - BOOST_REQUIRE(!trace->action_traces.empty()); - const auto expected = "reason=" + std::string{cases[i].reason}; - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find(expected)); - BOOST_REQUIRE(get_uwreq(att_id)["commits_by"].get_array().empty()); - } -} FC_LOG_AND_RETHROW() } - -// Strict protobuf canonicality rejects alternate host encodings before -// signature recovery. An absent nested address, a present default-valued nested -// address, and a zero top-level scalar all decode but normalize differently. -// Repeated unknown field keys exercise decoder behavior all the way to the 2 -// KiB boundary: decoding succeeds, canonical re-encoding drops the unknown -// keys, and the payload is rejected as non-canonical. None can create -// candidate evidence. After one honest source leg is stored, valid replays are -// write-once no-ops before signature work. -BOOST_FIXTURE_TEST_CASE(swap_noncanonical_uic_is_rejected_and_valid_leg_is_write_once, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 7'280; - setup_eth_to_sol_uwreq(ATT_ID); - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - - const auto honest = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - auto default_nested = create_uic(UWRIT_OP.to_string(), ATT_ID, eth, eth, primary); - default_nested.mutable_uw_ext_chain_addr()->set_kind( - ChainKind::CHAIN_KIND_UNKNOWN); - auto zero_scalar = create_uic(UWRIT_OP.to_string(), ATT_ID, eth, eth, primary); - zero_scalar.set_token_code(0); - auto repeated_unknown = honest; - constexpr size_t MAX_UIC_LEG_BYTES = 2048; - while (true) { - std::vector unknown_pair; - append_varint_field(unknown_pair, 9, 72); - if (repeated_unknown.size() + unknown_pair.size() > MAX_UIC_LEG_BYTES) break; - repeated_unknown.insert(repeated_unknown.end(), - unknown_pair.begin(), unknown_pair.end()); - } - sysio::opp::attestations::UnderwriteIntentCommit honest_decoded; - sysio::opp::attestations::UnderwriteIntentCommit unknown_decoded; - BOOST_REQUIRE(honest_decoded.ParseFromArray(honest.data(), honest.size())); - BOOST_REQUIRE(unknown_decoded.ParseFromArray( - repeated_unknown.data(), repeated_unknown.size())); - honest_decoded.DiscardUnknownFields(); - unknown_decoded.DiscardUnknownFields(); - BOOST_CHECK_EQUAL(honest_decoded.SerializeAsString(), - unknown_decoded.SerializeAsString()); - sysio::opp::types::WireAccount attacker_account; - attacker_account.set_name("alice"); - std::string attacker_account_bytes; - BOOST_REQUIRE(attacker_account.SerializeToString(&attacker_account_bytes)); - std::vector parser_differential; - append_length_delimited_field( - parser_differential, 1, - std::span{attacker_account_bytes.data(), attacker_account_bytes.size()}); - append_length_delimited_field(parser_differential, 15, honest); - struct alternate_encoding { - std::vector payload; - std::string_view rejection; - }; - const std::array alternates{ - alternate_encoding{ - sign_uic(std::move(default_nested), get_private_key(UWRIT_OP, "active")), - "non_canonical_uic"}, - alternate_encoding{ - sign_uic(std::move(zero_scalar), get_private_key(UWRIT_OP, "active")), - "non_canonical_uic"}, - alternate_encoding{std::move(repeated_unknown), "non_canonical_uic"}, - alternate_encoding{std::move(parser_differential), "non_canonical_uic"}, - }; - for (const auto& alternate : alternates) { - const auto trace = rcrdcommit_trace( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", alternate.payload); - BOOST_REQUIRE(!trace->except); - const auto& console = trace->action_traces.front().console; - BOOST_CHECK_MESSAGE( - console.find("reason=" + std::string{alternate.rejection}) != std::string::npos, - "unexpected rejection log: " << console); - BOOST_REQUIRE(get_uwreq(ATT_ID)["commits_by"].get_array().empty()); - // Seal the rejection before the next empty-block time jump. - produce_block(); - } - - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", honest)); - produce_block(); - const auto before = get_uwreq(ATT_ID)["commits_by"].get_array().front(); - - // Repeated canonical UICs can coexist in one relayed envelope. Pin the - // contract-side idempotency branch: every replay exits before signature - // recovery and leaves the first evidence and arrival time unchanged. - for (uint32_t replay = 0; replay < 8; ++replay) { - const auto trace = rcrdcommit_trace( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", honest); - BOOST_REQUIRE(!trace->except); - const auto& console = trace->action_traces.front().console; - BOOST_CHECK_NE(std::string::npos, - console.find("already carries a source UIC, skipping duplicate")); - BOOST_CHECK_EQUAL(std::string::npos, - console.find("UIC_SIGNATURE_REJECTED")); - const auto after = get_uwreq(ATT_ID)["commits_by"].get_array().front(); - BOOST_CHECK_EQUAL(before["source_uic_bytes"].as_string(), - after["source_uic_bytes"].as_string()); - BOOST_CHECK_EQUAL(before["source_received_at_ms"].as_uint64(), - after["source_received_at_ms"].as_uint64()); - BOOST_CHECK_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - after["status"].as_string()); - produce_block(); - } - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// K1, R1, and EM high-s alternates remain cryptographically recoverable but -// are not canonical protocol signatures. Each is logged and ignored before -// storage, with no lock or settlement side effect. -BOOST_FIXTURE_TEST_CASE(swap_high_s_ecdsa_alternates_are_rejected, - sysio_dispatch_tester) { try { - using private_key = fc::crypto::private_key; - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'290; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - const std::array keys{ - private_key::generate(private_key::key_type::k1), - private_key::generate(private_key::key_type::r1), - private_key::generate(private_key::key_type::em), - }; - std::vector active_keys; - for (const auto& key : keys) active_keys.push_back({key.get_public_key(), 1}); - set_authority(UWRIT_OP, config::active_name, - authority{1, std::move(active_keys)}, config::owner_name); - produce_blocks(); - - const std::string recipient = UWRIT_OP.to_string(); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, 1'000'000, - ChainKind::CHAIN_KIND_WIRE, - std::vector(recipient.begin(), recipient.end())); - for (size_t i = 0; i < keys.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - const auto high_s = create_high_s_signed_uic( - UWRIT_OP, keys[i], att_id, eth, eth, primary); - const auto trace = rcrdcommit_trace( - att_id, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", high_s); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=non_canonical_signature")); - BOOST_REQUIRE(get_uwreq(att_id)["commits_by"].get_array().empty()); - } - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Libfc recovery accepts the compact (31..34) and Ethereum (27..30) header -// families as aliases. The UIC boundary pins exactly one family per variant so -// the same key/signature cannot have two accepted byte representations. -BOOST_FIXTURE_TEST_CASE(swap_recovery_header_aliases_are_rejected, - sysio_dispatch_tester) { try { - using private_key = fc::crypto::private_key; - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'295; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - const std::array keys{ - private_key::generate(private_key::key_type::k1), - private_key::generate(private_key::key_type::r1), - private_key::generate(private_key::key_type::em), - }; - std::vector active_keys; - for (const auto& key : keys) active_keys.push_back({key.get_public_key(), 1}); - set_authority(UWRIT_OP, config::active_name, - authority{1, std::move(active_keys)}, config::owner_name); - produce_blocks(); - - const std::string recipient = UWRIT_OP.to_string(); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, 1'000'000, - ChainKind::CHAIN_KIND_WIRE, - std::vector(recipient.begin(), recipient.end())); - for (size_t i = 0; i < keys.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - const auto aliased = create_recovery_alias_signed_uic( - UWRIT_OP, keys[i], att_id, eth, eth, primary); - const auto trace = rcrdcommit_trace( - att_id, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", aliased); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=non_canonical_signature")); - BOOST_REQUIRE(get_uwreq(att_id)["commits_by"].get_array().empty()); - } - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// A UIC has one recoverable signature, so one minority key in a multisig -// permission cannot authorize the commitment by mere membership. The direct -// key's own weight must satisfy active or owner threshold by itself. -BOOST_FIXTURE_TEST_CASE(swap_below_threshold_permission_key_is_rejected, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7'299; - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const auto minority_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::k1); - const auto second_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::k1); - std::vector active_keys{ - {minority_key.get_public_key(), 1}, - {second_key.get_public_key(), 1}, - }; - std::sort(active_keys.begin(), active_keys.end()); - set_authority(UWRIT_OP, config::active_name, - authority{2, std::move(active_keys)}, config::owner_name); - produce_blocks(); - - const std::string recipient = UWRIT_OP.to_string(); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, 1'000'000, - ChainKind::CHAIN_KIND_WIRE, - std::vector(recipient.begin(), recipient.end())); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto uic = create_signed_uic_with_key( - UWRIT_OP, minority_key, ATT_ID, eth, eth, primary); - const auto trace = rcrdcommit_trace( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", uic); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=unauthorized_key")); - BOOST_REQUIRE(get_uwreq(ATT_ID)["commits_by"].get_array().empty()); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Winner-time verification exists for permission freshness. If the first leg's -// active key is removed before the matching leg arrives, only that older leg is -// revalidated and the candidate is durably disqualified without aborting. -BOOST_FIXTURE_TEST_CASE(swap_key_rotation_disqualifies_older_stored_leg, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 7'300; - setup_eth_to_sol_uwreq(ATT_ID); - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - - const auto old_source = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", old_source)); - - const auto replacement_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::k1); - set_authority(UWRIT_OP, config::active_name, - authority{replacement_key.get_public_key()}, config::owner_name); - produce_blocks(); - - const auto new_destination = create_signed_uic_with_key( - UWRIT_OP, replacement_key, ATT_ID, sol, sol_token, primary); - const auto trace = rcrdcommit_trace( - ATT_ID, UWRIT_OP, sol, "SOLANA", "SOL", "PRIMARY", new_destination); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=unauthorized_key")); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - const auto& candidate = req["commits_by"].get_array().front(); - BOOST_CHECK_EQUAL("UNDERWRITE_STATUS_DISQUALIFIED", candidate["status"].as_string()); - BOOST_CHECK_EQUAL("invalid underwrite-intent-commit source signature: unauthorized_key", - candidate["reason"].as_string()); - - // Advance past duplicate-transaction detection before replaying the same - // action bytes; the assertion below is about contract state, not mempool - // transaction de-duplication. - produce_blocks(); - - const auto assert_disqualification_unchanged = [&]() { - const auto current = get_uwreq(ATT_ID)["commits_by"].get_array().front(); - BOOST_CHECK_EQUAL(candidate["status"].as_string(), current["status"].as_string()); - BOOST_CHECK_EQUAL(candidate["reason"].as_string(), current["reason"].as_string()); - BOOST_CHECK_EQUAL(candidate["source_received_at_ms"].as_uint64(), - current["source_received_at_ms"].as_uint64()); - BOOST_CHECK_EQUAL(candidate["dest_received_at_ms"].as_uint64(), - current["dest_received_at_ms"].as_uint64()); - BOOST_CHECK_EQUAL(candidate["source_uic_bytes"].as_string(), - current["source_uic_bytes"].as_string()); - BOOST_CHECK_EQUAL(candidate["dest_uic_bytes"].as_string(), - current["dest_uic_bytes"].as_string()); - }; - - // An identical, otherwise valid replay cannot re-arm a disqualified entry. - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, sol, "SOLANA", "SOL", "PRIMARY", new_destination)); - assert_disqualification_unchanged(); - - // Neither malformed nor unauthorized changed replacements can re-arm it. - const auto malformed_source = create_uic_with_signature( - UWRIT_OP, ATT_ID, eth, eth, primary, - {packed_uic_signature_tag(fc::crypto::signature::sig_type::k1)}); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", malformed_source)); - assert_disqualification_unchanged(); - const auto forged_source = create_signed_uic_as( - UWRIT_OP, BATCHOP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", forged_source)); - assert_disqualification_unchanged(); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// The freshness rule is symmetric: when the destination arrives first, a -// permission rotation before the source arrives durably disqualifies that -// older destination evidence and produces no settlement side effect. -BOOST_FIXTURE_TEST_CASE(swap_destination_key_rotation_disqualifies_older_stored_leg, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 7'301; - setup_eth_to_sol_uwreq(ATT_ID); - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - - const auto old_destination = create_signed_uic( - UWRIT_OP, ATT_ID, sol, sol_token, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, sol, "SOLANA", "SOL", "PRIMARY", old_destination)); - - const auto replacement_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::k1); - set_authority(UWRIT_OP, config::active_name, - authority{replacement_key.get_public_key()}, config::owner_name); - produce_blocks(); - - const auto new_source = create_signed_uic_with_key( - UWRIT_OP, replacement_key, ATT_ID, eth, eth, primary); - const auto trace = rcrdcommit_trace( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", new_source); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=unauthorized_key")); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - const auto& candidate = req["commits_by"].get_array().front(); - BOOST_CHECK_EQUAL("UNDERWRITE_STATUS_DISQUALIFIED", candidate["status"].as_string()); - BOOST_CHECK_EQUAL( - "invalid underwrite-intent-commit destination signature: unauthorized_key", - candidate["reason"].as_string()); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// The WIRE provider key family includes four fixed-size recoverable variants. -// Pin the full contract boundary (packing, direct variant construction, host -// recovery, and active-permission comparison) for K1, R1, EM, and ED. -BOOST_FIXTURE_TEST_CASE(swap_fixed_size_recoverable_signatures_are_accepted, - sysio_dispatch_tester) { try { - using private_key = fc::crypto::private_key; - - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'230; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const std::array signing_keys{ - private_key::from_string( - "PVT_K1_2Nb6TgdiEJopY3dCFYhrA46NzXC3DSWzznes1Dc71oWF29tJTt"), - private_key::from_string( - "PVT_R1_iyQmnyPEGvFd8uffnk152WC2WryBjgTrg22fXQryuGL9mU6qW"), - private_key::from_string( - "PVT_EM_0x4646464646464646464646464646464646464646464646464646464646464646"), - private_key::from_string( - "4YFq9y5f5hi77Bq8kDCE6VgqoAqKGSQN87yW9YeGybpNfqKUG4WxnwhboHGUeXjY7g8262mhL1kCCM9yy8uGvdj7", - private_key::key_type::ed), - }; - constexpr std::array EXPECTED_PACKED_TAGS{0, 1, 3, 4}; - std::vector active_keys; - active_keys.reserve(signing_keys.size()); - for (const auto& key : signing_keys) { - active_keys.push_back({key.get_public_key(), 1}); - } - set_authority(UWRIT_OP, config::active_name, - authority{1, std::move(active_keys)}, config::owner_name); - produce_blocks(); - - const std::string recipient = UWRIT_OP.to_string(); - const std::vector recipient_bytes(recipient.begin(), recipient.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - /*tolerance_bps*/ 1'000'000, - ChainKind::CHAIN_KIND_WIRE, recipient_bytes); - - for (size_t i = 0; i < signing_keys.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - const auto uic = create_signed_uic_with_key( - UWRIT_OP, signing_keys[i], att_id, eth, eth, primary); - sysio::opp::attestations::UnderwriteIntentCommit decoded; - BOOST_REQUIRE(decoded.ParseFromArray(uic.data(), static_cast(uic.size()))); - BOOST_REQUIRE(!decoded.signature().empty()); - BOOST_CHECK_EQUAL(EXPECTED_PACKED_TAGS[i], - static_cast(decoded.signature().front())); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(att_id, UWRIT_OP, eth, - "ETH", "ETH", "PRIMARY", uic)); - - const auto req = get_uwreq(att_id); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - req["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), req["winner"].as_string()); - } -} FC_LOG_AND_RETHROW() } - -// Owner remains an authorization ancestor of active, so a UIC signed by the -// claimed underwriter's owner key is accepted by the permission-authority -// check even though underwriters normally configure an active key provider. -BOOST_FIXTURE_TEST_CASE(swap_owner_permission_signature_is_accepted, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7'240; - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const std::string recipient = UWRIT_OP.to_string(); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, 1'000'000, - ChainKind::CHAIN_KIND_WIRE, - std::vector(recipient.begin(), recipient.end())); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto owner_signed = create_signed_uic_with_key( - UWRIT_OP, get_private_key(UWRIT_OP, "owner"), - ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", owner_signed)); - - const auto req = get_uwreq(ATT_ID); - BOOST_CHECK_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - req["status"].as_string()); - BOOST_CHECK_EQUAL(UWRIT_OP.to_string(), req["winner"].as_string()); -} FC_LOG_AND_RETHROW() } - -// Authorization is independent of signature encoding. Unrelated K1, R1, EM, -// and ED keys all produce structurally valid UICs, but none may claim the -// underwriter account or create candidate/lock state. -BOOST_FIXTURE_TEST_CASE(swap_unrelated_supported_keys_are_unauthorized, - sysio_dispatch_tester) { try { - using private_key = fc::crypto::private_key; - bootstrap_for_dispatch(); - register_wire_depot(); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID_BASE = 7'245; - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const std::string recipient = UWRIT_OP.to_string(); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, wire, wire, primary, 50, 1'000'000, - ChainKind::CHAIN_KIND_WIRE, - std::vector(recipient.begin(), recipient.end())); - const std::array unrelated_keys{ - private_key::generate(private_key::key_type::k1), - private_key::generate(private_key::key_type::r1), - private_key::generate(private_key::key_type::em), - private_key::generate(private_key::key_type::ed), - }; - - for (size_t i = 0; i < unrelated_keys.size(); ++i) { - const uint64_t att_id = ATT_ID_BASE + i; - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - const auto forged = create_signed_uic_with_key( - UWRIT_OP, unrelated_keys[i], att_id, eth, eth, primary); - const auto trace = rcrdcommit_trace( - att_id, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", forged); - BOOST_REQUIRE(!trace->except); - BOOST_CHECK_NE(std::string::npos, - trace->action_traces.front().console.find("reason=unauthorized_key")); - BOOST_REQUIRE(get_uwreq(att_id)["commits_by"].get_array().empty()); - } - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Source and destination UICs are pre-validated independently. Exercise the -// destination branch with a valid source commit followed by a truncated ED -// destination signature; the bad record is ignored, the valid source bytes -// remain stored, and neither leg reaches lock creation. -BOOST_FIXTURE_TEST_CASE(swap_malformed_destination_signature_preserves_valid_source, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push( - CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7'219; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "SOLANA", "SOL", uint64_t{1'000'000})); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - /*tolerance_bps*/ 1'000'000, - ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto valid_source = - create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, - "ETH", "ETH", "PRIMARY", valid_source)); - - const auto malformed_destination = create_uic_with_signature( - UWRIT_OP, ATT_ID, sol_chain, sol_token, primary, - create_truncated_uic_signature(fc::crypto::private_key::key_type::ed)); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, sol_chain, - "SOLANA", "SOL", "PRIMARY", malformed_destination)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - req["status"].as_string()); - bool found = false; - for (const auto& c : req["commits_by"].get_array()) { - if (c["underwriter"].as_string() != UWRIT_OP.to_string()) continue; - found = true; - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - c["status"].as_string()); - BOOST_REQUIRE(c["source_received_at_ms"].as_uint64() > 0); - BOOST_REQUIRE_EQUAL(uint64_t{0}, c["dest_received_at_ms"].as_uint64()); - BOOST_REQUIRE(c["reason"].as_string().empty()); - } - BOOST_REQUIRE(found); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// WIRE-291 full-path regression: the first UIC in one consensus envelope has -// a truncated K1 shape that previously reached `ds >> signature` -// and aborted evalcons. A valid commit follows in the SAME envelope. The bad -// record must be ignored before storage, and the valid record must still win, -// proving dispatch continued past the malformed record. -BOOST_FIXTURE_TEST_CASE(malformed_uic_does_not_abort_later_attestations_in_same_envelope, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); // to-WIRE: a single (source) required leg - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH/PRIMARY source reserve w/ WIRE - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7'220; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - - const std::string recipient = UWRIT_OP.to_string(); - const std::vector recipient_bytes(recipient.begin(), recipient.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - /*tolerance_bps*/ 1'000'000, - ChainKind::CHAIN_KIND_WIRE, recipient_bytes); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto truncated_uic = create_uic_with_signature( - UWRIT_OP, ATT_ID, eth, eth, primary, - {packed_uic_signature_tag(fc::crypto::signature::sig_type::k1)}); - const auto valid_uic = create_signed_uic( - UWRIT_OP, ATT_ID, eth, eth, primary); - - const std::string truncated_bytes(truncated_uic.begin(), truncated_uic.end()); - const std::string valid_bytes(valid_uic.begin(), valid_uic.end()); - BOOST_REQUIRE_EQUAL(success(), deliver( - /*proven=*/ eth, - encode_envelope_with_attestations( - current_epoch(), - AttestationType::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - {truncated_bytes, valid_bytes}))); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - req["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), req["winner"].as_string()); - BOOST_REQUIRE(!get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// A UIC's `uw_account` remains a signed claim at the depot boundary. Current -// outposts decode and canonicalize each UIC, authenticate its chain-native -// submitter, and bind that caller to the claimed ACTIVE WIRE roster entry before -// relaying the original bytes. The depot independently repeats the current -// permission-signature check as defense in depth against a compromised, -// outdated, or misconfigured outpost. A forged direct delivery is ignored -// before mutation, so the untouched source leg can still pair with the honest -// destination and win. -BOOST_FIXTURE_TEST_CASE(swap_forged_claim_cannot_overwrite_honest_candidate, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push( - CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - const auto solana_link_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::ed).get_public_key(); - const auto solana_link_raw = solana_link_key.get().serialize(); - BOOST_REQUIRE_EQUAL(success(), push( - AUTHEX_ACCOUNT, authex_abi, AUTHEX_ACCOUNT, "recordlink"_n, mvo() - ("account", UWRIT_OP) - ("chain_kind", ChainKind::CHAIN_KIND_SVM) - ("pub_key", solana_link_key) - ("native_address", std::vector(solana_link_raw.begin(), solana_link_raw.end())))); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7200; - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "SOLANA", "SOL", uint64_t{1'000'000})); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - /*tolerance_bps*/ 1'000'000, - ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto honest_source = - create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, - "ETH", "ETH", "PRIMARY", honest_source)); - - const auto before_forgery = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - before_forgery["status"].as_string()); - BOOST_REQUIRE_EQUAL(1u, before_forgery["commits_by"].get_array().size()); - const auto& honest_candidate = before_forgery["commits_by"].get_array().front(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - honest_candidate["status"].as_string()); - const auto stored_source_before = honest_candidate["source_uic_bytes"].as_string(); - const auto source_received_before = - honest_candidate["source_received_at_ms"].as_uint64(); - BOOST_REQUIRE(source_received_before > 0); - - // This UIC claims UWRIT_OP but is signed by the unrelated batchop.a key. - // Drive the real envelope path so dispatch derives UWRIT_OP from the forged - // field before signature recovery rejects it. - const auto forged_source = - create_signed_uic_as(UWRIT_OP, BATCHOP, ATT_ID, eth, eth, primary); - const std::string forged_source_bytes(forged_source.begin(), forged_source.end()); - BOOST_REQUIRE_EQUAL(success(), - deliver( - /*proven=*/ eth, - encode_envelope_with_one_attestation( - current_epoch(), - AttestationType::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT, - forged_source_bytes))); - - const auto after_forgery = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - after_forgery["status"].as_string()); - BOOST_REQUIRE_EQUAL(1u, after_forgery["commits_by"].get_array().size()); - const auto& candidate_after = after_forgery["commits_by"].get_array().front(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - candidate_after["status"].as_string()); - BOOST_REQUIRE_EQUAL(stored_source_before, - candidate_after["source_uic_bytes"].as_string()); - BOOST_REQUIRE_EQUAL(source_received_before, - candidate_after["source_received_at_ms"].as_uint64()); - BOOST_REQUIRE(candidate_after["reason"].as_string().empty()); - - const auto honest_destination = - create_signed_uic(UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, sol_chain, - "SOLANA", "SOL", "PRIMARY", honest_destination)); - - const auto confirmed = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - confirmed["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), confirmed["winner"].as_string()); -} FC_LOG_AND_RETHROW() } - -// ── Underwriter role + activation gate at commit ingestion ─────────────────── -// -// Only an ACTIVE UNDERWRITER (opreg type == UNDERWRITER && status == ACTIVE) can -// win a race, so `rcrdcommit` refuses to record a commit_entry for anything else: -// a UIC from a non-underwriter, or a not-yet-active underwriter, is dropped before -// any mutation. This bounds `commits_by` to the registered active-underwriter set, -// so a matched-leg UIC cannot append one row per attacker-chosen valid name. -// `try_select_winner` keeps the same eligibility check as defensive depth. Both -// cases below carry real ETH bond and a valid self-signed UIC; the ingestion gate -// drops them (no row, no lock) and leaves the race PENDING (reclaimable) for a -// genuine winner. The positive control — an ACTIVE underwriter that wins — is -// `swap_same_token_legs_exact_balance_wins`. - -// A non-underwriter operator that is fully ACTIVE and bonded — a funded PRODUCER — -// cannot have a commit recorded even with a valid self-signed UIC. Activating it -// (status ACTIVE) isolates the op.type half of the gate: a status-only check would -// let it through, so this test fails if the type check is dropped. -BOOST_FIXTURE_TEST_CASE(swap_commit_non_underwriter_type_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH source outpost + UWRIT_OP - register_wire_depot(); // to-WIRE: a single (source) required leg - - // Require producer ETH collateral too, so funding the producer below promotes - // it to ACTIVE via opreg::processprod (the underwriter requirement stays as - // bootstrap set it). - BOOST_REQUIRE_EQUAL(success(), opreg_setconfig_collat( - /*req_uw_collat=*/ fc::variants{chain_min_bond_mvo("ETH", "ETH", 1)}, - /*req_prod_collat=*/ fc::variants{chain_min_bond_mvo("ETH", "ETH", 1)})); - - // A second operator registered as a PRODUCER (NOT an underwriter). Privileged - // opreg self-registration skips the authex-link precondition. - const name PRODOP = "prodop.a"_n; - create_account(PRODOP); - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, - "regoperator"_n, mvo() - ("account", PRODOP.to_string()) - ("type", OperatorType::OPERATOR_TYPE_PRODUCER) - ("is_bootstrapped", false))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7400; - - // Fund ETH bond: covers the source leg AND clears req_prod_collat, so - // processprod flips PRODOP to ACTIVE. The candidate is now an ACTIVE, - // sufficiently-bonded operator that is simply the wrong role — only op.type - // can disqualify it. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(PRODOP, "ETH", "ETH", uint64_t{1'000'000})); - { - const auto op = get_operator(PRODOP); - BOOST_REQUIRE_EQUAL("OPERATOR_TYPE_PRODUCER", op["type"].as_string()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", op["status"].as_string()); - } - - const std::string rs = UWRIT_OP.to_string(); - const std::vector rcpt(rs.begin(), rs.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - 5000, ChainKind::CHAIN_KIND_WIRE, rcpt); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - // Valid UIC self-signed by PRODOP — signature recovery passes, so only the - // eligibility gate can reject it. - const auto uic = create_signed_uic(PRODOP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, PRODOP, eth, "ETH", "ETH", "PRIMARY", uic)); - - // rcrdcommit drops the commit at ingestion — an ACTIVE PRODUCER is the wrong role, so it is not - // an ACTIVE underwriter. No commit_entry is created (no row growth), the uwreq stays PENDING, and - // no lock is written. - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(0u, req["commits_by"].get_array().size()); - BOOST_REQUIRE(get_lock(1).is_null()); // no lock written -} FC_LOG_AND_RETHROW() } - -// A registered UNDERWRITER that has NOT cleared its activation threshold (status -// UNKNOWN) cannot have a commit recorded, even funded on the swap's leg. Requiring -// an additional unfunded collateral pair (SOLANA/SOL) keeps UWRIT_OP inactive while -// it still holds ample ETH bond — isolating the activation gate from the bond check. -BOOST_FIXTURE_TEST_CASE(swap_commit_inactive_underwriter_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); - // Require ETH AND SOLANA collateral, so funding ETH alone no longer activates. - BOOST_REQUIRE_EQUAL(success(), opreg_setconfig_collat(fc::variants{ - chain_min_bond_mvo("ETH", "ETH", 1), - chain_min_bond_mvo("SOLANA", "SOL", 1)})); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7500; - - // Ample ETH bond for the source leg, but SOLANA stays unfunded → meets_role_min - // is false → UWRIT_OP never reaches ACTIVE. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE_EQUAL("OPERATOR_TYPE_UNDERWRITER", op["type"].as_string()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_UNKNOWN", op["status"].as_string()); - } - - const std::string rs = UWRIT_OP.to_string(); - const std::vector rcpt(rs.begin(), rs.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - 5000, ChainKind::CHAIN_KIND_WIRE, rcpt); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", uic)); - - // rcrdcommit drops the commit at ingestion — UWRIT_OP is a registered underwriter but not yet - // ACTIVE. No commit_entry is created, the uwreq stays PENDING, and no lock is written. - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(0u, req["commits_by"].get_array().size()); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// Regression (r3444212155): a malformed inbound SwapRequest must NOT abort the -// consensus-tipping delivery. createuwreq logs + skips (no row, no throw) when -// the payload fails to decode — it cannot be refunded either, since the revert -// needs the undecodable actor / source_amount. -BOOST_FIXTURE_TEST_CASE(createuwreq_malformed_swaprequest_does_not_abort, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - constexpr uint64_t ATT_ID = 7300; - // A length-delimited protobuf field (tag 0x0a) claiming far more bytes than - // are present — the decoder underruns the buffer and returns an error. (Even - // a tolerant decoder yields random fields that name no registered chain, so - // no row is created either way.) - const std::vector garbage{'\x0a', '\x80', '\x80', '\x80', '\x80', '\x08'}; - const std::string garbage_str(garbage.begin(), garbage.end()); - // Must NOT throw (a check() here would abort the delivery before any row - // exists) and must NOT create a uwreq row. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, garbage_str)); - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -// The consensus-envelope analogue of the case above. msgch's dispatch forwards SWAP_REQUEST bytes -// to createuwreq UNDECODED, so createuwreq's own decode guard is all that stands between -// attacker-shaped attestation bytes and a throw that would abort the consensus-tipping delivery -// (and drop the whole epoch's inbound dispatch with it). Three malformed shapes ride ONE envelope -// through the full deliver -> evalcons -> apply_consensus -> inline-createuwreq chain, ahead of a -// fully valid swap: deliver must succeed, the malformed entries must create no uwreq rows, and the -// valid entry behind them must still create exactly one. -BOOST_FIXTURE_TEST_CASE(swap_request_malformed_bytes_do_not_abort_consensus_delivery, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH source outpost - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - - // Empty bytes: proto3 decodes zero fields into an all-defaults SwapRequest whose - // source_chain_code (0) can never match the proven outpost -- refunded/dropped, never thrown. - const std::string empty_bytes{}; - // A lone length-delimited field tag with its length varint missing -- the decoder underruns. - const std::string truncated_tag{"\x0a"}; - // Deterministic junk: the leading tag varint carries wire type 6, which protobuf does not define. - const std::string junk_bytes{"\xde\xad\xbe\xef\x42"}; - // Fully valid ETH->SOLANA swap, placed LAST so every malformed entry dispatches before it. - const auto valid_sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - - // The envelope's four attestations mint sequential ids starting here (reserve registration - // above already consumed ids for its outbound RESERVE_READY queueouts). - const uint64_t first_att_id = next_att_id(); - - // deliver() must SUCCEED -- each malformed payload is dropped inside its inline createuwreq, - // never thrown up through the dispatch chain. - BOOST_REQUIRE_EQUAL(success(), deliver(/*proven=*/ eth, - encode_envelope_with_attestations(current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_SWAP_REQUEST, - {empty_bytes, truncated_tag, junk_bytes, valid_sr}))); - - // The three malformed entries created no uwreq; the valid one behind them created exactly - // one, keyed by its minted id. - BOOST_REQUIRE(get_uwreq(first_att_id + 0).is_null()); - BOOST_REQUIRE(get_uwreq(first_att_id + 1).is_null()); - BOOST_REQUIRE(get_uwreq(first_att_id + 2).is_null()); - const auto req = get_uwreq(first_att_id + 3); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(eth, req["src_chain_code"].as().value); - BOOST_REQUIRE_EQUAL(100u, req["src_amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// Duplicate-id idempotency: re-delivering an attestation_id that already has a uwreq row -- the -// protocol's normal every-cron-tick re-relay -- must no-op WITHOUT throwing and WITHOUT touching -// the existing row, even when the re-delivery carries different (or malformed) bytes. The -// duplicate guard runs before the decode, so a garbage duplicate is skipped by id alone. -BOOST_FIXTURE_TEST_CASE(createuwreq_duplicate_attestation_id_is_idempotent, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM)("code", "SOLANA") - ("external_chain_id", 900)("name", std::string("solana-test"))("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 1'000'000'000)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", 1'000'000'000)); - - const auto eth = fc::slug_name{"ETH"}.value; - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - const auto primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 7400; - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - sol_chain, sol_token, primary, /*target*/ 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto row_before = get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwreqs"_n, ATT_ID); - BOOST_REQUIRE(!row_before.empty()); - - // Same id, different payload (amounts 100 -> 250): the duplicate must be skipped, not - // overwritten -- the row stays byte-identical to the original delivery. - const auto sr_conflicting = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0c'), - eth, eth, primary, /*src_amount*/ 250, - sol_chain, sol_token, primary, /*target*/ 250, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0d')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr_conflicting)); - BOOST_REQUIRE(get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwreqs"_n, ATT_ID) == row_before); - - // Same id, malformed payload: skipped by id before the decode ever runs. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, std::string{"\x0a"})); - BOOST_REQUIRE(get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwreqs"_n, ATT_ID) == row_before); -} FC_LOG_AND_RETHROW() } - -// ───────────────────────────── WSA-028: signed TokenAmount ingress ───────────────────────────── -// -// OPP TokenAmount.amount is signed on the wire (int64 / vint64_t). The historical foot-gun was -// static_cast(static_cast(amount)): a negative value such as -1 wraps to -// 18446744073709551615, an impossible "balance" that sails through zero-only guards and inflates -// collateral / reserve / settlement accounting. Every value-bearing ingress path now routes the -// amount through sysio::opp::safe::to_depot_amount, which rejects amount <= 0 AND amount > -// asset::max_amount before any unsigned use. These cases drive malformed amounts through the real -// dispatch paths and assert the depot applies NO value-bearing effect — and never throws (a throw -// inside the evalcons dispatch chain stalls consensus chain-wide). The positive controls are -// dispatch_routes_deposit_to_opreg / dispatch_routes_withdraw_request_to_opreg above. - -// DEPOSIT_REQUEST: a valid +1,000,000 deposit rides one envelope alongside two malformed amounts — -// -1 (wraps to UINT64_MAX, the amount <= 0 branch) and 2^62 (== asset::max_amount + 1, the -// out-of-range branch). The valid deposit credits EXACTLY 1,000,000; neither malformed amount -// credits anything (a wrapped credit would make the final balance differ from the valid amount), and -// deliver never throws. -BOOST_FIXTURE_TEST_CASE(operator_action_negative_deposit_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - constexpr int64_t VALID_DEPOSIT = 1'000'000; - constexpr int64_t ASSET_MAX_PLUS_ONE = int64_t{1} << 62; // sysio::asset::max_amount + 1 - const auto eth_code = fc::slug_name{"ETH"}.value; - - auto mk = [&](int64_t amount) { - return encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, - eth_code, eth_code, amount); - }; - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, - encode_envelope_with_attestations(current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - {mk(VALID_DEPOSIT), mk(-1), mk(ASSET_MAX_PLUS_ONE)}))); - - auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - auto bal = find_balance(op, "ETH", "ETH"); - BOOST_REQUIRE(!bal.is_null()); - // Exactly the valid amount — the wrapped -1 and the out-of-range 2^62 credited nothing. - BOOST_REQUIRE_EQUAL(static_cast(VALID_DEPOSIT), bal["balance"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// WITHDRAW_REQUEST: a valid deposit funds the operator, then a wrapped -1 withdraw rides the same -// envelope. The deposit credits; the negative withdraw is dropped — no row is queued (and no -// successful action log is appended). Positive control: dispatch_routes_withdraw_request_to_opreg. -BOOST_FIXTURE_TEST_CASE(operator_action_negative_withdraw_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - constexpr int64_t INITIAL_DEPOSIT = 5'000'000; - const auto eth_code = fc::slug_name{"ETH"}.value; - - auto deposit_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, - eth_code, eth_code, INITIAL_DEPOSIT); - auto neg_wtdw_payload = encode_operator_action( - sysio::opp::attestations::OperatorAction::ACTION_TYPE_WITHDRAW_REQUEST, - sysio::opp::types::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, - eth_code, eth_code, /*amount=*/ -1); - - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, - encode_envelope_with_attestations(current_epoch(), - sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - {deposit_payload, neg_wtdw_payload}))); - - // The deposit credited normally... - auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - auto bal = find_balance(op, "ETH", "ETH"); - BOOST_REQUIRE(!bal.is_null()); - BOOST_REQUIRE_EQUAL(static_cast(INITIAL_DEPOSIT), bal["balance"].as_uint64()); - // ...but the wrapped-negative withdraw queued nothing. - BOOST_REQUIRE(get_wtdw(/*request_id=*/1).is_null()); -} FC_LOG_AND_RETHROW() } - -// SWAP_REQUEST: a wrapped -1 source_amount must REVERT (refund on the proven outpost) and create no -// uwreq — never wrap into a huge src_amount that corrupts the swap quote / reserve settlement. Mirrors -// swap_zero_quote_from_active_reserve_fails_closed; createuwreq never throws (it emits SWAP_REVERT). -BOOST_FIXTURE_TEST_CASE(swap_request_negative_source_is_reverted, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // registers ETH - - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 6200; - - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH and SOLANA/SOL reserves - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ -1, - sol_chain, sol_token, primary, /*target_amount*/ 900'000'000'000ull, - /*tolerance_bps*/ 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - // createuwreq never throws (it emits SWAP_REVERT and returns). - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - // Fail closed: the wrapped-negative source created no pending uwreq. - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -// ── Same-token underwriter overcommit (one collateral bucket, two legs) ────── -// -// Underwriter collateral is held per (underwriter, chain_code, token_code) — -// NOT per reserve_code. A swap whose source and destination legs share one -// (chain, token) bucket but use different reserve_code values (a shape -// rcrdcommit explicitly routes) draws BOTH locks against that single balance. -// The winner check must require availability to cover the AGGREGATE of both -// legs; checking each leg independently lets a balance covering each single leg -// but not their sum win and overcommit the bucket. - -// Balance 150 covers each single 100-leg but not the 200 aggregate. The valid -// candidate remains PENDING after the failed capacity check; a top-up followed -// by an exact external UIC replay settles from the stored UICs. -BOOST_FIXTURE_TEST_CASE(swap_same_token_legs_overcommit_recovers_after_top_up_replay, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // ETH chain + UWRIT_OP (EVM authex link) - // Both reserves must be ACTIVE and priceable: the resolver computes the live - // quote before the bond check so the bond covers the actual obligation, then - // applies request-level quote verdicts only after that candidate gate. This - // setup keeps the test specifically on the aggregate-collateral path. - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 8000; - constexpr int64_t SRC_AMOUNT = 100; - constexpr uint64_t DST_AMOUNT = 100; - // One (ETH, ETH) bucket holds 150 against an aggregate need of - // `src_amount + quote(src_amount)` — just under 200, so 150 cannot cover it. - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 150)); - - // Same-(chain, token) swap between two reserves on the one ETH outpost. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, SRC_AMOUNT, - eth, eth, secondary, DST_AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - bool found = false; - for (const auto& c : req["commits_by"].get_array()) { - if (c["underwriter"].as_string() == UWRIT_OP.to_string()) { - found = true; - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - c["status"].as_string()); - BOOST_REQUIRE(c["reason"].as_string().empty()); - BOOST_REQUIRE(!c["source_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(!c["dest_uic_bytes"].as_string().empty()); - } - } - BOOST_REQUIRE(found); - BOOST_REQUIRE(get_lock(1).is_null()); // no locks written - - // More collateral makes this already-proven candidate eligible. An exact - // replay revalidates both stored signatures and writes the two locks. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", 50)); - produce_block(); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - - const auto confirmed = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - confirmed["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), confirmed["winner"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), get_lock(1)["underwriter"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), get_lock(2)["underwriter"].as_string()); - BOOST_REQUIRE_EQUAL(ATT_ID, get_lock(1)["uwreq_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(ATT_ID, get_lock(2)["uwreq_id"].as_uint64()); - BOOST_REQUIRE(get_lock(3).is_null()); -} FC_LOG_AND_RETHROW() } - -// Reserve liquidity belongs to the request, not to an underwriter candidate. -// If it changes after request admission but before the second UIC reaches the -// depot, the live AMM re-quote turns the drain into price drift before the -// defensive raw-liquidity precheck. A fully bonded candidate may therefore -// trigger the request-level rejection/refund, releasing compact candidate -// metadata instead of leaving a complete row with no retry or wake-up path. -BOOST_FIXTURE_TEST_CASE(swap_race_time_reserve_drain_rejects_request, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - const auto solana_link_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::ed).get_public_key(); - BOOST_REQUIRE_EQUAL(success(), push( - AUTHEX_ACCOUNT, authex_abi, AUTHEX_ACCOUNT, "recordlink"_n, mvo() - ("account", UWRIT_OP) - ("chain_kind", ChainKind::CHAIN_KIND_SVM) - ("pub_key", solana_link_key) - ("native_address", std::vector(32, '\x0b')))); - setup_wire_token_and_reserves(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 8004; - constexpr uint64_t AMOUNT = 1'000'000'000; - - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", AMOUNT)); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "SOLANA", "SOL", AMOUNT)); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, static_cast(AMOUNT), - sol_chain, sol_token, primary, AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_SVM, - std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - // The request was admitted against 1e12 units. Drain the destination only - // after admission, while its two UICs are notionally in flight. The live - // quote remains positive but falls outside the fixed target's tolerance, so - // the candidate passes its bond gate before the request is rejected. - constexpr uint64_t SEEDED_RESERVE = 1'000'000'000'000ull; - BOOST_REQUIRE_EQUAL(success(), debit_reserve_chain( - "SOLANA", "SOL", "PRIMARY", SEEDED_RESERVE - (AMOUNT - 1))); - - const auto src_uic = create_signed_uic( - UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic( - UWRIT_OP, ATT_ID, sol_chain, sol_token, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, sol_chain, "SOLANA", "SOL", "PRIMARY", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_REJECTED", - req["status"].as_string()); - BOOST_REQUIRE(req["attestation_inbound_data"].as_string().empty()); - const auto candidate = req["commits_by"].get_array().front(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_RELEASED", - candidate["status"].as_string()); - BOOST_CHECK_NE(std::string::npos, - candidate["reason"].as_string().find("variance drift")); - BOOST_REQUIRE(candidate["source_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(candidate["dest_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// An exact external replay is a fresh outpost trigger. An adverse price move -// discovered while re-evaluating the immutable UIC pair therefore rejects and -// refunds this otherwise eligible request. -BOOST_FIXTURE_TEST_CASE(swap_replayed_uic_variance_drift_rejects_request, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); // registers ETH + UWRIT_OP (EVM link only) - BOOST_REQUIRE_EQUAL(success(), push(CHAINS_ACCOUNT, chains_abi, CHAINS_ACCOUNT, - "regchain"_n, mvo() - ("kind", ChainKind::CHAIN_KIND_SVM) - ("code", "SOLANA") - ("external_chain_id", 900) - ("name", std::string("solana-test")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo()))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 8006; - constexpr uint64_t AMOUNT = 1'000'000'000; - - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", AMOUNT)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "SOL", AMOUNT)); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, static_cast(AMOUNT), - sol_chain, sol_token, primary, AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_SVM, - std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - // Leave the destination authex link absent while both UICs arrive. This - // deliberately parks a complete, authenticated candidate for external replay. - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", - src_uic)); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, sol_chain, "SOLANA", "SOL", "PRIMARY", - create_signed_uic(UWRIT_OP, ATT_ID, sol_chain, sol_token, primary))); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - get_uwreq(ATT_ID)["status"].as_string()); - - constexpr uint64_t SEEDED_RESERVE = 1'000'000'000'000ull; - BOOST_REQUIRE_EQUAL(success(), debit_reserve_chain( - "SOLANA", "SOL", "PRIMARY", SEEDED_RESERVE - (AMOUNT - 1))); - - const auto solana_link_key = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::ed).get_public_key(); - BOOST_REQUIRE_EQUAL(success(), push( - AUTHEX_ACCOUNT, authex_abi, AUTHEX_ACCOUNT, "recordlink"_n, mvo() - ("account", UWRIT_OP) - ("chain_kind", ChainKind::CHAIN_KIND_SVM) - ("pub_key", solana_link_key) - ("native_address", std::vector(32, '\x0b')))); - produce_block(); - - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_REJECTED", req["status"].as_string()); - const auto candidate = req["commits_by"].get_array().front(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_RELEASED", - candidate["status"].as_string()); - BOOST_REQUIRE(candidate["source_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(candidate["dest_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(get_lock(1).is_null()); -} FC_LOG_AND_RETHROW() } - -// createuwreq admits missing/inactive reserves for dev and smoke clusters. A -// complete candidate must keep the request PENDING while the reserve route is -// provisioned, then settle after an exact external UIC replay. -BOOST_FIXTURE_TEST_CASE(swap_missing_reserve_complete_candidate_recovers_after_provisioning_replay, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 8005; - constexpr uint64_t AMOUNT = 100; - - // No reserve rows are provisioned. The zero ingestion quote leaves only the - // source amount in this same-bucket candidate's authoritative bond check. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", uint64_t{1'000'000})); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, static_cast(AMOUNT), - eth, eth, secondary, AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, - std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", - create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary))); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", - dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - req["status"].as_string()); - const auto candidate = req["commits_by"].get_array().front(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_STATUS_INTENT_SUBMITTED", - candidate["status"].as_string()); - BOOST_REQUIRE(!candidate["source_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(!candidate["dest_uic_bytes"].as_string().empty()); - BOOST_REQUIRE(get_lock(1).is_null()); - - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - // The replay has identical action data by design. Advance the test chain so - // its enclosing transaction is a new relay submission rather than the - // duplicate transaction ID of the original delivery. - produce_block(); - BOOST_REQUIRE_EQUAL(success(), rcrdcommit_direct( - ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(ATT_ID)["status"].as_string()); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); -} FC_LOG_AND_RETHROW() } - -// Positive + existing-locks coverage: a balance that covers the aggregate -// (`src_amount + quote`) must select the underwriter and write two locks -// totaling exactly that. A subsequent same-bucket swap must then see -// availability reduced by those active locks and disqualify that candidate. -// -// The destination lock is sized by the AMM QUOTE, not by the caller's -// `target_amount` (WNS-02) — hence the assertions derive from `expected_quote` -// rather than repeating the request's target. -BOOST_FIXTURE_TEST_CASE(swap_same_token_legs_exact_balance_wins, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); // ETH/ETH/PRIMARY (+ SOL) - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 8100; - - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 200)); - - // The quote the swap will settle at, taken before any reserve moves. - const uint64_t quote = expected_quote("ETH", "ETH", "PRIMARY", /*src_amount*/ 100, - "ETH", "ETH", "SECOND", kDefaultUwritFeeBps); - BOOST_REQUIRE(quote > 0); - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - eth, eth, secondary, /*target_amount*/ 100, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - // The row carries the QUOTE, never the caller's target. - BOOST_REQUIRE_EQUAL(quote, get_uwreq(ATT_ID)["dst_amount"].as_uint64()); - - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(UWRIT_OP.to_string(), req["winner"].as_string()); - - // Two locks, both on (ETH, ETH): the source leg at `src_amount`, the - // destination leg at the quote — together the aggregate the bond had to cover. - const auto l1 = get_lock(1); - const auto l2 = get_lock(2); - BOOST_REQUIRE(!l1.is_null()); - BOOST_REQUIRE(!l2.is_null()); - BOOST_REQUIRE_EQUAL(eth, l1["chain_code"].as().value); - BOOST_REQUIRE_EQUAL(eth, l1["token_code"].as().value); - BOOST_REQUIRE_EQUAL(eth, l2["chain_code"].as().value); - BOOST_REQUIRE_EQUAL(eth, l2["token_code"].as().value); - BOOST_REQUIRE_EQUAL(100u, l1["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(quote, l2["amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(100u + quote, l1["amount"].as_uint64() + l2["amount"].as_uint64()); - - // Existing active locks now reserve the whole bucket (available == 0), so a - // fresh same-bucket swap cannot win yet but retains valid retryable evidence. - // Amounts must be large enough - // to price against the 1e12 reserves — a sub-quote-floor amount is rejected - // earlier by the unpriceable-reserve gate, which would mask the bond check. - constexpr uint64_t ATT_ID2 = 8101; - const auto sr2 = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - eth, eth, secondary, /*dst_amount*/ 100, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID2, eth, sr2)); - const auto src_uic2 = create_signed_uic(UWRIT_OP, ATT_ID2, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID2, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic2)); - const auto dst_uic2 = create_signed_uic(UWRIT_OP, ATT_ID2, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID2, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic2)); - - const auto req2 = get_uwreq(ATT_ID2); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req2["status"].as_string()); - bool retryable = false; - for (const auto& c : req2["commits_by"].get_array()) { - if (c["underwriter"].as_string() == UWRIT_OP.to_string()) { - retryable = - c["status"].as_string() == "UNDERWRITE_STATUS_INTENT_SUBMITTED" - && c["reason"].as_string().empty() - && !c["source_uic_bytes"].as_string().empty() - && !c["dest_uic_bytes"].as_string().empty(); - } - } - BOOST_REQUIRE(retryable); -} FC_LOG_AND_RETHROW() } - -// WSA-028 closes the single-swap aggregate-overflow vector at ingress. SEC-15's -// uint128 winner-check guard (uwrit.cpp `need = src + dst`) was originally proven -// by driving src_amount to UINT64_MAX — reachable only because a signed source -// amount of -1 wrapped to UINT64_MAX. to_depot_amount now rejects that source -// before any uwreq exists, so a single swap can no longer form the overflow: the -// request reverts and creates no uwreq. The uint128 aggregate addition itself -// stays covered by swap_same_token_legs_overcommit_recovers_after_top_up / -// _exact_balance_wins. -BOOST_FIXTURE_TEST_CASE(swap_oversized_source_reverts_at_ingress, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 8200; - - // Maximal availability, so the revert below is provably from the oversized - // source at ingress — not from an insufficient-balance check downstream. - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "ETH", (uint64_t{1} << 62) - 1)); - - // source_amount == UINT64_MAX, encoded as -1 in the signed wire field (the - // exact pre-WSA-028 wrap). to_depot_amount rejects it via the amount <= 0 branch. - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, static_cast(~uint64_t{0}), - eth, eth, secondary, /*dst_amount*/ 1, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - // Reverts at ingress (never throws) and creates no uwreq. - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -// Defence-in-depth: opreg::releaselock settles deferred slashes INLINE inside -// sysio.uwrit::chklocks at sysio.epoch::advance. If a released amount ever -// exceeds the live balance bucket, subtract_balance must NOT underflow + abort -// — that would stall epoch advancement chain-wide. releaselock clamps the -// settled amount to the live balance instead. (The aggregate winner check above -// prevents the overcommit at lock-creation time; this guards the cleanup path -// regardless.) -BOOST_FIXTURE_TEST_CASE(releaselock_clamps_overdrain_without_aborting, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - register_wire_depot(); // to-WIRE: a single (source) required leg - setup_wire_token_and_reserves(); // ACTIVE ETH/ETH/PRIMARY source reserve w/ WIRE - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - constexpr uint64_t ATT_ID = 8300; - - // Bond 100 on (ETH, ETH); a to-WIRE swap locks the whole 100 (one source - // lock), so slash leaves the balance intact (slashable-now == 0). - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 100)); - - const std::string rs = UWRIT_OP.to_string(); - const std::vector rcpt(rs.begin(), rs.end()); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - wire, wire, primary, /*target*/ 50, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_WIRE, rcpt); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - const auto src_uic = create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(ATT_ID)["status"].as_string()); - - // Slash: locked 100 == balance 100, so nothing is debited now; status SLASHED. - BOOST_REQUIRE_EQUAL(success(), slash_op(UWRIT_OP, "test slash")); - { - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_SLASHED", op["status"].as_string()); - BOOST_REQUIRE_EQUAL(100u, find_balance(op, "ETH", "ETH")["balance"].as_uint64()); - } - - // Two deferred releases summing to 120 > balance 100 (distinct amounts so - // the txns don't collide as duplicates). The first debits 70 (100 -> 30); - // the second would underflow 30 - 50 without the clamp, which instead settles - // only the remaining 30. Both must succeed (no abort). - BOOST_REQUIRE_EQUAL(success(), releaselock_direct(UWRIT_OP, "ETH", "ETH", 70)); - BOOST_REQUIRE_EQUAL(success(), releaselock_direct(UWRIT_OP, "ETH", "ETH", 50)); - - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE_EQUAL(0u, find_balance(op, "ETH", "ETH")["balance"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// SEC-77 / WSA-165: drainfwq drains at most MAX_FWQ_DRAIN_PER_EPOCH swap-from-WIRE rows per advance, -// so a caller cannot split escrowed WIRE into enough queued rows to blow the transaction CPU deadline -// advance shares with the rest of its fan-out and stall epoch progress chain-wide. The remainder stays -// queued (escrow safe in reserve custody) and drains on the next advance. This is also the first -// end-to-end coverage of drainfwq draining a populated queue: the bounded front-read FIFO loop, and -// that a second drain resumes where the first stopped. -BOOST_FIXTURE_TEST_CASE(drainfwq_bounds_rows_per_epoch, sysio_dispatch_tester) { try { - // Mirror of the contract-internal cap (contract headers are not host-compilable, same convention - // as the msgch size-cap tests). Keep in sync with sysio.uwrit.hpp::MAX_FWQ_DRAIN_PER_EPOCH. - constexpr uint32_t MAX_FWQ_DRAIN_PER_EPOCH = 32; - constexpr uint32_t N = MAX_FWQ_DRAIN_PER_EPOCH + 8; // 40 > one epoch's drain budget - constexpr uint64_t DEPOT_ORIGIN_ID_BASE = 0x8000000000000000ull; // fwqueue id = base | seq - - bootstrap_for_dispatch(); // registers the ETH (EVM) outpost + epoch machinery - setup_wire_token_and_reserves(); // sysio.token + ACTIVE public ETH/ETH/PRIMARY reserve - register_wire_depot(); // so drainfwq's depot_chain_code() resolves - // Floor lowered to 1: this case exercises the drain BOUND with 40 cheap distinct rows; the - // default 5-WIRE floor and the revert fee have their own dedicated cases below. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 0) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - // A funded from-WIRE swap user (plain account, no ROA policy). - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "1000.000000000 WIRE")("memo", "fund swap user"))); - - // Queue N from-WIRE swaps. wire_amount varies (1e6 + i) so each is a distinct transaction AND - // prices to a positive quote (rows take drainfwq's uwreq-emplace path, not the refund path); the - // exact amount is otherwise irrelevant to the bound. target_amount + 100% tolerance keep every - // row within variance so none refund. - for (uint32_t i = 0; i < N; ++i) { - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", uint64_t{1'000'000} + i) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1'000'000}) - ("target_tolerance_bps", uint32_t{10000}) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a')))); - } - - auto get_fwqueue = [&](uint64_t id) -> fc::variant { - auto data = get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "fwqueue"_n, id); - return data.empty() ? fc::variant() : uwrit_abi.binary_to_variant( - "fromwire_q", data, abi_serializer::create_yield_function(abi_serializer_max_time)); - }; - auto count_queued = [&]() { - uint32_t n = 0; - for (uint32_t s = 0; s < N; ++s) - if (!get_fwqueue(DEPOT_ORIGIN_ID_BASE | s).is_null()) ++n; - return n; - }; - BOOST_REQUIRE_EQUAL(N, count_queued()); - - // One drain processes exactly MAX_FWQ_DRAIN_PER_EPOCH rows; the remainder stays queued. - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); - BOOST_REQUIRE_EQUAL(N - MAX_FWQ_DRAIN_PER_EPOCH, count_queued()); - - // Cross a block boundary so the second drain is a distinct transaction — an identical action in - // the same block is rejected as a duplicate before the contract runs, masking the guard under test. - produce_blocks(); - // The next drain resumes where the first stopped and clears the rest. - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); - BOOST_REQUIRE_EQUAL(0u, count_queued()); -} FC_LOG_AND_RETHROW() } - -// The swapfromwire escrow floor prices fwqueue slots in locked capital: dust rows are refunded in -// full at drain, so without the floor spam rows could hold drain slots while locking nothing. The -// floor defaults to 5 WIRE and is retunable via setconfig without an upgrade. -BOOST_FIXTURE_TEST_CASE(swapfromwire_enforces_min_amount, sysio_dispatch_tester) { try { - // Mirror of the contract default (contract headers are not host-compilable). Keep in sync with - // sysio.uwrit.hpp::DEFAULT_MIN_FROMWIRE_AMOUNT. - constexpr uint64_t DEFAULT_MIN_FROMWIRE_AMOUNT = 5'000'000'000ull; // 5 WIRE @ 9 decimals - constexpr uint64_t DEPOT_ORIGIN_ID_BASE = 0x8000000000000000ull; - - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); // ACTIVE public ETH/ETH/PRIMARY destination reserve - - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "100.000000000 WIRE")("memo", "fund swap user"))); - - auto swap = [&](uint64_t wire_amount) { - return push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", wire_amount) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1'000'000}) - ("target_tolerance_bps", uint32_t{10000}) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a'))); - }; - auto queued = [&](uint64_t seq) { - return !get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "fwqueue"_n, - DEPOT_ORIGIN_ID_BASE | seq).empty(); - }; - - // Default config (setconfig never pushed): one atom below the floor is rejected, the exact - // floor is accepted and lands in the queue. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: swapfromwire: wire_amount below the configured minimum"), - swap(DEFAULT_MIN_FROMWIRE_AMOUNT - 1)); - BOOST_REQUIRE_EQUAL(success(), swap(DEFAULT_MIN_FROMWIRE_AMOUNT)); - BOOST_REQUIRE(queued(0)); - - // The floor is live config: lower it and the new boundary is enforced instead. - constexpr uint64_t LOWERED_FLOOR = 1'000'000; - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", LOWERED_FLOOR)("fromwire_revert_fee_bps", 10) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: swapfromwire: wire_amount below the configured minimum"), - swap(LOWERED_FLOOR - 1)); - BOOST_REQUIRE_EQUAL(success(), swap(LOWERED_FLOOR)); - BOOST_REQUIRE(queued(1)); -} FC_LOG_AND_RETHROW() } - -// Caller-controlled drain-time reverts forfeit the configured revert fee: the refund returns the -// escrow minus the fee, and the fee routes through the standard `route_wire_fee` path exactly -// like a settlement fee — so revert churn pays the system instead of recycling for free. -BOOST_FIXTURE_TEST_CASE(drainfwq_charges_revert_fee_on_caller_fault, sysio_dispatch_tester) { try { - constexpr uint64_t ESCROW = 5'000'000'000ull; // the default floor exactly - constexpr uint32_t REVERT_FEE_BPS = 100; // 1% - constexpr uint64_t FEE = ESCROW * REVERT_FEE_BPS / 10000ull; // 0.05 WIRE - // A revert has no winning underwriter, so `refundwire` passes a zero underwriter share and - // the WHOLE fee becomes the rewards pool. reserv's `fee_emissions_share_bps` is never set - // here (the default 0), so that pool lands in the rewards bucket intact (batch operators); - // a configured dial would divert its share to the emissions treasury. - constexpr uint64_t REWARD_SHARE = FEE; - constexpr uint64_t DEPOT_ORIGIN_ID_0 = 0x8000000000000000ull; - const auto WIRE_SYM = symbol(9, "WIRE"); - - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - register_wire_depot(); // depot registered => the drain reaches the variance check - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", ESCROW)("fromwire_revert_fee_bps", REVERT_FEE_BPS) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "10.000000000 WIRE")("memo", "fund swap user"))); - const int64_t funded = get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount(); - - // target_amount=1 with zero tolerance: the live quote against the seeded 1e12/1e12 pool is - // ~5e9, so |quote - 1| > 0 == allowed and the row reverts at drain — a failure produced - // entirely by the caller's own parameters. - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", ESCROW) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1}) - ("target_tolerance_bps", uint32_t{0}) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a')))); - BOOST_REQUIRE_EQUAL(funded - static_cast(ESCROW), - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); - - // Row consumed; escrow minus the fee is now OWED to the caller; the whole fee accrued into - // the reserv rewards bucket (custody-internal — no transfer leaves reserv for a fee). - // - // The refund is credited, not pushed. drainfwq runs inline from sysio.epoch::advance, - // where a recipient's transfer-notify handler could otherwise abort the whole advance -- and - // because the queue-row erase rolls back with it, that block would be permanent. Pulling the - // credit via claimwire produces the original balance. - BOOST_REQUIRE(get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "fwqueue"_n, DEPOT_ORIGIN_ID_0).empty()); - BOOST_REQUIRE_EQUAL(success(), - push(RESERV_ACCOUNT, reserv_abi, "swapuser"_n, "claimwire"_n, mvo()("account", "swapuser"))); - BOOST_REQUIRE_EQUAL(funded - static_cast(FEE), - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - auto bkt_data = get_row_by_account(RESERV_ACCOUNT, RESERV_ACCOUNT, "rewardbkt"_n, "rewardbkt"_n); - BOOST_REQUIRE(!bkt_data.empty()); - auto bkt = reserv_abi.binary_to_variant("rewards_bucket", bkt_data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - BOOST_REQUIRE_EQUAL(REWARD_SHARE, bkt["balance"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// Reverts caused by system state changes after enqueue refund in full even with a nonzero revert -// fee configured — the caller did nothing wrong. Exercised via the cheapest system-fault branch: -// no WIRE depot chain registered at drain time. -BOOST_FIXTURE_TEST_CASE(drainfwq_full_refund_on_system_caused_revert, sysio_dispatch_tester) { try { - constexpr uint64_t ESCROW = 5'000'000'000ull; - constexpr uint64_t DEPOT_ORIGIN_ID_0 = 0x8000000000000000ull; - const auto WIRE_SYM = symbol(9, "WIRE"); - - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - // register_wire_depot() deliberately NOT called: drainfwq's depot_chain_code() comes back - // empty, which is a system-caused revert (the registry, not the caller's parameters). - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", ESCROW)("fromwire_revert_fee_bps", 100) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "10.000000000 WIRE")("memo", "fund swap user"))); - const int64_t funded = get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount(); - - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", ESCROW) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1'000'000}) - ("target_tolerance_bps", uint32_t{10000}) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a')))); - - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); - - // Full escrow returned — no fee — and no rewards-bucket accrual. Credited rather than pushed - // (credited, not pushed), so the caller pulls it with claimwire. - BOOST_REQUIRE(get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "fwqueue"_n, DEPOT_ORIGIN_ID_0).empty()); - BOOST_REQUIRE_EQUAL(success(), - push(RESERV_ACCOUNT, reserv_abi, "swapuser"_n, "claimwire"_n, mvo()("account", "swapuser"))); - BOOST_REQUIRE_EQUAL(funded, - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - BOOST_REQUIRE(get_row_by_account(RESERV_ACCOUNT, RESERV_ACCOUNT, - "rewardbkt"_n, "rewardbkt"_n).empty()); -} FC_LOG_AND_RETHROW() } - -// [P0] A swap-from-WIRE user cannot halt the epoch drain by refusing its own refund. -// -// This was the cheapest and most durable chain-halt in the protocol. `sysio.token::transfer` -// notifies `to`, and the chain runs notified receivers with no exception isolation, so a recipient -// asserting in its transfer-notify handler aborts the WHOLE transaction. `refundwire` is inlined -// from the never-throw `drainfwq` drain, which is itself inlined from `sysio.epoch::advance` -- -// so the abort took down epoch advancement chain-wide. Worse, the drain's `q.erase()` rolled back -// with it, leaving the offending queue row in place to re-block every later epoch. Cost to the -// attacker: one `min_fromwire_amount` escrow, once. -// -// `refundwire` now credits `wireclaims` and transfers nothing, so no recipient handler runs on the -// drain path. The drain completes, the row is consumed, and the hostile user is merely owed money -// it cannot collect. -BOOST_FIXTURE_TEST_CASE(blocking_refund_recipient_cannot_stall_drainfwq, sysio_dispatch_tester) { try { - constexpr uint64_t ESCROW = 5'000'000'000ull; - constexpr uint64_t DEPOT_ORIGIN_ID_0 = 0x8000000000000000ull; - const auto WIRE_SYM = symbol(9, "WIRE"); - - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - // No depot chain registered -> the drain takes the full-refund (system-fault) branch. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", ESCROW)("fromwire_revert_fee_bps", 100) - ("uwreq_pending_timeout_epochs", 10)("uwreq_retention_epochs", 10))); - - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "10.000000000 WIRE")("memo", "fund swap user"))); - - // Queue the swap FIRST: the escrow leg is an outgoing transfer, and arming the blocker before - // it would stop the setup rather than the path under test. - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", ESCROW) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1'000'000}) - ("target_tolerance_bps", uint32_t{10000}) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a')))); - const int64_t escrowed_bal = get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount(); - - // Arm the blocker: every INCOMING transfer to swapuser now asserts. - control->get_mutable_resource_limits_manager() - .set_account_limits("swapuser"_n, 1024 * 1024, -1, -1, false); - produce_blocks(); - set_code("swapuser"_n, contracts::util::block_transfer_wasm()); - set_abi("swapuser"_n, contracts::util::block_transfer_abi().data()); - produce_blocks(); - - // The drain must still complete. Before this change it aborted, taking advance() with it. - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); - - // The queue row is consumed for good -- this is what made the old failure permanent, since the - // erase rolled back with the aborted transaction and the row re-blocked every later epoch. - BOOST_REQUIRE(get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "fwqueue"_n, DEPOT_ORIGIN_ID_0).empty()); - - // The refund is owed, not paid: the escrow has not returned to the blocker's balance. - BOOST_REQUIRE_EQUAL(escrowed_bal, - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - - // And the blocker can only block itself: its own claim is what its handler rejects. - auto r = push(RESERV_ACCOUNT, reserv_abi, "swapuser"_n, "claimwire"_n, mvo()("account", "swapuser")); - BOOST_REQUIRE_MESSAGE(r != success(), "blocking user unexpectedly claimed its own refund"); - BOOST_REQUIRE_MESSAGE(r.find("block_transfer: rejecting incoming transfer") != std::string::npos, - "unexpected failure reason: " + r); - - // A second drain is a clean no-op -- the queue really is empty, so epochs keep advancing. - // Advance a block first: an identical action replayed in the same block is rejected as a - // duplicate transaction before the contract runs, which would assert nothing. - produce_blocks(); - BOOST_REQUIRE_EQUAL(success(), - push(UWRIT_ACCOUNT, uwrit_abi, EPOCH_ACCOUNT, "drainfwq"_n, mvo())); -} FC_LOG_AND_RETHROW() } - -// ═════════════════════════════════════════════════════════════════════════ -// SEC-129 / WSA-223 — UWREQ lifecycle: expiry is enforced by triggers. -// -// `expires_at_epoch` is passive row data; the `pruneuwreqs` sweep inlined in -// every real `sysio.epoch::advance` is the only thing that evaluates it. The -// cases below drive genuine advances (emissions gate satisfied) and assert -// the full lifecycle: PENDING deadline → EXPIRED + refund + compaction → -// retention → erased; CONFIRMED → COMPLETED (chklocks) → retention → erased; -// the per-epoch budget; and the rcrdcommit row-growth rails. -// ═════════════════════════════════════════════════════════════════════════ - -// A PENDING uwreq whose underwriter race never resolves survives pre-deadline -// advances untouched, is expired by the first advance at/past its deadline -// (EXPIRED + payload compaction), and is erased once retention elapses. -BOOST_FIXTURE_TEST_CASE(uwreq_pending_timeout_expires_then_retention_erases, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - // Pending timeout 2 epochs + retention 1 epoch — the shortest schedule - // that still proves the pre-deadline advance is a no-op for the row. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 10) - ("uwreq_pending_timeout_epochs", 2)("uwreq_retention_epochs", 1))); - constexpr uint64_t ATT_ID = 9100; - setup_eth_to_sol_uwreq(ATT_ID); // PENDING, no commits, deadline = 0 + 2 - enable_epoch_advancement(); - - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL(2u, req["expires_at_epoch"].as()); - BOOST_REQUIRE(req["attestation_inbound_data"].as_string().size() > 0); - } - - // Epoch 1 < deadline 2 — the row is inert data; the sweep leaves it alone. - age_one_epoch(); - BOOST_REQUIRE_EQUAL(1u, current_epoch()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", - get_uwreq(ATT_ID)["status"].as_string()); - - // Epoch 2 == deadline — the same advance's inline sweep expires it. - age_one_epoch(); - BOOST_REQUIRE_EQUAL(2u, current_epoch()); - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_EXPIRED", req["status"].as_string()); - BOOST_REQUIRE(req["settled_at_ms"].as_uint64() > 0); - // Terminal compaction: the inbound attestation copy is gone; the - // compact audit metadata (codes, amounts, timestamps) is retained. - BOOST_REQUIRE_EQUAL(0u, req["attestation_inbound_data"].as_string().size()); - // Retention window re-stamped: epoch 2 + 1. - BOOST_REQUIRE_EQUAL(3u, req["expires_at_epoch"].as()); - } - - // Epoch 3 — retention elapsed; the row is erased outright. - age_one_epoch(); - BOOST_REQUIRE_EQUAL(3u, current_epoch()); - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -// A queued swap-from-WIRE whose race never resolves refunds the user's FULL -// escrow at expiry (expiry is not a caller-controlled revert cause — no -// revert fee) and follows the same EXPIRED → retention → erased lifecycle. -BOOST_FIXTURE_TEST_CASE(uwreq_from_wire_pending_timeout_refunds_escrow, - sysio_dispatch_tester) { try { - constexpr uint64_t DEPOT_ORIGIN_ID_0 = 0x8000000000000000ull; - constexpr uint64_t ESCROW = 1'000'000; - const auto WIRE_SYM = symbol(9, "WIRE"); - - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - register_wire_depot(); - // Non-zero revert fee configured on purpose: the full refund below proves - // the expiry path is fee-exempt even when a fee is configured. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 100) - ("uwreq_pending_timeout_epochs", 1)("uwreq_retention_epochs", 1))); - - // Created + funded before the full system contract lands on `sysio`, the - // same way every other from-WIRE case provisions its user. - create_account("swapuser"_n, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", "swapuser") - ("quantity", "10.000000000 WIRE")("memo", "fund swap user"))); - const int64_t funded = - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount(); - - enable_epoch_advancement(); - - // target == wire_amount with 100% tolerance prices within variance against - // the balanced 1e12/1e12 reserve, so the drain emplaces the uwreq (no - // refund at drain) — same recipe as drainfwq_bounds_rows_per_epoch. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, "swapuser"_n, - "swapfromwire"_n, mvo() - ("user", "swapuser") - ("wire_amount", ESCROW) - ("dst_chain_code", "ETH") - ("dst_token_code", "ETH") - ("dst_reserve_code", "PRIMARY") - ("target_amount", uint64_t{1'000'000}) - ("target_tolerance_bps", uint32_t{10000}) - ("recipient_kind", ChainKind::CHAIN_KIND_EVM) - ("recipient_addr", std::vector(20, '\x0a')))); - BOOST_REQUIRE_EQUAL(funded - int64_t(ESCROW), - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - - // Advance #1: drainfwq (inline, post-increment) creates the PENDING uwreq - // at epoch 1 with deadline 1 + 1 = 2. - age_one_epoch(); - { - const auto req = get_uwreq(DEPOT_ORIGIN_ID_0); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_PENDING", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(2u, req["expires_at_epoch"].as()); - } - - // Advance #2 (epoch 2 == deadline): the sweep expires the row and refunds - // the FULL escrow via reserv::refundwire. - age_one_epoch(); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_EXPIRED", - get_uwreq(DEPOT_ORIGIN_ID_0)["status"].as_string()); - // Credited rather than pushed — the expiry sweep runs on the never-throw advance - // path, so the escrow is owed until the user pulls it. - BOOST_REQUIRE_EQUAL(success(), - push(RESERV_ACCOUNT, reserv_abi, "swapuser"_n, "claimwire"_n, mvo()("account", "swapuser"))); - BOOST_REQUIRE_EQUAL(funded, - get_currency_balance(TOKEN_ACCOUNT, WIRE_SYM, "swapuser"_n).get_amount()); - - // Advance #3 (epoch 3): retention elapsed — erased. - age_one_epoch(); - BOOST_REQUIRE(get_uwreq(DEPOT_ORIGIN_ID_0).is_null()); -} FC_LOG_AND_RETHROW() } - -// A settled swap's row is compacted at COMPLETED (chklocks closes the -// challenge window) and erased once retention elapses — the full happy-path -// lifecycle driven end to end by real advances. Winner selection itself -// zeroes the deadline (the lock window owns a CONFIRMED row). -BOOST_FIXTURE_TEST_CASE(uwreq_completed_row_erased_after_retention, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - // 60s challenge window (== one epoch) + 1-epoch retention; the pending - // timeout stays clear of the race so only the lock machinery drives it. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 60'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 10) - ("uwreq_pending_timeout_epochs", 5)("uwreq_retention_epochs", 1))); - enable_epoch_advancement(); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - constexpr uint64_t ATT_ID = 9200; - - // Same-chain double-leg winner recipe as swap_same_token_legs_exact_balance_wins. - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 200)); - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ 100, - eth, eth, secondary, /*dst_amount*/ 100, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(ATT_ID, eth, sr)); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", - create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, primary))); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "SECOND", - create_signed_uic(UWRIT_OP, ATT_ID, eth, eth, secondary))); - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", req["status"].as_string()); - // Winner selection cleared the deadline — chklocks owns the row now. - BOOST_REQUIRE_EQUAL(0u, req["expires_at_epoch"].as()); - } - - // Advance #1 (62s elapsed > the 60s lock window): chklocks sweeps both - // locks, flips COMPLETED, stamps retention 1 + 1 = 2, and clears the - // remaining heavy payloads. The same advance's sweep (epoch 1 < 2) must - // NOT touch the freshly-completed row. - age_one_epoch(); - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE(!req.is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", req["status"].as_string()); - BOOST_REQUIRE_EQUAL(2u, req["expires_at_epoch"].as()); - BOOST_REQUIRE_EQUAL(0u, req["attestation_inbound_data"].as_string().size()); - for (const auto& c : req["commits_by"].get_array()) { - BOOST_REQUIRE_EQUAL(0u, c["source_uic_bytes"].as_string().size()); - BOOST_REQUIRE_EQUAL(0u, c["dest_uic_bytes"].as_string().size()); - } - } - - // Advance #2 (epoch 2): retention elapsed — erased. Steady state: the - // table carries nothing from a fully-settled swap. - age_one_epoch(); - BOOST_REQUIRE(get_uwreq(ATT_ID).is_null()); -} FC_LOG_AND_RETHROW() } - -// The per-epoch budget bounds the sweep's work: with more due rows than -// MAX_UWREQ_PRUNE_PER_EPOCH (32), one advance handles exactly 32 and the -// backlog drains the next epoch — advance's CPU stays bounded, nothing is -// lost, nothing is double-handled. -BOOST_FIXTURE_TEST_CASE(pruneuwreqs_budget_bounds_rows_per_epoch, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - // Retention kept long (100) so the second advance only expires the - // leftover PENDING row instead of competing with 32 fresh erases. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", 10)("collateral_lock_duration_ms", 120'000u) - ("min_fromwire_amount", 1)("fromwire_revert_fee_bps", 10) - ("uwreq_pending_timeout_epochs", 1)("uwreq_retention_epochs", 100))); - constexpr uint64_t FIRST_ATT_ID = 9300; - constexpr uint32_t ROWS = 33; // MAX_UWREQ_PRUNE_PER_EPOCH + 1 - setup_eth_to_sol_uwreq(FIRST_ATT_ID); // registers SOLANA + reserves once - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t sol_chain = fc::slug_name{"SOLANA"}.value; - const uint64_t sol_token = fc::slug_name{"SOL"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - for (uint32_t i = 1; i < ROWS; ++i) { - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, 100, sol_chain, sol_token, primary, 100, - 5000, ChainKind::CHAIN_KIND_SVM, std::vector(32, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(FIRST_ATT_ID + i, eth, sr)); - if (i % 8 == 7) produce_blocks(); // spread across blocks (per-block CPU) - } - produce_blocks(); - enable_epoch_advancement(); - - // Advance #1 (epoch 1 == every row's deadline): exactly the budgeted 32 - // rows are expired; one stays PENDING for the next epoch. - age_one_epoch(); - uint32_t expired = 0, pending = 0; - for (uint32_t i = 0; i < ROWS; ++i) { - const auto req = get_uwreq(FIRST_ATT_ID + i); - BOOST_REQUIRE(!req.is_null()); - const auto st = req["status"].as_string(); - if (st == "UNDERWRITE_REQUEST_STATUS_EXPIRED") ++expired; - else if (st == "UNDERWRITE_REQUEST_STATUS_PENDING") ++pending; - } - BOOST_REQUIRE_EQUAL(32u, expired); - BOOST_REQUIRE_EQUAL(1u, pending); - - // Advance #2 drains the backlog: every row is now EXPIRED. - age_one_epoch(); - expired = 0; - for (uint32_t i = 0; i < ROWS; ++i) { - const auto req = get_uwreq(FIRST_ATT_ID + i); - BOOST_REQUIRE(!req.is_null()); - if (req["status"].as_string() == "UNDERWRITE_REQUEST_STATUS_EXPIRED") ++expired; - } - BOOST_REQUIRE_EQUAL(ROWS, expired); -} FC_LOG_AND_RETHROW() } - -// ── rcrdcommit row-growth rails (SEC-129 / WSA-223) ── - -// An oversized UIC payload is refused at the door: no candidate entry, no -// stored bytes, no throw. At the cap, the commit records normally. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_oversized_uic_leg_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9400; - setup_eth_to_sol_uwreq(ATT_ID); - const uint64_t eth = fc::slug_name{"ETH"}.value; - - // One byte past MAX_UIC_LEG_BYTES (2048) — validly signed, then dropped by - // the row-size boundary with no mutation. - const auto oversized_uic = create_signed_uic_of_size( - UWRIT_OP, ATT_ID, eth, eth, fc::slug_name{"PRIMARY"}.value, 2049); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", - oversized_uic)); - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); - - // At the cap the valid commit records normally. - const auto capped_uic = create_signed_uic_of_size( - UWRIT_OP, ATT_ID, eth, eth, fc::slug_name{"PRIMARY"}.value, 2048); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", - capped_uic)); - BOOST_REQUIRE_EQUAL(1u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); -} FC_LOG_AND_RETHROW() } - -// The candidate roster is capped at MAX_UWREQ_CANDIDATES (32): the 33rd -// distinct ACTIVE underwriter is refused with no row growth, while an -// existing candidate can still supply its missing leg at the cap. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_candidate_cap_bounds_row, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - constexpr uint64_t ATT_ID = 9500; - setup_eth_to_sol_uwreq(ATT_ID); - const uint64_t eth = fc::slug_name{"ETH"}.value; - - // Provision 33 ACTIVE underwriters (register + meet the 1-unit ETH/ETH - // minimum from bootstrap_for_dispatch's opreg config). UWRIT_OP stays out - // of this roster. - std::vector uws; - for (uint32_t i = 0; i < 33; ++i) { - std::string s = "uwcap"; - s += static_cast('a' + i / 26); - s += static_cast('a' + i % 26); - uws.emplace_back(s); - } - create_accounts(uws); - produce_blocks(); - // Blocks are produced along the way — 33 registrations + credits + 33 - // commits in one block would exhaust the per-block billable CPU. - for (uint32_t i = 0; i < uws.size(); ++i) { - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, - "regoperator"_n, mvo() - ("account", uws[i].to_string()) - ("type", OperatorType::OPERATOR_TYPE_UNDERWRITER) - ("is_bootstrapped", false))); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(uws[i], "ETH", "ETH", 1'000)); - if (i % 8 == 7) produce_blocks(); - } - produce_blocks(); - - // 32 distinct candidates record; the 33rd is refused at the cap before its - // deliberately malformed signature can reach protobuf/signature work. - for (uint32_t i = 0; i < 32; ++i) { - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, uws[i], eth, "ETH", "ETH", "PRIMARY", - create_signed_uic( - uws[i], ATT_ID, eth, eth, - fc::slug_name{"PRIMARY"}.value))); - if (i % 8 == 7) produce_blocks(); - } - BOOST_REQUIRE_EQUAL(32u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); - produce_blocks(); - const auto capped_trace = rcrdcommit_trace( - ATT_ID, uws[32], eth, "ETH", "ETH", "PRIMARY", - create_uic_with_signature( - uws[32], ATT_ID, eth, eth, fc::slug_name{"PRIMARY"}.value, - {packed_uic_signature_tag(fc::crypto::signature::sig_type::k1)})); - BOOST_REQUIRE(!capped_trace->except); - const auto& capped_console = capped_trace->action_traces.front().console; - BOOST_CHECK_NE(std::string::npos, capped_console.find("cap reached")); - BOOST_CHECK_EQUAL(std::string::npos, - capped_console.find("UIC_SIGNATURE_REJECTED")); - { - const auto req = get_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL(32u, req["commits_by"].get_array().size()); - for (const auto& c : req["commits_by"].get_array()) { - BOOST_REQUIRE(c["underwriter"].as_string() != uws[32].to_string()); - } - } - - // An EXISTING candidate can still supply its missing destination leg at the - // cap. It reaches normal validation/winner handling rather than the roster rail. - const auto sol_chain = fc::slug_name{"SOLANA"}.value; - const auto sol_token = fc::slug_name{"SOL"}.value; - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(ATT_ID, uws[0], sol_chain, "SOLANA", "SOL", "PRIMARY", - create_signed_uic( - uws[0], ATT_ID, sol_chain, sol_token, - fc::slug_name{"PRIMARY"}.value))); - BOOST_REQUIRE_EQUAL(32u, get_uwreq(ATT_ID)["commits_by"].get_array().size()); -} FC_LOG_AND_RETHROW() } - -// ═══════════════════════════════════════════════════════════════════════════ -// Underwriter-fault challenge (WIRE-297) — openuwchal / voteuwchal / -// chkuwchal / uwchalbond + the uwrit lock-hold trio -// ═══════════════════════════════════════════════════════════════════════════ - -/// The dispatch stack + a deployed `sysio.chalg`, a Tier-1 electorate of three, and a -/// WIRE-funded challenger. The confirmed-uwreq builder mirrors -/// `swap_same_token_legs_exact_balance_wins` exactly: two same-chain legs of LEG_AMOUNT -/// against the 1e12/1e12 cw-5000 books, so the winner holds two (ETH, ETH) locks — the source -/// leg at LEG_AMOUNT and the destination leg at the AMM quote (swaps settle on the quote, never -/// on the caller's target). Nothing here pins either amount: the bond math reads the live lock -/// rows, so the fixture tracks whatever the curve prices. -class sysio_uwchal_tester : public sysio_dispatch_tester { -public: - static constexpr auto CHALLENGER = "challenger"_n; - static constexpr auto VOTER1 = "voter1"_n; - static constexpr auto VOTER2 = "voter2"_n; - static constexpr auto VOTER3 = "voter3"_n; - static constexpr auto VOTER4 = "voter4"_n; - - /// Ballot wire values (the uint8 `uwchal_ballot` members). - static constexpr uint8_t BALLOT_UPHOLD = 0; - static constexpr uint8_t BALLOT_REJECT_REFUND = 1; - static constexpr uint8_t BALLOT_REJECT_FORFEIT = 2; - /// Fault-reason wire value (`underwrite_fault_reason::SOURCE_DEPOSIT_MISSING`). - static constexpr uint8_t REASON_DEPOSIT_MISSING = 0; - - /// Mirrors `chalg::min_uwchal_bucket_wire` — the floor a nonzero collateral bucket contributes - /// when the depot cannot price it (no ACTIVE reserve for the pair, or dust that the books floor - /// to zero). Keeps the quote nonzero so the commitment stays challengeable. - static constexpr uint64_t MIN_UWCHAL_BUCKET_WIRE = 1; - - /// Source amount (and requested target) the confirmed-uwreq builder swaps. The SOURCE leg - /// locks exactly this; the destination leg locks the AMM quote for it, which the curve puts - /// just under it — so the two locks together stay inside the 200 credited to the underwriter. - static constexpr uint64_t LEG_AMOUNT = 100; - - abi_serializer chalg_abi; - - sysio_uwchal_tester() { - // CHALG_ACCOUNT exists (base fixture creates it); it just never had code until now. - deploy(CHALG_ACCOUNT, contracts::chalg_wasm(), contracts::chalg_abi(), chalg_abi); - - // Tier-1 voters (no roa policy — same shape as the dispute tester's electorate) and the - // bond-posting challenger. - for (auto v : {VOTER1, VOTER2, VOTER3, VOTER4, CHALLENGER}) { - create_account(v, config::system_account_name, /*multisig=*/false, - /*include_code=*/true, /*include_roa_policy=*/false); - } - produce_blocks(); - - // T1 electorate rows in sysio.roa (gen 0). `forcereg` works pre-emitcfg — the electorate - // snapshot walks `nodeowners` directly, never `nodecount`. Genesis already carries ONE T1 - // owner, so the snapshot is these four + it: N = 5, Q = 3 — every quorum path below casts - // three ballots, and the tie case splits four rejectors 2–2. - for (auto v : {VOTER1, VOTER2, VOTER3, VOTER4}) { - BOOST_REQUIRE_EQUAL(success(), push(ROA_ACCOUNT, roa_abi, ROA_ACCOUNT, "forcereg"_n, - mvo()("owner", v.to_string())("tier", 1))); - } - produce_blocks(); - } - - // ── setup: the challenged commitment ───────────────────────────────────── - - /// Full path to a CONFIRMED uwreq with two live (ETH, ETH) locks — the source leg at - /// LEG_AMOUNT, the destination leg at the AMM quote — byte-for-byte the - /// `swap_same_token_legs_exact_balance_wins` recipe. - void make_confirmed_uwreq(uint64_t att_id) { - bootstrap_for_dispatch(); - setup_wire_token_and_reserves(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active("ETH", "ETH", "SECOND")); - - // The challenger's bond funding comes from the WIRE treasury seeded above. - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", CHALLENGER.to_string()) - ("quantity", "100.000000000 WIRE")("memo", "challenge bond funding"))); - - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 200)); - - add_confirmed_uwreq(att_id); - } - - /// The uwreq half of `make_confirmed_uwreq`, without the one-time bootstrap: creates one more - /// CONFIRMED (ETH,ETH) commitment and its two locks on an already-provisioned cluster. Callers - /// wanting a second commitment must first credit its collateral — the two legs lock - /// `2 * LEG_AMOUNT`, and the bootstrap deposits only enough for one. - void add_confirmed_uwreq(uint64_t att_id) { - const uint64_t eth = fc::slug_name{"ETH"}.value; - const uint64_t primary = fc::slug_name{"PRIMARY"}.value; - const uint64_t secondary = fc::slug_name{"SECOND"}.value; - - const auto sr = encode_swap_request( - ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0a'), - eth, eth, primary, /*src_amount*/ LEG_AMOUNT, - eth, eth, secondary, /*dst_amount*/ LEG_AMOUNT, - /*tolerance_bps*/ 1'000'000, ChainKind::CHAIN_KIND_EVM, std::vector(20, '\x0b')); - BOOST_REQUIRE_EQUAL(success(), createuwreq_direct(att_id, eth, sr)); - - const auto src_uic = create_signed_uic(UWRIT_OP, att_id, eth, eth, primary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(att_id, UWRIT_OP, eth, "ETH", "ETH", "PRIMARY", src_uic)); - const auto dst_uic = create_signed_uic(UWRIT_OP, att_id, eth, eth, secondary); - BOOST_REQUIRE_EQUAL(success(), - rcrdcommit_direct(att_id, UWRIT_OP, eth, "ETH", "ETH", "SECOND", dst_uic)); - - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_CONFIRMED", - get_uwreq(att_id)["status"].as_string()); - } - - /// Walk `sysio.reserv::reserves` (checksum256-keyed, so `get_row_by_id` cannot address it) - /// and return the row matching the slug triple — the reserv tests' scan workaround. Takes the - /// raw slug VALUES so a lock row's fields feed straight in. - fc::variant find_reserve(uint64_t target_chain, uint64_t target_token, - uint64_t target_reserve) { - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("reserves"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(RESERV_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == RESERV_ACCOUNT && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) - std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = reserv_abi.binary_to_variant( - "reserve_row", raw, abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token && - row["reserve_code"].as().value == target_reserve) { - return row; - } - } catch (...) { - // skip rows that don't decode - } - } - return fc::variant(); - } - - /// What `uwchalbond`/`openuwchal` must price: the underwriter's ENTIRE collateral, each - /// bucket quoted to WIRE on its pair's LIVE books and summed (Jonathan, 2026-08-11) — what - /// an upheld verdict actually costs them. Live books, not the registration constants: - /// winner selection settles inline, so by challenge time `applyswap` has already moved both - /// sides. Recomputing on the host over the same rows pins the contract to the shared kernel: - /// right reserve per bucket, right field order, and the sum. - uint64_t expected_bond(name underwriter) { - const auto op = get_operator(underwriter); - BOOST_REQUIRE(!op.is_null()); - uint64_t total = 0; - for (const auto& bal : op["balances"].get_array()) { - const uint64_t amount = bal["balance"].as_uint64(); - if (amount == 0) continue; - const auto chain = bal["chain_code"].as().value, - token = bal["token_code"].as().value; - const uint64_t wire = fc::slug_name{"WIRE"}.value; - if (chain == wire && token == wire) { - total += amount; // already WIRE — no curve - continue; - } - // A depot-native shadow bucket `(WIRE, LIQETH)` is priced on the outpost pair its - // `sysio.liq` stat row mirrors — `chalg::pricing_pair_for`; without a stat row it keeps - // its own (unreserved) pair. - const auto pair = chain == wire ? shadow_outpost_pair(token) : std::nullopt; - // A bucket with no ACTIVE reserve, or one whose books floor the conversion to zero, - // contributes the contract's floor rather than voiding the quote — mirrors - // `chalg::min_uwchal_bucket_wire`. - const auto row = pair ? first_active_reserve(pair->first, pair->second) - : first_active_reserve(chain, token); - const uint64_t bucket_wire = - row.is_null() ? 0 - : sysio::opp::amm::token_to_wire(row["reserve_chain_amount"].as_uint64(), - row["reserve_wire_amount"].as_uint64(), - row["connector_weight_bps"].as_uint64(), - amount); - total += (bucket_wire > 0 ? bucket_wire : MIN_UWCHAL_BUCKET_WIRE); - } - return total; - } - - /// The outpost `(chain_code, token_code)` pair the `sysio.liq` shadow symbol registered under - /// registry token code `token` mirrors, read off its `stat` row; `std::nullopt` when no shadow - /// symbol names `token` (or `sysio.liq` holds no stat rows at all). Walks the table rather - /// than the `bytoken` index — the host mirror of `chalg::pricing_pair_for`'s lookup. - std::optional> shadow_outpost_pair(uint64_t token) { - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("stat"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(LIQ_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == LIQ_ACCOUNT && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) std::memcpy(raw.data(), itr->value.data(), raw.size()); - const auto row = liq_abi.binary_to_variant("currency_stats", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["token_code"].as().value == token) { - return std::make_pair(row["chain_code"].as().value, token); - } - } - return std::nullopt; - } - - /// The FIRST ACTIVE reserve for a (chain, token) pair — the row the contract's `bychaintok` - /// walk lands on when pricing a collateral bucket that names no reserve of its own. - fc::variant first_active_reserve(uint64_t target_chain, uint64_t target_token) { - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("reserves"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(RESERV_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() && itr->code == RESERV_ACCOUNT - && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = reserv_abi.binary_to_variant("reserve_row", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token && - row["status"].as_string() == "RESERVE_STATUS_ACTIVE") { - return row; - } - } catch (...) { - // Not a reserve_row — skip. - } - } - return fc::variant(); - } - - // ── chalg action wrappers ──────────────────────────────────────────────── - - action_result openuwchal(name challenger, uint64_t uwreq_id, name underwriter, - uint8_t reason, const std::string& detail) { - return push(CHALG_ACCOUNT, chalg_abi, challenger, "openuwchal"_n, mvo() - ("challenger", challenger.to_string())("uwreq_id", uwreq_id) - ("underwriter", underwriter.to_string())("reason", reason)("detail", detail)); - } - - action_result voteuwchal(name owner, uint64_t chal_id, uint8_t ballot) { - return push(CHALG_ACCOUNT, chalg_abi, owner, "voteuwchal"_n, mvo() - ("owner", owner.to_string())("chal_id", chal_id)("ballot", ballot)); - } - - action_result chkuwchal(uint64_t chal_id, name signer = CHALLENGER) { - return push(CHALG_ACCOUNT, chalg_abi, signer, "chkuwchal"_n, mvo()("chal_id", chal_id)); - } - - /// Pull a resolved challenge's bond out of chalg custody, signed by the recipient itself. - action_result claimbond(name account, name signer = name()) { - return push(CHALG_ACCOUNT, chalg_abi, signer == name() ? account : signer, "claimbond"_n, - mvo()("account", account.to_string())); - } - - /// Decoded return of the read-only bond quote. Seals a block first: tests re-quote the SAME - /// (uwreq, underwriter) before and after filing, and identical bytes in one block window - /// collide on transaction dedup. - uint64_t uwchalbond(uint64_t uwreq_id, name underwriter) { - produce_block(); - auto trace = base_tester::push_action(CHALG_ACCOUNT, "uwchalbond"_n, CHALG_ACCOUNT, mvo() - ("uwreq_id", uwreq_id)("underwriter", underwriter.to_string())); - BOOST_REQUIRE(trace && !trace->action_traces.empty()); - return fc::raw::unpack(trace->action_traces[0].return_value); - } - - // ── row / balance readers ──────────────────────────────────────────────── - - fc::variant get_uwchal(uint64_t id) { - auto data = get_row_by_id(CHALG_ACCOUNT, CHALG_ACCOUNT, "uwchals"_n, id); - return data.empty() ? fc::variant() : chalg_abi.binary_to_variant( - "uwchal_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// The challenge's escrowed bond, straight off the row. - uint64_t uwchal_bond_amount(uint64_t id) { - return get_uwchal(id)["bond_amount"].as_uint64(); - } - - /// One owner's ballot in a challenge (the vote table is scoped by chal_id) — null once - /// resolution erased the scope. - fc::variant get_uwchal_vote(uint64_t chal_id, name owner) { - auto data = get_row_by_id(CHALG_ACCOUNT, name(chal_id), "uwchalvote"_n, owner.value); - return data.empty() ? fc::variant() : chalg_abi.binary_to_variant( - "uwchal_vote", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Unclaimed WIRE `sysio.chalg` owes `account` from resolved challenges; 0 when no row exists. - uint64_t get_bond_credit(name account) { - auto data = get_row_by_id(CHALG_ACCOUNT, CHALG_ACCOUNT, "bondcredits"_n, account.value); - if (data.empty()) return 0; - return chalg_abi.binary_to_variant( - "bond_credit", data, - abi_serializer::create_yield_function(abi_serializer_max_time))["amount"].as_uint64(); - } - - int64_t wire_balance(name account) { - return get_currency_balance(TOKEN_ACCOUNT, symbol{9, "WIRE"}, account).get_amount(); - } - - /// The decoded `sysio.opreg::operators` row. - fc::variant get_operator(name account) { - auto data = get_row_by_id(OPREG_ACCOUNT, OPREG_ACCOUNT, "operators"_n, account.value); - return data.empty() ? fc::variant() : opreg_abi.binary_to_variant( - "operator_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// The operator's status string straight off `sysio.opreg::operators`. - std::string operator_status(name account) { - return get_operator(account)["status"].as_string(); - } - - /// Sweep expired locks as the epoch machinery would (`chklocks` accepts epoch or self auth). - /// Defaults to the budget `sysio.epoch::advance` passes, so a challenge test sweeps exactly as - /// production does; pass a smaller one to exercise the bound itself. - action_result chklocks(uint32_t max_rows = kMaxLockReleasePerEpoch) { - return chklocks_direct(max_rows); - } -}; - -// The read-only quote prices the live locks through their own books and answers 0 for every -// not-challengeable state — the same soft contract as sysio.reserv::swapquote. -BOOST_FIXTURE_TEST_CASE(uwchalbond_quotes_the_live_lock_value, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9100; - make_confirmed_uwreq(ATT_ID); - - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), uwchalbond(ATT_ID, UWRIT_OP)); - BOOST_REQUIRE_GT(expected_bond(UWRIT_OP), 0u); - - BOOST_REQUIRE_EQUAL(0u, uwchalbond(ATT_ID + 1, UWRIT_OP)); // no such uwreq - BOOST_REQUIRE_EQUAL(0u, uwchalbond(ATT_ID, "batchop.a"_n)); // not the winner -} FC_LOG_AND_RETHROW() } - -/// WIRE-375: a direct depot deposit is already denominated in atomic WIRE-9, -/// so challenge-bond valuation must add exactly the deposited atomic amount. -/// The pre-existing ETH bucket keeps the confirmed request challengeable; the -/// before/after delta isolates the direct `(WIRE, WIRE)` collateral path. -BOOST_FIXTURE_TEST_CASE(wire_direct_deposit_adds_exact_units_to_uwchalbond, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9101; - const asset direct_deposit{2'000'000'000, symbol{9, "WIRE"}}; - make_confirmed_uwreq(ATT_ID); - - const uint64_t before = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, config::system_account_name, - "transfer"_n, mvo()("from", "sysio")("to", UWRIT_OP.to_string()) - ("quantity", direct_deposit)("memo", "fund native collateral"))); - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, UWRIT_OP, "deposit"_n, mvo() - ("account", UWRIT_OP.to_string())("token_code", codename_mvo("WIRE")) - ("amount", static_cast(direct_deposit.get_amount())))); - - const uint64_t after = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(before + static_cast(direct_deposit.get_amount()), after); - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), after); -} FC_LOG_AND_RETHROW() } - -// A collateral bucket whose (chain, token) pair carries NO ACTIVE reserve must not void the quote. -// `opreg::depositinle` accepts any positive amount for any pair without requiring one that quotes, -// so voiding here handed the underwriter an immunity switch: seed one unreserved bucket and every -// later CONFIRMED commitment becomes permanently unchallengeable. The bucket contributes the floor -// instead, and filing still works. -BOOST_FIXTURE_TEST_CASE(unreserved_collateral_bucket_still_quotes_a_bond, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9800; - make_confirmed_uwreq(ATT_ID); - - const uint64_t priced_only = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_GT(priced_only, 0u); - - // No reserve was ever registered for (SOLANA, USDC) — nothing to price the bucket against. - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "SOLANA", "USDC", 5'000)); - BOOST_REQUIRE(first_active_reserve(fc::slug_name{"SOLANA"}.value, - fc::slug_name{"USDC"}.value).is_null()); - - // Quote survives, grows by exactly the floor, and still matches the shared formula. - const uint64_t quoted = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(priced_only + MIN_UWCHAL_BUCKET_WIRE, quoted); - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), quoted); - - // The commitment is still challengeable — the property the void broke. - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no such deposit")); - BOOST_REQUIRE_EQUAL(static_cast(quoted), wire_balance(CHALG_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -/// A foreign-chain collateral bucket whose token codename is WIRE must not be -/// mistaken for depot-native WIRE and valued at par. With no ACTIVE reserve for -/// `(ETH, WIRE)`, it contributes only the unpriceable-bucket safety floor. -BOOST_FIXTURE_TEST_CASE(foreign_wire_collateral_bucket_is_not_native, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9801; - constexpr uint64_t FOREIGN_WIRE_AMOUNT = 5'000; - make_confirmed_uwreq(ATT_ID); - - const uint64_t priced_only = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(success(), - depositinle_credit(UWRIT_OP, "ETH", "WIRE", FOREIGN_WIRE_AMOUNT)); - BOOST_REQUIRE(first_active_reserve(fc::slug_name{"ETH"}.value, - fc::slug_name{"WIRE"}.value).is_null()); - - const uint64_t quoted = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(priced_only + MIN_UWCHAL_BUCKET_WIRE, quoted); - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), quoted); -} FC_LOG_AND_RETHROW() } - -// Same property for the DUST flavour: the pair HAS an ACTIVE reserve, but its books floor the -// conversion to zero. A deep token side over a shallow WIRE side does it at the 50% weight, where -// the curve is pure constant product: 1 * 1000 / (1e12 + 1) = 0. -BOOST_FIXTURE_TEST_CASE(dust_collateral_bucket_still_quotes_a_bond, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9810; - make_confirmed_uwreq(ATT_ID); - - const uint64_t priced_only = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_GT(priced_only, 0u); - - BOOST_REQUIRE_EQUAL(success(), regreserve_active_amounts("ETH", "USDC", "PRIMARY", - /*chain_amount*/ 1'000'000'000'000ull, - /*wire_amount*/ 1'000ull)); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "USDC", 1)); - - // Pin the premise: the pair IS priceable, and the books really do floor this bucket to zero. - const auto dust_row = first_active_reserve(fc::slug_name{"ETH"}.value, - fc::slug_name{"USDC"}.value); - BOOST_REQUIRE(!dust_row.is_null()); - BOOST_REQUIRE_EQUAL(0u, sysio::opp::amm::token_to_wire( - dust_row["reserve_chain_amount"].as_uint64(), - dust_row["reserve_wire_amount"].as_uint64(), - dust_row["connector_weight_bps"].as_uint64(), 1)); - - const uint64_t quoted = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(priced_only + MIN_UWCHAL_BUCKET_WIRE, quoted); - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), quoted); - - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no such deposit")); - BOOST_REQUIRE_EQUAL(static_cast(quoted), wire_balance(CHALG_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -/// A depot-native shadow LIQ bucket is keyed `(WIRE, LIQETH)` by `sysio.opreg::deposit`, and no -/// reserve carries that pair. The shadow mirrors the ETH outpost's LIQETH 1:1 in depot units, so -/// the bond prices it on the `(ETH, LIQETH)` books its `sysio.liq` stat row names: the same N -/// bonded as a shadow and escrowed on the outpost adds the same WIRE to the quote — well above -/// the unpriceable-bucket floor — and filing escrows that quote. -BOOST_FIXTURE_TEST_CASE(uwchal_bond_prices_depot_native_shadow_bucket_on_its_outpost_pair, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9820; - /// Bonded amount, in atomic LIQETH, on each side of the comparison. - constexpr uint64_t BONDED = 5 * static_cast(LIQ_UNIT); - /// WIRE depth of the (ETH, LIQETH) reserve — twice its token depth, so a LIQETH is worth - /// about 2 WIRE and the priced bucket is far from both par and the floor. - constexpr uint64_t LIQETH_RESERVE_TOKEN = 1'000'000'000'000ull; - constexpr uint64_t LIQETH_RESERVE_WIRE = 2'000'000'000'000ull; - make_confirmed_uwreq(ATT_ID); - setup_liq_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), regreserve_active_amounts("ETH", "LIQETH", "PRIMARY", - LIQETH_RESERVE_TOKEN, LIQETH_RESERVE_WIRE)); - - // Mint the shadow as sysio.synd releases it, then bond it through the operator-signed path. - BOOST_REQUIRE_EQUAL(success(), mint_shadow(UWRIT_OP, BONDED)); - BOOST_REQUIRE_EQUAL(static_cast(BONDED), liq_balance(UWRIT_OP)); - - const uint64_t base = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_GT(base, 0u); - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, UWRIT_OP, "deposit"_n, mvo() - ("account", UWRIT_OP.to_string())("token_code", codename_mvo("LIQETH"))("amount", BONDED))); - // Pin the premise: the bucket landed on the depot-native key, which no reserve carries. - const uint64_t wire = fc::slug_name{"WIRE"}.value, liqeth = fc::slug_name{"LIQETH"}.value; - BOOST_REQUIRE_EQUAL(BONDED, find_balance(get_operator(UWRIT_OP), "WIRE", "LIQETH")["balance"].as_uint64()); - BOOST_REQUIRE(first_active_reserve(wire, liqeth).is_null()); - - const uint64_t with_shadow = uwchalbond(ATT_ID, UWRIT_OP); - const uint64_t shadow_wire = with_shadow - base; - - // The same amount escrowed on the ETH outpost, priced on the same untouched books. - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "LIQETH", BONDED)); - const uint64_t with_both = uwchalbond(ATT_ID, UWRIT_OP); - const uint64_t outpost_wire = with_both - with_shadow; - - BOOST_REQUIRE_EQUAL(outpost_wire, shadow_wire); - BOOST_REQUIRE_GT(shadow_wire, MIN_UWCHAL_BUCKET_WIRE); - BOOST_REQUIRE_GT(shadow_wire, BONDED); // priced at ~2 WIRE per LIQETH, not at par - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), with_both); - - // The charge path agrees with the quote: filing escrows exactly it. - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no such deposit")); - BOOST_REQUIRE_EQUAL(static_cast(with_both), wire_balance(CHALG_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -/// A depot-native shadow bucket bonded through `sysio.opreg::deposit` prices on its outpost pair, -/// and when that pair has no ACTIVE reserve it contributes the unpriceable-bucket floor rather than -/// voiding the quote: an unquotable bucket must never block filing, the immunity switch the floor -/// exists to close. No `(ETH, LIQETH)` reserve is registered here. -BOOST_FIXTURE_TEST_CASE(uwchal_bond_floors_shadow_bucket_without_active_outpost_reserve, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9821; - constexpr uint64_t BONDED = 5 * static_cast(LIQ_UNIT); - make_confirmed_uwreq(ATT_ID); - setup_liq_for_dispatch(); - - const uint64_t eth = fc::slug_name{"ETH"}.value, liqeth = fc::slug_name{"LIQETH"}.value; - BOOST_REQUIRE(first_active_reserve(eth, liqeth).is_null()); - - BOOST_REQUIRE_EQUAL(success(), mint_shadow(UWRIT_OP, BONDED)); - const uint64_t priced_only = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_GT(priced_only, 0u); - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, UWRIT_OP, "deposit"_n, mvo() - ("account", UWRIT_OP.to_string())("token_code", codename_mvo("LIQETH"))("amount", BONDED))); - BOOST_REQUIRE_EQUAL(BONDED, find_balance(get_operator(UWRIT_OP), "WIRE", "LIQETH")["balance"].as_uint64()); - - const uint64_t quoted = uwchalbond(ATT_ID, UWRIT_OP); - BOOST_REQUIRE_EQUAL(priced_only + MIN_UWCHAL_BUCKET_WIRE, quoted); - BOOST_REQUIRE_EQUAL(expected_bond(UWRIT_OP), quoted); - - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no such deposit")); - BOOST_REQUIRE_EQUAL(static_cast(quoted), wire_balance(CHALG_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -/// A lock release on a TERMINATED operator pays the shadow yield termination could not cover. The -/// operator bonds shadow beside its locked (ETH, ETH) collateral; yield is distributed but not swept -/// before termination, so the payout covers none of it and it stays banked on the shadow row. Once -/// a sweep fills the pool, the next `releaselock` -- inline from `sysio.epoch::advance` in -/// production, on the (ETH, ETH) lock here -- settles every shadow row the operator holds and -/// credits the banked yield to its WIRE claim. -BOOST_FIXTURE_TEST_CASE(releaselock_on_terminated_operator_pays_banked_shadow_yield, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9822; - constexpr uint64_t BONDED = 5 * static_cast(LIQ_UNIT); - make_confirmed_uwreq(ATT_ID); // locks the whole (ETH, ETH) bond - setup_liq_for_dispatch(); - BOOST_REQUIRE_EQUAL(success(), mint_shadow(UWRIT_OP, BONDED)); - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, UWRIT_OP, "deposit"_n, mvo() - ("account", UWRIT_OP.to_string())("token_code", codename_mvo("LIQETH"))("amount", BONDED))); - - BOOST_REQUIRE_EQUAL(success(), push(LIQ_ACCOUNT, liq_abi, config::system_account_name, "addyield"_n, mvo() - ("from", "sysio")("quantity", "10.000000000 WIRE")("target", LIQETH_SYM.to_symbol_code()))); - - const auto wire_claim = [&]() -> uint64_t { - const auto data = get_row_by_id(OPREG_ACCOUNT, UWRIT_OP, "remitclaims"_n, fc::slug_name{"WIRE"}.value); - return data.empty() ? 0 : opreg_abi.binary_to_variant("remit_claim", data, - abi_serializer::create_yield_function(abi_serializer_max_time))["balance"].as_uint64(); - }; - const auto banked = [&]() -> uint64_t { - return find_balance(get_operator(UWRIT_OP), "WIRE", "LIQETH")["shadow_yield"]["owed_wire"].as_uint64(); - }; - - BOOST_REQUIRE_EQUAL(success(), terminate_op(UWRIT_OP, "test termination")); - const auto terminated = get_operator(UWRIT_OP); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_TERMINATED", terminated["status"].as_string()); - BOOST_REQUIRE_LT(0u, find_balance(terminated, "ETH", "ETH")["balance"].as_uint64()); // locked remainder - const uint64_t owed_at_termination = banked(); - BOOST_REQUIRE_LT(0u, owed_at_termination); - BOOST_REQUIRE_EQUAL(0u, wire_claim()); // the pool was empty: nothing covered yet - - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, CHALLENGER, "sweepyield"_n, mvo() - ("token_code", codename_mvo("LIQETH")))); - BOOST_REQUIRE_EQUAL(0u, wire_claim()); // a sweep only fills the pool - - BOOST_REQUIRE_EQUAL(success(), releaselock_direct(UWRIT_OP, "ETH", "ETH", LEG_AMOUNT)); - BOOST_REQUIRE_EQUAL(owed_at_termination, wire_claim()); - BOOST_REQUIRE_EQUAL(0u, banked()); -} FC_LOG_AND_RETHROW() } - -// Filing escrows exactly the quoted bond, stamps every lock with the challenge id, and records -// the OPEN row — and the quote answers 0 afterwards (a commitment is challengeable once). -BOOST_FIXTURE_TEST_CASE(openuwchal_escrows_bond_and_holds_locks, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9200; - make_confirmed_uwreq(ATT_ID); - - const uint64_t bond = uwchalbond(ATT_ID, UWRIT_OP); - const int64_t before = wire_balance(CHALLENGER); - - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no such deposit")); - - BOOST_REQUIRE_EQUAL(before - static_cast(bond), wire_balance(CHALLENGER)); - BOOST_REQUIRE_EQUAL(static_cast(bond), wire_balance(CHALG_ACCOUNT)); - - const auto chal = get_uwchal(1); - BOOST_REQUIRE(!chal.is_null()); - BOOST_REQUIRE_EQUAL("DISPUTE_STATUS_OPEN", chal["status"].as_string()); - BOOST_REQUIRE_EQUAL("NONE", chal["verdict"].as_string()); - BOOST_REQUIRE_EQUAL(ATT_ID, chal["uwreq_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(bond, chal["bond_amount"].as_uint64()); - // Four registered voters + the ONE genesis T1 owner (this assert breaks loudly if genesis - // ever seeds a different count — the quorum arithmetic below depends on it). - BOOST_REQUIRE_EQUAL(5u, chal["electorate"].get_array().size()); - BOOST_REQUIRE_EQUAL(3u, chal["quorum"].as_uint64()); - - BOOST_REQUIRE_EQUAL(1u, get_lock(1)["challenge_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(1u, get_lock(2)["challenge_id"].as_uint64()); - - BOOST_REQUIRE_EQUAL(0u, uwchalbond(ATT_ID, UWRIT_OP)); - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "again") - .find("already been challenged") != std::string::npos); -} FC_LOG_AND_RETHROW() } + permission_set permissions_of(name account) const { + permission_set out; + const auto& db = control->db(); + const auto& idx = db.get_index(); + for (auto it = idx.lower_bound(boost::make_tuple(account)); it != idx.end() && it->owner == account; ++it) { + const name parent = it->parent._id == 0 ? name{} : db.get(it->parent).name; + out.emplace(it->name, std::make_pair(parent, it->auth.to_authority())); + } + return out; + } +}; -// Every way a filing can be stale or malformed is refused before any WIRE moves. -BOOST_FIXTURE_TEST_CASE(openuwchal_guards, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9300; - make_confirmed_uwreq(ATT_ID); - - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID + 77, UWRIT_OP, REASON_DEPOSIT_MISSING, "x") - .find("not found") != std::string::npos); - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, "batchop.a"_n, REASON_DEPOSIT_MISSING, "x") - .find("not this request's winner") != std::string::npos); - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, /*reason*/ 99, "x") - .find("unknown fault reason") != std::string::npos); - // The caller-controlled detail note is retained on the audit row indefinitely (RAM billed to - // the contract) — the byte cap refuses the filing before any escrow moves. - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, - std::string(1024 + 1, 'x')) - .find("detail exceeds max_uwchal_detail_bytes") != std::string::npos); - - // Time-travel past the 12h window: the commitment is no longer challengeable, and the locks - // release healthy on the next sweep. Seal the pending block FIRST — a big skip aborts pending - // and re-applies its transactions at the jumped time, where they have expired. - const int64_t before = wire_balance(CHALLENGER); - produce_blocks(); - produce_block(fc::hours(13)); - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "late") - .find("lock window has closed") != std::string::npos); - // The whole transaction rolled back (the bond gate refuses pre-escrow here; a mid-window - // race would instead throw in holdlocks AFTER the escrow was sent — same atomicity): no - // WIRE moved, no challenge row survives. - BOOST_REQUIRE_EQUAL(before, wire_balance(CHALLENGER)); - BOOST_REQUIRE(get_uwchal(1).is_null()); -} FC_LOG_AND_RETHROW() } +// A tier-2/3 claim can take the name auth.msg and deploy code there, so an auth.msg::onlinkauth notification is +// attacker-controlled. It must reach sysio as an ordinary notification and change nothing: not sysio's owner, active, +// auth.ext or any other permission, and not a user's. +BOOST_FIXTURE_TEST_CASE(claimed_auth_msg_notification_changes_no_permission, node_owner_claim_tester) { try { + bootstrap_for_dispatch(NODE_OWNER_SOURCE_CHAIN); + create_account(PAYER_ACCOUNT); + deploy_system_contract(); -// Ballot guards mirror votedispute's: electorate membership, one vote per owner, a real ballot -// value, and an OPEN challenge. -BOOST_FIXTURE_TEST_CASE(voteuwchal_guards, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9400; - make_confirmed_uwreq(ATT_ID); - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "d")); - - BOOST_REQUIRE(voteuwchal(CHALLENGER, 1, BALLOT_UPHOLD) - .find("not in the challenge's tier-1 electorate") != std::string::npos); - BOOST_REQUIRE(voteuwchal(VOTER1, 1, /*ballot*/ 7).find("unknown ballot") != std::string::npos); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_UPHOLD)); - produce_block(); // identical re-vote bytes need fresh TAPOS to reach the contract's guard - BOOST_REQUIRE(voteuwchal(VOTER1, 1, BALLOT_UPHOLD) - .find("already voted") != std::string::npos); - BOOST_REQUIRE(chkuwchal(99).find("challenge not found") != std::string::npos); -} FC_LOG_AND_RETHROW() } + claim_node_owner(AUTH_MSG_ACCOUNT, get_public_key(AUTH_MSG_ACCOUNT, "active")); + const auto audit = get_nodeownerreg(AUTH_MSG_ACCOUNT); + BOOST_REQUIRE(!audit.is_null()); + BOOST_REQUIRE_EQUAL(audit["status"].as(), sysio_system::test_support::nodeownerreg::status_confirmed); + issue_own_policy(AUTH_MSG_ACCOUNT); + set_code(AUTH_MSG_ACCOUNT, notify_system_account_wasm()); + produce_block(); -// The uphold path end-to-end: quorum -> SLASHED -> the locks sweep through releaselock's -// deferred-slash branch (collateral debited) -> uwreq COMPLETED -> bond back to the challenger. -// An OPEN challenge PINS the uwreq row against the retention sweep. The locks hold the -// collateral, but THIS row holds the evidence the challenge adjudicates against -// (`attestation_inbound_data`, `source_tx_id`, `commits_by`), and the terminal retention -// window is far shorter than the challenge window — so without the pin a challenge could -// outlive the record it exists to adjudicate. Both release branches are covered: `freelocks` -// on a REJECTED verdict here, `sweeplocks` on UPHELD below. -BOOST_FIXTURE_TEST_CASE(pruneuwreqs_skips_a_challenged_uwreq, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9600; - make_confirmed_uwreq(ATT_ID); - - // Filing stamps the request, not just its locks. - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no deposit on ETH")); - BOOST_REQUIRE_EQUAL(1u, get_uwreq(ATT_ID)["challenge_id"].as_uint64()); - - // The sweep refuses it however generous the budget — the pin is not status- or - // retention-conditional. - BOOST_REQUIRE_EQUAL(success(), push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, - "pruneuwreqs"_n, mvo()("max_rows", 100))); - BOOST_REQUIRE(!get_uwreq(ATT_ID).is_null()); - - // REJECT_FORFEIT resolves through `freelocks`, which clears both markers. - for (auto v : {VOTER1, VOTER2, VOTER3}) { - BOOST_REQUIRE_EQUAL(success(), voteuwchal(v, 1, BALLOT_REJECT_FORFEIT)); + const auto replacement_key = get_public_key(REPLACEMENT_KEY_NAME, "active"); + const auto sysio_before = permissions_of(config::system_account_name); + const auto user_before = permissions_of(CLAIM_ACCOUNT); + for (const auto target : {config::system_account_name, CLAIM_ACCOUNT}) { + for (const auto permission : {config::owner_name, config::active_name, AUTH_EXT_PERMISSION, OTHER_PERMISSION}) { + BOOST_CHECK_MESSAGE(notify_onlinkauth(target, permission, replacement_key), + "onlinkauth for " << target.to_string() << "@" << permission.to_string() + << " did not commit as a plain notification"); + } } - produce_block(); - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["challenge_id"].as_uint64()); + BOOST_CHECK_MESSAGE(permissions_of(config::system_account_name) == sysio_before, "sysio permissions changed"); + BOOST_CHECK_MESSAGE(permissions_of(CLAIM_ACCOUNT) == user_before, "claimacct permissions changed"); } FC_LOG_AND_RETHROW() } -/// The UPHELD branch releases the pin too — `sweeplocks` ERASES the locks rather than -/// clearing their markers, so it is the only place the row's own marker comes off there. -/// Without it the row would be skipped forever and never reclaimable. -BOOST_FIXTURE_TEST_CASE(uphold_releases_the_uwreq_challenge_pin, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9610; - make_confirmed_uwreq(ATT_ID); +// No node owner may claim a name under the reserved sysio. prefix: the depot's claim is rejected as NAME_INVALID and +// no account is created. +BOOST_FIXTURE_TEST_CASE(node_owner_claim_rejects_reserved_system_name, node_owner_claim_tester) { try { + bootstrap_for_dispatch(NODE_OWNER_SOURCE_CHAIN); + deploy_system_contract(); - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no deposit on ETH")); - BOOST_REQUIRE_EQUAL(1u, get_uwreq(ATT_ID)["challenge_id"].as_uint64()); + claim_node_owner(RESERVED_SYSTEM_NAME, get_public_key(RESERVED_SYSTEM_NAME, "active")); - for (auto v : {VOTER1, VOTER2, VOTER3}) { - BOOST_REQUIRE_EQUAL(success(), voteuwchal(v, 1, BALLOT_UPHOLD)); - } - produce_block(); - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - BOOST_REQUIRE_EQUAL("UPHELD", get_uwchal(1)["verdict"].as_string()); - BOOST_REQUIRE_EQUAL(0u, get_uwreq(ATT_ID)["challenge_id"].as_uint64()); + const auto audit = get_nodeownerreg(RESERVED_SYSTEM_NAME); + BOOST_REQUIRE(!audit.is_null()); + BOOST_CHECK_EQUAL(audit["status"].as(), sysio_system::test_support::nodeownerreg::status_rejected); + BOOST_CHECK_EQUAL(audit["reason"].as(), sysio_system::test_support::nodeownerreg::reason_name_invalid); + BOOST_CHECK(get_nodeowner(RESERVED_SYSTEM_NAME).is_null()); + BOOST_CHECK(!account_exists(RESERVED_SYSTEM_NAME)); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(chkuwchal_uphold_slashes_and_returns_bond, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9500; - make_confirmed_uwreq(ATT_ID); - const int64_t challenger_start = wire_balance(CHALLENGER); +/// Regression: a non-advancing advance() must not permanently strand the epoch. +/// +/// When every active outpost has reached consensus and the wall clock has passed, chkcons triggers +/// sysio.epoch::advance. advance can legally return WITHOUT bumping the epoch when emissions are not +/// ready -- its emissions gate records a block and returns gracefully, it does not throw. The earlier +/// chkcons cleared per-outpost consensus_reached BEFORE calling advance, so that graceful return +/// committed the cleared state and nothing re-armed it (apply_consensus does not re-fire for an +/// already-complete delivery set) -- permanently stalling advancement even once emissions later became +/// ready. +/// +/// This fixture never deploys sysio.system, so the emissions gate always blocks (CONFIG_MISSING): +/// exactly the non-advancing path. After consensus, each chkcons must re-attempt advance, which the +/// gate records as blocklog.retry_count. Pre-fix, the second chkcons bailed at the consensus gate +/// (retry_count would stay 1) and the epoch was stuck forever. +BOOST_FIXTURE_TEST_CASE(chkcons_survives_non_advancing_advance, sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + const auto eth_code = fc::slug_name{"ETH"}.value; - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "no deposit on ETH")); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_UPHOLD)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER2, 1, BALLOT_UPHOLD)); - - // Sub-quorum (2 of Q=3): the crank resolves nothing and the operator stands. - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - BOOST_REQUIRE_EQUAL("DISPUTE_STATUS_OPEN", get_uwchal(1)["status"].as_string()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", operator_status(UWRIT_OP)); - - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER3, 1, BALLOT_UPHOLD)); - produce_block(); // the resolving crank re-pushes identical bytes — fresh TAPOS required - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - - const auto chal = get_uwchal(1); - BOOST_REQUIRE_EQUAL("DISPUTE_STATUS_RESOLVED", chal["status"].as_string()); - BOOST_REQUIRE_EQUAL("UPHELD", chal["verdict"].as_string()); - - // The operator is slashed and BOTH locks are gone — swept, not expired. - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_SLASHED", operator_status(UWRIT_OP)); - BOOST_REQUIRE(get_lock(1).is_null()); - BOOST_REQUIRE(get_lock(2).is_null()); - - // `sweeplocks` is a lock-erasure path that does NOT go through `chklocks`, so it carries the - // rollup obligation independently: the cache must not outlive the rows it summarizes. Assert - // both halves — agreement with an authoritative scan, and zero, since the sweep took every - // lock in the bucket. Without the `sub_locked_total` in `sweeplocks` the rollup stays at the - // pre-sweep 200 while the scan reads 0, and that phantom locked amount is permanent: it - // suppresses `available()` on this bucket for good, so collateral deposited after a later - // re-registration is unusable with nothing holding it. - BOOST_REQUIRE_EQUAL(scan_lock_total(UWRIT_OP, "ETH", "ETH"), - get_lock_sum(UWRIT_OP, "ETH", "ETH")); - BOOST_REQUIRE_EQUAL(0u, get_lock_sum(UWRIT_OP, "ETH", "ETH")); - - // Every bucket drains: `opreg::slash` debits each bucket's slashable-now portion (balance - // minus active locks — for the challenged (ETH, ETH) bucket that is everything ABOVE the two - // 100-unit locks), and the two sweeping releaselocks then take the deferred-slash branch for - // the locked 200. Unlocked buckets on other chains were slashed in full at slash time. - // (Bind the row first — ranging over `get_operator(...)[...]` would iterate a dangling - // temporary.) - const auto slashed_op = get_operator(UWRIT_OP); - for (const auto& bal : slashed_op["balances"].get_array()) { - BOOST_REQUIRE_EQUAL(0u, bal["balance"].as_uint64()); - } + // This fixture deploys no sysio.system, so even the genesis advance in bootstrap gate-blocked; the + // chain sits at `epoch0`. advance() always targets the next epoch, whose blocklog row counts gate + // re-attempts. + const uint32_t epoch0 = current_epoch(); + const uint32_t target = epoch0 + 1; - // COMPLETED flip ran through the shared tail. - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", - get_uwreq(ATT_ID)["status"].as_string()); - - // The bond is CREDITED, not pushed: resolution moves no WIRE at all, so custody is still - // chalg's and the challenger is still down the escrow. (The crank can run under the epoch - // tick, where a transfer would run the recipient's notification handler — see - // hostile_bond_recipient_cannot_stall_the_epoch_tick.) - const uint64_t bond = chal["bond_amount"].as_uint64(); - BOOST_REQUIRE_GT(bond, 0u); - BOOST_REQUIRE_EQUAL(challenger_start - static_cast(bond), wire_balance(CHALLENGER)); - BOOST_REQUIRE_EQUAL(static_cast(bond), wire_balance(CHALG_ACCOUNT)); - BOOST_REQUIRE_EQUAL(bond, get_bond_credit(CHALLENGER)); - - // The pull settles it: the bond comes home and chalg ends at zero custody. - BOOST_REQUIRE_EQUAL(success(), claimbond(CHALLENGER)); - BOOST_REQUIRE_EQUAL(challenger_start, wire_balance(CHALLENGER)); - BOOST_REQUIRE_EQUAL(0, wire_balance(CHALG_ACCOUNT)); - BOOST_REQUIRE_EQUAL(0u, get_bond_credit(CHALLENGER)); -} FC_LOG_AND_RETHROW() } + // operators_per_epoch == 1, so a single delivery is Option-A unanimous consensus; apply_consensus + // records the outpcons row for ETH at epoch0. + auto operator_payload = encode_operator_action( + sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, + sysio::opp::types::CHAIN_KIND_EVM, + uwrit_op_eth_pubkey, + /*chain_code_v=*/ eth_code, + /*token_code_v=*/ eth_code, + /*amount=*/ 1'000'000); + auto envelope = encode_envelope_with_one_attestation( + epoch0, + sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, + operator_payload); + BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/eth_code, envelope)); + produce_blocks(); // land the deliver in its own block (this fixture's push does not) -// An explicit REJECT_FORFEIT majority is the ONLY road to forfeiture: the bond lands on the -// wrongly-challenged underwriter, the holds clear, and the locks release healthy at expiry -// with the collateral untouched. -BOOST_FIXTURE_TEST_CASE(chkuwchal_reject_forfeit_pays_underwriter, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9600; - make_confirmed_uwreq(ATT_ID); - const uint64_t bond = uwchalbond(ATT_ID, UWRIT_OP); - const int64_t uw_start = wire_balance(UWRIT_OP); + auto retry_count = [&]() -> uint32_t { + auto bl = get_blocklog(target); + return bl.is_null() ? 0u : bl["retry_count"].as(); + }; + const uint32_t rc0 = retry_count(); - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "wrong")); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_REJECT_FORFEIT)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER2, 1, BALLOT_REJECT_FORFEIT)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER3, 1, BALLOT_REJECT_FORFEIT)); - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - - BOOST_REQUIRE_EQUAL("REJECTED_FORFEIT", get_uwchal(1)["verdict"].as_string()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", operator_status(UWRIT_OP)); - - // Forfeiture credits the underwriter; the WIRE lands when IT pulls. - BOOST_REQUIRE_EQUAL(bond, get_bond_credit(UWRIT_OP)); - BOOST_REQUIRE_EQUAL(uw_start, wire_balance(UWRIT_OP)); - BOOST_REQUIRE_EQUAL(success(), claimbond(UWRIT_OP)); - BOOST_REQUIRE_EQUAL(uw_start + static_cast(bond), wire_balance(UWRIT_OP)); - BOOST_REQUIRE_EQUAL(0, wire_balance(CHALG_ACCOUNT)); - - // Holds cleared; locks still present until natural expiry, then a HEALTHY release. - BOOST_REQUIRE_EQUAL(0u, get_lock(1)["challenge_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(0u, get_lock(2)["challenge_id"].as_uint64()); - const auto balances_before = get_operator(UWRIT_OP)["balances"]; + // Pass the wall clock (epoch_duration_sec == 60) so chkcons will fire advance, then refresh the head + // so the subsequent push is stamped against the post-skip time and does not expire. + produce_block(fc::seconds(120)); produce_blocks(); - produce_block(fc::hours(13)); - BOOST_REQUIRE_EQUAL(success(), chklocks()); - BOOST_REQUIRE(get_lock(1).is_null()); - // A healthy release moves NO collateral: the balances are byte-identical to the pre-expiry - // snapshot after a rejected challenge. - const auto balances_after = get_operator(UWRIT_OP)["balances"]; - BOOST_REQUIRE_EQUAL(fc::json::to_string(balances_before, fc::time_point::maximum()), - fc::json::to_string(balances_after, fc::time_point::maximum())); -} FC_LOG_AND_RETHROW() } -// A split reject quorum favours the refund on a tie — forfeiture requires a strict majority of -// the rejectors. -BOOST_FIXTURE_TEST_CASE(chkuwchal_reject_tie_favours_refund, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9700; - make_confirmed_uwreq(ATT_ID); - const int64_t challenger_start = wire_balance(CHALLENGER); + // First chkcons fires advance, which gate-blocks on missing emissions and returns without advancing. + // The epoch is unchanged and the gate bumps retry_count -- confirming advance was actually attempted. + BOOST_REQUIRE_EQUAL(success(), chkcons()); + produce_blocks(); + BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); + const uint32_t rc1 = retry_count(); + BOOST_REQUIRE_EQUAL(rc1, rc0 + 1); - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "honest mistake")); - // Four rejectors split 2–2: the combined rejects clear Q=3, and the forfeit/refund tie must - // fall to the refund — forfeiture only ever happens by a strict majority of the rejectors. - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_REJECT_REFUND)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER2, 1, BALLOT_REJECT_FORFEIT)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER3, 1, BALLOT_REJECT_REFUND)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER4, 1, BALLOT_REJECT_FORFEIT)); - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - - BOOST_REQUIRE_EQUAL("REJECTED_REFUND", get_uwchal(1)["verdict"].as_string()); - BOOST_REQUIRE_EQUAL(success(), claimbond(CHALLENGER)); - BOOST_REQUIRE_EQUAL(challenger_start, wire_balance(CHALLENGER)); + // REGRESSION: the per-outpost consensus signal must survive the non-advancing advance, so the second + // chkcons re-attempts advance (retry_count -> rc1 + 1). Pre-fix, chkcons cleared consensus_reached + // before calling advance, so this second call bailed at the consensus gate and never re-attempted -- + // retry_count would stay at rc1 and the epoch would be permanently stranded. + BOOST_REQUIRE_EQUAL(success(), chkcons()); + produce_blocks(); + BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); + BOOST_REQUIRE_EQUAL(retry_count(), rc1 + 1); } FC_LOG_AND_RETHROW() } -// The epoch tick IS the challenge's cadence: an expired-but-challenged lock is NOT released by -// the sweep; the sweep pokes the tally, the sub-quorum challenge LAPSES (bond refunded — silence -// never punishes), the freed locks release on the NEXT sweep. -BOOST_FIXTURE_TEST_CASE(chklocks_skips_held_lock_and_lapse_refunds, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9800; - make_confirmed_uwreq(ATT_ID); - const int64_t challenger_start = wire_balance(CHALLENGER); +// ═════════════════════════════════════════════════════════════════════════ +// msgch::bootstrap emissions guards. Missing emissions config is a bootstrap DEFECT, not an +// operational state: without these guards bootstrap's inline genesis advance would gate-block +// SILENTLY (the emissions gate never throws, by design) and the misconfiguration would surface +// only as "epoch stuck at 0". The three cases below cover both guards independently plus the +// fully-configured pass-through. advance()'s own soft block-and-retry behavior for the ECONOMIC +// gate reasons is covered above (chkcons_survives_non_advancing_advance) and in emissions_tests. +// ═════════════════════════════════════════════════════════════════════════ - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "nobody voted")); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_UPHOLD)); // sub-quorum (Q=3) - - produce_blocks(); // seal pending before the jump (see openuwchal_guards) - produce_block(fc::hours(13)); // past both the lock expiry and therefore the vote deadline - - // Past the deadline the ballot door is closed: a late quorum can never be assembled for a - // manual chkuwchal crank in the expiry→sweep gap — the only remaining outcome is LAPSED. - BOOST_REQUIRE(voteuwchal(VOTER2, 1, BALLOT_UPHOLD) - .find("the challenge window has expired") != std::string::npos); - - // First sweep: the held locks are SKIPPED (still present), the poke lapses the challenge. - BOOST_REQUIRE_EQUAL(success(), chklocks()); - BOOST_REQUIRE(!get_lock(1).is_null()); - BOOST_REQUIRE(!get_lock(2).is_null()); - BOOST_REQUIRE_EQUAL(0u, get_lock(1)["challenge_id"].as_uint64()); - BOOST_REQUIRE_EQUAL("LAPSED", get_uwchal(1)["verdict"].as_string()); - BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", operator_status(UWRIT_OP)); - // Silence never punishes: the whole bond is credited back, claimable on demand. - BOOST_REQUIRE_EQUAL(uwchal_bond_amount(1), get_bond_credit(CHALLENGER)); - BOOST_REQUIRE_EQUAL(success(), claimbond(CHALLENGER)); - BOOST_REQUIRE_EQUAL(challenger_start, wire_balance(CHALLENGER)); - - // Second sweep: the now-unheld expired locks release healthy; uwreq completes. - produce_block(); // identical sweep bytes — fresh TAPOS - BOOST_REQUIRE_EQUAL(success(), chklocks()); - BOOST_REQUIRE(get_lock(1).is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", - get_uwreq(ATT_ID)["status"].as_string()); +// Guard 1 — emitcfg missing. This fixture deploys no sysio.system at all (setemitcfg / initt5 +// never ran), which is exactly the never-configured deployment shape. +BOOST_FIXTURE_TEST_CASE(bootstrap_requires_emissions_config, sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("msgch::bootstrap: emissions config missing -- sysio.system::setemitcfg must run before bootstrap"), + push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); } FC_LOG_AND_RETHROW() } -// The epoch budget counts rows EXAMINED, not locks released. A held lock is skipped rather than -// released, so a budget spent only on releases would let an unbounded run of held rows be scanned -// — and every distinct challenge among them fan out its own inline `chkuwchal` — which is exactly -// the unbounded `advance` work the bound exists to delete. -// -// The discriminator is a budget SMALLER than the run of held locks, with an ordinary expired lock -// queued behind them: counting examined rows stops inside the held run and releases nothing; -// counting releases walks past every held row and reaches the ordinary lock in the same call. -BOOST_FIXTURE_TEST_CASE(chklocks_budget_counts_held_locks_and_still_drains, - sysio_uwchal_tester) { try { - constexpr uint64_t HELD_ATT = 9820; // challenged: contributes the two HELD locks (1, 2) - constexpr uint64_t ORDINARY_ATT = 9821; // unchallenged: its locks (3, 4) sit behind them - make_confirmed_uwreq(HELD_ATT); - - // A second commitment needs its own collateral; the bootstrap funds exactly one. Fresh TAPOS - // first — this credit is byte-identical to the bootstrap's and would otherwise collide as a - // duplicate transaction. - produce_block(); - BOOST_REQUIRE_EQUAL(success(), depositinle_credit(UWRIT_OP, "ETH", "ETH", 200)); - add_confirmed_uwreq(ORDINARY_ATT); - for (uint64_t id : {1u, 2u, 3u, 4u}) BOOST_REQUIRE(!get_lock(id).is_null()); - - // Hold ONLY the first commitment's locks, then expire everything. - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, HELD_ATT, UWRIT_OP, REASON_DEPOSIT_MISSING, "held run")); - BOOST_REQUIRE_GT(get_lock(1)["challenge_id"].as_uint64(), 0u); - BOOST_REQUIRE_GT(get_lock(2)["challenge_id"].as_uint64(), 0u); - BOOST_REQUIRE_EQUAL(0u, get_lock(3)["challenge_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(0u, get_lock(4)["challenge_id"].as_uint64()); - - produce_blocks(); - produce_block(fc::hours(13)); // every lock now expired; the challenge deadline has passed - - // Budget of 1 against a 2-lock held run: the sweep spends it on the first held row and stops. - // Nothing is released — in particular the ordinary locks behind the run are never reached. - BOOST_REQUIRE_EQUAL(success(), chklocks(1)); - for (uint64_t id : {1u, 2u, 3u, 4u}) BOOST_REQUIRE(!get_lock(id).is_null()); - - // Eventual progress: the pokes the budget DID afford lapse the challenge, and once the hold is - // gone the same bounded sweep drains the whole backlog over subsequent ticks. - for (int sweep = 0; sweep < 4; ++sweep) { - produce_block(); // identical sweep bytes — fresh TAPOS - BOOST_REQUIRE_EQUAL(success(), chklocks()); - } - BOOST_REQUIRE_EQUAL("LAPSED", get_uwchal(1)["verdict"].as_string()); - for (uint64_t id : {1u, 2u, 3u, 4u}) BOOST_REQUIRE(get_lock(id).is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", - get_uwreq(HELD_ATT)["status"].as_string()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", - get_uwreq(ORDINARY_ATT)["status"].as_string()); - - // The rollup tracked every release and lands at zero with the rows gone. - BOOST_REQUIRE_EQUAL(scan_lock_total(UWRIT_OP, "ETH", "ETH"), - get_lock_sum(UWRIT_OP, "ETH", "ETH")); - BOOST_REQUIRE_EQUAL(0u, get_lock_sum(UWRIT_OP, "ETH", "ETH")); +// Guard 2 — emitcfg present, t5state missing (the half-configured deployment: setemitcfg ran, +// initt5 was forgotten). Proves the SECOND check is independently reachable and reads t5state, +// not emitcfg again: guard 1 passes here, so a wrong table/account lookup or a shadowed copy of +// the first check would surface as the WRONG diagnostic (or no throw at all). +BOOST_FIXTURE_TEST_CASE(bootstrap_requires_emissions_state, sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + deploy_system_with_emitcfg(); // setemitcfg only -- initt5 deliberately not run + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("msgch::bootstrap: emissions state uninitialized -- sysio.system::initt5 must run before bootstrap"), + push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); } FC_LOG_AND_RETHROW() } -// A resolved challenge keeps only what consensus still needs: the uniqueness gate and the -// verdict. The caller-controlled detail note, the electorate snapshot, and every ballot row go — -// filing is permissionless and the bond returns on every non-forfeit outcome, so without this -// the same recycled capital could pin unbounded bytes in RAM billed to sysio. -BOOST_FIXTURE_TEST_CASE(chkuwchal_compacts_resolved_row_and_erases_ballots, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9250; - make_confirmed_uwreq(ATT_ID); +// Both guards pass — the genesis 0 -> 1 advance actually happens. Guards that always threw (or a +// gate that never passes) would look identical to guards 1 and 2 above, so this is the case that +// pins them as guards rather than a permanent bootstrap block. +BOOST_FIXTURE_TEST_CASE(bootstrap_advances_genesis_epoch_when_emissions_configured, + sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + setup_wire_token(); // payepoch pays WIRE out of sysio's token balance + enable_epoch_advancement(); // setemitcfg + initt5 + + // bootstrap_for_dispatch's own advance ran while emissions were unconfigured, so it + // gate-blocked and left a blocklog row for the target epoch. + BOOST_REQUIRE_EQUAL(current_epoch(), 0u); + BOOST_REQUIRE(!get_blocklog(1).is_null()); - const std::string detail(1024, 'e'); // a full-size note, right at max_uwchal_detail_bytes BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, detail)); - - // While OPEN the row carries everything the council votes against. - const auto open_row = get_uwchal(1); - BOOST_REQUIRE_EQUAL(detail, open_row["detail"].as_string()); - BOOST_REQUIRE_EQUAL(5u, open_row["electorate"].get_array().size()); - - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER1, 1, BALLOT_REJECT_REFUND)); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER2, 1, BALLOT_REJECT_REFUND)); - BOOST_REQUIRE(!get_uwchal_vote(1, VOTER1).is_null()); - BOOST_REQUIRE_EQUAL(success(), voteuwchal(VOTER3, 1, BALLOT_REJECT_REFUND)); - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - - // Resolved: the fixed-width record stands, both variable-length fields are empty, the ballots - // are gone. - const auto tomb = get_uwchal(1); - BOOST_REQUIRE_EQUAL("DISPUTE_STATUS_RESOLVED", tomb["status"].as_string()); - BOOST_REQUIRE_EQUAL("REJECTED_REFUND", tomb["verdict"].as_string()); - BOOST_REQUIRE_EQUAL(ATT_ID, tomb["uwreq_id"].as_uint64()); - BOOST_REQUIRE_EQUAL(3u, tomb["quorum"].as_uint64()); - BOOST_REQUIRE(tomb["detail"].as_string().empty()); - BOOST_REQUIRE_EQUAL(0u, tomb["electorate"].get_array().size()); - for (auto v : {VOTER1, VOTER2, VOTER3}) { - BOOST_REQUIRE(get_uwchal_vote(1, v).is_null()); - } + push(MSGCH_ACCOUNT, msgch_abi, MSGCH_ACCOUNT, "bootstrap"_n, mvo())); + produce_blocks(); - // The tombstone still does its consensus job — a verdict is final per commitment, even though - // the reject freed the locks and they are live again. - BOOST_REQUIRE_EQUAL(0u, uwchalbond(ATT_ID, UWRIT_OP)); - BOOST_REQUIRE(openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "retry") - .find("already been challenged") != std::string::npos); + // The inline genesis advance passed the gate: epoch 0 -> 1, and the gate cleared the stale + // blocklog row on its way through. + BOOST_REQUIRE_EQUAL(current_epoch(), 1u); + BOOST_REQUIRE(get_blocklog(1).is_null()); } FC_LOG_AND_RETHROW() } -// The payout is a pull under the recipient's own authority, and it settles exactly once. -BOOST_FIXTURE_TEST_CASE(claimbond_guards, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9350; - make_confirmed_uwreq(ATT_ID); +// A forged/invalid delivery cannot strand the epoch. SEC-102's semantic-header check runs at +// INGRESS (msgch::deliver's inbound_envelope_valid gate), so a forged envelope reverts on delivery +// and records no envelope row -- it can never reach the consensus tally and leave a phantom +// consensus_reached with no outpcons row. A subsequent VALID delivery reaches consensus normally and +// chkcons proceeds to ATTEMPT advance (retry_count bumps) rather than waiting forever for a consensus +// row that an accepted-then-soft-dropped invalid winner would have left missing. Drives the +// production chkcons gate, not a direct epoch::advance. +BOOST_FIXTURE_TEST_CASE(forged_delivery_does_not_strand_chkcons, sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + const auto eth_code = fc::slug_name{"ETH"}.value; + const uint32_t epoch0 = current_epoch(); + const uint32_t target = epoch0 + 1; - BOOST_REQUIRE(claimbond(CHALLENGER).find("no claimable bond") != std::string::npos); + auto operator_payload = encode_operator_action( + sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, + sysio::opp::types::CHAIN_KIND_EVM, + uwrit_op_eth_pubkey, + /*chain_code_v=*/ eth_code, + /*token_code_v=*/ eth_code, + /*amount=*/ 1'000'000); + const auto valid = encode_envelope_with_one_attestation( + epoch0, + sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, + operator_payload); - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "d")); - for (auto v : {VOTER1, VOTER2, VOTER3}) { - BOOST_REQUIRE_EQUAL(success(), voteuwchal(v, 1, BALLOT_REJECT_REFUND)); + // A forged copy: corrupt payload_checksum so the semantic header no longer recomputes. + std::vector forged; + { + sysio::opp::Envelope env; + BOOST_REQUIRE(env.ParseFromArray(valid.data(), static_cast(valid.size()))); + auto* h = env.mutable_messages(0)->mutable_header(); + std::string c = h->payload_checksum(); + c[0] ^= 0x01; + h->set_payload_checksum(c); + forged.resize(env.ByteSizeLong()); + env.SerializeToArray(forged.data(), static_cast(forged.size())); } - BOOST_REQUIRE_EQUAL(success(), chkuwchal(1)); - - const uint64_t bond = uwchal_bond_amount(1); - BOOST_REQUIRE_GT(bond, 0u); - BOOST_REQUIRE_EQUAL(bond, get_bond_credit(CHALLENGER)); - - // Another account cannot pull someone else's credit. - BOOST_REQUIRE(claimbond(CHALLENGER, /*signer*/ VOTER1) - .find("missing authority of challenger") != std::string::npos); - - const int64_t before = wire_balance(CHALLENGER); - BOOST_REQUIRE_EQUAL(success(), claimbond(CHALLENGER)); - BOOST_REQUIRE_EQUAL(before + static_cast(bond), wire_balance(CHALLENGER)); - // Erased on payout — a second pull draws nothing. - produce_block(); // identical claim bytes need fresh TAPOS to reach the contract's guard - BOOST_REQUIRE(claimbond(CHALLENGER).find("no claimable bond") != std::string::npos); -} FC_LOG_AND_RETHROW() } + // Forged delivery is rejected at ingress -- nothing recorded, no phantom consensus. + BOOST_REQUIRE_EQUAL( + error("assertion failure with message: delivered envelope failed inbound-chain or " + "semantic-header validation"), + deliver(eth_code, forged)); + produce_blocks(); -// A hostile bond recipient cannot hold epoch advancement hostage. `sysio.token::transfer` runs -// its recipient's code through `require_recipient(to)`, so a contract at the challenger's account -// can assert on the incoming refund. Were the payout pushed from the crank, that assert would -// abort `sysio.epoch::advance -> chklocks -> chkuwchal`, roll the challenge back to OPEN with its -// locks still held, and be retried identically by every later sweep — a permanent chain-wide -// stall bought with one permissionless filing. Crediting keeps the crank inside system-owned -// state: the challenge resolves, the locks release, and the only transaction the hostile account -// can abort is its own claim, stranding only its own funds. -BOOST_FIXTURE_TEST_CASE(hostile_bond_recipient_cannot_stall_the_epoch_tick, - sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9450; - make_confirmed_uwreq(ATT_ID); + auto retry_count = [&]() -> uint32_t { + auto bl = get_blocklog(target); + return bl.is_null() ? 0u : bl["retry_count"].as(); + }; - BOOST_REQUIRE_EQUAL(success(), - openuwchal(CHALLENGER, ATT_ID, UWRIT_OP, REASON_DEPOSIT_MISSING, "then i go hostile")); - const uint64_t bond = uwchal_bond_amount(1); - BOOST_REQUIRE_GT(bond, 0u); - - // The escrow is in; NOW the challenger deploys a contract that rejects every incoming - // notification. Nothing prevents this — filing is permissionless and code can land afterwards, - // so the payout path can never assume a bare recipient. (The fixture creates the challenger - // without a roa policy; setcode needs RAM, so grant one first.) - BOOST_REQUIRE(add_roa_policy(NODE_DADDY, CHALLENGER, "0.0010 SYS", "0.0010 SYS", - "50.0000 SYS", /*time_block*/ 0, /*network_gen*/ 0)); - set_code(CHALLENGER, contracts::util::reject_all_wasm()); - produce_blocks(); // seal pending before the jump (see openuwchal_guards) - produce_block(fc::hours(13)); // past the lock expiry: the sweep must now resolve the challenge - - // The epoch tick's sweep runs to completion: it pokes the tally, the sub-quorum challenge - // LAPSES, and nothing in the crank touches the hostile account. - BOOST_REQUIRE_EQUAL(success(), chklocks()); - BOOST_REQUIRE_EQUAL("LAPSED", get_uwchal(1)["verdict"].as_string()); - BOOST_REQUIRE_EQUAL(bond, get_bond_credit(CHALLENGER)); - BOOST_REQUIRE_EQUAL(static_cast(bond), wire_balance(CHALG_ACCOUNT)); - - // The freed locks release on the next sweep and the uwreq settles — advancement never stalled. - produce_block(); // identical sweep bytes — fresh TAPOS - BOOST_REQUIRE_EQUAL(success(), chklocks()); - BOOST_REQUIRE(get_lock(1).is_null()); - BOOST_REQUIRE(get_lock(2).is_null()); - BOOST_REQUIRE_EQUAL("UNDERWRITE_REQUEST_STATUS_COMPLETED", - get_uwreq(ATT_ID)["status"].as_string()); - - // Only the hostile account's OWN claim fails, and its credit simply stays put. - BOOST_REQUIRE(claimbond(CHALLENGER).find("rejecting all notifications") != std::string::npos); - BOOST_REQUIRE_EQUAL(bond, get_bond_credit(CHALLENGER)); - BOOST_REQUIRE_EQUAL(static_cast(bond), wire_balance(CHALG_ACCOUNT)); -} FC_LOG_AND_RETHROW() } + // The valid delivery (operators_per_epoch == 1 => Option-A unanimous) reaches consensus normally. + BOOST_REQUIRE_EQUAL(success(), deliver(eth_code, valid)); + produce_blocks(); + const uint32_t rc0 = retry_count(); -// The lock-control trio is chalg's alone. -BOOST_FIXTURE_TEST_CASE(lock_hold_actions_require_chalg_auth, sysio_uwchal_tester) { try { - constexpr uint64_t ATT_ID = 9900; - make_confirmed_uwreq(ATT_ID); - - BOOST_REQUIRE(push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "holdlocks"_n, mvo() - ("uwreq_id", ATT_ID)("underwriter", UWRIT_OP.to_string())("chal_id", 7)) - .find("missing authority of sysio.chalg") != std::string::npos); - BOOST_REQUIRE(push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "freelocks"_n, mvo() - ("uwreq_id", ATT_ID)("underwriter", UWRIT_OP.to_string())) - .find("missing authority of sysio.chalg") != std::string::npos); - BOOST_REQUIRE(push(UWRIT_ACCOUNT, uwrit_abi, UWRIT_ACCOUNT, "sweeplocks"_n, mvo() - ("uwreq_id", ATT_ID)("underwriter", UWRIT_OP.to_string())) - .find("missing authority of sysio.chalg") != std::string::npos); + // Pass the wall clock so chkcons fires advance; confirm it ATTEMPTED advance (retry_count bumps), + // i.e. it was NOT stranded waiting on a missing consensus row. Advance itself gate-blocks on + // emissions (this fixture deploys no sysio.system), exactly as in chkcons_survives_non_advancing_advance. + produce_block(fc::seconds(120)); + produce_blocks(); + BOOST_REQUIRE_EQUAL(success(), chkcons()); + produce_blocks(); + BOOST_REQUIRE_EQUAL(current_epoch(), epoch0); + BOOST_REQUIRE_EQUAL(retry_count(), rc0 + 1); } FC_LOG_AND_RETHROW() } -// An OPERATOR_ACTION whose `op_address` is not a key the chain family can link — here 20 -// address bytes where the EVM link holds the 33-byte pubkey — resolves to no account and is -// dropped; it must never abort the envelope (a link lookup only ever hashes EM / ED keys). -BOOST_FIXTURE_TEST_CASE(operator_action_with_a_malformed_address_is_dropped, - sysio_dispatch_tester) { try { - bootstrap_for_dispatch(); - const auto eth = fc::slug_name{"ETH"}.value; - const std::vector evm_address(20, '\x0c'); - const auto envelope = encode_envelope_with_one_attestation( - current_epoch(), sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION, - encode_operator_action(sysio::opp::attestations::OperatorAction::ACTION_TYPE_DEPOSIT_REQUEST, - ChainKind::CHAIN_KIND_EVM, evm_address, eth, eth, 1'000'000)); - BOOST_REQUIRE_EQUAL(success(), deliver(/*chain_code=*/ eth, envelope)); - const auto op = get_operator(UWRIT_OP); - BOOST_REQUIRE(!op.is_null()); - BOOST_CHECK_EQUAL(0u, op["balances"].get_array().size()); -} FC_LOG_AND_RETHROW() } +; // ── SYNDICATE_LIQ / LIQ_YIELD -> sysio.synd ───────────────────────────────── @@ -7219,6 +1615,34 @@ BOOST_FIXTURE_TEST_CASE(msgch_routes_syndicate_liq_to_synd, BOOST_CHECK_EQUAL(9 * LIQ_UNIT, liq_balance(SYND_ACCOUNT)); } FC_LOG_AND_RETHROW() } +// Retired payloads must be inert and must not prevent current LIQ traffic in +// the same envelope from reaching the syndication contract. +BOOST_FIXTURE_TEST_CASE(retired_attestations_do_not_mutate_collateral_or_block_liq, + sysio_dispatch_tester) { try { + bootstrap_for_dispatch(); + setup_liq_for_dispatch(); + 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, ATTESTATION_TYPE_RESERVE_CREATE_CANCEL}) { + entries.emplace_back(type, std::string(1, '\x0a')); + } + entries.emplace_back(ATTESTATION_TYPE_SYNDICATE_LIQ, + encode_syndicate_liq(eth, ChainKind::CHAIN_KIND_EVM, uwrit_op_eth_pubkey, + liqeth, LIQ_UNIT, 1)); + const auto trace = deliver_trace(eth, + encode_envelope_with_mixed_attestations(current_epoch(), entries)); + BOOST_REQUIRE(trace != nullptr); + BOOST_REQUIRE(!trace->except); + BOOST_CHECK(get_operator(UWRIT_OP)["balances"].get_array().empty()); + BOOST_CHECK(get_wtdw(0).is_null()); + BOOST_REQUIRE_EQUAL(1u, synd_items().size()); + BOOST_CHECK_EQUAL(LIQ_UNIT, liq_supply()); + BOOST_CHECK_EQUAL(LIQ_UNIT, liq_balance(SYND_ACCOUNT)); +} FC_LOG_AND_RETHROW() } + // sysio.msgch forwards each message's outpost custody total verbatim as the last argument of // `onsynd` and `onyield`: every syndication and yield report of one envelope carries its own reading. BOOST_FIXTURE_TEST_CASE(msgch_forwards_the_custody_total_to_synd, @@ -7488,7 +1912,7 @@ BOOST_FIXTURE_TEST_CASE(a_pulled_andon_cord_leaves_advance_running, sysio_dispat abi_serializer andon_abi; andon::deploy(*this, andon_abi, contracts::andon_wasm(), contracts::andon_abi()); BOOST_REQUIRE_EQUAL(success(), andon::pull(*this, andon_abi)); - setup_wire_token_and_reserves(); + setup_wire_token(); enable_epoch_advancement(); BOOST_REQUIRE_EQUAL(current_epoch(), 0u); @@ -7498,7 +1922,6 @@ BOOST_FIXTURE_TEST_CASE(a_pulled_andon_cord_leaves_advance_running, sysio_dispat BOOST_REQUIRE(get_blocklog(1).is_null()); } FC_LOG_AND_RETHROW() } - // ── The solvency check (sysio.synd) through a delivered envelope ───────────── // Review focus 3: sysio.msgch sends an envelope's messages to sysio.synd as inline actions, and each @@ -7553,7 +1976,7 @@ BOOST_FIXTURE_TEST_CASE(a_custody_shortfall_pulls_the_cord_and_the_epoch_advance setup_liq_for_dispatch(); BOOST_REQUIRE_EQUAL(success(), push(andon::account, andon_abi, config::system_account_name, "addpuller"_n, mvo()("contract", SYND_ACCOUNT))); - setup_wire_token_and_reserves(); + setup_wire_token(); enable_epoch_advancement(); const auto eth = fc::slug_name{"ETH"}.value; @@ -7610,7 +2033,6 @@ BOOST_FIXTURE_TEST_CASE(a_custody_shortfall_pulls_the_cord_and_the_epoch_advance BOOST_CHECK_EQUAL(current_epoch(), epoch + 1); } FC_LOG_AND_RETHROW() } - // Review focus 4 with work pending: the cord a shortfall pulls inline from `onsynd` freezes the very // queue step `closeenv` runs later in the same delivered envelope. The earlier envelope of the pair was // partly bonded, challenged and ruled VALID, so the next step would record the ruling, claim the hold-bond @@ -7641,7 +2063,7 @@ BOOST_FIXTURE_TEST_CASE(a_shortfall_freezes_the_same_envelopes_queue_step, sysio ("chain_code", codename_mvo("ETH"))("token_code", codename_mvo("LIQETH"))("synd_fee_bps", 0) ("desynd_fee_bps", 0)("synd_burst", 1'000'000 * LIQ_UNIT)("synd_refill", 1'000'000 * LIQ_UNIT) ("desynd_burst", 0)("desynd_refill", 0)("window_sec", 10800)("bounty", 0)("challenge_extra", 0))); - setup_wire_token_and_reserves(); + setup_wire_token(); enable_epoch_advancement(); const auto eth = fc::slug_name{"ETH"}.value; @@ -7750,7 +2172,6 @@ BOOST_FIXTURE_TEST_CASE(a_shortfall_freezes_the_same_envelopes_queue_step, sysio BOOST_CHECK(!envelope["share_pending"].as_bool()); } FC_LOG_AND_RETHROW() } - // The host-side simulator feeds canonical protobuf envelopes into the production // msgch -> epoch consensus -> synd -> liq path. No patched OPP or dispatch stub. BOOST_AUTO_TEST_CASE(generic_external_simulator_drives_production_dispatch) { @@ -7784,7 +2205,6 @@ BOOST_AUTO_TEST_CASE(generic_external_simulator_drives_production_dispatch) { } } - // A real staking-reward envelope crosses msgch -> dclaim -> system -> token. // Small treasury liquidity deliberately reaches the second fundclaim cap. BOOST_FIXTURE_TEST_CASE(generic_staking_rewards_fund_claim_and_dedupe_through_dispatch, sysio_dispatch_tester) { diff --git a/contracts/tests/sysio.dispute_tests.cpp b/contracts/tests/sysio.dispute_tests.cpp index 0c73b93940..206470e5bd 100644 --- a/contracts/tests/sysio.dispute_tests.cpp +++ b/contracts/tests/sysio.dispute_tests.cpp @@ -96,7 +96,6 @@ class sysio_dispute_tester : public tester { static constexpr auto EPOCH_ACCOUNT = "sysio.epoch"_n; static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; static constexpr auto OPREG_ACCOUNT = "sysio.opreg"_n; - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; static constexpr auto ROA_ACCOUNT = "sysio.roa"_n; static constexpr auto BATCHOP = "batchop.a"_n; @@ -111,7 +110,7 @@ class sysio_dispute_tester : public tester { create_accounts({ CHALG_ACCOUNT, EPOCH_ACCOUNT, MSGCH_ACCOUNT, OPREG_ACCOUNT, - UWRIT_ACCOUNT, CHAINS_ACCOUNT, BATCHOP + CHAINS_ACCOUNT, BATCHOP, "sysio.token"_n }); // Tier-1 voter accounts (node owners) and a Tier-2 account (ineligible voter). Created without // a roa policy so regnodeowner's reslimit creation does not collide. @@ -124,7 +123,6 @@ class sysio_dispute_tester : public tester { deploy(EPOCH_ACCOUNT, contracts::epoch_wasm(), contracts::epoch_abi(), epoch_abi); deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi); deploy(OPREG_ACCOUNT, contracts::opreg_wasm(), contracts::opreg_abi(), opreg_abi); - deploy(UWRIT_ACCOUNT, contracts::uwrit_wasm(), contracts::uwrit_abi(), uwrit_abi); deploy(CHAINS_ACCOUNT, contracts::chains_wasm(), contracts::chains_abi(), chains_abi); // sysio.roa is a genesis system account already running this build's code; load its abi. @@ -371,20 +369,25 @@ class sysio_dispute_tester : public tester { ("min_bond", min_bond)("config_timestamp_ms", uint64_t{0})); } - action_result depositinle(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "depositinle"_n, mvo() - ("account", account.to_string())("chain_code", chain_code) - ("token_code", token_code)("amount", amount) - ("actor_chain", ChainKind::CHAIN_KIND_EVM)("actor_address", std::vector{}) - ("original_message_id", std::string(64, '0'))); + action_result fund_and_bond(name account, uint64_t amount) { + const auto token = "sysio.token"_n; + set_code(token, contracts::token_wasm()); + set_abi(token, contracts::token_abi().data()); + set_privileged(token); + base_tester::push_action(token, "create"_n, token, + mvo()("issuer", "sysio")("maximum_supply", "1000000000.000000000 WIRE")); + const auto quantity = asset(static_cast(amount), symbol(9, "WIRE")); + base_tester::push_action(token, "issue"_n, config::system_account_name, + mvo()("to", "sysio")("quantity", quantity)("memo", "bond funding")); + base_tester::push_action(token, "transfer"_n, config::system_account_name, + mvo()("from", "sysio")("to", account)("quantity", quantity)("memo", "bond funding")); + return push(OPREG_ACCOUNT, opreg_abi, account, "deposit"_n, + mvo()("account", account)("token_code", "WIRE")("amount", amount)); } - action_result withdrawinle(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "withdrawinle"_n, mvo() - ("account", account.to_string())("chain_code", chain_code) - ("token_code", token_code)("amount", amount)); + action_result withdraw(name account, uint64_t amount) { + return push(OPREG_ACCOUNT, opreg_abi, account, "withdraw"_n, + mvo()("account", account)("token_code", "WIRE")("amount", amount)); } action_result flushwtdw(uint32_t up_to_epoch) { @@ -403,7 +406,7 @@ class sysio_dispute_tester : public tester { ("batch_op_name", op.to_string())("chain_code", chain_code)("data", data)); } - abi_serializer chalg_abi, epoch_abi, msgch_abi, opreg_abi, uwrit_abi, chains_abi, roa_abi, system_abi; + abi_serializer chalg_abi, epoch_abi, msgch_abi, opreg_abi, chains_abi, roa_abi, system_abi; }; /// The same OPP stack WITHOUT the emissions configuration: for tests that register node owners @@ -1064,14 +1067,14 @@ BOOST_FIXTURE_TEST_CASE(deliver_rejects_decollateralized_unknown_group_member, s ("terminate_prune_delay_ms", 600000)("terminate_max_consecutive_misses", 5) ("terminate_max_pct_misses_24h", 5)("terminate_window_ms", uint64_t{24ULL * 60 * 60 * 1000}) ("req_prod_collat", fc::variants{}) - ("req_batchop_collat", fc::variants{make_chain_min_bond("ETH", "ETH", MIN_BOND)}) + ("req_batchop_collat", fc::variants{make_chain_min_bond("WIRE", "WIRE", MIN_BOND)}) ("req_uw_collat", fc::variants{}))); // Register + fully collateralize batchop.b -> ACTIVE. BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "regoperator"_n, mvo() ("account", BATCHOP_B.to_string())("type", OperatorType::OPERATOR_TYPE_BATCH) ("is_bootstrapped", false))); - BOOST_REQUIRE_EQUAL(success(), depositinle(BATCHOP_B, "ETH", "ETH", MIN_BOND)); + BOOST_REQUIRE_EQUAL(success(), fund_and_bond(BATCHOP_B, MIN_BOND)); BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_ACTIVE", operator_status(BATCHOP_B)); // Schedule it into the single-slot group (non-bootstrapped sorts ahead of the @@ -1082,7 +1085,7 @@ BOOST_FIXTURE_TEST_CASE(deliver_rejects_decollateralized_unknown_group_member, s // Withdraw the full bond and flush past maturity -> reevaluate drops it to UNKNOWN. // The epoch group snapshot is untouched (no reschedule), so it stays resident. - BOOST_REQUIRE_EQUAL(success(), withdrawinle(BATCHOP_B, "ETH", "ETH", MIN_BOND)); + BOOST_REQUIRE_EQUAL(success(), withdraw(BATCHOP_B, MIN_BOND)); BOOST_REQUIRE_EQUAL(success(), flushwtdw(1'000'000u)); BOOST_REQUIRE_EQUAL("OPERATOR_STATUS_UNKNOWN", operator_status(BATCHOP_B)); BOOST_REQUIRE(group_contains(current_group(), BATCHOP_B)); diff --git a/contracts/tests/sysio.epoch_flushwtdw_tests.cpp b/contracts/tests/sysio.epoch_flushwtdw_tests.cpp index 3fc1c889e5..f65f28adf7 100644 --- a/contracts/tests/sysio.epoch_flushwtdw_tests.cpp +++ b/contracts/tests/sysio.epoch_flushwtdw_tests.cpp @@ -33,7 +33,6 @@ class sysio_epoch_flushwtdw_tester : public tester { static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; static constexpr auto BATCHOP = "batchop.a"_n; static constexpr auto UWRIT_OP = "uwrit.alice"_n; static constexpr auto UWRIT_OP_B = "uwrit.bob"_n; @@ -63,7 +62,7 @@ class sysio_epoch_flushwtdw_tester : public tester { // throws `account_name_exists_exception`. create_accounts({ TOKEN_ACCOUNT, EPOCH_ACCOUNT, OPREG_ACCOUNT, MSGCH_ACCOUNT, - CHALG_ACCOUNT, CHAINS_ACCOUNT, UWRIT_ACCOUNT, BATCHOP, UWRIT_OP, + CHALG_ACCOUNT, CHAINS_ACCOUNT, BATCHOP, UWRIT_OP, UWRIT_OP_B, UWRIT_OP_C, "sysio.dclaim"_n, "sysio.gov"_n, "sysio.ops"_n }); @@ -79,7 +78,6 @@ class sysio_epoch_flushwtdw_tester : public tester { deploy(OPREG_ACCOUNT, contracts::opreg_wasm(), contracts::opreg_abi(), opreg_abi); deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi); deploy(CHAINS_ACCOUNT, contracts::chains_wasm(), contracts::chains_abi(), chains_abi); - deploy(UWRIT_ACCOUNT, contracts::uwrit_wasm(), contracts::uwrit_abi(), uwrit_abi); deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi); produce_blocks(1); @@ -288,29 +286,6 @@ class sysio_epoch_flushwtdw_tester : public tester { ("is_bootstrapped", false)); } - /// Direct opreg::depositinle, signed as opreg itself. - /// Signature: codenames for chain and token, plus the actor identity. - action_result depositinle(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "depositinle"_n, mvo() - ("account", account.to_string()) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount) - ("actor_chain", ChainKind::CHAIN_KIND_EVM) - ("actor_address", std::vector{}) - ("original_message_id", std::string(64, '0'))); - } - - action_result withdrawinle(name account, std::string_view chain_code, - std::string_view token_code, uint64_t amount) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "withdrawinle"_n, mvo() - ("account", account.to_string()) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount)); - } - action_result slash(name account, std::string reason) { return push(OPREG_ACCOUNT, opreg_abi, CHALG_ACCOUNT, "slash"_n, mvo() ("account", account.to_string()) @@ -363,23 +338,30 @@ class sysio_epoch_flushwtdw_tester : public tester { return n; } - std::vector> - collect_attestation_data(sysio::opp::types::AttestationType expected, - uint64_t scan_until = 32) { - std::vector> out; - for (uint64_t id = 0; id < scan_until; ++id) { - auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, - "attestations"_n, id); - if (data.empty()) continue; - auto row = msgch_abi.binary_to_variant("attestation_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["type"].as() != expected) continue; - out.push_back(row["data"].as>()); - } - return out; + action_result fund_and_bond(name account, std::string_view chain, std::string_view token, uint64_t amount) { + BOOST_REQUIRE_EQUAL("WIRE", chain); + const auto quantity = asset(static_cast(amount), symbol(9, "WIRE")); + BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, SYSIO_ACCOUNT, "transfer"_n, + mvo()("from", SYSIO_ACCOUNT)("to", account)("quantity", quantity)("memo", "bond funding"))); + return push(OPREG_ACCOUNT, opreg_abi, account, "deposit"_n, + mvo()("account", account)("token_code", token)("amount", amount)); } - abi_serializer sysio_abi, token_abi, epoch_abi, opreg_abi, msgch_abi, chains_abi, uwrit_abi; + action_result withdraw(name account, std::string_view chain, std::string_view token, uint64_t amount) { + BOOST_REQUIRE_EQUAL("WIRE", chain); + return push(OPREG_ACCOUNT, opreg_abi, account, "withdraw"_n, + mvo()("account", account)("token_code", token)("amount", amount)); + } + + void claim_exact(name account, uint64_t amount) { + const auto before = get_currency_balance(TOKEN_ACCOUNT, symbol(9, "WIRE"), account).get_amount(); + BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, account, "claimremit"_n, + mvo()("account", account)("token_code", "WIRE"))); + BOOST_CHECK_EQUAL(before + amount, + get_currency_balance(TOKEN_ACCOUNT, symbol(9, "WIRE"), account).get_amount()); + } + + abi_serializer sysio_abi, token_abi, epoch_abi, opreg_abi, msgch_abi, chains_abi; }; // ---- Tests ---- @@ -394,29 +376,30 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_requires_epoch_auth, sysio_epoch_flushwtdw_tes BOOST_REQUIRE(r.find("missing authority of sysio.epoch") != std::string::npos); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(advance_drains_matured_eth_withdraw, sysio_epoch_flushwtdw_tester) { try { +BOOST_FIXTURE_TEST_CASE(advance_releases_matured_native_withdraw, sysio_epoch_flushwtdw_tester) { try { bootstrap_for_flushwtdw(); constexpr uint64_t INITIAL_DEPOSIT = 5'000'000; constexpr uint64_t WITHDRAW_AMOUNT = 2'000'000; BOOST_REQUIRE_EQUAL(success(), - depositinle(UWRIT_OP, "ETH", "ETH", INITIAL_DEPOSIT)); + fund_and_bond(UWRIT_OP, "WIRE", "WIRE", INITIAL_DEPOSIT)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(UWRIT_OP, "ETH", "ETH", WITHDRAW_AMOUNT)); + withdraw(UWRIT_OP, "WIRE", "WIRE", WITHDRAW_AMOUNT)); BOOST_REQUIRE(!get_wtdw(1).is_null()); - BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT, balance_of(UWRIT_OP, "ETH", "ETH")); + BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT, balance_of(UWRIT_OP, "WIRE", "WIRE")); advance_one_epoch(); advance_one_epoch(); BOOST_REQUIRE(get_wtdw(1).is_null()); BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT - WITHDRAW_AMOUNT, - balance_of(UWRIT_OP, "ETH", "ETH")); + balance_of(UWRIT_OP, "WIRE", "WIRE")); + claim_exact(UWRIT_OP, WITHDRAW_AMOUNT); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(flushwtdw_direct_emits_withdraw_remit_attestation, +BOOST_FIXTURE_TEST_CASE(flushwtdw_credits_native_claim_without_outbound_attestation, sysio_epoch_flushwtdw_tester) { try { bootstrap_for_flushwtdw(); @@ -424,9 +407,9 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_direct_emits_withdraw_remit_attestation, constexpr uint64_t WITHDRAW_AMOUNT = 400'000; BOOST_REQUIRE_EQUAL(success(), - depositinle(UWRIT_OP, "ETH", "ETH", INITIAL_DEPOSIT)); + fund_and_bond(UWRIT_OP, "WIRE", "WIRE", INITIAL_DEPOSIT)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(UWRIT_OP, "ETH", "ETH", WITHDRAW_AMOUNT)); + withdraw(UWRIT_OP, "WIRE", "WIRE", WITHDRAW_AMOUNT)); constexpr uint32_t FUTURE_EPOCH = 100; BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, EPOCH_ACCOUNT, @@ -434,10 +417,11 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_direct_emits_withdraw_remit_attestation, BOOST_REQUIRE(get_wtdw(1).is_null()); BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT - WITHDRAW_AMOUNT, - balance_of(UWRIT_OP, "ETH", "ETH")); + balance_of(UWRIT_OP, "WIRE", "WIRE")); - BOOST_REQUIRE_GE(count_attestations( - sysio::opp::types::AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION), 1u); + BOOST_REQUIRE_EQUAL(count_attestations( + sysio::opp::types::AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION), 0u); + claim_exact(UWRIT_OP, WITHDRAW_AMOUNT); } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(single_advance_leaves_immature_row_intact, @@ -448,14 +432,14 @@ BOOST_FIXTURE_TEST_CASE(single_advance_leaves_immature_row_intact, constexpr uint64_t WITHDRAW_AMOUNT = 400'000; BOOST_REQUIRE_EQUAL(success(), - depositinle(UWRIT_OP, "ETH", "ETH", INITIAL_DEPOSIT)); + fund_and_bond(UWRIT_OP, "WIRE", "WIRE", INITIAL_DEPOSIT)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(UWRIT_OP, "ETH", "ETH", WITHDRAW_AMOUNT)); + withdraw(UWRIT_OP, "WIRE", "WIRE", WITHDRAW_AMOUNT)); advance_one_epoch(); // only one boundary — eligible_at_epoch is +2 BOOST_REQUIRE(!get_wtdw(1).is_null()); - BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT, balance_of(UWRIT_OP, "ETH", "ETH")); + BOOST_REQUIRE_EQUAL(INITIAL_DEPOSIT, balance_of(UWRIT_OP, "WIRE", "WIRE")); } FC_LOG_AND_RETHROW() } BOOST_FIXTURE_TEST_CASE(slashed_operator_withdraw_drops_silently, @@ -466,107 +450,19 @@ BOOST_FIXTURE_TEST_CASE(slashed_operator_withdraw_drops_silently, constexpr uint64_t WITHDRAW_AMOUNT = 400'000; BOOST_REQUIRE_EQUAL(success(), - depositinle(UWRIT_OP, "ETH", "ETH", INITIAL_DEPOSIT)); + fund_and_bond(UWRIT_OP, "WIRE", "WIRE", INITIAL_DEPOSIT)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(UWRIT_OP, "ETH", "ETH", WITHDRAW_AMOUNT)); + withdraw(UWRIT_OP, "WIRE", "WIRE", WITHDRAW_AMOUNT)); BOOST_REQUIRE_EQUAL(success(), slash(UWRIT_OP, "test slash")); - uint64_t balance_after_slash = balance_of(UWRIT_OP, "ETH", "ETH"); + uint64_t balance_after_slash = balance_of(UWRIT_OP, "WIRE", "WIRE"); advance_one_epoch(); advance_one_epoch(); BOOST_REQUIRE(get_wtdw(1).is_null()); BOOST_REQUIRE_EQUAL(balance_after_slash, - balance_of(UWRIT_OP, "ETH", "ETH")); -} FC_LOG_AND_RETHROW() } - -/// The "clean protobuf" regression tests below verify that the bytes -/// emitted by `sysio.opreg::emit_*` parse as a standard protobuf -/// `OperatorAction` message. They were originally written against the v5 -/// OperatorAction proto (with a `chain` ChainKind field and a -/// `TokenAmount.kind` TokenKind field). The proto carries -/// `chain_code` (uint64) and `amount.token_code` (uint64) instead — same -/// shape, different field semantics — so the parse + field-1-tag-byte -/// invariant still holds. -BOOST_FIXTURE_TEST_CASE(flushwtdw_attestation_data_is_clean_protobuf, - sysio_epoch_flushwtdw_tester) { try { - bootstrap_for_flushwtdw(); - - constexpr uint64_t INITIAL_DEPOSIT = 1'000'000; - constexpr uint64_t WITHDRAW_AMOUNT = 400'000; - - BOOST_REQUIRE_EQUAL(success(), - depositinle(UWRIT_OP, "ETH", "ETH", INITIAL_DEPOSIT)); - BOOST_REQUIRE_EQUAL(success(), - withdrawinle(UWRIT_OP, "ETH", "ETH", WITHDRAW_AMOUNT)); - - constexpr uint32_t FUTURE_EPOCH = 100; - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, EPOCH_ACCOUNT, - "flushwtdw"_n, mvo()("current_epoch", FUTURE_EPOCH))); - - auto rows = collect_attestation_data( - sysio::opp::types::AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION); - BOOST_REQUIRE_EQUAL(rows.size(), 1u); - const auto& bytes = rows.front(); - BOOST_REQUIRE(!bytes.empty()); - - // First byte MUST be a valid protobuf field-1-varint tag (0x08 = - // OperatorAction.action_type). - BOOST_REQUIRE_EQUAL(static_cast(bytes.front()), 0x08u); - - sysio::opp::attestations::OperatorAction oa; - BOOST_REQUIRE(oa.ParseFromArray(bytes.data(), - static_cast(bytes.size()))); - BOOST_REQUIRE_EQUAL( - static_cast(oa.action_type()), - static_cast(sysio::opp::attestations:: - OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT)); - BOOST_REQUIRE_EQUAL(static_cast(oa.amount().amount()), - WITHDRAW_AMOUNT); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(flushwtdw_multiple_attestations_all_clean_protobuf, - sysio_epoch_flushwtdw_tester) { try { - bootstrap_for_flushwtdw(); - - constexpr uint64_t INITIAL_DEPOSIT = 1'000'000; - - const std::array operators{ UWRIT_OP, UWRIT_OP_B, UWRIT_OP_C }; - const std::array withdraws{ 100'000, 200'000, 300'000 }; - - BOOST_REQUIRE_EQUAL(success(), regunderwriter(UWRIT_OP_B)); - BOOST_REQUIRE_EQUAL(success(), regunderwriter(UWRIT_OP_C)); - for (size_t i = 0; i < operators.size(); ++i) { - BOOST_REQUIRE_EQUAL(success(), - depositinle(operators[i], "ETH", "ETH", INITIAL_DEPOSIT)); - BOOST_REQUIRE_EQUAL(success(), - withdrawinle(operators[i], "ETH", "ETH", withdraws[i])); - } - - constexpr uint32_t FUTURE_EPOCH = 100; - BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, EPOCH_ACCOUNT, - "flushwtdw"_n, mvo()("current_epoch", FUTURE_EPOCH))); - - auto rows = collect_attestation_data( - sysio::opp::types::AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION); - BOOST_REQUIRE_EQUAL(rows.size(), withdraws.size()); - - for (size_t i = 0; i < rows.size(); ++i) { - const auto& bytes = rows[i]; - BOOST_REQUIRE(!bytes.empty()); - BOOST_REQUIRE_EQUAL(static_cast(bytes.front()), 0x08u); - - sysio::opp::attestations::OperatorAction oa; - BOOST_REQUIRE(oa.ParseFromArray(bytes.data(), - static_cast(bytes.size()))); - BOOST_REQUIRE_EQUAL( - static_cast(oa.action_type()), - static_cast(sysio::opp::attestations:: - OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT)); - BOOST_REQUIRE_EQUAL(static_cast(oa.amount().amount()), - withdraws[i]); - } + balance_of(UWRIT_OP, "WIRE", "WIRE")); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/contracts/tests/sysio.msgch_chain_tests.cpp b/contracts/tests/sysio.msgch_chain_tests.cpp index 6e06e2b82c..5c0be1adfe 100644 --- a/contracts/tests/sysio.msgch_chain_tests.cpp +++ b/contracts/tests/sysio.msgch_chain_tests.cpp @@ -212,7 +212,6 @@ class sysio_msgch_chain_tester : public tester { static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; static constexpr auto ROA_ACCOUNT = "sysio.roa"_n; static constexpr auto BATCHOP = "batchop.a"_n; static constexpr auto BATCHOP_B = "batchop.b"_n; @@ -242,7 +241,7 @@ class sysio_msgch_chain_tester : public tester { // pay-epoch transfers. Same bootstrap rationale as sysio_epoch_flushwtdw_tester. create_accounts({ TOKEN_ACCOUNT, EPOCH_ACCOUNT, OPREG_ACCOUNT, MSGCH_ACCOUNT, - CHALG_ACCOUNT, CHAINS_ACCOUNT, UWRIT_ACCOUNT, + CHALG_ACCOUNT, CHAINS_ACCOUNT, BATCHOP, BATCHOP_B, BATCHOP_C, BATCHOP_D, BATCHOP_E, BATCHOP_F, "sysio.dclaim"_n, "sysio.gov"_n, "sysio.ops"_n }, false, true, !empty_roa); @@ -253,7 +252,6 @@ class sysio_msgch_chain_tester : public tester { deploy(OPREG_ACCOUNT, contracts::opreg_wasm(), contracts::opreg_abi(), opreg_abi); deploy(MSGCH_ACCOUNT, contracts::msgch_wasm(), contracts::msgch_abi(), msgch_abi); deploy(CHAINS_ACCOUNT, contracts::chains_wasm(), contracts::chains_abi(), chains_abi); - deploy(UWRIT_ACCOUNT, contracts::uwrit_wasm(), contracts::uwrit_abi(), uwrit_abi); deploy(TOKEN_ACCOUNT, contracts::token_wasm(), contracts::token_abi(), token_abi); produce_blocks(1); @@ -384,10 +382,7 @@ class sysio_msgch_chain_tester : public tester { ("req_batchop_collat", batchop_is_bootstrapped ? fc::variants{} : fc::variants{ - make_chain_min_bond(ETH_CHAIN_CODE, ETH_CHAIN_CODE, - BATCH_OPERATOR_MINIMUM_COLLATERAL), - make_chain_min_bond(SOL_CHAIN_CODE, SOL_CHAIN_CODE, - BATCH_OPERATOR_MINIMUM_COLLATERAL) }) + make_chain_min_bond("WIRE", "WIRE", BATCH_OPERATOR_MINIMUM_COLLATERAL) }) ("req_uw_collat", fc::variants{}))); const std::vector available_batch_ops{ @@ -409,11 +404,7 @@ class sysio_msgch_chain_tester : public tester { // A non-bootstrapped batch operator starts UNKNOWN and becomes ACTIVE // only after a collateral update re-evaluates its role eligibility. if (!batchop_is_bootstrapped) { - BOOST_REQUIRE_EQUAL(success(), depositinle(BATCHOP, ETH_CHAIN_CODE, ETH_CHAIN_CODE, - BATCH_OPERATOR_MINIMUM_COLLATERAL)); - BOOST_REQUIRE_EQUAL(success(), depositinle(BATCHOP, SOL_CHAIN_CODE, SOL_CHAIN_CODE, - BATCH_OPERATOR_MINIMUM_COLLATERAL, - opp::types::ChainKind::CHAIN_KIND_SVM)); + BOOST_REQUIRE_EQUAL(success(), fund_and_bond(BATCHOP, BATCH_OPERATOR_MINIMUM_COLLATERAL)); } BOOST_REQUIRE_EQUAL(success(), push(EPOCH_ACCOUNT, epoch_abi, EPOCH_ACCOUNT, @@ -931,17 +922,12 @@ class sysio_msgch_chain_tester : public tester { /// Inline collateral credit (the path sysio.msgch drives in production; pushed directly here) that /// lifts a non-bootstrapped operator to ACTIVE once its bond meets the configured minimum. - action_result depositinle(name account, std::string_view chain_code, std::string_view token_code, - uint64_t amount, - opp::types::ChainKind actor_chain = opp::types::ChainKind::CHAIN_KIND_EVM) { - return push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "depositinle"_n, mvo() - ("account", account.to_string()) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount) - ("actor_chain", actor_chain) - ("actor_address", std::vector{}) - ("original_message_id", std::string(64, '0'))); + action_result fund_and_bond(name account, uint64_t amount) { + BOOST_REQUIRE_EQUAL(success(), push(TOKEN_ACCOUNT, token_abi, SYSIO_ACCOUNT, "transfer"_n, + mvo()("from", SYSIO_ACCOUNT)("to", account) + ("quantity", asset(static_cast(amount), symbol(9, "WIRE")))("memo", "bond funding"))); + return push(OPREG_ACCOUNT, opreg_abi, account, "deposit"_n, + mvo()("account", account)("token_code", "WIRE")("amount", amount)); } /// bootstrap() variant for a real rotation: THREE single-operator groups (so a resident op is on @@ -969,7 +955,7 @@ class sysio_msgch_chain_tester : public tester { ("terminate_max_pct_misses_24h", 99) ("terminate_window_ms", terminate_window_ms) ("req_prod_collat", fc::variants{}) - ("req_batchop_collat", fc::variants{ make_chain_min_bond("ETH", "ETH", 1) }) + ("req_batchop_collat", fc::variants{ make_chain_min_bond("WIRE", "WIRE", 1) }) ("req_uw_collat", fc::variants{}))); register_chain(opp::types::ChainKind::CHAIN_KIND_EVM, "ETH", 31337); @@ -981,7 +967,7 @@ class sysio_msgch_chain_tester : public tester { BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "regoperator"_n, mvo() ("account", BATCHOP.to_string())("type", opp::types::OperatorType::OPERATOR_TYPE_BATCH) ("is_bootstrapped", false))); - BOOST_REQUIRE_EQUAL(success(), depositinle(BATCHOP, "ETH", "ETH", 1)); + BOOST_REQUIRE_EQUAL(success(), fund_and_bond(BATCHOP, 1)); for (const auto& op : {BATCHOP_B, BATCHOP_C}) { BOOST_REQUIRE_EQUAL(success(), push(OPREG_ACCOUNT, opreg_abi, OPREG_ACCOUNT, "regoperator"_n, mvo() ("account", op.to_string())("type", opp::types::OperatorType::OPERATOR_TYPE_BATCH) @@ -997,7 +983,7 @@ class sysio_msgch_chain_tester : public tester { produce_blocks(); } - abi_serializer sysio_abi, token_abi, epoch_abi, opreg_abi, msgch_abi, chalg_abi, chains_abi, uwrit_abi, roa_abi; + abi_serializer sysio_abi, token_abi, epoch_abi, opreg_abi, msgch_abi, chalg_abi, chains_abi, roa_abi; }; // --------------------------------------------------------------------------- @@ -1636,7 +1622,7 @@ BOOST_FIXTURE_TEST_CASE(noncanonical_delivery_slashes_before_termination, sysio_ constexpr const char* kHistoricalMissPayload = "history-miss"; constexpr const char* kCanonicalPayload = "canonical"; constexpr const char* kNonCanonicalPayload = "non-canonical"; - constexpr uint32_t kExpectedSlashActionsPerOutpost = 1; + constexpr uint32_t kExpectedSlashActionsPerOutpost = 0; constexpr uint32_t kEpochAdvanceCount = 1; constexpr uint32_t kExpectedDeliveredLogCount = 4; diff --git a/contracts/tests/sysio.opreg_tests.cpp b/contracts/tests/sysio.opreg_tests.cpp index c86d3ff13f..70640bde9d 100644 --- a/contracts/tests/sysio.opreg_tests.cpp +++ b/contracts/tests/sysio.opreg_tests.cpp @@ -497,14 +497,14 @@ class sysio_opreg_tester : public tester { /*terminate_window_ms=*/terminate_window_ms, /*req_prod_collat=*/{}, /*req_batchop_collat=*/{ - make_chain_min_bond("ETH", "ETH", kTestMinBond), + make_chain_min_bond("WIRE", "NTA", kTestMinBond), }, /*req_uw_collat=*/{})); BOOST_REQUIRE_EQUAL(success(), regoperator(account, OPERATOR_TYPE_BATCH, /*is_bootstrapped=*/false)); BOOST_REQUIRE_EQUAL(success(), - depositinle(account, "ETH", "ETH", kTestMinBond)); + bond_generic(account, "NTA", kTestMinBond)); produce_blocks(); auto op = get_operator(account); @@ -512,39 +512,6 @@ class sysio_opreg_tester : public tester { BOOST_REQUIRE_EQUAL(0, op["is_bootstrapped"].as_uint64()); } - // ── Collateral-action helpers (msgch-dispatched paths, codenames) ── - - /// `depositinle`: dispatched from sysio.msgch. - /// Signature: `(account, chain_code, token_code, amount, - /// actor_chain ChainKind, actor_address bytes, - /// original_message_id checksum256)`. - action_result depositinle(name account, - std::string_view chain_code, std::string_view token_code, - uint64_t amount, - ChainKind actor_chain = ChainKind::CHAIN_KIND_EVM, - const std::vector& actor_address = {}, - const std::string& original_message_id_hex = std::string(64, '0')) { - return push_opreg_action(OPREG_ACCOUNT, "depositinle"_n, mvo() - ("account", account) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount) - ("actor_chain", actor_chain) - ("actor_address", actor_address) - ("original_message_id", original_message_id_hex)); - } - - /// `withdrawinle`: same dispatch / auth model as `depositinle`. - action_result withdrawinle(name account, - std::string_view chain_code, std::string_view token_code, - uint64_t amount) { - return push_opreg_action(OPREG_ACCOUNT, "withdrawinle"_n, mvo() - ("account", account) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount)); - } - /// `withdraw`: operator-authorized request against the depot-native `(WIRE, token_code)` row. action_result withdraw(name account, std::string_view token_code, uint64_t amount) { return push_opreg_action(account, withdrawal_action::withdraw, mvo() @@ -601,16 +568,6 @@ class sysio_opreg_tester : public tester { ("is_eligible", is_eligible)); } - action_result releaselock(name signer, name account, - std::string_view chain_code, std::string_view token_code, - uint64_t amount) { - return push_opreg_action(signer, "releaselock"_n, mvo() - ("account", account) - ("chain_code", chain_code) - ("token_code", token_code) - ("amount", amount)); - } - /// Read a wtdwqueue row by request_id (primary key). fc::variant get_wtdw(uint64_t request_id) { auto data = get_row_by_id(OPREG_ACCOUNT, OPREG_ACCOUNT, "wtdwqueue"_n, request_id); @@ -696,6 +653,19 @@ class sysio_opreg_tester : public tester { produce_blocks(); } + bool generic_assets_ready = false; + + /// Mint generic shadow LIQ through sysio.synd and bond it through the public native action. + action_result bond_generic(name account, std::string_view token, uint64_t amount) { + if (!generic_assets_ready) { + setup_shadow_liq({sysio::testing::external::First, sysio::testing::external::Second}); + generic_assets_ready = true; + } + BOOST_REQUIRE_EQUAL(success(), push_contract(LIQ_ACCOUNT, liq_abi_ser, SYND_ACCOUNT, + shadow_action::mint, mvo()("to", account)("token_code", codename_mvo(token))("amount", amount))); + return deposit(account, token, amount); + } + // ── Shadow LIQ collateral (sysio.liq) ── /// Deploy a privileged registry or token contract and load its ABI serializer. @@ -961,14 +931,14 @@ BOOST_FIXTURE_TEST_CASE(setconfig_rejects_zero_min_bond, sysio_opreg_tester) { t "(an empty requirement set imposes no bond)"), setconfig(21, 63, 21, kDefaultPruneDelayMs, kDefaultMaxConsecutiveMisses, kDefaultMaxPctMisses24h, kTerminateWindowMs, - {}, {}, { make_chain_min_bond("ETH", "ETH", kRejectedZeroMinBond) }) + {}, {}, { make_chain_min_bond("WIRE", "NTA", kRejectedZeroMinBond) }) ); // The identical shape with a positive min_bond is accepted. BOOST_REQUIRE_EQUAL( success(), setconfig(21, 63, 21, kDefaultPruneDelayMs, kDefaultMaxConsecutiveMisses, kDefaultMaxPctMisses24h, kTerminateWindowMs, - {}, {}, { make_chain_min_bond("ETH", "ETH", kTestMinBond) }) + {}, {}, { make_chain_min_bond("WIRE", "NTA", kTestMinBond) }) ); } FC_LOG_AND_RETHROW() } @@ -1000,7 +970,7 @@ BOOST_FIXTURE_TEST_CASE(setconfig_rejects_uncanonical_collateral_code, sysio_opr success(), setconfig(21, 63, 21, kDefaultPruneDelayMs, kDefaultMaxConsecutiveMisses, kDefaultMaxPctMisses24h, kTerminateWindowMs, - {}, {}, { make_chain_min_bond("ETH", "ETH", kTestMinBond) }) + {}, {}, { make_chain_min_bond("WIRE", "NTA", kTestMinBond) }) ); } FC_LOG_AND_RETHROW() } @@ -1312,13 +1282,13 @@ BOOST_FIXTURE_TEST_CASE(deposit_credits_balance_row, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1'000'000)); + bond_generic("uwrit.alice"_n, "NTA", 1'000'000)); auto op = get_operator("uwrit.alice"_n); auto balances = op["balances"].get_array(); BOOST_REQUIRE_EQUAL(1, balances.size()); - BOOST_REQUIRE_EQUAL(cn("ETH").value, balances[0]["chain_code"].as().value); - BOOST_REQUIRE_EQUAL(cn("ETH").value, balances[0]["token_code"].as().value); + BOOST_REQUIRE_EQUAL(cn("WIRE").value, balances[0]["chain_code"].as().value); + BOOST_REQUIRE_EQUAL(cn("NTA").value, balances[0]["token_code"].as().value); BOOST_REQUIRE_EQUAL(1'000'000, balances[0]["balance"].as_uint64()); } FC_LOG_AND_RETHROW() } @@ -1327,9 +1297,9 @@ BOOST_FIXTURE_TEST_CASE(deposit_aggregates_into_existing_balance_row, sysio_opre BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 100)); + bond_generic("uwrit.alice"_n, "NTA", 100)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 50)); + bond_generic("uwrit.alice"_n, "NTA", 50)); auto op = get_operator("uwrit.alice"_n); auto balances = op["balances"].get_array(); @@ -1337,45 +1307,6 @@ BOOST_FIXTURE_TEST_CASE(deposit_aggregates_into_existing_balance_row, sysio_opre BOOST_REQUIRE_EQUAL(150, balances[0]["balance"].as_uint64()); } FC_LOG_AND_RETHROW() } -// SEC-103 (WSA-028 follow-up): operator collateral must never accumulate past -// asset::max_amount (2^62-1). A balance above the asset range would abort the -// depot-native claim payout's asset(balance, symbol); on the never-throw -// depositinle (OPP-inbound) path that abort would stall consensus. depositinle -// gates the RUNNING SUM: a credit that would push the balance over the cap is -// refunded via DEPOSIT_REVERT (the action still succeeds — never throws) and the -// stored balance is left unchanged. The single-value WSA-028 wrap is closed -// upstream in sysio.msgch; this is the accumulation guard. -BOOST_FIXTURE_TEST_CASE(depositinle_credit_over_max_collateral_is_reverted, sysio_opreg_tester) { try { - // asset::max_amount — the Antelope asset magnitude limit (2^62 - 1), the cap - // sysio.opreg enforces on a single balance row. - constexpr uint64_t MAX_COLLATERAL = (uint64_t{1} << 62) - 1; - - BOOST_REQUIRE_EQUAL(success(), setconfig()); - BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); - - // Credit the balance up to EXACTLY the cap — accepted (the boundary is inclusive). - BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", MAX_COLLATERAL)); - { - auto op = get_operator("uwrit.alice"_n); - auto balances = op["balances"].get_array(); - BOOST_REQUIRE_EQUAL(1, balances.size()); - BOOST_REQUIRE_EQUAL(MAX_COLLATERAL, balances[0]["balance"].as_uint64()); - } - - // A further +1 would push the sum to 2^62 (one past asset::max_amount). It is - // refunded via DEPOSIT_REVERT: the action succeeds (never throws) and the - // stored balance is unchanged — the credit did NOT wrap or saturate it in. - BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1)); - { - auto op = get_operator("uwrit.alice"_n); - auto balances = op["balances"].get_array(); - BOOST_REQUIRE_EQUAL(1, balances.size()); - BOOST_REQUIRE_EQUAL(MAX_COLLATERAL, balances[0]["balance"].as_uint64()); - } -} FC_LOG_AND_RETHROW() } - /// WIRE-375 / WNS-40: direct depot deposits must custody the same 9-decimal WIRE /// asset that the `(WIRE, WIRE)` collateral bucket denotes. Crediting raw /// 4-decimal SYS units into that bucket made challenge-bond valuation interpret @@ -1470,28 +1401,23 @@ BOOST_FIXTURE_TEST_CASE(deposit_keeps_chain_token_pairs_separate, sysio_opreg_te BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 100)); + bond_generic("uwrit.alice"_n, "NTA", 100)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "SOL", "SOL", 200)); + bond_generic("uwrit.alice"_n, "NTB", 200)); auto op = get_operator("uwrit.alice"_n); auto balances = op["balances"].get_array(); BOOST_REQUIRE_EQUAL(2, balances.size()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(depositinle_logs_failure_when_operator_slashed, sysio_opreg_tester) { try { +BOOST_FIXTURE_TEST_CASE(deposit_rejects_slashed_operator, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), slash("uwrit.alice"_n, "test slash")); - - BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 100)); - - auto entry = latest_action_log("uwrit.alice"_n); - BOOST_REQUIRE(!entry.is_null()); - BOOST_REQUIRE_EQUAL(false, entry["success"].as_bool()); - BOOST_REQUIRE_EQUAL(std::string("operator not in a deposit-eligible state"), - entry["error_message"].as_string()); + BOOST_REQUIRE_EQUAL(error("assertion failure with message: operator not in a deposit-eligible state"), + bond_generic("uwrit.alice"_n, "NTA", 100)); + BOOST_CHECK(get_operator("uwrit.alice"_n)["balances"].get_array().empty()); + BOOST_CHECK_EQUAL(100, get_currency_balance(LIQ_ACCOUNT, symbol(9, "NTA"), "uwrit.alice"_n).get_amount()); } FC_LOG_AND_RETHROW() } // ── queuewtdw + cancelwtdw ── @@ -1500,10 +1426,10 @@ BOOST_FIXTURE_TEST_CASE(queuewtdw_creates_request_row, sysio_opreg_tester) { try BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); auto row = get_wtdw(1); // monotonic id starts at 1 BOOST_REQUIRE(!row.is_null()); @@ -1511,14 +1437,14 @@ BOOST_FIXTURE_TEST_CASE(queuewtdw_creates_request_row, sysio_opreg_tester) { try BOOST_REQUIRE_EQUAL(400, row["amount"].as_uint64()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(withdrawinle_logs_failure_on_insufficient_available, sysio_opreg_tester) { try { +BOOST_FIXTURE_TEST_CASE(withdraw_logs_failure_on_insufficient_available, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 100)); + bond_generic("uwrit.alice"_n, "NTA", 100)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 200)); + withdraw("uwrit.alice"_n, "NTA", 200)); auto entry = latest_action_log("uwrit.alice"_n); BOOST_REQUIRE(!entry.is_null()); @@ -1527,18 +1453,18 @@ BOOST_FIXTURE_TEST_CASE(withdrawinle_logs_failure_on_insufficient_available, sys entry["error_message"].as_string()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(withdrawinle_subtracts_from_available_on_subsequent_call, +BOOST_FIXTURE_TEST_CASE(withdraw_subtracts_from_available_on_subsequent_call, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 700)); + withdraw("uwrit.alice"_n, "NTA", 700)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); auto entry = latest_action_log("uwrit.alice"_n); BOOST_REQUIRE(!entry.is_null()); @@ -1549,20 +1475,20 @@ BOOST_FIXTURE_TEST_CASE(withdrawinle_subtracts_from_available_on_subsequent_call BOOST_REQUIRE(get_wtdw(2).is_null()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(withdrawinle_allows_requests_across_collateral_buckets, +BOOST_FIXTURE_TEST_CASE(withdraw_allows_requests_across_collateral_buckets, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "SOL", "SOL", 1000)); + bond_generic("uwrit.alice"_n, "NTB", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "SOL", "SOL", 400)); + withdraw("uwrit.alice"_n, "NTB", 400)); auto entry = latest_action_log("uwrit.alice"_n); BOOST_REQUIRE(!entry.is_null()); @@ -1571,7 +1497,7 @@ BOOST_FIXTURE_TEST_CASE(withdrawinle_allows_requests_across_collateral_buckets, BOOST_REQUIRE(!get_wtdw(2).is_null()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(withdraw_rejects_when_inline_request_is_outstanding_for_same_bucket, +BOOST_FIXTURE_TEST_CASE(withdraw_rejects_when_request_is_outstanding_for_same_bucket, sysio_opreg_tester) { try { BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); @@ -1579,7 +1505,8 @@ BOOST_FIXTURE_TEST_CASE(withdraw_rejects_when_inline_request_is_outstanding_for_ BOOST_REQUIRE_EQUAL(success(), deposit("uwrit.alice"_n, kWireCodename, 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "WIRE", "WIRE", 400)); + withdraw("uwrit.alice"_n, "WIRE", 400)); + produce_blocks(); BOOST_REQUIRE_EQUAL(success(), withdraw("uwrit.alice"_n, kWireCodename, 400)); auto entry = latest_action_log("uwrit.alice"_n); @@ -1594,11 +1521,11 @@ BOOST_FIXTURE_TEST_CASE(withdraw_rejects_when_inline_request_is_outstanding_for_ /// A successful outpost withdrawal reservation immediately removes an /// undercollateralized batch operator from the active set. -BOOST_FIXTURE_TEST_CASE(withdrawinle_rechecks_eligibility_after_enqueue, sysio_opreg_tester) { try { +BOOST_FIXTURE_TEST_CASE(withdraw_rechecks_batch_eligibility_after_enqueue, sysio_opreg_tester) { try { activate_batch_operator(kEligibilityBatchOperator); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + withdraw(kEligibilityBatchOperator, "NTA", kTestMinBond)); auto op = get_operator(kEligibilityBatchOperator); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_UNKNOWN == op[eligibility_field::status].as()); @@ -1637,11 +1564,11 @@ BOOST_FIXTURE_TEST_CASE(withdraw_rechecks_eligibility_after_enqueue, sysio_opreg /// A rejected outpost withdrawal has no reservation side effect and therefore /// cannot change an otherwise eligible operator's status. -BOOST_FIXTURE_TEST_CASE(withdrawinle_rejection_preserves_eligibility, sysio_opreg_tester) { try { +BOOST_FIXTURE_TEST_CASE(withdraw_rejection_preserves_eligibility, sysio_opreg_tester) { try { activate_batch_operator(kEligibilityBatchOperator); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kInsufficientTestBond)); + withdraw(kEligibilityBatchOperator, "NTA", kInsufficientTestBond)); auto op = get_operator(kEligibilityBatchOperator); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_ACTIVE == op[eligibility_field::status].as()); @@ -1654,9 +1581,9 @@ BOOST_FIXTURE_TEST_CASE(cancelwtdw_removes_pending_request, sysio_opreg_tester) BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); BOOST_REQUIRE_EQUAL(success(), cancelwtdw("uwrit.alice"_n, "uwrit.alice"_n, 1)); @@ -1664,7 +1591,7 @@ BOOST_FIXTURE_TEST_CASE(cancelwtdw_removes_pending_request, sysio_opreg_tester) BOOST_REQUIRE(row.is_null()); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + withdraw("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE(!get_wtdw(2).is_null()); } FC_LOG_AND_RETHROW() } @@ -1673,7 +1600,7 @@ BOOST_FIXTURE_TEST_CASE(cancelwtdw_removes_pending_request, sysio_opreg_tester) BOOST_FIXTURE_TEST_CASE(cancelwtdw_rechecks_eligibility_after_erase, sysio_opreg_tester) { try { activate_batch_operator(kEligibilityBatchOperator); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + withdraw(kEligibilityBatchOperator, "NTA", kTestMinBond)); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_UNKNOWN == get_operator(kEligibilityBatchOperator)[eligibility_field::status].as()); @@ -1724,9 +1651,9 @@ BOOST_FIXTURE_TEST_CASE(processbatch_preserves_terminated_bootstrapped_status, s BOOST_FIXTURE_TEST_CASE(flushwtdw_rechecks_eligibility_after_erase, sysio_opreg_tester) { try { activate_batch_operator(kEligibilityBatchOperator); BOOST_REQUIRE_EQUAL(success(), - depositinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + bond_generic(kEligibilityBatchOperator, "NTA", kTestMinBond)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + withdraw(kEligibilityBatchOperator, "NTA", kTestMinBond)); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_ACTIVE == get_operator(kEligibilityBatchOperator)[eligibility_field::status].as()); @@ -1741,15 +1668,15 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_allows_operator_to_request_again, sysio_opreg_ BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); BOOST_REQUIRE_EQUAL(success(), flushwtdw(kFlushAllMaturedEpoch)); BOOST_REQUIRE(get_wtdw(1).is_null()); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 600)); + withdraw("uwrit.alice"_n, "NTA", 600)); BOOST_REQUIRE(!get_wtdw(2).is_null()); } FC_LOG_AND_RETHROW() } @@ -1758,9 +1685,9 @@ BOOST_FIXTURE_TEST_CASE(cancelwtdw_rejects_other_operators_request, sysio_opreg_ BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.bob"_n, OPERATOR_TYPE_UNDERWRITER, false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("uwrit.alice"_n, "ETH", "ETH", 1000)); + bond_generic("uwrit.alice"_n, "NTA", 1000)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle("uwrit.alice"_n, "ETH", "ETH", 400)); + withdraw("uwrit.alice"_n, "NTA", 400)); BOOST_REQUIRE_EQUAL( error("assertion failure with message: not your withdraw request"), @@ -1774,7 +1701,7 @@ BOOST_FIXTURE_TEST_CASE(terminate_marks_status_and_zeros_unlocked_balance, sysio BOOST_REQUIRE_EQUAL(success(), regoperator("batchop.a"_n, OPERATOR_TYPE_BATCH, /*is_bootstrapped=*/false)); BOOST_REQUIRE_EQUAL(success(), - depositinle("batchop.a"_n, "ETH", "ETH", 500)); + bond_generic("batchop.a"_n, "NTA", 500)); BOOST_REQUIRE_EQUAL(success(), terminate("batchop.a"_n, "rolling-24h: >5% miss rate")); @@ -2088,16 +2015,13 @@ BOOST_FIXTURE_TEST_CASE(remit_claims_for_two_tokens_coexist, sysio_opreg_tester) /// operator's ETH-bucket slash does queue an attestation, so the trace probe is live. BOOST_FIXTURE_TEST_CASE(slash_shadow_row_emits_no_attestation, sysio_opreg_tester) { try { const auto OPERATOR = "uwrit.alice"_n; - const auto CONTROL = "uwrit.bob"_n; constexpr uint64_t DEPOSIT = 2 * kWireUnit; BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator(OPERATOR, OPERATOR_TYPE_UNDERWRITER, /*is_bootstrapped=*/false)); - BOOST_REQUIRE_EQUAL(success(), regoperator(CONTROL, OPERATOR_TYPE_UNDERWRITER, /*is_bootstrapped=*/false)); setup_shadow_liq(); mint_shadow(OPERATOR, kShadowFunding); BOOST_REQUIRE_EQUAL(success(), deposit(OPERATOR, kLiqEthCodename, DEPOSIT)); - BOOST_REQUIRE_EQUAL(success(), depositinle(CONTROL, kEthCodename, kEthCodename, DEPOSIT)); // The row under test is the shadow row, custodied as shadow. const auto bonded = get_operator(OPERATOR)["balances"].get_array(); @@ -2114,7 +2038,6 @@ BOOST_FIXTURE_TEST_CASE(slash_shadow_row_emits_no_attestation, sysio_opreg_teste BOOST_REQUIRE_EQUAL(0u, balances[0]["balance"].as_uint64()); BOOST_REQUIRE_EQUAL(static_cast(DEPOSIT), shadow_balance(OPREG_ACCOUNT)); - BOOST_REQUIRE(slash_queues_outbound(CONTROL, kTestSlashReason)); } FC_LOG_AND_RETHROW() } /// `claimremit` resolves its token before touching any claim row, so a token opreg cannot @@ -2211,7 +2134,7 @@ BOOST_FIXTURE_TEST_CASE(recorddel_ignores_withdrawal_ineligibility, sysio_opreg_ produce_block(fc::seconds(kDeliveryTimestampSeparationSeconds)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + withdraw(kEligibilityBatchOperator, "NTA", kTestMinBond)); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_UNKNOWN == get_operator(kEligibilityBatchOperator)[eligibility_field::status].as()); @@ -2268,7 +2191,7 @@ BOOST_FIXTURE_TEST_CASE(termcheck_preserves_percent_sample_across_withdrawal_ine produce_block(fc::seconds(kDeliveryTimestampSeparationSeconds)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(kEligibilityBatchOperator, kEthCodename, kEthCodename, kTestMinBond)); + withdraw(kEligibilityBatchOperator, "NTA", kTestMinBond)); BOOST_REQUIRE_EQUAL(success(), cancelwtdw( kEligibilityBatchOperator, kEligibilityBatchOperator, kFirstWithdrawalRequestId)); @@ -2407,18 +2330,9 @@ BOOST_FIXTURE_TEST_CASE(recorddel_succeeds_without_opconfig, sysio_opreg_tester) BOOST_REQUIRE(!get_dellog_entry(1).is_null()); } FC_LOG_AND_RETHROW() } -BOOST_FIXTURE_TEST_CASE(releaselock_requires_uwrit_authority, sysio_opreg_tester) { try { - BOOST_REQUIRE_EQUAL(success(), setconfig()); - BOOST_REQUIRE_EQUAL(success(), regoperator("uwrit.alice"_n, OPERATOR_TYPE_UNDERWRITER, false)); - - BOOST_REQUIRE( - releaselock(OPREG_ACCOUNT, "uwrit.alice"_n, "ETH", "ETH", 100) - .find("missing authority of sysio.uwrit") != std::string::npos); -} FC_LOG_AND_RETHROW() } - // ── setconfig: per-(chain_code, token_code) collateral requirements ── -BOOST_FIXTURE_TEST_CASE(setconfig_two_chain_bond_activation, sysio_opreg_tester) { try { +BOOST_FIXTURE_TEST_CASE(setconfig_two_native_asset_bond_activation, sysio_opreg_tester) { try { constexpr uint64_t MIN_BOND = 1'000'000; BOOST_REQUIRE_EQUAL(success(), setconfig( @@ -2427,8 +2341,8 @@ BOOST_FIXTURE_TEST_CASE(setconfig_two_chain_bond_activation, sysio_opreg_tester) /*terminate_window_ms=*/24ULL * 60 * 60 * 1000, /*req_prod_collat=*/{}, /*req_batchop_collat=*/{ - make_chain_min_bond("ETH", "ETH", MIN_BOND), - make_chain_min_bond("SOL", "SOL", MIN_BOND), + make_chain_min_bond("WIRE", "NTA", MIN_BOND), + make_chain_min_bond("WIRE", "NTB", MIN_BOND), }, /*req_uw_collat=*/{})); @@ -2441,13 +2355,13 @@ BOOST_FIXTURE_TEST_CASE(setconfig_two_chain_bond_activation, sysio_opreg_tester) // After ETH bond: SOL still missing → still UNKNOWN. BOOST_REQUIRE_EQUAL(success(), - depositinle("batchop.a"_n, "ETH", "ETH", MIN_BOND)); + bond_generic("batchop.a"_n, "NTA", MIN_BOND)); op = get_operator("batchop.a"_n); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_UNKNOWN == op["status"].as()); // After SOL bond: every requirement met → ACTIVE. BOOST_REQUIRE_EQUAL(success(), - depositinle("batchop.a"_n, "SOL", "SOL", MIN_BOND)); + bond_generic("batchop.a"_n, "NTB", MIN_BOND)); op = get_operator("batchop.a"_n); BOOST_REQUIRE(OperatorStatus::OPERATOR_STATUS_ACTIVE == op["status"].as()); BOOST_REQUIRE(op["available_at"].as_uint64() > 0); @@ -2455,8 +2369,8 @@ BOOST_FIXTURE_TEST_CASE(setconfig_two_chain_bond_activation, sysio_opreg_tester) BOOST_FIXTURE_TEST_CASE(setconfig_rejects_duplicate_chain_token_in_collat, sysio_opreg_tester) { try { const auto duplicate_vec = std::vector{ - make_chain_min_bond("ETH", "ETH", 100), - make_chain_min_bond("ETH", "ETH", 200), + make_chain_min_bond("WIRE", "NTA", 100), + make_chain_min_bond("WIRE", "NTA", 200), }; // The duplicate-detection assertion text refers to "(chain, token_kind)" @@ -2474,7 +2388,7 @@ BOOST_FIXTURE_TEST_CASE(setconfig_stamps_collat_config_timestamp, sysio_opreg_te 21, 63, 21, 600000, 5, 5, 24ULL * 60 * 60 * 1000, /*req_prod_collat=*/{}, /*req_batchop_collat=*/{ - make_chain_min_bond("ETH", "ETH", 1000), + make_chain_min_bond("WIRE", "NTA", 1000), }, /*req_uw_collat=*/{})); @@ -2492,11 +2406,11 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_terminated_operator_does_not_abort, sysio_opre BOOST_REQUIRE_EQUAL(success(), setconfig()); BOOST_REQUIRE_EQUAL(success(), regoperator("batchop.a"_n, OPERATOR_TYPE_BATCH, /*is_bootstrapped=*/false)); - BOOST_REQUIRE_EQUAL(success(), depositinle("batchop.a"_n, "ETH", "ETH", 500)); + BOOST_REQUIRE_EQUAL(success(), bond_generic("batchop.a"_n, "NTA", 500)); // Queue a withdraw of the full balance, then terminate (remits + zeroes the balance), leaving // the queued row matured against a now-zero balance. - BOOST_REQUIRE_EQUAL(success(), withdrawinle("batchop.a"_n, "ETH", "ETH", 500)); + BOOST_REQUIRE_EQUAL(success(), withdraw("batchop.a"_n, "NTA", 500)); BOOST_REQUIRE(!get_wtdw(1).is_null()); BOOST_REQUIRE_EQUAL(success(), terminate("batchop.a"_n, "rolling-24h miss")); @@ -2525,12 +2439,14 @@ BOOST_FIXTURE_TEST_CASE(flushwtdw_bounds_rows_per_epoch, sysio_opreg_tester) { t // Each distinct operator contributes its one permitted queue row. Raw name values remain valid // Antelope names and avoid coupling this global-bound regression to a fixed account-name list. for (uint32_t i = 0; i < N; ++i) { - const name account{OPERATOR_NAME_BASE.value + i + 1}; + const name account{OPERATOR_NAME_BASE.value + ((uint64_t(i) + 1) << 4)}; + create_accounts({account}); + produce_blocks(); BOOST_REQUIRE_EQUAL(success(), regoperator(account, OPERATOR_TYPE_BATCH, /*is_bootstrapped=*/false)); - BOOST_REQUIRE_EQUAL(success(), depositinle(account, "ETH", "ETH", i + 1)); + BOOST_REQUIRE_EQUAL(success(), bond_generic(account, "NTA", i + 1)); BOOST_REQUIRE_EQUAL(success(), - withdrawinle(account, "ETH", "ETH", i + 1)); + withdraw(account, "NTA", i + 1)); BOOST_REQUIRE_EQUAL(success(), terminate(account, "rolling-24h miss")); produce_blocks(); } @@ -2836,20 +2752,6 @@ BOOST_FIXTURE_TEST_CASE(terminated_operator_claims_uncovered_yield_before_prune, BOOST_REQUIRE_EQUAL(static_cast(kYieldPrincipal), wire_balance(OPREG_ACCOUNT)); } FC_LOG_AND_RETHROW() } -/// `depositinle` never credits the depot chain: depot-native collateral is bonded only through -/// `deposit`. The credit is dropped and logged, not thrown, since it runs inside a dispatch. -BOOST_FIXTURE_TEST_CASE(depositinle_drops_a_depot_chain_credit, sysio_opreg_tester) { try { - BOOST_REQUIRE_EQUAL(success(), setconfig()); - BOOST_REQUIRE_EQUAL(success(), regoperator(kYieldBonderA, OPERATOR_TYPE_UNDERWRITER, /*is_bootstrapped=*/false)); - - BOOST_REQUIRE_EQUAL(success(), depositinle(kYieldBonderA, kWireCodename, kWireCodename, kWireUnit)); - BOOST_REQUIRE(depot_native_row(kYieldBonderA, kWireCodename).is_null()); - const auto entry = latest_action_log(kYieldBonderA); - BOOST_REQUIRE(!entry.is_null()); - BOOST_REQUIRE_EQUAL(false, entry[eligibility_field::success].as_bool()); - BOOST_REQUIRE_EQUAL(std::string(kDepotChainCreditReason), entry["error_message"].as_string()); -} FC_LOG_AND_RETHROW() } - /// `claimyield` is permissionless: a keeper with no stake cranks it for a TERMINATED operator whose /// yield termination could not cover, and the credit lands in the operator's own WIRE claim. BOOST_FIXTURE_TEST_CASE(keeper_claims_terminated_operators_yield, sysio_opreg_tester) { try { @@ -2929,7 +2831,6 @@ BOOST_FIXTURE_TEST_CASE(reregistration_forfeits_banked_yield_but_keeps_remit_cla BOOST_REQUIRE_EQUAL(success(), claimremit(kYieldBonderA, kLiqEthCodename)); } FC_LOG_AND_RETHROW() } - // This is the dual-shadow producer flow's depot policy with generic symbols. // Registration of the Ethereum node NFT itself remains in the ETH-specific suites. BOOST_FIXTURE_TEST_CASE(two_generic_depot_assets_are_both_required_and_withdrawals_preserve_identity, diff --git a/contracts/tests/sysio.reserv_tests.cpp b/contracts/tests/sysio.reserv_tests.cpp deleted file mode 100644 index 89766a1d08..0000000000 --- a/contracts/tests/sysio.reserv_tests.cpp +++ /dev/null @@ -1,1935 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "contracts.hpp" -#include "contract_test_support.hpp" - -using namespace sysio::testing; -using namespace sysio; -using namespace sysio::chain; -using namespace sysio::opp::types; -using namespace fc; - -using mvo = fc::mutable_variant_object; - -namespace { - -/// Extract the raw 33-byte compressed pubkey from an EM `public_key` — the -/// `creator_pub_key` form `oncrtreserve` reconstructs via `pubkey_from_raw` -/// (CHAIN_KIND_EVM → EM key). Mirrors the helper in `sysio.dispatch_tests`. -std::vector em_pubkey_bytes(const fc::crypto::public_key& pk) { - const auto& shim = pk.get(); - auto compressed = shim.serialize(); // std::array - return std::vector(compressed.begin(), compressed.end()); -} - -} // anonymous namespace - -/// Data model: reserves are keyed by the triple `(chain_code, token_code, -/// reserve_code)` (each a `sysio::slug_name` packed uint64). The legacy -/// `setreserve` action is gone; `regreserve` is the bootstrap-window -/// equivalent (it works only while `current_epoch_index == 0`, which is the -/// state immediately after deploying the contract in these tests). -/// -/// Custody model: `reserve_wire_amount` is REAL — `regreserve` drains the -/// seed from the `sysio` treasury via `sysio.token::transfer` and the -/// settlement actions (`paywire` / `refundwire`) move real WIRE back out, so -/// this fixture deploys `sysio.token` and issues a WIRE supply to `sysio` -/// (mirroring `sysio.epoch_flushwtdw_tests.cpp`'s bootstrap). -class sysio_reserve_tester : public tester { -public: - static constexpr auto RESERVE_ACCOUNT = "sysio.reserv"_n; - static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; - static constexpr auto TOKEN_ACCOUNT = "sysio.token"_n; - static constexpr auto AUTHEX_ACCOUNT = "sysio.authex"_n; - static constexpr auto DCLAIM_ACCOUNT = "sysio.dclaim"_n; - static constexpr auto CHAINS_ACCOUNT = "sysio.chains"_n; - static constexpr auto SYSIO_ACCOUNT = "sysio"_n; - /// Stand-in for a swap's winning underwriter — the account the settlement - /// actions accrue the underwriter half of the fee to. - static constexpr auto UNDERWRITER_ACCOUNT = "underwriter1"_n; - - sysio_reserve_tester() { - produce_blocks(2); - // sysio.authex is pre-created by the tester boot (account linking) — - // creating it again would collide. - create_accounts({RESERVE_ACCOUNT, MSGCH_ACCOUNT, UWRIT_ACCOUNT, - TOKEN_ACCOUNT, CHAINS_ACCOUNT, DCLAIM_ACCOUNT, "alice"_n, - UNDERWRITER_ACCOUNT}); - produce_blocks(2); - - set_code(RESERVE_ACCOUNT, contracts::reserve_wasm()); - set_abi(RESERVE_ACCOUNT, contracts::reserve_abi().data()); - set_privileged(RESERVE_ACCOUNT); - produce_blocks(); - - const auto* accnt = control->find_account_metadata(RESERVE_ACCOUNT); - BOOST_REQUIRE(accnt != nullptr); - abi_def abi; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt->abi, abi), true); - abi_ser.set_abi(std::move(abi), abi_serializer::create_yield_function(abi_serializer_max_time)); - - // Normal OPP reserve flows target registered outpost chains. The - // never-throw inbound handlers soft-skip unregistered chain_code values - // before they can queue outbound replies through sysio.msgch. - set_code(CHAINS_ACCOUNT, contracts::chains_wasm()); - set_abi(CHAINS_ACCOUNT, contracts::chains_abi().data()); - set_privileged(CHAINS_ACCOUNT); - produce_blocks(); - { - const auto* chains = control->find_account_metadata(CHAINS_ACCOUNT); - BOOST_REQUIRE(chains != nullptr); - abi_def chains_abi_def; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(chains->abi, chains_abi_def), true); - chains_abi_ser.set_abi(std::move(chains_abi_def), - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_EVM, "ETH", 1)); - BOOST_REQUIRE_EQUAL(success(), regchain(ChainKind::CHAIN_KIND_SVM, "SOLANA", 2)); - - // WIRE custody backing: deploy sysio.token + issue the treasury supply - // to `sysio` so regreserve's treasury drain and the paywire/refundwire - // payouts move real tokens. - set_code(TOKEN_ACCOUNT, contracts::token_wasm()); - set_abi(TOKEN_ACCOUNT, contracts::token_abi().data()); - // Privileged-contract RAM-pool model: sysio.token bills its rows to - // the sysio pool (same model reserv/uwrit use). - set_privileged(TOKEN_ACCOUNT); - produce_blocks(); - { - const auto* tok = control->find_account_metadata(TOKEN_ACCOUNT); - BOOST_REQUIRE(tok != nullptr); - abi_def tok_abi_def; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(tok->abi, tok_abi_def), true); - token_abi_ser.set_abi(std::move(tok_abi_def), - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - BOOST_REQUIRE_EQUAL(success(), push_token_action(TOKEN_ACCOUNT, "create"_n, mvo() - ("issuer", SYSIO_ACCOUNT) - ("maximum_supply", "1000000000.000000000 WIRE"))); - BOOST_REQUIRE_EQUAL(success(), push_token_action(SYSIO_ACCOUNT, "issue"_n, mvo() - ("to", SYSIO_ACCOUNT) - ("quantity", "1000000000.000000000 WIRE") - ("memo", "test bootstrap"))); - produce_blocks(); - } - - action_result push_action(name signer, name action_name, const variant_object& data) { - return push_to(RESERVE_ACCOUNT, abi_ser, signer, action_name, data); - } - - action_result push_token_action(name signer, name action_name, const variant_object& data) { - return push_to(TOKEN_ACCOUNT, token_abi_ser, signer, action_name, data); - } - - action_result regchain(ChainKind kind, - std::string_view code, - uint32_t external_chain_id) { - return push_to(CHAINS_ACCOUNT, chains_abi_ser, CHAINS_ACCOUNT, "regchain"_n, mvo() - ("kind", kind) - ("code", code) - ("external_chain_id", external_chain_id) - ("name", std::string("outpost")) - ("description", std::string{}) - ("outpost", sysio_system::test_support::no_outpost_mvo())); - } - - action_result push_to(name account, abi_serializer& ser, name signer, - name action_name, const variant_object& data) { - string action_type_name = ser.get_action_type(action_name); - action act; - act.account = account; - act.name = action_name; - act.data = ser.variant_to_binary( - action_type_name, data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - act.authorization = vector{{signer, config::active_name}}; - - signed_transaction trx; - trx.actions.emplace_back(std::move(act)); - set_transaction_headers(trx); - trx.sign(get_private_key(signer, "active"), control->get_chain_id()); - try { - push_transaction(trx); - return success(); - } catch (const fc::exception& ex) { - return error(ex.top_message()); - } - } - - /// `swapquote` is a read-only action whose ANSWER is its return value. - /// `push_action` above discards the transaction trace and yields only - /// `success()`, so a test written against it passes even when the quote - /// ignores every fee. Decode the action return value instead. - uint64_t swapquote_value(std::string_view from_chain, std::string_view from_token, - std::string_view from_reserve, uint64_t from_amount, - std::string_view to_chain, std::string_view to_token, - std::string_view to_reserve) { - auto trace = tester::push_action(RESERVE_ACCOUNT, "swapquote"_n, RESERVE_ACCOUNT, mvo() - ("from_chain_code", from_chain) - ("from_token_code", from_token) - ("from_reserve_code", from_reserve) - ("from_amount", from_amount) - ("to_chain_code", to_chain) - ("to_token_code", to_token) - ("to_reserve_code", to_reserve)); - BOOST_REQUIRE(trace && !trace->action_traces.empty()); - return fc::raw::unpack(trace->action_traces[0].return_value); - } - - // ── SlugName helpers ── - - static fc::slug_name cn(std::string_view s) { return fc::slug_name{s}; } - - /// `regreserve` is the bootstrap-window action for inserting a reserve - /// row with `status=ACTIVE` and REAL WIRE backing drained from the - /// treasury. Triple-slug_name PK is `(chain_code, token_code, - /// reserve_code)`; `is_private`/`owner` seed privately-owned reserves. - action_result regreserve(std::string_view chain_code, - std::string_view token_code, - std::string_view reserve_code, - uint64_t initial_chain_amount, - uint64_t initial_wire_amount, - uint32_t weight = 5000, - bool is_private = false, - name owner = name{}, - const std::string& name_str = "test reserve", - const std::string& description = "", - uint32_t source_token_precision = 9) { - return push_action(RESERVE_ACCOUNT, "regreserve"_n, mvo() - ("chain_code", chain_code) - ("token_code", token_code) - ("reserve_code", reserve_code) - ("name", name_str) - ("description", description) - ("initial_chain_amount", initial_chain_amount) - ("initial_wire_amount", initial_wire_amount) - ("source_token_precision", source_token_precision) - ("connector_weight_bps", weight) - ("is_private", is_private) - ("owner", owner)); - } - - /// The contract's real WIRE token balance (raw units, 9 decimals). - int64_t wire_balance(name account) { - auto bal = get_currency_balance(TOKEN_ACCOUNT, symbol(9, "WIRE"), account); - return bal.get_amount(); - } - - /// Read `underwriter`'s `uwfees` accrual row. Null when they have never - /// earned a swap fee (no row is created until the first accrual). - fc::variant get_uwfees(name underwriter) { - auto data = get_row_by_account(RESERVE_ACCOUNT, RESERVE_ACCOUNT, "uwfees"_n, underwriter); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( - "uw_fee_row", data, abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Read the `rewards_bucket` singleton (kv::global). Null when never accrued. - fc::variant get_rewardbkt() { - auto data = get_row_by_account(RESERVE_ACCOUNT, RESERVE_ACCOUNT, "rewardbkt"_n, "rewardbkt"_n); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( - "rewards_bucket", data, abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Claimable WIRE credited by `paywire` / `refundwire`. Null when the account is owed - /// nothing. `paywire` and `refundwire` credit rather than transfer, because both run on - /// never-throw paths where a recipient's transfer-notify handler could otherwise abort the - /// enclosing transaction. - fc::variant get_wireclaim(name account) { - auto data = get_row_by_account(RESERVE_ACCOUNT, RESERVE_ACCOUNT, "wireclaims"_n, account); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( - "wire_claim", data, abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Claimable balance owed to `account`, or 0 when there is no row. - uint64_t wire_claimable(name account) { - auto c = get_wireclaim(account); - return c.is_null() ? 0u : c["balance"].as_uint64(); - } - - action_result claimwire(name account) { - return push_action(account, "claimwire"_n, mvo()("account", account)); - } - - /// Mirrors `sysio.reserv::MAX_CLAIM_SWEEP_PER_EPOCH` — the budget - /// `sysio.epoch::advance` hands the inline retention sweep. - static constexpr uint32_t reserve_max_claim_sweep_per_epoch = 32; - - /// Drive the retention sweep directly, as the epoch tick would. Signed by the contract itself - /// (`sweepclaims` accepts `sysio.epoch` or self), so a test can age rows out without standing - /// up the epoch machinery. - action_result sweepclaims(uint32_t max_rows) { - return push_action(RESERVE_ACCOUNT, "sweepclaims"_n, mvo()("max_rows", max_rows)); - } - - /// Walk every row in `sysio.reserv::reserves` (KV-keyed by checksum256) - /// via the DB index and return the row whose slug_name triple matches. - /// `get_row_by_id` only supports uint64 keys; this scan is the test-side - /// workaround. - fc::variant find_reserve(std::string_view chain_code, - std::string_view token_code, - std::string_view reserve_code) { - const auto target_chain = cn(chain_code).value; - const auto target_token = cn(token_code).value; - const auto target_reserve = cn(reserve_code).value; - - const auto& db = control->db(); - const auto table_id = chain::compute_table_id("reserves"_n.to_uint64_t()); - const auto& kv_idx = db.get_index(); - auto itr = kv_idx.lower_bound(boost::make_tuple(RESERVE_ACCOUNT, table_id, std::string_view{})); - for (; itr != kv_idx.end() - && itr->code == RESERVE_ACCOUNT - && itr->table_id == table_id; ++itr) { - std::vector raw(itr->value.size()); - if (!raw.empty()) - std::memcpy(raw.data(), itr->value.data(), raw.size()); - try { - auto row = abi_ser.binary_to_variant( - "reserve_row", raw, - abi_serializer::create_yield_function(abi_serializer_max_time)); - if (row["chain_code"].as().value == target_chain && - row["token_code"].as().value == target_token && - row["reserve_code"].as().value == target_reserve) { - return row; - } - } catch (...) { - // skip rows that don't decode - } - } - return fc::variant(); - } - - // ── authex link seeding ── - // `oncrtreserve` gates a create on the creator being authex-linked (it - // probes `sysio.authex::links.bypubkey`). The base fixture deploys no - // authex code, so every creator reads as unlinked; tests that exercise the - // LINKED path (e.g. CANCELLED-row reclaim) deploy authex on demand and seed - // a link with `recordlink`. - - /// Deploy sysio.authex (account is pre-created by the tester boot) and load - /// its ABI so `recordlink` can be pushed and the cross-contract - /// `links.bypubkey` read in oncrtreserve resolves. - void deploy_authex() { - set_code(AUTHEX_ACCOUNT, contracts::authex_wasm()); - set_abi(AUTHEX_ACCOUNT, contracts::authex_abi().data()); - set_privileged(AUTHEX_ACCOUNT); - produce_blocks(); - const auto* a = control->find_account_metadata(AUTHEX_ACCOUNT); - BOOST_REQUIRE(a != nullptr); - abi_def abi; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(a->abi, abi), true); - authex_abi_ser.set_abi(std::move(abi), - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Seed an authex link for `pub` on `chain_kind` via the depot-only - /// `recordlink` (signed by sysio.authex itself). After this, a creator - /// presenting the matching raw pubkey reads as linked in oncrtreserve. - action_result recordlink(name account, ChainKind chain_kind, - const fc::crypto::public_key& pub) { - const auto address_bytes = fc::crypto::ethereum::address_to_bytes(pub); - const std::vector native_address(address_bytes.begin(), address_bytes.end()); - return push_to(AUTHEX_ACCOUNT, authex_abi_ser, AUTHEX_ACCOUNT, "recordlink"_n, mvo() - ("account", account) - ("chain_kind", chain_kind) - ("pub_key", pub) - ("native_address", native_address)); - } - - /// Deploy sysio.msgch so inline queueout actions execute in tests that - /// need to prove a reserve handler cannot trip msgch's hard chain guard. - void deploy_msgch() { - set_code(MSGCH_ACCOUNT, contracts::msgch_wasm()); - set_abi(MSGCH_ACCOUNT, contracts::msgch_abi().data()); - set_privileged(MSGCH_ACCOUNT); - produce_blocks(); - const auto* msgch_acct = control->find_account_metadata(MSGCH_ACCOUNT); - BOOST_REQUIRE(msgch_acct != nullptr); - abi_def msgch_def; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(msgch_acct->abi, msgch_def), true); - msgch_abi_ser.set_abi(std::move(msgch_def), - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// The first queued outbound attestation of `attest_type`, or a null variant. - /// `queueout` mints sequential ids, so a bounded forward scan finds any a test - /// queued. Requires deploy_msgch(). - fc::variant find_queued_attestation(const char* type_name, uint32_t type_value, - uint64_t scan_until = 32) { - for (uint64_t id = 0; id <= scan_until; ++id) { - auto data = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, "attestations"_n, id); - if (data.empty()) continue; - auto row = msgch_abi_ser.binary_to_variant( - "attestation_entry", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - // `type` is an AttestationType; accept either rendering so the helper - // survives a change in how the enum reflects. - const auto& t = row["type"]; - if ((t.is_string() && t.as_string() == type_name) || - (t.is_integer() && t.as_uint64() == type_value)) return row; - } - return fc::variant(); - } - - abi_serializer abi_ser; - abi_serializer msgch_abi_ser; - abi_serializer token_abi_ser; - abi_serializer authex_abi_ser; - abi_serializer chains_abi_ser; -}; - -BOOST_AUTO_TEST_SUITE(sysio_reserve_tests) - -// ── regreserve (bootstrap-window action; real-WIRE treasury drain) ── - -BOOST_FIXTURE_TEST_CASE(regreserve_creates_reserve_row, sysio_reserve_tester) { try { - const int64_t treasury_before = wire_balance(SYSIO_ACCOUNT); - - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", - /*chain_amount*/ 1'000'000, /*wire_amount*/ 2'000'000)); - - auto r = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL(1'000'000, r["reserve_chain_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(2'000'000, r["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(5000, r["connector_weight_bps"].as_uint64()); - BOOST_REQUIRE_EQUAL(false, r["is_private"].as_bool()); - BOOST_REQUIRE_EQUAL("", r["owner"].as_string()); - // Reserve self-describes its precision (default 9 here); never assumed. - BOOST_REQUIRE_EQUAL(9u, r["source_token_precision"].as_uint64()); - - // Custody invariant: the row's WIRE side is REAL — drained from the - // treasury into sysio.reserv's token balance. - BOOST_REQUIRE_EQUAL(2'000'000, wire_balance(RESERVE_ACCOUNT)); - BOOST_REQUIRE_EQUAL(treasury_before - 2'000'000, wire_balance(SYSIO_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -// A `slug_name` reaches action JSON either as its canonical STRING or through the -// transitional object form `{"value": N}`, and only the string arm validates. A reserve -// row is keyed on all three codes and there is no erase action, so an uncanonical code -// would be permanent. Rendering is TOTAL and will not complain: the value can decode to -// "" or to a valid spelling that re-parses as a DIFFERENT code, aliasing this reserve -// onto another's identity. `regreserve` is a privileged bootstrap-window action and -// refuses. -BOOST_FIXTURE_TEST_CASE(regreserve_uncanonical_code_rejected, sysio_reserve_tester) { try { - // Below the leading symbol's floor: decodes to "" and packs back to 0, so it is not a - // code and has no spelling. - constexpr uint64_t uncanonical = 7; - BOOST_REQUIRE(!fc::slug_name{uncanonical}.is_canonical()); - - // Any of the three key columns is enough to refuse the row. - for (const auto& which : {std::string("chain_code"), - std::string("token_code"), - std::string("reserve_code")}) { - auto data = mvo() - ("chain_code", which == "chain_code" ? fc::variant(mvo()("value", uncanonical)) - : fc::variant(std::string("ETH"))) - ("token_code", which == "token_code" ? fc::variant(mvo()("value", uncanonical)) - : fc::variant(std::string("ETH"))) - ("reserve_code", which == "reserve_code" ? fc::variant(mvo()("value", uncanonical)) - : fc::variant(std::string("PRIMARY"))) - ("name", std::string("bad reserve")) - ("description", std::string{}) - ("initial_chain_amount", uint64_t{1'000'000}) - ("initial_wire_amount", uint64_t{2'000'000}) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000u) - ("is_private", false) - ("owner", name{}); - BOOST_REQUIRE(push_action(RESERVE_ACCOUNT, "regreserve"_n, data) - .find("has no canonical slug_name spelling") != std::string::npos); - } - - // Control: spellable codes on the same shape still register. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", - /*chain_amount*/ 1'000'000, /*wire_amount*/ 2'000'000)); -} FC_LOG_AND_RETHROW() } - -// A token whose depot-frame precision is below 9 (e.g. a 6-decimal stablecoin) -// is recorded as-is on the reserve: precision is carried, never assumed to be 9. -BOOST_FIXTURE_TEST_CASE(regreserve_records_non_default_precision, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "USDC", "PRIMARY", - /*chain_amount*/ 1'000'000, /*wire_amount*/ 2'000'000, - /*weight*/ 5000, /*is_private*/ false, name{}, - "usdc reserve", "", /*source_token_precision*/ 6u)); - - auto r = find_reserve("ETH", "USDC", "PRIMARY"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL(6u, r["source_token_precision"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// source_token_precision above the depot frame (9) is rejected — the outpost must -// downscale to min(native, 9) at its boundary, so a higher value is malformed. -BOOST_FIXTURE_TEST_CASE(regreserve_rejects_precision_over_frame, sysio_reserve_tester) { try { - BOOST_REQUIRE( - regreserve("ETH", "ETH", "PRIMARY", - 1'000'000, 2'000'000, 5000, false, name{}, "", "", /*source_token_precision*/ 18u) - .find("source_token_precision exceeds the depot frame") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(regreserve_rejects_duplicate, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOL", "SOL", "PRIMARY", 100, 200, 5000)); - - // Re-call with the same triple must reject (regreserve only inserts). - BOOST_REQUIRE( - regreserve("SOL", "SOL", "PRIMARY", 999, 1234, 6000).find("already") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(regreserve_rejects_invalid_connector_weight, sysio_reserve_tester) { try { - BOOST_REQUIRE( - regreserve("ETH", "ETH", "PRIMARY", 100, 100, 0) - .find("connector_weight_bps") != std::string::npos); - - BOOST_REQUIRE( - regreserve("ETH", "ETH", "PRIMARY2", 100, 100, 10001) - .find("connector_weight_bps") != std::string::npos); - - // R10: cw == 10000 (== WEIGHT_TOTAL_BPS) makes the token-side weight zero, so the weighted - // curve returns 0 for every swap — a permanently dead reserve. It must be rejected (max is 9999). - BOOST_REQUIRE( - regreserve("ETH", "ETH", "PRIMARY3", 100, 100, 10000) - .find("connector_weight_bps") != std::string::npos); - - // The boundary value just below the total is accepted (token side keeps 1 bps). - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY4", 100, 100, 9999)); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(regreserve_private_requires_owner, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: a private bootstrap reserve must name an owner"), - regreserve("ETH", "ETH", "PRIVATE", 100, 100, 5000, /*is_private*/true, name{})); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(regreserve_seeds_private_reserve, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIVATE", 100, 100, 5000, - /*is_private*/true, "alice"_n)); - auto r = find_reserve("ETH", "ETH", "PRIVATE"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL(true, r["is_private"].as_bool()); - BOOST_REQUIRE_EQUAL("alice", r["owner"].as_string()); -} FC_LOG_AND_RETHROW() } - -// `name` and `description` persist into a reserve row billed to `ram_payer = sysio` — the -// shared system pool — so both are bounded before emplace, via the same -// `sysio::opp::registry::check_metadata` the chains and tokens registries use. CertiK WNS-10 -// raised the unbounded pair on `sysio.tokens::regtoken`; it was identical here. -// -// `regreserve` is privileged and abort-safe, so it enforces the bound with `check_metadata`. -// The post-bootstrap `oncrtreserve` path must never abort and instead routes an over-bound -// row into the existing cancel/refund flow — see `oncrtreserve_oversized_metadata_is_cancelled`. -BOOST_FIXTURE_TEST_CASE(regreserve_bounds_metadata, sysio_reserve_tester) { try { - BOOST_REQUIRE(regreserve("ETH", "ETH", "PRIMARY", 100, 100, 5000, false, name{}, - std::string(129, 'x')) - .find("label exceeds 128 bytes") != std::string::npos); - - BOOST_REQUIRE(regreserve("ETH", "ETH", "PRIMARY", 100, 100, 5000, false, name{}, - "ok", std::string(257, 'x')) - .find("description exceeds 256 bytes") != std::string::npos); - - // The bounds are inclusive, and neither rejection claimed the reserve key. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 100, 100, 5000, false, name{}, - std::string(128, 'x'), std::string(256, 'x'))); -} FC_LOG_AND_RETHROW() } - -// ── oncrtreserve (create gating) ── - -BOOST_FIXTURE_TEST_CASE(oncrtreserve_requires_msgch_auth, sysio_reserve_tester) { try { - BOOST_REQUIRE(push_action(RESERVE_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "user reserve") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x02')) - ).find("missing authority of sysio.msgch") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(oncrtreserve_unregistered_chain_soft_skips_before_queueout, sysio_reserve_tester) { try { - // Deploy msgch so the inline RESERVE_CREATE_CANCELLED queueout would execute. - // Pre-guard, the unlinked-creator cancel path inserted a CANCELLED row and - // then aborted inside sysio.msgch::queueout's hard chain registration check. - deploy_msgch(); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "NOCHAIN") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "user reserve") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x02')))); - - BOOST_REQUIRE(find_reserve("NOCHAIN", "ETH", "USERRES").is_null()); -} FC_LOG_AND_RETHROW() } - -// A reserve_code is chosen by the CALLER on the outpost -- ETH `create_reserve` and SOL -// `create_reserve_handler` validate only that it is non-zero -- and the creator's escrow is -// taken BEFORE the RESERVE_CREATE goes out. A code with no canonical spelling must therefore -// be REFUNDED, never dropped: a drop strands the funds in the outpost's PENDING record with -// no cancel path, since the outpost cannot release escrow on its own authority. -BOOST_FIXTURE_TEST_CASE(oncrtreserve_uncanonical_code_is_cancelled, sysio_reserve_tester) { try { - deploy_msgch(); - - // Below the leading symbol's floor: decodes to "" and packs back to 0, so it is - // not a code and has no spelling. - constexpr uint64_t uncanonical = 7; - BOOST_REQUIRE(!fc::slug_name{uncanonical}.is_canonical()); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", mvo()("value", uncanonical)) - ("name", "user reserve") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x02')) - )); - - // The refund is what matters: observe the OUTBOUND cancellation, not merely the - // absence of a depot row (the row's key IS the unspellable triple, so it could - // not be looked up by spelling anyway). - BOOST_REQUIRE(!find_queued_attestation( - "ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED", - sysio::opp::types::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED).is_null()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(oncrtreserve_unlinked_creator_is_cancelled, sysio_reserve_tester) { try { - // Create gating: no authex link exists for the creator's pubkey (the - // authex account carries no links table here), so the request must be - // rejected by inserting a CANCELLED row (idempotency + audit) and - // queueing RESERVE_CREATE_CANCELLED back. Never throws. - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "user reserve") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x02')) - )); - - auto r = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); -} FC_LOG_AND_RETHROW() } - -// WNS-28: `chain_code` determines the authoritative ChainKind. Keep the EVM -// address, key, and authex link otherwise valid so only the redundant payload -// kind is malformed. Without the explicit registry-kind comparison this would -// create a PENDING row under ETH despite declaring its creator as SVM. -BOOST_FIXTURE_TEST_CASE(oncrtreserve_creator_chain_kind_mismatch_is_cancelled, - sysio_reserve_tester) { try { - deploy_authex(); - deploy_msgch(); - - auto creator_pub = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::em).get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - const auto creator_key = em_pubkey_bytes(creator_pub); - const std::vector creator_address(20, '\x01'); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "mismatched creator") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_SVM) - ("creator_chain_addr", creator_address) - ("is_private", false) - ("creator_pub_key", creator_key))); - - auto r = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); - BOOST_REQUIRE(r["creator_pub_key"].as_string().empty()); - - // The mismatch must take the refund path, not merely write the tombstone. - // `queueout` starts attestation ids at 1 (id 0 is its sequence singleton). - auto queued = get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, "attestations"_n, 1); - BOOST_REQUIRE(!queued.empty()); -} FC_LOG_AND_RETHROW() } - -// `oncnclrsv` must authenticate the redundant payload kind against the chain -// registry before cancelling. A mismatched callback silently preserves the -// PENDING row and queues no refund; the matching callback then wins the race, -// marks the row CANCELLED, and queues exactly one refund attestation. -BOOST_FIXTURE_TEST_CASE(oncnclrsv_requires_registry_creator_kind, - sysio_reserve_tester) { try { - deploy_authex(); - deploy_msgch(); - - auto creator_pub = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::em).get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - const auto creator_key = em_pubkey_bytes(creator_pub); - const std::vector creator_address(20, '\x01'); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "CANCEL") - ("name", "cancel kind guard") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", creator_address) - ("is_private", false) - ("creator_pub_key", creator_key))); - - auto pending = find_reserve("ETH", "ETH", "CANCEL"); - BOOST_REQUIRE(!pending.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_PENDING", pending["status"].as_string()); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncnclrsv"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "CANCEL") - ("creator_chain_kind", ChainKind::CHAIN_KIND_SVM) - ("creator_chain_addr", creator_address))); - - auto after_mismatch = find_reserve("ETH", "ETH", "CANCEL"); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_PENDING", after_mismatch["status"].as_string()); - BOOST_REQUIRE(get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, - "attestations"_n, 1).empty()); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncnclrsv"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "CANCEL") - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", creator_address))); - - auto cancelled = find_reserve("ETH", "ETH", "CANCEL"); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", cancelled["status"].as_string()); - BOOST_REQUIRE(!get_row_by_id(MSGCH_ACCOUNT, MSGCH_ACCOUNT, - "attestations"_n, 1).empty()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(oncrtreserve_invalid_creator_address_is_cancelled, - sysio_reserve_tester) { try { - deploy_authex(); - - auto creator_pub = fc::crypto::private_key::generate( - fc::crypto::private_key::key_type::em).get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - const auto creator_key = em_pubkey_bytes(creator_pub); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "BADADDR") - ("name", "malformed creator address") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(32, '\x01')) - ("is_private", false) - ("creator_pub_key", creator_key))); - - auto r = find_reserve("ETH", "ETH", "BADADDR"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); - BOOST_REQUIRE(r["creator_addr"]["address"].as_string().empty()); -} FC_LOG_AND_RETHROW() } - -// A re-relay of the same unlinked create must be idempotent — it must NOT -// re-insert the row or queue a second RESERVE_CREATE_CANCELLED refund. The -// CANCELLED marker stays exactly as first written (the outpost refunds per -// (chain,token,reserve_code), so a second refund would be a double spend). -BOOST_FIXTURE_TEST_CASE(oncrtreserve_cancelled_relay_does_not_double_refund, sysio_reserve_tester) { try { - auto crt = [&]() { - return push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "user reserve") - ("description", "") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x02'))); - }; - - BOOST_REQUIRE_EQUAL(success(), crt()); - auto r1 = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r1.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r1["status"].as_string()); - const auto registered_at = r1["registered_at_ms"].as_uint64(); - - // Advance time, then re-relay the identical create. - produce_blocks(4); - BOOST_REQUIRE_EQUAL(success(), crt()); - - auto r2 = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r2.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r2["status"].as_string()); - // The row is untouched: same registered_at_ms despite the advanced clock — - // proving the second relay did NOT re-insert (and therefore did not refund). - BOOST_REQUIRE_EQUAL(registered_at, r2["registered_at_ms"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// A CANCELLED row must NOT permanently burn the (chain,token,reserve_code) -// identity. A later authex-LINKED creator reclaims the same triple — the row -// flips CANCELLED → PENDING with the new creator's fields, rather than the -// create being skipped by the existence guard. This is the namespace-squat fix. -BOOST_FIXTURE_TEST_CASE(oncrtreserve_cancelled_is_reclaimable_by_linked_creator, sysio_reserve_tester) { try { - deploy_authex(); - - // 1) An UNLINKED creator squats the triple → CANCELLED. - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "squatter") - ("description", "squat") - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x09')) - ("is_private", false) - ("creator_pub_key", std::vector(33, '\x07')))); // unlinked key - { - auto r = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); - } - - // 2) The rightful owner is authex-linked. Seed the link for their EM key, - // then register the SAME triple with the matching raw pubkey. - auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); - auto creator_pub = creator_priv.get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "rightful owner") - ("description", "reclaimed") - ("external_token_amount", 5000) - ("requested_wire_amount", 4000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", em_pubkey_bytes(creator_pub)))); // linked key - - // The squat is gone: the row is PENDING and carries the reclaiming creator's - // create (proving overwrite, not the existence-guard skip). - auto r = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_PENDING", r["status"].as_string()); - BOOST_REQUIRE_EQUAL("reclaimed", r["description"].as_string()); - BOOST_REQUIRE_EQUAL(5000u, r["external_token_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(5000u, r["reserve_chain_amount"].as_uint64()); - // The canonical creator pubkey is now stored (empty on the prior CANCELLED row). - BOOST_REQUIRE(!r["creator_pub_key"].as_string().empty()); -} FC_LOG_AND_RETHROW() } - -// WSA-028: a reserve-create whose external amount is invalid — zero, which is -// what sysio.msgch's to_depot_amount clamp produces for a negative or -// out-of-range inbound TokenAmount — must NOT be silently dropped. The creator's -// outpost escrow has to be released, so the request is routed into the -// cancel/refund flow: a CANCELLED row is inserted (the same branch that queues -// RESERVE_CREATE_CANCELLED back to the outpost). Pre-fix, oncrtreserve returned -// at the zero-amount guard before any cancel, stranding the escrow. The creator -// here is properly authex-LINKED, so the amount is the sole rejection reason. -BOOST_FIXTURE_TEST_CASE(oncrtreserve_invalid_amount_is_cancelled, sysio_reserve_tester) { try { - deploy_authex(); - - auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); - auto creator_pub = creator_priv.get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - - // Linked creator, but external_token_amount == 0 (the clamp result for an - // invalid inbound amount). The link is valid, so the amount alone forces the - // cancel/refund — proving the amount path no longer drops silently. - BOOST_REQUIRE_EQUAL(success(), push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "USERRES") - ("name", "invalid amount") - ("description", "") - ("external_token_amount", 0) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", em_pubkey_bytes(creator_pub)))); // linked key - - // The escrow is released via the cancel/refund flow: a CANCELLED row stands - // (inserted in the same branch that queues RESERVE_CREATE_CANCELLED). - auto r = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!r.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", r["status"].as_string()); -} FC_LOG_AND_RETHROW() } - -// The same `sysio`-billed metadata bound `regreserve` enforces, applied to the inbound -// creation path. `oncrtreserve` is an OPP dispatch handler, so it must NOT `check()` — -// aborting here rolls back the consensus-tipping delivery and stalls epoch advancement -// chain-wide (feedback_opp_handlers_never_throw / epoch-stall-is-fatal). An over-bound -// string therefore joins `invalid_amount` and the unlinked-creator case on the existing -// reject predicate, releasing the creator's escrow via RESERVE_CREATE_CANCELLED. -// -// The creator here is properly LINKED with a valid amount, so the metadata is the sole -// rejection reason. The tombstone is itself a `sysio`-billed row, so the over-bound strings -// are replaced by a fixed marker rather than carried onto it. Both an ASCII and a MULTIBYTE -// over-bound label are covered: because nothing is truncated there is no code-point boundary -// to split, so neither can leave malformed text in state. A third case drives the OTHER half -// of `metadata_exceeds_bounds` — an over-bound `description` behind an in-bound name — so -// each side of the predicate is exercised independently rather than only via the label. -BOOST_FIXTURE_TEST_CASE(oncrtreserve_oversized_metadata_is_cancelled, sysio_reserve_tester) { try { - deploy_authex(); - - auto creator_priv = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em); - auto creator_pub = creator_priv.get_public_key(); - BOOST_REQUIRE_EQUAL(success(), - recordlink("alice"_n, ChainKind::CHAIN_KIND_EVM, creator_pub)); - - auto create_with_metadata = [&](std::string_view reserve_code, - const std::string& name, - const std::string& description) { - return push_action(MSGCH_ACCOUNT, "oncrtreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", reserve_code) - ("name", name) - ("description", description) - ("external_token_amount", 1000) - ("requested_wire_amount", 1000) - ("source_token_precision", 9u) - ("connector_weight_bps", 5000) - ("creator_chain_kind", ChainKind::CHAIN_KIND_EVM) - ("creator_chain_addr", std::vector(20, '\x01')) - ("is_private", false) - ("creator_pub_key", em_pubkey_bytes(creator_pub))); // linked key - }; - - // 129 ASCII bytes — one over label_max_bytes. - BOOST_REQUIRE_EQUAL(success(), create_with_metadata("USERRES", std::string(129, 'x'), "")); - auto ascii_row = find_reserve("ETH", "ETH", "USERRES"); - BOOST_REQUIRE(!ascii_row.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", ascii_row["status"].as_string()); - BOOST_REQUIRE_EQUAL("", ascii_row["name"].as_string()); - - // 129 bytes as 127 ASCII + `é` (0xC3 0xA9) — a byte-wise truncation at the bound would - // have split the final character and persisted a lone lead byte. The marker sidesteps - // that entirely: nothing is truncated, so there is no code-point boundary to get wrong. - const std::string multibyte_name = std::string(127, 'x') + "\xC3\xA9"; - BOOST_REQUIRE_EQUAL(129u, multibyte_name.size()); - BOOST_REQUIRE_EQUAL(success(), create_with_metadata("USERRES2", multibyte_name, "")); - auto multibyte_row = find_reserve("ETH", "ETH", "USERRES2"); - BOOST_REQUIRE(!multibyte_row.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", multibyte_row["status"].as_string()); - BOOST_REQUIRE_EQUAL("", multibyte_row["name"].as_string()); - - // 257 bytes — one over description_max_bytes — behind an in-bound name. This is the - // `description` half of `metadata_exceeds_bounds`: the label alone would have passed, so - // the description is the sole rejection reason. The tombstone drops the over-bound text - // rather than carrying it onto a `sysio`-billed row, so the stored description is empty - // while the name still reads as the rejection marker. - const std::string oversized_description(257, 'd'); - BOOST_REQUIRE_EQUAL(success(), - create_with_metadata("USERRES3", "in-bound name", oversized_description)); - auto description_row = find_reserve("ETH", "ETH", "USERRES3"); - BOOST_REQUIRE(!description_row.is_null()); - BOOST_REQUIRE_EQUAL("RESERVE_STATUS_CANCELLED", description_row["status"].as_string()); - BOOST_REQUIRE_EQUAL("", description_row["name"].as_string()); - BOOST_REQUIRE_EQUAL("", description_row["description"].as_string()); -} FC_LOG_AND_RETHROW() } - -// ── matchreserve (gating preconditions) ── - -BOOST_FIXTURE_TEST_CASE(matchreserve_rejects_unknown_reserve, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: matchreserve: reserve not found"), - push_action("alice"_n, "matchreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "NOPE") - ("matcher", "alice") - ("wire_amount", 100))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(matchreserve_rejects_non_pending, sysio_reserve_tester) { try { - // Bootstrap rows activate inline — there is nothing to match. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: matchreserve: reserve is not PENDING"), - push_action("alice"_n, "matchreserve"_n, mvo() - ("chain_code", "ETH") - ("token_code", "ETH") - ("reserve_code", "PRIMARY") - ("matcher", "alice") - ("wire_amount", 1000))); -} FC_LOG_AND_RETHROW() } - -// ── Emit-time settlement actions (auth = sysio.uwrit) ── - -BOOST_FIXTURE_TEST_CASE(applyswap_requires_uwrit_auth, sysio_reserve_tester) { try { - BOOST_REQUIRE(push_action("alice"_n, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 50) - ("underwriter", "underwriter1") - ).find("missing authority of sysio.uwrit") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(applyswap_applies_four_legs, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1000, 1000)); - - // w = cp_output(1000, 1000, 100) = 1000*100 / (1000+100) = 90 (floor). - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 50) - ("underwriter", "underwriter1"))); - - auto src = find_reserve("ETH", "ETH", "PRIMARY"); - auto dst = find_reserve("SOLANA", "SOL", "PRIMARY"); - BOOST_REQUIRE_EQUAL(1100, src["reserve_chain_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(910, src["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(1090, dst["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(950, dst["reserve_chain_amount"].as_uint64()); - // Σ reserve_wire_amount unchanged (910 + 1090 == 2000) — at these tiny - // amounts the 0.1% fee floors to 0, so the w hop stays fully internal. -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-02: applyswap must refuse a destination debit larger than the -// destination curve's output for the post-fee WIRE it is receiving. The audited -// vulnerability paid out a caller-chosen `dst_amount` verbatim, so the reserve -// enforces its own floor instead of trusting `sysio.uwrit` to have derived it. -BOOST_FIXTURE_TEST_CASE(applyswap_rejects_debit_above_curve_output, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1000, 1000)); - - // w_gross = 90 (see applyswap_applies_four_legs), fee floors to 0, so the - // destination receives 90 WIRE and its curve yields - // floor(1000*90/1090) = 82 token. 83 is one subunit past what the AMM owes. - constexpr int64_t CURVE_OUT = 82; - auto apply = [&](int64_t dst_amount) { - return push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", dst_amount) - ("underwriter", "underwriter1")); - }; - - // One subunit over — refused. A grossly-inflated amount (the drain shape) - // takes the same path. - const auto expected = error("assertion failure with message: applyswap: " - "destination amount exceeds the curve output for the post-fee WIRE"); - BOOST_REQUIRE_EQUAL(expected, apply(CURVE_OUT + 1)); - BOOST_REQUIRE_EQUAL(expected, apply(900)); - - // Exactly the curve output settles — the amount `sysio.uwrit` derives. - BOOST_REQUIRE_EQUAL(success(), apply(CURVE_OUT)); - BOOST_REQUIRE_EQUAL(1000 - CURVE_OUT, - find_reserve("SOLANA", "SOL", "PRIMARY")["reserve_chain_amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(applyswap_charges_fee_and_routes_50_50, sysio_reserve_tester) { try { - // Large amounts so the default 0.1% (10 bps) fee is non-zero and routes. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - - const int64_t sysio_before = wire_balance(SYSIO_ACCOUNT); - const int64_t resv_before = wire_balance(RESERVE_ACCOUNT); - - // w_gross = cp_output(1e12, 1e12, 1e9) = 999'000'999 (50/50 = constant product). - // fee = 999'000'999 * 10 / 10000 = 999'000 ; underwriter = reward = 499'500 ; - // net = 999'000'999 - 999'000 = 998'001'999. - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - auto src = find_reserve("ETH", "ETH", "PRIMARY"); - auto dst = find_reserve("SOLANA", "SOL", "PRIMARY"); - // Source gives up the full gross WIRE; destination receives only the net. - BOOST_REQUIRE_EQUAL(1'000'000'000'000ULL - 999'000'999ULL, src["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(1'000'000'000'000ULL + 998'001'999ULL, dst["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(1'000'000'000'000ULL + 1'000'000'000ULL, src["reserve_chain_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(1'000'000'000'000ULL - 100'000'000ULL, dst["reserve_chain_amount"].as_uint64()); - - // Fee split 50/50: half accrues to the winning underwriter's claimable row, - // half to the batch-operator rewards bucket. - auto uwf = get_uwfees(UNDERWRITER_ACCOUNT); - BOOST_REQUIRE_EQUAL(499'500ULL, uwf["balance"].as_uint64()); - BOOST_REQUIRE_EQUAL(499'500ULL, uwf["lifetime_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(0ULL, uwf["lifetime_claimed"].as_uint64()); - - auto bkt = get_rewardbkt(); - BOOST_REQUIRE_EQUAL(499'500ULL, bkt["balance"].as_uint64()); - BOOST_REQUIRE_EQUAL(499'500ULL, bkt["lifetime_accrued"].as_uint64()); - - // BOTH halves stay in reserv custody — no part of a swap fee reaches the - // emissions treasury, so neither real balance moves at settlement. - BOOST_REQUIRE_EQUAL(sysio_before, wire_balance(SYSIO_ACCOUNT)); - BOOST_REQUIRE_EQUAL(resv_before, wire_balance(RESERVE_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_emissions_share_routes_pool_to_treasury, sysio_reserve_tester) { try { - // Stage 2 of the split is a governance dial: a non-zero - // `fee_emissions_share_bps` diverts that share of the REWARDS POOL (the half - // left after the underwriter's cut) out of custody to the `sysio` treasury. - BOOST_REQUIRE(push_action("alice"_n, "setconfig"_n, mvo()("fee_emissions_share_bps", 5000)) - .find("missing authority of sysio.reserv") != std::string::npos); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: setconfig: fee_emissions_share_bps must be <= 10000 (100% of the rewards pool)"), - push_action(RESERVE_ACCOUNT, "setconfig"_n, mvo()("fee_emissions_share_bps", 10001))); - - // Send the WHOLE rewards pool to the treasury so the split is unambiguous. - BOOST_REQUIRE_EQUAL(success(), push_action(RESERVE_ACCOUNT, "setconfig"_n, - mvo()("fee_emissions_share_bps", 10000))); - - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - - const int64_t sysio_before = wire_balance(SYSIO_ACCOUNT); - const int64_t resv_before = wire_balance(RESERVE_ACCOUNT); - - // Same swap as the 50/50 test: fee 999'000, underwriter 499'500, pool 499'500. - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - // The underwriter's half is untouched by this dial and stays in custody. - BOOST_REQUIRE_EQUAL(499'500ULL, get_uwfees(UNDERWRITER_ACCOUNT)["balance"].as_uint64()); - // The whole pool went to the treasury instead of the rewards bucket, and it - // is the ONLY part of the fee that left custody. - BOOST_REQUIRE(get_rewardbkt().is_null()); - BOOST_REQUIRE_EQUAL(sysio_before + 499'500, wire_balance(SYSIO_ACCOUNT)); - BOOST_REQUIRE_EQUAL(resv_before - 499'500, wire_balance(RESERVE_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -// ── Reserve OWNER fee: per-reserve rate, accrual, and owner claim (WIRE-281) ── - -BOOST_FIXTURE_TEST_CASE(setrsvfee_guards_owner_status_and_bounds, sysio_reserve_tester) { try { - // Owner-less (bootstrap-seeded) reserve: nobody can authorize a fee, so no - // WIRE can ever accrue with no claimant. - BOOST_REQUIRE_EQUAL(success(), regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: setrsvfee: reserve has no owner"), - push_action(RESERVE_ACCOUNT, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")("owner_fee_bps", 100))); - - // An OWNED reserve: only the owner may set the fee. - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1000, 1000, 5000, false, "alice"_n)); - auto setFee = [&](name signer, uint32_t bps) { - return push_action(signer, "setrsvfee"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL") - ("reserve_code", "PRIMARY")("owner_fee_bps", bps)); - }; - BOOST_REQUIRE(setFee(UNDERWRITER_ACCOUNT, 100).find("missing authority of alice") != std::string::npos); - - // 0 disables; the [1, 9900] band is accepted at both ends; past 99% rejected. - BOOST_REQUIRE_EQUAL(success(), setFee("alice"_n, 0)); - produce_block(); - BOOST_REQUIRE_EQUAL(success(), setFee("alice"_n, 1)); - produce_block(); - BOOST_REQUIRE_EQUAL(success(), setFee("alice"_n, 9900)); - produce_block(); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: setrsvfee: owner_fee_bps must be 0 or within [1, 9900]"), - setFee("alice"_n, 9901)); - - // Unknown reserve fails before any auth work. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: setrsvfee: reserve not found"), - push_action("alice"_n, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "NOPE") - ("reserve_code", "PRIMARY")("owner_fee_bps", 10))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(applyswap_charges_both_reserve_owner_fees, sysio_reserve_tester) { try { - // Jonathan, 2026-08-04: "per reserve" — a chain-to-chain swap pays 3 fees, - // two reserve owners plus the network. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL, - 5000, false, "alice"_n)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL, - 5000, false, UNDERWRITER_ACCOUNT)); - - // src 100 bps (1%), dst 200 bps (2%) on the same WIRE leg. - BOOST_REQUIRE_EQUAL(success(), push_action("alice"_n, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")("owner_fee_bps", 100))); - BOOST_REQUIRE_EQUAL(success(), push_action(UNDERWRITER_ACCOUNT, "setrsvfee"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL") - ("reserve_code", "PRIMARY")("owner_fee_bps", 200))); - - const int64_t resv_before = wire_balance(RESERVE_ACCOUNT); - - // w_gross = 999'000'999. network 10bps = 999'000; src 1% = 9'990'009; - // dst 2% = 19'980'019; net = w_gross - (999'000 + 9'990'009 + 19'980'019). - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - auto src = find_reserve("ETH", "ETH", "PRIMARY"); - auto dst = find_reserve("SOLANA", "SOL", "PRIMARY"); - BOOST_REQUIRE_EQUAL(9'990'009ULL, src["owner_fee_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(9'990'009ULL, src["owner_fee_lifetime"].as_uint64()); - BOOST_REQUIRE_EQUAL(19'980'019ULL, dst["owner_fee_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(19'980'019ULL, dst["owner_fee_lifetime"].as_uint64()); - - // Destination liquidity is the leg minus ALL THREE fees. - const uint64_t total_fee = 999'000ULL + 9'990'009ULL + 19'980'019ULL; - BOOST_REQUIRE_EQUAL(1'000'000'000'000ULL + (999'000'999ULL - total_fee), - dst["reserve_wire_amount"].as_uint64()); - - // Every fee stayed in custody — the reserve shares as accruals, the network - // fee as the underwriter accrual + rewards bucket. - BOOST_REQUIRE_EQUAL(resv_before, wire_balance(RESERVE_ACCOUNT)); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(single_reserve_paths_charge_only_their_own_side, sysio_reserve_tester) { try { - // A WIRE endpoint has no reserve, so only the one participating reserve - // charges: paywire → source, applyfromwire → destination. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL, - 5000, false, "alice"_n)); - BOOST_REQUIRE_EQUAL(success(), push_action("alice"_n, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")("owner_fee_bps", 100))); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "paywire"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("recipient", "alice") - ("wire_out", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - // 1% of the 999'000'999 gross leg accrued to the source reserve's owner. - BOOST_REQUIRE_EQUAL(9'990'009ULL, - find_reserve("ETH", "ETH", "PRIMARY")["owner_fee_accrued"].as_uint64()); - - // applyfromwire: the destination reserve is the only one that charges. - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL, - 5000, false, UNDERWRITER_ACCOUNT)); - BOOST_REQUIRE_EQUAL(success(), push_action(UNDERWRITER_ACCOUNT, "setrsvfee"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL") - ("reserve_code", "PRIMARY")("owner_fee_bps", 200))); - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyfromwire"_n, mvo() - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("wire_in", 1'000'000'000ULL) - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - // 2% of the 1e9 escrow. The source reserve is untouched by this swap. - BOOST_REQUIRE_EQUAL(20'000'000ULL, - find_reserve("SOLANA", "SOL", "PRIMARY")["owner_fee_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(9'990'009ULL, - find_reserve("ETH", "ETH", "PRIMARY")["owner_fee_accrued"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(claimrsvfee_pays_owner_and_guards_auth, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL, - 5000, false, "alice"_n)); - - auto claim = [&](name signer) { - return push_action(signer, "claimrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")); - }; - // Nothing earned yet. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: claimrsvfee: no unclaimed balance"), claim("alice"_n)); - - BOOST_REQUIRE_EQUAL(success(), push_action("alice"_n, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")("owner_fee_bps", 100))); - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "paywire"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("recipient", UNDERWRITER_ACCOUNT) - ("wire_out", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - const int64_t alice_before = wire_balance("alice"_n), - resv_before = wire_balance(RESERVE_ACCOUNT); - BOOST_REQUIRE_EQUAL(9'990'009ULL, - find_reserve("ETH", "ETH", "PRIMARY")["owner_fee_accrued"].as_uint64()); - - // Only the owner may sweep it. - BOOST_REQUIRE(claim(UNDERWRITER_ACCOUNT).find("missing authority of alice") != std::string::npos); - BOOST_REQUIRE_EQUAL(success(), claim("alice"_n)); - - BOOST_REQUIRE_EQUAL(alice_before + 9'990'009, wire_balance("alice"_n)); - BOOST_REQUIRE_EQUAL(resv_before - 9'990'009, wire_balance(RESERVE_ACCOUNT)); - - auto row = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE_EQUAL(0ULL, row["owner_fee_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(9'990'009ULL, row["owner_fee_lifetime"].as_uint64()); // audit total survives - - produce_block(); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: claimrsvfee: no unclaimed balance"), claim("alice"_n)); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(swapquote_prices_the_reserve_owner_fees, sysio_reserve_tester) { try { - // The read-only quote must price EXACTLY what settlement charges, reserve - // fees included — otherwise the variance check drifts against the books. - // So this asserts the DECODED quote against the shared AMM kernel, not just - // that the action succeeded: a quote that ignored `owner_fee_bps` entirely - // would still return success. - constexpr uint64_t POOL = 1'000'000'000'000ULL; - constexpr uint32_t WEIGHT = 5000; - constexpr uint64_t FROM = 1'000'000'000ULL; - constexpr uint32_t OWNER_FEE = 500; // 5%, charged by EACH side's reserve - // `sysio.uwrit` exists as a bare account in this fixture (no contract), so - // the contract's `uwrit_fee_bps()` reads the `uw_config{}` in-struct default. - constexpr uint32_t NETWORK_FEE_BPS = 10; - - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", POOL, POOL, WEIGHT, false, "alice"_n)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", POOL, POOL, WEIGHT, false, UNDERWRITER_ACCOUNT)); - - /// The kernel's answer for these pools at a given pair of reserve fees. - auto expected = [&](uint32_t src_fee, uint32_t dst_fee) { - return opp::amm::quote_swap(/*src_is_wire*/false, POOL, POOL, WEIGHT, - /*dst_is_wire*/false, POOL, POOL, WEIGHT, - FROM, NETWORK_FEE_BPS, src_fee, dst_fee); - }; - - // Fee-free baseline: network fee only. - const uint64_t before = swapquote_value("ETH", "ETH", "PRIMARY", FROM, - "SOLANA", "SOL", "PRIMARY"); - BOOST_REQUIRE_GT(before, 0u); - BOOST_CHECK_EQUAL(before, expected(0, 0)); - - BOOST_REQUIRE_EQUAL(success(), push_action("alice"_n, "setrsvfee"_n, mvo() - ("chain_code", "ETH")("token_code", "ETH") - ("reserve_code", "PRIMARY")("owner_fee_bps", OWNER_FEE))); - BOOST_REQUIRE_EQUAL(success(), push_action(UNDERWRITER_ACCOUNT, "setrsvfee"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL") - ("reserve_code", "PRIMARY")("owner_fee_bps", OWNER_FEE))); - produce_block(); - - // Both owner fees are now priced in, off the same gross WIRE leg. - const uint64_t after = swapquote_value("ETH", "ETH", "PRIMARY", FROM, - "SOLANA", "SOL", "PRIMARY"); - BOOST_CHECK_EQUAL(after, expected(OWNER_FEE, OWNER_FEE)); - BOOST_CHECK_LT(after, before); // the claim this case exists to prove - - // Each side is priced INDEPENDENTLY — clearing one must move the quote back - // by only that side's share, which a quote summing the wrong reserve's rate - // (or double-counting one) would not reproduce. - BOOST_REQUIRE_EQUAL(success(), push_action(UNDERWRITER_ACCOUNT, "setrsvfee"_n, mvo() - ("chain_code", "SOLANA")("token_code", "SOL") - ("reserve_code", "PRIMARY")("owner_fee_bps", 0))); - produce_block(); - const uint64_t source_only = swapquote_value("ETH", "ETH", "PRIMARY", FROM, - "SOLANA", "SOL", "PRIMARY"); - BOOST_CHECK_EQUAL(source_only, expected(OWNER_FEE, 0)); - BOOST_CHECK_LT(source_only, before); - BOOST_CHECK_GT(source_only, after); -} FC_LOG_AND_RETHROW() } - -/// WIRE-375: a token called WIRE on a foreign chain is not depot-native WIRE. -/// Without an ACTIVE `(ETH, WIRE, PRIMARY)` reserve, the source leg cannot be -/// priced and must return zero instead of taking the native WIRE-at-par path. -BOOST_FIXTURE_TEST_CASE(swapquote_foreign_wire_codename_requires_reserve, - sysio_reserve_tester) { try { - constexpr uint64_t FROM = 2'000'000'000; - - BOOST_REQUIRE(find_reserve("ETH", "WIRE", "PRIMARY").is_null()); - BOOST_REQUIRE_EQUAL(0u, swapquote_value("ETH", "WIRE", "PRIMARY", FROM, - "WIRE", "WIRE", "WIRE")); -} FC_LOG_AND_RETHROW() } - -// ── Underwriter fee accrual + owner-authenticated claim ── - -BOOST_FIXTURE_TEST_CASE(claimuwfee_pays_accrual_and_zeroes_balance, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - const int64_t resv_before = wire_balance(RESERVE_ACCOUNT); - BOOST_REQUIRE_EQUAL(0, wire_balance(UNDERWRITER_ACCOUNT)); - BOOST_REQUIRE_EQUAL(499'500ULL, get_uwfees(UNDERWRITER_ACCOUNT)["balance"].as_uint64()); - - // The earner claims their own accrual. - BOOST_REQUIRE_EQUAL(success(), push_action(UNDERWRITER_ACCOUNT, "claimuwfee"_n, - mvo()("underwriter", "underwriter1"))); - - // REAL WIRE moved out of reserv custody to the underwriter. - BOOST_REQUIRE_EQUAL(499'500, wire_balance(UNDERWRITER_ACCOUNT)); - BOOST_REQUIRE_EQUAL(resv_before - 499'500, wire_balance(RESERVE_ACCOUNT)); - - // Row retained at zero balance; lifetime totals record the round trip. - auto uwf = get_uwfees(UNDERWRITER_ACCOUNT); - BOOST_REQUIRE_EQUAL(0ULL, uwf["balance"].as_uint64()); - BOOST_REQUIRE_EQUAL(499'500ULL, uwf["lifetime_accrued"].as_uint64()); - BOOST_REQUIRE_EQUAL(499'500ULL, uwf["lifetime_claimed"].as_uint64()); - - // A second claim on the drained row is a caller error, not a silent no-op. - // Produce a block first: an identical action re-pushed against the same TAPOS - // reference block serializes to the same tx id and is rejected as a duplicate - // before it ever reaches the contract. - produce_block(); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: claimuwfee: no unclaimed balance"), - push_action(UNDERWRITER_ACCOUNT, "claimuwfee"_n, mvo()("underwriter", "underwriter1"))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(claimuwfee_requires_earner_auth_and_a_row, sysio_reserve_tester) { try { - // Nobody else may sweep an underwriter's accrual. - BOOST_REQUIRE(push_action("alice"_n, "claimuwfee"_n, mvo()("underwriter", "underwriter1")) - .find("missing authority of underwriter1") != std::string::npos); - - // An underwriter that never earned has no row at all. - BOOST_REQUIRE(get_uwfees(UNDERWRITER_ACCOUNT).is_null()); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: claimuwfee: no accrued swap fees for this underwriter"), - push_action(UNDERWRITER_ACCOUNT, "claimuwfee"_n, mvo()("underwriter", "underwriter1"))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(applyswap_accrues_per_underwriter_and_accumulates, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - - auto swap_won_by = [&](const char* underwriter) { - return push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", underwriter)); - }; - - // Two swaps won by the same underwriter accumulate on one row. Each repeat of - // an identical action needs its own block — same TAPOS reference + same bytes - // is one tx id, rejected as a duplicate before reaching the contract. - BOOST_REQUIRE_EQUAL(success(), swap_won_by("underwriter1")); - const uint64_t first = get_uwfees(UNDERWRITER_ACCOUNT)["balance"].as_uint64(); - BOOST_REQUIRE_GT(first, 0u); - produce_block(); - BOOST_REQUIRE_EQUAL(success(), swap_won_by("underwriter1")); - auto after_two = get_uwfees(UNDERWRITER_ACCOUNT); - BOOST_REQUIRE_GT(after_two["balance"].as_uint64(), first); - BOOST_REQUIRE_EQUAL(after_two["balance"].as_uint64(), - after_two["lifetime_accrued"].as_uint64()); - - // A different winner accrues to their OWN row, leaving the first untouched. - // (Different `underwriter` bytes, so no duplicate-tx risk here.) - const uint64_t before_other = after_two["balance"].as_uint64(); - BOOST_REQUIRE_EQUAL(success(), swap_won_by("alice")); - BOOST_REQUIRE_GT(get_uwfees("alice"_n)["balance"].as_uint64(), 0u); - BOOST_REQUIRE_EQUAL(before_other, get_uwfees(UNDERWRITER_ACCOUNT)["balance"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// ── drainrewards: sweep the accrued batch-operator share to the emissions treasury ── -// payepoch (sysio.system) calls this inline to fold swap fees into the per-epoch -// batch-operator distribution. - -BOOST_FIXTURE_TEST_CASE(drainrewards_sweeps_bucket_to_treasury, sysio_reserve_tester) { try { - // Seed the rewards bucket with a swap fee (same setup as the 50/50 routing test: - // reward half = 499'500 accrues into the bucket, staying in reserv custody). - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1'000'000'000'000ULL, 1'000'000'000'000ULL)); - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyswap"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 1'000'000'000ULL) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("dst_amount", 100'000'000ULL) - ("underwriter", "underwriter1"))); - - auto bkt = get_rewardbkt(); - const uint64_t reward = bkt["balance"].as_uint64(); - const uint64_t lifetime = bkt["lifetime_accrued"].as_uint64(); - BOOST_REQUIRE_EQUAL(499'500ULL, reward); - - const int64_t sysio_before = wire_balance(SYSIO_ACCOUNT); - const int64_t resv_before = wire_balance(RESERVE_ACCOUNT); - - // Sweep the whole bucket to the treasury (auth = sysio). - BOOST_REQUIRE_EQUAL(success(), push_action(SYSIO_ACCOUNT, "drainrewards"_n, - mvo()("amount", static_cast(reward)))); - - auto bkt_after = get_rewardbkt(); - BOOST_REQUIRE_EQUAL(0ULL, bkt_after["balance"].as_uint64()); // balance drained - BOOST_REQUIRE_EQUAL(lifetime, bkt_after["lifetime_accrued"].as_uint64()); // audit total untouched - BOOST_REQUIRE_EQUAL(sysio_before + static_cast(reward), - wire_balance(SYSIO_ACCOUNT)); // WIRE moved to treasury - BOOST_REQUIRE_EQUAL(resv_before - static_cast(reward), - wire_balance(RESERVE_ACCOUNT)); // left reserv custody -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(drainrewards_auth_and_overdrain_guarded, sysio_reserve_tester) { try { - // Only `sysio` (the treasury / system account) may drain. - BOOST_REQUIRE(push_action("alice"_n, "drainrewards"_n, mvo()("amount", int64_t(1))) - .find("missing authority of sysio") != std::string::npos); - - // Draining more than the live balance (here: an empty bucket) is rejected, - // not silently clamped -- this only fires on a caller bug. - BOOST_REQUIRE(push_action(SYSIO_ACCOUNT, "drainrewards"_n, mvo()("amount", int64_t(1))) - .find("amount exceeds rewards bucket balance") != std::string::npos); - - // Non-positive amounts fail loudly (internal sweep -> a non-positive amount - // signals a caller bug, not a no-op). - BOOST_REQUIRE(push_action(SYSIO_ACCOUNT, "drainrewards"_n, mvo()("amount", int64_t(0))) - .find("amount must be positive") != std::string::npos); - BOOST_REQUIRE(push_action(SYSIO_ACCOUNT, "drainrewards"_n, mvo()("amount", int64_t(-5))) - .find("amount must be positive") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(applyfromwire_credits_wire_and_debits_chain, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "applyfromwire"_n, mvo() - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("wire_in", 200) - ("dst_amount", 100) - ("underwriter", "underwriter1"))); - - auto r = find_reserve("SOLANA", "SOL", "PRIMARY"); - BOOST_REQUIRE_EQUAL(1200, r["reserve_wire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(900, r["reserve_chain_amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-02, from-WIRE shape — see applyswap_rejects_debit_above_curve_output. -// The user's escrowed WIRE feeds the WIRE leg directly, so the bound is the -// destination curve's output for the post-fee remainder. -BOOST_FIXTURE_TEST_CASE(applyfromwire_rejects_debit_above_curve_output, - sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("SOLANA", "SOL", "PRIMARY", 1000, 1000)); - - // 200 WIRE in, 0.1% fee floors to 0, so the curve yields - // floor(1000*200/1200) = 166 token. - constexpr int64_t CURVE_OUT = 166; - auto apply = [&](int64_t dst_amount) { - return push_action(UWRIT_ACCOUNT, "applyfromwire"_n, mvo() - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("wire_in", 200) - ("dst_amount", dst_amount) - ("underwriter", "underwriter1")); - }; - - const auto expected = error("assertion failure with message: applyfromwire: " - "destination amount exceeds the curve output for the post-fee WIRE"); - BOOST_REQUIRE_EQUAL(expected, apply(CURVE_OUT + 1)); - BOOST_REQUIRE_EQUAL(expected, apply(900)); - - BOOST_REQUIRE_EQUAL(success(), apply(CURVE_OUT)); - BOOST_REQUIRE_EQUAL(1000 - CURVE_OUT, - find_reserve("SOLANA", "SOL", "PRIMARY")["reserve_chain_amount"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(paywire_pays_real_wire_from_custody, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - BOOST_REQUIRE_EQUAL(1000, wire_balance(RESERVE_ACCOUNT)); - BOOST_REQUIRE_EQUAL(0, wire_balance("alice"_n)); - - // The curve's output for this leg: equal weights reduce to constant product, - // so `token_to_wire(1000, 1000, 100)` = floor(1000*100/1100) = 90, and the - // 0.1% fee floors to 0 at that size, leaving a post-fee 90. `sysio.uwrit` - // passes exactly this (its `swap_quote` returns the post-fee WIRE leg for a - // WIRE destination), and paywire now refuses anything above it (WNS-02). - constexpr int64_t CURVE_OUT = 90; - - // Swap-to-WIRE settlement: source books move + alice is paid REAL WIRE. - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "paywire"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("recipient", "alice") - ("wire_out", CURVE_OUT) - ("underwriter", "underwriter1"))); - - auto r = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE_EQUAL(1100, r["reserve_chain_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(910, r["reserve_wire_amount"].as_uint64()); - - // Custody invariant: `Σ reserve_wire_amount` drops by the payout immediately, but the - // WIRE itself does NOT leave -- it moves sideways into `Σ wireclaims.balance`, still in this - // contract's custody, until the recipient pulls it. paywire credits rather than transfers - // because it settles inside the never-throw consensus dispatch chain. - BOOST_REQUIRE_EQUAL(90u, wire_claimable("alice"_n)); - BOOST_REQUIRE_EQUAL(1000, wire_balance(RESERVE_ACCOUNT)); // unchanged: 910 booked + 90 claimable - BOOST_REQUIRE_EQUAL(0, wire_balance("alice"_n)); - - // The pull completes the settlement and restores the original end state. - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(910, wire_balance(RESERVE_ACCOUNT)); - BOOST_REQUIRE_EQUAL(90, wire_balance("alice"_n)); -} FC_LOG_AND_RETHROW() } - -// [P0] WNS-02: paywire must refuse a payout larger than the WIRE its own curve -// produces for `src_amount`. `wire_out` is a caller-supplied parameter, and the -// audited vulnerability was precisely a caller-chosen destination amount being -// paid out verbatim — so the reserve enforces its own floor rather than trusting -// `sysio.uwrit` to have derived it. -// -// This bound also subsumes the older "insufficient source reserve WIRE for -// payout + fee" guard: `w_gross` is capped at the reserve's own WIRE balance by -// `out_given_in`, so `wire_out + fee <= w_gross <= reserve_wire_amount` holds -// by construction once the payout is bounded. That guard stays in the contract -// as defense-in-depth but is no longer constructible through this action. -BOOST_FIXTURE_TEST_CASE(paywire_rejects_payout_above_curve_output, sysio_reserve_tester) { try { - // 1000 token / 100 WIRE: the curve yields floor(100*100/1100) = 9 WIRE for a - // 100-token source leg, so a 200 payout is ~22x the reserve's own price. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 100)); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: paywire: payout exceeds the post-fee WIRE the source leg produced"), - push_action(UWRIT_ACCOUNT, "paywire"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("recipient", "alice") - ("wire_out", 200) - ("underwriter", "underwriter1"))); - - // The curve's own output settles cleanly against the same reserve. - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "paywire"_n, mvo() - ("src_chain_code", "ETH") - ("src_token_code", "ETH") - ("src_reserve_code", "PRIMARY") - ("src_amount", 100) - ("recipient", "alice") - ("wire_out", 9) - ("underwriter", "underwriter1"))); - BOOST_REQUIRE_EQUAL(9u, wire_claimable("alice"_n)); // credited, not pushed - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(9, wire_balance("alice"_n)); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(refundwire_returns_escrow, sysio_reserve_tester) { try { - // Seed custody via a bootstrap reserve (the refund itself touches no - // reserve row — it returns in-flight escrow). Zero revert fee: a no-fault - // refund returns the full escrow. - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 150) - ("revert_fee_bps", 0))); - - // The refund is CREDITED, not pushed. Until alice pulls it the WIRE stays in this - // contract's custody, so the escrow is owed but has not moved. - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - BOOST_REQUIRE_EQUAL(0, wire_balance("alice"_n)); - BOOST_REQUIRE_EQUAL(1000, wire_balance(RESERVE_ACCOUNT)); - - // Pulling it produces the original settlement: alice holds the escrow, custody drops by it. - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(150, wire_balance("alice"_n)); - BOOST_REQUIRE_EQUAL(850, wire_balance(RESERVE_ACCOUNT)); - BOOST_REQUIRE(get_wireclaim("alice"_n).is_null()); // row consumed by the payout - - auto r = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE_EQUAL(1000, r["reserve_wire_amount"].as_uint64()); // untouched - - BOOST_REQUIRE(get_rewardbkt().is_null()); // no fee — nothing accrued -} FC_LOG_AND_RETHROW() } - -// The retention deadline has to hold WITHOUT further settlement traffic. `credit_wire_claim` -// sweeps opportunistically, so it only fires while swaps keep arriving; if traffic stops, nothing -// revisits an aged-out row and both the system-funded table and the WIRE it reserves stay -// outstanding forever. `sweepclaims` — inlined every epoch by `sysio.epoch::advance` — is the -// trigger that makes the window real, and `claimwire` refuses an expired row so the deadline means -// "claimable until" rather than "swept eventually". -BOOST_FIXTURE_TEST_CASE(expired_wire_claim_is_swept_without_further_traffic, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 150) - ("revert_fee_bps", 0))); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - - const int64_t treasury_before = wire_balance(SYSIO_ACCOUNT); - const int64_t custody_before = wire_balance(RESERVE_ACCOUNT); - - // Age past the one-year window with NO further credits — the opportunistic sweep never runs. - produce_block(); - produce_block(fc::days(366)); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); // still sitting there - - // The deadline is enforced at the claim, so the forfeit balance cannot be pulled. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: claimwire: this claim expired and is pending sweep to the treasury"), - claimwire("alice"_n)); - - // A zero budget is a no-op even with an expired row waiting. - BOOST_REQUIRE_EQUAL(success(), sweepclaims(0)); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - - // The epoch-driven sweep reclaims it with no settlement traffic involved: the row is gone and - // its WIRE has left custody for the treasury. - BOOST_REQUIRE_EQUAL(success(), sweepclaims(reserve_max_claim_sweep_per_epoch)); - BOOST_REQUIRE(get_wireclaim("alice"_n).is_null()); - BOOST_REQUIRE_EQUAL(0u, wire_claimable("alice"_n)); - BOOST_REQUIRE_EQUAL(treasury_before + 150, wire_balance(SYSIO_ACCOUNT)); - BOOST_REQUIRE_EQUAL(custody_before - 150, wire_balance(RESERVE_ACCOUNT)); - - // Nothing to claim afterwards — the balance is forfeit, not merely deferred. - BOOST_REQUIRE_EQUAL(error("assertion failure with message: no claimable WIRE for this account"), - claimwire("alice"_n)); -} FC_LOG_AND_RETHROW() } - -// Crediting an account whose row already expired must not resurrect the forfeited balance. -// `claimable::credit` upserts, so without settling first the old amount would be added to and its -// stamp refreshed — making a balance `claimwire` has been refusing claimable again, and letting a -// trickle of small credits keep a system-funded row alive forever. The new credit legitimately -// starts a fresh window (an account being credited is not abandoned); only the old balance is -// forfeit. -BOOST_FIXTURE_TEST_CASE(recredit_after_expiry_does_not_revive_the_forfeited_balance, - sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 150) - ("revert_fee_bps", 0))); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - - // Age past the window WITHOUT sweeping, so the forfeited row is still sitting there. - produce_block(); - produce_block(fc::days(366)); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - - const int64_t treasury_before = wire_balance(SYSIO_ACCOUNT); - - // A later refund for the same account credits 40. - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 40) - ("revert_fee_bps", 0))); - - // Only the NEW amount is claimable — the forfeited 150 went to the treasury, it did not - // accumulate into a 190 balance with a fresh window. - BOOST_REQUIRE_EQUAL(40u, wire_claimable("alice"_n)); - BOOST_REQUIRE_EQUAL(treasury_before + 150, wire_balance(SYSIO_ACCOUNT)); - - // And the fresh claim is live, so the recipient is not penalised for the new payout. - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(40, wire_balance("alice"_n)); -} FC_LOG_AND_RETHROW() } - -// A LIVE row is untouched by the sweep and still claims normally — the budget walks the -// expiry-ordered index and stops at the first row whose window is open. -BOOST_FIXTURE_TEST_CASE(sweepclaims_leaves_live_rows_alone, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 150) - ("revert_fee_bps", 0))); - - BOOST_REQUIRE_EQUAL(success(), sweepclaims(reserve_max_claim_sweep_per_epoch)); - BOOST_REQUIRE_EQUAL(150u, wire_claimable("alice"_n)); - - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(150, wire_balance("alice"_n)); -} FC_LOG_AND_RETHROW() } - -// A nonzero revert fee (caller-fault drain revert) is taken out of the refund and -// routed exactly like a settlement fee, through `route_wire_fee`. Arithmetic here: -// 10% of 150 = 15, and a revert has no winning underwriter, so the whole 15 is the -// rewards pool. This fixture never calls `setconfig`, so `fee_emissions_share_bps` -// is the default 0 and all 15 accrues to `rewards_bucket` — custody-internal, with -// nothing transferred to the emissions treasury. -BOOST_FIXTURE_TEST_CASE(refundwire_routes_revert_fee, sysio_reserve_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - regreserve("ETH", "ETH", "PRIMARY", 1000, 1000)); - - BOOST_REQUIRE_EQUAL(success(), push_action(UWRIT_ACCOUNT, "refundwire"_n, mvo() - ("recipient", "alice") - ("wire_amount", 150) - ("revert_fee_bps", 1000))); // 10% - - // Credited net of the fee, and the refund waits to be pulled. With the default zero - // emissions dial the whole fee stays in custody as rewards, so nothing leaves at all. - BOOST_REQUIRE_EQUAL(135u, wire_claimable("alice"_n)); // 150 - 15 fee - BOOST_REQUIRE_EQUAL(0, wire_balance("alice"_n)); - BOOST_REQUIRE_EQUAL(1000, wire_balance(RESERVE_ACCOUNT)); // nothing transferred out yet - - BOOST_REQUIRE_EQUAL(success(), claimwire("alice"_n)); - BOOST_REQUIRE_EQUAL(135, wire_balance("alice"_n)); // 150 - 15 fee - BOOST_REQUIRE_EQUAL(865, wire_balance(RESERVE_ACCOUNT)); // 1000 - 135; the fee stays in custody - - // The whole fee, per the header: no underwriter share to carve out, and a - // zero emissions dial leaves the pool intact. A configured dial would divert - // its share to the emissions treasury instead. - auto bkt = get_rewardbkt(); - BOOST_REQUIRE(!bkt.is_null()); - BOOST_REQUIRE_EQUAL(15u, bkt["balance"].as_uint64()); - BOOST_REQUIRE_EQUAL(15u, bkt["lifetime_accrued"].as_uint64()); - BOOST_REQUIRE(get_uwfees(UNDERWRITER_ACCOUNT).is_null()); // nobody underwrote it - - auto r = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE_EQUAL(1000, r["reserve_wire_amount"].as_uint64()); // untouched -} FC_LOG_AND_RETHROW() } - -// ── swapquote ── - -BOOST_FIXTURE_TEST_CASE(swapquote_returns_zero_when_reserve_missing, sysio_reserve_tester) { try { - // No regreserve — the row simply doesn't exist. - auto r = find_reserve("ETH", "ETH", "PRIMARY"); - BOOST_REQUIRE(r.is_null()); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/contracts/tests/sysio.uwrit_tests.cpp b/contracts/tests/sysio.uwrit_tests.cpp deleted file mode 100644 index 7dcb88f57a..0000000000 --- a/contracts/tests/sysio.uwrit_tests.cpp +++ /dev/null @@ -1,471 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include "contracts.hpp" - -using namespace sysio::testing; -using namespace sysio; -using namespace sysio::chain; -using namespace sysio::opp::types; -using namespace fc; -using namespace fc::crypto; - -using mvo = fc::mutable_variant_object; - -namespace { - -} // anonymous namespace - -class sysio_uwrit_tester : public tester { -public: - static constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_n; - static constexpr auto MSGCH_ACCOUNT = "sysio.msgch"_n; - static constexpr auto OPREG_ACCOUNT = "sysio.opreg"_n; - static constexpr auto CHALG_ACCOUNT = "sysio.chalg"_n; - - sysio_uwrit_tester() { - produce_blocks(2); - - create_accounts({ - UWRIT_ACCOUNT, MSGCH_ACCOUNT, OPREG_ACCOUNT, CHALG_ACCOUNT, - "sysio.epoch"_n, "sysio.chains"_n, "sysio.reserv"_n, - "uwrit.a"_n, "uwrit.b"_n - }); - produce_blocks(2); - - set_code(UWRIT_ACCOUNT, contracts::uwrit_wasm()); - set_abi(UWRIT_ACCOUNT, contracts::uwrit_abi().data()); - set_privileged(UWRIT_ACCOUNT); - - produce_blocks(); - - const auto* accnt = control->find_account_metadata(UWRIT_ACCOUNT); - BOOST_REQUIRE(accnt != nullptr); - abi_def abi; - BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt->abi, abi), true); - abi_ser.set_abi(std::move(abi), abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - action_result push_uwrit_action(name signer, name action_name, const variant_object& data) { - string action_type_name = abi_ser.get_action_type(action_name); - - action act; - act.account = UWRIT_ACCOUNT; - act.name = action_name; - act.data = abi_ser.variant_to_binary( - action_type_name, data, - abi_serializer::create_yield_function(abi_serializer_max_time) - ); - act.authorization = vector{{signer, config::active_name}}; - - signed_transaction trx; - trx.actions.emplace_back(std::move(act)); - set_transaction_headers(trx); - trx.sign(get_private_key(signer, "active"), control->get_chain_id()); - try { - push_transaction(trx); - return success(); - } catch (const fc::exception& ex) { - return error(ex.top_message()); - } - } - - action_result setconfig(uint32_t fee_bps = 10, - uint64_t collateral_lock_duration_ms = 43'200'000, - uint64_t min_fromwire_amount = 5'000'000'000ull, - uint32_t fromwire_revert_fee_bps = 10, - uint32_t uwreq_pending_timeout_epochs = 10, - uint32_t uwreq_retention_epochs = 10) { - return push_uwrit_action(UWRIT_ACCOUNT, "setconfig"_n, mvo() - ("fee_bps", fee_bps) - ("collateral_lock_duration_ms", collateral_lock_duration_ms) - ("min_fromwire_amount", min_fromwire_amount) - ("fromwire_revert_fee_bps", fromwire_revert_fee_bps) - ("uwreq_pending_timeout_epochs", uwreq_pending_timeout_epochs) - ("uwreq_retention_epochs", uwreq_retention_epochs) - ); - } - - /// Read uwconfig singleton row. - fc::variant get_uwconfig() { - auto data = get_row_by_account(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwconfig"_n, "uwconfig"_n); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( - "uw_config", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - /// Read a uwreq by id. - fc::variant get_uwreq(uint64_t id) { - auto data = get_row_by_id(UWRIT_ACCOUNT, UWRIT_ACCOUNT, "uwreqs"_n, id); - return data.empty() ? fc::variant() : abi_ser.binary_to_variant( - "uw_request_t", data, - abi_serializer::create_yield_function(abi_serializer_max_time)); - } - - abi_serializer abi_ser; -}; - -// ---- Tests ---- - -BOOST_AUTO_TEST_SUITE(sysio_uwrit_tests) - -BOOST_FIXTURE_TEST_CASE(setconfig_basic, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), setconfig(25)); - - auto cfg = get_uwconfig(); - BOOST_REQUIRE_EQUAL(25, cfg["fee_bps"].as_uint64()); - BOOST_REQUIRE_EQUAL(43'200'000u, cfg["collateral_lock_duration_ms"].as_uint64()); - BOOST_REQUIRE_EQUAL(5'000'000'000u, cfg["min_fromwire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(10, cfg["fromwire_revert_fee_bps"].as_uint64()); - BOOST_REQUIRE_EQUAL(10, cfg["uwreq_pending_timeout_epochs"].as_uint64()); - BOOST_REQUIRE_EQUAL(10, cfg["uwreq_retention_epochs"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_writes_custom_lock_duration, sysio_uwrit_tester) { try { - // Test clusters shorten the 12h challenge window via setconfig — e.g. - // two minutes here. - BOOST_REQUIRE_EQUAL(success(), - setconfig(/*fee_bps*/10, /*lock_ms*/120'000)); - auto cfg = get_uwconfig(); - BOOST_REQUIRE_EQUAL(120'000u, cfg["collateral_lock_duration_ms"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_excessive_fee, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: fee_bps must be below 10000 (100%): a 100% fee zeroes the post-fee WIRE leg"), - setconfig(10001) - ); -} FC_LOG_AND_RETHROW() } - -// SEC-26 / WSA-042: the exact 100% boundary must be rejected. It was previously -// accepted (`fee_bps <= 10000`), which zeroed the post-fee WIRE leg and let a -// swap debit destination reserve liquidity while crediting zero WIRE. -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_full_fee, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: fee_bps must be below 10000 (100%): a 100% fee zeroes the post-fee WIRE leg"), - setconfig(10000) - ); -} FC_LOG_AND_RETHROW() } - -// The largest sub-100% fee (MAX_FEE_BPS) is accepted and leaves a positive -// post-fee WIRE leg for every positive input. -BOOST_FIXTURE_TEST_CASE(setconfig_accepts_max_fee, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), setconfig(9999)); - BOOST_REQUIRE_EQUAL(9999, get_uwconfig()["fee_bps"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// The swap-from-WIRE floor is the queue-slot price: a zero floor would reopen -// free dust rows in the fwqueue, so setconfig rejects it. -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_zero_fromwire_minimum, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: min_fromwire_amount must be positive"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/0) - ); -} FC_LOG_AND_RETHROW() } - -// A 100% revert fee would zero the post-fee refund transfer inside the -// never-throw drainfwq drain, so the boundary is rejected exactly like -// fee_bps. -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_full_revert_fee, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: fromwire_revert_fee_bps must be below 10000 (100%): " - "a 100% revert fee zeroes the refund"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10000) - ); -} FC_LOG_AND_RETHROW() } - -// The largest sub-100% revert fee and a custom floor round-trip through the -// config singleton. -BOOST_FIXTURE_TEST_CASE(setconfig_accepts_max_revert_fee_and_custom_floor, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/1, /*revert_fee_bps*/9999)); - auto cfg = get_uwconfig(); - BOOST_REQUIRE_EQUAL(1u, cfg["min_fromwire_amount"].as_uint64()); - BOOST_REQUIRE_EQUAL(9999, cfg["fromwire_revert_fee_bps"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -// ── SEC-129 / WSA-223: UWREQ lifecycle knobs ── -// -// A zero pending timeout would expire every uwreq the epoch it is created; a -// zero retention would erase terminal rows before the audit window they exist -// for; a near-UINT32_MAX value would wrap the `current_epoch + knob` deadline -// stamp to a tiny epoch index (instant expiry). setconfig rejects all three. - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_zero_pending_timeout, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: uwreq_pending_timeout_epochs must be positive"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/0) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_zero_retention, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: uwreq_retention_epochs must be positive"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/10, /*retention*/0) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_lifecycle_epochs_over_ceiling, sysio_uwrit_tester) { try { - // The ceiling itself is accepted for both knobs; one epoch beyond it is - // rejected before the value could wrap a deadline stamp. - constexpr uint32_t ceiling = 1'000'000; - BOOST_REQUIRE_EQUAL(success(), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/ceiling, /*retention*/ceiling)); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: uwreq_pending_timeout_epochs exceeds the lifecycle ceiling"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/ceiling + 1, /*retention*/ceiling) - ); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: uwreq_retention_epochs exceeds the lifecycle ceiling"), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/ceiling, /*retention*/ceiling + 1) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_roundtrips_lifecycle_epochs, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - setconfig(/*fee_bps*/10, /*lock_ms*/43'200'000, /*min_fromwire*/5'000'000'000ull, - /*revert_fee_bps*/10, /*pending_timeout*/3, /*retention*/7)); - auto cfg = get_uwconfig(); - BOOST_REQUIRE_EQUAL(3, cfg["uwreq_pending_timeout_epochs"].as_uint64()); - BOOST_REQUIRE_EQUAL(7, cfg["uwreq_retention_epochs"].as_uint64()); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_zero_lock_duration, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: collateral_lock_duration_ms must be positive"), - setconfig(/*fee_bps*/10, /*lock_ms*/0) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(setconfig_rejects_excess_lock_duration, sysio_uwrit_tester) { try { - // The one-year ceiling is accepted; one millisecond beyond it is rejected - // before the duration could wrap `now_ms + collateral_lock_duration_ms` and - // release the lock the instant it is placed. - constexpr uint64_t one_year_ms = 365ull * 24 * 60 * 60 * 1000; - BOOST_REQUIRE_EQUAL(success(), setconfig(/*fee_bps*/10, one_year_ms)); - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: collateral_lock_duration_ms exceeds the one-year ceiling"), - setconfig(/*fee_bps*/10, one_year_ms + 1) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(createuwreq_requires_msgch_auth, sysio_uwrit_tester) { try { - // createuwreq must be invoked by sysio.msgch (inline action). A direct - // call from another account (uwrit.a here) is rejected. - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "createuwreq"_n, mvo() - ("attestation_id", 1) - ("type", sysio::opp::types::AttestationType::ATTESTATION_TYPE_SWAP_REQUEST) - ("chain_code", 1) - ("data", std::vector{}) - ).find("missing authority of sysio.msgch") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -// ── chklocks — THE lock-release path (12h wall-clock challenge window) ── -// -// Locks are never released by delivery (there is no SWAP_REMIT ack); the -// epoch-advance sweep is the only releaser. `release`/`expirelock` were -// retired with the dead reflected-remit dispatch. Budget-bounded like its -// sibling epoch-inline sweeps; the bound's BEHAVIOR (one lock per unit of -// budget, backlog draining on the next sweep) needs real locks and lives in -// sysio.dispatch_tests.cpp. - -BOOST_FIXTURE_TEST_CASE(chklocks_requires_epoch_or_self_auth, sysio_uwrit_tester) { try { - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "chklocks"_n, mvo() - ("max_rows", 10) - ).find("chklocks requires sysio.epoch or sysio.uwrit authority") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(chklocks_noop_with_no_locks, sysio_uwrit_tester) { try { - // Steady-state: nothing expired, nothing to sweep — must be a clean - // no-op (it runs inside every epoch advance). - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(UWRIT_ACCOUNT, "chklocks"_n, mvo()("max_rows", 10))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(chklocks_zero_budget_is_noop, sysio_uwrit_tester) { try { - // The budget gate short-circuits before any index walk — mirrors - // pruneuwreqs / drainfwq. - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(UWRIT_ACCOUNT, "chklocks"_n, mvo()("max_rows", 0))); -} FC_LOG_AND_RETHROW() } - -// ── pruneuwreqs — bounded UWREQ lifecycle sweep (SEC-129 / WSA-223) ── -// -// The enforcement trigger for `uw_request_t.expires_at_epoch`. Auth mirrors -// its sibling epoch-inline sweeps; the lifecycle behavior itself (PENDING -// timeout, terminal retention erase, per-epoch budget) needs real epoch -// movement and lives in sysio.dispatch_tests.cpp. - -BOOST_FIXTURE_TEST_CASE(pruneuwreqs_requires_epoch_or_self_auth, sysio_uwrit_tester) { try { - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "pruneuwreqs"_n, mvo() - ("max_rows", 10) - ).find("pruneuwreqs requires sysio.epoch or sysio.uwrit authority") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(pruneuwreqs_noop_with_empty_table, sysio_uwrit_tester) { try { - // Steady-state: nothing due, nothing to sweep — must be a clean no-op - // (it runs inside every epoch advance). - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(UWRIT_ACCOUNT, "pruneuwreqs"_n, mvo()("max_rows", 10))); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(pruneuwreqs_zero_budget_is_noop, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(UWRIT_ACCOUNT, "pruneuwreqs"_n, mvo()("max_rows", 0))); -} FC_LOG_AND_RETHROW() } - -// ── drainfwq — epoch-boundary swap-from-WIRE queue drain ── - -BOOST_FIXTURE_TEST_CASE(drainfwq_requires_epoch_or_self_auth, sysio_uwrit_tester) { try { - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "drainfwq"_n, mvo() - ).find("drainfwq requires sysio.epoch or sysio.uwrit authority") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(drainfwq_noop_with_empty_queue, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(UWRIT_ACCOUNT, "drainfwq"_n, mvo())); -} FC_LOG_AND_RETHROW() } - -// ── swapfromwire — queued depot-originated swap entry ── - -BOOST_FIXTURE_TEST_CASE(swapfromwire_rejects_zero_wire_amount, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: swapfromwire: wire_amount must be positive"), - push_uwrit_action("uwrit.a"_n, "swapfromwire"_n, mvo() - ("user", "uwrit.a") - ("wire_amount", 0) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("target_amount", 100) - ("target_tolerance_bps", 50) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_SVM) - ("recipient_addr", std::vector(32, '\x01')) - ) - ); -} FC_LOG_AND_RETHROW() } - -// One atom below the configured floor is rejected before any chain/reserve -// validation — the floor is the queue-slot price (default 5 WIRE). -BOOST_FIXTURE_TEST_CASE(swapfromwire_rejects_below_minimum, sysio_uwrit_tester) { try { - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: swapfromwire: wire_amount below the configured minimum"), - push_uwrit_action("uwrit.a"_n, "swapfromwire"_n, mvo() - ("user", "uwrit.a") - ("wire_amount", 4'999'999'999ull) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("target_amount", 100) - ("target_tolerance_bps", 50) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_SVM) - ("recipient_addr", std::vector(32, '\x01')) - ) - ); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(swapfromwire_rejects_unregistered_target_chain, sysio_uwrit_tester) { try { - // No sysio.chains rows exist in this fixture — the target chain cannot - // be registered+active, so the escrow never happens. wire_amount sits at - // the default floor so the chain check (not the floor) is what fires. - BOOST_REQUIRE_EQUAL( - error("assertion failure with message: swapfromwire: target chain not " - "registered or not active"), - push_uwrit_action("uwrit.a"_n, "swapfromwire"_n, mvo() - ("user", "uwrit.a") - ("wire_amount", 5'000'000'000ull) - ("dst_chain_code", "SOLANA") - ("dst_token_code", "SOL") - ("dst_reserve_code", "PRIMARY") - ("target_amount", 100) - ("target_tolerance_bps", 50) - ("recipient_kind", sysio::opp::types::ChainKind::CHAIN_KIND_SVM) - ("recipient_addr", std::vector(32, '\x01')) - ) - ); -} FC_LOG_AND_RETHROW() } - -// ── rcrdcommit (Task 3: per-leg COMMIT arrival recorder) ── - -BOOST_FIXTURE_TEST_CASE(rcrdcommit_requires_msgch_auth, sysio_uwrit_tester) { try { - // rcrdcommit is invoked inline from sysio.msgch on UNDERWRITE_INTENT_COMMIT - // dispatch. the signature carries (from_chain_code, from_token_code, - // reserve_code) slug_name triples in place of the old enum pair. - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "rcrdcommit"_n, mvo() - ("uwreq_id", 1) - ("underwriter", "uwrit.a") - ("chain_code", 1) - ("from_chain_code", "ETH") - ("from_token_code", "ETH") - ("reserve_code", "PRIMARY") - ("uic_bytes", std::vector{}) - ).find("missing authority of sysio.msgch") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_FIXTURE_TEST_CASE(rcrdcommit_rejects_unknown_uwreq, sysio_uwrit_tester) { try { - // OPP handlers MUST NEVER throw (feedback_opp_handlers_never_throw.md - // — a `check()` in dispatch halts consensus). The previous error-based - // assertion is gone; the action logs + skips on unknown uwreq and - // returns success. The test now pins THAT invariant. - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action(MSGCH_ACCOUNT, "rcrdcommit"_n, mvo() - ("uwreq_id", 42) - ("underwriter", "uwrit.a") - ("chain_code", 1) - ("from_chain_code", "ETH") - ("from_token_code", "ETH") - ("reserve_code", "PRIMARY") - ("uic_bytes", std::vector{}) - ) - ); -} FC_LOG_AND_RETHROW() } - -// ── sumlocks (Task 3: read-only per-(underwriter, chain, token) lock total) ── - -BOOST_FIXTURE_TEST_CASE(sumlocks_zero_for_unbonded_underwriter, sysio_uwrit_tester) { try { - // sumlocks signature: slug_name pair (chain_code, token_code). - BOOST_REQUIRE_EQUAL(success(), - push_uwrit_action("uwrit.a"_n, "sumlocks"_n, mvo() - ("underwriter", "uwrit.a") - ("chain_code", "ETH") - ("token_code", "ETH") - ) - ); -} FC_LOG_AND_RETHROW() } - -// ── B6: same-chain swap routing on rcrdcommit ────────────────────────── -// -// A swap on a single outpost (e.g. ERC20 → ETH-native, both on ETH) has -// src_chain == dst_chain. The depot routes the source-leg vs dest-leg of -// the COMMIT into commit_entry's source_uic_bytes / dest_uic_bytes slots -// based on (from_chain, from_token_kind) matching the uwreq's -// (src_chain, src_token_kind) vs (dst_chain, dst_token_kind). Without -// the from_token_kind discriminator, same-chain swaps would route both -// legs to the source slot. This case verifies the dispatch still -// auth-checks correctly when the two chains coincide. -BOOST_FIXTURE_TEST_CASE(rcrdcommit_same_chain_swap_auth, sysio_uwrit_tester) { try { - // Slug_name triple disambiguates same-chain swap legs. - BOOST_REQUIRE(push_uwrit_action("uwrit.a"_n, "rcrdcommit"_n, mvo() - ("uwreq_id", 7) - ("underwriter", "uwrit.a") - ("chain_code", 1) - ("from_chain_code", "ETH") // src == dst chain - ("from_token_code", "USDC") // distinguishes legs - ("reserve_code", "PRIMARY") - ("uic_bytes", std::vector{}) - ).find("missing authority of sysio.msgch") != std::string::npos); -} FC_LOG_AND_RETHROW() } - -BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/opp/include/sysio/opp/uic_signature_canonical.hpp b/libraries/opp/include/sysio/opp/uic_signature_canonical.hpp deleted file mode 100644 index ac5b969bad..0000000000 --- a/libraries/opp/include/sysio/opp/uic_signature_canonical.hpp +++ /dev/null @@ -1,148 +0,0 @@ -#pragma once -/** - * @file uic_signature_canonical.hpp - * @brief Host/contract-shared recovery-byte and scalar canonicality policy for - * UIC ECDSA signatures. - */ - -#include -#include -#include -#include - -namespace sysio::opp { - -/// Curve used by one recoverable UIC ECDSA signature variant. -enum class uic_ecdsa_curve { - secp256k1, - p256, -}; - -/// Position of the recovery byte inside a 65-byte compact signature body. -enum class uic_ecdsa_recovery_position { - prefix, - suffix, -}; - -/// Canonical numeric encoding of the recovery byte. -enum class uic_ecdsa_recovery_encoding { - compact, - ethereum, -}; - -namespace detail { - -using uic_scalar = std::array; - -inline constexpr uic_scalar secp256k1_order{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, - 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, - 0xbf, 0xd2, 0x5e, 0x8c, 0xd0, 0x36, 0x41, 0x41, -}; - -inline constexpr uic_scalar secp256k1_half_order{ - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x5d, 0x57, 0x6e, 0x73, 0x57, 0xa4, 0x50, 0x1d, - 0xdf, 0xe9, 0x2f, 0x46, 0x68, 0x1b, 0x20, 0xa0, -}; - -inline constexpr uic_scalar p256_order{ - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, - 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51, -}; - -inline constexpr uic_scalar p256_half_order{ - 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, - 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xde, 0x73, 0x7d, 0x56, 0xd3, 0x8b, 0xcf, 0x42, - 0x79, 0xdc, 0xe5, 0x61, 0x7e, 0x31, 0x92, 0xa8, -}; - -/// Compare a 32-byte big-endian scalar with a fixed 32-byte bound. -inline int compare_uic_scalar(std::span scalar, - const uic_scalar& bound) { - for (size_t i = 0; i < scalar.size(); ++i) { - const auto value = static_cast(scalar[i]); - if (value < bound[i]) return -1; - if (value > bound[i]) return 1; - } - return 0; -} - -/// Return true when a 32-byte big-endian scalar is nonzero. -inline bool is_nonzero_uic_scalar(std::span scalar) { - for (const char byte : scalar) { - if (byte != 0) return true; - } - return false; -} - -} // namespace detail - -/** - * @brief Validate the recovery byte, `r`/`s` scalars, and low-s form. - * - * K1 and R1 use `[recovery || r || s]`; EM uses `[r || s || recovery]`. - * K1/R1 use compact recovery headers 31..34, while EM uses Ethereum recovery - * values 27..30. Accepting the opposite range would create a second byte - * representation that libfc normalizes to the same recovered key. - * - * @param compact_body Exact 65-byte compact signature body, without the variant tag. - * @param curve Curve order governing the scalar fields. - * @param recovery_position Location of the recovery byte in `compact_body`. - * @param recovery_encoding Required numeric recovery-byte representation. - * @return True only for the required recovery range, `0 < r < n`, and - * `0 < s <= n/2`. - */ -inline bool is_canonical_uic_ecdsa_signature( - std::span compact_body, - uic_ecdsa_curve curve, - uic_ecdsa_recovery_position recovery_position, - uic_ecdsa_recovery_encoding recovery_encoding) { - constexpr size_t scalar_size = 32; - constexpr size_t recovery_field_size = 1; - constexpr size_t prefixed_recovery_offset = 0; - constexpr size_t suffixed_recovery_offset = 2 * scalar_size; - constexpr size_t compact_size = - 2 * scalar_size + recovery_field_size; - constexpr uint8_t compact_recovery_base = 31; - constexpr uint8_t ethereum_recovery_base = 27; - constexpr uint8_t recovery_value_count = 4; - if (compact_body.size() != compact_size) return false; - - const size_t recovery_offset = - recovery_position == uic_ecdsa_recovery_position::prefix - ? prefixed_recovery_offset : suffixed_recovery_offset; - const size_t r_offset = - recovery_position == uic_ecdsa_recovery_position::prefix - ? prefixed_recovery_offset + recovery_field_size - : prefixed_recovery_offset; - const size_t s_offset = r_offset + scalar_size; - const std::span r{compact_body.data() + r_offset, scalar_size}; - const std::span s{compact_body.data() + s_offset, scalar_size}; - - const uint8_t recovery_base = - recovery_encoding == uic_ecdsa_recovery_encoding::compact - ? compact_recovery_base : ethereum_recovery_base; - const auto recovery = static_cast(compact_body[recovery_offset]); - if (recovery < recovery_base || - recovery >= recovery_base + recovery_value_count) { - return false; - } - - const auto& order = curve == uic_ecdsa_curve::p256 - ? detail::p256_order : detail::secp256k1_order; - const auto& half_order = curve == uic_ecdsa_curve::p256 - ? detail::p256_half_order : detail::secp256k1_half_order; - - return detail::is_nonzero_uic_scalar(r) - && detail::compare_uic_scalar(r, order) < 0 - && detail::is_nonzero_uic_scalar(s) - && detail::compare_uic_scalar(s, half_order) <= 0; -} - -} // namespace sysio::opp diff --git a/libraries/opp/test/include/sysio/opp/test/uic_signature_test_utils.hpp b/libraries/opp/test/include/sysio/opp/test/uic_signature_test_utils.hpp deleted file mode 100644 index c8a1e68f10..0000000000 --- a/libraries/opp/test/include/sysio/opp/test/uic_signature_test_utils.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - -namespace sysio::opp::test { - -/** - * Replace a canonical recovery byte with libfc's equivalent noncanonical - * alias while preserving the recovery id. - * - * @param packed Canonically packed recoverable signature. - * @param key_type Concrete signing-key variant. - * @return Mutated packed signature, or nullopt for an unsupported type or - * malformed input. - */ -inline std::optional> create_recovery_alias( - std::vector packed, - fc::crypto::private_key::key_type key_type) { - constexpr size_t signature_tag_size = 1; - constexpr size_t scalar_size = detail::uic_scalar{}.size(); - constexpr size_t prefixed_recovery_offset = signature_tag_size; - constexpr size_t suffixed_recovery_offset = - signature_tag_size + 2 * scalar_size; - constexpr uint8_t compact_recovery_base = 31; - constexpr uint8_t ethereum_recovery_base = 27; - constexpr uint8_t recovery_value_count = 4; - - size_t recovery_offset = 0; - uint8_t recovery_base = 0; - int alias_delta = 0; - using key_variant = fc::crypto::private_key::key_type; - switch (key_type) { - case key_variant::k1: - case key_variant::r1: - recovery_offset = prefixed_recovery_offset; - recovery_base = compact_recovery_base; - alias_delta = -static_cast(recovery_value_count); - break; - case key_variant::em: - recovery_offset = suffixed_recovery_offset; - recovery_base = ethereum_recovery_base; - alias_delta = recovery_value_count; - break; - default: - return std::nullopt; - } - - if (recovery_offset >= packed.size()) return std::nullopt; - const auto recovery = static_cast(packed[recovery_offset]); - if (recovery < recovery_base || - recovery >= recovery_base + recovery_value_count) { - return std::nullopt; - } - packed[recovery_offset] = static_cast( - static_cast(recovery) + alias_delta); - return packed; -} - -/** - * Convert a canonically packed low-s K1, R1, or EM signature into its - * cryptographically equivalent high-s form. - * - * @param packed Canonically packed recoverable signature. - * @param key_type Concrete signing-key variant. - * @return Mutated packed signature, or nullopt for an unsupported type or - * malformed input. - */ -inline std::optional> create_high_s_alternate( - std::vector packed, - fc::crypto::private_key::key_type key_type) { - constexpr size_t signature_tag_size = 1; - constexpr size_t recovery_field_size = 1; - constexpr size_t scalar_size = detail::uic_scalar{}.size(); - constexpr size_t prefixed_recovery_offset = signature_tag_size; - constexpr size_t prefixed_s_offset = - signature_tag_size + recovery_field_size + scalar_size; - constexpr size_t suffixed_s_offset = signature_tag_size + scalar_size; - constexpr size_t suffixed_recovery_offset = - signature_tag_size + 2 * scalar_size; - constexpr uint8_t compact_recovery_base = 31; - constexpr uint8_t ethereum_recovery_base = 27; - - size_t s_offset = 0; - size_t recovery_offset = 0; - uint8_t recovery_base = 0; - const detail::uic_scalar* order = nullptr; - using key_variant = fc::crypto::private_key::key_type; - switch (key_type) { - case key_variant::k1: - s_offset = prefixed_s_offset; - recovery_offset = prefixed_recovery_offset; - recovery_base = compact_recovery_base; - order = &detail::secp256k1_order; - break; - case key_variant::r1: - s_offset = prefixed_s_offset; - recovery_offset = prefixed_recovery_offset; - recovery_base = compact_recovery_base; - order = &detail::p256_order; - break; - case key_variant::em: - s_offset = suffixed_s_offset; - recovery_offset = suffixed_recovery_offset; - recovery_base = ethereum_recovery_base; - order = &detail::secp256k1_order; - break; - default: - return std::nullopt; - } - - if (s_offset + scalar_size > packed.size() || recovery_offset >= packed.size()) { - return std::nullopt; - } - - detail::uic_scalar high_s{}; - unsigned borrow = 0; - for (size_t offset = 0; offset < scalar_size; ++offset) { - const size_t i = scalar_size - 1 - offset; - const unsigned minuend = (*order)[i]; - const unsigned subtrahend = - static_cast(packed[s_offset + i]) + borrow; - if (minuend < subtrahend) { - high_s[i] = static_cast(minuend + 256u - subtrahend); - borrow = 1; - } else { - high_s[i] = static_cast(minuend - subtrahend); - borrow = 0; - } - } - if (borrow != 0) return std::nullopt; - - const auto recovery = static_cast(packed[recovery_offset]); - if (recovery < recovery_base) return std::nullopt; - std::copy(high_s.begin(), high_s.end(), packed.begin() + s_offset); - packed[recovery_offset] = static_cast( - recovery_base + ((recovery - recovery_base) ^ 1u)); - return packed; -} - -} // namespace sysio::opp::test diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0125f3c31f..557517a8f7 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -25,6 +25,5 @@ add_subdirectory(outpost_ethereum_client_plugin) add_subdirectory(outpost_solana_client_plugin) add_subdirectory(batch_operator_plugin) add_subdirectory(external_debugging_plugin) -add_subdirectory(underwriter_plugin) add_subdirectory(status_monitor_plugin) add_subdirectory(query_engine_plugin) diff --git a/plugins/batch_operator_plugin/test/mocks/mock_outpost_client.hpp b/plugins/batch_operator_plugin/test/mocks/mock_outpost_client.hpp index 57a5f7d669..94babd52b5 100644 --- a/plugins/batch_operator_plugin/test/mocks/mock_outpost_client.hpp +++ b/plugins/batch_operator_plugin/test/mocks/mock_outpost_client.hpp @@ -49,12 +49,6 @@ class mock_outpost_client : public sysio::outpost_client { _chain_id); } - struct commit_call { - uint64_t uw_request_id = 0; - std::vector uic_bytes; - fc::microseconds deadline; - }; - /// Deliver response — can be set to either a scripted string or a functor /// that produces responses / throws per-call. std::function deliver_response = @@ -63,11 +57,6 @@ class mock_outpost_client : public sysio::outpost_client { std::function(const inbound_call&)> inbound_response = [](const inbound_call&) { return std::vector{}; }; - /// uw_commit response — scripted per call; tests that don't exercise the - /// UIC relay path can leave the default in place. - std::function commit_response = - [](const commit_call&) { return std::string{"mock-commit-tx"}; }; - std::string deliver_outbound_envelope(uint32_t epoch_index, const std::vector& envelope_bytes, fc::microseconds deadline) override { @@ -89,17 +78,6 @@ class mock_outpost_client : public sysio::outpost_client { return inbound_response(call); } - std::string uw_commit(uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) override { - commit_call call{uw_request_id, uic_bytes, deadline}; - { - std::lock_guard lock(_mx); - commit_calls.push_back(call); - } - return commit_response(call); - } - struct crank_call { uint32_t epoch_index = 0; fc::microseconds deadline; @@ -119,7 +97,7 @@ class mock_outpost_client : public sysio::outpost_client { std::vector outbound_calls; std::vector inbound_calls; - std::vector commit_calls; + std::vector crank_calls; std::vector caller_address; diff --git a/plugins/http_plugin/include/sysio/http_plugin/api_category.hpp b/plugins/http_plugin/include/sysio/http_plugin/api_category.hpp index 5afab03177..c51c3c6c01 100644 --- a/plugins/http_plugin/include/sysio/http_plugin/api_category.hpp +++ b/plugins/http_plugin/include/sysio/http_plugin/api_category.hpp @@ -17,7 +17,6 @@ enum class api_category : uint32_t { prometheus = 1 << 9, test_control = 1 << 10, snapshot_ro = 1 << 11, // public read-only snapshot metadata + download - underwriter = 1 << 12, // read-only underwriter diagnostics (stats/commits) node = UINT32_MAX }; @@ -30,10 +29,10 @@ class api_category_set { for (auto c: l) insert(c); } - constexpr bool contains(api_category category) const { + constexpr bool contains(api_category category) const { return sysio::chain::has_field(data, category); } - constexpr void insert(api_category category) { + constexpr void insert(api_category category) { data = sysio::chain::set_field(data, category, true); } @@ -46,4 +45,4 @@ class api_category_set { } }; -} \ No newline at end of file +} diff --git a/plugins/http_plugin/src/http_plugin.cpp b/plugins/http_plugin/src/http_plugin.cpp index 6b584ec715..9db99aec7a 100644 --- a/plugins/http_plugin/src/http_plugin.cpp +++ b/plugins/http_plugin/src/http_plugin.cpp @@ -75,7 +75,6 @@ namespace sysio { if (name == "prometheus") return api_category::prometheus; if (name == "test_control") return api_category::test_control; if (name == "snapshot_ro") return api_category::snapshot_ro; - if (name == "underwriter") return api_category::underwriter; return api_category::unknown; } @@ -92,9 +91,8 @@ namespace sysio { if (category == api_category::prometheus) return "prometheus"; if (category == api_category::test_control) return "test_control"; if (category == api_category::snapshot_ro) return "snapshot_ro"; - if (category == api_category::underwriter) return "underwriter"; if (category == api_category::node) return "node"; - // It's a programming error when the control flow reaches this point, + // It's a programming error when the control flow reaches this point, // please make sure all the category names are returned from above statements. assert(false && "No correspding category name for the category value"); return ""; @@ -118,8 +116,6 @@ namespace sysio { return "sysio::producer_api_plugin"; if (category == api_category::snapshot_ro) return "sysio::snapshot_api_plugin"; - if (category == api_category::underwriter) - return "sysio::underwriter_plugin"; // It's a programming error when the control flow reaches this point, // please make sure all the plugin names are returned from above statements. assert(false && "No corresponding plugin for the category value"); @@ -129,7 +125,7 @@ namespace sysio { std::string category_names(api_category_set set) { if (set == api_category_set::all()) return "all"; std::string result; - for (uint32_t i = 1; i <= static_cast(api_category::underwriter); i<<=1) { + for (uint32_t i = 1; i <= static_cast(api_category::snapshot_ro); i<<=1) { if (set.contains(api_category(i))) { result += from_category(api_category(i)); result += " "; @@ -153,7 +149,6 @@ namespace sysio { std::shared_ptr plugin_state{new http_plugin_state(logger())}; std::atomic listening; - /** * Make an internal_url_handler that will run the url_handler on the app() thread and then * return to the http thread pool for response processing @@ -227,7 +222,7 @@ namespace sysio { }; return handler; } - + bool is_unix_socket_address(const std::string& address) const { return address.starts_with("/") || address.starts_with("./") || address.starts_with("../"); } @@ -252,7 +247,7 @@ namespace sysio { void create_listener(const std::string& address, api_category_set categories) { const std::chrono::milliseconds accept_timeout(500); auto extra_listening_log_info = " for API categories: " + category_names(categories); - using socket_type = typename Protocol::socket; + using socket_type = typename Protocol::socket; auto create_session = [this, categories, address](socket_type&& socket) { std::string remote_endpoint; if constexpr (std::is_same_v) { @@ -335,16 +330,16 @@ namespace sysio { if (current_http_plugin_defaults.support_categories) { cfg.add_options() - ("http-category-address", bpo::value>(), + ("http-category-address", bpo::value>(), "The local IP and port to listen for incoming http category connections." " Syntax: category,address\n" " Where the address can be :port, :port or unix socket path;\n" " in addition, unix socket path must starts with '/', './' or '../'. When relative path\n" " is used, it is relative to the data path.\n\n" " Valid categories include chain_ro, chain_rw, db_size, net_ro, net_rw, producer_ro\n" - " producer_rw, snapshot, trace_api, prometheus, test_control, snapshot_ro, and underwriter.\n\n" - " A single `hostname:port` specification can be used by multiple categories\n" - " However, two specifications having the same port with different hostname strings\n" + " producer_rw, snapshot, trace_api, prometheus, test_control, and snapshot_ro.\n\n" + " A single `hostname:port` specification can be used by multiple categories\n" + " However, two specifications having the same port with different hostname strings\n" " are always considered as configuration error regardless of whether they can be resolved\n" " into the same set of IP addresses.\n\n" " Examples:\n" @@ -462,7 +457,7 @@ namespace sysio { if (unix_sock_path.size()) { if (unix_sock_path[0] != '/') unix_sock_path = "./" + unix_sock_path; my->categories_by_address[unix_sock_path].insert(api_category::node); - } + } } if (options.count("http-category-address") != 0) { @@ -486,10 +481,10 @@ namespace sysio { auto category_name = spec.substr(0, comma_pos); auto category = to_category(category_name); - SYS_ASSERT(category != api_category::unknown, chain::plugin_config_exception, + SYS_ASSERT(category != api_category::unknown, chain::plugin_config_exception, "invalid category name `{}` for http_category_address", category_name); - SYS_ASSERT(has_plugin(category_plugin_name(category)), chain::plugin_config_exception, + SYS_ASSERT(has_plugin(category_plugin_name(category)), chain::plugin_config_exception, "--plugin={} is required for --http-category-address={}", category_plugin_name(category), spec); @@ -507,7 +502,6 @@ namespace sysio { } my->plugin_state->server_header = current_http_plugin_defaults.server_header; - //watch out for the returns above when adding new code here } FC_LOG_AND_RETHROW() } diff --git a/plugins/http_plugin/test/unit_tests.cpp b/plugins/http_plugin/test/unit_tests.cpp index 248cd3d31e..f1dbd67ba6 100644 --- a/plugins/http_plugin/test/unit_tests.cpp +++ b/plugins/http_plugin/test/unit_tests.cpp @@ -42,7 +42,7 @@ constexpr uint32_t category_ro_index = 2; constexpr uint32_t bytes_in_flight_index = 3; constexpr uint32_t requests_in_flight_index = 4; constexpr uint32_t request_body_bytes_in_flight_index = 5; -constexpr uint32_t category_uw_index = 6; +constexpr uint32_t category_snapshot_index = 6; constexpr uint32_t plaintext_response_index = 7; // Keeps unsharded IPv6 probe coverage on the historical port 9999. constexpr uint32_t ipv6_probe_index = 2; @@ -125,7 +125,6 @@ struct ProtocolCommon Results& results; }; - template struct BasicProtocol : public ProtocolCommon { @@ -280,7 +279,8 @@ class producer_api_plugin : public appbase::plugin { void plugin_shutdown() {} }; -class underwriter_plugin : public appbase::plugin { +// Minimal registration fixture for the retained snapshot HTTP category. +class snapshot_api_plugin : public appbase::plugin { public: APPBASE_PLUGIN_REQUIRES(); virtual void set_program_options(options_description& cli, options_description& cfg) override {} @@ -292,7 +292,7 @@ class underwriter_plugin : public appbase::plugin { static auto _chain_api_plugin = application::register_plugin(); static auto _net_api_plugin = application::register_plugin(); static auto _producer_api_plugin = application::register_plugin(); -static auto _underwriter_plugin = application::register_plugin(); +static auto _snapshot_api_plugin = application::register_plugin(); } // namespace sysio struct http_plugin_test_fixture { @@ -448,10 +448,10 @@ BOOST_AUTO_TEST_CASE(invalid_category_addresses) { "http-category-address", "--http-category-address", chain_ro_localhost.c_str()}) .contains("--plugin=sysio::chain_api_plugin is required")); - const std::string underwriter_localhost = "underwriter," + localhost_rw; + const std::string snapshot_localhost = "snapshot_ro," + localhost_rw; BOOST_TEST(app_log({test_name, "--plugin=sysio::chain_api_plugin", "--http-server-address", - "http-category-address", "--http-category-address", underwriter_localhost.c_str()}) - .contains("--plugin=sysio::underwriter_plugin is required")); + "http-category-address", "--http-category-address", snapshot_localhost.c_str()}) + .contains("--plugin=sysio::snapshot_api_plugin is required")); // The test_control endpoints are registered by test_control_api_plugin, so that is the plugin the category needs. const std::string test_control_localhost = "test_control," + localhost_rw; @@ -560,12 +560,12 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { const std::string ro_localhost = test_http_endpoint("localhost", category_ro_index); const std::string rw_loopback = test_http_endpoint("127.0.0.1", category_rw_index); const std::string rw_any = ":" + rw_port; - const std::string uw_loopback = test_http_endpoint("127.0.0.1", category_uw_index); + const std::string snapshot_loopback = test_http_endpoint("127.0.0.1", category_snapshot_index); const std::string chain_ro = "chain_ro," + ro_loopback; const std::string chain_rw = "chain_rw," + rw_any; const std::string net_ro = "net_ro," + ro_loopback; const std::string net_rw = "net_rw," + rw_any; - const std::string underwriter = "underwriter," + uw_loopback; + const std::string snapshot = "snapshot_ro," + snapshot_loopback; const std::string ipv6_rw = "[::1]:" + rw_port; // clang-format off @@ -574,13 +574,13 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { "--plugin=sysio::chain_api_plugin", "--plugin=sysio::net_api_plugin", "--plugin=sysio::producer_api_plugin", - "--plugin=sysio::underwriter_plugin", + "--plugin=sysio::snapshot_api_plugin", "--http-server-address", "http-category-address", "--http-category-address", chain_ro.c_str(), "--http-category-address", chain_rw.c_str(), "--http-category-address", net_ro.c_str(), "--http-category-address", net_rw.c_str(), - "--http-category-address", underwriter.c_str(), + "--http-category-address", snapshot.c_str(), "--http-category-address", "producer_ro,./producer_ro.sock", "--http-category-address", "producer_rw,../producer_rw.sock" }); @@ -616,7 +616,7 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { [&](string&&, string&& body, url_response_callback&& cb) { cb(200, fc::variant("world!")); }}, - {std::string("/v1/underwriter/hello"), api_category::underwriter, + {std::string("/v1/snapshot/hello"), api_category::snapshot_ro, [&](string&&, string&& body, url_response_callback&& cb) { cb(200, fc::variant("world!")); }}}, @@ -626,7 +626,7 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { BOOST_CHECK(http_plugin->is_on_loopback(api_category::net_ro)); BOOST_CHECK(http_plugin->is_on_loopback(api_category::producer_ro)); BOOST_CHECK(http_plugin->is_on_loopback(api_category::producer_rw)); - BOOST_CHECK(http_plugin->is_on_loopback(api_category::underwriter)); + BOOST_CHECK(http_plugin->is_on_loopback(api_category::snapshot_ro)); BOOST_CHECK(!http_plugin->is_on_loopback(api_category::chain_rw)); BOOST_CHECK(!http_plugin->is_on_loopback(api_category::net_rw)); @@ -662,13 +662,13 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/chain_rw/hello").body(), world_string); BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/net_rw/hello").body(), world_string); - // The dedicated underwriter category serves its own listener (where node + // The dedicated snapshot category serves its own listener (where node // handlers stay reachable, node being all categories) and is NOT served // by other category-isolated listeners. - BOOST_CHECK_EQUAL(http_response_for(uw_loopback.c_str(), "/v1/underwriter/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for(uw_loopback.c_str(), "/v1/node/hello").body(), world_string); - BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/underwriter/hello").status(), http::status::not_found); - BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/underwriter/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(snapshot_loopback.c_str(), "/v1/snapshot/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(snapshot_loopback.c_str(), "/v1/node/hello").body(), world_string); + BOOST_CHECK_EQUAL(http_response_for(ro_loopback.c_str(), "/v1/snapshot/hello").status(), http::status::not_found); + BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/snapshot/hello").status(), http::status::not_found); BOOST_CHECK_EQUAL(http_response_for(data_dir / "./producer_ro.sock", "/v1/producer_ro/hello").body(), world_string); BOOST_CHECK_EQUAL(http_response_for(data_dir / "../producer_rw.sock", "/v1/producer_rw/hello").body(), world_string); @@ -679,11 +679,10 @@ BOOST_FIXTURE_TEST_CASE(valid_category_addresses, http_plugin_test_fixture) { BOOST_CHECK_EQUAL(http_response_for(rw_loopback.c_str(), "/v1/node/get_supported_apis").body(), R"({"apis":["/v1/chain_rw/hello","/v1/net_rw/hello","/v1/node/hello"]})"); - BOOST_CHECK_EQUAL(http_response_for(uw_loopback.c_str(), "/v1/node/get_supported_apis").body(), - R"({"apis":["/v1/node/hello","/v1/underwriter/hello"]})"); + BOOST_CHECK_EQUAL(http_response_for(snapshot_loopback.c_str(), "/v1/node/get_supported_apis").body(), + R"({"apis":["/v1/node/hello","/v1/snapshot/hello"]})"); } - bool on_loopback(std::initializer_list args){ appbase::scoped_app app; BOOST_REQUIRE(app->initialize(args.size(), const_cast(args.begin()))); @@ -771,7 +770,6 @@ BOOST_FIXTURE_TEST_CASE(bytes_in_flight, http_plugin_test_fixture) { BOOST_CHECK(max > 0); }; - //send a single request to start with send_4mb_requests(1u); std::unordered_map r = drain_http_replies(); @@ -929,7 +927,6 @@ BOOST_FIXTURE_TEST_CASE(requests_in_flight, http_plugin_test_fixture) { BOOST_CHECK(max > 0); }; - //8 requests to start with send_requests(8u); std::unordered_map r = scan_http_replies(); 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 1306111b1d..7fcd75cd05 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 @@ -129,43 +129,6 @@ class outpost_client { virtual std::vector read_inbound_envelope(uint32_t epoch_index, fc::microseconds deadline) = 0; - /** - * @brief UNDERWRITER COMMIT — submit a signed `UnderwriteIntentCommit` - * (UIC) through an ACTIVE-role-gated outpost relay. - * - * Called by the underwriter plugin (or any future plugin that issues - * outpost-side commits) to deliver a signed intent without the caller - * knowing the outpost's contract surface, ABI / IDL layout, or message - * encoding. The chain-specific concrete resolves which contract or - * program action to invoke, how to encode the bytes for the wire, and - * how to await on-chain confirmation. The current outposts accept only a - * canonically encoded UIC whose claimed WIRE account and external address - * match the authenticated caller's current ACTIVE underwriter roster row. - * They queue the original validated bytes unchanged. The WIRE depot remains - * authoritative for validating the embedded permission signature and bond. - * - * Returns only after on-chain inclusion + confirmations — the caller - * uses the return value as a "this leg landed" signal before recording - * the commit locally. Late-arriving commits (after consensus has already - * been reached for the underlying envelope) are benign no-ops on the - * outpost side per `opp-consensus.md`; they still confirm here. - * - * @param uw_request_id The depot's `sysio.uwrit::uwreqs` row id this - * UIC is committing to. Used only for log - * correlation; the on-chain call carries the original - * validated UIC bytes. - * @param uic_bytes Serialized `UnderwriteIntentCommit` (protobuf - * encoded, signed by an authorized WIRE K1, R1, - * EM, or ED permission key). - * @param deadline Upper bound on the total time spent talking to - * the remote chain for this call. - * @return Chain-native tx id / signature suitable for logs. - * @throws fc::exception on RPC failure, tx revert, or deadline expiry. - */ - virtual std::string uw_commit(uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) = 0; - /** * @brief OUTPOST CRANKS — drive the outpost's permissionless per-epoch * instructions that nothing on the outpost schedules. diff --git a/plugins/outpost_client_plugin/test/test_outpost_client_interface.cpp b/plugins/outpost_client_plugin/test/test_outpost_client_interface.cpp index 9e1d145ca7..bed0a34a59 100644 --- a/plugins/outpost_client_plugin/test/test_outpost_client_interface.cpp +++ b/plugins/outpost_client_plugin/test/test_outpost_client_interface.cpp @@ -34,9 +34,6 @@ class minimal_outpost_client : public sysio::outpost_client { const std::vector&, fc::microseconds) override { return {}; } std::vector read_inbound_envelope(uint32_t, fc::microseconds) override { return {}; } - std::string uw_commit(uint64_t, - const std::vector&, - fc::microseconds) override { return {}; } private: sysio::opp::types::ChainKind _kind; 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 b0e89cacb3..3cc05cad59 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 @@ -122,30 +122,6 @@ struct syndication_pool_contract_client : ethereum_contract_client { , realize_yield(create_tx_and_confirm(get_abi("realizeYield"))) {} }; -/// Typed contract client for OperatorRegistry.sol. Carries the actions -/// plugins reach for outside the OPP envelope path — today `commit` -/// (underwriter UIC relay); future deposit / withdraw / slash actions -/// land here as additional `ethereum_contract_tx_fn` members. -/// -/// State-changing actions use `create_tx_and_confirm` so the call -/// returns only after on-chain inclusion + confirmations — the caller -/// uses the return as a "this leg landed" signal before recording the -/// action locally. -struct operator_registry_contract_client : ethereum_contract_client { - /// `commit(bytes uicBytes)` — submits the original canonical - /// `UnderwriteIntentCommit` bytes. OperatorRegistry binds their signed EVM - /// caller and claimed ACTIVE roster identity before queuing the unchanged - /// bytes. The hardhat-generated ABI passes the parameter as a hex-encoded - /// string (per `ethereum_abi::encode_dynamic_data` for `dt::bytes`). - ethereum_contract_tx_fn commit; - - operator_registry_contract_client(const ethereum_client_ptr& client, - const address_compat_type& contract_address, - const std::vector& contracts) - : ethereum_contract_client(client, contract_address, contracts) - , commit(create_tx_and_confirm(get_abi("commit"))) {} -}; - class outpost_ethereum_client_plugin : public appbase::plugin { public: APPBASE_PLUGIN_REQUIRES((outpost_client_plugin)(signature_provider_manager_plugin)) @@ -197,12 +173,10 @@ class outpost_ethereum_client_plugin : public appbase::plugin my; }; - } // namespace sysio diff --git a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp index f3beeb355d..9cc45ba5c4 100644 --- a/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp +++ b/plugins/outpost_ethereum_client_plugin/include/sysio/outpost_ethereum_client_plugin/outpost_ethereum_client.hpp @@ -177,7 +177,6 @@ class outpost_ethereum_client : public outpost_client { outpost_ethereum_client(ethereum_client_entry_ptr entry, std::string opp_addr, std::string opp_inbound_addr, - std::string operator_registry_addr, std::vector abis, uint64_t chain_code, uint32_t chain_id); @@ -196,10 +195,6 @@ class outpost_ethereum_client : public outpost_client { std::vector read_inbound_envelope(uint32_t epoch_index, fc::microseconds deadline) override; - std::string uw_commit(uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) override; - /// The Ethereum crank: `SyndicationPool.realizeYield()` on the pool the /// outpost registers as its `DESYNDICATE_LIQ` handler. Idle, at debug level, /// while the ABI set carries no `realizeYield` (a deployment that predates @@ -211,7 +206,6 @@ class outpost_ethereum_client : public outpost_client { const ethereum_client_entry_ptr& entry() const { return _entry; } const std::string& opp_address() const { return _opp_addr; } const std::string& opp_inbound_address() const { return _opp_inbound_addr; } - const std::string& operator_registry_address() const { return _operator_registry_addr; } /// This relay's own signer address in `0x`-hex — the identity every staging /// header is bound to. Derived once at construction; the chunk resume path /// compares it against `envelopeChunkState`'s `owner` on every multi-chunk @@ -264,10 +258,10 @@ class outpost_ethereum_client : public outpost_client { ethereum_client_entry_ptr _entry; std::string _opp_addr; std::string _opp_inbound_addr; - std::string _operator_registry_addr; + std::shared_ptr _opp_client; std::shared_ptr _opp_inbound_client; - std::shared_ptr _operator_registry_client; // nullable + // nullable /// The plugin's loaded ABI set, kept for the wrapper bound after construction. std::vector _abis; /// See `syndication_pool_address()` / `bind_syndication_pool`. 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 e480104ef4..9d6f34b829 100644 --- a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp +++ b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client.cpp @@ -28,7 +28,7 @@ namespace detail = outpost_ethereum_client_detail; // ── Op labels used for deadline-exceeded error messages ────────────────── constexpr std::string_view OP_DELIVER_OUTBOUND = "deliver_outbound_envelope"; constexpr std::string_view OP_READ_INBOUND = "read_inbound_envelope"; -constexpr std::string_view OP_UW_COMMIT = "uw_commit"; + constexpr std::string_view OP_REALIZE_YIELD = "crank_outpost:realizeYield"; /// Execution APIs code for a call the node executed and that reverted, as distinct from a @@ -267,14 +267,12 @@ outpost_ethereum_client::outpost_ethereum_client( ethereum_client_entry_ptr entry, std::string opp_addr, std::string opp_inbound_addr, - std::string operator_registry_addr, std::vector abis, uint64_t chain_code, uint32_t chain_id) : _entry(std::move(entry)) , _opp_addr(std::move(opp_addr)) , _opp_inbound_addr(std::move(opp_inbound_addr)) - , _operator_registry_addr(std::move(operator_registry_addr)) , _abis(std::move(abis)) , _outpost_id(chain_code) , _chain_id(chain_id) { @@ -296,10 +294,6 @@ outpost_ethereum_client::outpost_ethereum_client( _opp_inbound_client = _entry->client->get_contract(_opp_inbound_addr, _abis); } - if (!_operator_registry_addr.empty()) { - _operator_registry_client = - _entry->client->get_contract(_operator_registry_addr, _abis); - } // Every OPPInbound staging header is bound to the delivering signer, so the // chunk-resume read needs this relay's own address on every multi-chunk @@ -692,40 +686,6 @@ std::vector outpost_ethereum_client::read_inbound_envelope( return out; } -std::string outpost_ethereum_client::uw_commit( - uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) { - const auto deadline_abs = fc::time_point::now() + deadline; - fc::task::deadline_scope rpc_deadline(deadline_abs); - - throw_if_past_deadline(deadline_abs, OP_UW_COMMIT); - - FC_ASSERT(_operator_registry_client, - "outpost_ethereum_client[{}]: uw_commit requires an OperatorRegistry " - "address — pass operator_registry_addr to create_outpost_client", - to_string()); - - // Solidity `commit(bytes uicBytes)` takes a `bytes` parameter; the - // libfc ABI encoder for `dt::bytes` expects a hex-encoded string - // (see ethereum_abi.cpp::encode_dynamic_data). Building the variant - // around the raw `std::vector` triggers an `fc::bad_cast` - // inside the encoder — the typed wrapper takes the hex form directly. - // - // The `ethereum_contract_tx_fn` signature - // binds the argument as a non-const `std::string&`, so the local - // must be a non-const lvalue (mirroring the `epoch_in(envelope_hex)` - // pattern in `deliver_outbound_envelope`). - std::string uic_hex = std::string("0x") + - fc::to_hex(uic_bytes.data(), uic_bytes.size()); - - const auto result = _operator_registry_client->commit(uic_hex); - const auto tx_hash = result.as_string(); - ilog("outpost_ethereum_client[{}]: uw_commit confirmed uwreq={} tx_hash={} bytes={}", - to_string(), uw_request_id, tx_hash, uic_bytes.size()); - return tx_hash; -} - void outpost_ethereum_client::bind_syndication_pool( std::string address, std::shared_ptr client) { FC_ASSERT(client, "outpost_ethereum_client[{}]: bind_syndication_pool needs a wrapper", to_string()); diff --git a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client_plugin.cpp b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client_plugin.cpp index 250f9a14e6..4787802b75 100644 --- a/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client_plugin.cpp +++ b/plugins/outpost_ethereum_client_plugin/src/outpost_ethereum_client_plugin.cpp @@ -542,8 +542,7 @@ outpost_ethereum_client_plugin::create_outpost_client(const std::string& eth_cli uint64_t chain_code, uint32_t chain_id, const std::string& opp_addr, - const std::string& opp_inbound_addr, - const std::string& operator_registry_addr) { + const std::string& opp_inbound_addr) { const auto entry = my->get_client(eth_client_id); FC_ASSERT(entry, "Unknown ethereum client id: {}", eth_client_id); const auto chain_name = fc::slug_name{chain_code}.to_string(); @@ -564,7 +563,6 @@ outpost_ethereum_client_plugin::create_outpost_client(const std::string& eth_cli return std::make_shared(entry, opp_addr, opp_inbound_addr, - operator_registry_addr, std::move(all_abis), chain_code, chain_id); diff --git a/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp b/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp index 3e423fa235..0f8eced462 100644 --- a/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp +++ b/plugins/outpost_ethereum_client_plugin/test/test_ethereum_transaction_policy.cpp @@ -507,14 +507,6 @@ BOOST_AUTO_TEST_CASE(all_typed_write_wrappers_share_the_policy_enforced_path) { [&] { inbound.epoch_in(epoch_index, chunk_index, total_chunks, total_bytes, chunk); }); expect_policy_rejection([&] { inbound.discard_envelope_chunks(); }); - sysio::operator_registry_contract_client registry{ - client, - std::string(contract_address), - {bytes_argument_function("commit")}, - }; - std::string commitment = "01"; - expect_policy_rejection([&] { registry.commit(commitment); }); - sysio::opp_contract_client opp{ client, std::string(contract_address), @@ -734,7 +726,6 @@ BOOST_AUTO_TEST_CASE(outpost_factory_reports_client_configuration_chain_mismatch chain_code, 1, std::string(contract_address), - std::string(contract_address), std::string(contract_address)); BOOST_FAIL("expected client/outpost chain mismatch rejection"); } catch (const sysio::chain::plugin_config_exception& rejection) { diff --git a/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp b/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp index 6ac2d7f0a6..5462a0234b 100644 --- a/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp +++ b/plugins/outpost_ethereum_client_plugin/test/test_outpost_ethereum_client_plugin.cpp @@ -6,7 +6,6 @@ #include #include - #include #include #include @@ -88,7 +87,6 @@ std::vector test_tx_01_unsigned_result{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0xc0 }; - std::string test_tx_01_r = "93166a3ed10a4050dce7261c4ca8bcba16a1731117c453a326a1742c959b33f0"; std::string test_tx_01_s = "7c17a232cd69ce93f21a30579a2a94309b2d71918043134b4c5df5788078a0e4"; fc::uint256 test_tx_01_v = 0; @@ -575,7 +573,6 @@ std::unique_ptr create_chunked_delivery_fixture() { stack.entry, /*opp_addr=*/std::string{}, inbound_address, - /*operator_registry_addr=*/std::string{}, abis, test_outpost_chain_code, test_evm_chain_id); @@ -716,7 +713,6 @@ std::unique_ptr create_crank_fixture(bool with_pool_abi = true) { stack.entry, /*opp_addr=*/std::string{}, inbound_address, - /*operator_registry_addr=*/std::string{}, fixture->abis, test_outpost_chain_code, test_evm_chain_id); @@ -1114,7 +1110,6 @@ BOOST_AUTO_TEST_CASE(read_inbound_envelope_validates_latest_slot) try { entry, opp_address, "", - "", abis, test_outpost_chain_code, test_evm_chain_id); @@ -1914,7 +1909,6 @@ BOOST_AUTO_TEST_CASE(can_encode_tx_01) try { "0x8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5", private_key_spec); - // Provider should be retrievable // Sign raw unsigned TX bytes — eth_client_signer hashes with keccak256 internally fc::crypto::eth_client_signer eth_signer(*sig_provider); 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 a9021bb36b..89509f270f 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 @@ -11,34 +11,9 @@ namespace sysio { using namespace fc::network::solana; -/// Default program name used in the Anchor IDL for the Solana OPP outpost. -/// Shared between the outpost_solana_client_plugin and the batch_operator_plugin -/// so both speak a single constant when locating the program's IDL entry. -/// Overridable at runtime via `--solana-outpost-program-name`: the clean-room -/// outpost implementation is hosted inside the `liqsol_core` program, whose -/// generated IDL carries that name instead of `opp_outpost`. +/// Default IDL name; integrated liqsol-core deployments override it through configuration. inline constexpr const char* OPP_SOLANA_OUTPOST_PROGRAM_NAME = "opp_outpost"; -/// Interval between successive `getSignaturesForAddress` + log-scan attempts -/// inside the underwriter daemon's `verify_source_deposit_sol`. Kept long -/// enough to keep the RPC load production-acceptable — a tighter interval is -/// reserved for tighter-budget flows (e.g. tx-confirmation polling at the -/// `processed` commitment level, ~400ms). -/// -/// Shared with the underwriter plugin so the production-tuning lever lives -/// in one place per chain client. -inline constexpr auto SOL_SWAP_DEPOSIT_POLL_INTERVAL = fc::seconds(15); - -/// Total wall-clock budget for `verify_source_deposit_sol` to find the -/// `SwapDeposit` marker log line emitted by `opp-outpost::request_swap`. -/// On expiry the verifier returns `false` and the underwriter's outer poll -/// loop reattempts on its next tick. 120s comfortably covers: -/// - the slot it took for `request_swap` to land + finalize, AND -/// - the RPC `getSignaturesForAddress` window (default ~1000 sigs back), -/// - across an `solana-test-validator` cluster (no ledger pruning within -/// this horizon) and a production RPC (≥ 2 epochs of tx history). -inline constexpr auto SOL_SWAP_DEPOSIT_TOTAL_TIMEOUT = fc::seconds(120); - /// Consumer role a Solana `outpost_client` is constructed for. Boot-time IDL /// validation is role-aware: only roles that call `read_inbound_envelope` /// require the loaded IDL to declare a decodable `LatestOutboundEnvelope` @@ -49,11 +24,7 @@ enum class solana_outpost_role { /// Delivers outbound envelopes AND polls `read_inbound_envelope` - the /// IDL must declare a readable `LatestOutboundEnvelope`, asserted at boot. batch_operator, - /// Only submits `uw_commit`; never reads inbound envelopes, so the - /// `LatestOutboundEnvelope` boot assertion is skipped. If such a client - /// ever does call `read_inbound_envelope` against an IDL without the - /// account, the read logs a warning and yields no envelope. - underwriter + }; struct solana_client_entry_t { @@ -110,18 +81,6 @@ struct opp_solana_outpost_client : fc::network::solana::solana_program_client { /// bytes — overwritten on every emit. The WIRE batch operator reads /// this to relay the envelope back to WIRE. fc::network::solana::solana_public_key latest_outbound_envelope_pda; - /// Outpost lamport vault. Holds escrowed collateral deposited via - /// `deposit`; drained on inbound WITHDRAW_REMIT / SLASH / - /// DEPOSIT_REVERT by the program's signed system_program::transfer - /// CPI. Pre-derived from seed `outpost_vault`. - fc::network::solana::solana_public_key vault_pda; - /// Outpost `reserve_aggregate` PDA — receives slashed-collateral routing - /// and DEPOSIT_REVERT penalties, and owns the destination ATA of an SPL - /// SLASH seizure. Pre-derived from seed `reserve_aggregate` (see the - /// derivation below for the `outpost_reserve` mis-seed incident this - /// spelling replaced). - fc::network::solana::solana_public_key reserve_pda; - /// `initialize(consensus_threshold: u32) -> signature`. solana_program_tx_fn initialize; /// `epoch_in(epoch_index, chunk_index, total_chunks, total_bytes, chunk_data, @@ -155,16 +114,6 @@ struct opp_solana_outpost_client : fc::network::solana::solana_program_client { /// never calls it because the consensus-reaching terminal `epoch_in` emits /// the outbound envelope inline. solana_program_tx_fn emit_outbound_envelope; - /// `deposit(operator_type: u8, wire_account_name: string, amount: u64) -> signature`. - solana_program_tx_fn deposit; - /// `commit_underwrite(uic_bytes: bytes) -> signature`. - /// Submits an underwriter's original canonical `UnderwriteIntentCommit` - /// bytes. The on-chain handler binds their signed SVM caller and claimed - /// ACTIVE roster identity, then stores the unchanged bytes for the next - /// outbound envelope so the batch operator can relay the COMMIT to the - /// depot. - solana_program_tx_fn> commit_underwrite; - /// Decode already-fetched Anchor account bytes using the outpost IDL. /// This lets callers distinguish account-not-found from RPC/transport /// failure before decoding the returned data. @@ -196,17 +145,6 @@ struct opp_solana_outpost_client : fc::network::solana::solana_program_client { , latest_outbound_envelope_pda(fc::network::solana::system::find_program_address( {std::vector{'l','a','t','e','s','t','_','o','u','t','b','o','u','n','d','_','e','n','v','e','l','o','p','e'}}, prog_id).first) - , vault_pda(fc::network::solana::system::find_program_address( - {std::vector{'o','u','t','p','o','s','t','_','v','a','u','l','t'}}, - prog_id).first) - // SOL outpost reserve aggregate is seeded with b"reserve_aggregate" - // (see `RESERVE_AGGREGATE_SEED` in programs/opp-outpost/src/state/reserve.rs). - // Previously this used b"outpost_reserve" which derived to a non-existent - // PDA → epoch_in's `reserve_aggregate` account validation failed with - // fc::assert_exception 10. - , reserve_pda(fc::network::solana::system::find_program_address( - {std::vector{'r','e','s','e','r','v','e','_','a','g','g','r','e','g','a','t','e'}}, - prog_id).first) // OPP writes default to the confirmed variant — any state-changing // call on this client is consensus-critical and must not silently // drop (see epoch-859 stall RCA). `execute_tx_and_confirm` + default @@ -350,22 +288,7 @@ struct opp_solana_outpost_client : fc::network::solana::solana_program_client { program_invoke_data_items params = {fc::variant(wire_epoch_index)}; return execute_tx_and_confirm(instr, resolve_accounts(instr, params, overrides), params); }) - , deposit(create_tx_and_confirm(get_idl("deposit"))) - // commit_underwrite is `(uic_bytes: bytes) -> signature`. The IDL declares - // three accounts — `underwriter` (signer, default-resolved from the - // client), `operator_registry` (PDA), and `outbound_message_buffer` - // (PDA). IDL v2 (Anchor 0.31+) does not embed PDA seeds, so the typed - // wrapper must inject the pre-derived PDAs as overrides — same pattern - // as `epoch_in` / `emit_outbound_envelope` above. - , commit_underwrite([this](std::vector uic_bytes) -> std::string { - account_overrides_t overrides = { - {"operator_registry", operator_registry_pda}, - {"outbound_message_buffer", outbound_message_buffer_pda}, - }; - auto& instr = get_idl("commit_underwrite"); - program_invoke_data_items params = {fc::variant(uic_bytes)}; - return execute_tx_and_confirm(instr, resolve_accounts(instr, params, overrides), params); - }) {} + {} }; class outpost_solana_client_plugin : public appbase::plugin { diff --git a/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin/outpost_solana_client.hpp b/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin/outpost_solana_client.hpp index 996dace4fd..47a49363c1 100644 --- a/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin/outpost_solana_client.hpp +++ b/plugins/outpost_solana_client_plugin/include/sysio/outpost_solana_client_plugin/outpost_solana_client.hpp @@ -64,10 +64,9 @@ inline constexpr uint32_t SOLANA_DISPATCH_HEAP_FRAME_BYTES = 256'000; /// the relay can never spin forever on one epoch. /// /// Sized against the depot's worst case now that the depot no longer bounds the -/// envelope by Solana's packet limit: `MAX_ENVELOPE_BYTES` (64 KiB) admits on the -/// order of 500 small SPL swap-remits across distinct reserves, and at ~5 -/// attestations per 16-account round that needs ~100 rounds. Exhausting this is -/// a liveness alarm, never a silent success. +/// envelope by Solana's packet limit: many LIQ effects can require separate +/// rounds under the account budget. Exhaustion is a liveness alarm, never a +/// silent success. /// /// Exported so the plugin's round-exhaustion test sizes its fixture against /// THIS constant rather than a copy of it. @@ -186,45 +185,6 @@ std::vector decode_latest_envelope_account(opp_solana_outpost_client& pro /// is absent or disagrees with what the cursor read decodes. void assert_epoch_deliveries_shape(const fc::network::solana::idl::program& program); -/// Assert that the loaded IDL's `CollateralPosition` declaration carries the -/// four fields the on-chain settlement path binds together: `operator` and -/// `custody_mint` (pubkeys), plus `token_code` and `amount` (u64), in either -/// IDL field home. -/// -/// The relay itself decodes only `custody_mint`; the other three are a -/// DELIBERATE drift canary — a program-side rename of any of the four fails -/// loudly at boot rather than surfacing later as a live position this relay -/// silently half-understands. -/// -/// Called at construction for the batch-operator role. IDL drift is the -/// realistic reason a live, program-readable position becomes unreadable to -/// THIS relay; without its pinned custody mint the manifest cannot include the -/// effect accounts the program's real branch requires, so the dispatch window -/// would abort and every retry would wedge on the same cursor position. -/// -/// @param program the program's loaded Anchor IDL. -/// @throws fc::exception if the account or any of the four fields is absent, -/// or a field has a type the manifest builder cannot interpret. -void assert_collateral_position_shape(const fc::network::solana::idl::program& program); - -/// Assert that the loaded IDL's `Reserve` declaration carries the four fields -/// the terminal manifest resolves from it: `creator`, `custody_mint` and -/// `custody_token_program` (pubkeys) and `custody_decimals` (u8), in either IDL -/// field home. -/// -/// Called at construction for the batch-operator role, beside the -/// `EpochDeliveries` check. IDL drift is the realistic reason a live, -/// program-readable `Reserve` becomes unreadable to THIS relay, and that -/// failure is unrecoverable in flight: the manifest cannot be completed, the -/// dispatch window would abort on a missing effect account, and every later -/// window repacks from the same unadvanced cursor. Catching it at boot turns a -/// wedged epoch into a startup error while the IDL is still fixable. -/// -/// @param program the program's loaded Anchor IDL. -/// @throws fc::exception if the account or any of the four fields is absent, -/// or a field has a type the manifest builder cannot interpret. -void assert_reserve_shape(const fc::network::solana::idl::program& program); - /// Assert the loaded IDL declares `DistributionState` with the one field a /// DESYNDICATE_LIQ manifest resolves from it: `liqsol_mint` (pubkey). Only the /// integrated liqsol-core program declares the account, so the boot check runs @@ -232,73 +192,6 @@ void assert_reserve_shape(const fc::network::solana::idl::program& program); /// unreadable and wedge every desyndication window on the same cursor. void assert_distribution_state_shape(const fc::network::solana::idl::program& program); -/// Terminal-finalization facts for one per-`(token_code, reserve_code)` -/// Reserve PDA, read from the `Reserve` ACCOUNT itself. -/// -/// Custody is pinned on the Reserve at creation time and is exactly what the -/// on-chain handlers branch on (`handle_swap_remit` / `handle_swap_revert` / -/// `handle_reserve_create_cancelled` all test `reserve.custody_mint` against -/// the native marker). Resolving it from the mutable -/// `OutpostConfig.token_addresses_by_code` instead would let an admin -/// re-pointing a token address between reserve creation and dispatch drive the -/// relay down the native branch while the program takes the SPL one -- the -/// manifest would then lack the vault/ATA accounts the handler requires and -/// the call would abort permanently, wedging the epoch. -struct reserve_terminal_info { - /// Reserve creator -- the `RESERVE_CREATE_CANCELLED` refund target. - fc::network::solana::solana_public_key creator; - /// Custody mint, or the all-zero system-program key for native lamports - /// (the program's `NATIVE_TOKEN_MARKER` convention). - fc::network::solana::solana_public_key custody_mint; - /// Chain-native decimals pinned at reserve creation. - uint8_t custody_decimals = 0; - /// Token program the reserve's custody is held under, pinned at reserve - /// creation (SOL-396). The SAME (owner, mint) pair has DIFFERENT canonical - /// ATAs under SPL-Token and Token-2022, so this drives BOTH the ATA - /// derivation and the token-program account the terminal handlers require. - /// Deriving with the legacy default against a Token-2022 reserve yields an - /// address the program never asks for -- `EffectAccountMissing`, forever. - fc::network::solana::solana_public_key custody_token_program; -}; - -/// Extract the terminal-finalization facts from an already-decoded `Reserve` -/// account object. ALL FOUR fields are required: `creator`, `custody_mint`, -/// `custody_decimals` and `custody_token_program` are written together at -/// reserve creation, so a record missing any of them is not a reserve this -/// relay can build an account-consistent manifest for — guessing custody is -/// precisely the divergence that aborts the on-chain call. -/// -/// Throwing here FAILS THE BUILD, by design — do not "restore" a degrade on -/// this path. Reaching this function means the account EXISTS and the program -/// therefore decodes it fine and takes its real branch, demanding the accounts -/// that branch needs. A manifest built by guessing the missing field is -/// guaranteed to abort on chain (`require_remaining_account` -> -/// EffectAccountMissing), and because `drive_dispatch_rounds` repacks every -/// window from the unadvanced on-chain cursor, that aborting attestation heads -/// every future window and the epoch never closes. -/// -/// The benign degrade lives one level up and covers a DIFFERENT cause: an -/// ABSENT or EMPTY reserve never reaches this function, and -/// `reserve_info_for_codes` returns empty for it because the program skips an -/// uninitialized reserve. Only that cause may degrade. -/// -/// Exposed in this header so the plugin's unit tests can drive custody -/// resolution — including a config-vs-reserve divergence — without RPC. -/// -/// @param reserve a decoded `Reserve` account object. -/// @throws fc::exception when a required field is absent or unparseable. -reserve_terminal_info reserve_info_from_account(const fc::variant_object& reserve); - -/// Reads the `Reserve` account behind `(token_code, reserve_code)`. -/// -/// Returns empty ONLY for a reserve the program itself will skip (absent or -/// uninitialized) — a benign per-attestation degrade. A reserve that exists -/// but cannot be read THROWS, because the program would take its real branch -/// and abort on the accounts a degraded manifest omits; that exception is -/// propagated by `build_dispatch_manifests` rather than absorbed. -using reserve_info_reader = - std::function(uint64_t token_code, uint64_t reserve_code)>; - /// One entry of a Token-2022 mint's `ExtraAccountMetaList`, exactly as the /// validation account stores it (35 bytes: discriminator, 32-byte address /// config, signer flag, writable flag). @@ -380,8 +273,6 @@ using transfer_hook_reader = std::function( const fc::network::solana::solana_public_key& custody_mint)>; - - /// How far the outpost has settled one inbound epoch's attestations. struct epoch_dispatch_progress { /// Whether consensus has tipped for the epoch. Until it has, a terminal @@ -443,26 +334,6 @@ std::string drive_dispatch_rounds( send_dispatch, const std::string& log_label); -/// Custody binding read from the account the on-chain settlement handler -/// branches on. Deliberately mint-only: `CollateralPosition` carries no -/// decimals, and the manifest path branches on the mint alone — a decimals -/// field here could only ever hold a made-up value (contrast -/// `reserve_terminal_info`, whose `custody_decimals` is a real pinned fact). -struct token_custody_info { - /// SPL mint for the token, or the all-zero system-program key when the - /// token is native lamports (the on-chain zero-marker convention). - fc::network::solana::solana_public_key mint; -}; - -/// Reads ONE `(operator, token_code)` position's custody binding from its -/// `CollateralPosition` PDA, exactly where the on-chain handler resolves it. -/// Absent positions degrade because the program log-and-skips them; a present -/// but unreadable position throws so the relay never submits a manifest that -/// is guaranteed to abort. -using collateral_custody_reader = - std::function( - const fc::network::solana::solana_public_key& operator_key, uint64_t token_code)>; - /// The syndicated liqSOL pool's facts a DESYNDICATE_LIQ manifest is derived /// from, read off the program's `DistributionState` singleton: the pool's /// liqSOL mint (Token-2022). The handler binds the mint from the same account, @@ -636,33 +507,11 @@ class outpost_solana_client : public outpost_client { std::vector read_inbound_envelope(uint32_t epoch_index, fc::microseconds deadline) override; - std::string uw_commit(uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) override; - // Expose for inspection / tests const solana_client_entry_ptr& entry() const { return _entry; } const fc::network::solana::solana_public_key& program_id() const { return _program_id; } private: - /// Resolve the terminal-finalization facts for a per-reserve PDA -- creator - /// AND custody (mint / decimals) -- from the ONE account the on-chain - /// handlers themselves branch on: the `Reserve` PDA. One RPC read per - /// distinct reserve; no `OutpostConfig` read on this path at all. - /// - /// The two failure modes are deliberately NOT treated alike: - /// - /// * An ABSENT or EMPTY reserve returns empty (a warning is logged). The - /// program skips an uninitialized reserve, so a partial manifest is - /// harmless. - /// * A reserve that EXISTS but this relay cannot decode (or that is - /// missing creator/custody) THROWS, after logging the chain-side - /// reason. The program reads that account fine and demands the accounts - /// its real branch needs, so a degraded manifest would be guaranteed to - /// abort — permanently, since every later window repacks from the same - /// unadvanced cursor. Failing the tick leaves the cursor untouched. - std::optional - reserve_info_for_codes(uint64_t token_code, uint64_t reserve_code); /// Read `EpochDeliveries` for `epoch_index`. A missing/empty account means /// nothing has been delivered yet, reported as zero progress rather than @@ -681,19 +530,6 @@ class outpost_solana_client : public outpost_client { uint32_t dispatch_limit, std::vector extra_remaining_accounts); - /// Resolve one per-`(operator, token_code)` collateral position's pinned - /// custody mint from the `CollateralPosition` PDA the on-chain handlers - /// themselves branch on. - /// - /// An ABSENT or EMPTY position returns empty because the program - /// log-and-skips an uninitialized account. A position that EXISTS but this - /// relay cannot decode THROWS after logging: the program takes its real - /// custody branch, so degrading would omit required effect accounts and - /// permanently wedge the unadvanced dispatch cursor. - std::optional - collateral_position_custody( - const fc::network::solana::solana_public_key& operator_key, uint64_t token_code); - /// Read one custody mint's transfer-hook configuration (SOL-396). `nullopt` /// when the mint carries no hook -- every mint in the system today, for /// which the manifest is unchanged. A CONFIGURED hook whose @@ -743,56 +579,6 @@ void record_terminal_account(std::vector& met const fc::network::solana::solana_public_key& key, bool is_writable); -/// `(token_code, reserve_code)` pair for a Reserve PDA derivation, carried on -/// every reserve-backed `inbound_effect`. The manifest builder derives the -/// Reserve PDA via Anchor's `find_program_address` with the `[RESERVE_SEED, -/// &token_code.to_le_bytes(), &reserve_code.to_le_bytes()]` seed list against -/// the program id. -struct reserve_pda_seeds { - uint64_t token_code; - uint64_t reserve_code; -}; - -/// Derive the per-`(token_code, reserve_code)` `Reserve` PDA: seeds -/// `["reserve", token_code.to_le_bytes(), reserve_code.to_le_bytes()]`. -/// Byte-exact mirror of the program's `#[account(seeds = ...)]` declaration — -/// a derivation that disagrees fails seeds validation on chain. -/// -/// Exported so the manifest builder and its tests derive through ONE -/// implementation rather than re-spelling the seed list. -fc::network::solana::solana_public_key -derive_reserve_pda(const fc::network::solana::solana_public_key& program_id, - uint64_t token_code, - uint64_t reserve_code); - -/// Derive the per-`(token_code, reserve_code)` `reserve_vault` PDA: seeds -/// `["reserve_vault", token_code.to_le_bytes(), reserve_code.to_le_bytes()]`. -/// The vault holds the reserve's SPL custody; native reserves settle straight -/// out of the Reserve PDA, so this account only rides an SPL manifest. -fc::network::solana::solana_public_key -derive_reserve_vault_pda(const fc::network::solana::solana_public_key& program_id, - uint64_t token_code, - uint64_t reserve_code); - -/// Derive the per-`(operator, token_code)` `CollateralPosition` PDA (SOL-379): -/// seeds `["collateral_position", operator.as_ref(), token_code.to_le_bytes()]`. -/// Byte-exact mirror of the program's `#[account(seeds = ...)]` declaration. -/// -/// Exported so the manifest builder and its tests derive through ONE -/// implementation rather than re-spelling the seed list. -fc::network::solana::solana_public_key -derive_collateral_position_pda(const fc::network::solana::solana_public_key& program_id, - const fc::network::solana::solana_public_key& operator_key, - uint64_t token_code); - -/// Derive the per-`token_code` `collateral_vault` PDA: seeds -/// `["collateral_vault", token_code.to_le_bytes()]`. The vault holds the -/// token's SPL collateral custody; native collateral settles straight out of -/// the named `vault`, so this account only rides an SPL manifest. -fc::network::solana::solana_public_key -derive_collateral_vault_pda(const fc::network::solana::solana_public_key& program_id, - uint64_t token_code); - /// The liqSOL pool's fixed PDAs (wire-solana `liqsol-core`), byte-exact mirrors /// of the program's seed declarations: `GlobalState` /// (`["outpost_global_state"]`), `DistributionState` (`["distribution_state"]`), @@ -837,38 +623,7 @@ derive_liqsol_user_record_pda(const fc::network::solana::solana_public_key& prog /// derives the concrete metas per shape; the on-chain handler resolves them /// out of `remaining_accounts` by pubkey. /// -/// The collateral-settling shapes (`withdraw_remit`, `slash`, -/// `deposit_revert`) exist because SOL-379 replaced the bounded collateral -/// `Vec` on `OperatorRegistry` with a per-`(operator, token_code)` -/// `CollateralPosition` PDA, and SOL-380 made the handlers settle in the -/// asset the position actually escrows — so their manifests must declare the -/// position PDA (seeds `[COLLATERAL_POSITION_SEED, operator.as_ref(), -/// &token_code.to_le_bytes()]`) and, for SPL custody, the -/// `[COLLATERAL_VAULT_SEED, &token_code.to_le_bytes()]` vault PDA, the -/// destination ATA and the SPL token program. enum class effect_shape { - /// OPERATOR_ACTION(WITHDRAW_REMIT): pays the operator (natively out of the - /// named `vault`, or into their canonical ATA under SPL custody) and - /// debits their `CollateralPosition` PDA. - withdraw_remit, - /// OPERATOR_ACTION(SLASH): debits the operator's `CollateralPosition` PDA - /// and routes the seizure into the named `reserve_aggregate` (native) or - /// its canonical ATA via the collateral vault + token program (SPL). - slash, - /// OPERATOR_ACTION(DEPOSIT_REVERT): refunds the depositor (natively out of - /// the named `vault`, or into their canonical ATA under SPL custody) and - /// debits their `CollateralPosition` PDA. - deposit_revert, - /// SWAP_REMIT: Reserve PDA, plus vault + recipient ATA + token program - /// when the reserve's custody mint is SPL rather than native. - swap_remit, - /// SWAP_REVERT: refunds the depositor, so it additionally needs the mint, - /// the ATA program and the system program to create the ATA if absent. - swap_revert, - /// RESERVE_READY: Reserve PDA only. - reserve_ready, - /// RESERVE_CREATE_CANCELLED: refunds the reserve's creator. - reserve_create_cancelled, /// DESYNDICATE_LIQ: the depot releases a user's syndicated liqSOL. The /// handler resolves the pool's two state singletons, the pool-authority-signed /// Token-2022 transfer's accounts (the pool and user ATAs with their @@ -890,18 +645,9 @@ enum class effect_shape { struct inbound_effect { size_t attestation_index; effect_shape shape; - /// WITHDRAW_REMIT operator / DEPOSIT_REVERT depositor / SWAP_REMIT - /// recipient / SWAP_REVERT depositor / DESYNDICATE_LIQ user (paid into - /// their liqSOL ATA). For `slash` it is the SLASHED operator — it keys the - /// `CollateralPosition` PDA and the destination ATA owner lookup but is - /// never itself paid. + /// DESYNDICATE_LIQ recipient, paid into its liqSOL ATA. std::optional recipient; - /// Set for every reserve-backed shape. - std::optional reserve; - /// Set for every collateral-settling shape (`withdraw_remit`, `slash`, - /// `deposit_revert`): the `token_code` keying the `CollateralPosition` - /// PDA and its pinned custody lookup. - std::optional collateral_token_code; + /// Set for `desyndicate_liq`: the depot's `DesyndicateLIQ.request_id`, which /// keys the `PendingPayout` PDA the handler stores the payout at when it /// cannot pay inline (`derive_pending_desyndication_pda`). @@ -933,78 +679,23 @@ extract_inbound_effects(const std::vector& envelope_bytes); /// envelope does not decode. uint32_t count_inbound_attestations(const std::vector& envelope_bytes); -/// Build the per-attestation effect-account manifests `drive_dispatch_rounds` -/// packs its `dispatch_attestations` windows from, factored over its ONE RPC -/// touchpoint (`read_reserve_info`) so the plugin's unit tests can drive the -/// whole build -- custody branching, per-reserve read coalescing, degrade -/// paths and the deadline probe -- without a live Solana endpoint. -/// -/// The result is indexed by the FLAT attestation position the on-chain cursor -/// counts, sized to `total_attestations`; attestations needing no effect -/// account keep an empty entry so the indices stay aligned. -/// -/// Behaviour that matters: -/// * `read_reserve_info` is called at most ONCE per distinct -/// `(token_code, reserve_code)` -- the results are memoised for the build, -/// including the empty (degraded) ones, so a repeated reserve never -/// re-pays an RPC round-trip. -/// * Custody branching follows `reserve_terminal_info::custody_mint`, the -/// same field the on-chain handler branches on. -/// * `throw_if_past_deadline` runs once per effect, BEFORE its reserve read, -/// so an over-deadline build fails at the loop rather than deep in the RPC -/// layer with the work already lost. -/// * A degraded (empty) reserve read costs only THAT attestation its -/// custody-dependent accounts; every other attestation's manifest is still -/// built, so the envelope's healthy prefix still dispatches. A THROWING -/// read propagates untouched — it means a manifest the program would abort -/// on, and shipping one would wedge the epoch rather than delay it. -/// -/// * Collateral-settling shapes (`withdraw_remit`, `slash`, `deposit_revert`) -/// resolve custody through `read_collateral_custody` — called at most ONCE -/// per distinct `(operator, token_code)` position, memoised like the -/// reserve reads. A degraded (empty) custody read costs that attestation -/// only its SPL extras; the `CollateralPosition` PDA and (where owed) the -/// recipient are still declared, matching the program's log-and-skip / -/// abort-and-retry gates. -/// -/// * `desyndicate_liq` derives everything from the user, the program's fixed -/// pool PDAs and the mint on `DistributionState`, read through -/// `read_liq_pool` at most ONCE per build. A degraded (empty) read costs -/// the attestation everything but the two state singletons and the -/// pending-payout PDA, which is what lets the handler log-and-skip an -/// uninitialized pool instead of aborting. The pending-payout PDA -/// (`derive_pending_desyndication_pda`, writable) rides right after the -/// singletons for every non-zero request id, pool read or not: the handler's -/// frozen-outpost deferral needs it before any pool account, and its -/// legacy-`UserRecord` deferral after them; the paid path never touches it. -/// -/// @param program_id outpost program id, for PDA derivation. -/// @param effects account-needing attestations, in dispatch order. -/// @param total_attestations the envelope's attestation total (the cursor's -/// denominator) -- the size of the result. -/// @param throw_if_past_deadline throws once the caller's deadline passes. -/// @param read_reserve_info reads one `Reserve` record (may degrade). -/// @param read_collateral_custody reads one `(operator, token_code)` -/// position's pinned custody (may degrade only -/// when the position is absent or empty). -/// @param read_liq_pool reads the liqSOL pool's `DistributionState` -/// (may degrade only when the account is absent -/// or empty). -/// @param reserve_aggregate the named `reserve_aggregate` account — the -/// destination whose ATA receives an SPL slash -/// seizure. -/// @param log_label client identity for log lines. -/// @return one manifest per attestation, in dispatch order. +/// Build LIQ settlement account manifests, indexed by the envelope's flat +/// attestation position. Probe the deadline before each effect and memoize the +/// pool and transfer-hook reads for this build. An absent pool carries only the +/// state singletons and pending-payout PDA; an RPC error propagates for retry. +/// +/// The full manifest includes the Token-2022 transfer, share-accounting and +/// hook accounts. A nonzero request id always contributes its writable pending +/// payout PDA so frozen or refused payouts can be stored instead of lost. +/// +/// @return one manifest per attestation, including empty entries for no-effect tags. std::vector> build_dispatch_manifests( const fc::network::solana::solana_public_key& program_id, const std::vector& effects, uint32_t total_attestations, const std::function& throw_if_past_deadline, - const reserve_info_reader& read_reserve_info, - const collateral_custody_reader& read_collateral_custody, const transfer_hook_reader& read_transfer_hook, const liq_pool_reader& read_liq_pool, - const fc::network::solana::solana_public_key& reserve_aggregate, const std::string& log_label); } // namespace outpost_solana_client_detail 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 27d417bd4d..ad0cff7bff 100644 --- a/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp +++ b/plugins/outpost_solana_client_plugin/src/outpost_solana_client.cpp @@ -35,7 +35,6 @@ constexpr auto REPORT_LIQ_YIELD_INSTRUCTION = "report_liq_yield"; constexpr std::string_view OP_DISPATCH_ATTESTATIONS = "deliver_outbound_envelope:dispatch_attestations"; constexpr std::string_view OP_READ_LATEST = "read_inbound_envelope:get_account_info"; -constexpr std::string_view OP_UW_COMMIT = "uw_commit:commit_underwrite"; /// Anchor seed literals for the outpost program's per-epoch PDAs. Byte-exact /// mirrors of the program's `EPOCH_DELIVERIES_SEED` / `ENVELOPE_CHUNKS_SEED` @@ -45,13 +44,6 @@ constexpr std::string_view OP_UW_COMMIT = "uw_commit:commit_underwrite"; constexpr std::string_view EPOCH_DELIVERIES_SEED = "epoch_deliveries"; constexpr std::string_view ENVELOPE_CHUNKS_SEED = "envelope_chunks"; -/// Anchor seed literals for the collateral-settlement PDAs. Byte-exact -/// mirrors of the program's `COLLATERAL_POSITION_SEED` / -/// `COLLATERAL_VAULT_SEED` constants (wire-solana `opp_states.rs`) -- a -/// one-character drift derives a well-formed WRONG PDA that only surfaces at -/// runtime as `EffectAccountMissing`, holding the dispatch cursor. -constexpr std::string_view COLLATERAL_POSITION_SEED = "collateral_position"; -constexpr std::string_view COLLATERAL_VAULT_SEED = "collateral_vault"; /// The liqSOL pool's seeds (wire-solana `liqsol-core`: `inbound.rs` and the /// `report_liq_yield` / `synd` account declarations), same lock-step caveat. constexpr std::string_view LIQSOL_GLOBAL_STATE_SEED = "outpost_global_state"; @@ -133,30 +125,6 @@ namespace epoch_deliveries { constexpr auto field_dispatched_count = "dispatched_count"; } // namespace epoch_deliveries -/// Field identifiers of the per-`(token_code, reserve_code)` `Reserve` -/// account. `custody_mint` / `custody_decimals` / `custody_token_program` are -/// pinned at reserve creation -/// and are what the on-chain terminal handlers branch on, so the relay's -/// manifest must read custody from HERE and nowhere else. -namespace reserve_account { - constexpr auto account_name = "Reserve"; - constexpr auto field_creator = "creator"; - constexpr auto field_custody_mint = "custody_mint"; - constexpr auto field_custody_decimals = "custody_decimals"; - constexpr auto field_custody_token_program = "custody_token_program"; -} // namespace reserve_account - -/// Field identifiers of the per-`(operator, token_code)` collateral position. -/// Its custody mint is pinned at first deposit and is what the on-chain -/// settlement handlers branch on. -namespace collateral_position { - constexpr auto account_name = "CollateralPosition"; - constexpr auto field_operator = "operator"; - constexpr auto field_token_code = "token_code"; - constexpr auto field_custody_mint = "custody_mint"; - constexpr auto field_amount = "amount"; -} // namespace collateral_position - /// `DistributionState` -- the liqSOL pool singleton `handle_desyndicate_liq` /// binds the pool's mint from. The relay decodes only `liqsol_mint`. namespace distribution_state { @@ -308,98 +276,6 @@ void assert_epoch_deliveries_shape(const fc::network::solana::idl::program& prog epoch_deliveries::field_dispatched_count); } -/// Assert the loaded IDL declares `CollateralPosition` with the four fields -/// its on-chain settlement path binds together. Full contract on the header -/// declaration. -void assert_collateral_position_shape(const fc::network::solana::idl::program& program) { - namespace idl = fc::network::solana::idl; - const auto& fields = declared_account_fields(program, collateral_position::account_name); - - bool has_operator = false; - bool has_token_code = false; - bool has_mint = false; - bool has_amount = false; - for (const auto& field : fields) { - const bool is_pubkey = - field.type.is_primitive() && field.type.primitive == idl::primitive_type::pubkey; - const bool is_u64 = - field.type.is_primitive() && field.type.primitive == idl::primitive_type::u64; - if (field.name == collateral_position::field_operator) { - FC_ASSERT(is_pubkey, "CollateralPosition '{}' must be declared pubkey, got '{}'", - collateral_position::field_operator, describe_idl_type(field.type)); - has_operator = true; - } else if (field.name == collateral_position::field_token_code) { - FC_ASSERT(is_u64, "CollateralPosition '{}' must be declared u64, got '{}'", - collateral_position::field_token_code, describe_idl_type(field.type)); - has_token_code = true; - } else if (field.name == collateral_position::field_custody_mint) { - FC_ASSERT(is_pubkey, "CollateralPosition '{}' must be declared pubkey, got '{}'", - collateral_position::field_custody_mint, describe_idl_type(field.type)); - has_mint = true; - } else if (field.name == collateral_position::field_amount) { - FC_ASSERT(is_u64, "CollateralPosition '{}' must be declared u64, got '{}'", - collateral_position::field_amount, describe_idl_type(field.type)); - has_amount = true; - } - } - FC_ASSERT(has_operator && has_token_code && has_mint && has_amount, - "CollateralPosition IDL missing '{}' / '{}' / '{}' / '{}' " - "(found {} / {} / {} / {}); a live collateral position's pinned custody cannot be " - "resolved safely without this declaration", - collateral_position::field_operator, collateral_position::field_token_code, - collateral_position::field_custody_mint, collateral_position::field_amount, - has_operator, has_token_code, has_mint, has_amount); -} - -/// Assert the loaded IDL declares `Reserve` with the four fields the terminal -/// manifest resolves from it. Full contract on the header declaration. -void assert_reserve_shape(const fc::network::solana::idl::program& program) { - namespace idl = fc::network::solana::idl; - const auto& fields = declared_account_fields(program, reserve_account::account_name); - - bool has_creator = false; - bool has_mint = false; - bool has_decimals = false; - bool has_token_program = false; - for (const auto& field : fields) { - // Compare against the optional member directly, as the sibling shape - // checks do: a malformed type object must reach the per-field diagnostic - // rather than `get_primitive()`'s generic throw. - const bool is_pubkey = - field.type.is_primitive() && field.type.primitive == idl::primitive_type::pubkey; - if (field.name == reserve_account::field_creator) { - FC_ASSERT(is_pubkey, "Reserve '{}' must be declared pubkey, got '{}'", - reserve_account::field_creator, describe_idl_type(field.type)); - has_creator = true; - } else if (field.name == reserve_account::field_custody_mint) { - FC_ASSERT(is_pubkey, "Reserve '{}' must be declared pubkey, got '{}'", - reserve_account::field_custody_mint, describe_idl_type(field.type)); - has_mint = true; - } else if (field.name == reserve_account::field_custody_decimals) { - FC_ASSERT(field.type.is_primitive() && field.type.primitive == idl::primitive_type::u8, - "Reserve '{}' must be declared u8, got '{}'", - reserve_account::field_custody_decimals, describe_idl_type(field.type)); - has_decimals = true; - } else if (field.name == reserve_account::field_custody_token_program) { - FC_ASSERT(is_pubkey, "Reserve '{}' must be declared pubkey, got '{}'", - reserve_account::field_custody_token_program, describe_idl_type(field.type)); - has_token_program = true; - } - } - // One message for all four: they are written together at reserve creation, - // and any one missing costs the manifest the same way -- the effect account - // the program's branch requires cannot be derived, so its dispatch window - // aborts and the cursor never moves past it. - FC_ASSERT(has_creator && has_mint && has_decimals && has_token_program, - "Reserve IDL missing '{}' / '{}' / '{}' / '{}' (found {} / {} / {} / {}); the terminal " - "manifest resolves custody, the cancel-refund target and the ATA-deriving token " - "program from these, and a reserve-backed dispatch window cannot be built without them", - reserve_account::field_creator, reserve_account::field_custody_mint, - reserve_account::field_custody_decimals, - reserve_account::field_custody_token_program, - has_creator, has_mint, has_decimals, has_token_program); -} - /// Keep only the candidate IDLs whose declared address matches the deployed /// program id, so `--solana-idl-file` ORDER can never decide which same-named /// IDL version's field order drives account decoding. Full contract on the @@ -612,45 +488,6 @@ std::vector pubkey_seed(const fc::network::solana::solana_public_key& k return std::vector(key._data.begin(), key._data.end()); } -} // anonymous namespace (within outpost_solana_client_detail) - -/// Derive the per-reserve `Reserve` PDA. Full contract on the header -/// declaration. -fc::network::solana::solana_public_key derive_reserve_pda( - const fc::network::solana::solana_public_key& program_id, - uint64_t token_code, - uint64_t reserve_code) { - return fc::network::solana::system::find_program_address( - {std::vector{'r','e','s','e','r','v','e'}, - u64_seed(token_code), - u64_seed(reserve_code)}, - program_id).first; -} - -/// Derive the per-reserve `reserve_vault` PDA. Full contract on the header -/// declaration. -fc::network::solana::solana_public_key derive_reserve_vault_pda( - const fc::network::solana::solana_public_key& program_id, - uint64_t token_code, - uint64_t reserve_code) { - return fc::network::solana::system::find_program_address( - {std::vector{'r','e','s','e','r','v','e','_','v','a','u','l','t'}, - u64_seed(token_code), - u64_seed(reserve_code)}, - program_id).first; -} - -/// Derive the per-`(operator, token_code)` `CollateralPosition` PDA. Full -/// contract on the header declaration. -fc::network::solana::solana_public_key derive_collateral_position_pda( - const fc::network::solana::solana_public_key& program_id, - const fc::network::solana::solana_public_key& operator_key, - uint64_t token_code) { - return fc::network::solana::system::find_program_address( - {std::vector(COLLATERAL_POSITION_SEED.begin(), COLLATERAL_POSITION_SEED.end()), - pubkey_seed(operator_key), - u64_seed(token_code)}, - program_id).first; } namespace { @@ -821,7 +658,6 @@ fc::network::solana::account_overrides_t report_liq_yield_overrides( }; } - // ── Token-2022 transfer-hook resolution (SOL-396 lock-step) ───────────────── // // Layout constants are the on-chain ones, not guesses: @@ -1068,17 +904,6 @@ resolve_hook_metas(const std::vector& metas, return resolved; } -/// Derive the per-`token_code` `collateral_vault` PDA. Full contract on the -/// header declaration. -fc::network::solana::solana_public_key derive_collateral_vault_pda( - const fc::network::solana::solana_public_key& program_id, - uint64_t token_code) { - return fc::network::solana::system::find_program_address( - {std::vector(COLLATERAL_VAULT_SEED.begin(), COLLATERAL_VAULT_SEED.end()), - u64_seed(token_code)}, - program_id).first; -} - uint32_t count_inbound_attestations(const std::vector& envelope_bytes) { sysio::opp::Envelope env; if (!env.ParseFromArray(envelope_bytes.data(), @@ -1120,78 +945,6 @@ extract_inbound_effects(const std::vector& envelope_bytes) { for (const auto& entry : message.payload().attestations()) { const size_t at = index++; switch (entry.type()) { - // The collateral-settling operator actions (SOL-379/380). Both - // resolve the per-(operator, token_code) CollateralPosition PDA - // out of remaining_accounts, so both carry the amount's - // token_code alongside the operator key. - case sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION: { - sysio::opp::attestations::OperatorAction oa; - if (!oa.ParseFromString(entry.data())) continue; - std::optional shape; - if (oa.action_type() == - sysio::opp::attestations::OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT) { - shape = effect_shape::withdraw_remit; - } else if (oa.action_type() == - sysio::opp::attestations::OperatorAction_ActionType_ACTION_TYPE_SLASH) { - shape = effect_shape::slash; - } - if (!shape.has_value()) continue; - if (auto pk = sol_pubkey_from_chain_address(oa.op_address())) { - effects.push_back(inbound_effect{ - at, *shape, *pk, std::nullopt, oa.amount().token_code()}); - } - break; - } - case sysio::opp::types::ATTESTATION_TYPE_DEPOSIT_REVERT: { - sysio::opp::attestations::DepositRevert dr; - if (!dr.ParseFromString(entry.data())) continue; - if (auto pk = sol_pubkey_from_chain_address(dr.depositor())) { - effects.push_back(inbound_effect{ - at, effect_shape::deposit_revert, *pk, std::nullopt, - dr.refund_amount().token_code()}); - } - break; - } - case sysio::opp::types::ATTESTATION_TYPE_SWAP_REMIT: { - sysio::opp::attestations::SwapRemit sr; - if (!sr.ParseFromString(entry.data())) continue; - effects.push_back(inbound_effect{ - at, effect_shape::swap_remit, - sol_pubkey_from_chain_address(sr.recipient()), - reserve_pda_seeds{sr.amount().token_code(), sr.reserve_code()}}); - break; - } - case sysio::opp::types::ATTESTATION_TYPE_SWAP_REVERT: { - sysio::opp::attestations::SwapRevert sr; - if (!sr.ParseFromString(entry.data())) continue; - effects.push_back(inbound_effect{ - at, effect_shape::swap_revert, - sol_pubkey_from_chain_address(sr.depositor()), - reserve_pda_seeds{sr.refund_amount().token_code(), sr.source_reserve_code()}}); - break; - } - // The reserve-lifecycle round-trips need the per-(token, reserve) - // Reserve PDA too: `handle_reserve_ready` flips its status field, - // and `handle_reserve_create_cancelled` reads the refund - // amount/creator off it. RESERVE_READY rides exactly ONE envelope - // (queued once at `matchreserve`), so a missing PDA does not defer - // the flip -- it strands the reserve in PENDING permanently. - case sysio::opp::types::ATTESTATION_TYPE_RESERVE_READY: { - sysio::opp::attestations::ReserveReady rr; - if (!rr.ParseFromString(entry.data())) continue; - effects.push_back(inbound_effect{ - at, effect_shape::reserve_ready, std::nullopt, - reserve_pda_seeds{rr.token_code(), rr.reserve_code()}}); - break; - } - case sysio::opp::types::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED: { - sysio::opp::attestations::ReserveCreateCancelled rcc; - if (!rcc.ParseFromString(entry.data())) continue; - effects.push_back(inbound_effect{ - at, effect_shape::reserve_create_cancelled, std::nullopt, - reserve_pda_seeds{rcc.token_code(), rcc.reserve_code()}}); - break; - } // The depot releasing a user's syndicated liqSOL. The user's pubkey is // the only payload fact the manifest needs -- the pool's mint comes from // `DistributionState`, and the handler's own token_code check precedes @@ -1201,7 +954,7 @@ extract_inbound_effects(const std::vector& envelope_bytes) { if (!dl.ParseFromString(entry.data())) continue; if (auto pk = sol_pubkey_from_chain_address(dl.user())) { effects.push_back(inbound_effect{ - at, effect_shape::desyndicate_liq, *pk, std::nullopt, std::nullopt, dl.request_id()}); + at, effect_shape::desyndicate_liq, *pk, dl.request_id()}); } break; } @@ -1214,74 +967,16 @@ extract_inbound_effects(const std::vector& envelope_bytes) { return effects; } -reserve_terminal_info reserve_info_from_account(const fc::variant_object& reserve) { - // Required, not defaulted: a Reserve without custody is one this relay - // cannot build an account-consistent manifest for, and a defaulted - // (all-zero) mint would silently mean "native" -- the exact branch - // divergence that makes the on-chain call abort for good. - FC_ASSERT(reserve.contains(reserve_account::field_creator), - "Reserve account missing '{}' field", reserve_account::field_creator); - FC_ASSERT(reserve.contains(reserve_account::field_custody_mint), - "Reserve account missing '{}' field — custody must come from the Reserve the " - "on-chain handlers branch on, never from the mutable OutpostConfig token maps", - reserve_account::field_custody_mint); - FC_ASSERT(reserve.contains(reserve_account::field_custody_decimals), - "Reserve account missing '{}' field", reserve_account::field_custody_decimals); - FC_ASSERT(reserve.contains(reserve_account::field_custody_token_program), - "Reserve account missing '{}' field — the canonical ATA differs per token program, " - "so deriving with the legacy default would name an account the program never asks for", - reserve_account::field_custody_token_program); - - const auto decimals = reserve[reserve_account::field_custody_decimals].as_uint64(); - FC_ASSERT(decimals <= std::numeric_limits::max(), - "Reserve '{}' = {} is out of byte range", - reserve_account::field_custody_decimals, decimals); - - return reserve_terminal_info{ - fc::network::solana::solana_public_key::from_base58_string( - reserve[reserve_account::field_creator].as_string()), - fc::network::solana::solana_public_key::from_base58_string( - reserve[reserve_account::field_custody_mint].as_string()), - static_cast(decimals), - fc::network::solana::solana_public_key::from_base58_string( - reserve[reserve_account::field_custody_token_program].as_string())}; -} - std::vector> build_dispatch_manifests( const fc::network::solana::solana_public_key& program_id, const std::vector& effects, uint32_t total_attestations, const std::function& throw_if_past_deadline, - const reserve_info_reader& read_reserve_info, - const collateral_custody_reader& read_collateral_custody, const transfer_hook_reader& read_transfer_hook, const liq_pool_reader& read_liq_pool, - const fc::network::solana::solana_public_key& reserve_aggregate, const std::string& log_label) { - const auto& token_program_id = fc::network::solana::system::program_ids::TOKEN_PROGRAM; const auto& token_2022_program_id = fc::network::solana::system::program_ids::TOKEN_2022_PROGRAM; - const auto& system_program_id = fc::network::solana::system::program_ids::SYSTEM_PROGRAM; - - // `NATIVE_TOKEN_MARKER` on the program side: an all-zero custody mint means - // the reserve custodies lamports, which is the branch the handler takes. - auto is_native_custody = [&](const fc::network::solana::solana_public_key& mint) { - return mint == system_program_id; - }; - - // One read per DISTINCT reserve for the whole build -- the same reserve can - // back many attestations of one envelope, and a degraded (empty) read is - // memoised too so a permanently unreadable reserve costs exactly one - // round-trip, not one per attestation. - std::map, std::optional> reserve_cache; - auto reserve_info = [&](uint64_t token_code, - uint64_t reserve_code) -> const std::optional& { - const auto cache_key = std::make_pair(token_code, reserve_code); - auto it = reserve_cache.find(cache_key); - if (it != reserve_cache.end()) return it->second; - return reserve_cache.emplace(cache_key, read_reserve_info(token_code, reserve_code)) - .first->second; - }; // 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 @@ -1299,22 +994,6 @@ std::vector> build_dispatch_manif return hook_cache.emplace(custody_mint, read_transfer_hook(custody_mint)).first->second; }; - // One read per DISTINCT collateral position for the whole build. Custody - // is pinned per `(operator, token_code)`, so both values are load-bearing - // cache keys; absent/empty reads are memoised too. - using collateral_key = std::pair; - std::map> collateral_custody_cache; - auto collateral_custody = - [&](const fc::network::solana::solana_public_key& operator_key, - uint64_t token_code) -> const std::optional& { - const auto cache_key = std::make_pair(operator_key, token_code); - auto it = collateral_custody_cache.find(cache_key); - if (it != collateral_custody_cache.end()) return it->second; - return collateral_custody_cache - .emplace(cache_key, read_collateral_custody(operator_key, token_code)) - .first->second; - }; - // One DistributionState read for the whole build -- the pool singleton is // the same for every DESYNDICATE_LIQ in the envelope -- memoised like the // caches above, an absent read included. @@ -1347,49 +1026,6 @@ std::vector> build_dispatch_manif record_terminal_account(metas, key, is_writable); }; - // Collateral-settling shapes (SOL-379/380). Every one resolves the - // per-(operator, token_code) `CollateralPosition` PDA out of - // remaining_accounts and settles in the asset the position escrows. - if (effect.shape == effect_shape::withdraw_remit || effect.shape == effect_shape::slash || - effect.shape == effect_shape::deposit_revert) { - if (!effect.recipient || !effect.collateral_token_code) continue; - const auto collateral_token_code = *effect.collateral_token_code; - // WITHDRAW_REMIT pays the operator and DEPOSIT_REVERT refunds the - // depositor; SLASH pays nobody directly -- its native seizure lands - // in the named `reserve_aggregate`. - if (effect.shape != effect_shape::slash) add(*effect.recipient, true); - add(derive_collateral_position_pda(program_id, *effect.recipient, - collateral_token_code), - true); - - const auto& custody_opt = collateral_custody(*effect.recipient, collateral_token_code); - if (!custody_opt.has_value()) continue; // custody lookup already wlogged - const auto& custody = *custody_opt; - if (is_native_custody(custody.mint)) continue; - - // SPL custody: the collateral vault drains into the destination's - // canonical ATA -- the operator's for WITHDRAW_REMIT, the - // depositor's for DEPOSIT_REVERT, the `reserve_aggregate`'s for - // SLASH. - // - // LOCK-STEP: which shapes settle SPL here must match the program's - // handlers (`resolve_collateral_vault_transfer` callers in - // wire-solana `inbound.rs`) exactly -- the relay's shape decisions - // are unversioned against the program, and a handler that - // `require_remaining_account`s an account this manifest omits aborts - // the dispatch round and re-packs the identical window from the same - // cursor on every retry. A program-side settlement-policy change and - // this manifest must move together. - const auto settlement_owner = - effect.shape == effect_shape::slash ? reserve_aggregate : *effect.recipient; - add(derive_collateral_vault_pda(program_id, collateral_token_code), true); - add(fc::network::solana::system::get_associated_token_address( - settlement_owner, custody.mint), - true); - add(token_program_id, false); - continue; - } - // DESYNDICATE_LIQ: the depot releases a user's syndicated liqSOL. Everything // derives from the user's pubkey, the program's fixed pool PDAs and the mint // pinned on `DistributionState` -- the account the handler itself binds the @@ -1467,143 +1103,6 @@ std::vector> build_dispatch_manif continue; } - if (!effect.reserve) continue; - - const auto token_code = effect.reserve->token_code; - const auto reserve_code = effect.reserve->reserve_code; - - // Every reserve-backed handler loads the Reserve PDA first. - add(derive_reserve_pda(program_id, token_code, reserve_code), true); - if (effect.shape == effect_shape::reserve_ready) continue; - - const auto vault_pda = derive_reserve_vault_pda(program_id, token_code, reserve_code); - - const auto& info_opt = reserve_info(token_code, reserve_code); - if (!info_opt.has_value()) { - // ONE cause reaches here: the Reserve account is ABSENT or EMPTY (see - // `reserve_info_for_codes`, which throws on every other cause rather - // than degrade). That case is benign by construction — the program's - // `load_reserve_from_remaining` finds the Reserve PDA we DO pass, - // fails to deserialize an uninitialized account, and returns - // `Ok(None)`, which every reserve-backed handler turns into a logged - // skip. No account we omit below is ever reached. - // - // The accounts are still passed because the reserve may be created - // between this read and the crank landing. If it is, this manifest is - // complete only for a NATIVE swap remit/revert (Reserve PDA + recipient); - // an SPL one is short the recipient ATA, the custody mint and the custody - // token program — all three are read off the Reserve we could not decode, - // and the ATA is not even derivable without the token program (SOL-396) — - // and a RESERVE_CREATE_CANCELLED is short the `creator` on EITHER custody - // kind, since the creator is read off the Reserve we could not decode. - // Those windows abort and the next tick rebuilds them against - // a now-readable Reserve. - // - // The degrade is PER-ATTESTATION: the manifests are built for every - // other attestation too, so the envelope's healthy PREFIX — the - // attestations ahead of this one in dispatch order — still settles. - // Nothing behind an aborting window can settle until it clears, since - // `drive_dispatch_rounds` repacks each window from the on-chain - // cursor. - wlog("outpost_solana_client[{}]: Reserve({}, {}) absent or empty while building the " - "terminal manifest for attestation {}; passing derivable accounts only -- the " - "handler will log-and-skip an uninitialized reserve", - log_label, token_code, reserve_code, effect.attestation_index); - add(vault_pda, true); - if (effect.recipient) add(*effect.recipient, true); - // Best-effort only, and right solely for a legacy-SPL reserve: the real - // token program is pinned on the Reserve we could not read. Not the - // sanctioned derivation — see `custody_token_program` below. - add(token_program_id, false); - continue; - } - const auto& info = *info_opt; - - // SOL-396 LOCK-STEP: every reserve-backed SPL settlement routes through the - // program's `reserve_vault_transfer`, which requires BOTH the reserve's - // `custody_token_program` AND its `custody_mint` in remaining_accounts, and - // derives the destination ATA with that token program. Both facts come from - // the Reserve — never the legacy SPL-Token default — because the same - // (owner, mint) pair has different canonical ATAs under each program. - const auto& custody_token_program = info.custody_token_program; - const auto custody_ata = [&](const fc::network::solana::solana_public_key& owner) { - return fc::network::solana::system::get_associated_token_address( - owner, info.custody_mint, custody_token_program); - }; - - // SOL-396, one layer deeper: `reserve_vault_transfer` routes through - // `spl_token_2022::onchain::invoke_transfer_checked`, which for a mint - // carrying the TransferHook extension resolves the hook program, its - // validation PDA and every account that PDA declares OUT OF - // `remaining_accounts`. A hook mint whose extras are missing aborts the - // CPI before the transfer -- the same wedge, with the cursor pinned. - // - // No-op for every non-hook mint, which is all of them today: the reader - // returns nullopt and nothing is appended. - const auto add_hook_accounts = [&](const fc::network::solana::solana_public_key& source, - const fc::network::solana::solana_public_key& destination, - const fc::network::solana::solana_public_key& authority) { - // A legacy-SPL reserve cannot carry a Token-2022 extension, so there is - // no hook to find and no reason to pay a `get_account_info` -- nor to - // let a transient absent read fail the WHOLE build (the reader asserts - // rather than degrading). Gating here keeps that assert scoped to the - // mints where a hook is actually representable. - if (info.custody_token_program != token_2022_program_id) return; - const auto& hook = transfer_hook(info.custody_mint); - if (!hook.has_value()) return; - const auto validation_pda = - derive_extra_account_metas_pda(hook->program, info.custody_mint); - add(hook->program, false); - add(validation_pda, false); - for (const auto& meta : resolve_hook_metas(hook->declared, hook->program, source, - info.custody_mint, destination, authority, - validation_pda)) { - add(meta.key, meta.is_writable); - } - }; - - switch (effect.shape) { - case effect_shape::swap_remit: { - if (!effect.recipient) break; - if (is_native_custody(info.custody_mint)) { add(*effect.recipient, true); break; } - add(vault_pda, true); - add(custody_ata(*effect.recipient), true); - add_hook_accounts(vault_pda, custody_ata(*effect.recipient), vault_pda); - // The mint is REQUIRED here, not optional: `reserve_vault_transfer` - // `require_remaining_account`s it, and the CPI's `transfer_checked` - // needs the mint account to verify the decimals against — the decimals - // themselves come from the Reserve record, not from this account. - // Omitting it is what aborted every SPL swap remit with - // `EffectAccountMissing` after SOL-396 — the one reserve-backed shape - // that had never carried it. - add(info.custody_mint, false); - add(custody_token_program, false); - break; - } - case effect_shape::swap_revert: { - if (!effect.recipient) break; - if (is_native_custody(info.custody_mint)) { add(*effect.recipient, true); break; } - add(vault_pda, true); - add(info.custody_mint, false); - add(*effect.recipient, true); - add(custody_ata(*effect.recipient), true); - add(custody_token_program, false); - add_hook_accounts(vault_pda, custody_ata(*effect.recipient), vault_pda); - break; - } - case effect_shape::reserve_create_cancelled: { - if (is_native_custody(info.custody_mint)) { add(info.creator, true); break; } - add(vault_pda, true); - add(info.creator, false); - add(custody_ata(info.creator), true); - add(info.custody_mint, false); - add(custody_token_program, false); - add_hook_accounts(vault_pda, custody_ata(info.creator), vault_pda); - break; - } - default: - break; - } } return per_attestation; @@ -1761,18 +1260,6 @@ outpost_solana_client::outpost_solana_client( // a dlog -- the outpost would stop settling with no error anywhere. // Fail at boot instead, where the IDL is still fixable. outpost_solana_client_detail::assert_epoch_deliveries_shape(*_program_client->get_program()); - // Same reasoning one level down: the crank's manifests resolve custody - // and the cancel-refund target from `Reserve`, and a drifted declaration - // makes a LIVE reserve unreadable to this relay only. In flight that is - // unrecoverable -- the window aborts on the effect account it could not - // derive, and every later window repacks from the same cursor -- so it - // has to be a boot failure, not a first-drain surprise. - outpost_solana_client_detail::assert_reserve_shape(*_program_client->get_program()); - // A drifted CollateralPosition declaration makes a LIVE position's - // pinned custody unreadable and would wedge every collateral dispatch - // window on the same unadvanced cursor. - outpost_solana_client_detail::assert_collateral_position_shape( - *_program_client->get_program()); // `DistributionState` is read only for DESYNDICATE_LIQ manifests, and only // the integrated liqsol-core program declares it -- a standalone outpost // IDL has no syndicated pool to release from, and its DistributionState @@ -1850,70 +1337,6 @@ outpost_solana_client::read_epoch_dispatch_progress(uint32_t epoch_index) { return progress; } -std::optional -outpost_solana_client::reserve_info_for_codes(uint64_t token_code, uint64_t reserve_code) { - const auto reserve_pda = - outpost_solana_client_detail::derive_reserve_pda(_program_id, token_code, reserve_code); - const auto pda_label = reserve_pda.to_string(fc::yield_function_t{}); - - // The two ways this can fail are NOT interchangeable, and conflating them - // is what turns a bad reserve into a wedged epoch: - // - // * ABSENT / EMPTY -> degrade. The program's `load_reserve_from_remaining` - // fails to deserialize an uninitialized account and returns `Ok(None)`, - // which every reserve-backed handler turns into a logged skip. Passing a - // partial manifest costs nothing, because no omitted account is reached. - // - // * PRESENT BUT UNREADABLE BY US -> throw. The account exists and the - // PROGRAM decodes it fine (its Borsh is authoritative), so it takes the - // real branch and demands the accounts that branch needs -- the custody - // ATA for an SPL remit/revert, the `creator` for a cancel with an escrow - // to refund, on EITHER custody kind. A degraded manifest is then - // guaranteed to hit `require_remaining_account` -> EffectAccountMissing - // and abort. That is not a retryable blip: `drive_dispatch_rounds` - // repacks every window from the on-chain cursor, so the same unreadable - // attestation heads every future window and the epoch never closes. - // Failing the tick loudly leaves the cursor untouched and keeps the - // envelope re-drivable once the cause (realistically IDL drift, which - // `assert_reserve_shape` now catches at boot) is fixed. - // - // `get_account_info` sits OUTSIDE the try on purpose: an RPC or deadline - // exception is not a statement about this reserve, and `deadline_scope` - // MUST propagate so an over-deadline build stops instead of degrading every - // remaining attestation into an aborting manifest. - const auto account_info = _entry->client->get_account_info(reserve_pda); - if (!account_info.has_value() || account_info->data.empty()) { - wlog("outpost_solana_client[{}]: Reserve({}, {}) absent or empty at {}; terminal manifest " - "will omit branch-specific accounts for this reserve -- the handler log-and-skips an " - "uninitialized reserve", - to_string(), token_code, reserve_code, pda_label); - return std::nullopt; - } - - try { - const auto reserve_v = - _program_client->decode_account_info_data(reserve_account::account_name, account_info->data); - const auto& reserve = reserve_v.get_object(); - - // Custody is read from the RESERVE, never from the mutable - // `OutpostConfig` token maps: `handle_swap_remit`, `handle_swap_revert` - // and `handle_reserve_create_cancelled` all branch on - // `reserve.custody_mint`, so resolving it anywhere else lets an admin - // re-point of a token address between reserve creation and dispatch put - // the relay on the native branch while the program takes the SPL one -- - // the missing vault/ATA accounts then abort the call permanently. - return outpost_solana_client_detail::reserve_info_from_account(reserve); - } catch (const fc::exception& e) { - elog("outpost_solana_client[{}]: Reserve({}, {}) at {} EXISTS ({} bytes) but this relay " - "cannot read it; refusing to build a manifest the program is guaranteed to abort on. " - "The dispatch cursor is left untouched and this epoch cannot settle until the cause is " - "fixed -- check the loaded IDL against the deployed program: {}", - to_string(), token_code, reserve_code, pda_label, account_info->data.size(), - e.to_detail_string()); - throw; - } -} - std::string outpost_solana_client::send_dispatch_attestations( uint32_t epoch_index, uint32_t dispatch_limit, @@ -1933,8 +1356,6 @@ std::string outpost_solana_client::send_dispatch_attestations( {"outbound_message_buffer", _program_client->outbound_message_buffer_pda}, {"outbound_envelopes", _program_client->outbound_envelopes_pda}, {"latest_outbound_envelope", _program_client->latest_outbound_envelope_pda}, - {"vault", _program_client->vault_pda}, - {"reserve_aggregate", _program_client->reserve_pda}, }; const auto& instr = _program_client->get_idl("dispatch_attestations"); fc::network::solana::program_invoke_data_items params = { @@ -2010,17 +1431,10 @@ std::string outpost_solana_client::drain_dispatch( effects, total_attestations, [&] { throw_if_past_deadline(deadline_abs, OP_DISPATCH_ATTESTATIONS); }, - [&](uint64_t token_code, uint64_t reserve_code) { - return reserve_info_for_codes(token_code, reserve_code); - }, - [&](const fc::network::solana::solana_public_key& operator_key, uint64_t token_code) { - return collateral_position_custody(operator_key, token_code); - }, [&](const fc::network::solana::solana_public_key& custody_mint) { return mint_transfer_hook_for(custody_mint); }, [&] { return syndicated_liq_pool(); }, - _program_client->reserve_pda, to_string()); // Settlement is a SEPARATE instruction, driven from the on-chain cursor. @@ -2085,46 +1499,6 @@ outpost_solana_client::mint_transfer_hook_for( outpost_solana_client_detail::parse_extra_account_metas(validation_info->data)}; } -std::optional -outpost_solana_client::collateral_position_custody( - const fc::network::solana::solana_public_key& operator_key, uint64_t token_code) { - const auto position_pda = outpost_solana_client_detail::derive_collateral_position_pda( - _program_id, operator_key, token_code); - const auto pda_label = position_pda.to_string(fc::yield_function_t{}); - - // An RPC/deadline exception is not evidence that this position is absent, - // so the read deliberately sits outside the decode-only try/catch. - const auto account_info = _entry->client->get_account_info(position_pda); - if (!account_info.has_value() || account_info->data.empty()) { - wlog("outpost_solana_client[{}]: CollateralPosition({}, {}) absent or empty at {}; " - "terminal manifest will omit branch-specific accounts for this position -- the " - "handler log-and-skips an uninitialized position", - to_string(), operator_key.to_string(fc::yield_function_t{}), token_code, pda_label); - return std::nullopt; - } - - try { - const auto position_v = _program_client->decode_account_info_data( - collateral_position::account_name, account_info->data); - const auto& position = position_v.get_object(); - FC_ASSERT(position.contains(collateral_position::field_custody_mint), - "CollateralPosition account missing '{}' field", - collateral_position::field_custody_mint); - return outpost_solana_client_detail::token_custody_info{ - fc::network::solana::solana_public_key::from_base58_string( - position[collateral_position::field_custody_mint].as_string())}; - } catch (const fc::exception& e) { - elog("outpost_solana_client[{}]: CollateralPosition({}, {}) at {} EXISTS ({} bytes) but " - "this relay cannot read its pinned custody; refusing to build a manifest the program " - "is guaranteed to abort on. The dispatch cursor is left untouched and this epoch " - "cannot settle until the cause is fixed -- check the loaded IDL against the deployed " - "program: {}", - to_string(), operator_key.to_string(fc::yield_function_t{}), token_code, pda_label, - account_info->data.size(), e.to_detail_string()); - throw; - } -} - std::optional outpost_solana_client::syndicated_liq_pool() { const auto state_pda = @@ -2314,7 +1688,6 @@ std::string outpost_solana_client::deliver_outbound_envelope( return last_sig; } - std::vector outpost_solana_client::read_inbound_envelope( uint32_t epoch_index, fc::microseconds deadline) { @@ -2398,25 +1771,4 @@ std::vector outpost_solana_client::read_inbound_envelope( *_program_client, info->data, epoch_index, to_string()); } -std::string outpost_solana_client::uw_commit( - uint64_t uw_request_id, - const std::vector& uic_bytes, - fc::microseconds deadline) { - const auto deadline_abs = fc::time_point::now() + deadline; - fc::task::deadline_scope rpc_deadline(deadline_abs); - - throw_if_past_deadline(deadline_abs, OP_UW_COMMIT); - - // Submit the original canonical UIC bytes unchanged. The on-chain - // `commit_underwrite` handler decodes them and binds the signed SVM caller - // plus claimed ACTIVE roster identity before relaying them. The typed - // wrapper supplies the IDL-derived signer, operator-registry, and outbound - // message-buffer accounts; the underwriter does not override that list. - std::vector uic_bytes_u8(uic_bytes.begin(), uic_bytes.end()); - auto signature = _program_client->commit_underwrite(std::move(uic_bytes_u8)); - ilog("outpost_solana_client[{}]: uw_commit confirmed uwreq={} sig={} bytes={}", - to_string(), uw_request_id, signature, uic_bytes.size()); - return signature; -} - } // namespace sysio 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 bc5b922d37..78b07b9508 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 @@ -227,7 +227,6 @@ transaction build_measured_legacy_transaction(const std::vector& in return tx; } - } // anonymous namespace BOOST_AUTO_TEST_SUITE(outpost_solana_client_plugin) @@ -280,7 +279,7 @@ BOOST_AUTO_TEST_CASE(authenticated_caller_address_returns_configured_signer_pubk {load_idl_fixture(opp_outpost_idl_fixture)}, 1, 1, - sysio::solana_outpost_role::underwriter); + sysio::solana_outpost_role::batch_operator); const auto expected_caller = sol_client->get_pubkey().serialize(); const auto actual_caller = outpost.authenticated_caller_address(); @@ -432,7 +431,6 @@ BOOST_AUTO_TEST_CASE(can_load_counter_anchor_idl) try { BOOST_CHECK(has_initialize); BOOST_CHECK(has_increment); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(can_load_opp_outpost_idl) try { auto prog = load_idl_fixture(opp_outpost_idl_fixture); @@ -444,7 +442,6 @@ BOOST_AUTO_TEST_CASE(can_load_opp_outpost_idl) try { BOOST_CHECK(has_epoch_in); BOOST_CHECK(has_emit); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(filter_outpost_program_idls_selects_configured_name) try { std::vector>> idl_files; idl_files.emplace_back("counter.json", @@ -469,97 +466,15 @@ BOOST_AUTO_TEST_CASE(filter_outpost_program_idls_selects_configured_name) try { BOOST_CHECK(sysio::filter_outpost_program_idls(idl_files, "liqsol_core").empty()); BOOST_CHECK(sysio::filter_outpost_program_idls({}, sysio::OPP_SOLANA_OUTPOST_PROGRAM_NAME).empty()); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(outpost_program_name_default_is_opp_outpost) try { // The option default is back-compat load-bearing: pre-cleanroom deployments // pass no --solana-outpost-program-name and must keep matching opp_outpost. BOOST_CHECK_EQUAL(std::string{sysio::OPP_SOLANA_OUTPOST_PROGRAM_NAME}, "opp_outpost"); } FC_LOG_AND_RETHROW(); - namespace { -/// A decoded `Reserve` account object as libfc's IDL decoder renders it: -/// pubkeys as base58 strings, `custody_decimals` as an integer. -fc::variant_object reserve_row(const solana_public_key& creator, - const solana_public_key& custody_mint, - unsigned custody_decimals, - const solana_public_key& custody_token_program = - system::program_ids::TOKEN_PROGRAM) { - return fc::mutable_variant_object("creator", creator.to_string(fc::yield_function_t{}))( - "custody_mint", custody_mint.to_string(fc::yield_function_t{}))( - "custody_decimals", custody_decimals)( - "custody_token_program", custody_token_program.to_string(fc::yield_function_t{})); } -} // anonymous namespace - -BOOST_AUTO_TEST_CASE(reserve_info_reads_custody_from_the_reserve_account) try { - using sysio::outpost_solana_client_detail::reserve_info_from_account; - - const auto creator = measurement_pubkey(76); - const auto spl_mint = measurement_pubkey(77); - const auto native_marker = system::program_ids::SYSTEM_PROGRAM; - - // SPL custody: the mint and decimals PINNED on the reserve at creation -- - // the same two values `handle_swap_remit_spl` / `handle_swap_revert_spl` - // are handed off `reserve.custody_mint` / `reserve.custody_decimals`. - const auto spl = reserve_info_from_account(reserve_row(creator, spl_mint, 6)); - BOOST_CHECK(spl.creator == creator); - BOOST_CHECK(spl.custody_mint == spl_mint); - BOOST_CHECK_EQUAL(static_cast(spl.custody_decimals), 6u); - BOOST_CHECK(spl.custody_token_program == system::program_ids::TOKEN_PROGRAM); - - // SOL-396: a Token-2022 reserve must round-trip its OWN token program -- - // the ATA derivation and the token-program account both hang off this, and - // silently substituting the legacy default names accounts the program never - // asks for (EffectAccountMissing, on every retry, forever). - const auto t22_program = measurement_pubkey(80); - const auto t22 = reserve_info_from_account(reserve_row(creator, spl_mint, 6, t22_program)); - BOOST_CHECK(t22.custody_token_program == t22_program); - - // Native custody is the all-zero `NATIVE_TOKEN_MARKER`, which is exactly - // what the handler compares against to take the lamport branch. - const auto native = reserve_info_from_account(reserve_row(creator, native_marker, 9)); - BOOST_CHECK(native.custody_mint == native_marker); - BOOST_CHECK_EQUAL(static_cast(native.custody_decimals), 9u); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(reserve_info_requires_every_custody_field) try { - using sysio::outpost_solana_client_detail::reserve_info_from_account; - - const auto creator = measurement_pubkey(78); - const auto spl_mint = measurement_pubkey(79); - - // A record missing ANY of the three is not one this relay can build an - // account-consistent manifest from. Defaulting custody_mint would silently - // mean "native" -- the divergence that makes the on-chain call abort. - BOOST_CHECK_THROW( - reserve_info_from_account(fc::mutable_variant_object( - "custody_mint", spl_mint.to_string(fc::yield_function_t{}))("custody_decimals", 6)), - fc::assert_exception); - BOOST_CHECK_THROW( - reserve_info_from_account(fc::mutable_variant_object( - "creator", creator.to_string(fc::yield_function_t{}))("custody_decimals", 6)), - fc::assert_exception); - BOOST_CHECK_THROW( - reserve_info_from_account(fc::mutable_variant_object( - "creator", creator.to_string(fc::yield_function_t{}))( - "custody_mint", spl_mint.to_string(fc::yield_function_t{}))), - fc::assert_exception); - // ...including the token program: without it the ATA is not derivable at all. - BOOST_CHECK_THROW( - reserve_info_from_account(fc::mutable_variant_object( - "creator", creator.to_string(fc::yield_function_t{}))( - "custody_mint", spl_mint.to_string(fc::yield_function_t{}))("custody_decimals", 6)), - fc::assert_exception); - BOOST_CHECK_THROW(reserve_info_from_account(fc::variant_object{fc::mutable_variant_object{}}), - fc::assert_exception); - // Out-of-byte-range decimals: a decoded row disagreeing with the on-chain - // u8 must be refused, not truncated. - BOOST_CHECK_THROW(reserve_info_from_account(reserve_row(creator, spl_mint, 256)), - fc::assert_exception); -} FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(opp_outpost_epoch_in_has_chunked_args) try { auto prog = load_idl_fixture(opp_outpost_idl_fixture); @@ -603,7 +518,7 @@ BOOST_AUTO_TEST_CASE(opp_outpost_epoch_in_has_chunked_args) try { BOOST_CHECK_EQUAL(dispatch->args[1].name, "dispatch_limit"); // No inbound_envelopes: the crank settles effects, it does not append the // inbound audit record -- that happens once, on the consensus-tipping call. - BOOST_CHECK_EQUAL(dispatch->accounts.size(), 11u); + BOOST_CHECK_EQUAL(dispatch->accounts.size(), 9u); BOOST_CHECK(dispatch->accounts[0].is_signer); BOOST_CHECK_EQUAL(dispatch->accounts[4].name, "chunk_buffer"); @@ -618,7 +533,6 @@ BOOST_AUTO_TEST_CASE(opp_outpost_epoch_in_has_chunked_args) try { BOOST_CHECK_EQUAL(epoch_in->accounts[5].name, "inbound_envelopes"); BOOST_CHECK_EQUAL(epoch_in->accounts[6].name, "system_program"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(opp_outpost_dispatch_attestations_present) try { // The relay's crank path requires the two-phase program: a deployment // whose IDL lacks `dispatch_attestations` must fail at BOOT (the @@ -637,13 +551,12 @@ BOOST_AUTO_TEST_CASE(opp_outpost_dispatch_attestations_present) try { // Accounts: caller (permissionless signer), then the same static outpost // account list the relay overrides by name in send_dispatch_attestations. - BOOST_REQUIRE_EQUAL(dispatch->accounts.size(), 11u); + BOOST_REQUIRE_EQUAL(dispatch->accounts.size(), 9u); BOOST_CHECK_EQUAL(dispatch->accounts[0].name, "caller"); BOOST_CHECK(dispatch->accounts[0].is_signer); BOOST_CHECK_EQUAL(dispatch->accounts[3].name, "epoch_deliveries"); BOOST_CHECK_EQUAL(dispatch->accounts[4].name, "chunk_buffer"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(opp_outpost_cleanup_envelope_chunks_present) try { auto prog = load_idl_fixture(opp_outpost_idl_fixture); @@ -664,7 +577,6 @@ BOOST_AUTO_TEST_CASE(opp_outpost_cleanup_envelope_chunks_present) try { BOOST_CHECK_EQUAL(cleanup->accounts[3].name, "chunk_buffer"); BOOST_CHECK_EQUAL(cleanup->accounts[4].name, "uploader"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(epoch_in_full_data_chunk_fits_packet_limit) try { // A full data chunk must serialise under Solana's raw packet limit. Any // change to the `epoch_in` account list or argument tuple moves the fixed @@ -688,7 +600,6 @@ BOOST_AUTO_TEST_CASE(epoch_in_full_data_chunk_fits_packet_limit) try { const auto packet = build_measured_legacy_transaction(instructions, fee_payer).serialize(); BOOST_CHECK_LE(packet.size(), limits::PACKET_DATA_SIZE); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(dispatch_attestations_full_manifest_fits_packet_limit) try { // MAX_TERMINAL_DYNAMIC_ACCOUNTS is a BUDGET, and a budget nobody measures is // a guess. It counts only the `remaining_accounts` extras -- not the @@ -734,7 +645,6 @@ BOOST_AUTO_TEST_CASE(dispatch_attestations_full_manifest_fits_packet_limit) try << " extras serialises to " << packet.size() << " bytes"); BOOST_CHECK_LE(packet.size(), limits::PACKET_DATA_SIZE); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(envelope_chunk_count_math) try { // The relay derives `total_chunks` as `ceil(total / MAX_CHUNK_BYTES)`. // Verify the arithmetic at sentinel sizes: empty (rejected by the relay @@ -768,7 +678,6 @@ BOOST_AUTO_TEST_CASE(envelope_chunk_count_math) try { const size_t last_chunk_size = 2526 - 3 * sysio::SOLANA_MAX_CHUNK_BYTES; BOOST_CHECK_EQUAL(last_chunk_size, 522u); // 2526 - 2004 = 522 } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(opp_outpost_emit_has_wire_epoch_arg) try { auto prog = load_idl_fixture(opp_outpost_idl_fixture); @@ -780,8 +689,7 @@ BOOST_AUTO_TEST_CASE(opp_outpost_emit_has_wire_epoch_arg) try { BOOST_CHECK_EQUAL(emit->args.size(), 1u); BOOST_CHECK_EQUAL(emit->args[0].name, "wire_epoch_index"); } FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(opp_outpost_has_initialize_and_deposit) try { +BOOST_AUTO_TEST_CASE(opp_outpost_excludes_retired_instruction_entrypoints) try { auto prog = load_idl_fixture(opp_outpost_idl_fixture); // `add_attestation` was retired from the client (attestations flow through @@ -794,9 +702,8 @@ BOOST_AUTO_TEST_CASE(opp_outpost_has_initialize_and_deposit) try { } BOOST_CHECK(has_initialize); BOOST_CHECK(!has_add); - BOOST_CHECK(has_deposit); + BOOST_CHECK(!has_deposit); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(borsh_encode_u32_roundtrip) try { borsh::encoder enc; enc.write_u32(42); @@ -806,7 +713,6 @@ BOOST_AUTO_TEST_CASE(borsh_encode_u32_roundtrip) try { uint32_t val = dec.read_u32(); BOOST_CHECK_EQUAL(val, 42u); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(borsh_encode_bytes_roundtrip) try { std::vector test_data = {0x12, 0x0c, 0x0a, 0x04, 0x08}; borsh::encoder enc; @@ -819,7 +725,6 @@ BOOST_AUTO_TEST_CASE(borsh_encode_bytes_roundtrip) try { auto decoded = dec.read_bytes(); BOOST_CHECK(decoded == test_data); } FC_LOG_AND_RETHROW(); - // ── extract_inbound_effects: the single authoritative envelope decode. // Walks an inbound envelope's attestations in dispatch order and // surfaces one `inbound_effect` per account-needing attestation -- @@ -866,29 +771,30 @@ std::vector envelope_with_entries( /// `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. -sysio::opp::AttestationEntry remit_entry(uint64_t token_code, - const sysio::opp::types::ChainAddress& op_addr) { - sysio::opp::attestations::OperatorAction oa; - oa.set_action_type(sysio::opp::attestations::OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT); - oa.mutable_amount()->set_token_code(token_code); - oa.mutable_amount()->set_amount(777); - *oa.mutable_op_address() = op_addr; +constexpr uint64_t default_desyndication_request_id = 1; + +sysio::opp::AttestationEntry desyndicate_liq_entry(uint64_t token_code, + const sysio::opp::types::ChainAddress& user, + uint64_t request_id = default_desyndication_request_id) { + sysio::opp::attestations::DesyndicateLIQ dl; + dl.set_chain_code(900); + *dl.mutable_user() = user; + dl.mutable_amount()->set_token_code(token_code); + dl.mutable_amount()->set_amount(777); + dl.set_request_id(request_id); std::string body; - oa.SerializeToString(&body); + dl.SerializeToString(&body); sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_OPERATOR_ACTION); + entry.set_type(sysio::opp::types::ATTESTATION_TYPE_DESYNDICATE_LIQ); entry.set_data(std::move(body)); return entry; } -/// Same as `remit_entry` but for SLASH — surfaced with its own shape so the -/// manifest can declare the slashed operator's CollateralPosition PDA (and -/// the reserve_aggregate ATA under SPL custody). -sysio::opp::AttestationEntry slash_entry(uint64_t token_code, +sysio::opp::AttestationEntry remit_entry(uint64_t token_code, const sysio::opp::types::ChainAddress& op_addr) { sysio::opp::attestations::OperatorAction oa; - oa.set_action_type(sysio::opp::attestations::OperatorAction_ActionType_ACTION_TYPE_SLASH); + oa.set_action_type(sysio::opp::attestations::OperatorAction_ActionType_ACTION_TYPE_WITHDRAW_REMIT); oa.mutable_amount()->set_token_code(token_code); oa.mutable_amount()->set_amount(777); *oa.mutable_op_address() = op_addr; @@ -928,87 +834,6 @@ sysio::opp::AttestationEntry operators_mirror_entry() { return entry; } -/// Build a `DEPOSIT_REVERT` entry pointing at `depositor_addr`, refunding -/// `token_code` (keys the CollateralPosition PDA). -sysio::opp::AttestationEntry revert_entry(uint64_t token_code, - const sysio::opp::types::ChainAddress& depositor_addr) { - sysio::opp::attestations::DepositRevert dr; - dr.mutable_refund_amount()->set_token_code(token_code); - dr.mutable_refund_amount()->set_amount(888); - *dr.mutable_depositor() = depositor_addr; - std::string body; - dr.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_DEPOSIT_REVERT); - entry.set_data(std::move(body)); - return entry; -} - -/// Build a `SWAP_REMIT` entry pointing at `recipient_addr`. -sysio::opp::AttestationEntry swap_remit_entry(uint64_t token_code, - uint64_t reserve_code, - const sysio::opp::types::ChainAddress& recipient_addr) { - sysio::opp::attestations::SwapRemit remit; - remit.mutable_amount()->set_token_code(token_code); - remit.mutable_amount()->set_amount(123); - remit.set_reserve_code(reserve_code); - *remit.mutable_recipient() = recipient_addr; - std::string body; - remit.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_SWAP_REMIT); - entry.set_data(std::move(body)); - return entry; -} - -/// Build a `SWAP_REVERT` entry pointing at `depositor_addr`. -sysio::opp::AttestationEntry swap_revert_entry(uint64_t token_code, - uint64_t reserve_code, - const sysio::opp::types::ChainAddress& depositor_addr) { - sysio::opp::attestations::SwapRevert revert; - revert.mutable_refund_amount()->set_token_code(token_code); - revert.mutable_refund_amount()->set_amount(456); - revert.set_source_reserve_code(reserve_code); - *revert.mutable_depositor() = depositor_addr; - std::string body; - revert.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_SWAP_REVERT); - entry.set_data(std::move(body)); - return entry; -} - -/// Build a `RESERVE_READY` entry. -sysio::opp::AttestationEntry reserve_ready_entry(uint64_t token_code, uint64_t reserve_code) { - sysio::opp::attestations::ReserveReady ready; - ready.set_token_code(token_code); - ready.set_reserve_code(reserve_code); - std::string body; - ready.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_RESERVE_READY); - entry.set_data(std::move(body)); - return entry; -} - -/// Build a `RESERVE_CREATE_CANCELLED` entry. -sysio::opp::AttestationEntry reserve_create_cancelled_entry(uint64_t token_code, uint64_t reserve_code) { - sysio::opp::attestations::ReserveCreateCancelled cancelled; - cancelled.set_token_code(token_code); - cancelled.set_reserve_code(reserve_code); - std::string body; - cancelled.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED); - entry.set_data(std::move(body)); - return entry; -} - std::array filled_pubkey(uint8_t byte) { std::array arr{}; arr.fill(byte); @@ -1022,39 +847,6 @@ BOOST_AUTO_TEST_CASE(extract_effects_empty_envelope_returns_empty) try { auto effects = sysio::outpost_solana_client_detail::extract_inbound_effects(envelope); BOOST_CHECK(effects.empty()); } FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_single_withdraw_remit) try { - namespace detail = sysio::outpost_solana_client_detail; - auto op_pk = filled_pubkey(0xAA); - auto envelope = envelope_with_entries({remit_entry(501, make_sol_addr(op_pk))}); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 1u); - BOOST_CHECK_EQUAL(effects[0].attestation_index, 0u); - BOOST_CHECK(effects[0].shape == detail::effect_shape::withdraw_remit); - BOOST_REQUIRE(effects[0].recipient.has_value()); - BOOST_CHECK(effects[0].recipient->serialize() == op_pk); - BOOST_CHECK(!effects[0].reserve.has_value()); - // SOL-379: the remit amount's token_code keys the CollateralPosition PDA. - BOOST_REQUIRE(effects[0].collateral_token_code.has_value()); - BOOST_CHECK_EQUAL(*effects[0].collateral_token_code, 501u); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_deposit_revert) try { - namespace detail = sysio::outpost_solana_client_detail; - auto depositor_pk = filled_pubkey(0xBB); - auto envelope = envelope_with_entries({revert_entry(503, make_sol_addr(depositor_pk))}); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 1u); - BOOST_CHECK(effects[0].shape == detail::effect_shape::deposit_revert); - BOOST_REQUIRE(effects[0].recipient.has_value()); - BOOST_CHECK(effects[0].recipient->serialize() == depositor_pk); - // SOL-379: the refund amount's token_code keys the CollateralPosition PDA. - BOOST_REQUIRE(effects[0].collateral_token_code.has_value()); - BOOST_CHECK_EQUAL(*effects[0].collateral_token_code, 503u); -} FC_LOG_AND_RETHROW(); - /// The effect index MUST equal the attestation's flat position in dispatch /// order, counting attestations that need no effect accounts. /// @@ -1075,9 +867,9 @@ BOOST_AUTO_TEST_CASE(extract_effects_indices_track_dispatch_order) try { // index 1 on chain, so the entries either side must report 0 and 2, not // 0 and 1. auto envelope = envelope_with_entries({ - remit_entry(601, make_sol_addr(remit_op)), + desyndicate_liq_entry(601, make_sol_addr(remit_op)), operators_mirror_entry(), - revert_entry(602, make_sol_addr(revert_op)), + desyndicate_liq_entry(602, make_sol_addr(revert_op)), }); BOOST_CHECK_EQUAL(detail::count_inbound_attestations(envelope), 3u); @@ -1086,16 +878,15 @@ BOOST_AUTO_TEST_CASE(extract_effects_indices_track_dispatch_order) try { BOOST_REQUIRE_EQUAL(effects.size(), 2u); BOOST_CHECK_EQUAL(effects[0].attestation_index, 0u); - BOOST_CHECK(effects[0].shape == detail::effect_shape::withdraw_remit); + BOOST_CHECK(effects[0].shape == detail::effect_shape::desyndicate_liq); BOOST_REQUIRE(effects[0].recipient.has_value()); BOOST_CHECK(effects[0].recipient->serialize() == remit_op); BOOST_CHECK_EQUAL(effects[1].attestation_index, 2u); - BOOST_CHECK(effects[1].shape == detail::effect_shape::deposit_revert); + BOOST_CHECK(effects[1].shape == detail::effect_shape::desyndicate_liq); BOOST_REQUIRE(effects[1].recipient.has_value()); BOOST_CHECK(effects[1].recipient->serialize() == revert_op); } FC_LOG_AND_RETHROW(); - /// An envelope the decoder cannot parse yields no effects AND a zero count, /// so the relay submits a terminal call claiming nothing rather than one /// claiming attestations whose accounts it never derived. @@ -1105,7 +896,6 @@ BOOST_AUTO_TEST_CASE(extract_effects_on_undecodable_envelope_is_empty) try { BOOST_CHECK_EQUAL(detail::count_inbound_attestations(garbage), 0u); BOOST_CHECK(detail::extract_inbound_effects(garbage).empty()); } FC_LOG_AND_RETHROW(); - 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); @@ -1117,8 +907,8 @@ BOOST_AUTO_TEST_CASE(extract_effects_repeated_recipient_yields_one_entry_per_att // batch's manifests union (pinned by // record_terminal_account_dedupes_and_merges_writable). auto envelope = envelope_with_entries({ - remit_entry(600, make_sol_addr(op_pk)), - remit_entry(600, make_sol_addr(op_pk)), + desyndicate_liq_entry(600, make_sol_addr(op_pk)), + desyndicate_liq_entry(600, make_sol_addr(op_pk)), }); const auto effects = detail::extract_inbound_effects(envelope); @@ -1128,60 +918,13 @@ 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(); - -BOOST_AUTO_TEST_CASE(extract_effects_slash_carries_collateral_position_key) try { - namespace detail = sysio::outpost_solana_client_detail; - // SLASH MUST be surfaced (SOL-379/380): the handler resolves the slashed - // operator's per-(operator, token_code) CollateralPosition PDA out of - // remaining_accounts, and under SPL custody additionally the collateral - // vault + reserve_aggregate ATA. An omitted manifest entry would abort - // the dispatch call on-chain and stall the cursor on this attestation. - auto slash_op = filled_pubkey(0xDD); - auto remit_op = filled_pubkey(0xEE); - auto envelope = envelope_with_entries({ - slash_entry(502, make_sol_addr(slash_op)), - remit_entry(504, make_sol_addr(remit_op)), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 2u); - - BOOST_CHECK_EQUAL(effects[0].attestation_index, 0u); - BOOST_CHECK(effects[0].shape == detail::effect_shape::slash); - BOOST_REQUIRE(effects[0].recipient.has_value()); - BOOST_CHECK(effects[0].recipient->serialize() == slash_op); - BOOST_REQUIRE(effects[0].collateral_token_code.has_value()); - BOOST_CHECK_EQUAL(*effects[0].collateral_token_code, 502u); - - BOOST_CHECK_EQUAL(effects[1].attestation_index, 1u); - BOOST_CHECK(effects[1].shape == detail::effect_shape::withdraw_remit); -} FC_LOG_AND_RETHROW(); - namespace { /// 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) { - sysio::opp::attestations::DesyndicateLIQ dl; - dl.set_chain_code(900); - *dl.mutable_user() = user; - dl.mutable_amount()->set_token_code(token_code); - dl.mutable_amount()->set_amount(777); - dl.set_request_id(request_id); - std::string body; - dl.SerializeToString(&body); - - sysio::opp::AttestationEntry entry; - entry.set_type(sysio::opp::types::ATTESTATION_TYPE_DESYNDICATE_LIQ); - entry.set_data(std::move(body)); - return entry; -} } // namespace @@ -1202,39 +945,17 @@ BOOST_AUTO_TEST_CASE(extract_effects_desyndicate_liq_carries_the_user) try { }); const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 2u); + BOOST_REQUIRE_EQUAL(effects.size(), 1u); BOOST_CHECK_EQUAL(effects[0].attestation_index, 0u); BOOST_CHECK(effects[0].shape == detail::effect_shape::desyndicate_liq); BOOST_REQUIRE(effects[0].recipient.has_value()); BOOST_CHECK(effects[0].recipient->serialize() == user); - BOOST_CHECK(!effects[0].reserve.has_value()); - BOOST_CHECK(!effects[0].collateral_token_code.has_value()); // The request id keys the pending-payout PDA a frozen outpost stores the payout at. BOOST_REQUIRE(effects[0].desyndication_request_id.has_value()); BOOST_CHECK_EQUAL(*effects[0].desyndication_request_id, request_id); - BOOST_CHECK_EQUAL(effects[1].attestation_index, 2u); - BOOST_CHECK(effects[1].shape == detail::effect_shape::withdraw_remit); - BOOST_CHECK(!effects[1].desyndication_request_id.has_value()); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_keeps_distinct_collateral_token_codes) try { - namespace detail = sysio::outpost_solana_client_detail; - // One operator withdrawing two different token_codes resolves TWO - // distinct CollateralPosition PDAs — each effect carries its own key. - auto op_pk = filled_pubkey(0x75); - auto envelope = envelope_with_entries({ - remit_entry(700, make_sol_addr(op_pk)), - remit_entry(701, make_sol_addr(op_pk)), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 2u); - BOOST_CHECK_EQUAL(*effects[0].collateral_token_code, 700u); - BOOST_CHECK_EQUAL(*effects[1].collateral_token_code, 701u); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(extract_effects_skips_non_settling_operator_actions) try { namespace detail = sysio::outpost_solana_client_detail; // A DEPOSIT_REQUEST is outbound-from-outpost — inbound it settles @@ -1246,23 +967,19 @@ BOOST_AUTO_TEST_CASE(extract_effects_skips_non_settling_operator_actions) try { }); const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 1u); - BOOST_CHECK_EQUAL(effects[0].attestation_index, 1u); - BOOST_CHECK(effects[0].shape == detail::effect_shape::withdraw_remit); + 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 // for this outpost and must not contribute a SOL effect entry. auto eth_bytes = filled_pubkey(0x01); auto envelope = envelope_with_entries({ - remit_entry(800, make_eth_addr_32(eth_bytes)), + desyndicate_liq_entry(800, make_eth_addr_32(eth_bytes)), }); auto effects = sysio::outpost_solana_client_detail::extract_inbound_effects(envelope); BOOST_CHECK(effects.empty()); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(extract_effects_skips_malformed_address_length) try { // 20-byte address with kind=SOLANA — the bytes pass the chain check // but fail the length check; the decoder must drop the entry rather @@ -1272,125 +989,11 @@ BOOST_AUTO_TEST_CASE(extract_effects_skips_malformed_address_length) try { std::vector short_addr(20, 0xAB); malformed.set_address(short_addr.data(), short_addr.size()); - auto envelope = envelope_with_entries({remit_entry(801, malformed)}); + auto envelope = envelope_with_entries({desyndicate_liq_entry(801, malformed)}); auto effects = sysio::outpost_solana_client_detail::extract_inbound_effects(envelope); BOOST_CHECK(effects.empty()); } FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_mixed_remit_and_revert_preserved_order) try { - namespace detail = sysio::outpost_solana_client_detail; - auto op_a = filled_pubkey(0x10); - auto depositor = filled_pubkey(0x20); - auto op_b = filled_pubkey(0x30); - auto envelope = envelope_with_entries({ - remit_entry(901, make_sol_addr(op_a)), - revert_entry(902, make_sol_addr(depositor)), - remit_entry(903, make_sol_addr(op_b)), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 3u); - BOOST_CHECK(effects[0].recipient->serialize() == op_a); - BOOST_CHECK(effects[1].recipient->serialize() == depositor); - BOOST_CHECK(effects[2].recipient->serialize() == op_b); - BOOST_CHECK_EQUAL(effects[0].attestation_index, 0u); - BOOST_CHECK_EQUAL(effects[1].attestation_index, 1u); - BOOST_CHECK_EQUAL(effects[2].attestation_index, 2u); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_swap_shapes_carry_wallets_and_reserve_seeds) try { - namespace detail = sysio::outpost_solana_client_detail; - auto swap_recipient = filled_pubkey(0x41); - auto swap_depositor = filled_pubkey(0x42); - auto withdraw_op = filled_pubkey(0x43); - auto envelope = envelope_with_entries({ - swap_remit_entry(10, 20, make_sol_addr(swap_recipient)), - swap_revert_entry(11, 21, make_sol_addr(swap_depositor)), - remit_entry(12, make_sol_addr(withdraw_op)), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 3u); - - // SWAP_REMIT: recipient wallet + (token, reserve) seeds for the - // Reserve/vault PDA derivations and the recipient ATA. Reserve-backed - // shapes carry no collateral key — custody rides the Reserve record. - BOOST_CHECK(effects[0].shape == detail::effect_shape::swap_remit); - BOOST_CHECK(effects[0].recipient->serialize() == swap_recipient); - BOOST_REQUIRE(effects[0].reserve.has_value()); - BOOST_CHECK_EQUAL(effects[0].reserve->token_code, 10u); - BOOST_CHECK_EQUAL(effects[0].reserve->reserve_code, 20u); - BOOST_CHECK(!effects[0].collateral_token_code.has_value()); - - // SWAP_REVERT: the wallet is the DEPOSITOR (the refund target). - BOOST_CHECK(effects[1].shape == detail::effect_shape::swap_revert); - BOOST_CHECK(effects[1].recipient->serialize() == swap_depositor); - BOOST_REQUIRE(effects[1].reserve.has_value()); - BOOST_CHECK_EQUAL(effects[1].reserve->token_code, 11u); - BOOST_CHECK_EQUAL(effects[1].reserve->reserve_code, 21u); - BOOST_CHECK(!effects[1].collateral_token_code.has_value()); - - BOOST_CHECK(effects[2].shape == detail::effect_shape::withdraw_remit); - BOOST_CHECK(effects[2].recipient->serialize() == withdraw_op); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_reserve_shapes_carry_seeds_per_attestation) try { - namespace detail = sysio::outpost_solana_client_detail; - auto recipient = filled_pubkey(0x51); - auto depositor = filled_pubkey(0x52); - // The repeated RESERVE_READY pair appears TWICE: the walk is - // per-attestation (the cursor counts both); the duplicate Reserve PDA - // merges later in `record_terminal_account`. - auto envelope = envelope_with_entries({ - swap_remit_entry(100, 200, make_sol_addr(recipient)), - swap_revert_entry(101, 201, make_sol_addr(depositor)), - reserve_ready_entry(102, 202), - reserve_create_cancelled_entry(103, 203), - reserve_ready_entry(102, 202), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 5u); - const std::array, 5> expected_seeds{{ - {100u, 200u}, {101u, 201u}, {102u, 202u}, {103u, 203u}, {102u, 202u}}}; - for (size_t i = 0; i < expected_seeds.size(); ++i) { - BOOST_REQUIRE(effects[i].reserve.has_value()); - BOOST_CHECK_EQUAL(effects[i].reserve->token_code, expected_seeds[i].first); - BOOST_CHECK_EQUAL(effects[i].reserve->reserve_code, expected_seeds[i].second); - } - BOOST_CHECK(effects[2].shape == detail::effect_shape::reserve_ready); - BOOST_CHECK(!effects[2].recipient.has_value()); - BOOST_CHECK(effects[3].shape == detail::effect_shape::reserve_create_cancelled); - BOOST_CHECK(!effects[3].recipient.has_value()); - BOOST_CHECK(effects[4].shape == detail::effect_shape::reserve_ready); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(extract_effects_reserve_create_cancelled_shape_selects_cancelled_only) try { - namespace detail = sysio::outpost_solana_client_detail; - auto recipient = filled_pubkey(0x53); - auto depositor = filled_pubkey(0x54); - auto envelope = envelope_with_entries({ - swap_remit_entry(110, 210, make_sol_addr(recipient)), - reserve_create_cancelled_entry(111, 211), - swap_revert_entry(112, 212, make_sol_addr(depositor)), - reserve_create_cancelled_entry(111, 211), - reserve_create_cancelled_entry(113, 213), - }); - - const auto effects = detail::extract_inbound_effects(envelope); - BOOST_REQUIRE_EQUAL(effects.size(), 5u); - std::vector> cancelled; - for (const auto& effect : effects) { - if (effect.shape != detail::effect_shape::reserve_create_cancelled) continue; - BOOST_REQUIRE(effect.reserve.has_value()); - cancelled.emplace_back(effect.reserve->token_code, effect.reserve->reserve_code); - } - const std::vector> expected{ - {111u, 211u}, {111u, 211u}, {113u, 213u}}; - BOOST_CHECK(cancelled == expected); -} FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(record_terminal_account_dedupes_and_merges_writable) try { std::vector metas; const solana_public_key readonly_key(filled_pubkey(0x55)); @@ -1414,7 +1017,6 @@ BOOST_AUTO_TEST_CASE(record_terminal_account_dedupes_and_merges_writable) try { BOOST_CHECK(!metas[1].is_signer); 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 @@ -1431,59 +1033,7 @@ namespace manifest_detail = sysio::outpost_solana_client_detail; /// as uninitialized accounts. Every call is recorded so memoization is /// assertable. struct manifest_build_harness { - using seeds = std::pair; - /// One collateral position's full `(operator, token_code)` PDA seed tuple. - using collateral_seeds = std::pair; - solana_public_key program_id = measurement_pubkey(42); - /// The named `reserve_aggregate` account — SPL slash seizures settle into - /// its canonical ATA. - solana_public_key reserve_aggregate = measurement_pubkey(43); - std::map records; - std::vector reads; - std::map collateral_records; - std::vector collateral_reads; - - void put(uint64_t token_code, uint64_t reserve_code, - const solana_public_key& creator, const solana_public_key& custody_mint, - uint8_t custody_decimals, - const solana_public_key& custody_token_program = - system::program_ids::TOKEN_PROGRAM) { - records.emplace(seeds{token_code, reserve_code}, - manifest_detail::reserve_terminal_info{creator, custody_mint, - custody_decimals, - custody_token_program}); - } - - /// Seed one position's pinned custody for the collateral reader. - void put_collateral(const solana_public_key& operator_key, uint64_t token_code, - const solana_public_key& custody_mint) { - collateral_records.emplace(collateral_seeds{operator_key, token_code}, - manifest_detail::token_custody_info{custody_mint}); - } - - manifest_detail::reserve_info_reader reader() { - return [this](uint64_t token_code, - uint64_t reserve_code) -> std::optional { - reads.emplace_back(token_code, reserve_code); - auto it = records.find(seeds{token_code, reserve_code}); - if (it == records.end()) return std::nullopt; - return it->second; - }; - } - - /// Scripted stand-in for `collateral_position_custody`: anything absent - /// from `collateral_records` reads as an absent-position degrade. - manifest_detail::collateral_custody_reader collateral_reader() { - return [this](const solana_public_key& operator_key, - uint64_t token_code) -> std::optional { - const auto key = collateral_seeds{operator_key, token_code}; - collateral_reads.emplace_back(key); - auto it = collateral_records.find(key); - if (it == collateral_records.end()) return std::nullopt; - return it->second; - }; - } /// Per-mint transfer-hook configuration. Empty by default: every mint in /// the system today is hook-free, so the manifest is unchanged unless a @@ -1534,129 +1084,17 @@ struct manifest_build_harness { uint32_t total_attestations, const std::function& deadline_probe = [] {}) { return manifest_detail::build_dispatch_manifests( - program_id, effects, total_attestations, deadline_probe, reader(), - collateral_reader(), hook_reader(), liq_pool_reader(), reserve_aggregate, "test-relay"); + program_id, effects, total_attestations, deadline_probe, + hook_reader(), liq_pool_reader(), "test-relay"); } }; -/// One SWAP_REMIT effect at `index` paying `recipient` out of `(token, reserve)`. -manifest_detail::inbound_effect swap_remit_effect(size_t index, uint64_t token_code, - uint64_t reserve_code, - const solana_public_key& recipient) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::swap_remit, recipient, - manifest_detail::reserve_pda_seeds{token_code, reserve_code}}; -} - -/// One SWAP_REVERT effect at `index` refunding `depositor` out of `(token, reserve)`. -manifest_detail::inbound_effect swap_revert_effect(size_t index, uint64_t token_code, - uint64_t reserve_code, - const solana_public_key& depositor) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::swap_revert, depositor, - manifest_detail::reserve_pda_seeds{token_code, reserve_code}}; -} - -/// One RESERVE_CREATE_CANCELLED effect at `index` for `(token, reserve)`; the -/// refund target is the creator read off the Reserve, not a carried recipient. -manifest_detail::inbound_effect reserve_create_cancelled_effect(size_t index, uint64_t token_code, - uint64_t reserve_code) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::reserve_create_cancelled, std::nullopt, - manifest_detail::reserve_pda_seeds{token_code, reserve_code}}; -} - -/// One WITHDRAW_REMIT effect at `index` paying `recipient` out of their -/// `token_code`-keyed `CollateralPosition`. -manifest_detail::inbound_effect withdraw_remit_effect(size_t index, uint64_t token_code, - const solana_public_key& recipient) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::withdraw_remit, recipient, std::nullopt, - token_code}; -} - -/// One SLASH effect at `index` seizing `operator_key`'s `token_code`-keyed -/// `CollateralPosition`. -manifest_detail::inbound_effect slash_effect(size_t index, uint64_t token_code, - const solana_public_key& operator_key) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::slash, operator_key, std::nullopt, token_code}; -} - -/// One DEPOSIT_REVERT effect at `index` refunding `depositor`'s -/// `token_code`-keyed `CollateralPosition`. -manifest_detail::inbound_effect deposit_revert_effect(size_t index, uint64_t token_code, - const solana_public_key& depositor) { - return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::deposit_revert, depositor, std::nullopt, - token_code}; -} - bool manifest_has(const std::vector& metas, const solana_public_key& key) { return std::any_of(metas.begin(), metas.end(), [&](const account_meta& meta) { return meta.key == key; }); } -} // anonymous namespace - -/// Custody MUST come from the Reserve account, never from a token-code-keyed -/// config map. The program's `handle_swap_remit` branches on -/// `reserve.custody_mint`; if the relay resolved custody from the mutable -/// `OutpostConfig.token_addresses_by_code` instead, an admin re-pointing that -/// token address after the reserve was created would send the relay down the -/// native branch while the program took the SPL one -- and the missing vault -/// and recipient-ATA accounts abort the call permanently, wedging the epoch. -/// -/// The divergence is modelled directly: ONE token_code backs two reserves -/// whose pinned custody disagrees. A config-keyed lookup could only produce -/// one answer for both; following the Reserve produces the right answer twice. -BOOST_AUTO_TEST_CASE(build_manifests_follows_reserve_custody_per_reserve) try { - constexpr uint64_t token_code = 111; - const auto spl_mint = measurement_pubkey(77); - const auto spl_recipient = measurement_pubkey(80); - const auto native_recipient = measurement_pubkey(81); - const auto creator = measurement_pubkey(82); - - manifest_build_harness harness; - harness.put(token_code, 200, creator, spl_mint, 6); // SPL custody - harness.put(token_code, 201, creator, system::program_ids::SYSTEM_PROGRAM, 9); // native custody - - const auto manifests = harness.build( - {swap_remit_effect(0, token_code, 200, spl_recipient), - swap_remit_effect(1, token_code, 201, native_recipient)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - // SPL branch: Reserve PDA + vault + the recipient's ATA FOR THE RESERVE'S - // OWN MINT + the custody mint + the token program. - const auto& spl = manifests[0]; - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_reserve_pda( - harness.program_id, token_code, 200))); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_reserve_vault_pda( - harness.program_id, token_code, 200))); - BOOST_CHECK(manifest_has(spl, system::get_associated_token_address(spl_recipient, spl_mint))); - // The MINT itself. `reserve_vault_transfer` require_remaining_account()s it - // for the checked transfer's decimals, and swap_remit was the one - // reserve-backed shape that never carried it -- every SPL swap remit aborted - // with EffectAccountMissing and the epoch never closed. Asserted here - // because this case previously checked the ATA and token program only, - // which is exactly why the omission shipped. - BOOST_CHECK(manifest_has(spl, spl_mint)); - BOOST_CHECK(manifest_has(spl, system::program_ids::TOKEN_PROGRAM)); - // The bare recipient wallet is the NATIVE branch's account and must not - // appear on the SPL one. - BOOST_CHECK(!manifest_has(spl, spl_recipient)); - - // Native branch, same token_code: Reserve PDA + the recipient wallet, and - // NO vault / ATA / token program. - const auto& native = manifests[1]; - BOOST_CHECK(manifest_has(native, manifest_detail::derive_reserve_pda( - harness.program_id, token_code, 201))); - BOOST_CHECK(manifest_has(native, native_recipient)); - BOOST_CHECK(!manifest_has(native, manifest_detail::derive_reserve_vault_pda( - harness.program_id, token_code, 201))); - BOOST_CHECK(!manifest_has(native, system::program_ids::TOKEN_PROGRAM)); -} FC_LOG_AND_RETHROW(); +} namespace { @@ -1665,7 +1103,7 @@ namespace { manifest_detail::inbound_effect desyndicate_liq_effect(size_t index, const solana_public_key& user, uint64_t request_id) { return manifest_detail::inbound_effect{ - index, manifest_detail::effect_shape::desyndicate_liq, user, std::nullopt, std::nullopt, request_id}; + index, manifest_detail::effect_shape::desyndicate_liq, user, request_id}; } /// Whether `key` rides `metas` as WRITABLE. @@ -1677,6 +1115,29 @@ bool manifest_writes(const std::vector& metas, const solana_public } // namespace +BOOST_AUTO_TEST_CASE(build_liq_manifests_honors_deadline_before_pool_read) try { + manifest_build_harness harness; + BOOST_CHECK_THROW( + harness.build({desyndicate_liq_effect(0, measurement_pubkey(92), 7)}, 1, + [] { FC_THROW_EXCEPTION(fc::timeout_exception, "deadline exceeded"); }), + fc::timeout_exception); + BOOST_CHECK_EQUAL(harness.liq_pool_reads, 0u); + BOOST_CHECK_EQUAL(harness.hook_reads, 0u); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(build_liq_manifests_propagates_pool_rpc_failure) try { + manifest_build_harness harness; + BOOST_CHECK_THROW( + manifest_detail::build_dispatch_manifests( + harness.program_id, {desyndicate_liq_effect(0, measurement_pubkey(92), 7)}, 1, + [] {}, harness.hook_reader(), + []() -> std::optional { + FC_THROW_EXCEPTION(fc::exception, "pool RPC unavailable"); + }, "test-relay"), + fc::exception); + BOOST_CHECK_EQUAL(harness.hook_reads, 0u); +} FC_LOG_AND_RETHROW(); + // The DESYNDICATE_LIQ manifest is `handle_desyndicate_liq`'s // `require_remaining_account` list, derived from the user's pubkey, the // program's fixed pool PDAs and the mint on `DistributionState`: the two state @@ -1745,7 +1206,6 @@ BOOST_AUTO_TEST_CASE(build_manifests_desyndicate_liq_derives_the_pool_accounts) } } } FC_LOG_AND_RETHROW(); - // Without a readable `DistributionState` the manifest carries the two state // singletons only: the handler loads them first and turns an uninitialized // pool into a logged skip, so nothing behind them is derivable or required. @@ -1763,7 +1223,6 @@ BOOST_AUTO_TEST_CASE(build_manifests_desyndicate_liq_degrades_to_the_state_singl BOOST_CHECK(manifest_writes(m, manifest_detail::derive_pending_desyndication_pda(harness.program_id, request_id))); BOOST_CHECK_EQUAL(harness.hook_reads, 0u); } FC_LOG_AND_RETHROW(); - // Request id 0 is "no id": it cannot key a PendingPayout, and the handler records // such a payout as unpaid before it asks for the pending account, so the manifest // carries no pending-payout PDA for it -- and nothing else changes. @@ -1780,7 +1239,7 @@ BOOST_AUTO_TEST_CASE(build_manifests_desyndicate_liq_omits_the_pending_pda_for_r // An effect built without an id at all behaves the same. manifest_detail::inbound_effect{ 1, manifest_detail::effect_shape::desyndicate_liq, user, - std::nullopt, std::nullopt, std::nullopt}}, + std::nullopt}}, 2); BOOST_REQUIRE_EQUAL(manifests.size(), 2u); for (size_t i = 0; i < manifests.size(); ++i) { @@ -1796,7 +1255,6 @@ BOOST_AUTO_TEST_CASE(build_manifests_desyndicate_liq_omits_the_pending_pda_for_r BOOST_REQUIRE_EQUAL(bare.size(), 1u); BOOST_CHECK_EQUAL(bare[0].size(), 2u); } FC_LOG_AND_RETHROW(); - // The pending-payout PDA is derived exactly as wire-solana's // `PendingPayout::find_address` does: `["pending_desyndication", // request_id.to_le_bytes()]` under liqsol-core. The expected addresses were @@ -1830,7 +1288,6 @@ BOOST_AUTO_TEST_CASE(pending_desyndication_pda_matches_the_program_derivation) t liqsol_core) .first); } FC_LOG_AND_RETHROW(); - namespace { /// Pack a seed list into an `ExtraAccountMeta::address_config`, exactly as @@ -1902,17 +1359,14 @@ manifest_detail::extra_account_meta external_pda_meta(uint8_t program_index, /// 2 destination, 3 authority, 4 validation PDA) is load-bearing rather than /// decorative. BOOST_AUTO_TEST_CASE(build_manifests_carry_transfer_hook_accounts_for_a_hook_mint) try { - constexpr uint64_t token_code = 141; - constexpr uint64_t reserve_code = 242; const auto spl_mint = measurement_pubkey(92); const auto recipient = measurement_pubkey(93); - const auto creator = measurement_pubkey(94); const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; const auto hook_program = measurement_pubkey(96); const auto hook_owned = measurement_pubkey(97); // a literal meta, e.g. liqsol_core manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); + harness.put_liq_pool(spl_mint); harness.put_hook(spl_mint, hook_program, {literal_meta(hook_owned, false), // sender's user_record, seeded on the SOURCE token account (index 0) @@ -1921,12 +1375,12 @@ BOOST_AUTO_TEST_CASE(build_manifests_carry_transfer_hook_accounts_for_a_hook_min external_pda_meta(5, {seed_literal{"user_record"}, seed_account_key{2}}, true)}); const auto manifests = - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1); + harness.build({desyndicate_liq_effect(0, recipient, 1)}, 1); BOOST_REQUIRE_EQUAL(manifests.size(), 1u); const auto& m = manifests[0]; - const auto vault = manifest_detail::derive_reserve_vault_pda(harness.program_id, token_code, - reserve_code); + const auto vault = system::get_associated_token_address( + manifest_detail::derive_liqsol_pool_authority_pda(harness.program_id), spl_mint, token_2022); const auto dest_ata = system::get_associated_token_address(recipient, spl_mint, token_2022); const auto validation_pda = @@ -1962,7 +1416,6 @@ BOOST_AUTO_TEST_CASE(build_manifests_carry_transfer_hook_accounts_for_a_hook_min BOOST_CHECK_EQUAL(writable_of(hook_owned), false); BOOST_CHECK_EQUAL(writable_of(hook_program), false); } FC_LOG_AND_RETHROW(); - namespace { /// Build a well-formed `ExtraAccountMetaList` account: the Execute TLV @@ -2020,62 +1473,6 @@ BOOST_AUTO_TEST_CASE(extra_account_meta_parser_refuses_malformed_accounts) try { wrong_disc[0] ^= 0xff; BOOST_CHECK_THROW(parse_extra_account_metas(wrong_disc), fc::assert_exception); } FC_LOG_AND_RETHROW(); - -/// The worst-case hook manifest against the dispatch budget. -/// -/// `swap_revert` and `reserve_create_cancelled` on a hook mint carry the most -/// accounts of any shape, and with the REAL liqSOL hook's five declared metas -/// that comes to 13 of `MAX_TERMINAL_DYNAMIC_ACCOUNTS` (16) -- 15 before the -/// associated-token and system programs were dropped from both shapes, neither -/// of which any handler uses (nothing in `inbound.rs` creates an ATA; both -/// handlers log-and-skip on an uninitialized one). The packer always -/// takes at least one attestation regardless of size, so a manifest over budget -/// is not deferred -- `transaction::serialize` throws locally, which the header -/// documents as "an alarm, not a retry", and the epoch cannot settle at all. -/// -/// One more declared meta, or one more effect account added program-side, spends -/// the last slot. Pinning the number here turns that cliff into a failing test -/// instead of a wedged outpost. -BOOST_AUTO_TEST_CASE(hook_manifest_worst_case_fits_the_dispatch_budget) try { - constexpr uint64_t token_code = 161; - constexpr uint64_t reserve_code = 262; - const auto spl_mint = measurement_pubkey(102); - const auto depositor = measurement_pubkey(103); - const auto creator = measurement_pubkey(104); - const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; - const auto hook_program = measurement_pubkey(106); - const auto core_program = measurement_pubkey(107); - const auto bucket_ata = measurement_pubkey(108); - - manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); - // The real liqSOL declaration (transfer-hook/src/lib.rs): the core program, - // the source and destination `user_record`s, `distribution_state`, and the - // bucket token account. - harness.put_hook(spl_mint, hook_program, - {literal_meta(core_program, false), - external_pda_meta(5, {seed_literal{"user_record"}, seed_account_key{0}}, true), - external_pda_meta(5, {seed_literal{"user_record"}, seed_account_key{2}}, true), - external_pda_meta(5, {seed_literal{"distribution_state"}}, true), - literal_meta(bucket_ata, false)}); - - const auto manifests = harness.build( - {swap_revert_effect(0, token_code, reserve_code, depositor), - reserve_create_cancelled_effect(1, token_code, reserve_code)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - for (const auto& m : manifests) { - BOOST_TEST_CONTEXT("manifest size " << m.size()) { - BOOST_CHECK_LE(m.size(), sysio::MAX_TERMINAL_DYNAMIC_ACCOUNTS); - } - } - // Pinned exactly, not just bounded: a silent creep to 16 would still pass a - // <= check while leaving zero headroom. - BOOST_CHECK_EQUAL(manifests[0].size(), 13u); - BOOST_CHECK_EQUAL(manifests[1].size(), 13u); -} FC_LOG_AND_RETHROW(); - /// The DESYNDICATE_LIQ manifest under the REAL liqSOL hook declaration, pending- /// payout PDA included. Every meta the hook declares resolves to an account the /// handler already requires (liqsol-core, the two `UserRecord`s, @@ -2107,69 +1504,32 @@ BOOST_AUTO_TEST_CASE(desyndicate_liq_manifest_under_the_real_hook_fits_the_dispa BOOST_CHECK(manifest_has(manifests[0], manifest_detail::derive_pending_desyndication_pda(harness.program_id, 9))); } FC_LOG_AND_RETHROW(); - /// A `ExtraAccountMeta` may legally demand a signature. The dispatch /// transaction is signed by the batch operator alone, so the resolver cannot /// satisfy one -- and dropping the flag silently (keeping only key+writable) /// would build a manifest that aborts inside the CPI as an unauthorized signer, /// with the cursor pinned and no local diagnostic. Refuse at build time. BOOST_AUTO_TEST_CASE(hook_manifest_refuses_a_meta_demanding_a_signature) try { - constexpr uint64_t token_code = 171; - constexpr uint64_t reserve_code = 272; const auto spl_mint = measurement_pubkey(120); - const auto creator = measurement_pubkey(121); const auto recipient = measurement_pubkey(122); const auto hook_program = measurement_pubkey(123); const auto needs_sig = measurement_pubkey(124); - const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); + harness.put_liq_pool(spl_mint); // Sanity: the same declaration WITHOUT the signer flag builds fine, so the // rejection below is about the flag and not about the meta being unresolvable. harness.put_hook(spl_mint, hook_program, {literal_meta(needs_sig, false)}); BOOST_CHECK_NO_THROW( - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1)); + harness.build({desyndicate_liq_effect(0, recipient, 1)}, 1)); harness.hooks.clear(); harness.put_hook(spl_mint, hook_program, {signer_meta(needs_sig)}); BOOST_CHECK_THROW( - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1), + harness.build({desyndicate_liq_effect(0, recipient, 1)}, 1), fc::assert_exception); } FC_LOG_AND_RETHROW(); - -/// A legacy-SPL reserve cannot carry a Token-2022 extension, so the mint is -/// never read for a hook. This is not just an economy: the reader ASSERTS on an -/// absent or empty mint account, and that assert fails the whole build -- every -/// attestation in the envelope, not just this effect. Scoping the read to -/// Token-2022 custody keeps a transient read anomaly on a legacy mint from -/// wedging an epoch it has no bearing on. -BOOST_AUTO_TEST_CASE(legacy_spl_custody_never_reads_the_mint_for_a_hook) try { - constexpr uint64_t token_code = 181; - constexpr uint64_t reserve_code = 282; - const auto spl_mint = measurement_pubkey(130); - const auto creator = measurement_pubkey(131); - const auto recipient = measurement_pubkey(132); - const auto hook_program = measurement_pubkey(133); - const auto hook_owned = measurement_pubkey(134); - - manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6); // legacy SPL custody - // Declared anyway: if the gate regressed, these accounts would appear. - harness.put_hook(spl_mint, hook_program, {literal_meta(hook_owned, false)}); - - const auto manifests = - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1); - BOOST_REQUIRE_EQUAL(manifests.size(), 1u); - - BOOST_CHECK_EQUAL(harness.hook_reads, 0u); - for (const auto& meta : manifests[0]) { - BOOST_CHECK(meta.key != hook_program); - BOOST_CHECK(meta.key != hook_owned); - } -} FC_LOG_AND_RETHROW(); - /// The mint TLV walk itself: what it reads, and what it refuses. /// /// The refusals matter for the same reason `parse_extra_account_metas`'s do -- @@ -2232,429 +1592,21 @@ BOOST_AUTO_TEST_CASE(mint_transfer_hook_parser_reads_and_refuses) try { false)), fc::assert_exception); } FC_LOG_AND_RETHROW(); - -/// A hook-free mint -- every mint in the system today -- must be byte-for-byte -/// unchanged by the hook support. Pins that this is additive. -BOOST_AUTO_TEST_CASE(build_manifests_are_unchanged_for_a_hook_free_mint) try { - constexpr uint64_t token_code = 151; - constexpr uint64_t reserve_code = 252; - const auto spl_mint = measurement_pubkey(98); - const auto recipient = measurement_pubkey(99); - const auto creator = measurement_pubkey(100); - const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; - - manifest_build_harness harness; // no put_hook: the reader returns nullopt - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); - - const auto manifests = - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1); - BOOST_REQUIRE_EQUAL(manifests.size(), 1u); - // Reserve PDA + vault + recipient ATA + mint + token program, and nothing else. - BOOST_CHECK_EQUAL(manifests[0].size(), 5u); -} FC_LOG_AND_RETHROW(); - -/// SOL-396: the reserve's OWN `custody_token_program` drives both the ATA -/// derivation and the token-program account. The same (owner, mint) pair has a -/// DIFFERENT canonical ATA under Token-2022, so deriving with the legacy -/// SPL-Token default names an account the program never asks for -- and -/// `require_remaining_account` then aborts the window on every retry, with the -/// cursor pinned, forever. -BOOST_AUTO_TEST_CASE(build_manifests_derive_the_ata_with_the_reserves_token_program) try { - constexpr uint64_t token_code = 121; - constexpr uint64_t reserve_code = 222; - const auto spl_mint = measurement_pubkey(84); - const auto recipient = measurement_pubkey(85); - const auto creator = measurement_pubkey(86); - const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; - - manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); - - const auto manifests = - harness.build({swap_remit_effect(0, token_code, reserve_code, recipient)}, 1); - BOOST_REQUIRE_EQUAL(manifests.size(), 1u); - const auto& m = manifests[0]; - - // The ATA under the RESERVE'S program, and that program as the token - // program account. - BOOST_CHECK(manifest_has( - m, system::get_associated_token_address(recipient, spl_mint, token_2022))); - BOOST_CHECK(manifest_has(m, token_2022)); - BOOST_CHECK(manifest_has(m, spl_mint)); - - // And NOT the legacy default's ATA, nor the legacy program. - BOOST_CHECK(!manifest_has(m, system::get_associated_token_address(recipient, spl_mint))); - BOOST_CHECK(!manifest_has(m, system::program_ids::TOKEN_PROGRAM)); -} FC_LOG_AND_RETHROW(); - -/// The SAME pinned-token-program derivation must hold for the other two -/// reserve-backed shapes. Without these, reverting either branch to the legacy -/// 2-arg `get_associated_token_address` leaves the suite green — which is exactly -/// how the missing custody mint shipped on `swap_remit` in the first place. -BOOST_AUTO_TEST_CASE(build_manifests_pin_the_token_program_on_revert_and_cancel) try { - constexpr uint64_t token_code = 131; - constexpr uint64_t reserve_code = 232; - const auto spl_mint = measurement_pubkey(88); - const auto depositor = measurement_pubkey(89); - const auto creator = measurement_pubkey(90); - const auto token_2022 = system::program_ids::TOKEN_2022_PROGRAM; - - manifest_build_harness harness; - harness.put(token_code, reserve_code, creator, spl_mint, 6, token_2022); - - const auto manifests = harness.build( - {swap_revert_effect(0, token_code, reserve_code, depositor), - reserve_create_cancelled_effect(1, token_code, reserve_code)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - // SWAP_REVERT refunds the depositor's ATA under the reserve's own program. - const auto& revert = manifests[0]; - BOOST_CHECK(manifest_has( - revert, system::get_associated_token_address(depositor, spl_mint, token_2022))); - BOOST_CHECK(manifest_has(revert, token_2022)); - BOOST_CHECK(manifest_has(revert, spl_mint)); - BOOST_CHECK(!manifest_has(revert, system::get_associated_token_address(depositor, spl_mint))); - BOOST_CHECK(!manifest_has(revert, system::program_ids::TOKEN_PROGRAM)); - - // RESERVE_CREATE_CANCELLED refunds the CREATOR's ATA, same program. - const auto& cancelled = manifests[1]; - BOOST_CHECK(manifest_has( - cancelled, system::get_associated_token_address(creator, spl_mint, token_2022))); - BOOST_CHECK(manifest_has(cancelled, token_2022)); - BOOST_CHECK(manifest_has(cancelled, spl_mint)); - BOOST_CHECK(!manifest_has(cancelled, system::get_associated_token_address(creator, spl_mint))); - BOOST_CHECK(!manifest_has(cancelled, system::program_ids::TOKEN_PROGRAM)); -} FC_LOG_AND_RETHROW(); - -/// Collateral custody MUST be resolved per position, never per token code. -/// The program pins `custody_mint` on each `(operator, token_code)` -/// `CollateralPosition`, so two operators using the same token code may take -/// different native/SPL settlement branches. A token-code-only cache would -/// make one manifest disagree with the program and permanently wedge the -/// dispatch cursor on `EffectAccountMissing`. -BOOST_AUTO_TEST_CASE(build_manifests_follows_collateral_custody_per_position) try { - constexpr uint64_t token_code = 700; - const auto native_operator = measurement_pubkey(96); - const auto spl_operator = measurement_pubkey(97); - const auto spl_mint = measurement_pubkey(98); - - manifest_build_harness harness; - harness.put_collateral(native_operator, token_code, - system::program_ids::SYSTEM_PROGRAM); - harness.put_collateral(spl_operator, token_code, spl_mint); - - const auto manifests = harness.build( - {withdraw_remit_effect(0, token_code, native_operator), - withdraw_remit_effect(1, token_code, spl_operator)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - // Native position: operator + position only, with no SPL extras. - const auto& native = manifests[0]; - BOOST_REQUIRE_EQUAL(native.size(), 2u); - BOOST_CHECK(manifest_has(native, native_operator)); - BOOST_CHECK(manifest_has(native, manifest_detail::derive_collateral_position_pda( - harness.program_id, native_operator, token_code))); - BOOST_CHECK(!manifest_has(native, manifest_detail::derive_collateral_vault_pda( - harness.program_id, token_code))); - BOOST_CHECK(!manifest_has(native, system::program_ids::TOKEN_PROGRAM)); - - // SPL position, same token code: position + collateral vault + operator's - // ATA for THIS POSITION'S mint + token program. - const auto& spl = manifests[1]; - BOOST_REQUIRE_EQUAL(spl.size(), 5u); - BOOST_CHECK(manifest_has(spl, spl_operator)); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_position_pda( - harness.program_id, spl_operator, token_code))); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_vault_pda( - harness.program_id, token_code))); - BOOST_CHECK(manifest_has(spl, system::get_associated_token_address(spl_operator, spl_mint))); - BOOST_CHECK(manifest_has(spl, system::program_ids::TOKEN_PROGRAM)); - - BOOST_REQUIRE_EQUAL(harness.collateral_reads.size(), 2u); - BOOST_CHECK((harness.collateral_reads[0] == - manifest_build_harness::collateral_seeds{native_operator, token_code})); - BOOST_CHECK((harness.collateral_reads[1] == - manifest_build_harness::collateral_seeds{spl_operator, token_code})); -} FC_LOG_AND_RETHROW(); - -/// SLASH is the one collateral shape whose SPL destination is NOT the -/// recipient: the seizure settles into the `reserve_aggregate`'s canonical -/// ATA, and the operator wallet is deliberately absent (SLASH pays nobody -/// directly). A wrong ATA owner here is the silently-stuck-seizure failure -/// mode — the handler's uninitialised-destination check degrades to -/// "status flipped, funds stuck" rather than aborting — so the owner swap is -/// pinned at the manifest level, native and SPL. -BOOST_AUTO_TEST_CASE(build_manifests_slash_settles_into_reserve_aggregate_ata) try { - constexpr uint64_t token_code = 700; - const auto native_operator = measurement_pubkey(90); - const auto spl_operator = measurement_pubkey(91); - const auto spl_mint = measurement_pubkey(92); - - manifest_build_harness harness; - harness.put_collateral(native_operator, token_code, - system::program_ids::SYSTEM_PROGRAM); - harness.put_collateral(spl_operator, token_code, spl_mint); - - const auto manifests = harness.build( - {slash_effect(0, token_code, native_operator), - slash_effect(1, token_code, spl_operator)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - // Native seizure: position ONLY — the lamports land in the named - // `reserve_aggregate` account, and the operator wallet is never declared. - const auto& native = manifests[0]; - BOOST_REQUIRE_EQUAL(native.size(), 1u); - BOOST_CHECK(manifest_has(native, manifest_detail::derive_collateral_position_pda( - harness.program_id, native_operator, token_code))); - BOOST_CHECK(!manifest_has(native, native_operator)); - - // SPL seizure: position + collateral vault + the RESERVE_AGGREGATE's ATA - // (not the operator's) + token program; still no operator wallet. - const auto& spl = manifests[1]; - BOOST_REQUIRE_EQUAL(spl.size(), 4u); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_position_pda( - harness.program_id, spl_operator, token_code))); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_vault_pda( - harness.program_id, token_code))); - BOOST_CHECK(manifest_has( - spl, system::get_associated_token_address(harness.reserve_aggregate, spl_mint))); - BOOST_CHECK(!manifest_has(spl, system::get_associated_token_address(spl_operator, spl_mint))); - BOOST_CHECK(manifest_has(spl, system::program_ids::TOKEN_PROGRAM)); - BOOST_CHECK(!manifest_has(spl, spl_operator)); -} FC_LOG_AND_RETHROW(); - -/// DEPOSIT_REVERT settles SPL for real on the companion program -/// (`handle_deposit_revert`'s non-native branch drains the collateral vault -/// into the DEPOSITOR's canonical ATA via `resolve_collateral_vault_transfer`, -/// which `require_remaining_account`s the vault, the destination ATA, and the -/// token program). A manifest that omits them aborts the dispatch round and -/// re-packs the identical window from the same cursor on every retry — the -/// regression this test pins is exactly the earlier "SPL deposit-revert is -/// refused on-chain" early-return, which held the cursor once the companion -/// implemented settlement. -BOOST_AUTO_TEST_CASE(build_manifests_deposit_revert_declares_spl_refund_accounts) try { - constexpr uint64_t token_code = 700; - const auto native_depositor = measurement_pubkey(93); - const auto spl_depositor = measurement_pubkey(94); - const auto spl_mint = measurement_pubkey(95); - - manifest_build_harness harness; - harness.put_collateral(native_depositor, token_code, - system::program_ids::SYSTEM_PROGRAM); - harness.put_collateral(spl_depositor, token_code, spl_mint); - - const auto manifests = harness.build( - {deposit_revert_effect(0, token_code, native_depositor), - deposit_revert_effect(1, token_code, spl_depositor)}, - 2); - BOOST_REQUIRE_EQUAL(manifests.size(), 2u); - - // Native refund: depositor + position only, with no SPL extras. - const auto& native = manifests[0]; - BOOST_REQUIRE_EQUAL(native.size(), 2u); - BOOST_CHECK(manifest_has(native, native_depositor)); - BOOST_CHECK(manifest_has(native, manifest_detail::derive_collateral_position_pda( - harness.program_id, native_depositor, token_code))); - BOOST_CHECK(!manifest_has(native, system::program_ids::TOKEN_PROGRAM)); - - // SPL refund: depositor wallet (the full-drain close's rent recipient) + - // position + collateral vault + the DEPOSITOR's ATA + token program. - const auto& spl = manifests[1]; - BOOST_REQUIRE_EQUAL(spl.size(), 5u); - BOOST_CHECK(manifest_has(spl, spl_depositor)); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_position_pda( - harness.program_id, spl_depositor, token_code))); - BOOST_CHECK(manifest_has(spl, manifest_detail::derive_collateral_vault_pda( - harness.program_id, token_code))); - BOOST_CHECK(manifest_has(spl, system::get_associated_token_address(spl_depositor, spl_mint))); - BOOST_CHECK(manifest_has(spl, system::program_ids::TOKEN_PROGRAM)); -} FC_LOG_AND_RETHROW(); - -/// The build reads each DISTINCT reserve exactly once, however many -/// attestations reference it -- the property that keeps a 500-remit envelope -/// from costing 500 sequential round-trips per drain. There is no -/// `OutpostConfig` read on this path at all: custody rides the Reserve. -BOOST_AUTO_TEST_CASE(build_manifests_reads_each_reserve_once) try { - const auto recipient = measurement_pubkey(83); - const auto creator = measurement_pubkey(84); - const auto mint = measurement_pubkey(85); - - manifest_build_harness harness; - harness.put(10, 20, creator, mint, 6); - harness.put(11, 21, creator, mint, 6); - - // Six attestations across TWO distinct reserves. - harness.build({swap_remit_effect(0, 10, 20, recipient), - swap_remit_effect(1, 11, 21, recipient), - swap_remit_effect(2, 10, 20, recipient), - swap_remit_effect(3, 10, 20, recipient), - swap_remit_effect(4, 11, 21, recipient), - swap_remit_effect(5, 10, 20, recipient)}, - 6); - - BOOST_CHECK_EQUAL(harness.reads.size(), 2u); - const std::vector> expected{{10u, 20u}, {11u, 21u}}; - BOOST_CHECK(harness.reads == expected); -} FC_LOG_AND_RETHROW(); - -/// An ABSENT reserve costs THAT attestation its custody-dependent accounts and -/// nothing else: every other attestation's manifest is still built, so the -/// envelope's healthy prefix still dispatches. This degrade is safe precisely -/// because the program skips an uninitialized reserve -/// (`load_reserve_from_remaining` -> `Ok(None)` -> logged skip), so none of the -/// omitted accounts is ever reached. The opposite case — a reserve that exists -/// but this relay cannot read — must NOT come through here; it is pinned by -/// `build_manifests_propagate_an_unreadable_reserve` below. -BOOST_AUTO_TEST_CASE(build_manifests_degrade_an_absent_reserve) try { - const auto recipient = measurement_pubkey(86); - const auto payee = measurement_pubkey(87); - const auto creator = measurement_pubkey(88); - const auto mint = measurement_pubkey(89); - - manifest_build_harness harness; - harness.put(10, 20, creator, mint, 6); - // (12, 22) deliberately absent -> the read degrades. - // Native custody for the collateral token: the zero-mint marker. - harness.put_collateral(payee, 700, system::program_ids::SYSTEM_PROGRAM); - - const auto manifests = harness.build({swap_remit_effect(0, 10, 20, recipient), - swap_remit_effect(1, 12, 22, recipient), - withdraw_remit_effect(2, 700, payee)}, - 3); - BOOST_REQUIRE_EQUAL(manifests.size(), 3u); - - // The healthy SPL attestation is complete. - BOOST_CHECK(manifest_has(manifests[0], system::get_associated_token_address(recipient, mint))); - // The degraded one still carries everything derivable WITHOUT the record - // (Reserve PDA, vault, recipient, token program) -- just not the ATA, which - // needs the custody mint. - BOOST_CHECK(manifest_has(manifests[1], manifest_detail::derive_reserve_pda( - harness.program_id, 12, 22))); - BOOST_CHECK(manifest_has(manifests[1], manifest_detail::derive_reserve_vault_pda( - harness.program_id, 12, 22))); - BOOST_CHECK(manifest_has(manifests[1], recipient)); - // The attestation AFTER the degraded one is untouched by it: a native - // withdraw remit declares exactly the payee and their CollateralPosition. - BOOST_REQUIRE_EQUAL(manifests[2].size(), 2u); - BOOST_CHECK(manifest_has(manifests[2], payee)); - BOOST_CHECK(manifest_has(manifests[2], manifest_detail::derive_collateral_position_pda( - harness.program_id, payee, 700))); - - // A permanently unusable reserve costs ONE read for the whole build, not - // one per attestation referencing it. - const auto degraded_reads = std::count(harness.reads.begin(), harness.reads.end(), - std::pair{12u, 22u}); - BOOST_CHECK_EQUAL(degraded_reads, 1); -} FC_LOG_AND_RETHROW(); - -/// A reserve that EXISTS but cannot be read by this relay must abort the build, -/// never degrade into a manifest. The program decodes that account fine, takes -/// its real branch, and requires the effect accounts that branch needs — the -/// custody ATA for an SPL remit/revert, the `creator` for a cancel with an -/// escrow to refund — so a degraded manifest is GUARANTEED to hit -/// `require_remaining_account` and abort. -/// -/// That is the difference between a delayed epoch and a wedged one: -/// `drive_dispatch_rounds` repacks every window from the on-chain cursor, so an -/// aborting attestation heads every future window and the cursor never moves. -/// Letting the exception out fails the tick with the cursor untouched, which is -/// recoverable; shipping the manifest is not. -BOOST_AUTO_TEST_CASE(build_manifests_propagate_an_unreadable_reserve) try { - const auto recipient = measurement_pubkey(94); - const auto later = measurement_pubkey(95); - - manifest_build_harness harness; - // The reader models `reserve_info_for_codes` meeting a present-but- - // undecodable Reserve: it throws rather than returning empty. - auto throwing_reader = [&](uint64_t token_code, uint64_t reserve_code) - -> std::optional { - harness.reads.emplace_back(token_code, reserve_code); - FC_THROW_EXCEPTION(fc::exception, "Reserve exists but is undecodable (test probe)"); - }; - - // BOTH effects are reserve-backed, on DIFFERENT reserves, so the read count - // is load-bearing: had the build swallowed the throw and carried on, the - // second effect would have paid its own read and `reads` would be 2. - BOOST_CHECK_EXCEPTION( - manifest_detail::build_dispatch_manifests( - harness.program_id, - {swap_remit_effect(0, 10, 20, recipient), swap_remit_effect(1, 11, 21, later)}, - 2, [] {}, throwing_reader, harness.collateral_reader(), harness.hook_reader(), - harness.liq_pool_reader(), harness.reserve_aggregate, "test-relay"), - fc::exception, - [](const fc::exception& e) { - return e.to_detail_string().find("undecodable") != std::string::npos; - }); - // It failed AT the first bad reserve; the second was never reached. - BOOST_REQUIRE_EQUAL(harness.reads.size(), 1u); - BOOST_CHECK((harness.reads[0] == std::pair{10u, 20u})); -} FC_LOG_AND_RETHROW(); - -/// The manifest build is where a large envelope burns its tick: the deadline -/// is probed per effect, BEFORE that effect's reserve read, so an over-deadline -/// build fails at the loop instead of deep in the RPC layer with the work -/// already lost and zero dispatches sent. -BOOST_AUTO_TEST_CASE(build_manifests_probes_deadline_before_each_reserve_read) try { - const auto recipient = measurement_pubkey(90); - const auto creator = measurement_pubkey(91); - const auto mint = measurement_pubkey(92); - - manifest_build_harness harness; - harness.put(10, 20, creator, mint, 6); - harness.put(11, 21, creator, mint, 6); - - uint32_t probes = 0; - BOOST_CHECK_EXCEPTION( - harness.build({swap_remit_effect(0, 10, 20, recipient), - swap_remit_effect(1, 11, 21, recipient)}, - 2, - [&] { - if (probes++ > 0) { - FC_THROW_EXCEPTION(fc::timeout_exception, - "deadline exceeded (test probe)"); - } - }), - fc::timeout_exception, - [](const fc::timeout_exception& e) { - return e.to_detail_string().find("deadline exceeded") != std::string::npos; - }); - // Probe fired before the SECOND effect's read, so only the first reserve - // was ever fetched. - BOOST_REQUIRE_EQUAL(harness.reads.size(), 1u); - BOOST_CHECK((harness.reads[0] == std::pair{10u, 20u})); -} FC_LOG_AND_RETHROW(); - /// The result is sized to the envelope's attestation TOTAL and indexed by the /// flat dispatch position, so an attestation needing no effect account keeps /// an empty entry and the cursor stays aligned with the manifests. BOOST_AUTO_TEST_CASE(build_manifests_index_by_flat_dispatch_position) try { - const auto payee = measurement_pubkey(93); - manifest_build_harness harness; - // Native custody for the collateral token: the zero-mint marker. - harness.put_collateral(payee, 700, system::program_ids::SYSTEM_PROGRAM); - // Attestations 0, 1 and 3 contribute no effect at all. - const auto manifests = harness.build({withdraw_remit_effect(2, 700, payee)}, 4); - + const auto payee = measurement_pubkey(93); + const auto manifests = harness.build({desyndicate_liq_effect(2, payee, 7)}, 4); BOOST_REQUIRE_EQUAL(manifests.size(), 4u); BOOST_CHECK(manifests[0].empty()); BOOST_CHECK(manifests[1].empty()); - BOOST_REQUIRE_EQUAL(manifests[2].size(), 2u); - BOOST_CHECK(manifest_has(manifests[2], payee)); - BOOST_CHECK(manifest_has(manifests[2], manifest_detail::derive_collateral_position_pda( - harness.program_id, payee, 700))); BOOST_CHECK(manifests[3].empty()); - // A collateral settlement needs no Reserve record — only its one custody - // lookup is paid. - BOOST_CHECK(harness.reads.empty()); - BOOST_REQUIRE_EQUAL(harness.collateral_reads.size(), 1u); - BOOST_CHECK((harness.collateral_reads[0] == - manifest_build_harness::collateral_seeds{payee, 700u})); + BOOST_REQUIRE_EQUAL(manifests[2].size(), 3u); + BOOST_CHECK(manifest_has(manifests[2], manifest_detail::derive_pending_desyndication_pda(harness.program_id, 7))); + BOOST_CHECK_EQUAL(harness.liq_pool_reads, 1u); } FC_LOG_AND_RETHROW(); - // ── drive_dispatch_rounds: the dispatch-crank state machine, driven through // its RPC seams (progress read + dispatch send) with a scripted on-chain // model. Both confirmed drain defects (the zero-attestation early return @@ -2747,28 +1699,24 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_zero_attestation_envelope_sends_one_close_cr BOOST_CHECK(harness.sends[0].accounts.empty()); BOOST_CHECK_EQUAL(sig, "sig-0"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_zero_attestation_waits_for_consensus) try { dispatch_drive_harness harness(0, /*consensus_reached=*/false); const auto sig = harness.drive({}); BOOST_CHECK(harness.sends.empty()); BOOST_CHECK_EQUAL(sig, ""); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_consensus_not_reached_sends_nothing) try { dispatch_drive_harness harness(3, /*consensus_reached=*/false); const auto sig = harness.drive(distinct_manifests(3, 2)); BOOST_CHECK(harness.sends.empty()); BOOST_CHECK_EQUAL(sig, ""); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_already_drained_cursor_sends_nothing) try { dispatch_drive_harness harness(3, true, /*initial_cursor=*/3); const auto sig = harness.drive(distinct_manifests(3, 2)); BOOST_CHECK(harness.sends.empty()); BOOST_CHECK_EQUAL(sig, ""); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_single_round_settles_small_manifests) try { // 3 attestations x 2 distinct accounts = a 6-account union, well inside // the 16-account budget -- one send covers the whole envelope. @@ -2779,7 +1727,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_single_round_settles_small_manifests) try { BOOST_CHECK_EQUAL(harness.sends[0].accounts.size(), 6u); BOOST_CHECK_EQUAL(sig, "sig-0"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_packs_union_to_exact_account_budget) try { // 8 distinct accounts per attestation: two fill the 16-account budget // EXACTLY (16 is allowed; the pack breaks only past it), a third would @@ -2792,7 +1739,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_packs_union_to_exact_account_budget) try { BOOST_CHECK_EQUAL(harness.sends[1].limit, 2u); BOOST_CHECK_EQUAL(harness.sends[1].accounts.size(), sysio::MAX_TERMINAL_DYNAMIC_ACCOUNTS); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_shared_accounts_pack_as_a_union) try { // Every attestation references the SAME 10 accounts: the batch union stays // at 10, so all 3 fit one round even though 3 x 10 raw metas would not. @@ -2806,7 +1752,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_shared_accounts_pack_as_a_union) try { BOOST_CHECK_EQUAL(harness.sends[0].limit, 3u); BOOST_CHECK_EQUAL(harness.sends[0].accounts.size(), 10u); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_resumes_from_nonzero_cursor) try { // Cursor already at 2 of 5 (a prior partial drain): the batch starts AT // the cursor and carries only the unsettled manifests' accounts. @@ -2826,7 +1771,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_resumes_from_nonzero_cursor) try { BOOST_CHECK(sent_has(manifests[2][0].key)); // the resumed window BOOST_CHECK(sent_has(manifests[4][1].key)); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_oversized_single_manifest_still_takes_one) try { // One attestation whose own manifest exceeds the budget: the pack must // still take it (alone) so it can make progress -- an unsendable manifest @@ -2838,7 +1782,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_oversized_single_manifest_still_takes_one) t BOOST_CHECK_EQUAL(harness.sends[0].limit, 1u); BOOST_CHECK_EQUAL(harness.sends[0].accounts.size(), oversized); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_exits_when_cursor_does_not_advance) try { // The send lands but the observed cursor does not move: another caller is // draining this envelope (or the read raced the write). One send, then a @@ -2849,7 +1792,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_exits_when_cursor_does_not_advance) try { BOOST_REQUIRE_EQUAL(harness.sends.size(), 1u); BOOST_CHECK_EQUAL(sig, "sig-0"); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_round_exhaustion_alarms_and_returns) try { // 9-account manifests force one attestation per round; two more // attestations than the round budget means the loop stops at the budget. @@ -2863,7 +1805,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_round_exhaustion_alarms_and_returns) try { BOOST_CHECK_EQUAL(harness.sends.size(), sysio::MAX_DISPATCH_ROUNDS); BOOST_CHECK_EQUAL(sig, "sig-" + std::to_string(sysio::MAX_DISPATCH_ROUNDS - 1)); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(drive_dispatch_deadline_expiry_throws) try { // The deadline probe fires once per round, before any read or send. Let // round 0 through and expire on round 1: exactly one batch goes out and @@ -2884,7 +1825,6 @@ BOOST_AUTO_TEST_CASE(drive_dispatch_deadline_expiry_throws) try { }); BOOST_REQUIRE_EQUAL(harness.sends.size(), 1u); } FC_LOG_AND_RETHROW(); - // ── LatestOutboundEnvelope inbound-read path. The epoch=511 RCA was // hardcoded STANDALONE offsets (epoch@8) silently misreading the // INTEGRATED account (`bump`=0xFF at byte 8, `epoch_index`=1 at byte 9 ⇒ @@ -3102,7 +2042,6 @@ BOOST_AUTO_TEST_CASE(latest_envelope_shape_accepts_known_layouts) try { } } } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(latest_envelope_shape_rejects_misdeclared_idls) try { struct reject_case { const char* name; @@ -3151,7 +2090,6 @@ BOOST_AUTO_TEST_CASE(latest_envelope_shape_rejects_misdeclared_idls) try { BOOST_CHECK_THROW(assert_latest_envelope_shape(latest_envelope_program({}, false)), fc::assert_exception); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(epoch_deliveries_shape_accepts_the_deployed_declaration) try { using sysio::outpost_solana_client_detail::assert_epoch_deliveries_shape; @@ -3161,7 +2099,6 @@ BOOST_AUTO_TEST_CASE(epoch_deliveries_shape_accepts_the_deployed_declaration) tr // guaranteed present before the first drain. BOOST_CHECK_NO_THROW(assert_epoch_deliveries_shape(load_idl_fixture(opp_outpost_idl_fixture))); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(epoch_deliveries_shape_rejects_drifted_declarations) try { using sysio::outpost_solana_client_detail::assert_epoch_deliveries_shape; @@ -3204,17 +2141,6 @@ BOOST_AUTO_TEST_CASE(epoch_deliveries_shape_rejects_drifted_declarations) try { BOOST_CHECK_THROW(assert_epoch_deliveries_shape(named_account_program("EpochDeliveries", {}, false)), fc::assert_exception); } FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(reserve_shape_accepts_the_deployed_declaration) try { - using sysio::outpost_solana_client_detail::assert_reserve_shape; - - // The stub fixture carries the program's own `Reserve` struct, so this is - // the boot check running against the declaration a batch operator will meet. - // It is what guarantees the manifest builder can always resolve custody and - // the cancel-refund creator from a reserve the program can read. - BOOST_CHECK_NO_THROW(assert_reserve_shape(load_idl_fixture(opp_outpost_idl_fixture))); -} FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(distribution_state_shape_accepts_a_pubkey_mint_and_rejects_drift) try { using sysio::outpost_solana_client_detail::assert_distribution_state_shape; @@ -3241,7 +2167,6 @@ BOOST_AUTO_TEST_CASE(distribution_state_shape_accepts_a_pubkey_mint_and_rejects_ } } } FC_LOG_AND_RETHROW(); - namespace { /// A synthetic program declaring `GlobalState` with `wire_state` of enum type @@ -3293,7 +2218,6 @@ BOOST_AUTO_TEST_CASE(global_state_shape_requires_a_post_launch_variant) try { } } } FC_LOG_AND_RETHROW(); - // The crank gates on the decoder's rendering of `GlobalState.wire_state` -- an // enum comes back as `{"variant": }` -- and only PostLaunch is due. BOOST_AUTO_TEST_CASE(liq_yield_report_is_due_only_post_launch) try { @@ -3308,7 +2232,6 @@ BOOST_AUTO_TEST_CASE(liq_yield_report_is_due_only_post_launch) try { // A bare string is not the decoder's shape: never due rather than guessed. BOOST_CHECK(!liq_yield_report_due(fc::mutable_variant_object()("wire_state", "PostLaunch"))); } FC_LOG_AND_RETHROW(); - // The emergency stop: while `GlobalState.frozen` is set the program refuses // `report_liq_yield`, so the crank does not fire. A row without `frozen` comes // from an IDL that declares no emergency stop and stays due; a `frozen` of any @@ -3335,7 +2258,6 @@ BOOST_AUTO_TEST_CASE(liq_yield_report_is_not_due_while_the_outpost_is_frozen) tr BOOST_CHECK(liq_outpost_frozen(state("PostLaunch", fc::variant(uint64_t{0})))); BOOST_CHECK(!liq_yield_report_due(state("PostLaunch", fc::variant("false")))); } FC_LOG_AND_RETHROW(); - // A `GlobalState` still at the pre-freeze layout (8 + 131 bytes, wire-solana // `GLOBAL_STATE_PRE_FREEZE_INIT_SPACE`) cannot carry the `frozen` the loaded IDL // declares: the crank treats it as not due instead of decoding past its end. The @@ -3363,7 +2285,6 @@ BOOST_AUTO_TEST_CASE(global_state_predates_freeze_only_under_an_idl_that_declare } BOOST_CHECK(!global_state_predates_freeze(idl::program{}, 0)); } FC_LOG_AND_RETHROW(); - // The overrides cover every non-signer account `report_liq_yield` declares, and // derive each the way the program's `#[derive(Accounts)]` constraints do: the // Token-2022 ATAs of the pool and bucket authorities, the `UserRecord`s seeded @@ -3417,151 +2338,6 @@ BOOST_AUTO_TEST_CASE(report_liq_yield_overrides_resolve_every_declared_account) BOOST_CHECK(overrides.at(accounts::associated_token_program) == system::program_ids::ASSOCIATED_TOKEN_PROGRAM); BOOST_CHECK(overrides.at(accounts::system_program) == system::program_ids::SYSTEM_PROGRAM); } FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(reserve_shape_rejects_drifted_declarations) try { - using sysio::outpost_solana_client_detail::assert_reserve_shape; - - // IDL drift here is the realistic way a LIVE, program-readable Reserve - // becomes unreadable to this relay — and in flight that is unrecoverable: - // the effect account the program's branch requires cannot be derived, the - // window aborts, and every later window repacks from the same cursor. Each - // shape below must therefore fail at BOOT, while the IDL is still fixable. - struct reject_case { - const char* name; - std::vector fields; - }; - const auto pubkey_t = prim(idl::primitive_type::pubkey); - const auto u8_t = prim(idl::primitive_type::u8); - - // Every case is the VALID four-field declaration with exactly ONE field - // dropped or mis-typed. Building them that way is load-bearing: the previous - // cases each omitted `custody_token_program` while testing some other defect, - // so deleting the `has_token_program` check (or its is_pubkey branch) left the - // whole suite green — the same coverage hole that let the missing custody mint - // ship in the first place. - const auto valid = [&](const char* drop, const char* retype, - const idl::idl_type& replacement) { - std::vector fields; - const std::pair declared[] = { - {"creator", pubkey_t}, - {"custody_mint", pubkey_t}, - {"custody_decimals", u8_t}, - {"custody_token_program", pubkey_t}, - }; - for (const auto& [name, type] : declared) { - if (drop != nullptr && std::string_view(name) == drop) continue; - fields.push_back({name, - (retype != nullptr && std::string_view(name) == retype) ? replacement - : type}); - } - return fields; - }; - const auto none = prim(idl::primitive_type::u8); // unused placeholder - - std::vector cases; - cases.push_back({"creator dropped", valid("creator", nullptr, none)}); - cases.push_back({"custody_mint dropped", valid("custody_mint", nullptr, none)}); - cases.push_back({"custody_decimals dropped", valid("custody_decimals", nullptr, none)}); - // The boot gate this PR adds: a pre-SOL-396 IDL is exactly the valid three - // fields with `custody_token_program` absent. - cases.push_back({"custody_token_program dropped (pre-SOL-396 IDL)", - valid("custody_token_program", nullptr, none)}); - cases.push_back({"custody_mint declared as a byte array", - valid(nullptr, "custody_mint", u8_32_array())}); - cases.push_back({"custody_decimals declared u32", - valid(nullptr, "custody_decimals", prim(idl::primitive_type::u32))}); - cases.push_back({"custody_token_program declared as a byte array", - valid(nullptr, "custody_token_program", u8_32_array())}); - cases.push_back({"creator declared as a byte array", - valid(nullptr, "creator", u8_32_array())}); - - for (const auto& c : cases) { - BOOST_TEST_CONTEXT(c.name) { - for (bool in_types : {false, true}) { - BOOST_CHECK_THROW(assert_reserve_shape(named_account_program("Reserve", c.fields, in_types)), - fc::assert_exception); - } - } - } - - // No `Reserve` account declared at all, and one declared with no field - // definition in either home. - BOOST_CHECK_THROW(assert_reserve_shape(idl::program{}), fc::assert_exception); - BOOST_CHECK_THROW(assert_reserve_shape(named_account_program("Reserve", {}, false)), - fc::assert_exception); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(collateral_position_shape_accepts_the_expected_declaration) try { - using sysio::outpost_solana_client_detail::assert_collateral_position_shape; - - const std::vector fields{ - {"bump", prim(idl::primitive_type::u8)}, - {"operator", prim(idl::primitive_type::pubkey)}, - {"token_code", prim(idl::primitive_type::u64)}, - {"custody_mint", prim(idl::primitive_type::pubkey)}, - {"amount", prim(idl::primitive_type::u64)}}; - for (bool in_types : {false, true}) { - BOOST_CHECK_NO_THROW(assert_collateral_position_shape( - named_account_program("CollateralPosition", fields, in_types))); - } -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(collateral_position_shape_rejects_drifted_declarations) try { - using sysio::outpost_solana_client_detail::assert_collateral_position_shape; - - struct reject_case { - const char* name; - std::vector fields; - }; - const auto pubkey_t = prim(idl::primitive_type::pubkey); - const auto u64_t = prim(idl::primitive_type::u64); - - std::vector cases; - cases.push_back({"operator dropped", - {{"token_code", u64_t}, {"custody_mint", pubkey_t}, {"amount", u64_t}}}); - cases.push_back({"token_code dropped", - {{"operator", pubkey_t}, {"custody_mint", pubkey_t}, {"amount", u64_t}}}); - cases.push_back({"custody_mint dropped", - {{"operator", pubkey_t}, {"token_code", u64_t}, {"amount", u64_t}}}); - cases.push_back({"amount dropped", - {{"operator", pubkey_t}, {"token_code", u64_t}, {"custody_mint", pubkey_t}}}); - cases.push_back({"operator declared as a byte array", - {{"operator", u8_32_array()}, - {"token_code", u64_t}, - {"custody_mint", pubkey_t}, - {"amount", u64_t}}}); - cases.push_back({"custody_mint declared as a byte array", - {{"operator", pubkey_t}, - {"token_code", u64_t}, - {"custody_mint", u8_32_array()}, - {"amount", u64_t}}}); - cases.push_back({"token_code declared u32", - {{"operator", pubkey_t}, - {"token_code", prim(idl::primitive_type::u32)}, - {"custody_mint", pubkey_t}, - {"amount", u64_t}}}); - cases.push_back({"amount declared u32", - {{"operator", pubkey_t}, - {"token_code", u64_t}, - {"custody_mint", pubkey_t}, - {"amount", prim(idl::primitive_type::u32)}}}); - - for (const auto& c : cases) { - BOOST_TEST_CONTEXT(c.name) { - for (bool in_types : {false, true}) { - BOOST_CHECK_THROW(assert_collateral_position_shape( - named_account_program("CollateralPosition", c.fields, in_types)), - fc::assert_exception); - } - } - } - - BOOST_CHECK_THROW(assert_collateral_position_shape(idl::program{}), fc::assert_exception); - BOOST_CHECK_THROW( - assert_collateral_position_shape(named_account_program("CollateralPosition", {}, false)), - fc::assert_exception); -} FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(decode_latest_envelope_reads_both_known_layouts) try { constexpr uint32_t epoch = 7; const auto payload = envelope_payload_bytes(epoch); @@ -3579,7 +2355,6 @@ BOOST_AUTO_TEST_CASE(decode_latest_envelope_reads_both_known_layouts) try { integrated_latest_fields(), integrated_latest_account(epoch, payload, checksum), epoch); BOOST_CHECK(bytes_equal(integrated, payload)); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(decode_latest_envelope_catches_field_order_drift) try { // The epoch=511 RCA input: an INTEGRATED account (bump=0xFF first) read // by a client whose loaded IDL declares the STANDALONE field order. The @@ -3595,7 +2370,6 @@ BOOST_AUTO_TEST_CASE(decode_latest_envelope_catches_field_order_drift) try { // must not be handed drift-garbage bytes. BOOST_CHECK(decode_with_layout(standalone_latest_fields(), integrated_account, 511).empty()); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(decode_latest_envelope_rejects_invalid_accounts) try { constexpr uint32_t epoch = 7; const auto payload = envelope_payload_bytes(epoch); @@ -3644,7 +2418,6 @@ BOOST_AUTO_TEST_CASE(decode_latest_envelope_rejects_invalid_accounts) try { epoch).empty()); } } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(borsh_payload_bytes_accepts_both_decoded_shapes) try { const std::vector payload = {0x12, 0x00, 0xFF, 0x42}; @@ -3663,7 +2436,6 @@ BOOST_AUTO_TEST_CASE(borsh_payload_bytes_accepts_both_decoded_shapes) try { BOOST_CHECK_THROW(borsh_payload_bytes(fc::variant(oversized)), fc::assert_exception); BOOST_CHECK_THROW(borsh_payload_bytes(fc::variant(static_cast(7))), fc::assert_exception); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(libfc_decode_renders_bytes_base64_and_vec_u8_as_array) try { // `borsh_payload_bytes` depends on HOW libfc's IDL decoder renders the // payload field into a variant: `bytes` as a base64 STRING, `Vec` as @@ -3706,7 +2478,6 @@ BOOST_AUTO_TEST_CASE(libfc_decode_renders_bytes_base64_and_vec_u8_as_array) try } } } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_CASE(select_program_idls_prefers_declared_address_match) try { const auto deployed = measurement_pubkey(90); const auto other = measurement_pubkey(91); @@ -3742,5 +2513,4 @@ BOOST_AUTO_TEST_CASE(select_program_idls_prefers_declared_address_match) try { select_program_idls_matching({named_program(""), named_program("")}, deployed), fc::assert_exception); } FC_LOG_AND_RETHROW(); - BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/underwriter_plugin/CMakeLists.txt b/plugins/underwriter_plugin/CMakeLists.txt deleted file mode 100644 index 13c8b8a017..0000000000 --- a/plugins/underwriter_plugin/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -plugin_target( - underwriter_plugin - LIBRARIES - appbase - chainbase - chain_plugin - cron_plugin - http_plugin - signature_provider_manager_plugin - outpost_client_plugin - outpost_ethereum_client_plugin - outpost_solana_client_plugin - opp - fc - sysio_chain) - -if(TARGET test_underwriter_plugin) - target_include_directories(test_underwriter_plugin PRIVATE - ${CMAKE_SOURCE_DIR}/libraries/opp/test/include) -endif() diff --git a/plugins/underwriter_plugin/README.md b/plugins/underwriter_plugin/README.md deleted file mode 100644 index daa63f35c0..0000000000 --- a/plugins/underwriter_plugin/README.md +++ /dev/null @@ -1,276 +0,0 @@ -# underwriter_plugin - -Autonomous underwriter daemon. Polls `sysio.uwrit::uwreqs` for PENDING -swaps, derives each candidate's authoritative stored-evidence state, and -submits signed `UnderwriteIntentCommit` records only for missing outpost legs. -Before submitting, it budgets the candidate's full eventual depot collateral -requirement, including stored or locally confirmed legs whose locks do not yet -exist. A complete depot candidate never automatically replays a paid outpost -commit; it remains reserved while the request awaits an external exact-UIC -replay. - -The underwriter is a **separate daemon** from the batch operator. It does -not relay OPP envelopes — that is the batch operator's job. The -underwriter signs and submits underwriting commits to outposts. The depot -receives those commits through ordinary OPP dispatch and performs the terminal -winner-selection decision. - -## Lifecycle - -### Startup pre-flight (unconditional, no dev escape hatch) - -`plugin_startup` runs a series of checks before scheduling the cron job; -any gating failure logs a structured `elog` and skips cron registration: - -1. The underwriter exists in `sysio.opreg::operators`. Its status is observed - but non-gating because bootstrap may activate it after node startup; - `poll_own_status()` blocks work until it is `OPERATOR_STATUS_ACTIVE`. -2. Every **active** non-depot chain in `sysio.chains::chains` has a - configured `--underwriter-{eth,sol}-outpost` endpoint of the matching - VM family. The served set is derived from the registry while the - outpost clients are built from config, so a missing or wrong-family - endpoint would let the scan loop pick a request it cannot fully - commit — landing one leg and stalling the other. Inactive - (not-yet-`activchain`ed) chains are skipped, so registering a future - chain never blocks startup before its endpoint/collateral land. -3. `sysio.authex::links` covers every active chain in `sysio.chains::chains` — - the underwriter cannot sign a commit on a chain it has no authex link for. -4. The required source-deposit function / instruction names resolve - against the loaded ABI / IDL files; exactly one operator-configured WIRE - UIC provider whose public key is a direct key whose weight alone reaches the - underwriter account's current `owner` or `active` threshold is selected - (automatically registered defaults and unrelated WIRE/block-signing - providers are ignored); - its public key and self-test signature use the same supported - fixed-size recoverable variant (K1, R1, EM, or ED); and the signature - self-test uses compact recovery headers `31..34` for K1/R1 or Ethereum - recovery values `27..30` for EM, is scalar-valid and low-`s` for ECDSA, - and recovers that threshold-satisfying key. If multiple - operator-configured providers can each satisfy one of those permissions, - startup fails as ambiguous instead of selecting by registry order. The - provider targets `chain=wire`; its native key type is - `wire` for K1/R1, `ethereum` for EM, or `solana` for ED. - -Raw collateral is observed during preflight but zero balance is non-fatal: -bootstrap may deposit after the node starts. Runtime admission uses each -request's exact `(chain_code, token_code)` buckets, so an unrelated active -chain with no collateral does not halt otherwise coverable work. - -No `--strict=false` flag, no dev fallback. Cluster bootstrap is -responsible for establishing the required state — see -`feedback_no_dev_escape_hatches.md`. - -### Per-cycle scan - -Every `--underwriter-scan-interval-ms` (default 5 s): - -1. `poll_own_status()` — short-circuit if the underwriter's status has - flipped to `SLASHED` / `TERMINATED`. -2. `read_outpost_registry()` — refresh the `(chain_code → chain_kind)` - cache from `sysio.chains::chains`. -3. `read_credit_lines()` — compute available bond per - `(chain_code, token_code)` by mirroring the depot's `sysio.opreg::available()` - math: - - available = balance(opreg::balances) - − sum(uwrit::locks where underwriter == self) - − sum(opreg::wtdwqueue where account == self) - -4. `scan_pending_requests()` — walk the small in-flight - `sysio.uwrit::uwreqs` KV table, filter to `PENDING` rows, and derive this - underwriter's absent, partial, complete, or terminal candidate state - from the authoritative `commits_by` row. -5. Remove terminal candidates before cover selection. For a complete stored - candidate, reserve its full eventual bond while it awaits an external exact - UIC replay; do not automatically replay either paid outpost transaction. -6. For candidates whose remaining legs are only locally confirmed, reserve the - full eventual bond while OPP catches up, then remove them from submission - work. Saturating reservation prevents a concurrent shortfall from exposing - leftover capacity to a new paid commit. -7. For remaining absent/partial candidates, run the bounded branch-and-bound - selector (with its value-sorted fallback) over every eventual non-depot - lock, including a leg whose UIC is already stored or locally confirmed. The - selector uses the daemon's current credit snapshot to avoid knowingly - submitting a candidate the depot cannot cover. `submit_intent_to_outpost()` - still sends only missing legs. This pre-validation is advisory because - collateral can change before OPP delivery: the contract rechecks live state - atomically, creates locks on success, and otherwise disqualifies only that - candidate while the request remains PENDING for another underwriter. - -Fresh outpost evidence, including an exact replay of a stored UIC, can make -request-level terminal decisions such as a variance rejection. If the route, -quote, or liquidity is still unsuitable, the row remains PENDING for another -external replay or for expiry pruning. - -### Commit submission (`create_signed_uic_bytes`) - -For each leg of every selected uwreq: - -1. Construct a proto `UnderwriteIntentCommit` with `uw_account`, - `uw_request_id`, `chain_code`, and a blank `signature`. Populate - `uw_ext_chain_addr` from the same concrete outpost client's authenticated - transaction signer (20-byte EVM address or 32-byte Solana public key), so - every production field has one unambiguous serialized representation. -2. Serialize the proto, compute `sha256(blanked_bytes)` — the digest. -3. Sign with the permission-authorized provider selected and cached during - startup preflight. Runtime signing never re-queries or switches providers; - a null cache fails closed and no UIC is emitted. Named and anonymous - `--signature-provider` entries qualify, but generated defaults and unrelated - WIRE signers do not. K1/R1 use the `wire` native key type, EM uses - `ethereum`, and ED uses `solana`. The fc::crypto::signature is packed via - `fc::raw::pack` into the wire format the depot's - `sysio.uwrit::verify_uic_signature` reads. -4. Place the packed signature back into the proto, re-serialize, and - submit those bytes verbatim to the outpost — `commit(bytes uicBytes)` - on Ethereum, `commit_underwrite(uic_bytes)` on Solana. - -The current outposts require an ACTIVE-role transaction caller, decode and -canonically re-encode the complete UIC, and require the original bytes to match. -They bind `uw_account` and `uw_ext_chain_addr` to that authenticated caller and -its current roster row, then enqueue the original validated UIC bytes unchanged. -The outposts do not validate the embedded WIRE permission signature or bond; -those remain the depot's authoritative responsibilities. Before storing a leg, -the depot first requires the complete signed protobuf bytes to equal the -CDT generator's canonical re-encoding. It then reconstructs the digest and -accepts exactly the canonical packed fixed-size recoverable shapes: K1/R1/EM -(variant tags `0`/`1`/`3` plus a 65-byte ECC body) or ED (tag `4` plus a -96-byte body). K1 and R1 require recovery headers `31..34`; EM requires recovery -values `27..30`. All three ECDSA variants additionally require in-range scalars -and low-`s`. -It constructs that known variant directly, recovers the signing key, and -accepts it only when that direct key's weight alone reaches the `active` or -`owner` permission threshold on the claimed `uw_account`. WebAuthn and BLS -remain unsupported. Malformed, -noncanonical, unsupported, or unauthorized incoming signatures are logged and -ignored without changing candidate evidence or aborting consensus dispatch. -When a second required leg arrives, winner selection revalidates only the older -stored leg so permission-key rotation cannot authorize stale evidence. A -candidate disqualified by that check remains durably disqualified for the -request; later records cannot replace its evidence, refresh its timestamps, or -re-arm it. The `UIC_SIGNATURE_REJECTED` marker names the -account field as `claimed_underwriter` and reports the provenance-bound -`chain_code`; it identifies the rejected candidate claim, not an independently -authenticated depot submitter. - -## Configuration - -| Option | Default | Description | -|---|---|---| -| `--underwriter-account` | — | WIRE account name for this underwriter. Configuring it enables the scan cycle | -| `--underwriter-scan-interval-ms` | 5000 | How often to scan for pending uwreqs (ms) | -| `--underwriter-action-timeout-ms` | 15000 | Timeout for outpost RPC calls and table reads (ms) | -| `--underwriter-eth-source-deposit-function` | — | Name of the ETH swap-deposit function; the chain-agnostic 4-byte selector is resolved at preflight from the loaded `--ethereum-abi-file` ABIs (required) | -| `--underwriter-sol-source-deposit-instruction` | — | Name of the SOL swap-deposit instruction; the 8-byte anchor discriminator is resolved at preflight from the loaded `--solana-idl-file` IDLs (required) | -| `--underwriter-eth-source-deposit-lookback-blocks` | 7200 | Recent finalized ETH blocks searched per source deposit | - -### Outpost wiring - -Nothing about an outpost is declared per node. - -* **Which chains** — every active non-depot `sysio.chains` row. There is no - per-chain config entry to keep in sync with the registry. -* **Where each one lives** — the row's `outpost` struct: the EVM - OperatorRegistry (the `uw_commit` target) and source-deposit contract, or the - single SVM outpost program, which stands in for both roles. Every underwriter - therefore commits against the same deployment. -* **How to reach it** — the RPC client registered under that chain's **own - code**. `--outpost-ethereum-client` / `--outpost-solana-client` take the client - id as their first field, and for an outpost that id must be the chain code - (`ETHEREUM`, `SOLANA`, ...). - -Preflight fails closed when an active chain has no RPC client registered under -its code, or when its row carries no contract addresses. A -`sysio.chains::setoutpost` redeploy is picked up on the next scan tick — the -client handle is rebuilt against the new address. - -SEC-13/WSA-027 is preserved by construction: the client is keyed by exact chain -code, so two chains of the same VM family are wired independently and a -wrong-family entry is simply a client that is not there. - -## HTTP diagnostics - -Read-only diagnostic endpoints, served by `http_plugin` on the -read-only exec queue: - -- `/v1/underwriter/stats` — session counters + config snapshot: - underwriter account, enabled/active flags, scan + timeout intervals, - per-chain outpost wiring (`chain_code`, `kind`, `client_id`, - `commit_addr`, `source_deposit_addr`), uwreq/commit/failure/mismatch - counters, outstanding-commit count, SOL source-deposit cursor health. -- `/v1/underwriter/commits` — outstanding confirmed commits, one entry - per leg: `uwreq_id`, `chain_code`, `token_code`, `reserve_code`. - -Both carry a `status` discriminator. Until the deferred startup body -completes they report the startup-gate state instead of the payload -(`waiting_for_sync` with `head_behind_sec` / `lib_behind_sec`, -`preflight_retrying`, or a terminal `preflight_failed` / -`wiring_failed` / `startup_failed` with `detail`); once the gate opens -they serve the payloads above with `status: "active"`. - -The endpoints are registered only when the underwriter is enabled, -which means only when `--underwriter-account` is configured. With the -plugin loaded but no account, `plugin_startup` skips endpoint -registration and every listener returns 404 for these routes. - -### Listener exposure - -The endpoints live in the dedicated `underwriter` HTTP API category — -not the always-on `node` category — because they expose operator -metadata (account identity, client ids, outpost contract addresses, -the outstanding-commit ledger). - -Default deployments are unchanged: the all-category listeners -(`--http-server-address`, `--unix-socket-path`) serve -`/v1/underwriter/*` as before. - -Category-isolated deployments -(`--http-server-address http-category-address`) must opt in -explicitly: listeners without the `underwriter` category return 404 -for these routes and omit them from `/v1/node/get_supported_apis`. -Bind the category to loopback or a private management network: - -``` -nodeop \ - --http-server-address http-category-address \ - --http-category-address underwriter,127.0.0.1:8890 \ - --plugin sysio::underwriter_plugin \ - --underwriter-account myuwriter \ - ... -``` - -The listener also serves the node-global endpoints, which are -reachable on every listener by design: `/v1/node/get_supported_apis` -(always registered) and `/v1/chain/get_info` when -`sysio::chain_api_plugin` is loaded — the underwriter depends only on -`chain_plugin`, so the example above does not load it. Like every -category, -`underwriter` is validated against its owning plugin: naming it in -`--http-category-address` without `--plugin sysio::underwriter_plugin` -is a startup configuration error. Binding it to a non-loopback address -logs a startup warning (the same pattern as the `snapshot_ro` exposure -notice). - -Query them directly over HTTP, e.g. -`curl http://127.0.0.1:8890/v1/underwriter/stats`. - -## Dependencies - -- `chain_plugin` — read-only table access against `sysio.opreg`, `sysio.uwrit`, `sysio.authex`, `sysio.chains`. -- `cron_plugin` — scheduled scan loop. -- `signature_provider_manager_plugin` — fixed-size recoverable WIRE signer for - the UIC digest (K1, R1, EM, or ED). -- `outpost_ethereum_client_plugin` — ETH RPC + ABI loader for the `commit(bytes)` call. -- `outpost_solana_client_plugin` — SOL RPC + IDL loader for the `commit_underwrite(uic_bytes)` call. - -## Deferred / follow-up - -The current implementation covers canonical commit construction, bounded -cover selection, full-candidate collateral pre-validation, terminal depot -resolution, and restart-safe missing-leg routing. The following optional -locator improvement remains out of scope: - -- **Source-deposit locator hardening** — the current verifier validates - `SwapRequest.source_tx_id` before committing; ETH uses a bounded - `eth_getLogs` window over finalized blocks, while SOL reads the source - tx directly. Future work can carry a richer tx/block locator to avoid - event search entirely. diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/routing_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/routing_detail.hpp deleted file mode 100644 index 7e61c41544..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/routing_detail.hpp +++ /dev/null @@ -1,175 +0,0 @@ -#pragma once -/** - * @file routing_detail.hpp - * @brief Pure, side-effect-free routing/accounting helpers for the underwriter - * plugin, lifted out of the `.cpp`-private `impl` so they are unit-testable - * without standing up a chain. - * - * SEC-13 / WSA-027: the depot identifies every value-bearing underwrite leg by - * its EXACT slug codes — `chain_code`, `token_code`, `reserve_code` — and an - * underwriter's collateral is posted per exact `(chain_code, token_code)` on every - * registered outpost. Two active chains of the same VM family (e.g. two EVM - * outposts) therefore hold INDEPENDENT collateral and back DISTINCT legs. The - * keys below are exact slug values (the packed `fc::slug_name` `uint64`), never the - * coarse `ChainKind`/`TokenKind` family enums, so same-family chains never collide. - * - * Codes are carried here as the raw `uint64` `fc::slug_name::value` (not - * `fc::slug_name`) to keep this header free of fc/opp dependencies — the plugin - * converts at the boundary, and tests can construct keys from plain integers. - */ - -#include -#include -#include - -namespace sysio::underwriter_detail { - -/// Identity of a collateral / credit bucket: the exact `(chain_code, token_code)` -/// slug pair. Collateral is posted per chain+token (NOT per reserve — multiple -/// reserves on one `(chain, token)` draw from the same bond), mirroring the -/// `sysio.opreg::operators.balances` row key. -struct bucket_key { - uint64_t chain_code = 0; ///< `fc::slug_name::value` of the leg's chain. - uint64_t token_code = 0; ///< `fc::slug_name::value` of the leg's token. - friend auto operator<=>(const bucket_key&, const bucket_key&) = default; -}; - -/// One swap leg's bond requirement for the bucket solver. `require == 0` marks a -/// WIRE depot leg (no outpost, no bond, no bucket consulted). -struct leg_bond { - bucket_key bucket{}; - uint64_t require = 0; -}; - -/// Construct the full winner-time collateral requirement for one candidate -/// leg. Only depot legs require no collateral. A UIC already submitted to or -/// stored from an outpost has not created a depot lock, so it still consumes -/// capacity during pre-validation of the candidate's final missing leg. -inline leg_bond candidate_leg_bond(bucket_key bucket, - uint64_t amount, - bool is_depot) { - return { - .bucket = bucket, - .require = is_depot ? 0 : amount, - }; -} - -/// Per-bucket spendable credit: exact `(chain_code, token_code)` → balance. -using credit_buckets = std::map; - -/// Attempt to debit a request's two legs from `remaining`. Returns true (and -/// mutates `remaining`) iff every required leg fits; leaves `remaining` untouched -/// on failure. A request with both legs requiring zero (degenerate depot/depot) -/// returns false. Same-bucket dual legs (e.g. ERC20 → native on one chain) debit -/// both from the single shared row, so a same-`(chain, token)` swap needs the sum. -inline bool try_debit_buckets(credit_buckets& remaining, - const leg_bond& src, const leg_bond& dst) { - const uint64_t src_req = src.require; - const uint64_t dst_req = dst.require; - if (src_req == 0 && dst_req == 0) return false; - - // Same bucket on both legs: a single row must cover the combined draw. - if (src_req > 0 && dst_req > 0 && src.bucket == dst.bucket) { - auto it = remaining.find(src.bucket); - // Sum the two user-controlled requirements in 128-bit width: a pair - // whose true sum exceeds UINT64_MAX (e.g. UINT64_MAX + 1) must never - // wrap to a small uint64 and make an impossible request look coverable. - const __uint128_t combined = static_cast<__uint128_t>(src_req) + dst_req; - if (it == remaining.end() || it->second < combined) return false; - // Reached only when combined <= it->second <= UINT64_MAX, so the draw - // fits a uint64 exactly and the debit cannot underflow. - it->second -= static_cast(combined); - return true; - } - - // Distinct buckets: check both fit BEFORE mutating either, so a partial - // failure never leaves `remaining` half-debited. - if (src_req > 0) { - auto it = remaining.find(src.bucket); - if (it == remaining.end() || it->second < src_req) return false; - } - if (dst_req > 0) { - auto it = remaining.find(dst.bucket); - if (it == remaining.end() || it->second < dst_req) return false; - } - if (src_req > 0) remaining[src.bucket] -= src_req; - if (dst_req > 0) remaining[dst.bucket] -= dst_req; - return true; -} - -/// Conservatively reserve the future depot locks for an in-flight candidate -/// that has no outpost submission work left. Unlike `try_debit_buckets`, this -/// operation is intentionally saturating: if concurrent state changes have -/// already reduced a bucket below the outstanding requirement, no additional -/// request may consume the remainder while the paid UIC is still reaching the -/// depot. -inline void reserve_buckets(credit_buckets& remaining, - const leg_bond& src, const leg_bond& dst) { - const auto reserve = [&](const leg_bond& leg) { - if (leg.require == 0) return; - auto it = remaining.find(leg.bucket); - if (it == remaining.end()) return; - it->second = it->second > leg.require ? it->second - leg.require : 0; - }; - reserve(src); - reserve(dst); -} - -/// Local commit de-dup key — one CONFIRMED leg. Keyed by the exact leg -/// identity `(uwreq_id, chain_code, token_code, reserve_code)` so two legs that -/// differ only by chain OR reserve (e.g. a same-`(chain, token)` swap with two -/// reserves) are tracked independently and never suppress each other. -struct commit_key { - uint64_t uwreq_id = 0; - uint64_t chain_code = 0; ///< `fc::slug_name::value` - uint64_t token_code = 0; ///< `fc::slug_name::value` - uint64_t reserve_code = 0; ///< `fc::slug_name::value` - friend auto operator<=>(const commit_key&, const commit_key&) = default; -}; - -/// Restart-safe work derived from one candidate's authoritative depot row. -/// A partial candidate submits only missing outpost legs. A complete candidate -/// awaits an external exact-UIC replay; an existing non-submitted terminal -/// candidate is still skipped. Depot legs never submit. -struct stored_commit_plan { - bool skip_candidate = false; - bool submit_source = false; - bool submit_destination = false; -}; - -/// True when at least one leg still requires a paid outpost submission after -/// combining the restart-safe depot plan with this process's confirmations. -inline bool has_submission_work(const stored_commit_plan& plan, - bool source_confirmed_locally, - bool destination_confirmed_locally) { - return (plan.submit_source && !source_confirmed_locally) || - (plan.submit_destination && !destination_confirmed_locally); -} - -/// Derive the restart-safe submission plan from one authoritative depot -/// candidate. Existing terminal candidates are skipped. A complete candidate -/// has no local submission work; otherwise only missing non-depot UIC legs -/// remain eligible for submission. -inline stored_commit_plan plan_stored_commits(bool candidate_exists, - bool intent_submitted, - bool source_is_depot, - bool destination_is_depot, - bool source_uic_stored, - bool destination_uic_stored) { - if (candidate_exists && !intent_submitted) { - return {.skip_candidate = true}; - } - const bool source_complete = source_is_depot || - (intent_submitted && source_uic_stored); - const bool destination_complete = destination_is_depot || - (intent_submitted && destination_uic_stored); - if (intent_submitted && source_complete && destination_complete) { - return {}; - } - return { - .submit_source = !source_complete, - .submit_destination = !destination_complete, - }; -} - -} // namespace sysio::underwriter_detail diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/solana_source_deposit_scanner.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/solana_source_deposit_scanner.hpp deleted file mode 100644 index a18fa7c365..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/solana_source_deposit_scanner.hpp +++ /dev/null @@ -1,324 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sysio::underwriter { - -/// Result categories for scanning one Solana `getSignaturesForAddress` page -/// for an authenticated `SwapDeposit` source-deposit marker. -enum class solana_source_deposit_page_status { - matched, - not_found, - deferred, - hard_mismatch -}; - -/// Inputs shared by every transaction inspected in one signature page. -struct solana_source_deposit_page_scan_config { - /// Deployed opp-outpost program id resolved from the loaded Anchor IDL. - std::string_view sol_program_id; - /// Full log prefix ending immediately before the 64-character hash field. - std::string_view marker_prefix; - /// Expected correlation hash recomputed from the depot UWREQ row. - const fc::crypto::keccak256& recomputed_hash; -}; - -/// Outcome details from scanning a single Solana signature page. -struct solana_source_deposit_page_scan_result { - solana_source_deposit_page_status status = solana_source_deposit_page_status::not_found; - /// Matching transaction signature when `status == matched`. - std::string matched_signature; - /// Last valid signature in the page. Use as the next `before` cursor only - /// when `status == not_found` and `page_exhausted == false`. - std::optional next_before; - /// True when the page had fewer results than the RPC page limit, so there - /// is no older page to request for this address history. - bool page_exhausted = false; - /// Human-readable reason for hard mismatches or deferred scans. - std::string reason; -}; - -/// Fetches one decoded Solana transaction for the supplied base58 signature. -using solana_transaction_fetcher = std::function; - -/// Cursor key for SOL source-deposit discovery. The uwreq id bounds the -/// cursor to one depot request while the deposit id prevents accidental reuse -/// if a stale entry survives across schema or replay changes. -struct solana_source_deposit_scan_key { - uint64_t uwreq_id; - uint64_t deposit_id; - friend auto operator<=>(const solana_source_deposit_scan_key&, - const solana_source_deposit_scan_key&) = default; -}; - -/// Persistent SOL signature scan progress for one pending UWREQ. -struct solana_source_deposit_scan_cursor { - /// Signature cursor passed to `getSignaturesForAddress(before=...)`. - std::optional before; - /// Number of pages inspected while walking this UWREQ's source deposit. - uint64_t pages_scanned = 0; - /// Number of signature-list entries inspected across scanned pages. - uint64_t signatures_scanned = 0; - /// True once verification found a terminal source-deposit failure. - bool terminal_failure = false; - /// First terminal failure reason, kept for later short-circuit logs. - std::string terminal_failure_reason; -}; - -/// Per-pending-UWREQ Solana scan state. -using solana_source_deposit_scan_cursor_map = - std::map; - -/// Result from recording a terminal SOL source-deposit verification failure. -struct solana_source_deposit_terminal_failure_result { - /// Updated cursor state after recording the failure. - solana_source_deposit_scan_cursor cursor; - /// True only for the first terminal failure recorded for this UWREQ. - bool first_failure = false; -}; - -/// Returns the current SOL source-deposit scan cursor for `key`, creating one -/// at the newest page when this is the first scan attempt for a UWREQ. -inline solana_source_deposit_scan_cursor -get_or_create_solana_source_deposit_scan_cursor(solana_source_deposit_scan_cursor_map& cursors, - const solana_source_deposit_scan_key& key) { - return cursors[key]; -} - -/// Returns terminal failure state for a SOL source-deposit scan, when present. -inline std::optional -get_solana_source_deposit_terminal_failure(const solana_source_deposit_scan_cursor_map& cursors, - const solana_source_deposit_scan_key& key) { - const auto it = cursors.find(key); - if (it == cursors.end() || !it->second.terminal_failure) return std::nullopt; - return it->second; -} - -/// Persists the next SOL source-deposit `before` cursor after a clean page -/// that did not contain the target marker. -inline solana_source_deposit_scan_cursor -advance_solana_source_deposit_scan_cursor(solana_source_deposit_scan_cursor_map& cursors, - const solana_source_deposit_scan_key& key, - std::string before, - size_t signature_count) { - auto& cursor = cursors[key]; - cursor.before = std::move(before); - cursor.pages_scanned++; - cursor.signatures_scanned += signature_count; - return cursor; -} - -/// Records a terminal SOL source-deposit failure without allowing future scan -/// cycles to re-walk the same pending UWREQ from the newest signature page. -inline solana_source_deposit_terminal_failure_result -record_solana_source_deposit_terminal_failure(solana_source_deposit_scan_cursor_map& cursors, - const solana_source_deposit_scan_key& key, - std::string reason, - size_t signature_count) { - auto& cursor = cursors[key]; - const bool first_failure = !cursor.terminal_failure; - if (first_failure) { - cursor.pages_scanned++; - cursor.signatures_scanned += signature_count; - cursor.terminal_failure = true; - cursor.terminal_failure_reason = std::move(reason); - } - return {.cursor = cursor, .first_failure = first_failure}; -} - -/// Drops SOL source-deposit scan progress for a UWREQ once it matches or -/// leaves the PENDING set. -inline void erase_solana_source_deposit_scan_cursor(solana_source_deposit_scan_cursor_map& cursors, - const solana_source_deposit_scan_key& key) { - cursors.erase(key); -} - -/// Prunes SOL source-deposit scan state for UWREQs that are no longer pending. -template -void prune_solana_source_deposit_scan_cursors(solana_source_deposit_scan_cursor_map& cursors, - const PendingUwreqIds& still_pending) { - std::erase_if(cursors, [&](const auto& entry) { - return !still_pending.contains(entry.first.uwreq_id); - }); -} - -/// Returns true when a Solana log line opens, closes, or otherwise describes -/// an invocation frame rather than carrying a user `Program log:` payload. -inline bool is_solana_program_frame_line(const std::string& line) { - return boost::algorithm::starts_with(line, "Program ") && - !boost::algorithm::starts_with(line, "Program log:") && - !boost::algorithm::starts_with(line, "Program data:") && - !boost::algorithm::starts_with(line, "Program return:"); -} - -/// Updates the executing-program stack from one Solana runtime frame line. -inline void update_solana_program_stack(const std::string& line, - std::vector& program_stack) { - std::string_view rest{line}; - rest.remove_prefix(8); // strip "Program " - const size_t sp1 = rest.find(' '); - if (sp1 == std::string_view::npos) return; - - const std::string_view prog = rest.substr(0, sp1); - std::string_view after = rest.substr(sp1 + 1); - const size_t sp2 = after.find(' '); - const std::string_view verb = (sp2 == std::string_view::npos) ? after : after.substr(0, sp2); - if (verb == "invoke") { - program_stack.emplace_back(prog); - } else if (verb.starts_with("success") || verb.starts_with("failed")) { - if (!program_stack.empty()) program_stack.pop_back(); - } -} - -/// Decodes one lower-case hex digit from the canonical marker hash. -inline std::optional decode_lowercase_hex_digit(char c) { - if (c >= '0' && c <= '9') return static_cast(c - '0'); - if (c >= 'a' && c <= 'f') return static_cast(c - 'a' + 10); - return std::nullopt; -} - -/// Parses the 64-character lower-case hex hash suffix emitted by -/// `opp-outpost::request_swap` after the canonical marker prefix. -inline std::optional> parse_solana_swap_deposit_hash(std::string_view hash_hex) { - if (hash_hex.size() != 64) return std::nullopt; - - std::array parsed{}; - for (size_t i = 0; i < parsed.size(); ++i) { - const auto high = decode_lowercase_hex_digit(hash_hex[i * 2]); - const auto low = decode_lowercase_hex_digit(hash_hex[i * 2 + 1]); - if (!high || !low) return std::nullopt; - parsed[i] = static_cast((*high << 4) | *low); - } - return parsed; -} - -/// Scans one page of Solana program signatures for a source-deposit marker. -/// -/// The function is deliberately free of plugin state: production code supplies -/// the RPC-backed transaction fetcher, and unit tests supply an in-memory -/// fetcher. Pagination is page-at-a-time; callers persist `next_before` only -/// after a clean `not_found` result so transient transaction fetch failures -/// cannot advance past an uninspected candidate. -inline solana_source_deposit_page_scan_result -scan_solana_source_deposit_signature_page(const std::vector& sigs, - const solana_transaction_fetcher& get_transaction, - const solana_source_deposit_page_scan_config& config) { - solana_source_deposit_page_scan_result result; - result.page_exhausted = sigs.size() < SOL_SIGNATURE_SCAN_PAGE_SIZE; - bool saw_uninspected_candidate = false; - std::string deferred_reason; - - for (const auto& sig_var : sigs) { - if (!sig_var.is_object()) continue; - const auto sig_obj = sig_var.get_object(); - if (!sig_obj.contains("signature") || !sig_obj["signature"].is_string()) continue; - const std::string sig_b58 = sig_obj["signature"].as_string(); - result.next_before = sig_b58; - - // Skip failed txs at the listing level. They cannot have emitted the - // successful source-deposit marker. - if (sig_obj.contains("err") && !sig_obj["err"].is_null()) { - continue; - } - - fc::variant tx; - try { - tx = get_transaction(sig_b58); - } catch (const fc::exception& e) { - saw_uninspected_candidate = true; - if (deferred_reason.empty()) { - deferred_reason = "getTransaction(" + sig_b58 + ") RPC error: " + e.to_detail_string(); - } - continue; - } - if (tx.is_null() || !tx.is_object()) continue; - const auto tx_obj = tx.get_object(); - if (!tx_obj.contains("meta") || !tx_obj["meta"].is_object()) continue; - const auto meta_obj = tx_obj["meta"].get_object(); - if (meta_obj.contains("err") && !meta_obj["err"].is_null()) continue; - if (!meta_obj.contains("logMessages") || !meta_obj["logMessages"].is_array()) continue; - - std::vector program_stack; - for (const auto& line_var : meta_obj["logMessages"].get_array()) { - if (!line_var.is_string()) continue; - const std::string line = line_var.as_string(); - - if (is_solana_program_frame_line(line)) { - update_solana_program_stack(line, program_stack); - continue; - } - - if (!boost::algorithm::starts_with(line, config.marker_prefix)) { - continue; - } - - // Attribute "Program log:" to the program currently executing. - if (program_stack.empty() || program_stack.back() != config.sol_program_id) { - continue; - } - - const auto hash_hex = std::string_view{line}.substr(config.marker_prefix.size()); - const auto on_chain_hash = parse_solana_swap_deposit_hash(hash_hex); - if (!on_chain_hash) { - result.status = solana_source_deposit_page_status::hard_mismatch; - result.next_before.reset(); - result.reason = "marker hash is malformed"; - return result; - } - - if (std::memcmp(config.recomputed_hash.data(), on_chain_hash->data(), on_chain_hash->size()) != 0) { - const std::string want_hex = - fc::to_hex(reinterpret_cast(config.recomputed_hash.data()), 32); - result.status = solana_source_deposit_page_status::hard_mismatch; - result.next_before.reset(); - result.reason = "SwapDeposit hash mismatch: recomputed=" + want_hex + - " on-chain=" + std::string{hash_hex}; - return result; - } - - std::string conf_status; - if (sig_obj.contains("confirmationStatus") && sig_obj["confirmationStatus"].is_string()) { - conf_status = sig_obj["confirmationStatus"].as_string(); - } - if (conf_status != "finalized") { - result.status = solana_source_deposit_page_status::deferred; - result.next_before.reset(); - result.reason = "matching tx is not finalized"; - return result; - } - - result.status = solana_source_deposit_page_status::matched; - result.matched_signature = sig_b58; - result.next_before.reset(); - return result; - } - } - - if (saw_uninspected_candidate) { - result.status = solana_source_deposit_page_status::deferred; - result.next_before.reset(); - result.reason = std::move(deferred_reason); - } - - return result; -} - -} // namespace sysio::underwriter diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_constants.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_constants.hpp deleted file mode 100644 index 16033e7ce7..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_constants.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace sysio::underwriter { - -/// Byte length of an EVM address carried in `SwapRequest.actor.address`. -inline constexpr size_t EVM_DEPOSITOR_SIZE = 20; - -/// Byte length of a Solana Ed25519 public key carried in -/// `SwapRequest.actor.address`. -inline constexpr size_t SVM_DEPOSITOR_SIZE = 32; - -/// ETH: maximum number of recent blocks one source-deposit `eth_getLogs` -/// lookup may cover. -/// -/// The verifier only has the outpost-local `SwapDeposit.id`, so it still -/// needs an event lookup. Keep that lookup bounded to protect underwriter -/// scan cycles and EVM RPC providers from whole-history scans on absent or -/// expensive ids. The window is anchored on Ethereum's `finalized` block so -/// the underwriter never fronts collateral against reversible source-chain -/// history. 7200 mainnet blocks is roughly one day at 12 seconds per block; -/// deployments with a different source-chain cadence can override the plugin -/// option without changing code. -inline constexpr uint64_t ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS = 7200; - -/// Configuration option name for the EVM source-deposit log lookup window. -inline constexpr std::string_view ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION = - "underwriter-eth-source-deposit-lookback-blocks"; - -/// JSON-RPC quantity prefix used for explicit EVM block numbers. -inline constexpr std::string_view ETH_JSON_RPC_QUANTITY_PREFIX = "0x"; - -/// Parses an Ethereum JSON-RPC quantity into a block number. -/// -/// The parser requires the canonical `0x` prefix, a non-empty hex body, full -/// consumption, and no overflow. Malformed provider fields return -/// `std::nullopt` so callers can fail closed without throwing through the scan -/// cycle. -inline std::optional eth_parse_block_quantity(std::string_view block_quantity) { - if (block_quantity.size() <= ETH_JSON_RPC_QUANTITY_PREFIX.size()) return std::nullopt; - if (block_quantity[0] != '0' || (block_quantity[1] != 'x' && block_quantity[1] != 'X')) { - return std::nullopt; - } - - block_quantity.remove_prefix(ETH_JSON_RPC_QUANTITY_PREFIX.size()); - uint64_t out = 0; - const char* const begin = block_quantity.data(); - const char* const end = begin + block_quantity.size(); - const auto [ptr, ec] = std::from_chars(begin, end, out, 16); - if (ec != std::errc{} || ptr != end) return std::nullopt; - return out; -} - -/// Returns the inclusive lower block bound for a bounded EVM source-deposit -/// log lookup. -/// -/// `lookback_blocks` is the maximum inclusive span. A one-block lookback -/// searches only `head_block`; larger values walk backward from the captured -/// head without underflowing below genesis. A zero value is invalid at the -/// plugin option boundary, but this helper defensively collapses it to the -/// current head so arithmetic remains well-defined in tests and callers. -inline constexpr uint64_t eth_source_deposit_from_block(uint64_t head_block, uint64_t lookback_blocks) { - if (lookback_blocks <= 1) return head_block; - const uint64_t trailing_blocks = lookback_blocks - 1; - return head_block > trailing_blocks ? head_block - trailing_blocks : 0; -} - -/// Formats an EVM block number as a JSON-RPC quantity string. -/// -/// Ethereum JSON-RPC expects explicit block numbers as lowercase hex with a -/// `0x` prefix and no leading zeroes, e.g. `0x0`, `0xf`, `0x10`. -inline std::string eth_block_quantity(uint64_t block_num) { - char digits[sizeof(uint64_t) * 2]{}; - auto [ptr, ec] = std::to_chars(digits, digits + sizeof(digits), block_num, 16); - (void)ec; - - std::string out{ETH_JSON_RPC_QUANTITY_PREFIX.data(), ETH_JSON_RPC_QUANTITY_PREFIX.size()}; - out.append(digits, ptr); - return out; -} - -/// SOL: the JSON-RPC commitment level used when fetching a source-deposit -/// tx. `confirmed` = the tx has been voted on by a supermajority of the -/// cluster (one-vote level, ~400ms-2s after submission, typical Solana -/// reorg-immunity threshold for non-critical reads). `processed` is -/// faster (leader-only, can reorg) and `finalized` is slower (~12.8s+ -/// for 32-slot finality). -/// -/// `confirmed` strikes the balance: as short as reasonable while still -/// guaranteeing the tx will eventually land in the canonical chain -/// barring a catastrophic cluster failure. The single point of -/// configuration here means any future bump (e.g. to `finalized` if -/// reorgs become a real concern) is one edit. -inline constexpr auto SOL_COMMITMENT = - fc::network::solana::commitment_t::confirmed; - -/// SOL: maximum page size accepted by Solana JSON-RPC -/// `getSignaturesForAddress`. The underwriter walks one page per retry -/// callback and persists the `before` cursor between outer scan cycles so -/// unrelated newer program traffic cannot keep an older valid source deposit -/// outside the verifier's search window forever. -inline constexpr size_t SOL_SIGNATURE_SCAN_PAGE_SIZE = 1000; - -} // namespace sysio::underwriter diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_hash_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_hash_detail.hpp deleted file mode 100644 index 701376bd9d..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/source_deposit_hash_detail.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include - -namespace sysio::underwriter { - -/// Number of unsigned 64-bit fields in the outpost correlation-hash payload. -inline constexpr size_t SOURCE_DEPOSIT_U64_FIELD_COUNT = 7; - -/// Immutable source-outpost fields covered by the `SwapDeposit` correlation -/// hash. The outposts hash the user's accepted `target_amount`, not the depot's -/// mutable AMM quote (`uwreq.dst_amount`). Keeping the two names distinct here -/// prevents a later re-price from invalidating otherwise genuine deposits. -struct source_deposit_hash_input { - std::span depositor; - uint64_t source_amount; - uint64_t source_token_code; - uint64_t source_reserve_code; - uint64_t target_chain_code; - uint64_t target_token_code; - uint64_t target_reserve_code; - uint64_t target_amount; - uint32_t target_tolerance_bps; -}; - -/// Reproduce the outposts' packed `SwapDeposit` correlation hash. -inline fc::crypto::keccak256 source_deposit_hash(const source_deposit_hash_input& input) { - std::vector packed; - packed.reserve(input.depositor.size() + SOURCE_DEPOSIT_U64_FIELD_COUNT * sizeof(uint64_t) + - sizeof(uint32_t)); - packed.insert(packed.end(), input.depositor.begin(), input.depositor.end()); - - const auto append_big_endian = [&](auto value) { - using value_type = decltype(value); - static_assert(std::is_unsigned_v); - for (size_t remaining_bytes = sizeof(value_type); remaining_bytes > 0; --remaining_bytes) { - const size_t shift = (remaining_bytes - 1) * CHAR_BIT; - packed.push_back(static_cast(value >> shift)); - } - }; - - append_big_endian(input.source_amount); - append_big_endian(input.source_token_code); - append_big_endian(input.source_reserve_code); - append_big_endian(input.target_chain_code); - append_big_endian(input.target_token_code); - append_big_endian(input.target_reserve_code); - append_big_endian(input.target_amount); - append_big_endian(input.target_tolerance_bps); - - return fc::crypto::keccak256::hash( - std::span{packed.data(), packed.size()}); -} - -} // namespace sysio::underwriter diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/sync_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/sync_detail.hpp deleted file mode 100644 index 2f99d2cc1d..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/sync_detail.hpp +++ /dev/null @@ -1,149 +0,0 @@ -#pragma once -/** - * @file sync_detail.hpp - * @brief Pure gate-lifecycle machinery for the underwriter plugin — the deferred- - * startup states and the `/v1/underwriter/` gate payloads — lifted out of - * the `.cpp`-private impl so they are unit-testable without standing up a - * chain. - * - * The underwriter's startup preflight validates depot-side state (opreg - * registration, chain registry, authex links) via LOCAL table reads. On a - * cold-booting operator node those reads see mid-sync (possibly genesis) - * state and fail spuriously, so the plugin must not begin underwriting until - * the node is synced. The sync predicate itself is `controller::is_synced()` - * (LIB recency, woken by the `irreversible_block` channel), shared by every - * operator-daemon plugin; this header carries only the underwriter's OWN gate - * lifecycle surface. - */ - -#include -#include - -#include - -#include - -namespace sysio::underwriter_detail { - -/// JSON field keys shared by the gate payloads below, the post-startup -/// response builders in `underwriter_plugin.cpp`, and their tests — one -/// spelling authority instead of per-translation-unit string literals. -namespace field { - inline constexpr auto status = "status"; - inline constexpr auto head_behind_sec = "head_behind_sec"; - inline constexpr auto lib_behind_sec = "lib_behind_sec"; - inline constexpr auto detail = "detail"; -} - -/** - * @brief Lifecycle of the deferred startup body, surfaced via the - * `/v1/underwriter/...` HTTP endpoints. - * - * The endpoints register unconditionally during `plugin_startup` — every - * `http_plugin` handler registration must happen before the posted HTTP - * listener goes live, because the beast worker threads read the handler map - * lock-free — so a request can arrive while the startup body is still gated - * behind chain sync (or after it failed terminally). The handlers answer with - * this state until the deferred startup completes, giving operators a - * queryable surface for why underwriting isn't live. Member spellings ARE the - * wire `status` values (via `magic_enum::enum_name`). - */ -enum class startup_state : uint8_t { - waiting_for_sync, ///< Sync gate not yet armed — the node is still catching up. - preflight_retrying, ///< Gate armed; preflight incomplete, retrying within the bounded grace. - preflight_failed, ///< Preflight still failing after the retry grace (terminal). - wiring_failed, ///< Preflight passed but outpost client wiring threw (terminal). - startup_failed, ///< The deferred startup body threw past the specific - ///< failure paths above (terminal). Exists so an escaping - ///< exception is contained as a diagnosable gate state — - ///< stored and logged BEFORE the fail-fast shutdown — instead - ///< of unwinding the app executor mid-task with the gate - ///< still claiming `waiting_for_sync`. - active ///< Deferred startup completed — the scan cron is scheduled. -}; - -/** - * @brief True for the terminal deferred-startup FAILURE states — the states the - * uniform fail-fast policy shuts the node down on (see - * `underwriter_plugin.cpp`'s `quit_if_startup_failed_terminally`). - * - * `active` is terminal success, not failure; `preflight_retrying` is transient by - * definition (bounded by the retry grace). Kept beside the enum so a new state - * cannot be added without deciding its fail-fast classification here. - * - * @param s The deferred-startup lifecycle state to classify. - * @return True when `s` is a terminal failure state. - */ -inline constexpr bool is_terminal_failure(startup_state s) { - return s == startup_state::preflight_failed || s == startup_state::wiring_failed || - s == startup_state::startup_failed; -} - -/// The wire `status` spelling of {@link startup_state::active} — shared by the -/// post-startup response builders so the value has one authority beside the -/// enum whose member spellings define the wire format. -inline constexpr std::string_view active_status = magic_enum::enum_name(startup_state::active); -// Compile-time wire-format pin: renaming the enum member would silently change -// the HTTP `status` value every consumer switches on. -static_assert(active_status == "active"); - -/// `detail` carried by the {@link startup_state::preflight_failed} gate payload. -inline constexpr std::string_view preflight_failed_detail = - "startup preflight failed after sync — depot-side state for this underwriter " - "is incomplete; the node is shutting down (fail-fast; see node log)"; -/// `detail` carried by the {@link startup_state::wiring_failed} gate payload. -inline constexpr std::string_view wiring_failed_detail = - "outpost client wiring failed after preflight; the node is shutting down " - "(fail-fast; see node log)"; -/// `detail` carried by the {@link startup_state::startup_failed} gate payload. -inline constexpr std::string_view startup_failed_detail = - "deferred startup failed unexpectedly; the node is shutting down " - "(fail-fast; see node log)"; - -/** - * @brief The JSON body a `/v1/underwriter/...` endpoint answers with for `state`. - * - * Pure (unit-testable without a chain). Shape per state: - * - `waiting_for_sync` → `{"status":"waiting_for_sync","head_behind_sec":N - * [,"lib_behind_sec":M]}` — the gate's criterion is the IRREVERSIBLE - * state (`lib_behind_sec`); the head gap is reported alongside so a - * head-current-but-finality-stalled node is distinguishable from one - * still catching up. `lib_behind_sec` is ABSENT until an irreversible - * root exists: an in-band sentinel would collide with real negative - * gaps (clock skew) after whole-second truncation. - * - `preflight_retrying` → `{"status":"preflight_retrying"}` - * - `preflight_failed` / `wiring_failed` / `startup_failed` → - * `{"status":...,"detail":...}` - * - `active` → `{"status":"active"}` (callers normally serve the real - * payload instead once active — the builders stamp the same field). - * - * @param state The current deferred-startup lifecycle state. - * @param head_behind How far the head trails wall-clock now; consumed only by - * the `waiting_for_sync` payload (emitted in whole seconds; - * may be negative under clock skew). - * @param lib_behind How far the last-irreversible block trails wall-clock - * now, or empty while no irreversible root exists yet (the - * key is then omitted); consumed only by the - * `waiting_for_sync` payload. - * @return The response body as an `fc::variant`. - */ -inline fc::variant startup_gate_payload(startup_state state, fc::microseconds head_behind, - std::optional lib_behind) { - fc::mutable_variant_object body; - body(field::status, std::string{magic_enum::enum_name(state)}); - if (state == startup_state::waiting_for_sync) { - body(field::head_behind_sec, head_behind.to_seconds()); - if (lib_behind) { - body(field::lib_behind_sec, lib_behind->to_seconds()); - } - } else if (state == startup_state::preflight_failed) { - body(field::detail, std::string{preflight_failed_detail}); - } else if (state == startup_state::wiring_failed) { - body(field::detail, std::string{wiring_failed_detail}); - } else if (state == startup_state::startup_failed) { - body(field::detail, std::string{startup_failed_detail}); - } - return fc::variant(std::move(body)); -} - -} // namespace sysio::underwriter_detail diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_construction_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_construction_detail.hpp deleted file mode 100644 index c2b8b59fd0..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_construction_detail.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace sysio::underwriter_detail { - -/** - * Return the canonical external caller-address size for a UIC outpost kind. - * @param kind Outpost chain family carried by the signed UIC. - * @return 20 for EVM, 32 for SVM, or zero for an unsupported kind. - */ -inline size_t expected_uic_caller_address_size( - sysio::opp::types::ChainKind kind) { - switch (kind) { - case sysio::opp::types::CHAIN_KIND_EVM: - return fc::crypto::chain_address_size_ethereum; - case sysio::opp::types::CHAIN_KIND_SVM: - return fc::crypto::chain_address_size_solana; - default: - return 0; - } -} - -/** - * Populate the non-default chain kind and exact authenticated caller bytes - * required by every production underwriter UIC. - * - * @param uic UIC to populate after all validation succeeds. - * @param kind Configured outpost transaction signer's chain family. - * @param address Authenticated chain-native caller bytes. - * @return true when the kind is supported and the address has its exact - * chain-native size; false without mutating `uic` otherwise. - */ -inline bool set_uic_authenticated_caller( - sysio::opp::attestations::UnderwriteIntentCommit& uic, - sysio::opp::types::ChainKind kind, - std::span address) { - const auto expected_size = expected_uic_caller_address_size(kind); - if (expected_size == 0 || address.size() != expected_size) return false; - - auto* ext_address = uic.mutable_uw_ext_chain_addr(); - ext_address->set_kind(kind); - ext_address->set_address(address.data(), address.size()); - return true; -} - -} // namespace sysio::underwriter_detail diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_signature_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_signature_detail.hpp deleted file mode 100644 index 3197f54f6b..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/uic_signature_detail.hpp +++ /dev/null @@ -1,279 +0,0 @@ -#pragma once -/** - * @file uic_signature_detail.hpp - * @brief Shared fixed-size recoverable signature policy for underwriter UIC - * preflight and construction. - * - * The depot accepts the canonical packed K1, R1, EM, and ED variants. These - * helpers keep the plugin's startup self-test, runtime construction guard, and - * unit tests on that same non-parsing boundary. Variable-size WebAuthn and - * unrecoverable BLS signatures are intentionally excluded. - */ - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sysio::underwriter_detail { - -using uic_signature_type = fc::crypto::signature::sig_type; - -using uic_signature_storage = fc::crypto::signature::storage_type; -static_assert(std::variant_size_v == 6); -static_assert(std::is_same_v, - fc::ecc::signature_shim>); -static_assert(std::is_same_v, - fc::crypto::r1::signature_shim>); -static_assert(std::is_same_v, - fc::crypto::webauthn::signature>); -static_assert(std::is_same_v, - fc::em::signature_shim>); -static_assert(std::is_same_v, - fc::crypto::ed::signature_shim>); -static_assert(std::is_same_v, - fc::crypto::bls::signature_shim>); - -/// Return the canonical fc-packed size for one concrete signature variant. -template -inline std::size_t packed_uic_signature_size() { - static const auto size = fc::raw::pack(fc::crypto::signature{ - fc::crypto::signature::storage_type{std::in_place_type} - }).size(); - return size; -} - -/// Return the canonical packed size for a depot-supported signature type. -inline std::size_t canonical_packed_uic_signature_size(uic_signature_type type) { - switch (type) { - case uic_signature_type::k1: - return packed_uic_signature_size(); - case uic_signature_type::r1: - return packed_uic_signature_size(); - case uic_signature_type::em: - return packed_uic_signature_size(); - case uic_signature_type::ed: - return packed_uic_signature_size(); - default: - return 0; - } -} - -/// Domain-separated message signed by the provider during startup preflight. -inline constexpr std::string_view uic_signature_self_test_domain = - "wire.underwriter_plugin.signature_self_test.v1"; - -/** - * @brief Test whether a configured UIC provider public key is fixed-size and - * recoverable by the depot. - * @param key Provider public key to classify. - * @return True for K1, R1, EM, or ED. - */ -inline bool is_supported_uic_public_key(const fc::crypto::public_key& key) { - using key_type = fc::crypto::public_key::key_type; - return key.contains_type(key_type::k1, key_type::r1, key_type::em, key_type::ed); -} - -/** - * @brief Test whether a provider's UIC signature is fixed-size and recoverable. - * @param signature Signature to classify. - * @return True for K1, R1, EM, or ED. - */ -inline bool is_supported_uic_signature(const fc::crypto::signature& signature) { - using sig_type = fc::crypto::signature::sig_type; - return signature.contains_type(sig_type::k1, sig_type::r1, sig_type::em, sig_type::ed); -} - -/** - * @brief Select supported UIC signers from providers assigned to the WIRE - * target chain. - * - * K1/R1 providers use the WIRE native key format, while EM/ED providers use - * the Ethereum/Solana native key formats. Provider selection must therefore - * filter by the concrete public-key variant rather than by one native-format - * `chain_key_type_t` bucket. - * - * Automatically generated defaults are useful for ordinary WIRE transaction - * signing, but they must not compete with the operator's deliberate UIC key. - * The caller supplies both the manager's operator-configuration predicate and - * a current direct-permission-key predicate. Their intersection prevents an - * unrelated operator-configured WIRE provider (for example, a block-signing - * key) from competing with the underwriter account's UIC key. Both named and - * anonymous `--signature-provider` entries express operator intent; generated - * defaults and programmatic registrations do not. - * - * @param providers Providers returned by the signature-provider registry. - * @param is_operator_configured Predicate accepting one provider key name. - * @param is_permission_authorized Predicate accepting one provider public key. - * @return Operator-configured WIRE-target providers whose public keys are K1, - * R1, EM, or ED and are direct keys of the underwriter account's - * current owner or active permission. - */ -template -inline std::vector select_uic_signature_providers( - const std::vector& providers, - IsOperatorConfigured&& is_operator_configured, - IsPermissionAuthorized&& is_permission_authorized) { - std::vector selected; - selected.reserve(providers.size()); - for (const auto& provider : providers) { - if (provider && - is_operator_configured(provider->key_name) && - provider->target_chain == fc::crypto::chain_kind_wire && - is_supported_uic_public_key(provider->public_key) && - is_permission_authorized(provider->public_key)) { - selected.push_back(provider); - } - } - return selected; -} - -/** - * @brief Test whether the provider key and produced signature use the same - * supported variant. - * @param key Provider public key. - * @param signature Signature returned by the provider. - * @return True only when their shared variant tags match. - */ -inline bool uic_signature_type_matches_provider_key( - const fc::crypto::public_key& key, - const fc::crypto::signature& signature) { - using key_type = fc::crypto::public_key::key_type; - using sig_type = fc::crypto::signature::sig_type; - static_assert(magic_enum::enum_integer(key_type::k1) == - magic_enum::enum_integer(sig_type::k1)); - static_assert(magic_enum::enum_integer(key_type::r1) == - magic_enum::enum_integer(sig_type::r1)); - static_assert(magic_enum::enum_integer(key_type::em) == - magic_enum::enum_integer(sig_type::em)); - static_assert(magic_enum::enum_integer(key_type::ed) == - magic_enum::enum_integer(sig_type::ed)); - return magic_enum::enum_integer(key.type()) == - magic_enum::enum_integer(signature.type()); -} - -/** - * @brief Validate the depot's exact packed fixed-size UIC boundary. - * @param packed Packed fc signature bytes. - * @return True for canonical K1/R1/EM (66 bytes) or ED (97 bytes). - */ -inline bool is_canonical_packed_uic_signature(std::span packed) { - if (packed.empty()) return false; - const auto type = magic_enum::enum_cast( - static_cast(static_cast(packed.front()))); - if (!type) return false; - const auto expected_size = canonical_packed_uic_signature_size(*type); - return expected_size != 0 && packed.size() == expected_size; -} - -/** - * @brief Validate the recovery-byte and scalar policy for one packed UIC signature. - * @param packed Canonically sized fc-packed signature bytes. - * @return True for ED, or for K1/R1/EM with their canonical recovery range, - * nonzero in-range `r`, and low `s`. - */ -inline bool has_canonical_uic_signature_body(std::span packed) { - if (!is_canonical_packed_uic_signature(packed)) return false; - const auto type = magic_enum::enum_cast( - static_cast(static_cast(packed.front()))); - if (!type) return false; - const auto body = packed.subspan(1); - switch (*type) { - case uic_signature_type::k1: - return sysio::opp::is_canonical_uic_ecdsa_signature( - body, sysio::opp::uic_ecdsa_curve::secp256k1, - sysio::opp::uic_ecdsa_recovery_position::prefix, - sysio::opp::uic_ecdsa_recovery_encoding::compact); - case uic_signature_type::r1: - return sysio::opp::is_canonical_uic_ecdsa_signature( - body, sysio::opp::uic_ecdsa_curve::p256, - sysio::opp::uic_ecdsa_recovery_position::prefix, - sysio::opp::uic_ecdsa_recovery_encoding::compact); - case uic_signature_type::em: - return sysio::opp::is_canonical_uic_ecdsa_signature( - body, sysio::opp::uic_ecdsa_curve::secp256k1, - sysio::opp::uic_ecdsa_recovery_position::suffix, - sysio::opp::uic_ecdsa_recovery_encoding::ethereum); - case uic_signature_type::ed: - return true; - default: - return false; - } -} - -/// Result of the signature-shape portion of underwriter startup preflight. -enum class uic_signature_provider_result { - compatible, ///< Provider satisfies the complete fixed-shape policy. - unsupported_public_key_type, ///< Configured provider key is WA or BLS. - unsupported_signature_type, ///< Provider returned WA or BLS. - signature_type_mismatch, ///< Provider key and signature variants differ. - non_canonical_packed_signature, ///< Packed result violates the depot boundary. - non_canonical_signature, ///< ECDSA recovery/scalar body is noncanonical. -}; - -/// Provider-policy result and the recovered key available on success. -struct uic_signature_provider_check { - uic_signature_provider_result result; ///< Shape-policy classification. - std::optional recovered_key; ///< Present only when compatible. -}; - -/** - * @brief Run the signature-shape portion of underwriter startup preflight. - * - * Account-permission authorization remains a chain-state check in - * `run_preflight`; this shared policy prevents its implementation and unit - * regression from drifting apart. - * - * @param provider Configured WIRE provider to exercise. - * @param digest Fixed self-test digest the provider must sign. - * @return Shape classification and, on success, the recovered public key. - * @throws fc exceptions propagated by provider signing, packing, or recovery. - */ -inline uic_signature_provider_check check_uic_signature_provider( - const fc::crypto::signature_provider_t& provider, - const fc::sha256& digest) { - if (!is_supported_uic_public_key(provider.public_key)) { - return {uic_signature_provider_result::unsupported_public_key_type, std::nullopt}; - } - - const auto signature = provider.sign(digest); - if (!is_supported_uic_signature(signature)) { - return {uic_signature_provider_result::unsupported_signature_type, std::nullopt}; - } - if (!uic_signature_type_matches_provider_key(provider.public_key, signature)) { - return {uic_signature_provider_result::signature_type_mismatch, std::nullopt}; - } - - const auto packed = fc::raw::pack(signature); - if (!is_canonical_packed_uic_signature(packed)) { - return { - uic_signature_provider_result::non_canonical_packed_signature, - std::nullopt, - }; - } - if (!has_canonical_uic_signature_body(packed)) { - return { - uic_signature_provider_result::non_canonical_signature, - std::nullopt, - }; - } - - return { - uic_signature_provider_result::compatible, - fc::crypto::public_key::recover(signature, digest), - }; -} - -} // namespace sysio::underwriter_detail diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/underwriter_plugin.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/underwriter_plugin.hpp deleted file mode 100644 index 7e9a2877ae..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/underwriter_plugin.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace sysio { - - /// Underwriter plugin — polls for underwrite requests and submits intents. - /// - /// The underwriter does NOT relay OPP envelopes (that's the batch operator's job). - /// Instead it: - /// 1. Polls sysio.uwrit::uwreqs for PENDING underwrite requests. - /// 2. Reads its available credit and authoritative stored-candidate state. - /// 3. Suppresses terminal and already-complete candidates after restart. - /// 4. Selects absent/partial candidates whose complete winner-time bond fits - /// the daemon's provisional available-collateral view. - /// 5. Submits signed UICs only for missing legs to the relevant ACTIVE-role- - /// gated outpost contract (ETH/SOL), which caller-binds and canonicalizes - /// each UIC before relaying its original validated bytes. - /// 6. The depot gives the complete candidate one authoritative attempt, - /// then either creates locks, disqualifies a candidate-local failure, or - /// rejects and refunds a request-global failure. - namespace underwriter_defaults { - constexpr uint32_t scan_interval_ms = 5000; - constexpr uint32_t action_timeout_ms = 15000; - // The sync-recency window moved to `controller::default_sync_recency_ms` - // — the sync predicate is `controller::is_synced()`, shared by every - // operator-daemon plugin. - /// How long after the sync gate arms a failing preflight keeps - /// RETRYING before the failure is terminal. Rows the harness confirmed - /// final on the producer can land in the LOCAL irreversible state a - /// beat after the gate arms (observed live: a registration in block N - /// readable only 50ms after a preflight read at LIB N−3); the grace - /// absorbs that boundary race while a genuinely incomplete bootstrap - /// still fails loudly once it expires. - constexpr uint32_t preflight_retry_grace_ms = 15000; - /// Cadence (ms) of preflight retries within the grace window. - constexpr uint32_t preflight_retry_interval_ms = 500; - // SEC-13/WSA-027: the single eth/sol client-id defaults were removed with - // the single-client config — outpost wiring is now per-chain and required - // (--underwriter-{eth,sol}-outpost), with no single fallback. - } - - class underwriter_plugin : public appbase::plugin { - public: - APPBASE_PLUGIN_REQUIRES( - (chain_plugin) - (cron_plugin) - (http_plugin) - (outpost_ethereum_client_plugin) - (outpost_solana_client_plugin) - (signature_provider_manager_plugin) - ) - - underwriter_plugin(); - virtual ~underwriter_plugin(); - - virtual void set_program_options(options_description& cli, options_description& cfg) override; - void plugin_initialize(const variables_map& options); - void plugin_startup(); - void plugin_shutdown(); - - private: - struct impl; - std::unique_ptr _impl; - }; - -} // namespace sysio diff --git a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/variant_enum_detail.hpp b/plugins/underwriter_plugin/include/sysio/underwriter_plugin/variant_enum_detail.hpp deleted file mode 100644 index cfbc2060da..0000000000 --- a/plugins/underwriter_plugin/include/sysio/underwriter_plugin/variant_enum_detail.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include - -#include -#include -#include - -namespace sysio::underwriter_detail { - -/// Decode an FC-reflected enum at a table/ABI trust boundary. Reflected enum -/// decoding accepts both the ABI spelling and a numeric representation while -/// validating that the value names a declared enumerator. Malformed values are -/// reported as `nullopt` so callers can skip the untrusted row safely. -template -requires std::is_enum_v -std::optional decode_enum_variant(const fc::variant& value) { - // FC's generic reflected-enum path coerces bool/null/double through - // `as_int64()`. Admit only the two exact integer storage types produced by - // the ABI serializer plus symbolic strings, and range-check unsigned input - // before the reflected decoder converts through int64_t. - if (!value.is_string() && !value.is_int64() && !value.is_uint64()) { - return std::nullopt; - } - if (value.is_uint64() && - value.as_uint64() > static_cast( - std::numeric_limits::max())) { - return std::nullopt; - } - try { - return value.as(); - } catch (const fc::exception&) { - return std::nullopt; - } -} - -/// Look up and decode one required FC-reflected enum field without throwing on -/// a missing key. Both absence and malformed values return `nullopt` so a -/// caller can skip an untrusted table row atomically. -template -requires std::is_enum_v -std::optional decode_enum_field(const fc::variant_object& object, - const char* key) { - const auto it = object.find(key); - if (it == object.end()) return std::nullopt; - return decode_enum_variant(it->value()); -} - -} // namespace sysio::underwriter_detail diff --git a/plugins/underwriter_plugin/src/underwriter_plugin.cpp b/plugins/underwriter_plugin/src/underwriter_plugin.cpp deleted file mode 100644 index 5dbfe7ba86..0000000000 --- a/plugins/underwriter_plugin/src/underwriter_plugin.cpp +++ /dev/null @@ -1,3054 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace sysio { - -using namespace chain_apis; -using namespace sysio::opp::types; -namespace eth = fc::network::ethereum; -namespace opp_att = sysio::opp::attestations; -/// Field spellings for the `sysio.chains::chains` rows this plugin reads, -/// shared with batch_operator_plugin so the two daemons cannot drift apart. -namespace depot_chains = sysio::opp::depot::chains; - -// SEC-13/WSA-027: exact-(chain_code, token_code, reserve_code) routing / -// accounting keys, lifted to a testable detail header. These replace the -// former ChainKind/TokenKind-collapsed in-memory keys so two active chains of -// the same VM family (e.g. two EVM outposts) never share a credit bucket or a -// commit-dedup slot. -using underwriter_detail::bucket_key; -using underwriter_detail::leg_bond; -using underwriter_detail::credit_buckets; -using underwriter_detail::commit_key; - -namespace { - -/// Ethereum JSON-RPC method used to inspect the finalized source-chain head. -constexpr std::string_view ETH_GET_BLOCK_BY_NUMBER_METHOD = "eth_getBlockByNumber"; -/// `sysio.uwrit` ABI identifiers used by the daemon. Keeping the contract, -/// action, table, and row-field spellings together makes host/contract schema -/// drift visible in one place. -namespace uwrit { -constexpr auto account = "sysio.uwrit"; -constexpr auto table_locks = "locks"; -constexpr auto table_requests = "uwreqs"; - -namespace lock_field { -constexpr auto underwriter = "underwriter"; -constexpr auto amount = "amount"; -} - -namespace request_field { -constexpr auto id = "id"; -constexpr auto type = "type"; -constexpr auto status = "status"; -constexpr auto underwriter_name = "uw_name"; -constexpr auto source_chain_code = "src_chain_code"; -constexpr auto source_token_code = "src_token_code"; -constexpr auto source_reserve_code = "src_reserve_code"; -constexpr auto source_amount = "src_amount"; -constexpr auto destination_chain_code = "dst_chain_code"; -constexpr auto destination_token_code = "dst_token_code"; -constexpr auto destination_reserve_code = "dst_reserve_code"; -constexpr auto destination_amount = "dst_amount"; -constexpr auto target_amount = "target_amount"; -constexpr auto variance_tolerance_bps = "variance_tolerance_bps"; -constexpr auto commits_by = "commits_by"; -constexpr auto source_transaction_id = "source_tx_id"; -constexpr auto depositor = "depositor"; -} - -namespace commit_field { -constexpr auto underwriter = "underwriter"; -constexpr auto status = "status"; -constexpr auto source_received_at_ms = "source_received_at_ms"; -constexpr auto source_uic_bytes = "source_uic_bytes"; -constexpr auto destination_received_at_ms = "dest_received_at_ms"; -constexpr auto destination_uic_bytes = "dest_uic_bytes"; -} -} // namespace uwrit - -} // namespace - -// --------------------------------------------------------------------------- -// Underwrite request — read directly from sysio.uwrit::uwreqs table. -// -// Per Task 3's uwrit refactor, the row carries src/dst (chain, token_kind, -// amount) fields populated by createuwreq from the originating SwapRequest. -// No more chasing through sysio.msgch::attestations to decode the -// attestation payload — the data we need is right on the uwreq row. -// --------------------------------------------------------------------------- -struct uw_request { - uint64_t id; // attestation ID (PK of uwreqs table) - AttestationType attestation_type; // AttestationType that needs underwriting (e.g., SWAP) - UnderwriteRequestStatus status; // typed status (always PENDING for plugin-selected rows) - std::string uw_name; // assigned underwriter ('' if unassigned, populated post race-resolve) - ChainKind src_chain; - TokenKind src_token_kind; - uint64_t src_amount; - ChainKind dst_chain; - TokenKind dst_token_kind; - uint64_t dst_amount; - /// The destination amount the caller ASKED for, retained as the fixed - /// reference the depot's slippage check measures against. Distinct from - /// `dst_amount`, which is the depot's AMM quote and is re-priced at - /// settlement. The outposts fold THIS field — never `dst_amount` — into the - /// `SwapDeposit` correlation hash, so it is the value the source-deposit - /// verifier must reproduce. - uint64_t target_amount; - /// Per-leg slug_name triples (data-model). These are the authoritative - /// identifiers for the depot's `rcrdcommit` routing and the - /// `UnderwriteIntentCommit` (`chain_code` / `token_code` / `reserve_code`) - /// payload populated in `create_signed_uic_bytes`. The `ChainKind` / - /// `TokenKind` siblings above are retained for VM-family dispatch and - /// human-readable diagnostics; collateral bucketing uses the exact slug - /// codes below. - fc::slug_name src_chain_code{}; - fc::slug_name src_token_code{}; - fc::slug_name src_reserve_code{}; - fc::slug_name dst_chain_code{}; - fc::slug_name dst_token_code{}; - fc::slug_name dst_reserve_code{}; - /// Variance tolerance the user attached to the original SwapRequest - /// (as basis points). Verified in the source-deposit hash binding - /// so the underwriter cannot collude with a stale on-chain - /// `SwapDeposit` whose terms differ from the depot's UWREQ. - uint32_t variance_tolerance_bps{}; - /// Source-chain identifier. ETH and SOL both use the 8-byte big-endian - /// `SwapDeposit.id` minted by their source outpost's monotonic counter. - /// Populated by `createuwreq` from `SwapRequest.source_tx_id`. The - /// underwriter's source-deposit verifier interprets the bytes - /// per-chain. - std::vector source_tx_id; - - /// Depositor's address on the source chain (decoded from - /// `SwapRequest.actor.address`). The plugin's verify_source_deposit - /// step cross-references the source-chain tx's `from` field (ETH) or - /// fee-payer (SOL) against this to confirm the recorded depositor - /// actually authorized the deposit. - std::vector depositor; - - /// Per-leg depot flags — true when the leg's chain_code is the WIRE - /// depot's own registry row (`sysio.chains` `is_depot`). A depot leg is - /// not underwritten: no outpost commit, no bond, no source-deposit - /// verification. Stamped by `scan_pending_requests` from the exact - /// depot code captured in `read_outpost_registry` — NOT inferred from - /// `CHAIN_KIND_UNKNOWN`, which also matches genuinely-unregistered - /// chains. - bool src_is_depot = false; - bool dst_is_depot = false; - - /// Authoritative depot-row plan for this daemon's candidate. It - /// distinguishes missing-leg work from complete or terminal candidates even - /// after restart has emptied the local `confirmed_commits` cache. - underwriter_detail::stored_commit_plan own_stored_commit_plan; -}; - -// --------------------------------------------------------------------------- -// Credit line — per-(chain_code, token_code) bond from sysio.opreg::operators -// -// Reads the `balances` field (one aggregate balance per EXACT -// (chain_code, token_code) — SEC-13/WSA-027: keyed by the slug codes, NOT -// the coarse (ChainKind, TokenKind) family, so two same-family chains hold -// independent collateral). `read_credit_lines` starts from this raw balance -// and subtracts active locks plus pending withdrawals to mirror the depot's -// authoritative `sysio.opreg::available()` rollup. Winner selection still -// revalidates the live rollup because capacity can change after the scan. -// -// Codes held as raw `fc::slug_name::value` (uint64) to match `bucket_key`. -// --------------------------------------------------------------------------- -struct credit_line { - uint64_t chain_code; - uint64_t token_code; - uint64_t balance; -}; - -// --------------------------------------------------------------------------- -// Outpost endpoint — operator-supplied wiring for ONE active chain -// -// SEC-13/WSA-027: the underwriter (one process) multiplexes every chain it -// serves, so it holds one of these per EXACT `chain_code` — two chains of the -// same VM family (e.g. two EVM outposts) are configured and routed -// independently. The chain-agnostic deposit selector / instruction -// discriminator + event decoding still come from the shared ABI / IDL files; -// only the per-chain *addresses* and the RPC `client_id` live here. -// -// ETH chain: commit_addr = OperatorRegistry address (uw_commit) -// source_deposit_addr = SwapDeposit-emitting contract (verify) -// SOL chain: commit_addr = source_deposit_addr = opp-outpost program id -// --------------------------------------------------------------------------- -struct outpost_endpoint { - ChainKind kind = ChainKind::CHAIN_KIND_UNKNOWN; - std::string client_id; ///< RPC connection id in the outpost client plugin - /// Remote contract identities, filled by `read_outpost_registry` from the - /// chain's `sysio.chains` row rather than from this node's config, so every - /// underwriter commits against the same deployment. Empty until governance - /// configures the row; the preflight and the wiring step both fail closed. - std::string commit_addr; ///< ETH OperatorRegistry addr / SOL program id - std::string source_deposit_addr; ///< ETH SwapDeposit contract / SOL program id -}; - -// --------------------------------------------------------------------------- -// Implementation -// --------------------------------------------------------------------------- -struct underwriter_plugin::impl { - // Configuration - chain::name underwriter_account; - /// Derived from `underwriter_account` at plugin_initialize: the scan cycle - /// runs iff an account was configured. There is no separate enable flag. - bool enabled = false; - uint32_t scan_interval_ms = underwriter_defaults::scan_interval_ms; - uint32_t action_timeout_ms = underwriter_defaults::action_timeout_ms; - /// SEC-13/WSA-027: per-chain outpost wiring, keyed by EXACT `chain_code` - /// slug value — one entry per ACTIVE non-depot chain. Built entirely by - /// `read_outpost_registry` from `sysio.chains`: the registry names which - /// chains are served and where their contracts live, and the RPC client id - /// is the chain's own code. Nothing here comes from this node's config. - std::map outpost_endpoints; - /// Per-chain external (numeric) chain id, captured from `sysio.chains` - /// (`external_chain_id`) by `read_outpost_registry`, fed to - /// `create_outpost_client` so each client carries its chain's real id. - std::map outpost_external_chain_ids; - /// Maximum number of recent EVM blocks one `eth_getLogs` - /// source-deposit lookup may cover. Keeping this window bounded prevents - /// invalid or stale deposit ids from forcing whole-history RPC scans. - uint64_t eth_source_deposit_lookback_blocks = - sysio::underwriter::ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS; - /// Configured names of the swap-deposit function (ETH) / instruction - /// (SOL). The CHAIN-AGNOSTIC function selector / instruction discriminator - /// are resolved at preflight from the ABI / IDL files registered with the - /// outpost client plugins; the per-chain contract / program ADDRESS comes - /// from `outpost_endpoints` instead (SEC-13/WSA-027 — the selector is shared - /// across same-family chains, only the address varies). Both names required. - std::string eth_source_deposit_function_name; - std::string sol_source_deposit_instruction_name; - - /// Resolved-at-preflight CHAIN-AGNOSTIC verify-path state (the deposit - /// selector / discriminator), derived from the outpost client plugins' - /// ABI / IDL surfaces. Populated only after `run_preflight()` succeeds; the - /// per-chain address is looked up from `outpost_endpoints` at verify time. - std::vector resolved_eth_source_deposit_selector; - std::vector resolved_sol_source_deposit_discriminator; - - /// Unique operator-configured WIRE-target provider whose public key is a - /// direct key whose weight alone reaches this underwriter account's current - /// owner or active threshold. Populated only after preflight validates its - /// signature shape and recovered key; runtime UIC construction uses this - /// exact cached provider instead of re-querying a broader registry that also - /// contains unrelated WIRE providers such as block signers. - fc::crypto::signature_provider_ptr uic_signature_provider; - - // ── Diagnostic counters surfaced via the `/v1/underwriter/*` HTTP API. - // - // `source_deposit_mismatch_count` increments every time a source- - // deposit verification fails for a uwreq the plugin tried to cover. - // The other counters tally session-level outcomes — they reset on - // plugin restart (no on-disk persistence; the read endpoint is for - // live monitoring, not historical accounting). - uint64_t source_deposit_mismatch_count = 0; - uint64_t commits_confirmed_count = 0; - uint64_t commits_failed_count = 0; - uint64_t uwreqs_seen_pending_count = 0; - - // Protects `confirmed_commits`, `sol_source_deposit_scan_cursors`, and - // the diagnostic counters from concurrent access between the - // cron-callback (single-threaded) and the HTTP handler threads. The cron - // callback takes the lock around mutations; the HTTP handlers take it - // around reads. - mutable std::mutex stats_mutex; - - // Credit lines (read from sysio.opreg::operators each cycle) - std::vector credit_lines; - - // Awareness: own status from `sysio.opreg::operators[underwriter_account]`. - // SLASHED / TERMINATED short-circuits the relay loop. Refreshed each cycle - // by `poll_own_status()` (mirror of batch_operator_plugin's awareness). - bool is_active = true; - - // Plugin references - chain_plugin* chain_plug = nullptr; - cron_plugin* cron_plug = nullptr; - outpost_ethereum_client_plugin* eth_plug = nullptr; - outpost_solana_client_plugin* sol_plug = nullptr; - - // Cron job handle - cron_service::job_id_t scan_job_id = 0; - std::atomic shutting_down{false}; - - /// Sync gate: `channels::irreversible_block` subscription that arms - /// {@link run_deferred_startup} once `controller::is_synced()` holds — a LIB - /// advance is the only event that can turn the predicate true. Unsubscribed - /// after arming; otherwise the handle's destructor releases it (no shutdown - /// unsubscribe needed: posted channel deliveries are drained without - /// executing after quit, and the slot checks `shutting_down` first). - chain::plugin_interface::channels::irreversible_block::channel_type::handle sync_gate_subscription; - /// When the sync gate armed — bounds the preflight retry grace window. - fc::time_point startup_armed_at; - /// Bounded-grace preflight retry timer (main io_context; main-thread wait). - std::optional preflight_retry_timer; - /// Deferred-startup lifecycle surfaced by the `/v1/underwriter/*` handlers - /// (which register in `plugin_startup`, BEFORE the gate arms). Written on - /// the main thread by {@link run_deferred_startup}; read from the HTTP - /// handlers' read-only queue — hence atomic. Also the single source of - /// truth for "has the deferred startup been armed" ({@link - /// startup_attempted}) — every armed attempt leaves `waiting_for_sync` - /// before it returns, so no separate armed flag exists to drift. - std::atomic gate_state{ - underwriter_detail::startup_state::waiting_for_sync}; - - /// True once the deferred startup has been armed: {@link - /// attempt_deferred_startup} stores a non-waiting `gate_state` on every - /// path (retrying / a terminal failure / active), so "still - /// `waiting_for_sync`" IS "not yet armed". Guards the gate callback and - /// its posted task against re-arming (each site also checks - /// `shutting_down`, which covers the only path that returns before the - /// first store). - bool startup_attempted() const { - return gate_state.load() != underwriter_detail::startup_state::waiting_for_sync; - } - - // Outpost chain_kind cache: chain_code -> ChainKind - std::map outpost_chain_kinds; - - /// The WIRE depot's own chain code (the `is_depot` row of - /// `sysio.chains::chains`), captured by `read_outpost_registry` each - /// cycle. Used for exact per-leg depot detection on uwreq rows — - /// to/from-WIRE swaps have one depot leg that the plugin must skip - /// (no commit, no bond) rather than error on. - std::optional depot_chain_code; - - /// SPI handles to the configured outposts, keyed by EXACT `chain_code` slug - /// value (SEC-13/WSA-027 — NOT `ChainKind`, so two chains of the same VM - /// family each get their own client). Built at `plugin_startup` (after - /// preflight) from `outpost_endpoints` via the - /// `outpost_{ethereum,solana}_client_plugin::create_outpost_client` - /// factories. The relay loop selects by the leg's `chain_code` and calls - /// `outpost->uw_commit(...)` — every chain-specific concern (ABI / IDL - /// discovery, address encoding, on-chain confirmation) lives in the - /// concrete. Per `outpost-client-spi.md`. - std::map outpost_by_chain; - /// The `commit_addr` each entry of `outpost_by_chain` was built against, so - /// a `sysio.chains::setoutpost` redeploy is noticed and the handle rebuilt. - std::map wired_commit_addrs; - /// cross-walk: token slug_name → TokenKind enum. Refreshed each - /// scan cycle by `read_credit_lines` (which reads `sysio.tokens::tokens` - /// for the lookup); used by `scan_pending_requests` to translate the - /// uwreq row's `src/dst_token_code` slug into the `TokenKind` the - /// `select_coverable` bucket lookup needs. - std::map token_kind_by_code; - - // ── Outstanding commit tracking (one entry per CONFIRMED leg) ─────── - // Per `feedback`: an underwriter that confirmed a commit tx for a leg - // should NOT resubmit on the next scan cycle. The de-dup key is the EXACT - // leg identity `(uwreq_id, chain_code, token_code, reserve_code)` - // (`underwriter_detail::commit_key`) — NOT the coarse (ChainKind, - // TokenKind), so two legs differing only by chain or reserve are tracked - // independently (SEC-13/WSA-027). The set is pruned at the end of each scan - // cycle to drop entries whose uwreq is no longer PENDING (the depot has - // resolved the race), keeping the set bounded. - std::set confirmed_commits; - - /// Per-pending-UWREQ Solana scan cursors. Persisting `before` across - /// retry attempts and outer scan cycles prevents unrelated newer - /// opp-outpost traffic from keeping a valid source deposit forever - /// outside the newest-page window. Terminal negative entries remain until - /// the UWREQ leaves PENDING so failed scans do not re-walk full history or - /// re-bump mismatch counters every outer scan cycle. - underwriter::solana_source_deposit_scan_cursor_map sol_source_deposit_scan_cursors; - - /// Returns the current SOL source-deposit scan cursor for `key`, creating - /// one at the newest page when this is the first scan attempt for a UWREQ. - underwriter::solana_source_deposit_scan_cursor get_or_create_sol_source_deposit_scan_cursor( - const underwriter::solana_source_deposit_scan_key& key) { - std::lock_guard lk{stats_mutex}; - return underwriter::get_or_create_solana_source_deposit_scan_cursor( - sol_source_deposit_scan_cursors, key); - } - - /// Returns a terminal SOL source-deposit failure for `key`, when one has - /// already been recorded while the UWREQ remains PENDING. - std::optional - get_sol_source_deposit_terminal_failure( - const underwriter::solana_source_deposit_scan_key& key) const { - std::lock_guard lk{stats_mutex}; - return underwriter::get_solana_source_deposit_terminal_failure( - sol_source_deposit_scan_cursors, key); - } - - /// Persists the next SOL source-deposit `before` cursor after a clean page - /// that did not contain the target marker. - underwriter::solana_source_deposit_scan_cursor advance_sol_source_deposit_scan_cursor( - const underwriter::solana_source_deposit_scan_key& key, - std::string before, - size_t signature_count) { - std::lock_guard lk{stats_mutex}; - return underwriter::advance_solana_source_deposit_scan_cursor( - sol_source_deposit_scan_cursors, key, std::move(before), signature_count); - } - - /// Records a terminal SOL source-deposit failure while keeping the scan - /// state until the UWREQ leaves PENDING. - underwriter::solana_source_deposit_terminal_failure_result - record_sol_source_deposit_terminal_failure( - const underwriter::solana_source_deposit_scan_key& key, - std::string reason, - size_t signature_count) { - std::lock_guard lk{stats_mutex}; - return underwriter::record_solana_source_deposit_terminal_failure( - sol_source_deposit_scan_cursors, key, std::move(reason), signature_count); - } - - /// Drops SOL source-deposit scan progress for a UWREQ once it either - /// matches or leaves the PENDING set. - void erase_sol_source_deposit_scan_cursor( - const underwriter::solana_source_deposit_scan_key& key) { - std::lock_guard lk{stats_mutex}; - underwriter::erase_solana_source_deposit_scan_cursor( - sol_source_deposit_scan_cursors, key); - } - - // ----------------------------------------------------------------------- - // Table read helper - // ----------------------------------------------------------------------- - - /// Thin delegate to `chain_plugin::read_table_rows`, which posts the scan onto the app executor's read_only queue - /// so chainbase iteration runs during the controller's read window instead of racing with block apply. - sysio::chain_apis::read_only::get_table_rows_result - read_table(sysio::chain_apis::read_only::get_table_rows_params p) { - return chain_plug->read_table_rows(std::move(p), fc::milliseconds(action_timeout_ms), - "underwriter", shutting_down); - } - - /// Shortcut for the common scan shape: walk every row from a code/scope/table and return unwrapped values. - sysio::chain_apis::read_only::get_table_rows_result - read_all(std::string_view code, std::string_view scope, std::string_view table) { - sysio::chain_apis::read_only::get_table_rows_params p; - p.code = chain::name(code); - p.scope = scope; - p.table = table; - p.all_rows = true; - p.values_only = true; - return read_table(std::move(p)); - } - - // ----------------------------------------------------------------------- - // Pre-flight checks — unconditional, no dev escape hatch - // - // Verifies that the configured `underwriter_account` is set up to - // participate in the race BEFORE any cron job is scheduled. Failure - // prevents the scan loop from starting; the cluster bootstrap is - // responsible for establishing whatever state is missing. - // - // Gating checks (all required): - // 1. Operator exists in `sysio.opreg::operators`. ACTIVE is observed - // but non-gating because bootstrap may activate after node startup; - // the scan poll blocks work until then. - // 2. Every ACTIVE outpost chain in `sysio.chains::chains` has a - // configured `--underwriter-{eth,sol}-outpost` endpoint of the - // matching VM family. The served set is derived from the registry - // while the outpost clients are built from config, so a gap would - // let the scan loop pick a request it cannot fully commit. Inactive - // (not-yet-activated) chains are excluded, so registering a future - // chain does not block startup before its endpoint is configured. - // 3. `sysio.authex::links` covers every chain in the - // `sysio.chains::chains` registered set; without an authex link - // for a chain the underwriter cannot sign a commit on that chain. - // - // Collateral is also observed and logged here, but zero balance is not a - // startup failure: bootstrap may deposit after the node starts, and the - // scan loop admits each request using its exact route buckets. - // - // Returns true on success. On any gating failure logs a structured `elog` - // naming the specific missing item, and returns false. The caller - // (plugin_startup) treats false as "do not schedule the cron". - // - // Per `feedback_no_dev_escape_hatches.md`: NO `--strict=false` option, - // no dev fallback. Dev clusters that fail preflight are bootstrap bugs - // to fix in `wire-tools-ts/packages/test-cluster-tool`, not workarounds - // to ship in the plugin. - // ----------------------------------------------------------------------- - bool run_preflight() { - // A retrying preflight must never retain a provider from an earlier - // attempt whose chain-state authorization may no longer be current. - uic_signature_provider.reset(); - // -- Check 1: operator status -- - bool found_op = false; - bool active = false; - { - auto rows = read_all("sysio.opreg", "sysio.opreg", "operators"); - for (auto& row : rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["account"].as_string()) != underwriter_account) continue; - found_op = true; - auto status = obj["status"].as(); - active = (status == OperatorStatus::OPERATOR_STATUS_ACTIVE); - break; - } - } - if (!found_op) { - elog("underwriter preflight: account {} not registered in sysio.opreg::operators", - underwriter_account.to_string()); - return false; - } - if (!active) { - // Non-fatal: depot's collateral-deposit + meets_role_min activation - // can land AFTER the underwriter node is up (cluster bootstrap - // orders it that way — Phase 11d deposits collateral on the - // already-running underwriter node to flip uwrit.a → ACTIVE). - // The scan loop's `poll_own_status()` re-checks every cycle and - // skips work until ACTIVE, so we just log and let the cron job - // pick up the activation transition. - ilog("underwriter preflight: account {} not yet OPERATOR_STATUS_ACTIVE — " - "scan loop will start polling and wait for activation", - underwriter_account.to_string()); - } - - // Populate the outpost-chain cache (also used by the scan loop) so - // the link + balance coverage checks know what to look for. - read_outpost_registry(); - - // -- Check 2: an RPC client is registered for every active chain -- - // - // The served set is `outpost_chain_kinds` (ACTIVE non-depot chains only, - // per `read_outpost_registry`) as consumed by `select_coverable`. An - // active chain with no RPC client would let the scan loop SELECT a - // request for it and land one leg before discovering the other leg has - // nowhere to commit (SEC-13/WSA-027). Fail closed here so a - // misconfigured underwriter never starts committing partial swaps. - // - // The client is looked up under the chain's OWN code: an - // outpost-ethereum-client / outpost-solana-client entry is registered - // with the chain code as its client id, which makes the binding exact by - // construction rather than inferred, and makes "wrong VM family" simply a - // client that is not there. - for (const auto& [chain_code, ep] : outpost_endpoints) { - const auto code_str = fc::slug_name{chain_code}.to_string(); - const bool have_client = - ep.kind == ChainKind::CHAIN_KIND_EVM ? eth_plug->get_client(ep.client_id) != nullptr - : ep.kind == ChainKind::CHAIN_KIND_SVM ? sol_plug->get_client(ep.client_id) != nullptr - : false; - if (!have_client) { - elog("underwriter preflight: active outpost chain {} (kind={}) has no RPC client " - "registered under that chain code — add an outpost-ethereum-client / " - "outpost-solana-client entry whose client id is {}", - code_str, - std::string{sysio::opp::types::ChainKind_Name(ep.kind)}, - code_str); - return false; - } - } - - // -- Check 2b: the registry supplies every served chain's remote addresses -- - // - // The addresses are no longer per-node config: `sysio.chains` carries them - // so every underwriter commits against the same deployment. A row - // registered before its remote contracts existed leaves them empty, and - // committing against an empty address would mean signing to the zero - // address. Fail closed until governance runs `sysio.chains::setoutpost`. - for (const auto& [chain_code, ep] : outpost_endpoints) { - if (ep.commit_addr.empty() || ep.source_deposit_addr.empty()) { - elog("underwriter preflight: outpost chain {} carries no remote contract addresses " - "on its sysio.chains row — run sysio.chains::setoutpost for this chain", - fc::slug_name{chain_code}.to_string()); - return false; - } - } - - if (outpost_chain_kinds.empty()) { - elog("underwriter preflight: no outposts registered in sysio.chains::chains — " - "nothing to commit against"); - return false; - } - - // -- Check 3: authex link coverage per outpost chain -- - std::set linked_chains; - { - auto rows = read_all("sysio.authex", "sysio.authex", "links"); - for (auto& row : rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["username"].as_string()) != underwriter_account) continue; - linked_chains.insert(obj["chain_kind"].as()); - } - } - for (auto& [chain_code, chain_kind] : outpost_chain_kinds) { - if (!linked_chains.count(chain_kind)) { - elog("underwriter preflight: missing sysio.authex link for outpost {} " - "(chain_kind={}) — bootstrap must call sysio.authex::createlink for " - "this account on every outpost chain", - chain_code, - std::string{sysio::opp::types::ChainKind_Name(chain_kind)}); - return false; - } - } - - // -- Observation 4: RAW balance per outpost chain (non-gating) -- - // - // Reads `sysio.opreg::operators[underwriter].balances` directly and - // does NOT subtract active locks or pending withdraws. An underwriter - // with all collateral currently locked is still eligible to remain - // running — the moment their locks expire (via the chklocks sweep) - // they can underwrite the next round. Deducting locks here would - // false-fail a healthy underwriter who is in the middle of an active - // race. - std::map raw_balance_by_chain; - { - auto ops_rows = read_all("sysio.opreg", "sysio.opreg", "operators"); - for (auto& row : ops_rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["account"].as_string()) != underwriter_account) continue; - if (!obj.contains("balances") || !obj["balances"].is_array()) break; - for (auto& bal_entry : obj["balances"].get_array()) { - auto be = bal_entry.get_object(); - if (!be.contains("chain") || !be.contains("balance")) continue; - const int chain = magic_enum::enum_integer(be["chain"].as()); - const uint64_t balance = be["balance"].as_uint64(); - raw_balance_by_chain[chain] += balance; - } - break; - } - } - for (auto& [chain_code, chain_kind] : outpost_chain_kinds) { - const int ck = magic_enum::enum_integer(chain_kind); - auto it = raw_balance_by_chain.find(ck); - if (it == raw_balance_by_chain.end() || it->second == 0) { - // Non-fatal: collateral may be deposited AFTER plugin startup - // (cluster bootstrap orders it that way — Phase 11d deposits - // on the already-running underwriter node to flip uwrit.a → - // ACTIVE). Per-request cover selection re-checks the exact - // route buckets every cycle; the plugin naturally joins a race - // once the needed collateral lands, without making an unrelated - // zero-balance chain a global liveness gate. - ilog("underwriter preflight: zero raw balance on outpost {} " - "(chain_kind={}) — scan loop will poll for collateral " - "deposit and begin committing once available", - chain_code, - std::string{sysio::opp::types::ChainKind_Name(chain_kind)}); - } - } - - // -- Check 5: required CLI options + ABI/IDL resolution -- - // - // The verify_source_deposit path identifies the swap-deposit - // function (ETH) / instruction (SOL) by NAME. The contract - // address + function selector + instruction discriminator are - // resolved from the ABI / IDL files registered with the outpost - // client plugins (avoiding duplicate plugin options that would - // need to be kept in sync with `--ethereum-abi-file` / - // `--solana-idl-file`). - if (eth_source_deposit_function_name.empty()) { - elog("underwriter preflight: --underwriter-eth-source-deposit-function is required"); - return false; - } - if (sol_source_deposit_instruction_name.empty()) { - elog("underwriter preflight: --underwriter-sol-source-deposit-instruction is required"); - return false; - } - - // ETH: walk every loaded ABI for a `function` contract whose name - // matches. The match yields keccak256(signature) → the CHAIN-AGNOSTIC - // 4-byte selector (identical on every EVM chain running the same - // contract). The per-chain deployed contract ADDRESS comes from - // `outpost_endpoints` (SEC-13/WSA-027), so the ABI's `contract_address` - // field is no longer consulted here. - { - resolved_eth_source_deposit_selector.clear(); - bool found = false; - for (const auto& [path, contracts] : eth_plug->get_abi_files()) { - for (const auto& c : contracts) { - if (c.type != fc::network::ethereum::abi::invoke_target_type::function) continue; - if (c.name != eth_source_deposit_function_name) continue; - const auto sel_hash = fc::network::ethereum::abi::to_contract_function_selector(c); - const uint8_t* sp = sel_hash.data(); - resolved_eth_source_deposit_selector.assign(sp, sp + 4); - dlog("underwriter preflight: ETH deposit selector for '{}' resolved from ABI '{}'", - eth_source_deposit_function_name, path.string()); - found = true; - break; - } - if (found) break; - } - if (!found) { - elog("underwriter preflight: no ETH ABI entry found for function '{}'; " - "pass --ethereum-abi-file pointing at the ABI that declares it", - eth_source_deposit_function_name); - return false; - } - } - - // SOL: walk every loaded IDL for the named instruction. The IDL parser - // populates each instruction's 8-byte anchor discriminator - // (`sha256("global:")[0..8]`) — the CHAIN-AGNOSTIC - // identifier the verify path matches. The per-chain program id comes from - // `outpost_endpoints` (SEC-13/WSA-027), not the IDL's `address`. - { - resolved_sol_source_deposit_discriminator.clear(); - bool found = false; - for (const auto& [path, programs] : sol_plug->get_idl_files()) { - for (const auto& p : programs) { - if (const auto* ix = p.find_instruction(sol_source_deposit_instruction_name); ix) { - resolved_sol_source_deposit_discriminator.assign( - ix->discriminator.begin(), ix->discriminator.end()); - dlog("underwriter preflight: SOL deposit discriminator for '{}' resolved from IDL '{}'", - sol_source_deposit_instruction_name, path.string()); - found = true; - break; - } - } - if (found) break; - } - if (!found) { - elog("underwriter preflight: no SOL IDL instruction found named '{}'; " - "pass --solana-idl-file pointing at the IDL that declares it", - sol_source_deposit_instruction_name); - return false; - } - } - - // ── Check 5: signature providers — 3-provider minimum ──────────── - // - // The underwriter signs UIC digests with a fixed-size recoverable WIRE - // key (K1, R1, EM, or ED), and submits - // commit transactions on each active outpost (ETH + SOL) — so - // three sig-provider entries are required at startup: - // • exactly one supported provider targeting chain=wire — for UIC - // signing. K1/R1 use key-type=wire, EM uses key-type=ethereum, and - // ED uses key-type=solana. More than one supported provider would - // silently arbitrate at `.front()`; we refuse the ambiguity. - // • at least one (chain=ethereum, key-type=ethereum) — for - // paying gas on ETH outpost commits. - // • at least one (chain=solana, key-type=solana) — for SOL - // outpost commits. - auto& sig_plug = app().get_plugin(); - auto wire_providers = underwriter_detail::select_uic_signature_providers( - sig_plug.query_providers(std::nullopt, fc::crypto::chain_kind_wire), - [&](const std::string& key_name) { - return sig_plug.is_operator_configured_provider(key_name); - }, - [&](const fc::crypto::public_key& key) { - return chain_plug->provider_can_authorize_active_alone( - underwriter_account, key); - }); - if (wire_providers.size() != 1) { - elog("underwriter preflight: expected exactly 1 operator-configured WIRE UIC " - "signature provider whose key alone reaches the underwriter " - "account's current owner or active threshold, got {} — configure one " - "supported chain=wire provider (K1/R1 key-type=wire, EM " - "key-type=ethereum, or ED key-type=solana)", wire_providers.size()); - return false; - } - auto eth_providers = sig_plug.query_providers( - std::nullopt, fc::crypto::chain_kind_ethereum, fc::crypto::chain_key_type_ethereum); - if (eth_providers.empty()) { - elog("underwriter preflight: at least 1 Ethereum signature provider is required " - "(chain=ethereum, key-type=ethereum) for paying gas on ETH outpost commits"); - return false; - } - auto sol_providers = sig_plug.query_providers( - std::nullopt, fc::crypto::chain_kind_solana, fc::crypto::chain_key_type_solana); - if (sol_providers.empty()) { - elog("underwriter preflight: at least 1 Solana signature provider is required " - "(chain=solana, key-type=solana) for SOL outpost commits"); - return false; - } - - // ── Check 6: signature self-test ───────────────────────────────── - // - // Sign a fixed test digest with the configured provider, recover - // the pubkey, and confirm its weight alone reaches the underwriter - // account's `owner` or `active` threshold. Catches "wrong key configured" - // at startup instead of after a live uwreq is silently rejected - // by the depot's verify_uic_signature. - try { - const fc::sha256 self_test_digest = fc::sha256::hash( - std::string{underwriter_detail::uic_signature_self_test_domain}); - const auto provider_check = underwriter_detail::check_uic_signature_provider( - *wire_providers.front(), self_test_digest); - switch (provider_check.result) { - case underwriter_detail::uic_signature_provider_result::compatible: - break; - case underwriter_detail::uic_signature_provider_result:: - unsupported_public_key_type: - elog("underwriter preflight: the WIRE UIC signature provider must use a " - "fixed-size recoverable K1, R1, EM, or ED public key; configured key " - "{} is unsupported.", - wire_providers.front()->public_key.to_string(fc::yield_function_t{})); - return false; - case underwriter_detail::uic_signature_provider_result:: - unsupported_signature_type: - elog("underwriter preflight: the WIRE UIC signature provider returned a " - "WebAuthn or BLS signature during its self-test. The depot accepts " - "only fixed-size recoverable K1, R1, EM, or ED UIC signatures."); - return false; - case underwriter_detail::uic_signature_provider_result::signature_type_mismatch: - elog("underwriter preflight: the WIRE UIC signature provider's public-key " - "and self-test signature variants do not match."); - return false; - case underwriter_detail::uic_signature_provider_result:: - non_canonical_packed_signature: - elog("underwriter preflight: the WIRE UIC signature provider returned a " - "non-canonical packed fixed-size signature during its self-test."); - return false; - case underwriter_detail::uic_signature_provider_result:: - non_canonical_signature: - elog("underwriter preflight: the WIRE UIC signature provider returned " - "a noncanonical ECDSA recovery/scalar body during its self-test."); - return false; - } - if (!provider_check.recovered_key) { - elog("underwriter preflight: the WIRE UIC signature provider self-test " - "did not recover a public key."); - return false; - } - const auto& recovered = *provider_check.recovered_key; - - if (recovered != wire_providers.front()->public_key || - !chain_plug->provider_can_authorize_active_alone( - underwriter_account, recovered)) { - elog("underwriter preflight: signature self-test failed — the configured " - "WIRE UIC signature provider's recovered pubkey ({}) does not equal " - "its configured key or its weight no longer reaches the underwriter " - "account's owner or active threshold. The depot will reject every " - "commit signed by this provider.", - recovered.to_string(fc::yield_function_t{})); - return false; - } - } catch (const fc::exception& e) { - elog("underwriter preflight: signature self-test threw: {}", e.to_detail_string()); - return false; - } - - uic_signature_provider = wire_providers.front(); - - ilog("underwriter preflight: all checks passed (account={} outposts={})", - underwriter_account.to_string(), - outpost_chain_kinds.size()); - return true; - } - - // ----------------------------------------------------------------------- - // Sync-gated startup - // ----------------------------------------------------------------------- - - /// Arm the deferred startup: runs AT MOST once (re-entry is guarded by - /// {@link startup_attempted}), on the main thread from the sync-gate channel - /// callback once `controller::is_synced()` holds. The body itself lives - /// in {@link attempt_deferred_startup}, which may retry the preflight - /// within a bounded grace and shuts the node down on terminal failure - /// (fail-fast). - void run_deferred_startup() { - startup_armed_at = fc::time_point::now(); - // Release the gate subscription; the callback already unsubscribed - // before calling here, so this is an idempotent cleanup, not the - // load-bearing disconnect. - sync_gate_subscription.unsubscribe(); - attempt_deferred_startup(); - } - - /// One attempt of the deferred startup body: preflight → outpost client - /// wiring → cron scheduling. A failing preflight within - /// `underwriter_defaults::preflight_retry_grace_ms` of the gate arming is - /// NOT terminal — rows the harness confirmed final on the producer can - /// land in the LOCAL irreversible state a beat after the gate arms - /// (observed live: `regoperator` in block 405 readable 50ms after a - /// preflight read at LIB 402), so the attempt re-schedules itself on - /// `preflight_retry_interval_ms` until the grace expires. Past the grace, - /// a preflight failure on a synced chain is a terminal bootstrap bug — - /// stored as a diagnosable gate state, logged, and answered with a - /// fail-fast node shutdown (see {@link quit_if_startup_failed_terminally}). - /// - /// Escapes are contained FIRST (unwinding out of a posted channel delivery - /// would skip the diagnosable gate-state store and the shutdown log) and - /// then routed through the same fail-fast shutdown. The expected - /// preflight/wiring failures inside the body set their own more specific - /// states; the containment catches what they don't (a throwing table - /// decode in the preflight or registry read, a non-fc exception from - /// client wiring, a cron add_job failure). FC_LOG_AND_DROP is the tree's - /// containment idiom (see scan_cycle below) and deliberately rethrows - /// boost::interprocess::bad_alloc — chainbase shared-memory exhaustion - /// stays immediately fatal. - void attempt_deferred_startup() { - if (shutting_down) { - return; - } - bool completed = false; - try { - attempt_deferred_startup_body(); - completed = true; - } FC_LOG_AND_DROP("underwriter_plugin: deferred startup failed unexpectedly:"); - if (!completed) { - gate_state = underwriter_detail::startup_state::startup_failed; - } - // Tripwire for the {@link startup_attempted} derivation: every attempt - // that returns normally must have left `waiting_for_sync` — a future - // early return in the body before its first gate_state store would - // otherwise strand the node in undiagnosable limbo (gate subscription - // already released, nothing left to re-arm). - assert(startup_attempted()); - quit_if_startup_failed_terminally(); - } - - /// The uniform fail-fast policy: a terminal deferred-startup failure shuts - /// the node down instead of leaving a quiet, non-underwriting node behind a - /// running process — an operator daemon that is not performing its duties - /// must be supervisor-visible (it has liveness/slashing consequences), not - /// hidden behind one log line. The specific failure was already stored as a - /// gate state and logged by the attempt; this holds the shutdown decision in - /// ONE place so the policy cannot diverge between the first attempt and the - /// bounded preflight retries. - void quit_if_startup_failed_terminally() { - const auto state = gate_state.load(); - if (!underwriter_detail::is_terminal_failure(state)) { - return; - } - elog("underwriter_plugin: deferred startup failed terminally (state={}) — " - "shutting down node (fail-fast)", magic_enum::enum_name(state)); - app().quit(); - } - - /// The deferred startup body proper — see {@link attempt_deferred_startup} - /// for the retry semantics and the exception containment around it. - void attempt_deferred_startup_body() { - // Pre-flight: bail (no cron job) if the depot-side state for this - // underwriter is incomplete. Cluster bootstrap is responsible for - // establishing the missing state — there is no dev escape hatch. - // The already-registered HTTP endpoints keep reporting the gate state - // so a not-yet-live underwriter is diagnosable over HTTP. - if (!run_preflight()) { - const auto since_armed = fc::time_point::now() - startup_armed_at; - if (since_armed < - fc::milliseconds(underwriter_defaults::preflight_retry_grace_ms)) { - gate_state = underwriter_detail::startup_state::preflight_retrying; - ilog("underwriter_plugin: preflight incomplete {}ms after the sync gate " - "armed — retrying in {}ms (grace {}ms); the error above is " - "transient until the grace expires", - since_armed.count() / 1000, - underwriter_defaults::preflight_retry_interval_ms, - underwriter_defaults::preflight_retry_grace_ms); - schedule_preflight_retry(); - return; - } - gate_state = underwriter_detail::startup_state::preflight_failed; - elog("underwriter_plugin: pre-flight failed — cron job NOT registered"); - return; - } - - // The preflight above already established that every active chain has both - // an RPC client and contract addresses; wire_outpost_clients documents the - // handles it builds. - read_outpost_registry(); - try { - wire_outpost_clients(); - if (outpost_by_chain.empty()) { - // Preflight passed, so every ACTIVE chain had a client and addresses - // — reaching here means the registry holds no active non-depot chain - // at all, which the preflight reports separately. Nothing to serve yet. - wlog("underwriter_plugin: NO outpost_clients wired — sysio.chains lists no active " - "non-depot chain"); - } - } catch (const fc::exception& e) { - gate_state = underwriter_detail::startup_state::wiring_failed; - elog("underwriter_plugin: failed to build outpost_client(s): {}", - e.to_detail_string()); - return; - } - - cron_service::job_schedule sched; - sched.milliseconds = {cron_service::job_schedule::step_value{scan_interval_ms}}; - - cron_service::job_metadata_t meta; - meta.label = "underwriter_scan"; - meta.one_at_a_time = true; - - scan_job_id = cron_plug->add_job( - sched, - [this]() { scan_cycle(); }, - meta - ); - // Stored immediately after the job exists — anything thrown between a - // successful add_job and this store would otherwise leave the scan cron - // running while the gate reports a terminal failure. The - // `/v1/underwriter/*` endpoints (registered back in `plugin_startup`) - // switch from gate-state reporting to the real payloads here. - gate_state = underwriter_detail::startup_state::active; - - ilog("underwriter_plugin: scheduled scan (id={}, interval={}ms)", - scan_job_id, scan_interval_ms); - } - - /// Re-run {@link attempt_deferred_startup} after one retry interval, on - /// the main thread (appbase-wrapped wait on the app io_context). - void schedule_preflight_retry() { - if (!preflight_retry_timer) { - preflight_retry_timer.emplace(app().get_io_context()); - } - preflight_retry_timer->expires_after( - std::chrono::milliseconds(underwriter_defaults::preflight_retry_interval_ms)); - // Plain wait on the main io_context, then POST the attempt to the app - // queue — the same pattern the sync-gate callback uses, so the body - // runs in the same main-thread context plugin_startup does. - preflight_retry_timer->async_wait([this](const boost::system::error_code& ec) { - if (ec || shutting_down) { - return; - } - app().executor().post(appbase::priority::medium, [this]() { - // Proceed only while the retry grace is the live state: a - // terminal state stored between the timer firing and this task - // running must stay terminal. Structural guard — today no such - // interleaving exists, but the terminal-stays-terminal invariant - // should not rest on statement order in schedule_preflight_retry. - if (shutting_down || - gate_state.load() != underwriter_detail::startup_state::preflight_retrying) { - return; - } - attempt_deferred_startup(); - }); - }); - } - - // ----------------------------------------------------------------------- - // Main scan cycle - // ----------------------------------------------------------------------- - - void scan_cycle() { - if (shutting_down || !enabled) return; - try { - do_scan_cycle(); - } FC_LOG_AND_DROP(); - } - - void do_scan_cycle() { - // Step 0: refresh own status. SLASHED / TERMINATED operators must NOT - // call commit() on outposts — the depot rejects (or simply doesn't - // select them as winner), but the wasted JSON-RPC tx + on-chain - // attestation is observable noise. Halting locally is cleaner. - poll_own_status(); - if (!is_active) return; - - // Step 1: Read outpost registry for chain_kind mappings, then re-wire any - // outpost whose remote deployment moved since the handle was built. - read_outpost_registry(); - try { - wire_outpost_clients(); - } catch (const fc::exception& e) { - // A rebuild that fails leaves the previous handle in place; the next - // tick retries. Losing the whole scan cycle over one chain's RPC - // hiccup would stall commits on every OTHER chain too. - wlog("underwriter_plugin: outpost re-wire failed, keeping existing clients: {}", - e.to_detail_string()); - } - - // Step 2: Read our credit lines from sysio.opreg::operators - read_credit_lines(); - - // Step 3: Scan sysio.uwrit::uwreqs for PENDING requests. Admission is - // request-specific: select_coverable checks the exact collateral - // buckets for each route, so an unrelated active chain with no balance - // must not block otherwise coverable work. - auto requests = scan_pending_requests(); - - // Step 3b: prune local per-uwreq state whose uwreq is no longer - // PENDING — the depot has resolved (won/lost/expired) those races, so - // the local sets should not grow unbounded. This is the same pass that - // already reads the PENDING set, so it's free. - { - std::unordered_set still_pending; - still_pending.reserve(requests.size()); - for (auto& r : requests) still_pending.insert(r.id); - std::lock_guard lk{stats_mutex}; - std::erase_if(confirmed_commits, [&](const commit_key& k) { - return !still_pending.contains(k.uwreq_id); - }); - underwriter::prune_solana_source_deposit_scan_cursors( - sol_source_deposit_scan_cursors, still_pending); - uwreqs_seen_pending_count = requests.size(); - } - - if (requests.empty()) return; - - ilog("underwriter: found {} pending underwrite requests", requests.size()); - - // A durable terminal candidate must not consume fresh-cover selection. - // A complete stored candidate is reserved below while it awaits its - // explicit external replay trigger. - const auto skipped_candidates = std::erase_if( - requests, [](const uw_request& r) { - return r.own_stored_commit_plan.skip_candidate; - }); - if (skipped_candidates != 0) { - ilog("underwriter: skipped {} terminal depot candidates before cover selection", - skipped_candidates); - } - if (requests.empty()) return; - - // Seed provisional capacity from the depot's current available balance. - // Candidates whose paid legs are all stored or locally confirmed have no - // submission work, but their eventual locks do not exist yet. Reserve - // their full winner-time requirements before selecting new work so OPP - // relay latency cannot make the daemon knowingly overcommit collateral. - credit_buckets remaining_credit; - for (const auto& cl : credit_lines) { - remaining_credit[bucket_key{cl.chain_code, cl.token_code}] = cl.balance; - } - size_t outstanding_no_work = 0; - std::erase_if(requests, [&](const uw_request& r) { - const bool source_confirmed = confirmed_commits.contains( - commit_key{r.id, r.src_chain_code.value, r.src_token_code.value, - r.src_reserve_code.value}); - const bool destination_confirmed = confirmed_commits.contains( - commit_key{r.id, r.dst_chain_code.value, r.dst_token_code.value, - r.dst_reserve_code.value}); - if (underwriter_detail::has_submission_work( - r.own_stored_commit_plan, source_confirmed, - destination_confirmed)) { - return false; - } - underwriter_detail::reserve_buckets( - remaining_credit, src_bond(r), dst_bond(r)); - ++outstanding_no_work; - return true; - }); - - if (outstanding_no_work != 0) { - ilog("underwriter: reserved collateral for {} locally complete " - "candidate(s) awaiting depot observation", outstanding_no_work); - } - - if (requests.empty()) { - ilog("underwriter: pending uwreqs have no remaining outpost " - "submission work"); - return; - } - - // Step 4: Select requests whose complete winner-time collateral - // requirements can be covered, then submit only their missing legs. - auto selected = select_coverable(requests, std::move(remaining_credit)); - if (selected.empty()) { - ilog("underwriter: no requests coverable with current credit lines"); - return; - } - - ilog("underwriter: selected {} requests for underwriting", selected.size()); - - // Step 5: Submit intent for each selected request - for (auto& req : selected) { - submit_intent_to_outpost(req); - } - } - - // ----------------------------------------------------------------------- - // Read outpost registry - // ----------------------------------------------------------------------- - - void read_outpost_registry() { - outpost_chain_kinds.clear(); - outpost_external_chain_ids.clear(); - outpost_endpoints.clear(); - // refactor: chain rows moved from `sysio.epoch::outposts` to - // `sysio.chains::chains`. Each row is a `Chain` with fields: - // `code` — slug_name (the universal chain identifier; the - // v5 `outpost_id` was just this slug's uint64). - // `kind` — ChainKind enum. - // `external_chain_id` — the chain's numeric id (1 = ETH mainnet, …); - // fed to `create_outpost_client` (SEC-13/WSA-027). - // `is_depot` — true for the WIRE depot's own row; we filter - // it out since underwriters don't commit to the - // depot itself. - // `active` - false until `sysio.chains::activchain` runs; - // post-bootstrap `regchain` rows start inactive, - // so they are not yet live outposts and are skipped. - auto rows = read_all("sysio.chains", "sysio.chains", "chains"); - depot_chain_code.reset(); - for (auto& row : rows.rows) { - auto obj = row.get_object(); - // `code` is a `slug_name`. Read it through fc::slug_name's own - // from_variant, which takes the decoded slug string ("" for the zero - // sentinel) plus the transitional `{"value": }` object. - uint64_t chain_code = obj["code"].as().value; - if (obj.contains("is_depot") && obj["is_depot"].as_bool()) { - // Record the depot's own code for exact per-leg depot - // detection (to/from-WIRE swaps), then skip caching it as an - // outpost — underwriters never commit to the depot itself. - depot_chain_code = chain_code; - continue; - } - // Skip chains not yet activated. `regchain` inserts post-bootstrap - // rows with `active=false` until `activchain` flips them; treating an - // inactive (future) chain as a live outpost would make the endpoint- - // coverage preflight and per-request cover selection demand config - // for a chain that is not serving yet, blocking startup or halting - // the already-active chains. Mirrors batch_operator_plugin's - // `is_depot || !active` outpost filter. - if (!obj.contains("active") || !obj["active"].as_bool()) - continue; - // FC_REFLECT_ENUM in sysio/opp/opp.hpp gives us a direct enum - // round-trip — the variant carries the symbolic name and `.as()` - // recovers the typed value without a string switch. - outpost_chain_kinds[chain_code] = obj["kind"].as(); - outpost_external_chain_ids[chain_code] = - static_cast(obj["external_chain_id"].as_uint64()); - build_endpoint_from_row(chain_code, obj); - } - } - - /// Build one `outpost_client` SPI handle per CONFIGURED chain (SEC-13/WSA-027: - /// keyed by EXACT chain_code, so two chains of the same VM family each get - /// their own client + RPC). The underwriter never sees raw `ethereum_client` - /// / `solana_client` instances after this point — every outpost-side action - /// goes through the SPI virtuals. Per `outpost-client-spi.md`: - /// * ETH client carries only the OperatorRegistry address (the uw_commit - /// target); the underwriter neither consumes nor emits OPP envelopes, so - /// OPP / OPPInbound addresses are left empty. - /// * SOL client carries the opp-outpost program id; the typed wrapper - /// exposes `commit_underwrite` directly. - /// `external_chain_id` and the contract addresses both come from the chain's - /// ACTIVE `sysio.chains` row, read by {@link read_outpost_registry}. - /// - /// Idempotent, and re-run every scan tick: a handle is rebuilt only when the - /// registry now names a DIFFERENT remote address than the one it was built - /// with. Governance can move a deployment under a running underwriter with - /// `sysio.chains::setoutpost`, and without this the daemon would keep - /// committing to the address the outpost has moved off. - void wire_outpost_clients() { - for (const auto& [chain_code, ep] : outpost_endpoints) { - const auto code_str = fc::slug_name{chain_code}.to_string(); - // Skip chains with no active registry row: the preflight already - // failed the startup path on those, and on a rescan a chain can be - // deactivated without invalidating the clients still in use. - auto ext = outpost_external_chain_ids.find(chain_code); - if (ext == outpost_external_chain_ids.end()) continue; - if (ep.commit_addr.empty()) { - // A row can LOSE an address: `setoutpost` replaces the whole set, so - // clearing a role retires that deployment. Drop any handle built - // against it — keeping one would let a later destination leg commit - // to a deployment the registry no longer names, which is worse than - // not committing at all. - if (outpost_by_chain.erase(chain_code) > 0) { - wlog("underwriter_plugin: outpost chain {} no longer carries a remote contract " - "address — retired the wired client; not committing on this chain until " - "sysio.chains::setoutpost supplies one", - code_str); - } else { - wlog("underwriter_plugin: outpost chain {} has no remote contract address on its " - "sysio.chains row — not wiring until sysio.chains::setoutpost supplies one", - code_str); - } - wired_commit_addrs.erase(chain_code); - continue; - } - if (auto wired = wired_commit_addrs.find(chain_code); - wired != wired_commit_addrs.end() && wired->second == ep.commit_addr - && outpost_by_chain.contains(chain_code)) { - continue; // already wired against this exact deployment - } - const bool rebuilt = outpost_by_chain.contains(chain_code); - if (ep.kind == ChainKind::CHAIN_KIND_EVM) { - outpost_by_chain[chain_code] = - eth_plug->create_outpost_client(ep.client_id, chain_code, ext->second, - /*opp_addr=*/"", /*opp_inbound_addr=*/"", - ep.commit_addr); - ilog("underwriter_plugin: {} ETH outpost_client chain={} (client_id='{}', opreg={})", - rebuilt ? "rewired" : "wired", code_str, ep.client_id, ep.commit_addr); - } else if (ep.kind == ChainKind::CHAIN_KIND_SVM) { - outpost_by_chain[chain_code] = - sol_plug->create_outpost_client(ep.client_id, chain_code, ext->second, - ep.commit_addr /*program_id*/, - solana_outpost_role::underwriter); - ilog("underwriter_plugin: {} SOL outpost_client chain={} (client_id='{}', program={})", - rebuilt ? "rewired" : "wired", code_str, ep.client_id, ep.commit_addr); - } else { - wlog("underwriter_plugin: outpost_endpoint chain={} has unknown kind — skipped", - code_str); - continue; - } - wired_commit_addrs[chain_code] = ep.commit_addr; - } - } - - /// Build this chain's endpoint entirely from its `chains` row. - /// - /// Nothing about an outpost is per-node any more: the registry names WHICH - /// chains are served (every active non-depot row) and WHERE each one lives, - /// and the RPC client is the one registered under the chain's own code. That - /// leaves the operator responsible only for supplying the endpoints - /// themselves, and makes it impossible for one underwriter to commit against - /// a deployment the rest of the network disagrees on. - void build_endpoint_from_row(uint64_t chain_code, const fc::variant_object& obj) { - outpost_endpoint ep; - ep.kind = outpost_chain_kinds.at(chain_code); - // The chain code IS the RPC client id — see outpost-ethereum-client / - // outpost-solana-client. The preflight fails closed when no client is - // registered under it. - ep.client_id = fc::slug_name{chain_code}.to_string(); - - std::string opp_addr, operator_registry_addr, source_deposit_addr; - if (auto out_it = obj.find(depot_chains::field::outpost); - out_it != obj.end() && out_it->value().is_object()) { - const auto& out = out_it->value().get_object(); - const auto read = [&](const char* f) -> std::string { - auto it = out.find(f); - return it == out.end() ? std::string{} : it->value().as_string(); - }; - opp_addr = read(depot_chains::field::outpost_addr::opp_addr); - operator_registry_addr = read(depot_chains::field::outpost_addr::operator_registry_addr); - source_deposit_addr = read(depot_chains::field::outpost_addr::source_deposit_addr); - } - // An SVM outpost is one program serving every role; an EVM outpost names a - // distinct contract per role and must NEVER fall back to opp_addr, or a - // row whose OperatorRegistry is not deployed yet would commit to the OPP - // contract instead of failing closed. - const bool single_program = ep.kind == ChainKind::CHAIN_KIND_SVM; - ep.commit_addr = - depot_chains::resolve_role_addr(operator_registry_addr, opp_addr, single_program); - ep.source_deposit_addr = - depot_chains::resolve_role_addr(source_deposit_addr, opp_addr, single_program); - outpost_endpoints[chain_code] = std::move(ep); - } - - /// True iff `code` is the WIRE depot's own chain code. Exact compare - /// against the registry's `is_depot` row — never inferred from - /// CHAIN_KIND_UNKNOWN (which also matches unregistered chains). - bool is_depot_leg(fc::slug_name code) const { - return depot_chain_code && code.value == *depot_chain_code; - } - - // ----------------------------------------------------------------------- - // Read credit lines from sysio.opreg::operators - // ----------------------------------------------------------------------- - - void read_credit_lines() { - credit_lines.clear(); - - // schema: balance / lock / withdraw rows carry `chain_code` - // and `token_code` slug_names (read via fc::slug_name's from_variant) - // — not the v5 `chain` (ChainKind enum) / `token_kind` (TokenKind - // enum). Translation: - // chain_code → ChainKind via `outpost_chain_kinds` map - // (populated by `read_outpost_registry`). - // token_code → TokenKind via `sysio.tokens::tokens.kind` - // (lazy-loaded into `token_kind_by_code`). - // Reads happen in this order so the registry caches are warm when - // the balance loop runs. - - // Refresh token-code → TokenKind cache once per scan (tokens - // table is small — 5 rows in the test cluster). Stored as a - // member so `scan_pending_requests` can reuse the same map when - // translating uwreq slug codes to TokenKind for bucket matching. - token_kind_by_code.clear(); - { - auto tk_rows = read_all("sysio.tokens", "sysio.tokens", "tokens"); - for (auto& row : tk_rows.rows) { - auto obj = row.get_object(); - uint64_t code = obj["code"].as().value; - token_kind_by_code[code] = obj["kind"].as(); - } - } - - // Local helper: read `chain_code`/`token_code` slug fields (shape - // `{"value": }`) as their EXACT packed slug values. Returns nullopt - // (row skipped) when the chain isn't a registered non-depot outpost or - // the token is unknown — neither can back a leg. SEC-13/WSA-027: key by - // exact code, never collapse to ChainKind/TokenKind. - auto read_slug_pair = [&](const fc::variant_object& obj) - -> std::optional> { - if (!obj.contains("chain_code") || !obj.contains("token_code")) { - return std::nullopt; - } - uint64_t chain_code = obj["chain_code"].as().value; - uint64_t token_code = obj["token_code"].as().value; - if (!outpost_chain_kinds.contains(chain_code) - || !token_kind_by_code.contains(token_code)) { - return std::nullopt; - } - return std::make_pair(chain_code, token_code); - }; - - // ── Step 1: raw balances from sysio.opreg::operators[underwriter] ── - auto ops_rows = read_all("sysio.opreg", "sysio.opreg", "operators"); - for (auto& row : ops_rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["account"].as_string()) != underwriter_account) continue; - if (!obj.contains("balances") || !obj["balances"].is_array()) break; - for (auto& bal_entry : obj["balances"].get_array()) { - auto be = bal_entry.get_object(); - auto codes = read_slug_pair(be); - if (!codes) continue; - credit_lines.push_back(credit_line{ - .chain_code = codes->first, - .token_code = codes->second, - .balance = be["balance"].as_uint64(), - }); - } - break; - } - - // ── Step 2: subtract active locks (sysio.uwrit::locks) ───────────── - // Locks that exceed the raw balance clamp to 0 — same convention as - // the depot's `available()`. - auto lock_rows = read_all(uwrit::account, uwrit::account, uwrit::table_locks); - for (auto& row : lock_rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj[uwrit::lock_field::underwriter].as_string()) != underwriter_account) continue; - auto codes = read_slug_pair(obj); - if (!codes) continue; - const uint64_t amount = obj[uwrit::lock_field::amount].as_uint64(); - for (auto& cl : credit_lines) { - if (cl.chain_code == codes->first && cl.token_code == codes->second) { - cl.balance = (cl.balance > amount) ? (cl.balance - amount) : 0; - break; - } - } - } - - // ── Step 3: subtract pending withdraws (sysio.opreg::wtdwqueue) ──── - auto wq_rows = read_all("sysio.opreg", "sysio.opreg", "wtdwqueue"); - for (auto& row : wq_rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["account"].as_string()) != underwriter_account) continue; - auto codes = read_slug_pair(obj); - if (!codes) continue; - const uint64_t amount = obj["amount"].as_uint64(); - for (auto& cl : credit_lines) { - if (cl.chain_code == codes->first && cl.token_code == codes->second) { - cl.balance = (cl.balance > amount) ? (cl.balance - amount) : 0; - break; - } - } - } - - for (auto& cl : credit_lines) { - ilog("underwriter: credit line chain_code={} token_code={} available={}", - fc::slug_name{cl.chain_code}.to_string(), - fc::slug_name{cl.token_code}.to_string(), - cl.balance); - } - } - - /** - * Refresh `is_active` from `sysio.opreg::operators[underwriter_account].status`. - * Mirror of the awareness poll on batch_operator_plugin — both share - * the `sysio::opp::depot::opreg_status::compute_is_active` helper so the - * status spellings + decision table live in one place. Logs once per - * transition. - */ - void poll_own_status() { - auto rows = read_all("sysio.opreg", "sysio.opreg", "operators"); - bool was_active = is_active; - for (auto& row : rows.rows) { - auto obj = row.get_object(); - if (chain::name(obj["account"].as_string()) != underwriter_account) continue; - is_active = sysio::opp::depot::opreg_status::compute_is_active( - obj["status"].as_string(), was_active); - break; - } - if (was_active && !is_active) { - elog("underwriter: own status flipped to SLASHED / TERMINATED — halting relay loop"); - } else if (!was_active && is_active) { - ilog("underwriter: own status flipped to ACTIVE — resuming relay loop"); - } - } - - // ----------------------------------------------------------------------- - // Scan sysio.uwrit::uwreqs for PENDING requests - // ----------------------------------------------------------------------- - - std::vector scan_pending_requests() { - std::vector requests; - - // `sysio.uwrit::uwreqs` is now a KV table. The legacy - // multi_index-style `{"bystatus": }` lower_bound format - // doesn't traverse the KV secondary index — it returns 0 rows. - // Until a KV-index query path lands here, scan by primary - // key and filter PENDING in C++. uwreqs is small (one row per - // in-flight swap; race-resolved rows transition to other - // statuses within an epoch), so this is cheap. - auto rows = read_all(uwrit::account, uwrit::account, uwrit::table_requests); - for (auto& row : rows.rows) { - auto obj = row.get_object(); - - // Filter to PENDING only. FC-reflected decoding accepts the ABI - // spelling or numeric representation and rejects undeclared values. - const auto request_status = - underwriter_detail::decode_enum_field( - obj, uwrit::request_field::status); - if (!request_status || *request_status != - UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING) continue; - - // Skip if already assigned to another underwriter - auto uw_name = obj.contains(uwrit::request_field::underwriter_name) - ? obj[uwrit::request_field::underwriter_name].as_string() - : std::string{}; - if (!uw_name.empty() && chain::name(uw_name) != underwriter_account && - chain::name(uw_name) != chain::name()) { - continue; - } - - uw_request req; - req.id = obj[uwrit::request_field::id].as_uint64(); - req.status = *request_status; - req.uw_name = uw_name; - - const auto attestation_type = - underwriter_detail::decode_enum_field( - obj, uwrit::request_field::type); - if (!attestation_type || *attestation_type != - AttestationType::ATTESTATION_TYPE_SWAP_REQUEST) continue; - req.attestation_type = *attestation_type; - - // Data-model schema: src/dst identity lives on the uwreq row as - // `(chain_code, token_code, reserve_code)` slug_name triples plus a - // `*_amount`. Populated by `sysio.uwrit::createuwreq` from the - // originating SwapRequest. Each is read through fc::slug_name's own - // from_variant, so the carrier is decoded in exactly one place. - if (!obj.contains(uwrit::request_field::source_chain_code) || - !obj.contains(uwrit::request_field::source_amount) || - !obj.contains(uwrit::request_field::destination_chain_code) || - !obj.contains(uwrit::request_field::destination_amount) || - !obj.contains(uwrit::request_field::target_amount)) { - // Row not yet populated (createuwreq writes them inline so this - // should be unreachable for SWAP-derived UWREQs). Skip safely. - continue; - } - auto read_codename = [&](const char* key) -> fc::slug_name { - return obj[key].as(); - }; - req.src_chain_code = read_codename( - uwrit::request_field::source_chain_code); - req.src_token_code = read_codename( - uwrit::request_field::source_token_code); - req.src_reserve_code = read_codename( - uwrit::request_field::source_reserve_code); - req.src_amount = obj[uwrit::request_field::source_amount].as_uint64(); - req.dst_chain_code = read_codename( - uwrit::request_field::destination_chain_code); - req.dst_token_code = read_codename( - uwrit::request_field::destination_token_code); - req.dst_reserve_code = read_codename( - uwrit::request_field::destination_reserve_code); - req.dst_amount = obj[ - uwrit::request_field::destination_amount].as_uint64(); - req.target_amount = obj[ - uwrit::request_field::target_amount].as_uint64(); - req.variance_tolerance_bps = obj.contains( - uwrit::request_field::variance_tolerance_bps) - ? static_cast(obj[ - uwrit::request_field::variance_tolerance_bps].as_uint64()) - : 0u; - // Cross-walk the slug_name codes to ChainKind/TokenKind enums for - // outpost dispatch and diagnostics. Maps populated by the upstream - // `read_outpost_registry` (`sysio.chains::chains` → ChainKind) - // and `read_credit_lines` (`sysio.tokens::tokens` → TokenKind) - // calls in `do_scan_cycle`. Any uncovered slug — e.g. the - // depot's WIRE chain (filtered out of outpost_chain_kinds via - // is_depot) — falls through as UNKNOWN; depot legs are identified - // independently below and require neither an outpost nor collateral. - auto resolve_chain_kind = [&](fc::slug_name code) -> ChainKind { - auto it = outpost_chain_kinds.find(code.value); - return it != outpost_chain_kinds.end() - ? it->second : ChainKind::CHAIN_KIND_UNKNOWN; - }; - auto resolve_token_kind = [&](fc::slug_name code) -> TokenKind { - auto it = token_kind_by_code.find(code.value); - return it != token_kind_by_code.end() - ? it->second : TokenKind::TOKEN_KIND_UNKNOWN; - }; - req.src_chain = resolve_chain_kind(req.src_chain_code); - req.src_token_kind = resolve_token_kind(req.src_token_code); - req.dst_chain = resolve_chain_kind(req.dst_chain_code); - req.dst_token_kind = resolve_token_kind(req.dst_token_code); - // Depot-leg stamps — exact compare against the registry's - // is_depot row. A depot (WIRE) leg needs no commit / bond / - // source verification; downstream stages branch on these. - req.src_is_depot = is_depot_leg(req.src_chain_code); - req.dst_is_depot = is_depot_leg(req.dst_chain_code); - - // The depot row, not the process-local confirmation cache, is the - // restart-safe authority for whether this candidate already has all - // required outpost evidence. A complete INTENT_SUBMITTED candidate has - // already received its one depot attempt and must never replay an - // outpost commit. An existing status that is invalid or no longer - // INTENT_SUBMITTED also fails closed rather than masquerading as absence. - bool own_candidate_exists = false; - bool own_candidate_submitted = false; - bool source_stored = false; - bool destination_stored = false; - if (obj.contains(uwrit::request_field::commits_by) && - obj[uwrit::request_field::commits_by].is_array()) { - for (const auto& commit_v : - obj[uwrit::request_field::commits_by].get_array()) { - const auto commit = commit_v.get_object(); - if (!commit.contains(uwrit::commit_field::underwriter) || - chain::name(commit[ - uwrit::commit_field::underwriter].as_string()) != - underwriter_account) { - continue; - } - own_candidate_exists = true; - const auto stored_status = - underwriter_detail::decode_enum_field( - commit, uwrit::commit_field::status); - own_candidate_submitted = stored_status == - UnderwriteStatus::UNDERWRITE_STATUS_INTENT_SUBMITTED; - const auto leg_ready = [&](const char* timestamp, - const char* payload) { - return commit.contains(timestamp) && - commit[timestamp].as_uint64() != 0 && - commit.contains(payload) && - commit[payload].is_string() && - !commit[payload].as_string().empty(); - }; - source_stored = leg_ready( - uwrit::commit_field::source_received_at_ms, - uwrit::commit_field::source_uic_bytes); - destination_stored = leg_ready( - uwrit::commit_field::destination_received_at_ms, - uwrit::commit_field::destination_uic_bytes); - break; - } - } - req.own_stored_commit_plan = underwriter_detail::plan_stored_commits( - own_candidate_exists, own_candidate_submitted, - req.src_is_depot, req.dst_is_depot, - source_stored, destination_stored); - - // The ABI surfaces `bytes` as a hex string. Decode both - // source_tx_id and depositor — the depot rejects any SwapRequest - // with empty source_tx_id at createuwreq (emits SwapRevert), so - // every uwreq the plugin sees should carry both fields. - auto decode_hex_field = [&](const char* key, std::vector& out) { - if (!obj.contains(key)) return; - auto s = obj[key].as_string(); - if (s.empty()) return; - out.resize(s.size() / 2); - fc::from_hex(s, out.data(), out.size()); - }; - decode_hex_field( - uwrit::request_field::source_transaction_id, req.source_tx_id); - decode_hex_field(uwrit::request_field::depositor, req.depositor); - - requests.push_back(std::move(req)); - } - - return requests; - } - - // ----------------------------------------------------------------------- - // Select requests coverable by our credit lines - // Requires 100% coverage on BOTH non-depot legs of the swap, where each - // leg's required bond is per exact `(chain_code, token_code)` bucket. - // ----------------------------------------------------------------------- - - /// Hard cap on the number of candidates the branch-and-bound search - /// considers. Above this we fall back to value-sort-descending greedy - /// to keep the cycle bounded — the upper-bound prune is good but worst - /// case is still 2^N branches. - static constexpr size_t MAX_CANDIDATES = 64; - - /// Build a `leg_bond` (exact `(chain_code, token_code)` bucket + bond - /// requirement) for one eventual lock of `r`. Only depot legs require no - /// collateral. Stored or locally-confirmed UICs have not created a lock yet, - /// so pre-validation must still budget their full winner-time requirement. - leg_bond src_bond(const uw_request& r) const { - return underwriter_detail::candidate_leg_bond( - bucket_key{r.src_chain_code.value, r.src_token_code.value}, - r.src_amount, r.src_is_depot); - } - leg_bond dst_bond(const uw_request& r) const { - return underwriter_detail::candidate_leg_bond( - bucket_key{r.dst_chain_code.value, r.dst_token_code.value}, - r.dst_amount, r.dst_is_depot); - } - - /// Attempt to debit `r`'s per-leg bond requirements from `remaining`, - /// delegating to the pure `underwriter_detail::try_debit_buckets`. Returns - /// true (and mutates `remaining`) iff every required leg fits. Buckets are - /// keyed by the EXACT `(chain_code, token_code)` slug pair (SEC-13/WSA-027), - /// so two same-VM-family chains hold independent credit and never collide. - /// Same-bucket dual-leg swaps (e.g. ERC20 → native on one chain) debit both - /// legs from the single shared row; a both-depot swap is rejected. - bool try_debit_buckets(credit_buckets& remaining, const uw_request& r) const { - return underwriter_detail::try_debit_buckets(remaining, src_bond(r), dst_bond(r)); - } - - /// Branch-and-bound search that returns the subset of `candidates` - /// maximizing `Σ(src_amount + dst_amount)` while each exact - /// `(chain_code, token_code)` credit bucket stays non-negative. Recurses depth-first - /// in two branches per candidate (include / skip); on each include - /// branch verifies feasibility before descending, and prunes the - /// subtree on infeasibility OR when the upper-bound estimate (current - /// value + every remaining candidate's value, regardless of fit) can't - /// beat the current best. - /// - /// Same-chain swaps (e.g. ERC20 → ETH-native) draw from a single - /// bucket when `src` and `dst` keys coincide; the include branch - /// debits both legs from the same row in that case. - void knapsack_dfs(size_t i, - const std::vector& candidates, - const std::vector& suffix_value, - credit_buckets remaining, - std::vector cur_indices, - uint64_t cur_value, - uint64_t& best_value, - std::vector& best_indices) { - // Upper-bound prune: even if every remaining candidate fit, the - // resulting value couldn't beat the current best. - if (cur_value + suffix_value[i] <= best_value) return; - - if (i == candidates.size()) { - if (cur_value > best_value) { - best_value = cur_value; - best_indices = cur_indices; - } - return; - } - - const auto& r = candidates[i]; - credit_buckets after = remaining; - const bool feasible = try_debit_buckets(after, r); - - // Branch 1: include (if feasible). - if (feasible) { - cur_indices.push_back(i); - knapsack_dfs(i + 1, candidates, suffix_value, - std::move(after), cur_indices, - cur_value + r.src_amount + r.dst_amount, - best_value, best_indices); - cur_indices.pop_back(); - } - - // Branch 2: skip. - knapsack_dfs(i + 1, candidates, suffix_value, - std::move(remaining), std::move(cur_indices), - cur_value, best_value, best_indices); - } - - /// Greedy fallback for above-cap candidate counts. Sorts by total leg - /// value descending and picks anything that still fits. - std::vector - greedy_fallback(std::vector requests, - credit_buckets remaining) const { - std::sort(requests.begin(), requests.end(), - [](const uw_request& a, const uw_request& b) { - return (a.src_amount + a.dst_amount) - > (b.src_amount + b.dst_amount); - }); - std::vector picked; - for (auto& r : requests) { - if (!try_debit_buckets(remaining, r)) continue; - picked.push_back(r); - } - return picked; - } - - std::vector select_coverable( - std::vector& requests, credit_buckets initial_credit) { - // Pre-filter requests that can never fit in isolation (no bucket even - // matches), so the search space stays small. Selection budgets every - // eventual outpost lock, including stored or locally-confirmed UIC legs; - // only a depot leg costs zero. - std::vector feasible_in_isolation; - feasible_in_isolation.reserve(requests.size()); - for (auto& r : requests) { - auto scratch = initial_credit; - if (!try_debit_buckets(scratch, r)) continue; - feasible_in_isolation.push_back(r); - } - - std::vector selected; - if (feasible_in_isolation.size() > MAX_CANDIDATES) { - wlog("underwriter: {} feasible candidates exceeds knapsack cap ({}); " - "falling back to value-sort-descending greedy", - feasible_in_isolation.size(), MAX_CANDIDATES); - selected = greedy_fallback(std::move(feasible_in_isolation), initial_credit); - } else if (!feasible_in_isolation.empty()) { - // Suffix sum of per-candidate value — the upper-bound prune in - // knapsack_dfs uses this to skip subtrees whose maximum possible - // remaining value can't beat the current best. - const size_t n = feasible_in_isolation.size(); - std::vector suffix_value(n + 1, 0); - for (size_t k = n; k > 0; --k) { - suffix_value[k - 1] = suffix_value[k] - + feasible_in_isolation[k - 1].src_amount - + feasible_in_isolation[k - 1].dst_amount; - } - - uint64_t best_value = 0; - std::vector best_indices; - knapsack_dfs(/*i=*/0, feasible_in_isolation, suffix_value, - initial_credit, /*cur_indices=*/{}, /*cur_value=*/0, - best_value, best_indices); - - selected.reserve(best_indices.size()); - for (size_t idx : best_indices) { - selected.push_back(feasible_in_isolation[idx]); - } - } - - for (auto& r : selected) { - ilog("underwriter: selected request {} — " - "src(chain={},token={},amt={}) dst(chain={},token={},amt={})", - r.id, - ChainKind_Name(r.src_chain), TokenKind_Name(r.src_token_kind), r.src_amount, - ChainKind_Name(r.dst_chain), TokenKind_Name(r.dst_token_kind), r.dst_amount); - } - return selected; - } - - // ----------------------------------------------------------------------- - // Submit commit to outpost contract - // The outpost requires an ACTIVE caller, validates caller binding and - // canonicality, then relays the original UIC bytes unchanged - // ----------------------------------------------------------------------- - - /// Build a verbatim, signed `UnderwriteIntentCommit` payload for the - /// given leg's `(uwreq_id, leg_chain_kind, chain_code, token_code, - /// reserve_code)`. Returns an empty vector on any failure (no signature - /// provider, serialize failure, etc.). - /// - /// The slug_name triple `(chain_code, token_code, reserve_code)` is the - /// routing scalar set the depot's `rcrdcommit` uses to disambiguate - /// src vs dst legs — same-chain swaps with multiple reserves on a single - /// `(chain, token)` pair are still resolvable because `reserve_code` - /// breaks the tie. `chain_code` carries the chain identity at the OPP - /// envelope level (the originating-outpost field on the inbound - /// envelope); `chain_code` is the same chain as a slug_name and is what - /// the depot indexes against `sysio.uwrit::uw_request_t.*_chain_code`. - /// - /// Digest semantics: the underwriter signs `sha256(serialize(uic with - /// signature blanked))`. The depot's `try_select_winner` rebuilds the - /// same digest from the bytes it received and verifies the embedded - /// signature against the underwriter's WIRE account permissions - /// (`owner` / `active` only) — see `sysio.uwrit::verify_uic_signature`. - std::vector create_signed_uic_bytes( - uint64_t uwreq_id, const outpost_client& outpost, - fc::slug_name chain_code, fc::slug_name token_code, - fc::slug_name reserve_code) { - opp_att::UnderwriteIntentCommit uic; - uic.mutable_uw_account()->set_name(underwriter_account.to_string()); - uic.set_uw_request_id(uwreq_id); - // Data model: leg identity is the slug_name triple. The wire format - // for each field is the packed uint64 slug_name value (alphabet - // `[A-Z0-9_]+`, ≤8 chars). The depot decodes these back to - // `sysio::slug_name` via `sysio::slug_name{uic.chain_code}` etc. in - // `sysio.msgch::dispatch_underwrite_commit`. - uic.set_chain_code(chain_code.value); - uic.set_token_code(token_code.value); - uic.set_reserve_code(reserve_code.value); - // Populate BOTH members from the concrete client that will submit this - // UIC. Host protobuf omits empty byte fields while the generated encoders - // emit them, so including the configured transaction signer gives every - // production encoder the same representation. The WIRE signature covers - // this signer metadata. The outposts bind the claimed account and address - // to their authenticated caller and require canonical encoding, then - // relay these original validated bytes unchanged. - const auto leg_chain_kind = outpost.chain_kind(); - const auto caller_address = outpost.authenticated_caller_address(); - const auto expected_address_size = - underwriter_detail::expected_uic_caller_address_size(leg_chain_kind); - if (!underwriter_detail::set_uic_authenticated_caller( - uic, leg_chain_kind, caller_address)) { - elog("underwriter: invalid authenticated caller address size {} for {} " - "outpost on uwreq {} (expected {})", - caller_address.size(), ChainKind_Name(leg_chain_kind), uwreq_id, - expected_address_size); - return {}; - } - uic.clear_signature(); - - std::string blanked; - if (!uic.SerializeToString(&blanked)) { - elog("underwriter: UIC serialize failed (blank phase) for uwreq {}", uwreq_id); - return {}; - } - - auto digest = fc::sha256::hash(blanked.data(), blanked.size()); - - // Preflight intersects operator configuration with the underwriter's - // direct owner/active keys, exercises the unique provider, and caches - // this exact handle. Runtime must not re-query the broader WIRE registry: - // it also contains legitimate providers for unrelated duties. - const auto& wire_provider = uic_signature_provider; - if (!wire_provider) { - elog("underwriter: no cached WIRE UIC signature provider — preflight should " - "have caught this. Aborting commit for uwreq {}.", uwreq_id); - return {}; - } - if (!underwriter_detail::is_supported_uic_public_key( - wire_provider->public_key)) { - elog("underwriter: WIRE UIC signature provider no longer uses a supported " - "fixed-size recoverable key — preflight should have caught this. " - "Aborting commit for uwreq {}.", - uwreq_id); - return {}; - } - auto fc_sig = wire_provider->sign(digest); - if (!underwriter_detail::is_supported_uic_signature(fc_sig)) { - elog("underwriter: WIRE UIC signature provider returned an unsupported " - "signature type — preflight should have caught this. Aborting commit " - "for uwreq {}.", - uwreq_id); - return {}; - } - if (!underwriter_detail::uic_signature_type_matches_provider_key( - wire_provider->public_key, fc_sig)) { - elog("underwriter: WIRE UIC signature provider returned a signature variant " - "that does not match its configured public key — aborting commit for " - "uwreq {}.", uwreq_id); - return {}; - } - - // Pack the fc::crypto::signature via fc::raw. The configured provider - // emits one canonical fixed-size shape accepted by the depot: K1/R1/EM - // use a 65-byte ECC body and ED uses a 96-byte body, each following its - // one-byte variant tag. The depot constructs that known variant directly. - std::vector sig_bytes = fc::raw::pack(fc_sig); - if (!underwriter_detail::is_canonical_packed_uic_signature(sig_bytes)) { - elog("underwriter: WIRE UIC signature provider produced a non-canonical packed " - "fixed-size signature — aborting commit for uwreq {}.", - uwreq_id); - return {}; - } - if (!underwriter_detail::has_canonical_uic_signature_body(sig_bytes)) { - elog("underwriter: WIRE UIC signature provider produced a noncanonical " - "ECDSA recovery/scalar body — aborting commit for uwreq {}.", uwreq_id); - return {}; - } - uic.set_signature(std::string(sig_bytes.begin(), sig_bytes.end())); - - std::string final_bytes; - if (!uic.SerializeToString(&final_bytes)) { - elog("underwriter: UIC serialize failed (final phase) for uwreq {}", uwreq_id); - return {}; - } - return std::vector(final_bytes.begin(), final_bytes.end()); - } - - /// Before committing collateral, independently verify the source-chain - /// deposit that funded this swap. `req.source_tx_id` is the chain-native - /// deposit id captured at swap-emit time (ETH/SOL: 8-byte monotonic - /// `SwapDeposit.id`). The verify path locates the source-chain event or - /// log for that id, confirms the transaction succeeded against the - /// configured source contract / program, and cross-references every - /// argument we can decode against the uwreq: - /// - /// * `depositor` — `tx.from` (ETH) / fee-payer (SOL) must match `req.depositor`. - /// * source contract — `tx.to` (ETH) / program-id (SOL) must match the configured address. - /// * function selector / instruction discriminator — must match the configured value. - /// * receipt status (ETH) / meta.err (SOL) — must indicate success. - /// * finality — ETH logs are queried only through `finalized`; SOL txs - /// are fetched at commitment `SOL_COMMITMENT`. - /// - /// Hard-fail on empty `source_tx_id` — the depot's `createuwreq` rejects - /// SwapRequests without one (emits SwapRevert), so by the time a uwreq - /// reaches the plugin every row MUST carry one. A `req.source_tx_id` - /// empty here means either the depot's reject regressed OR the plugin - /// read a row pre-validation; either way the safe move is to refuse to - /// commit until the data is whole. - bool verify_source_deposit(const uw_request& req) { - if (req.src_is_depot) { - // Swap-from-WIRE: the source funds were escrowed ON the depot by - // `sysio.uwrit::swapfromwire` before the uwreq existed — there is - // no outpost SwapDeposit to verify, and the synthetic - // source_tx_id is just the depot-origin queue id. This branch - // must precede the empty-source_tx_id hard-fail below. - dlog("underwriter: uwreq {} source is the WIRE depot — skipping " - "source-deposit verification", req.id); - return true; - } - if (req.source_tx_id.empty()) { - elog("underwriter: REFUSING to commit uwreq {} — source_tx_id empty. " - "Every SwapRequest is required to carry a populated source_tx_id; " - "the depot's createuwreq must have regressed.", req.id); - std::lock_guard lk{stats_mutex}; - source_deposit_mismatch_count++; - return false; - } - switch (req.src_chain) { - case ChainKind::CHAIN_KIND_EVM: - return verify_source_deposit_eth(req); - case ChainKind::CHAIN_KIND_SVM: - return verify_source_deposit_sol(req); - default: - elog("underwriter: cannot verify source deposit for chain={} (uwreq {})", - ChainKind_Name(req.src_chain), req.id); - return false; - } - } - - /// ETH-side source-deposit verification, event-scan flavour. - /// - /// `req.source_tx_id` is an 8-byte big-endian uint64 — the outpost- - /// local monotonic counter value written into the SwapRequest - /// envelope by `ReserveManager.requestSwap`. The corresponding - /// `SwapDeposit(uint64 indexed id, bytes32 hash)` event is emitted - /// in the same tx. Verification: - /// - /// 1. Parse `source_tx_id` → `id` (uint64). Reject empty / wrong-size. - /// 2. `eth_getLogs` filtered by: - /// - address = resolved ReserveManager contract address - /// - topic[0] = keccak256("SwapDeposit(uint64,bytes32)") - /// - topic[1] = abi-encoded `id` (32-byte BE-padded uint64) - /// Must return exactly one matching log. - /// 3. Decode the log's `data` field as the emitted hash bytes. - /// 4. Recompute the same hash from UWREQ fields: - /// keccak256(packed - /// depositor[20] - /// src_amount [u64 BE] src_token_code [u64 BE] src_reserve_code [u64 BE] - /// dst_chain_code [u64 BE] dst_token_code [u64 BE] dst_reserve_code [u64 BE] - /// target_amount [u64 BE] - /// variance_tolerance_bps [u32 BE]) - /// The depot's UWREQ row carries every input; matches must be - /// bit-exact against the contract-emitted `hash`. - /// 5. Pull the matching log's `transactionHash`. Receipt must exist - /// and status != "0x0". The log lookup itself is bounded by - /// Ethereum's finalized head; if the finalized head is unavailable - /// the verifier defers without widening to `latest`. - /// - /// A non-matching hash is a hard mismatch — the depositor's swap - /// params disagree with what's recorded on the source chain. A - /// missing log / receipt without mismatched fields is a deferred - /// retry (returns false but no mismatch counter bump). - bool verify_source_deposit_eth(const uw_request& req) { - // SEC-13/WSA-027: resolve this leg's outpost wiring by EXACT chain_code — - // the RPC client and the SwapDeposit contract address are per-chain, so a - // second EVM chain is verified against ITS endpoint, not the first one's. - const auto ep_it = outpost_endpoints.find(req.src_chain_code.value); - if (ep_it == outpost_endpoints.end()) { - elog("underwriter: no outpost endpoint configured for source chain={} " - "(uwreq {})", req.src_chain_code.to_string(), req.id); - return false; - } - const std::string& eth_client_id = ep_it->second.client_id; - const std::string& resolved_eth_source_contract_addr = ep_it->second.source_deposit_addr; - - auto entry = eth_plug->get_client(eth_client_id); - if (!entry || !entry->client) { - elog("underwriter: ETH client '{}' not found for source-deposit verify " - "(uwreq {})", eth_client_id, req.id); - return false; - } - auto bump_mismatch = [&]() { - std::lock_guard lk{stats_mutex}; - source_deposit_mismatch_count++; - }; - - // (1) Decode source_tx_id as an 8-byte big-endian uint64 id. - if (req.source_tx_id.size() != 8) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "source_tx_id has wrong size ({} bytes; expected 8 for ETH " - "monotonic id)", req.id, req.source_tx_id.size()); - bump_mismatch(); - return false; - } - uint64_t deposit_id = 0; - for (size_t i = 0; i < 8; ++i) { - deposit_id = (deposit_id << 8) | - static_cast(req.source_tx_id[i]); - } - - // (2) eth_getLogs filter for SwapDeposit(uint64 indexed id, bytes32 hash). - // The event topic[0] is keccak256 of the canonical signature. - static const std::string SWAP_DEPOSIT_TOPIC = - "0x" + fc::to_hex(reinterpret_cast( - fc::crypto::keccak256::hash(std::string{ - "SwapDeposit(uint64,bytes32)"}).data()), 32); - // Indexed uint64 → left-pad to 32 bytes (Solidity uses 0-padded - // big-endian; topics are always 32 bytes). - std::string id_topic = "0x"; - id_topic.reserve(66); - id_topic.append(48, '0'); // 24 leading zero bytes = 48 hex chars - for (int shift = 56; shift >= 0; shift -= 8) { - uint8_t b = static_cast((deposit_id >> shift) & 0xff); - char buf[3]; std::snprintf(buf, sizeof buf, "%02x", b); - id_topic.append(buf, 2); - } - - uint64_t finalized_blk = 0; - try { - const auto finalized_var = entry->client->execute_idempotent( - std::string{ETH_GET_BLOCK_BY_NUMBER_METHOD}, - fc::variants{eth::to_block_tag(eth::block_tag_t::finalized), false}); - if (finalized_var.is_null()) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head unavailable", req.id); - return false; - } - if (!finalized_var.is_object()) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head response is not an object", req.id); - return false; - } - const auto finalized_obj = finalized_var.get_object(); - if (!finalized_obj.contains("number") || !finalized_obj["number"].is_string()) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head missing numeric block number", req.id); - return false; - } - const auto parsed_finalized_blk = - sysio::underwriter::eth_parse_block_quantity(finalized_obj["number"].as_string()); - if (!parsed_finalized_blk) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head block number is malformed ('{}')", - req.id, finalized_obj["number"].as_string()); - return false; - } - finalized_blk = *parsed_finalized_blk; - } catch (const fc::exception& e) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head unavailable: {}", req.id, e.to_detail_string()); - return false; - } catch (const std::exception& e) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "ETH finalized head response is malformed: {}", req.id, e.what()); - return false; - } - - try { - const uint64_t from_blk = sysio::underwriter::eth_source_deposit_from_block( - finalized_blk, eth_source_deposit_lookback_blocks); - const std::string from_block = sysio::underwriter::eth_block_quantity(from_blk); - const std::string to_block = sysio::underwriter::eth_block_quantity(finalized_blk); - - fc::mutable_variant_object filter; - filter("address", resolved_eth_source_contract_addr); - filter("fromBlock", from_block); - filter("toBlock", to_block); - filter("topics", fc::variants{ - fc::variant{SWAP_DEPOSIT_TOPIC}, - fc::variant{id_topic}, - }); - // eth_getLogs takes its filter as the first element of the JSON-RPC - // `params` array — wrap the filter object in a one-element array. - auto logs_var = entry->client->get_logs( - fc::variant{fc::variants{fc::variant{filter}}}); - if (!logs_var.is_array() || logs_var.size() == 0) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "no SwapDeposit log in recent block window (id={}, " - "contract={}, range={}..{})", - req.id, deposit_id, resolved_eth_source_contract_addr, - from_block, to_block); - return false; - } - // Should be exactly one match — id is unique per outpost. Use - // the first; warn (don't fail) on duplicates so we surface a - // bug without halting the underwriter. - if (logs_var.size() > 1) { - wlog("underwriter: source-deposit verify uwreq {} — got {} " - "SwapDeposit logs for id={}; using the first", - req.id, logs_var.size(), deposit_id); - } - const auto& log = logs_var.get_array().at(0).get_object(); - - // (3) Decode log.data = abi.encodePacked(bytes32 hash) = 32 bytes. - std::string data_hex; - if (log.contains("data") && log["data"].is_string()) { - data_hex = log["data"].as_string(); - } - std::string_view data_view = data_hex; - if (data_view.size() >= 2 && data_view[0] == '0' && - (data_view[1] == 'x' || data_view[1] == 'X')) { - data_view.remove_prefix(2); - } - if (data_view.size() != 64) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "SwapDeposit log.data has wrong size ({} hex chars; " - "expected 64 for bytes32)", req.id, data_view.size()); - bump_mismatch(); - return false; - } - std::array on_chain_hash{}; - for (size_t i = 0; i < 32; ++i) { - on_chain_hash[i] = static_cast(std::stoul( - std::string{data_view.substr(i * 2, 2)}, nullptr, 16)); - } - - // (4) Recompute the hash from the UWREQ row's immutable source-outpost - // fields. Layout MUST match ReserveManagerLib.hashSwapDeposit's - // abi.encodePacked(...) call exactly. In particular, the outpost - // hashes the user's original `target_amount`, not the depot's - // re-priced `dst_amount` settlement quote. - if (req.depositor.size() != underwriter::EVM_DEPOSITOR_SIZE) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "depositor has wrong size ({} bytes; expected {} for EVM " - "address)", req.id, req.depositor.size(), underwriter::EVM_DEPOSITOR_SIZE); - bump_mismatch(); - return false; - } - const auto recomputed = underwriter::source_deposit_hash({ - .depositor = std::span{req.depositor}, - .source_amount = req.src_amount, - .source_token_code = req.src_token_code.value, - .source_reserve_code = req.src_reserve_code.value, - .target_chain_code = req.dst_chain_code.value, - .target_token_code = req.dst_token_code.value, - .target_reserve_code = req.dst_reserve_code.value, - .target_amount = req.target_amount, - .target_tolerance_bps = req.variance_tolerance_bps, - }); - if (std::memcmp(recomputed.data(), on_chain_hash.data(), 32) != 0) { - const std::string got_hex = fc::to_hex( - reinterpret_cast(on_chain_hash.data()), 32); - const std::string want_hex = fc::to_hex( - reinterpret_cast(recomputed.data()), 32); - elog("underwriter: source-deposit verify failed for uwreq {} — " - "SwapDeposit hash mismatch (id={}): on-chain={} recomputed={}", - req.id, deposit_id, got_hex, want_hex); - bump_mismatch(); - return false; - } - - // (5) Receipt status on the matching finalized tx. - std::string tx_hash_hex; - if (log.contains("transactionHash") && - log["transactionHash"].is_string()) { - tx_hash_hex = log["transactionHash"].as_string(); - } - if (tx_hash_hex.empty()) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "SwapDeposit log missing transactionHash", req.id); - bump_mismatch(); - return false; - } - auto receipt = entry->client->get_transaction_receipt(tx_hash_hex); - if (receipt.is_null()) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "no receipt yet for matching tx {}", req.id, tx_hash_hex); - return false; - } - const auto rcpt_obj = receipt.get_object(); - if (rcpt_obj.contains("status") && - rcpt_obj["status"].as_string() == "0x0") { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "matching tx {} reverted on-chain", req.id, tx_hash_hex); - bump_mismatch(); - return false; - } - ilog("underwriter: source-deposit verify passed for uwreq {} " - "(SwapDeposit id={} tx={} finalized_head={})", - req.id, deposit_id, tx_hash_hex, finalized_blk); - return true; - } catch (const fc::exception& e) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "RPC error: {}", req.id, e.to_detail_string()); - bump_mismatch(); - return false; - } catch (const std::exception& e) { - // std::stoul/std::stoull on a malformed or non-hex RPC field throw std::invalid_argument / - // std::out_of_range, which are NOT fc::exception; catch them here so one bad response fails - // just this uwreq instead of escaping and aborting the whole scan cycle for every request. - elog("underwriter: source-deposit verify failed for uwreq {} — " - "malformed RPC response: {}", req.id, e.what()); - bump_mismatch(); - return false; - } - } - - /// SOL-side source-deposit verification. Mirrors - /// `verify_source_deposit_eth` step-for-step: - /// - /// 1. `req.source_tx_id` is an 8-byte big-endian monotonic - /// `deposit_id` (minted by `opp-outpost::request_swap`, - /// `OutboundMessageBuffer.next_swap_id`). Wrong-size → - /// hard mismatch. - /// 2. `fc::task::retry_until` with - /// `SOL_SWAP_DEPOSIT_POLL_INTERVAL` (15s) / - /// `SOL_SWAP_DEPOSIT_TOTAL_TIMEOUT` (120s, both in - /// `outpost_solana_client_plugin.hpp`) drives the paginated scan: - /// a. `getSignaturesForAddress(sol_program_id, - /// before=, limit=SOL_SIGNATURE_SCAN_PAGE_SIZE)` - /// enumerates the next older page of program sigs. - /// b. For each sig, `getTransaction(sig)`; inspect - /// `meta.err` (skip failed tx) and - /// `meta.logMessages[]` for the canonical marker: - /// `Program log: opp_outpost: SwapDeposit id= hash=<64hex>`. - /// c. Persist the next `before` cursor only after a clean page with - /// no match; unfinalized matches and transient tx fetch failures - /// retry the same page. - /// 3. On match, parse the 32-byte on-chain hash from the log. - /// 4. Recompute the same hash from the UWREQ row's flat fields - /// (depositor[32] + 7×u64 BE + u32 BE = 92 packed bytes). - /// Bit-exact mismatch → hard mismatch counter. - /// 5. Finality gate: matching tx's `meta.confirmationStatus` - /// (when present on the per-sig listing from step 2a) must - /// be `"finalized"` — same finality requirement as ETH's finalized - /// log lookup. Tx not yet finalized → deferred retry (no mismatch bump). - /// - /// `false` returns: - /// - hard mismatch on bad size; - /// - terminal source-deposit failure (counter bumped once per pending - /// UWREQ) on hash divergence, malformed marker hash, or full-history - /// exhaustion without a marker; - /// - deferred retry (no counter bump) on `retry_until` deadline - /// reached without a matching finalized log — the outer poll loop - /// reattempts on its next tick from the persisted cursor. - bool verify_source_deposit_sol(const uw_request& req) { - // SEC-13/WSA-027: resolve this leg's outpost wiring by EXACT chain_code. - const auto ep_it = outpost_endpoints.find(req.src_chain_code.value); - if (ep_it == outpost_endpoints.end()) { - elog("underwriter: no outpost endpoint configured for source chain={} " - "(uwreq {})", req.src_chain_code.to_string(), req.id); - return false; - } - const std::string& sol_client_id = ep_it->second.client_id; - const std::string& sol_program_id = ep_it->second.commit_addr; - - auto entry = sol_plug->get_client(sol_client_id); - if (!entry || !entry->client) { - elog("underwriter: SOL client '{}' not found for source-deposit verify " - "(uwreq {})", sol_client_id, req.id); - return false; - } - auto bump_mismatch = [&]() { - std::lock_guard lk{stats_mutex}; - source_deposit_mismatch_count++; - }; - - // ── (1) source_tx_id → 8-byte big-endian deposit_id ──────────────── - if (req.source_tx_id.size() != 8) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "source_tx_id has wrong size ({} bytes; expected 8 for SOL " - "monotonic deposit_id mirroring the ETH wire shape)", - req.id, req.source_tx_id.size()); - bump_mismatch(); - return false; - } - uint64_t deposit_id = 0; - for (size_t i = 0; i < 8; ++i) { - deposit_id = (deposit_id << 8) | - static_cast(req.source_tx_id[i]); - } - - // ── (4) Recompute the expected correlation hash from UWREQ fields ── - // Layout MUST stay synchronized with the producer side - // (`opp-outpost/src/instructions/request_swap.rs::correlation_hash`). - // Like the EVM outpost, it hashes the user's original `target_amount`, - // not the depot's re-priced `dst_amount` settlement quote. - if (req.depositor.size() != underwriter::SVM_DEPOSITOR_SIZE) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "depositor has wrong size ({} bytes; expected {} for SVM " - "Ed25519 pubkey)", req.id, req.depositor.size(), underwriter::SVM_DEPOSITOR_SIZE); - bump_mismatch(); - return false; - } - const auto recomputed_hash = underwriter::source_deposit_hash({ - .depositor = std::span{req.depositor}, - .source_amount = req.src_amount, - .source_token_code = req.src_token_code.value, - .source_reserve_code = req.src_reserve_code.value, - .target_chain_code = req.dst_chain_code.value, - .target_token_code = req.dst_token_code.value, - .target_reserve_code = req.dst_reserve_code.value, - .target_amount = req.target_amount, - .target_tolerance_bps = req.variance_tolerance_bps, - }); - - // Canonical marker the producer emits on `request_swap`. The - // Solana JSON-RPC `meta.logMessages[]` array contains each - // `msg!` line verbatim, prefixed with `"Program log: "`. - const std::string marker_prefix = - "Program log: opp_outpost: SwapDeposit id=" + - std::to_string(deposit_id) + " hash="; - - // ── (2) + (3) retry-loop until matched or budget expires ─────────── - // `bool hard_mismatch` distinguishes "found and known-bad" (hash - // divergence / malformed marker / exhausted history) from "not yet - // found" (deferred). Terminal SOL failures are cached until the UWREQ - // leaves PENDING so later scan cycles do not re-walk full history. - // The retry-callback returns: - // - Some(true) -> success, exit retry_until - // - Some(false) hard_mismatch -> fail-fast, exit retry_until - // - None -> deferred, sleep + retry - // Outer `try { retry_until } catch (timeout_exception)` maps the - // deadline-without-match case to deferred-no-mismatch-bump, while the - // cursor survives for the next outer scan tick. - const underwriter::solana_source_deposit_scan_key scan_key{req.id, deposit_id}; - if (const auto terminal = get_sol_source_deposit_terminal_failure(scan_key)) { - ilog("underwriter: source-deposit verify skipped for uwreq {} — " - "previous terminal SOL verification failure: {} " - "(deposit_id={}, pages_scanned={}, signatures_scanned={})", - req.id, - terminal->terminal_failure_reason, - deposit_id, - terminal->pages_scanned, - terminal->signatures_scanned); - return false; - } - - bool hard_mismatch_seen = false; - std::string matched_sig; - - try { - fc::task::retry_options opts; - opts.initial_backoff = SOL_SWAP_DEPOSIT_POLL_INTERVAL; - opts.max_backoff = SOL_SWAP_DEPOSIT_POLL_INTERVAL; - opts.total_timeout = SOL_SWAP_DEPOSIT_TOTAL_TIMEOUT; - opts.growth_factor = 1.0; // fixed-interval cadence - - std::function()> attempt = - [&]() -> std::optional { - const auto cursor = get_or_create_sol_source_deposit_scan_cursor(scan_key); - std::vector sigs; - try { - sigs = entry->client->get_signatures_for_address( - sol_program_id, - cursor.before, - std::nullopt, - underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - } catch (const fc::exception& e) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "getSignaturesForAddress({}, before={}) RPC error: {}", - req.id, - sol_program_id, - cursor.before.value_or(""), - e.to_detail_string()); - return std::nullopt; - } - - const underwriter::solana_source_deposit_page_scan_config scan_config{ - .sol_program_id = sol_program_id, - .marker_prefix = marker_prefix, - .recomputed_hash = recomputed_hash, - }; - const auto scan_result = underwriter::scan_solana_source_deposit_signature_page( - sigs, - [&](const std::string& sig_b58) { - return entry->client->get_transaction(sig_b58, underwriter::SOL_COMMITMENT); - }, - scan_config); - - using page_status = underwriter::solana_source_deposit_page_status; - switch (scan_result.status) { - case page_status::matched: - matched_sig = scan_result.matched_signature; - erase_sol_source_deposit_scan_cursor(scan_key); - return std::optional(true); - case page_status::hard_mismatch: - elog("underwriter: source-deposit verify failed for uwreq {} — " - "{} (deposit_id={}, program={}, before={})", - req.id, - scan_result.reason, - deposit_id, - sol_program_id, - cursor.before.value_or("")); - hard_mismatch_seen = true; - { - const auto terminal = record_sol_source_deposit_terminal_failure( - scan_key, scan_result.reason, sigs.size()); - if (terminal.first_failure) bump_mismatch(); - } - return std::optional(false); - case page_status::deferred: - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "{} (deposit_id={}, program={}, before={})", - req.id, - scan_result.reason, - deposit_id, - sol_program_id, - cursor.before.value_or("")); - return std::nullopt; - case page_status::not_found: - break; - } - - if (scan_result.page_exhausted) { - const std::string reason = - "exhausted Solana program signature history without SwapDeposit log"; - elog("underwriter: source-deposit verify failed for uwreq {} — " - "{} (deposit_id={}, program={}, before={}, page_size={})", - req.id, - reason, - deposit_id, - sol_program_id, - cursor.before.value_or(""), - sigs.size()); - hard_mismatch_seen = true; - { - const auto terminal = record_sol_source_deposit_terminal_failure( - scan_key, reason, sigs.size()); - if (terminal.first_failure) bump_mismatch(); - } - return std::optional(false); - } - - if (!scan_result.next_before) { - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "signature page produced no usable pagination cursor " - "(deposit_id={}, program={}, before={}, page_size={})", - req.id, - deposit_id, - sol_program_id, - cursor.before.value_or(""), - sigs.size()); - return std::nullopt; - } - - const auto advanced = advance_sol_source_deposit_scan_cursor( - scan_key, *scan_result.next_before, sigs.size()); - ilog("underwriter: source-deposit verify paginated for uwreq {} — " - "no SwapDeposit log in page (deposit_id={}, program={}, " - "before={}, next_before={}, pages_scanned={}, signatures_scanned={})", - req.id, - deposit_id, - sol_program_id, - cursor.before.value_or(""), - *scan_result.next_before, - advanced.pages_scanned, - advanced.signatures_scanned); - return std::nullopt; // not found this iteration; sleep + retry - }; - - const bool ok = fc::task::retry_until( - "underwriter:verify_source_deposit_sol", opts, attempt); - if (ok) { - ilog("underwriter: source-deposit verify passed for uwreq {} " - "(deposit_id={} tx={} program={})", - req.id, deposit_id, matched_sig, sol_program_id); - return true; - } - // retry_until returned `false` → hard mismatch already counted + - // logged inside the callback. Propagate the failure unchanged. - return false; - } catch (const fc::timeout_exception&) { - // Deadline reached without finding a matching log. Treat as a - // deferred retry by the outer underwriter loop — DO NOT bump - // the mismatch counter, matching the ETH verify's semantics - // for "log not yet present." - if (hard_mismatch_seen) { - // Defensive: shouldn't get here since hard mismatch returns - // Some(false) above, but if it ever does, keep counting. - return false; - } - ilog("underwriter: source-deposit verify deferred for uwreq {} — " - "no SwapDeposit log line for deposit_id={} after {}s " - "(program={}, scan-interval={}s, page-size={}, will retry from persisted cursor)", - req.id, deposit_id, - SOL_SWAP_DEPOSIT_TOTAL_TIMEOUT.count() / 1'000'000, - sol_program_id, - SOL_SWAP_DEPOSIT_POLL_INTERVAL.count() / 1'000'000, - underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - return false; - } catch (const fc::exception& e) { - elog("underwriter: source-deposit verify failed for uwreq {} — " - "RPC error: {}", req.id, e.to_detail_string()); - bump_mismatch(); - return false; - } - } - - /** - * Submit a `commit` JSON-RPC call to each still-missing outpost leg. Depot - * rows suppress already-stored legs after restart, while complete stored - * candidates are skipped before this function is reached. - * Each outpost requires an ACTIVE-role caller, canonicalizes the - * UNDERWRITE_INTENT_COMMIT, binds its identity fields to that caller and - * current roster row, then queues the original validated bytes. The depot's - * resolver (sysio.uwrit::try_select_winner) reconstructs the digest, verifies - * the signature against the underwriter's account permissions, and - * promotes the underwriter to winner only after every required UIC leg and - * current WIRE permission verifies, the complete live obligation is bonded - * (via `available()` rollup), destination remit identity is available, and - * the request-global live-price and reserve gates pass. - * - * Outposts do not validate the WIRE permission signature or bond; those - * remain the depot's authoritative responsibilities. - */ - void submit_intent_to_outpost(const uw_request& req) { - // T13: confirm the source-chain deposit backing this swap is real - // before committing collateral. Returns true with a warning log - // until the swap-emit-site populates source_tx_id (staged rollout). - if (!verify_source_deposit(req)) { - ilog("underwriter: skipping uwreq {} — source-deposit verify failed", - req.id); - return; - } - - ilog("underwriter: submitting missing commit legs for uwreq {} " - "src=({},{},{}) dst=({},{},{})", - req.id, - req.src_chain_code.to_string(), - req.src_token_code.to_string(), - req.src_reserve_code.to_string(), - req.dst_chain_code.to_string(), - req.dst_token_code.to_string(), - req.dst_reserve_code.to_string()); - - // Per-leg dispatch keyed on the slug_name triple - // `(chain_code, token_code, reserve_code)`. Same-chain swaps (e.g. - // ERC20 → native on one outpost) share `chain_code` between the two - // legs but differ on `token_code`/`reserve_code`; the UIC payload - // carries the full triple so the depot's `rcrdcommit` can route to - // the correct source/dest slot on `commit_entry`. - // - // Confirmation discipline: we skip any leg whose exact - // `(uwreq_id, chain_code, token_code, reserve_code)` identity is already - // in `confirmed_commits` (a previous cycle's tx confirmed on-chain). - // After a successful confirm we record the key so the next scan doesn't - // resubmit. Per project rules: confirm BEFORE recording so a - // partial-landing in the map cannot happen without OPP breakage. - auto submit_one = [this](ChainKind chain, - fc::slug_name chain_code, - fc::slug_name token_code, - fc::slug_name reserve_code, - uint64_t uw_request_id, - bool is_depot, - bool stored_by_depot) { - if (is_depot) { - // Intended, not an error: depot (WIRE) legs are never - // underwritten — no outpost exists, no UIC is built, no bond - // is consumed. Single-leg swaps commit only their real leg. - dlog("underwriter: uwreq {} leg chain={} is the WIRE depot — " - "no commit needed", uw_request_id, chain_code.to_string()); - return; - } - if (stored_by_depot) { - ilog("underwriter: skip depot-stored commit uwreq={} chain={} token={}", - uw_request_id, chain_code.to_string(), token_code.to_string()); - return; - } - const commit_key key{uw_request_id, chain_code.value, token_code.value, - reserve_code.value}; - if (confirmed_commits.contains(key)) { - ilog("underwriter: skip already-confirmed commit uwreq={} chain={} token={}", - uw_request_id, - chain_code.to_string(), - token_code.to_string()); - return; - } - - // Chain-agnostic SPI call. The `outpost_by_chain` map carries one - // `outpost_client` per EXACT `chain_code` (SEC-13/WSA-027), built at - // startup via the outpost-plugin factories. Each concrete owns its own - // ABI / IDL discovery, address encoding, and on-chain confirmation - // discipline — this loop passes the serialized UIC bytes unchanged - // through the virtual. Per `outpost-client-spi.md`. - auto it = outpost_by_chain.find(chain_code.value); - if (it == outpost_by_chain.end()) { - elog("underwriter: no outpost_client wired for chain={} (uwreq {})", - chain_code.to_string(), uw_request_id); - std::lock_guard lk{stats_mutex}; - commits_failed_count++; - return; - } - if (it->second->chain_kind() != chain) { - elog("underwriter: outpost_client kind {} does not match leg kind {} " - "for chain={} (uwreq {})", - ChainKind_Name(it->second->chain_kind()), ChainKind_Name(chain), - chain_code.to_string(), uw_request_id); - std::lock_guard lk{stats_mutex}; - commits_failed_count++; - return; - } - auto uic_bytes = create_signed_uic_bytes( - uw_request_id, *it->second, chain_code, token_code, reserve_code); - if (uic_bytes.empty()) return; // already logged - bool confirmed = false; - try { - auto tx_or_sig = it->second->uw_commit( - uw_request_id, uic_bytes, fc::milliseconds(action_timeout_ms)); - ilog("underwriter: commit landed on {} uwreq={} tx_or_sig={}", - it->second->to_string(), uw_request_id, tx_or_sig); - confirmed = true; - } catch (const fc::exception& e) { - elog("underwriter: commit failed on {} for uwreq={}: {}", - it->second->to_string(), uw_request_id, e.to_detail_string()); - } - std::lock_guard lk{stats_mutex}; - if (confirmed) { - confirmed_commits.insert(key); - commits_confirmed_count++; - } else { - commits_failed_count++; - } - }; - submit_one(req.src_chain, - req.src_chain_code, req.src_token_code, req.src_reserve_code, - req.id, req.src_is_depot, - !req.own_stored_commit_plan.submit_source); - submit_one(req.dst_chain, - req.dst_chain_code, req.dst_token_code, req.dst_reserve_code, - req.id, req.dst_is_depot, - !req.own_stored_commit_plan.submit_destination); - } - - // Outpost commit submission is delegated entirely to the `outpost_client` - // SPI — see `outpost_by_chain` above and `submit_intent_to_outpost` for - // the dispatch. Chain-specific ABI / IDL discovery, address encoding, - // and on-chain confirmation live inside - // `outpost_ethereum_client::uw_commit` and - // `outpost_solana_client::uw_commit`. Per `outpost-client-spi.md`. - - // Paid outpost commit submissions use the outpost_client SPI. Terminal - // candidates are suppressed by the authoritative depot-row plan; complete - // candidates await an external exact-UIC replay rather than resubmitting - // paid outpost work automatically. - - // ── HTTP API: /v1/underwriter/* ───────────────────────────────────── - // Read-only diagnostic surface for the operator. Wraps internal state - // (`confirmed_commits`, counters, account/config) under `stats_mutex` - // so http_plugin worker threads and the single cron thread don't race. - // - // Endpoints: - // /v1/underwriter/stats — session counters + config snapshot - // /v1/underwriter/commits — outstanding confirmed commits (per leg) - // - // Both carry a `status` discriminator: until the deferred startup body - // completes they answer with the gate state (`waiting_for_sync` + - // `head_behind_sec`/`lib_behind_sec`, `preflight_retrying`, or a terminal - // `preflight_failed`/`wiring_failed`/`startup_failed` with `detail`); - // once active they serve the payloads below with `status:"active"`. - - fc::variant build_stats_response() { - std::lock_guard lk{stats_mutex}; - size_t active_sol_source_deposit_cursor_count = 0; - size_t sol_source_deposit_terminal_failure_count = 0; - for (const auto& entry : sol_source_deposit_scan_cursors) { - if (entry.second.terminal_failure) { - sol_source_deposit_terminal_failure_count++; - } else { - active_sol_source_deposit_cursor_count++; - } - } - // SEC-13/WSA-027: surface the per-chain outpost wiring (was the single - // eth/sol client-id + address) so operators can confirm each served - // chain is configured. - std::vector ep_v; - ep_v.reserve(outpost_endpoints.size()); - for (const auto& [code, ep] : outpost_endpoints) { - ep_v.push_back(fc::mutable_variant_object() - ("chain_code", fc::slug_name{code}.to_string()) - ("kind", ChainKind_Name(ep.kind)) - ("client_id", ep.client_id) - ("commit_addr", ep.commit_addr) - ("source_deposit_addr", ep.source_deposit_addr)); - } - return fc::variant(fc::mutable_variant_object() - (underwriter_detail::field::status, std::string{underwriter_detail::active_status}) - ("underwriter_account", underwriter_account.to_string()) - ("enabled", enabled) - ("is_active", is_active) - ("scan_interval_ms", scan_interval_ms) - ("action_timeout_ms", action_timeout_ms) - ("outpost_endpoints", fc::variant(std::move(ep_v))) - ("eth_source_deposit_function", eth_source_deposit_function_name) - ("sol_source_deposit_instruction", sol_source_deposit_instruction_name) - ("uwreqs_seen_pending", uwreqs_seen_pending_count) - ("commits_confirmed", commits_confirmed_count) - ("commits_failed", commits_failed_count) - ("source_deposit_mismatch", source_deposit_mismatch_count) - ("outstanding_commit_count", confirmed_commits.size()) - ("sol_source_deposit_cursor_count", active_sol_source_deposit_cursor_count) - ("sol_source_deposit_terminal_failure_count", sol_source_deposit_terminal_failure_count) - ); - } - - fc::variant build_commits_response() { - std::lock_guard lk{stats_mutex}; - std::vector entries; - entries.reserve(confirmed_commits.size()); - for (const auto& k : confirmed_commits) { - entries.push_back(fc::variant(fc::mutable_variant_object() - ("uwreq_id", k.uwreq_id) - ("chain_code", fc::slug_name{k.chain_code}.to_string()) - ("token_code", fc::slug_name{k.token_code}.to_string()) - ("reserve_code", fc::slug_name{k.reserve_code}.to_string()) - )); - } - return fc::variant(fc::mutable_variant_object() - (underwriter_detail::field::status, std::string{underwriter_detail::active_status}) - ("count", entries.size()) - ("entries", std::move(entries)) - ); - } - - /// Answer `cb` with the gate-state payload and return true when the - /// deferred startup body has not completed (waiting for sync, retrying, - /// or failed terminally); return false once {@link run_deferred_startup} - /// reached `active` so the caller serves the real payload. - bool respond_if_gated(const url_response_callback& cb) { - const auto state = gate_state.load(); - if (state == underwriter_detail::startup_state::active) { - return false; - } - // Only the waiting_for_sync payload carries the behind-now gaps; skip - // the chain reads for the other states. `lib_behind_sec` is the gate's - // actual criterion (reads serve the irreversible state); the head gap - // distinguishes a finality-stalled node from one still catching up. - // An absent irreversible root stays a typed empty optional here — the - // payload builder turns it into the wire's -1 sentinel. - fc::microseconds head_behind{}; - std::optional lib_behind; - if (state == underwriter_detail::startup_state::waiting_for_sync) { - auto& chain = chain_plug->chain(); - const auto now = fc::time_point::now(); - head_behind = now - chain.head().block_time(); - if (chain.fork_db_has_root()) { - lib_behind = now - chain.fork_db_root().block_time(); - } - } - cb(200, underwriter_detail::startup_gate_payload(state, head_behind, lib_behind)); - return true; - } - - /// Register the `/v1/underwriter/*` HTTP endpoints. Called once from - /// `plugin_startup` when the underwriter is enabled, BEFORE the sync gate: - /// `http_plugin`'s handler map is read lock-free by the HTTP worker - /// threads, so every registration must happen during plugin startup — - /// before the posted listener creation runs — never from a task queued - /// after `exec()` is live. Until the deferred startup body completes the - /// handlers report the gate state (see {@link respond_if_gated}). - /// - /// Both endpoints live in the dedicated `api_category::underwriter`, NOT - /// `api_category::node`: they expose operator metadata (account identity, - /// client ids, outpost contract addresses, outstanding commits) that must - /// not ride the always-on node category onto category-isolated public - /// listeners. They remain reachable on the default all-category listeners - /// (`http-server-address` / `unix-socket-path`); on category-isolated - /// setups the operator opts in with `--http-category-address=underwriter,`. - void register_http_endpoints() { - auto& hp = app().get_plugin(); - hp.add_api({ - {"/v1/underwriter/stats", api_category::underwriter, - [this](std::string&& /*url*/, - std::string&& /*body*/, - url_response_callback&& cb) { - try { - if (respond_if_gated(cb)) { - return; - } - cb(200, build_stats_response()); - } catch (const fc::exception& e) { - cb(500, fc::variant(fc::mutable_variant_object() - ("error", e.to_detail_string()))); - } - }}, - {"/v1/underwriter/commits", api_category::underwriter, - [this](std::string&& /*url*/, - std::string&& /*body*/, - url_response_callback&& cb) { - try { - if (respond_if_gated(cb)) { - return; - } - cb(200, build_commits_response()); - } catch (const fc::exception& e) { - cb(500, fc::variant(fc::mutable_variant_object() - ("error", e.to_detail_string()))); - } - }}, - }, appbase::exec_queue::read_only); - - // Operator metadata should normally stay on loopback / private - // management networks; a public bind is a deliberate choice worth a - // startup warning (same pattern as the snapshot_ro exposure notice). - if (!hp.is_on_loopback(api_category::underwriter)) { - wlog("underwriter diagnostics API (/v1/underwriter/*) exposed on a non-loopback address"); - } - } -}; - -// --------------------------------------------------------------------------- -// Plugin lifecycle -// --------------------------------------------------------------------------- -underwriter_plugin::underwriter_plugin() - : _impl(std::make_unique()) {} - -underwriter_plugin::~underwriter_plugin() = default; - -void underwriter_plugin::set_program_options(options_description& cli, - options_description& cfg) { - auto opts = cfg.add_options(); - // Presence of this option IS the enable switch (mirrors producer_plugin's - // producer-name): the scan cycle runs when an account is configured. - opts("underwriter-account", bpo::value(), - "WIRE account name for this underwriter. Configuring an account enables the scan cycle."); - opts("underwriter-scan-interval-ms", bpo::value()->default_value(underwriter_defaults::scan_interval_ms), - "How often to scan for pending underwrite requests (ms)"); - opts("underwriter-action-timeout-ms", bpo::value()->default_value(underwriter_defaults::action_timeout_ms), - "Timeout for outpost contract calls and table reads (ms)"); - opts("underwriter-eth-source-deposit-function", bpo::value(), - "Name of the ETH swap-deposit function. Resolved at preflight against the ABI " - "files registered with --ethereum-abi-file; the matching `function` entry's keccak256 " - "signature yields the chain-agnostic 4-byte selector. The per-chain source contract " - "address comes from the chain's sysio.chains row. Required."); - opts("underwriter-sol-source-deposit-instruction", bpo::value(), - "Name of the SOL swap-deposit instruction. Resolved at preflight against the IDL " - "files registered with --solana-idl-file; the matching instruction's anchor " - "discriminator (8 bytes) is used to identify the deposit instruction in source " - "txs. Required."); - opts(sysio::underwriter::ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION.data(), - bpo::value()->default_value( - sysio::underwriter::ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS), - "Maximum number of recent finalized ETH blocks searched for the source " - "SwapDeposit event. Bounds the per-uwreq eth_getLogs filter so stale " - "or invalid deposit ids cannot trigger whole-history provider scans."); -} - -void underwriter_plugin::plugin_initialize(const variables_map& options) { - if (options.count("underwriter-account")) - _impl->underwriter_account = chain::name(options["underwriter-account"].as()); - _impl->scan_interval_ms = options["underwriter-scan-interval-ms"].as(); - _impl->action_timeout_ms = options["underwriter-action-timeout-ms"].as(); - _impl->enabled = _impl->underwriter_account.good(); - if (options.count("underwriter-eth-source-deposit-function")) - _impl->eth_source_deposit_function_name = - options["underwriter-eth-source-deposit-function"].as(); - if (options.count("underwriter-sol-source-deposit-instruction")) - _impl->sol_source_deposit_instruction_name = - options["underwriter-sol-source-deposit-instruction"].as(); - _impl->eth_source_deposit_lookback_blocks = - options[sysio::underwriter::ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION.data()].as(); - FC_ASSERT(_impl->eth_source_deposit_lookback_blocks > 0, - "{} must be positive", - sysio::underwriter::ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION); - - _impl->chain_plug = &app().get_plugin(); - _impl->cron_plug = &app().get_plugin(); - _impl->eth_plug = &app().get_plugin(); - _impl->sol_plug = &app().get_plugin(); - - // Operator daemons are designed for read-mode = irreversible: the sync gate - // (controller::is_synced) measures LIB recency and every local table read the - // preflight and scan cycle perform serves the irreversible view. Any other read - // mode would validate/underwrite against state that can still fork out. Together - // with producer_plugin's inverse assert (no producer-name under irreversible - // read-mode), this also makes co-hosting a producer with an operator daemon - // impossible by configuration. - FC_ASSERT(!_impl->enabled || - _impl->chain_plug->chain().get_read_mode() == chain::db_read_mode::IRREVERSIBLE, - "underwriter_plugin requires read-mode = irreversible"); -} - -void underwriter_plugin::plugin_startup() { - if (!_impl->enabled) { - ilog("underwriter_plugin: no underwriter-account configured, skipping startup"); - return; - } - - ilog("underwriter_plugin: starting for account {}", _impl->underwriter_account.to_string()); - - // Register the `/v1/underwriter/*` endpoints FIRST, before the sync - // gate: handler registration must complete during plugin startup - // (before the posted HTTP listener goes live) because the handler map - // is read lock-free by the HTTP threads — it must never ride the - // deferred startup body below. Until that body completes, the handlers - // answer with the gate state, so a cold-booting (or terminally-failed) - // underwriter is diagnosable over HTTP instead of a single ilog. - _impl->register_http_endpoints(); - - // The preflight validates depot-side state (opreg registration, chain - // registry, authex links) via LOCAL table reads. On a cold-booting - // operator node those reads see mid-sync (possibly genesis) state and - // would fail spuriously, so the whole startup body (preflight → outpost - // client wiring → cron) is DEFERRED until the node is synced — - // `controller::is_synced()`: the LAST IRREVERSIBLE block's time within - // `controller::default_sync_recency_ms` of now (the state the reads - // actually serve under read-mode = irreversible). The wake-up is the - // existing `irreversible_block` channel: a LIB advance is the only event - // that can turn the predicate true, and channel deliveries are posted to - // the application executor — main thread, AFTER the triggering block - // fully commits — so the callback may run the startup body directly. - // There is deliberately no already-synced fast path: operator daemons - // boot with genesis-stale LIB in every deployment topology (producer - // co-hosting is impossible by configuration — see the read-mode - // requirement in plugin_initialize), and a node that somehow is synced at - // startup is released by the next LIB advance. That advance is also the - // underwriter's WORK SUPPLY — everything here acts on newly-finalized - // state — so a finality stall that spans startup (the classic lost-wakeup - // case) is one with nothing to underwrite: the first finalized block - // re-arms the gate and creates the first actionable state at the same - // instant. Post-arming, the preflight - // retries within a bounded grace (see attempt_deferred_startup) to absorb - // the LIB boundary race; a genuinely incomplete bootstrap still fails - // loudly once the grace expires and shuts the node down (fail-fast) — - // there remains no dev escape hatch. - auto& chain = _impl->chain_plug->chain(); - ilog("underwriter_plugin: waiting for chain sync before preflight " - "(head {} is {}s behind now; irreversible state is {}s behind)", - chain.head().block_num(), - (fc::time_point::now() - chain.head().block_time()).to_seconds(), - chain.fork_db_has_root() - ? std::to_string((fc::time_point::now() - chain.fork_db_root().block_time()).to_seconds()) - : "n/a"); - _impl->sync_gate_subscription = - app().get_channel().subscribe( - [impl = _impl.get()](const chain::plugin_interface::channels::block_params&) { - if (impl->startup_attempted() || impl->shutting_down || - !impl->chain_plug->chain().is_synced()) { - return; - } - // One-shot consumption: unsubscribe (safe from within the slot) and - // run the startup body directly — channel deliveries are posted to - // the application executor, so this already runs on the main thread - // AFTER the triggering block committed (the exact context the old - // accepted_block gate had to re-post itself into; running mid - // block-application, table reads see an incomplete view — observed - // live: a registered operator row read back EMPTY → spurious - // preflight failure). - impl->sync_gate_subscription.unsubscribe(); - ilog("underwriter_plugin: chain synced — starting deferred startup"); - impl->run_deferred_startup(); - }); -} - -void underwriter_plugin::plugin_shutdown() { - _impl->shutting_down = true; - if (_impl->preflight_retry_timer) { - _impl->preflight_retry_timer->cancel(); - } - - if (_impl->scan_job_id != 0) { - auto& cron = app().get_plugin(); - cron.cancel_job(_impl->scan_job_id); - } - - ilog("underwriter_plugin: shutdown complete"); -} - -} // namespace sysio diff --git a/plugins/underwriter_plugin/test/main.cpp b/plugins/underwriter_plugin/test/main.cpp deleted file mode 100644 index f897527acb..0000000000 --- a/plugins/underwriter_plugin/test/main.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define BOOST_TEST_MODULE underwriter_plugin_tests -#include diff --git a/plugins/underwriter_plugin/test/test_underwriter_plugin.cpp b/plugins/underwriter_plugin/test/test_underwriter_plugin.cpp deleted file mode 100644 index df8f8ceac4..0000000000 --- a/plugins/underwriter_plugin/test/test_underwriter_plugin.cpp +++ /dev/null @@ -1,1238 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::literals; -using namespace sysio::underwriter_defaults; -using namespace sysio::underwriter; - -namespace { - -/// Removed ETH confirmation-depth option name. Keeping the spelling here makes -/// the option-surface test catch any future non-finality escape hatch. -constexpr std::string_view removed_eth_min_confirmations_option = "underwriter-eth-min-confirmations"; - -/// Removed enable flag. Configuring underwriter-account is what enables the -/// scan cycle; a separate flag would let an operator set the account and still -/// underwrite nothing, which is indistinguishable from a healthy node. -constexpr std::string_view removed_enabled_option = "underwriter-enabled"; - -/// Removed per-chain outpost wiring options. Nothing about an outpost is -/// declared per node any more: `sysio.chains` names which chains are served and -/// where their contracts live, and the RPC client for a chain is the one -/// registered under that chain's own code. -constexpr std::string_view removed_eth_outpost_option = "underwriter-eth-outpost"; -constexpr std::string_view removed_sol_outpost_option = "underwriter-sol-outpost"; - -/// Program id used by the scanner tests to model the configured opp-outpost. -const std::string test_sol_program_id = "OppOutpost11111111111111111111111111111111"; - -/// Program id used by the scanner tests to model an attacker-controlled CPI caller. -const std::string attacker_program_id = "Attacker111111111111111111111111111111111"; - -/// Returns a deterministic expected correlation hash for test markers. -fc::crypto::keccak256 test_expected_hash() { - return fc::crypto::keccak256::hash(std::string{"sec-40-source-deposit"}); -} - -/// Hex-encodes a 32-byte keccak hash the same way the Solana outpost log does. -std::string hash_hex(const fc::crypto::keccak256& hash) { - return fc::to_hex(reinterpret_cast(hash.data()), 32); -} - -/// Decodes a hex string into the `std::vector` shape a UWREQ row's -/// `depositor` field carries. -std::vector depositor_bytes(std::string_view hex) { - std::vector bytes(hex.size() / 2); - fc::from_hex(hex, bytes.data(), bytes.size()); - return bytes; -} - -/// Builds the canonical SwapDeposit marker prefix for a deposit id. -std::string marker_prefix(uint64_t deposit_id) { - return "Program log: opp_outpost: SwapDeposit id=" + std::to_string(deposit_id) + " hash="; -} - -/// Builds one `getSignaturesForAddress` response entry. -fc::variant signature_entry(const std::string& sig, - const std::string& confirmation_status = "finalized", - bool failed = false) { - fc::mutable_variant_object obj; - obj("signature", sig); - obj("confirmationStatus", confirmation_status); - if (failed) { - obj("err", "failed"); - } else { - obj("err", fc::variant()); - } - return fc::variant(obj); -} - -/// Builds one `getTransaction` response with the supplied runtime log lines. -fc::variant transaction_with_logs(const std::vector& logs, bool failed = false) { - fc::variants log_variants; - log_variants.reserve(logs.size()); - for (const auto& line : logs) { - log_variants.emplace_back(line); - } - - fc::mutable_variant_object meta; - if (failed) { - meta("err", "failed"); - } else { - meta("err", fc::variant()); - } - meta("logMessages", log_variants); - - return fc::variant(fc::mutable_variant_object()("meta", meta)); -} - -/// Builds runtime logs where `program_id` is the current executing program. -std::vector program_logs(const std::string& program_id, - const std::vector& payloads) { - std::vector logs; - logs.reserve(payloads.size() + 2); - logs.push_back("Program " + program_id + " invoke [1]"); - logs.insert(logs.end(), payloads.begin(), payloads.end()); - logs.push_back("Program " + program_id + " success"); - return logs; -} - -/// Runs the Solana source-deposit page scanner against in-memory transactions. -sysio::underwriter::solana_source_deposit_page_scan_result -scan_test_page(const std::vector& sigs, - const std::map& tx_by_sig, - const fc::crypto::keccak256& expected_hash, - uint64_t deposit_id, - size_t* fetch_count = nullptr) { - const auto prefix = marker_prefix(deposit_id); - const sysio::underwriter::solana_source_deposit_page_scan_config config{ - .sol_program_id = test_sol_program_id, - .marker_prefix = prefix, - .recomputed_hash = expected_hash, - }; - return sysio::underwriter::scan_solana_source_deposit_signature_page( - sigs, - [&](const std::string& sig) { - if (fetch_count) ++*fetch_count; - return tx_by_sig.at(sig); - }, - config); -} - -/** Build one explicitly named local signature-provider specification. */ -std::string explicit_uic_provider_spec( - std::string_view key_name, - fc::crypto::chain_key_type_t key_type, - fc::crypto::private_key::key_type concrete_type) { - const auto key = fc::crypto::private_key::generate(concrete_type); - return fc::crypto::to_signature_provider_spec( - std::string{key_name}, fc::crypto::chain_kind_wire, key_type, - key.get_public_key().to_string({}), "KEY:" + key.to_string({})); -} - -/** Build one anonymous four-field local signature-provider specification. */ -std::string anonymous_uic_provider_spec( - std::string_view key_name, - fc::crypto::chain_key_type_t key_type, - fc::crypto::private_key::key_type concrete_type) { - const auto named = explicit_uic_provider_spec(key_name, key_type, concrete_type); - return named.substr(named.find(',') + 1); -} - -/** - * Exercise operator-configured UIC selection after production-style WIRE default - * registration for one supported provider key type. - */ -void check_operator_configured_uic_provider_beats_wire_default( - std::string_view key_name, - fc::crypto::chain_key_type_t key_type, - fc::crypto::private_key::key_type concrete_type, - bool automatic_default_coexists = true, - bool has_explicit_name = true) { - using namespace fc::crypto; - auto reset_app = gsl_lite::finally([]() { - appbase::application::reset_app_singleton(); - }); - const auto config_dir = std::filesystem::temp_directory_path() - / ("underwriter-uic-provider-" + std::string{key_name}); - std::error_code ec; - std::filesystem::remove_all(config_dir, ec); - std::filesystem::create_directories(config_dir); - auto cleanup = gsl_lite::finally([&]() { - std::error_code ignored; - std::filesystem::remove_all(config_dir, ignored); - }); - - std::vector args{ - "test_underwriter_plugin", - "--config-dir", config_dir.string(), - "--signature-provider", - has_explicit_name - ? explicit_uic_provider_spec(key_name, key_type, concrete_type) - : anonymous_uic_provider_spec(key_name, key_type, concrete_type), - }; - std::vector argv; - argv.reserve(args.size()); - for (auto& arg : args) argv.push_back(arg.data()); - - appbase::scoped_app test_app; - BOOST_REQUIRE(test_app->initialize( - argv.size(), argv.data())); - auto& manager = test_app->get_plugin(); - - // Emulate chain_plugin's production initialize-time call. This focused test - // intentionally couples to that API; if chain_plugin changes registration - // timing, reassess the preflight ordering as well as this regression. - manager.register_default_signature_providers({chain_key_type_wire}); - const auto wire_providers = manager.query_providers(std::nullopt, chain_kind_wire); - const auto selected = sysio::underwriter_detail::select_uic_signature_providers( - wire_providers, [&](const std::string& name) { - return manager.is_operator_configured_provider(name); - }, [](const public_key&) { return true; }); - - BOOST_REQUIRE_EQUAL(1u, selected.size()); - BOOST_CHECK_EQUAL(has_explicit_name ? std::string{key_name} : "key-0", - selected.front()->key_name); - BOOST_CHECK(manager.is_operator_configured_provider( - selected.front()->key_name)); - BOOST_CHECK_EQUAL(has_explicit_name, - manager.is_explicitly_configured_provider(selected.front()->key_name)); - BOOST_CHECK(sysio::underwriter_detail::is_supported_uic_public_key( - selected.front()->public_key)); - - std::vector automatic_only; - for (const auto& provider : wire_providers) { - if (!manager.is_operator_configured_provider(provider->key_name)) { - automatic_only.push_back(provider); - } - } - if (automatic_default_coexists) { - BOOST_REQUIRE(!automatic_only.empty()); - } else { - // Production default registration is keyed by chain-key type. An - // explicit WIRE-native K1/R1 provider suppresses the automatic WIRE - // default in that same bucket; EM/ED occupy different buckets and - // therefore coexist with it. - BOOST_CHECK(automatic_only.empty()); - } - BOOST_CHECK(sysio::underwriter_detail::select_uic_signature_providers( - automatic_only, [&](const std::string& name) { - return manager.is_operator_configured_provider(name); - }, [](const public_key&) { return true; }).empty()); -} - -} // namespace - -BOOST_AUTO_TEST_SUITE(underwriter_plugin_tests) - -BOOST_AUTO_TEST_CASE(enum_variant_decode_is_typed_and_checked) try { - using sysio::opp::types::AttestationType; - using sysio::opp::types::UnderwriteRequestStatus; - using sysio::opp::types::UnderwriteStatus; - - const fc::variant_object request_object{ - fc::mutable_variant_object{}( - "status", std::string{"UNDERWRITE_REQUEST_STATUS_PENDING"})}; - const auto request_status = - sysio::underwriter_detail::decode_enum_field( - request_object, "status"); - BOOST_REQUIRE(request_status); - BOOST_CHECK(*request_status == - UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_PENDING); - BOOST_CHECK(!sysio::underwriter_detail::decode_enum_field< - UnderwriteRequestStatus>(request_object, "missing")); - - const auto attestation_type = - sysio::underwriter_detail::decode_enum_variant( - fc::variant{uint64_t{magic_enum::enum_integer( - AttestationType::ATTESTATION_TYPE_SWAP_REQUEST)}}); - BOOST_REQUIRE(attestation_type); - BOOST_CHECK(*attestation_type == AttestationType::ATTESTATION_TYPE_SWAP_REQUEST); - - const auto candidate_status = - sysio::underwriter_detail::decode_enum_variant( - fc::variant{std::string{"UNDERWRITE_STATUS_INTENT_SUBMITTED"}}); - BOOST_REQUIRE(candidate_status); - BOOST_CHECK(*candidate_status == - UnderwriteStatus::UNDERWRITE_STATUS_INTENT_SUBMITTED); - - const std::array rejected_shapes{ - fc::variant{}, - fc::variant{false}, - fc::variant{true}, - fc::variant{0.9}, - fc::variant{std::numeric_limits::max()}, - fc::variant{std::numeric_limits::infinity()}, - fc::variant{fc::variant_object{}}, - fc::variant{fc::variants{}}, - fc::variant{fc::blob{}}, - fc::variant{std::numeric_limits::max()}, - fc::variant{int64_t{-1}}, - }; - for (const auto& value : rejected_shapes) { - BOOST_CHECK(!sysio::underwriter_detail::decode_enum_variant< - UnderwriteStatus>(value)); - } - BOOST_CHECK(!sysio::underwriter_detail::decode_enum_variant( - fc::variant{std::numeric_limits::max()})); - BOOST_CHECK(!sysio::underwriter_detail::decode_enum_variant( - fc::variant{std::string{"UNDERWRITE_STATUS_NOT_REAL"}})); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_authenticated_caller_shape_is_non_default_and_exact) try { - using sysio::opp::attestations::UnderwriteIntentCommit; - using sysio::opp::types::CHAIN_KIND_EVM; - using sysio::opp::types::CHAIN_KIND_SVM; - using sysio::opp::types::CHAIN_KIND_UNKNOWN; - - for (const auto [kind, size] : std::array{ - std::pair{CHAIN_KIND_EVM, size_t{20}}, - std::pair{CHAIN_KIND_SVM, size_t{32}}, - }) { - UnderwriteIntentCommit uic; - const std::vector address(size, 0x5au); - BOOST_REQUIRE(sysio::underwriter_detail::set_uic_authenticated_caller( - uic, kind, address)); - BOOST_CHECK_EQUAL(kind, uic.uw_ext_chain_addr().kind()); - BOOST_CHECK_EQUAL_COLLECTIONS( - address.begin(), address.end(), - uic.uw_ext_chain_addr().address().begin(), - uic.uw_ext_chain_addr().address().end()); - } - - UnderwriteIntentCommit invalid; - const std::vector short_evm(19, 0x01u); - BOOST_CHECK(!sysio::underwriter_detail::set_uic_authenticated_caller( - invalid, CHAIN_KIND_EVM, short_evm)); - BOOST_CHECK(!invalid.has_uw_ext_chain_addr()); - const std::vector unsupported(32, 0x01u); - BOOST_CHECK(!sysio::underwriter_detail::set_uic_authenticated_caller( - invalid, CHAIN_KIND_UNKNOWN, unsupported)); - BOOST_CHECK(!invalid.has_uw_ext_chain_addr()); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(plugin_can_be_constructed) try { - sysio::underwriter_plugin plugin; - BOOST_CHECK(true); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(plugin_options_are_registered) try { - sysio::underwriter_plugin plugin; - boost::program_options::options_description cli, cfg; - plugin.set_program_options(cli, cfg); - - const auto& opts = cfg.options(); - std::set option_names; - for (const auto& opt : opts) { - option_names.insert(opt->long_name()); - } - BOOST_CHECK(option_names.count("underwriter-account") > 0); - BOOST_CHECK(option_names.count("underwriter-scan-interval-ms") > 0); - BOOST_CHECK(option_names.count("underwriter-action-timeout-ms") > 0); - BOOST_CHECK(option_names.count(std::string{ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION}) > 0); - BOOST_CHECK_EQUAL(option_names.count(std::string{removed_eth_min_confirmations_option}), 0); - BOOST_CHECK_EQUAL(option_names.count(std::string{removed_enabled_option}), 0); - BOOST_CHECK_EQUAL(option_names.count(std::string{removed_eth_outpost_option}), 0); - BOOST_CHECK_EQUAL(option_names.count(std::string{removed_sol_outpost_option}), 0); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(default_options_are_correct) try { - sysio::underwriter_plugin plugin; - boost::program_options::options_description cli, cfg; - plugin.set_program_options(cli, cfg); - - boost::program_options::variables_map vm; - boost::program_options::store( - boost::program_options::parse_command_line(0, static_cast(nullptr), cfg), vm); - boost::program_options::notify(vm); - - BOOST_CHECK_EQUAL(vm["underwriter-scan-interval-ms"].as(), scan_interval_ms); - BOOST_CHECK_EQUAL(vm["underwriter-action-timeout-ms"].as(), action_timeout_ms); - // No default account: an unconfigured node leaves the scan cycle off. - BOOST_CHECK_EQUAL(vm.count("underwriter-account"), 0u); - // No per-chain outpost wiring to default either: the former - // --underwriter-{eth,sol}-outpost options are gone, and their absence is - // asserted in the option-registration case above. - BOOST_CHECK_EQUAL( - vm[std::string{ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION}].as(), - ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS); -} FC_LOG_AND_RETHROW(); - -/// The ETH source-deposit event lookup window is inclusive and anchored on the -/// finalized head. It never reaches below genesis, so the verifier can bound -/// each `eth_getLogs` request without losing well-defined behavior on young -/// chains. -BOOST_AUTO_TEST_CASE(eth_source_deposit_log_window_is_bounded) try { - constexpr uint64_t first_non_genesis_head = 1; - constexpr uint64_t one_block_window = 1; - constexpr uint64_t larger_head = ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS + 99; - - BOOST_CHECK_EQUAL( - eth_source_deposit_from_block(larger_head, ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS), - larger_head - ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS + 1); - BOOST_CHECK_EQUAL( - larger_head - eth_source_deposit_from_block(larger_head, ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS) + 1, - ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS); - BOOST_CHECK_EQUAL(eth_source_deposit_from_block(first_non_genesis_head, - ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS), - 0); - BOOST_CHECK_EQUAL(eth_source_deposit_from_block(larger_head, one_block_window), - larger_head); -} FC_LOG_AND_RETHROW(); - -/// Reject an empty bounded ETH log lookup window. -BOOST_AUTO_TEST_CASE(eth_source_deposit_options_reject_zero_window) try { - sysio::underwriter_plugin plugin; - boost::program_options::options_description cli, cfg; - plugin.set_program_options(cli, cfg); - - const std::vector args{ - "--" + std::string{ETH_SOURCE_DEPOSIT_LOOKBACK_BLOCKS_OPTION} + "=0", - }; - - boost::program_options::variables_map vm; - boost::program_options::store( - boost::program_options::command_line_parser(args).options(cfg).run(), vm); - boost::program_options::notify(vm); - - BOOST_CHECK_THROW(plugin.plugin_initialize(vm), fc::exception); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(source_deposit_hash_uses_the_immutable_target_amount) try { - const std::array depositor{ - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, - }; - const sysio::underwriter::source_deposit_hash_input input{ - .depositor = depositor, - .source_amount = 0x0102030405060708ULL, - .source_token_code = 0x1112131415161718ULL, - .source_reserve_code = 0x2122232425262728ULL, - .target_chain_code = 0x3132333435363738ULL, - .target_token_code = 0x4142434445464748ULL, - .target_reserve_code = 0x5152535455565758ULL, - .target_amount = 0x6162636465666768ULL, - .target_tolerance_bps = 0x71727374U, - }; - const auto hash = sysio::underwriter::source_deposit_hash(input); - - BOOST_CHECK_EQUAL( - fc::to_hex(reinterpret_cast(hash.data()), 32), - "08e7395c07b220b0c1ae590b5273028f0e2f424a39fbd307d691e2b5eba2ac04"); - - auto changed_target = input; - ++changed_target.target_amount; - const auto different_target = sysio::underwriter::source_deposit_hash(changed_target); - BOOST_CHECK(hash != different_target); -} FC_LOG_AND_RETHROW(); - -/// Explicit block bounds must be encoded as JSON-RPC quantities, not tags such -/// as `earliest` or `latest`. -BOOST_AUTO_TEST_CASE(eth_block_quantity_formats_json_rpc_numbers) try { - constexpr uint64_t low_nibble_block = 15; - constexpr uint64_t two_digit_block = 16; - constexpr uint64_t sample_block = 0x1234abcd; - - BOOST_CHECK_EQUAL(eth_block_quantity(0), "0x0"); - BOOST_CHECK_EQUAL(eth_block_quantity(low_nibble_block), "0xf"); - BOOST_CHECK_EQUAL(eth_block_quantity(two_digit_block), "0x10"); - BOOST_CHECK_EQUAL(eth_block_quantity(sample_block), "0x1234abcd"); -} FC_LOG_AND_RETHROW(); - -/// Malformed finalized-head block numbers defer source-deposit verification -/// instead of escaping through the scan cycle. -BOOST_AUTO_TEST_CASE(eth_block_quantity_parser_rejects_malformed_rpc_numbers) try { - const auto parsed_zero = eth_parse_block_quantity("0x0"); - const auto parsed_sample = eth_parse_block_quantity("0x1234abcd"); - - BOOST_REQUIRE(parsed_zero); - BOOST_REQUIRE(parsed_sample); - BOOST_CHECK_EQUAL(*parsed_zero, 0); - BOOST_CHECK_EQUAL(*parsed_sample, 0x1234abcd); - BOOST_CHECK(!eth_parse_block_quantity("")); - BOOST_CHECK(!eth_parse_block_quantity("0x")); - BOOST_CHECK(!eth_parse_block_quantity("1234")); - BOOST_CHECK(!eth_parse_block_quantity("0x12zz")); - BOOST_CHECK(!eth_parse_block_quantity("0x10000000000000000")); -} FC_LOG_AND_RETHROW(); - -// ── B1: preflight option-coverage ────────────────────────────────────── -// -// The full preflight (operator status / authex links / balances / -// signature self-test) needs a live chain context to exercise. What we -// CAN cover at unit-test level is that every required CLI option for -// the verify path is declared by `set_program_options` — so a typo'd -// option name in production won't slip past with a silent default. -BOOST_AUTO_TEST_CASE(preflight_required_options_are_registered) try { - sysio::underwriter_plugin plugin; - boost::program_options::options_description cli, cfg; - plugin.set_program_options(cli, cfg); - - const auto& opts = cfg.options(); - std::set option_names; - for (const auto& opt : opts) { - option_names.insert(opt->long_name()); - } - BOOST_CHECK(option_names.count("underwriter-account") > 0); - BOOST_CHECK(option_names.count("underwriter-eth-source-deposit-function") > 0); - BOOST_CHECK(option_names.count("underwriter-sol-source-deposit-instruction") > 0); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(preflight_accepts_fixed_size_recoverable_uic_signature_providers) try { - using private_key = fc::crypto::private_key; - using provider_result = - sysio::underwriter_detail::uic_signature_provider_result; - - const auto digest = fc::sha256::hash( - std::string{sysio::underwriter_detail::uic_signature_self_test_domain}); - const std::array supported_types{ - private_key::key_type::k1, - private_key::key_type::r1, - private_key::key_type::em, - private_key::key_type::ed, - }; - - for (const auto type : supported_types) { - const auto key = private_key::generate(type); - const auto signature = key.sign(digest); - - fc::crypto::signature_provider_t provider; - provider.public_key = key.get_public_key(); - provider.sign = [signature](const fc::sha256&) { return signature; }; - - const auto check = - sysio::underwriter_detail::check_uic_signature_provider(provider, digest); - BOOST_CHECK(check.result == provider_result::compatible); - BOOST_REQUIRE(check.recovered_key); - BOOST_CHECK(*check.recovered_key == key.get_public_key()); - - const auto packed = fc::raw::pack(signature); - BOOST_CHECK( - sysio::underwriter_detail::is_canonical_packed_uic_signature(packed)); - const size_t expected_size = - sysio::underwriter_detail::canonical_packed_uic_signature_size( - signature.type()); - BOOST_CHECK_EQUAL(packed.size(), expected_size); - } - - const auto k1_private = private_key::generate(private_key::key_type::k1); - const auto r1_private = private_key::generate(private_key::key_type::r1); - const auto r1_signature = r1_private.sign(digest); - - // A supported provider key paired with another supported signature variant - // still fails: the signer identity must match its configured public key. - fc::crypto::signature_provider_t mismatched_provider; - mismatched_provider.public_key = k1_private.get_public_key(); - mismatched_provider.sign = [r1_signature](const fc::sha256&) { - return r1_signature; - }; - const auto mismatched_check = - sysio::underwriter_detail::check_uic_signature_provider(mismatched_provider, digest); - BOOST_CHECK(mismatched_check.result == provider_result::signature_type_mismatch); - BOOST_CHECK(!mismatched_check.recovered_key); - - // WebAuthn and BLS tags remain outside the non-parsing protocol boundary, - // and supported tags with the wrong exact size are non-canonical. - using sig_type = fc::crypto::signature::sig_type; - auto tag = [](sig_type type) { - return static_cast(magic_enum::enum_integer(type)); - }; - fc::crypto::webauthn::signature webauthn{ - fc::crypto::r1::compact_signature{}, - std::vector{1, 2, 3}, - R"({"type":"webauthn.get"})"}; - const auto webauthn_shape = fc::raw::pack(fc::crypto::signature{ - fc::crypto::signature::storage_type{std::move(webauthn)}}); - const auto bls_shape = fc::raw::pack(fc::crypto::signature{ - fc::crypto::signature::storage_type{ - std::in_place_type}}); - std::vector truncated_ed( - sysio::underwriter_detail::canonical_packed_uic_signature_size(sig_type::ed) - 1, - '\0'); - truncated_ed.front() = tag(sig_type::ed); - BOOST_CHECK(!sysio::underwriter_detail::is_canonical_packed_uic_signature( - webauthn_shape)); - BOOST_CHECK(!sysio::underwriter_detail::is_canonical_packed_uic_signature( - bls_shape)); - BOOST_CHECK(!sysio::underwriter_detail::is_canonical_packed_uic_signature( - truncated_ed)); - - // A provider can return a recoverable high-s alternate even though local - // libfc signers normally emit low-s. Preflight must reject it before key - // recovery so runtime construction cannot submit a depot-invalid UIC. - const auto canonical_k1 = k1_private.sign(digest); - auto high_s_packed = sysio::opp::test::create_high_s_alternate( - fc::raw::pack(canonical_k1), private_key::key_type::k1); - BOOST_REQUIRE(high_s_packed.has_value()); - BOOST_REQUIRE_EQUAL( - sysio::underwriter_detail::canonical_packed_uic_signature_size( - sig_type::k1), - high_s_packed->size()); - // create_signed_uic_bytes calls this shared predicate independently at - // runtime after packing every returned signature, even after startup - // preflight has passed. Exercise that exact guard as well as the aggregate - // preflight result below. - BOOST_CHECK(!sysio::underwriter_detail::has_canonical_uic_signature_body( - *high_s_packed)); - const auto high_s_signature = - fc::raw::unpack(*high_s_packed); - fc::crypto::signature_provider_t high_s_provider; - high_s_provider.public_key = k1_private.get_public_key(); - high_s_provider.sign = [high_s_signature](const fc::sha256&) { - return high_s_signature; - }; - const auto high_s_check = - sysio::underwriter_detail::check_uic_signature_provider(high_s_provider, digest); - BOOST_CHECK(high_s_check.result == provider_result::non_canonical_signature); - BOOST_CHECK(!high_s_check.recovered_key); - - // Libfc can recover the same key from the other recovery-header family. - // Both startup preflight and the runtime predicate must reject that alias - // so a UIC signature has exactly one accepted byte representation. - const std::array alias_types{ - private_key::key_type::k1, - private_key::key_type::r1, - private_key::key_type::em, - }; - for (const auto type : alias_types) { - const auto key = private_key::generate(type); - const auto canonical = key.sign(digest); - auto aliased_packed = sysio::opp::test::create_recovery_alias( - fc::raw::pack(canonical), type); - BOOST_REQUIRE(aliased_packed.has_value()); - BOOST_CHECK(!sysio::underwriter_detail::has_canonical_uic_signature_body( - *aliased_packed)); - - const auto aliased = fc::raw::unpack(*aliased_packed); - BOOST_REQUIRE(fc::crypto::public_key::recover(aliased, digest) - == key.get_public_key()); - fc::crypto::signature_provider_t alias_provider; - alias_provider.public_key = key.get_public_key(); - alias_provider.sign = [aliased](const fc::sha256&) { return aliased; }; - const auto alias_check = - sysio::underwriter_detail::check_uic_signature_provider(alias_provider, digest); - BOOST_CHECK(alias_check.result == provider_result::non_canonical_signature); - BOOST_CHECK(!alias_check.recovered_key); - } -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_reaches_all_supported_native_key_formats) try { - using namespace fc::crypto; - - appbase::scoped_app test_app; - char program_name[] = "test_underwriter_plugin"; - char* argv[] = {program_name}; - BOOST_REQUIRE(test_app->initialize( - std::size(argv), argv)); - auto& manager = test_app->get_plugin(); - - auto add_provider = [&](std::string key_name, - chain_kind_t target_chain, - chain_key_type_t key_type, - private_key::key_type concrete_type) { - const auto key = private_key::generate(concrete_type); - return manager.create_provider( - key_name, target_chain, key_type, - key.get_public_key().to_string({}), "KEY:" + key.to_string({})); - }; - - add_provider("uic-k1", chain_kind_wire, chain_key_type_wire, - private_key::key_type::k1); - add_provider("uic-r1", chain_kind_wire, chain_key_type_wire, - private_key::key_type::r1); - add_provider("uic-em", chain_kind_wire, chain_key_type_ethereum, - private_key::key_type::em); - add_provider("uic-ed", chain_kind_wire, chain_key_type_solana, - private_key::key_type::ed); - add_provider("wire-bls", chain_kind_wire, chain_key_type_wire_bls, - private_key::key_type::bls); - add_provider("ethereum-transaction", chain_kind_ethereum, - chain_key_type_ethereum, private_key::key_type::em); - - const auto wire_target_providers = - manager.query_providers(std::nullopt, chain_kind_wire); - const auto uic_providers = - sysio::underwriter_detail::select_uic_signature_providers( - wire_target_providers, - [](const std::string&) { return true; }, - [](const public_key&) { return true; }); - - BOOST_REQUIRE_EQUAL(5u, wire_target_providers.size()); - BOOST_REQUIRE_EQUAL(4u, uic_providers.size()); - std::set selected_types; - for (const auto& provider : uic_providers) { - selected_types.insert(provider->public_key.type()); - BOOST_CHECK(provider->target_chain == chain_kind_wire); - } - const std::set expected_types{ - public_key::key_type::k1, - public_key::key_type::r1, - public_key::key_type::em, - public_key::key_type::ed, - }; - BOOST_CHECK(selected_types == expected_types); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_excludes_unrelated_wire_signer) try { - using namespace fc::crypto; - const auto underwriter_key = private_key::generate(private_key::key_type::em); - const auto block_signing_key = private_key::generate(private_key::key_type::k1); - - auto create_provider = [](std::string name, chain_key_type_t key_type, - const private_key& key) { - auto provider = std::make_shared(); - provider->target_chain = chain_kind_wire; - provider->key_type = key_type; - provider->key_name = std::move(name); - provider->public_key = key.get_public_key(); - provider->sign = [key](const fc::sha256& digest) { return key.sign(digest); }; - return provider; - }; - - const std::vector providers{ - create_provider("uic-em", chain_key_type_ethereum, underwriter_key), - create_provider("block-k1", chain_key_type_wire, block_signing_key), - }; - const auto selected = sysio::underwriter_detail::select_uic_signature_providers( - providers, - [](const std::string&) { return true; }, - [&](const public_key& key) { - return key == underwriter_key.get_public_key(); - }); - - BOOST_REQUIRE_EQUAL(1u, selected.size()); - BOOST_CHECK_EQUAL("uic-em", selected.front()->key_name); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_ignores_default_for_explicit_em) try { - check_operator_configured_uic_provider_beats_wire_default( - "uic-em", fc::crypto::chain_key_type_ethereum, - fc::crypto::private_key::key_type::em); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_uses_explicit_k1_after_default_suppression) try { - check_operator_configured_uic_provider_beats_wire_default( - "uic-k1", fc::crypto::chain_key_type_wire, - fc::crypto::private_key::key_type::k1, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_uses_explicit_r1_after_default_suppression) try { - check_operator_configured_uic_provider_beats_wire_default( - "uic-r1", fc::crypto::chain_key_type_wire, - fc::crypto::private_key::key_type::r1, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_ignores_default_for_explicit_ed) try { - check_operator_configured_uic_provider_beats_wire_default( - "uic-ed", fc::crypto::chain_key_type_solana, - fc::crypto::private_key::key_type::ed); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_accepts_anonymous_em) try { - check_operator_configured_uic_provider_beats_wire_default( - "ignored-em", fc::crypto::chain_key_type_ethereum, - fc::crypto::private_key::key_type::em, true, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_accepts_anonymous_k1) try { - check_operator_configured_uic_provider_beats_wire_default( - "ignored-k1", fc::crypto::chain_key_type_wire, - fc::crypto::private_key::key_type::k1, false, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_accepts_anonymous_r1) try { - check_operator_configured_uic_provider_beats_wire_default( - "ignored-r1", fc::crypto::chain_key_type_wire, - fc::crypto::private_key::key_type::r1, false, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_accepts_anonymous_ed) try { - check_operator_configured_uic_provider_beats_wire_default( - "ignored-ed", fc::crypto::chain_key_type_solana, - fc::crypto::private_key::key_type::ed, true, false); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(uic_provider_selection_preserves_two_explicit_ambiguity) try { - using namespace fc::crypto; - auto reset_app = gsl_lite::finally([]() { - appbase::application::reset_app_singleton(); - }); - const auto config_dir = std::filesystem::temp_directory_path() - / "underwriter-uic-provider-ambiguity"; - std::error_code ec; - std::filesystem::remove_all(config_dir, ec); - std::filesystem::create_directories(config_dir); - auto cleanup = gsl_lite::finally([&]() { - std::error_code ignored; - std::filesystem::remove_all(config_dir, ignored); - }); - - std::vector args{ - "test_underwriter_plugin", - "--config-dir", config_dir.string(), - "--signature-provider", - explicit_uic_provider_spec("uic-em", chain_key_type_ethereum, - private_key::key_type::em), - "--signature-provider", - explicit_uic_provider_spec("uic-ed", chain_key_type_solana, - private_key::key_type::ed), - }; - std::vector argv; - argv.reserve(args.size()); - for (auto& arg : args) argv.push_back(arg.data()); - - appbase::scoped_app test_app; - BOOST_REQUIRE(test_app->initialize( - argv.size(), argv.data())); - auto& manager = test_app->get_plugin(); - manager.register_default_signature_providers({chain_key_type_wire}); - - const auto selected = sysio::underwriter_detail::select_uic_signature_providers( - manager.query_providers(std::nullopt, chain_kind_wire), - [&](const std::string& name) { - return manager.is_operator_configured_provider(name); - }, [](const public_key&) { return true; }); - BOOST_REQUIRE_EQUAL(2u, selected.size()); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(stored_commit_plan_leaves_complete_candidate_for_external_replay) try { - const auto plan = sysio::underwriter_detail::plan_stored_commits( - /*candidate_exists=*/true, - /*intent_submitted=*/true, - /*source_is_depot=*/false, - /*destination_is_depot=*/false, - /*source_uic_stored=*/true, - /*destination_uic_stored=*/true); - BOOST_CHECK(!plan.skip_candidate); - BOOST_CHECK(!plan.submit_source); - BOOST_CHECK(!plan.submit_destination); - BOOST_CHECK(!sysio::underwriter_detail::has_submission_work( - plan, /*source_confirmed_locally=*/false, - /*destination_confirmed_locally=*/false)); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(stored_commit_plan_submits_only_missing_outpost_leg) try { - const auto plan = sysio::underwriter_detail::plan_stored_commits( - /*candidate_exists=*/true, - /*intent_submitted=*/true, - /*source_is_depot=*/false, - /*destination_is_depot=*/false, - /*source_uic_stored=*/true, - /*destination_uic_stored=*/false); - BOOST_CHECK(!plan.skip_candidate); - BOOST_CHECK(!plan.submit_source); - BOOST_CHECK(plan.submit_destination); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(stored_commit_plan_handles_single_outpost_candidate) try { - const auto ready = sysio::underwriter_detail::plan_stored_commits( - true, true, false, true, true, false); - BOOST_CHECK(!ready.skip_candidate); - BOOST_CHECK(!ready.submit_source); - BOOST_CHECK(!ready.submit_destination); - BOOST_CHECK(!sysio::underwriter_detail::has_submission_work( - ready, /*source_confirmed_locally=*/false, - /*destination_confirmed_locally=*/false)); - - const auto missing = sysio::underwriter_detail::plan_stored_commits( - true, true, false, true, false, false); - BOOST_CHECK(!missing.skip_candidate); - BOOST_CHECK(missing.submit_source); - BOOST_CHECK(!missing.submit_destination); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(stored_commit_plan_skips_disqualified_candidate_after_restart) try { - const auto disqualified = sysio::underwriter_detail::plan_stored_commits( - /*candidate_exists=*/true, - /*intent_submitted=*/false, - /*source_is_depot=*/false, - /*destination_is_depot=*/false, - /*source_uic_stored=*/true, - /*destination_uic_stored=*/true); - BOOST_CHECK(disqualified.skip_candidate); - BOOST_CHECK(!disqualified.submit_source); - BOOST_CHECK(!disqualified.submit_destination); - - const auto absent = sysio::underwriter_detail::plan_stored_commits( - /*candidate_exists=*/false, - /*intent_submitted=*/false, - /*source_is_depot=*/false, - /*destination_is_depot=*/false, - /*source_uic_stored=*/false, - /*destination_uic_stored=*/false); - BOOST_CHECK(!absent.skip_candidate); - BOOST_CHECK(absent.submit_source); - BOOST_CHECK(absent.submit_destination); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_state_advances_across_pages) try { - sysio::underwriter::solana_source_deposit_scan_cursor_map cursors; - const sysio::underwriter::solana_source_deposit_scan_key key{ - .uwreq_id = 42, - .deposit_id = 7, - }; - - const auto initial = - sysio::underwriter::get_or_create_solana_source_deposit_scan_cursor(cursors, key); - BOOST_CHECK(!initial.before); - BOOST_CHECK_EQUAL(initial.pages_scanned, 0); - BOOST_CHECK_EQUAL(initial.signatures_scanned, 0); - - const auto first_page = sysio::underwriter::advance_solana_source_deposit_scan_cursor( - cursors, key, "page-0-last-signature", sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - BOOST_REQUIRE(first_page.before); - BOOST_CHECK_EQUAL(*first_page.before, "page-0-last-signature"); - BOOST_CHECK_EQUAL(first_page.pages_scanned, 1); - BOOST_CHECK_EQUAL(first_page.signatures_scanned, sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - - const auto second_page = sysio::underwriter::advance_solana_source_deposit_scan_cursor( - cursors, key, "page-1-last-signature", sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - BOOST_REQUIRE(second_page.before); - BOOST_CHECK_EQUAL(*second_page.before, "page-1-last-signature"); - BOOST_CHECK_EQUAL(second_page.pages_scanned, 2); - BOOST_CHECK_EQUAL(second_page.signatures_scanned, sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE * 2); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_state_records_terminal_failure_once) try { - sysio::underwriter::solana_source_deposit_scan_cursor_map cursors; - const sysio::underwriter::solana_source_deposit_scan_key key{ - .uwreq_id = 43, - .deposit_id = 8, - }; - - sysio::underwriter::advance_solana_source_deposit_scan_cursor( - cursors, key, "page-0-last-signature", sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE); - const auto first = sysio::underwriter::record_solana_source_deposit_terminal_failure( - cursors, key, "exhausted history", 17); - BOOST_CHECK(first.first_failure); - BOOST_CHECK(first.cursor.terminal_failure); - BOOST_CHECK_EQUAL(first.cursor.terminal_failure_reason, "exhausted history"); - BOOST_CHECK_EQUAL(first.cursor.pages_scanned, 2); - BOOST_CHECK_EQUAL(first.cursor.signatures_scanned, sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE + 17); - - const auto terminal = - sysio::underwriter::get_solana_source_deposit_terminal_failure(cursors, key); - BOOST_REQUIRE(terminal); - BOOST_CHECK_EQUAL(terminal->terminal_failure_reason, "exhausted history"); - - const auto repeated = sysio::underwriter::record_solana_source_deposit_terminal_failure( - cursors, key, "second failure should not replace first", 99); - BOOST_CHECK(!repeated.first_failure); - BOOST_CHECK_EQUAL(repeated.cursor.terminal_failure_reason, "exhausted history"); - BOOST_CHECK_EQUAL(repeated.cursor.pages_scanned, 2); - BOOST_CHECK_EQUAL(repeated.cursor.signatures_scanned, sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE + 17); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_state_prunes_resolved_uwreqs) try { - sysio::underwriter::solana_source_deposit_scan_cursor_map cursors; - const sysio::underwriter::solana_source_deposit_scan_key pending_key{ - .uwreq_id = 44, - .deposit_id = 9, - }; - const sysio::underwriter::solana_source_deposit_scan_key resolved_key{ - .uwreq_id = 45, - .deposit_id = 10, - }; - sysio::underwriter::advance_solana_source_deposit_scan_cursor( - cursors, pending_key, "pending-last-signature", 3); - sysio::underwriter::record_solana_source_deposit_terminal_failure( - cursors, resolved_key, "resolved terminal failure", 5); - - const std::unordered_set still_pending{pending_key.uwreq_id}; - sysio::underwriter::prune_solana_source_deposit_scan_cursors(cursors, still_pending); - - BOOST_CHECK(cursors.contains(pending_key)); - BOOST_CHECK(!cursors.contains(resolved_key)); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_finds_match_after_legacy_window) try { - const uint64_t deposit_id = 7; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + hash_hex(expected_hash); - - std::vector sigs; - std::map tx_by_sig; - for (size_t i = 0; i < 51; ++i) { - const std::string sig = "noise-" + std::to_string(i); - sigs.push_back(signature_entry(sig)); - tx_by_sig.emplace(sig, transaction_with_logs(program_logs(test_sol_program_id, {"Program log: unrelated"}))); - } - - const std::string target_sig = "target-source-deposit"; - sigs.push_back(signature_entry(target_sig)); - tx_by_sig.emplace(target_sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))); - - size_t fetch_count = 0; - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id, &fetch_count); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::matched); - BOOST_CHECK_EQUAL(result.matched_signature, target_sig); - BOOST_CHECK_EQUAL(fetch_count, 52); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_returns_next_before_for_full_clean_page) try { - const uint64_t deposit_id = 8; - const auto expected_hash = test_expected_hash(); - - std::vector sigs; - std::map tx_by_sig; - for (size_t i = 0; i < sysio::underwriter::SOL_SIGNATURE_SCAN_PAGE_SIZE; ++i) { - const std::string sig = "full-page-" + std::to_string(i); - sigs.push_back(signature_entry(sig)); - tx_by_sig.emplace(sig, transaction_with_logs(program_logs(test_sol_program_id, {"Program log: unrelated"}))); - } - - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::not_found); - BOOST_REQUIRE(result.next_before); - BOOST_CHECK_EQUAL(*result.next_before, "full-page-999"); - BOOST_CHECK(!result.page_exhausted); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_ignores_forged_marker_from_wrong_program) try { - const uint64_t deposit_id = 9; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + hash_hex(expected_hash); - const std::string sig = "forged-marker"; - - const std::vector sigs{signature_entry(sig)}; - const std::map tx_by_sig{ - {sig, transaction_with_logs(program_logs(attacker_program_id, {marker}))}, - }; - - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::not_found); - BOOST_REQUIRE(result.next_before); - BOOST_CHECK_EQUAL(*result.next_before, sig); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_defers_unfinalized_match_without_cursor_advance) try { - const uint64_t deposit_id = 10; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + hash_hex(expected_hash); - const std::string sig = "unfinalized-marker"; - - const std::vector sigs{signature_entry(sig, "confirmed")}; - const std::map tx_by_sig{ - {sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))}, - }; - - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::deferred); - BOOST_CHECK(!result.next_before); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_continues_after_fetch_error_to_find_match) try { - const uint64_t deposit_id = 11; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + hash_hex(expected_hash); - const std::string error_sig = "transient-fetch-error"; - const std::string target_sig = "target-after-fetch-error"; - const std::vector sigs{signature_entry(error_sig), signature_entry(target_sig)}; - const std::map tx_by_sig{ - {target_sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))}, - }; - const auto prefix = marker_prefix(deposit_id); - const sysio::underwriter::solana_source_deposit_page_scan_config config{ - .sol_program_id = test_sol_program_id, - .marker_prefix = prefix, - .recomputed_hash = expected_hash, - }; - - const auto result = sysio::underwriter::scan_solana_source_deposit_signature_page( - sigs, - [&](const std::string& sig) -> fc::variant { - if (sig == error_sig) { - FC_THROW_EXCEPTION(fc::exception, "transient fetch failure"); - } - return tx_by_sig.at(sig); - }, - config); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::matched); - BOOST_CHECK_EQUAL(result.matched_signature, target_sig); - BOOST_CHECK(!result.next_before); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_defers_fetch_error_without_cursor_advance) try { - const uint64_t deposit_id = 12; - const auto expected_hash = test_expected_hash(); - const std::string error_sig = "transient-fetch-error"; - const std::string clean_sig = "clean-after-fetch-error"; - const std::vector sigs{signature_entry(error_sig), signature_entry(clean_sig)}; - const std::map tx_by_sig{ - {clean_sig, transaction_with_logs(program_logs(test_sol_program_id, {"Program log: unrelated"}))}, - }; - const auto prefix = marker_prefix(deposit_id); - const sysio::underwriter::solana_source_deposit_page_scan_config config{ - .sol_program_id = test_sol_program_id, - .marker_prefix = prefix, - .recomputed_hash = expected_hash, - }; - - const auto result = sysio::underwriter::scan_solana_source_deposit_signature_page( - sigs, - [&](const std::string& sig) -> fc::variant { - if (sig == error_sig) { - FC_THROW_EXCEPTION(fc::exception, "transient fetch failure"); - } - return tx_by_sig.at(sig); - }, - config); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::deferred); - BOOST_CHECK(!result.next_before); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_hard_fails_malformed_marker_hash) try { - const uint64_t deposit_id = 13; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + "abc"; - const std::string sig = "malformed-marker"; - - const std::vector sigs{signature_entry(sig)}; - const std::map tx_by_sig{ - {sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))}, - }; - - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::hard_mismatch); - BOOST_CHECK(!result.next_before); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_hard_fails_partial_hex_parse) try { - const uint64_t deposit_id = 14; - const fc::crypto::keccak256 expected_zero_hash; - std::string malformed_zero_hash; - malformed_zero_hash.reserve(64); - for (size_t i = 0; i < 32; ++i) { - malformed_zero_hash += "0g"; - } - const auto marker = marker_prefix(deposit_id) + malformed_zero_hash; - const std::string sig = "partial-hex-marker"; - - const std::vector sigs{signature_entry(sig)}; - const std::map tx_by_sig{ - {sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))}, - }; - - const auto result = scan_test_page(sigs, tx_by_sig, expected_zero_hash, deposit_id); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::hard_mismatch); - BOOST_CHECK(!result.next_before); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_CASE(solana_source_deposit_scan_skips_failed_listing_transactions) try { - const uint64_t deposit_id = 15; - const auto expected_hash = test_expected_hash(); - const auto marker = marker_prefix(deposit_id) + hash_hex(expected_hash); - const std::string sig = "failed-listing"; - - const std::vector sigs{signature_entry(sig, "finalized", true)}; - const std::map tx_by_sig{ - {sig, transaction_with_logs(program_logs(test_sol_program_id, {marker}))}, - }; - - size_t fetch_count = 0; - const auto result = scan_test_page(sigs, tx_by_sig, expected_hash, deposit_id, &fetch_count); - - BOOST_CHECK(result.status == sysio::underwriter::solana_source_deposit_page_status::not_found); - BOOST_CHECK_EQUAL(fetch_count, 0); -} FC_LOG_AND_RETHROW(); - -// ── SwapDeposit correlation-hash preimage ────────────────────────────── -// -// Both outposts hash the user's accepted `target_amount` -// (`ReserveManagerLib.hashSwapDeposit` on EVM, -// `swap_correlation_hash` on SVM). The depot re-prices `dst_amount` at -// ingestion, so the two diverge whenever the caller's target is not -// exactly the depot's quote — which is the normal case, and precisely -// what `variance_tolerance_bps` exists to bound. - -/// The EVM leg of a real ETH→SOL underwritten swap, captured from a live -/// cluster run. `on_chain_swap_deposit_hash` is the `SwapDeposit` event's -/// hash as `ReserveManager` emitted it. -const std::vector production_depositor = - depositor_bytes("cf2d5b3cbb4d7bf04e3f7bfa8e27081b52191f91"); -constexpr uint64_t production_source_amount = 100000000; -constexpr uint64_t production_source_token_code = 23373212024832; -constexpr uint64_t production_source_reserve_code = 71615576876608; -constexpr uint64_t production_target_chain_code = 84606581215232; -constexpr uint64_t production_target_token_code = 84606560763904; -constexpr uint64_t production_target_reserve_code = 71615576876608; -constexpr uint64_t production_target_amount = 98039214; -constexpr uint64_t production_settlement_quote = 97747972; -constexpr uint32_t production_tolerance_bps = 50; -constexpr auto on_chain_swap_deposit_hash = - "fd8f16aad2443acf2847c6f49c41feac6f32feadc87682df25835ae328a76695"; - -/// Build the production preimage, varying only the destination amount. -sysio::underwriter::source_deposit_hash_input production_hash_input(uint64_t destination_amount) { - return { - .depositor = std::span{production_depositor}, - .source_amount = production_source_amount, - .source_token_code = production_source_token_code, - .source_reserve_code = production_source_reserve_code, - .target_chain_code = production_target_chain_code, - .target_token_code = production_target_token_code, - .target_reserve_code = production_target_reserve_code, - .target_amount = destination_amount, - .target_tolerance_bps = production_tolerance_bps, - }; -} - -/// The recomputed hash matches the outpost's only when the preimage carries -/// the caller's `target_amount`. -BOOST_AUTO_TEST_CASE(source_deposit_hash_binds_the_callers_target_amount) try { - const auto recomputed = sysio::underwriter::source_deposit_hash( - production_hash_input(production_target_amount)); - - BOOST_CHECK_EQUAL(hash_hex(recomputed), on_chain_swap_deposit_hash); -} FC_LOG_AND_RETHROW(); - -/// The depot's re-priced settlement quote is NOT what the outpost hashed; -/// feeding it in reproduces the divergence that stalls every UWREQ whose -/// target differs from the quote. -BOOST_AUTO_TEST_CASE(source_deposit_hash_rejects_the_depot_settlement_quote) try { - BOOST_REQUIRE_NE(production_settlement_quote, production_target_amount); - - const auto recomputed = sysio::underwriter::source_deposit_hash( - production_hash_input(production_settlement_quote)); - - BOOST_CHECK_NE(hash_hex(recomputed), on_chain_swap_deposit_hash); -} FC_LOG_AND_RETHROW(); - -BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/underwriter_plugin/test/test_underwriter_routing.cpp b/plugins/underwriter_plugin/test/test_underwriter_routing.cpp deleted file mode 100644 index 6569bf46b1..0000000000 --- a/plugins/underwriter_plugin/test/test_underwriter_routing.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include - -#include - -#include -#include -#include - -/** - * SEC-13 / WSA-027 regression tests for the underwriter plugin's exact-chain - * routing/accounting keys. - * - * The plugin must key its credit buckets and its local commit de-dup by the - * EXACT slug codes — `(chain_code, token_code[, reserve_code])` — NOT the - * coarse `(ChainKind, TokenKind)` VM family. Otherwise two active chains of the - * same family (e.g. two EVM outposts) collapse onto one key: their collateral - * merges into a single bucket and a confirmed commit on one suppresses the - * still-needed commit on the other. These tests pin the exact-keying on the - * pure helpers lifted into `routing_detail.hpp`. - * - * Boost.Test module is defined once in `test/main.cpp`; this file only adds a - * suite. - */ - -using namespace sysio::underwriter_detail; - -namespace { -// Stand-in `fc::slug_name` values. Real codes are packed `[A-Z0-9_]` (≤8 chars), -// but the helpers treat them as opaque `uint64`, so any distinct values -// exercise the keying. ETH and EVM2 model two DISTINCT chains of the SAME VM -// family (both EVM) — the exact scenario WSA-027 is about. -constexpr uint64_t ETH = 0xE1; // chain_code, e.g. "ETHEREUM" -constexpr uint64_t EVM2 = 0xE2; // a SECOND active EVM chain, e.g. "POLYGON" -constexpr uint64_t USDC = 0x70; // token_code -constexpr uint64_t PRIM = 0x91; // reserve_code, e.g. "PRIMARY" -constexpr uint64_t SEC2 = 0x92; // reserve_code, e.g. "SECOND" - -// A non-participating leg (no outpost, no bond) — the shape of a WIRE depot leg. -const leg_bond NO_LEG{{0, 0}, 0}; - -// Pre-built bucket keys. Named so they can appear inside BOOST_* macros: a bare -// braced `{ETH, USDC}` inside a macro argument is split on its comma by the -// preprocessor ("too many arguments to function-like macro invocation"). -const bucket_key B_ETH_USDC { ETH, USDC }; -const bucket_key B_EVM2_USDC{ EVM2, USDC }; -} // namespace - -BOOST_AUTO_TEST_SUITE(underwriter_routing_tests) - -// -- commit_key: exact-identity de-dup -- - -BOOST_AUTO_TEST_CASE(commit_key_distinguishes_same_kind_chains) { - // Two legs identical but for chain_code: a confirmed commit on ETH must NOT - // suppress the still-needed commit on a second EVM chain. - const commit_key on_eth { /*uwreq*/ 1, ETH, USDC, PRIM }; - const commit_key on_evm2{ /*uwreq*/ 1, EVM2, USDC, PRIM }; - BOOST_CHECK(on_eth != on_evm2); - - std::set confirmed; - confirmed.insert(on_eth); - BOOST_CHECK(confirmed.contains(on_eth)); - BOOST_CHECK(!confirmed.contains(on_evm2)); // the EVM2 leg is still open -} - -BOOST_AUTO_TEST_CASE(commit_key_distinguishes_reserves_on_same_chain_token) { - // Same (uwreq, chain, token) but different reserve = distinct legs (a - // same-(chain, token) swap with two reserves). - const commit_key prim{7, ETH, USDC, PRIM}; - const commit_key sec {7, ETH, USDC, SEC2}; - BOOST_CHECK(prim != sec); - - std::set confirmed{prim}; - BOOST_CHECK(confirmed.contains(prim)); - BOOST_CHECK(!confirmed.contains(sec)); -} - -BOOST_AUTO_TEST_CASE(commit_key_identical_legs_dedup) { - const commit_key a{3, ETH, USDC, PRIM}; - const commit_key b{3, ETH, USDC, PRIM}; - BOOST_CHECK(a == b); - std::set s; - s.insert(a); - s.insert(b); - BOOST_CHECK_EQUAL(s.size(), 1u); // same leg recorded once -} - -// -- credit buckets: per-exact-chain capacity -- - -BOOST_AUTO_TEST_CASE(same_kind_chains_have_independent_credit) { - // Underwriter holds 100 USDC collateral on EACH of two EVM chains. - credit_buckets credit{ - {B_ETH_USDC, 100}, - {B_EVM2_USDC, 100}, - }; - - // A 100 swap leg on EVM2 debits ONLY the EVM2 bucket; ETH is untouched. - const leg_bond on_evm2{B_EVM2_USDC, 100}; - BOOST_REQUIRE(try_debit_buckets(credit, on_evm2, NO_LEG)); - BOOST_CHECK_EQUAL(credit[B_EVM2_USDC], 0u); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 100u); // NOT shared/collapsed -} - -BOOST_AUTO_TEST_CASE(insufficient_on_one_chain_does_not_borrow_from_same_kind) { - // 10 on EVM2, 100 on ETH. A 100 leg on EVM2 must FAIL — it cannot borrow - // ETH's collateral just because both chains are EVM. (The pre-fix collapse - // merged them into one 110 bucket and would have wrongly succeeded.) - credit_buckets credit{ - {B_ETH_USDC, 100}, - {B_EVM2_USDC, 10}, - }; - const leg_bond on_evm2{B_EVM2_USDC, 100}; - BOOST_CHECK(!try_debit_buckets(credit, on_evm2, NO_LEG)); - // `remaining` untouched on failure. - BOOST_CHECK_EQUAL(credit[B_EVM2_USDC], 10u); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 100u); -} - -BOOST_AUTO_TEST_CASE(same_bucket_dual_leg_needs_combined_balance) { - // ERC20 → native on ONE chain: both legs share the (chain, token) bucket, - // so the single row must cover the SUM, not each leg independently. - credit_buckets credit{{B_ETH_USDC, 150}}; - const leg_bond src{B_ETH_USDC, 100}; - const leg_bond dst{B_ETH_USDC, 100}; // 200 > 150 → must fail - BOOST_CHECK(!try_debit_buckets(credit, src, dst)); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 150u); - - credit[B_ETH_USDC] = 200; - BOOST_REQUIRE(try_debit_buckets(credit, src, dst)); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 0u); -} - -BOOST_AUTO_TEST_CASE(same_bucket_dual_leg_sum_cannot_overflow) { - // Two same-bucket legs whose bond requirements sum past UINT64_MAX must be - // rejected, never wrapped to a small, coverable-looking value. Before the - // 128-bit combine, `UINT64_MAX + 1` folded to 0 and the request debited the - // bucket as if free. - constexpr uint64_t U64_MAX = std::numeric_limits::max(); - credit_buckets credit{{B_ETH_USDC, 100}}; - const leg_bond src{B_ETH_USDC, U64_MAX}; - const leg_bond dst{B_ETH_USDC, 1}; // U64_MAX + 1 wraps to 0 in 64-bit - BOOST_CHECK(!try_debit_buckets(credit, src, dst)); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 100u); // untouched on failure - - // Even a bucket holding the max possible uint64 balance cannot cover a draw - // that overflows: the row can never exceed UINT64_MAX < (U64_MAX + 1). - credit[B_ETH_USDC] = U64_MAX; - BOOST_CHECK(!try_debit_buckets(credit, src, dst)); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], U64_MAX); -} - -BOOST_AUTO_TEST_CASE(depot_leg_requires_no_bucket) { - // A to-WIRE swap has one real leg + one depot leg (require 0). The depot leg - // consults no bucket; only the real leg must cover. - credit_buckets credit{{B_ETH_USDC, 100}}; - const leg_bond real{B_ETH_USDC, 100}; - BOOST_REQUIRE(try_debit_buckets(credit, real, NO_LEG)); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 0u); - - // both-depot (degenerate) is rejected — there is nothing to underwrite. - BOOST_CHECK(!try_debit_buckets(credit, NO_LEG, NO_LEG)); -} - -BOOST_AUTO_TEST_CASE(every_eventual_outpost_lock_requires_credit) { - const bucket_key bucket{.chain_code = 101, .token_code = 202}; - const auto outpost = candidate_leg_bond( - bucket, 50, /*is_depot=*/false); - const auto depot = candidate_leg_bond( - bucket, 50, /*is_depot=*/true); - - BOOST_CHECK(outpost.bucket == bucket); - BOOST_CHECK_EQUAL(outpost.require, 50u); - BOOST_CHECK_EQUAL(depot.require, 0u); - - // Pre-validation budgets the full winner-time lock even when one leg's UIC - // is already stored or locally confirmed. Those states avoid another paid - // outpost submission, but neither has consumed collateral on the depot. - credit_buckets insufficient{{bucket, 49}}; - BOOST_CHECK(!try_debit_buckets(insufficient, outpost, depot)); - BOOST_CHECK_EQUAL(insufficient[bucket], 49u); - - credit_buckets exact{{bucket, 50}}; - BOOST_CHECK(try_debit_buckets(exact, outpost, depot)); - BOOST_CHECK_EQUAL(exact[bucket], 0u); -} - -BOOST_AUTO_TEST_CASE(fully_local_candidate_reserves_before_new_submission) { - const stored_commit_plan absent{ - .submit_source = true, - .submit_destination = true, - }; - BOOST_CHECK(!has_submission_work( - absent, /*source_confirmed_locally=*/true, - /*destination_confirmed_locally=*/true)); - - credit_buckets remaining{{B_ETH_USDC, 100}}; - reserve_buckets( - remaining, leg_bond{B_ETH_USDC, 100}, NO_LEG); - BOOST_CHECK_EQUAL(remaining[B_ETH_USDC], 0u); - BOOST_CHECK(!try_debit_buckets( - remaining, leg_bond{B_ETH_USDC, 1}, NO_LEG)); -} - -BOOST_AUTO_TEST_CASE(stored_complete_candidate_reserves_while_awaiting_external_replay) { - credit_buckets credit{{B_ETH_USDC, 100}}; - const leg_bond src{B_ETH_USDC, 40}; - const leg_bond dst{B_ETH_USDC, 60}; - - reserve_buckets(credit, src, dst); - BOOST_CHECK_EQUAL(credit[B_ETH_USDC], 0u); - BOOST_CHECK(!try_debit_buckets(credit, leg_bond{B_ETH_USDC, 1}, NO_LEG)); -} - -BOOST_AUTO_TEST_CASE(mixed_stored_local_candidate_reserves_full_bond) { - const auto partial = plan_stored_commits( - /*candidate_exists=*/true, - /*intent_submitted=*/true, - /*source_is_depot=*/false, - /*destination_is_depot=*/false, - /*source_uic_stored=*/true, - /*destination_uic_stored=*/false); - BOOST_CHECK(!has_submission_work( - partial, /*source_confirmed_locally=*/false, - /*destination_confirmed_locally=*/true)); - - credit_buckets remaining{{B_ETH_USDC, 100}}; - reserve_buckets( - remaining, leg_bond{B_ETH_USDC, 40}, leg_bond{B_ETH_USDC, 60}); - BOOST_CHECK_EQUAL(remaining[B_ETH_USDC], 0u); - BOOST_CHECK(!try_debit_buckets( - remaining, leg_bond{B_ETH_USDC, 1}, NO_LEG)); -} - -BOOST_AUTO_TEST_SUITE_END() diff --git a/plugins/underwriter_plugin/test/test_underwriter_sync.cpp b/plugins/underwriter_plugin/test/test_underwriter_sync.cpp deleted file mode 100644 index c8b55f8e69..0000000000 --- a/plugins/underwriter_plugin/test/test_underwriter_sync.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file test_underwriter_sync.cpp - * @brief Unit tests for the underwriter plugin's sync-gate lifecycle surface — - * the `/v1/underwriter/` gate payloads served until the deferred startup - * completes, and the terminal-failure classification behind the fail-fast - * shutdown. The sync predicate itself is the first-class chain_plugin - * gate; its tests live in `plugins/chain_plugin/test/test_sync_gate.cpp`. - */ -#include - -#include -#include - -BOOST_AUTO_TEST_SUITE(underwriter_sync_tests) - -// ── is_terminal_failure: the fail-fast trigger behind the node shutdown ── - -BOOST_AUTO_TEST_CASE(terminal_failure_states_are_exactly_the_failed_states) { - using sysio::underwriter_detail::is_terminal_failure; - using sysio::underwriter_detail::startup_state; - // Non-failures: the pre-arm wait, the bounded transient retry, and success. - BOOST_TEST(!is_terminal_failure(startup_state::waiting_for_sync)); - BOOST_TEST(!is_terminal_failure(startup_state::preflight_retrying)); - BOOST_TEST(!is_terminal_failure(startup_state::active)); - // Failures: each one must shut the node down (fail-fast). - BOOST_TEST(is_terminal_failure(startup_state::preflight_failed)); - BOOST_TEST(is_terminal_failure(startup_state::wiring_failed)); - BOOST_TEST(is_terminal_failure(startup_state::startup_failed)); -} - -// ── startup_gate_payload: the /v1/underwriter/* body served until the ── -// ── deferred startup body completes (registration is unconditional at ── -// ── plugin_startup, so these payloads are what a cold-boot query sees) ── -// -// Wire-format pinning: the static_asserts below pin each shared `field::` -// KEY constant to its literal wire spelling once (a typo'd or renamed -// constant would otherwise change the HTTP format with builder and tests -// agreeing tautologically); every assertion after that is free to use the -// constants. The `status` VALUES stay literal in the cases for the same -// reason — an enum-member rename that would silently change the wire format -// fails here. - -using sysio::underwriter_detail::startup_gate_payload; -using sysio::underwriter_detail::startup_state; -namespace field = sysio::underwriter_detail::field; - -static_assert(std::string_view{field::status} == "status"); -static_assert(std::string_view{field::head_behind_sec} == "head_behind_sec"); -static_assert(std::string_view{field::lib_behind_sec} == "lib_behind_sec"); -static_assert(std::string_view{field::detail} == "detail"); - -BOOST_AUTO_TEST_CASE(gate_payload_waiting_for_sync_reports_behind_gaps) { - const auto body = startup_gate_payload(startup_state::waiting_for_sync, - fc::seconds(204), fc::seconds(206)) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "waiting_for_sync"); - BOOST_TEST(body[field::head_behind_sec].as_int64() == 204); - BOOST_TEST(body[field::lib_behind_sec].as_int64() == 206); - BOOST_TEST(!body.contains(field::detail)); -} - -BOOST_AUTO_TEST_CASE(gate_payload_no_root_omits_lib_gap) { - // Before the fork database has a root the caller passes an empty optional - // and the key is omitted — an in-band sentinel would collide with real - // negative (clock-skew) gaps after whole-second truncation. - const auto body = startup_gate_payload(startup_state::waiting_for_sync, - fc::seconds(3), std::nullopt) - .get_object(); - BOOST_TEST(body[field::head_behind_sec].as_int64() == 3); - BOOST_TEST(!body.contains(field::lib_behind_sec)); -} - -BOOST_AUTO_TEST_CASE(gate_payload_passes_negative_skew_gaps_through) { - // A block time ahead of local wall clock (clock skew) yields a negative - // gap; it is reported as-is — the skew is itself diagnostic signal. - const auto body = startup_gate_payload(startup_state::waiting_for_sync, - fc::milliseconds(-1500), fc::milliseconds(-1500)) - .get_object(); - BOOST_TEST(body[field::head_behind_sec].as_int64() == -1); - BOOST_TEST(body[field::lib_behind_sec].as_int64() == -1); -} - -BOOST_AUTO_TEST_CASE(gate_payload_truncates_to_whole_seconds) { - // Durations travel as fc::microseconds; the JSON boundary emits whole - // seconds. - const auto body = startup_gate_payload(startup_state::waiting_for_sync, - fc::milliseconds(2500), fc::milliseconds(1999)) - .get_object(); - BOOST_TEST(body[field::head_behind_sec].as_int64() == 2); - BOOST_TEST(body[field::lib_behind_sec].as_int64() == 1); -} - -BOOST_AUTO_TEST_CASE(gate_payload_preflight_failed_carries_detail) { - const auto body = startup_gate_payload(startup_state::preflight_failed, - fc::microseconds(0), std::nullopt) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "preflight_failed"); - BOOST_TEST(body[field::detail].as_string() == - std::string{sysio::underwriter_detail::preflight_failed_detail}); - BOOST_TEST(!body.contains(field::head_behind_sec)); - BOOST_TEST(!body.contains(field::lib_behind_sec)); -} - -BOOST_AUTO_TEST_CASE(gate_payload_wiring_failed_carries_detail) { - const auto body = startup_gate_payload(startup_state::wiring_failed, - fc::microseconds(0), std::nullopt) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "wiring_failed"); - BOOST_TEST(body[field::detail].as_string() == - std::string{sysio::underwriter_detail::wiring_failed_detail}); - BOOST_TEST(!body.contains(field::head_behind_sec)); - BOOST_TEST(!body.contains(field::lib_behind_sec)); -} - -BOOST_AUTO_TEST_CASE(gate_payload_startup_failed_carries_detail) { - // The catch-all terminal state: the deferred startup body threw past the - // specific preflight/wiring failure paths. - const auto body = startup_gate_payload(startup_state::startup_failed, - fc::microseconds(0), std::nullopt) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "startup_failed"); - BOOST_TEST(body[field::detail].as_string() == - std::string{sysio::underwriter_detail::startup_failed_detail}); - BOOST_TEST(!body.contains(field::head_behind_sec)); - BOOST_TEST(!body.contains(field::lib_behind_sec)); -} - -BOOST_AUTO_TEST_CASE(gate_payload_preflight_retrying_is_status_only) { - const auto body = - startup_gate_payload(startup_state::preflight_retrying, fc::microseconds(0), std::nullopt) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "preflight_retrying"); - BOOST_TEST(body.size() == 1u); -} - -BOOST_AUTO_TEST_CASE(gate_payload_active_is_status_only) { - const auto body = startup_gate_payload(startup_state::active, - fc::seconds(99), fc::seconds(99)) - .get_object(); - BOOST_TEST(body[field::status].as_string() == "active"); - BOOST_TEST(body.size() == 1u); -} - -// `active_status` is pinned at compile time next to its definition in -// sync_detail.hpp (static_assert), so no runtime case is needed for it. - -BOOST_AUTO_TEST_SUITE_END() diff --git a/programs/nodeop/main.cpp b/programs/nodeop/main.cpp index e96ed19b41..c893279b3f 100644 --- a/programs/nodeop/main.cpp +++ b/programs/nodeop/main.cpp @@ -20,7 +20,6 @@ #include #include #include -#include using namespace appbase; using namespace sysio; @@ -47,7 +46,6 @@ int main(int argc, char** argv) application_base::register_plugin(); application_base::register_plugin(); application_base::register_plugin(); - application_base::register_plugin(); auto r = exe.init< resource_monitor_plugin, chain_plugin, diff --git a/tests/fixtures/solana-idl-opp-outpost-stub.json b/tests/fixtures/solana-idl-opp-outpost-stub.json index 69451a536d..ef645d4cbb 100644 --- a/tests/fixtures/solana-idl-opp-outpost-stub.json +++ b/tests/fixtures/solana-idl-opp-outpost-stub.json @@ -162,58 +162,6 @@ } ] }, - { - "name": "deposit", - "discriminator": [ - 242, - 35, - 198, - 137, - 82, - 225, - 242, - 182 - ], - "accounts": [ - { - "name": "depositor", - "writable": true, - "signer": true - }, - { - "name": "config" - }, - { - "name": "operator_registry", - "writable": true - }, - { - "name": "outbound_message_buffer", - "writable": true - }, - { - "name": "vault", - "writable": true - }, - { - "name": "system_program" - } - ], - "args": [ - { - "name": "operator_type", - "type": "u32" - }, - { - "name": "token_code", - "type": "u64" - }, - { - "name": "amount", - "type": "u64" - } - ] - }, { "name": "cleanup_envelope_chunks", "discriminator": [ @@ -298,14 +246,6 @@ "name": "latest_outbound_envelope", "writable": true }, - { - "name": "vault", - "writable": true - }, - { - "name": "reserve_aggregate", - "writable": true - }, { "name": "system_program" } @@ -416,19 +356,6 @@ 182 ] }, - { - "name": "Reserve", - "discriminator": [ - 43, - 242, - 204, - 202, - 26, - 247, - 59, - 127 - ] - }, { "name": "OperatorRegistry", "discriminator": [ @@ -480,6 +407,19 @@ 4, 103 ] + }, + { + "name": "LatestOutboundEnvelope", + "discriminator": [ + 74, + 80, + 163, + 159, + 178, + 236, + 249, + 15 + ] } ], "types": [ @@ -574,88 +514,6 @@ ] } }, - { - "name": "Reserve", - "type": { - "kind": "struct", - "fields": [ - { - "name": "bump", - "type": "u8" - }, - { - "name": "token_code", - "type": "u64" - }, - { - "name": "reserve_code", - "type": "u64" - }, - { - "name": "external_token_amount", - "type": "u64" - }, - { - "name": "requested_wire_amount", - "type": "u64" - }, - { - "name": "connector_weight_bps", - "type": "u32" - }, - { - "name": "status", - "type": { - "defined": { - "name": "ReserveStatus" - } - } - }, - { - "name": "creator", - "type": "pubkey" - }, - { - "name": "custody_mint", - "type": "pubkey" - }, - { - "name": "custody_decimals", - "type": "u8" - }, - { - "name": "custody_token_program", - "type": "pubkey" - }, - { - "name": "name_len", - "type": "u8" - }, - { - "name": "name_bytes", - "type": { - "array": [ - "u8", - 64 - ] - } - }, - { - "name": "description_len", - "type": "u16" - }, - { - "name": "description_bytes", - "type": { - "array": [ - "u8", - 256 - ] - } - } - ] - } - }, { "name": "OperatorDelivery", "type": { @@ -840,6 +698,37 @@ } ] } + }, + { + "name": "LatestOutboundEnvelope", + "type": { + "kind": "struct", + "fields": [ + { + "name": "bump", + "type": "u8" + }, + { + "name": "epoch_index", + "type": "u32" + }, + { + "name": "checksum", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "data", + "type": { + "vec": "u8" + } + } + ] + } } ] } From 32669e1128fe615b8eb78fe759d9c530ba2263e9 Mon Sep 17 00:00:00 2001 From: Jonathan Glanz Date: Tue, 6 Oct 2026 13:55:28 -0400 Subject: [PATCH 3/5] msgch: stop outbound-only attestations falling into node-owner dispatch In dispatch_attestation, SWAP_REVERT, DEPOSIT_REVERT, OPERATORS and BATCH_OPERATOR_GROUPS shared case labels with NODE_OWNER_REG and had no break, so an inbound payload of any of those types from the node-owner source outpost was decoded and processed as a NodeOwnerRegistration. They now break on their own; only NODE_OWNER_REG reaches dispatch_node_owner_reg. Adds outbound_only_types_never_reach_node_owner_dispatch: a well-formed NodeOwnerRegistration delivered under each outbound-only type leaves no owner or audit row (fails against the previous wasm, passes now). contracts_unit_test --sys-vm: no errors. Co-Authored-By: Claude Opus 5.5 (1M context) Change-Id: Ie86a40c9656dc2e433df3e90b817351758012c0f --- contracts/sysio.msgch/src/sysio.msgch.cpp | 8 +++--- contracts/sysio.msgch/sysio.msgch.wasm | Bin 177916 -> 148850 bytes contracts/tests/sysio.dispatch_tests.cpp | 30 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index 3c01a35222..dacb318981 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -827,14 +827,14 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) // task alongside liqEth / liqsol-token wiring. break; - // Outbound-only types (depot emits these, never receives them inbound) - // and deprecated pre-launch types are dropped silently. SLASH was - // formerly its own attestation type; it now rides on OPERATOR_ACTION - // with action_type=SLASH and is gated inside `dispatch_operator_action`. + // 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; + case AttestationType::ATTESTATION_TYPE_NODE_OWNER_REG: // NFT node-owner claim: create the account + register + record the ETH link. Self-contained // (decodes NodeOwnerRegistration, inline-sends sysio.roa); soft-drops a malformed envelope. diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 42e3a7677a7b10980cdd1702746a06b82f858045..f7ab14b0232fa7893a27ed7dadec86fe7021a813 100755 GIT binary patch literal 148850 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 diff --git a/contracts/tests/sysio.dispatch_tests.cpp b/contracts/tests/sysio.dispatch_tests.cpp index 20f3c5fa07..6f54e5f4aa 100644 --- a/contracts/tests/sysio.dispatch_tests.cpp +++ b/contracts/tests/sysio.dispatch_tests.cpp @@ -1033,6 +1033,36 @@ BOOST_FIXTURE_TEST_CASE(dispatch_routes_node_owner_reg_to_roa, sysio_dispatch_te BOOST_REQUIRE_EQUAL(pending["balance"].as().get_amount(), 4321); } 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 +// 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 { + bootstrap_for_dispatch("ETHEREUM"); + + const auto eth_code = fc::slug_name{"ETHEREUM"}.value; + auto wire_key = k1_pubkey_bytes(get_public_key(CLAIM_ACCOUNT, "active")); + auto eth_pub = fc::crypto::private_key::generate(fc::crypto::private_key::key_type::em).get_public_key(); + auto eth_bytes = em_uncompressed_pubkey_bytes(eth_pub); + auto eth_address = fc::crypto::ethereum::address_to_bytes(eth_pub); + const auto payload = encode_node_owner_registration( + CLAIM_ACCOUNT.to_string(), /*tier=*/2, + 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}) { + entries.emplace_back(type, payload); + } + const auto trace = deliver_trace(eth_code, + encode_envelope_with_mixed_attestations(current_epoch(), entries)); + BOOST_REQUIRE(trace != nullptr); + BOOST_REQUIRE(!trace->except); + + BOOST_CHECK(get_nodeowner(CLAIM_ACCOUNT).is_null()); + BOOST_CHECK(get_nodeownerreg(CLAIM_ACCOUNT).is_null()); +} FC_LOG_AND_RETHROW() } + /// One slot and two fresh-name claims must commit the first, reject the second, and keep epochs moving. BOOST_FIXTURE_TEST_CASE(dispatch_node_owner_tier_cap_preserves_epoch_progress, sysio_dispatch_tester) { try { namespace owners = sysio_system::test_support::nodeowners; From 8b55f06aa24e612c013310fcfe5084491d04ed0c Mon Sep 17 00:00:00 2001 From: Jonathan Glanz Date: Tue, 6 Oct 2026 13:55:28 -0400 Subject: [PATCH 4/5] docs: align with the syndication feature set OVERVIEW, PLUGINS, contract READMEs, EMISSIONS.md, docs/ guides and plugin READMEs now describe the current set: liq syndication (sysio.synd / sysio.bond / sysio.liq), node-owner registration, depot-native sysio.opreg collateral, sysio.chalg envelope disputes, sysio.andon, and the depot-local sysio.swap AMM. sysio.reserv, sysio.uwrit and underwriter_plugin are described only as removed. Co-Authored-By: Claude Opus 5.5 (1M context) Change-Id: I2074697b965b1af3181e70ab0ed12f57027600ed --- OVERVIEW.md | 124 ++++++----- PLUGINS.md | 3 +- contracts/sysio.chalg/README.md | 5 +- contracts/sysio.epoch/README.md | 61 ++++-- contracts/sysio.msgch/README.md | 76 ++++--- contracts/sysio.system/EMISSIONS.md | 29 +-- docs/becoming-a-block-producer.md | 63 +++--- docs/contract-upgrade-order.md | 133 ++++++------ docs/emergency-stop-playbook.md | 39 ++-- docs/opp-two-phase-dispatch-design.md | 20 +- docs/platform-bootstrap-config.md | 198 +++++++----------- plugins/batch_operator_plugin/README.md | 34 +-- plugins/chain_interface/README.md | 14 +- plugins/chain_plugin/README.md | 6 +- plugins/cron_plugin/README.md | 10 +- plugins/external_debugging_plugin/README.md | 2 - plugins/http_plugin/README.md | 7 +- plugins/outpost_client_plugin/README.md | 6 +- .../outpost_ethereum_client_plugin/README.md | 36 ++-- .../outpost_solana_client_plugin/README.md | 25 ++- .../README.md | 8 +- 21 files changed, 433 insertions(+), 466 deletions(-) diff --git a/OVERVIEW.md b/OVERVIEW.md index 88d9644cd9..3f38fc3b21 100644 --- a/OVERVIEW.md +++ b/OVERVIEW.md @@ -2,16 +2,16 @@ ## Wire Network -Wire Network is a multi-chain consensus and interoperability platform built on the AntelopeIO protocol. It enables secure, trustless cross-chain communication through a specialized consensus mechanism and unified proof protocol. The network connects the Wire blockchain (the coordination layer) with external chains — Ethereum, Solana, and Sui — via on-chain **Outpost** contracts deployed on each supported chain. +Wire Network is a multi-chain consensus and interoperability platform built on the AntelopeIO protocol. It enables secure, trustless cross-chain communication through a specialized consensus mechanism and unified proof protocol. The network connects the Wire blockchain (the coordination layer, or **depot**) with external chains — Ethereum (EVM) and Solana (SVM) — via on-chain **Outpost** contracts deployed on each supported chain. ### Architecture at a Glance | Layer | Components | |-------|-----------| -| **Wire Blockchain (Depot)** | Block producers, system contracts (`sysio.msgch`, `sysio.epoch`, `sysio.uwrit`, `sysio.chalg`) | -| **Operator Network** | 21 batch operators (3 groups of 7), underwriters | -| **External Chains** | Outpost smart contracts on Ethereum, Solana, Sui | -| **Protocol Layer** | OPP — protobuf-encoded messages, merkle proofs, multi-sig consensus | +| **Wire Blockchain (Depot)** | Block producers, system contracts (`sysio.msgch`, `sysio.epoch`, `sysio.opreg`, `sysio.chalg`, `sysio.synd`, `sysio.bond`, `sysio.liq`, `sysio.swap`, `sysio.dclaim`, `sysio.andon`, …) | +| **Operator Network** | Batch operators relaying OPP envelopes in rotating groups (default 21 operators, 3 groups of 7); bond providers underwriting syndication statements on the depot | +| **External Chains** | Outpost contracts on Ethereum and Solana | +| **Protocol Layer** | OPP — protobuf-encoded envelopes, per-outpost envelope hash chains, group consensus | --- @@ -20,23 +20,23 @@ Wire Network is a multi-chain consensus and interoperability platform built on t OPP is the cryptographic wire protocol that enables deterministic, consensus-backed proof transmission across blockchains. It uses Protocol Buffers for serialization and defines a hierarchical message structure: ``` -Envelope (epoch-spanning container) - └─ Message (individual message within an epoch) +Envelope (one per outpost per epoch, per direction) + └─ Message (individual message within an envelope) └─ Attestation (typed data entry within a message) ``` ### Key Concepts -- **Epochs**: Time-bounded intervals managed by `sysio.epoch`. Each epoch assigns one group of 7 batch operators as active relayers. -- **Envelopes**: Epoch-level containers carrying a merkle root, epoch index, timestamp, and the range of messages they cover. -- **Messages**: Individual cross-chain payloads containing a header (message ID, checksum, encoding flags) and a payload (an array of attestation entries). -- **Attestations**: Typed data units that represent cross-chain actions — swaps, stake updates, operator actions, underwriting intents, and more. +- **Epochs**: Time-bounded intervals managed by `sysio.epoch` — one global epoch clock for every chain. Each epoch has one group of batch operators on duty as relayers. +- **Envelopes**: Epoch-level containers. Each envelope carries the epoch index and timestamp, its route endpoints, and `previous_envelope_hash` — the digest of the previous envelope on the same (depot, outpost) stream — so each direction forms a hash chain. An envelope is capped at 32,768 bytes on every chain. +- **Messages**: Individual cross-chain payloads containing a header (message ID, sequence, checksum, encoding flags) and a payload (an array of attestation entries). +- **Attestations**: Typed data units that represent cross-chain actions — operator rosters, liquidity syndication and desyndication, yield reports, node-owner registrations, staking rewards. ### Chain Addressing OPP uses a universal chain-agnostic address system: -- **ChainKind** enum: `WIRE`, `ETHEREUM`, `SOLANA`, `SUI` +- **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 @@ -48,53 +48,55 @@ This allows a single unified message format across all supported chains. ### Inbound (External Chain → Wire) -1. A user action on an external chain (e.g., depositing ETH on the Ethereum Outpost) triggers an `OPPMessage` event containing protobuf-encoded attestations. -2. All 7 active batch operators independently observe the event via chain-specific client plugins (`outpost_ethereum_client_plugin`, `outpost_solana_client_plugin`). +1. A user action on an external chain (e.g. syndicating liqETH into the Ethereum `SyndicationPool`) queues a protobuf-encoded attestation in the outpost's outbound buffer, which the outpost seals into its envelope for the epoch. +2. Every batch operator in the active group independently reads that envelope through its chain-specific client plugin (`outpost_ethereum_client_plugin`, `outpost_solana_client_plugin`). 3. Each operator delivers the envelope to Wire's `sysio.msgch` contract via the `deliver()` action. -4. `sysio.msgch` evaluates consensus — when all operators in the active group submit identical envelope checksums, the messages are accepted. -5. Attestations are extracted, parsed, and routed to the appropriate system contract handlers. +4. `sysio.msgch` evaluates consensus: every eligible operator delivering an identical envelope is accepted immediately; otherwise a strict majority is accepted once the epoch boundary has passed. A split with no strict majority opens a `sysio.chalg` dispute. +5. Attestations are extracted, validated, and routed to the appropriate system contract handlers. ### Outbound (Wire → External Chain) 1. System contracts queue attestations for a target outpost via `sysio.msgch::queueout()`. -2. When the epoch advances, `buildenv()` packs all `READY` attestations into a protobuf-encoded outbound envelope with a merkle root. -3. Batch operators read the outbound envelope and submit it to the target chain's Outpost contract. -4. The Outpost contract (`OPPInbound.sol` on Ethereum) verifies consensus, checks merkle proofs, and routes attestations to registered handler contracts. +2. When the epoch advances, `buildenv()` packs the `READY` attestations into a protobuf-encoded outbound envelope; anything that does not fit stays queued for the next epoch. +3. Batch operators read the outbound envelope and submit it to the target chain's Outpost — chunked on both chains (`OPPInbound.epochIn` on Ethereum, `epoch_in` on Solana). +4. The Outpost verifies the envelope chain and group consensus, then routes attestations to their registered handlers. An unhandled type is skipped, never reverted. --- ## Key Attestation Types -| Category | Types | Purpose | -|----------|-------|---------| -| **Operator Management** | `OPERATOR_ACTION`, `BATCH_OPERATOR_NEXT_GROUP`, `SLASH_OPERATOR` | Operator registration, rotation, penalties | -| **Staking** | `STAKING_REWARD` | Per-staker cross-chain yield distribution (validator-staking lifecycle types are reserved but deferred post-launch) | -| **Swaps & Liquidity** | `SWAP`, `UNDERWRITE_INTENT`, `UNDERWRITE_CONFIRM`, `REMIT` | Cross-chain swaps backed by underwriter collateral | -| **Governance** | `CHALLENGE_REQUEST`, `CHALLENGE_RESPONSE`, `EPOCH_SYNC`, `ROSTER_UPDATE` | Dispute resolution and epoch synchronization | -| **Reserves** | `RESERVE_BALANCE_SHEET` | Collateral snapshots across chains | +| Category | Types | Direction | Purpose | +|----------|-------|-----------|---------| +| **Operator Rosters** | `OPERATORS`, `BATCH_OPERATOR_GROUPS` | depot → outpost | Full operator roster and the next epoch's batch-operator group, sent every epoch | +| **Liquidity Syndication** | `SYNDICATE_LIQ`, `LIQ_YIELD` | outpost → depot | A user syndicating liq tokens into outpost custody; realized custody yield | +| **Liquidity Desyndication** | `DESYNDICATE_LIQ` | depot → outpost | Return syndicated liq tokens to a holder on the outpost | +| **Node Owners** | `NODE_OWNER_REG` | outpost → depot | Node-owner NFT committed on Ethereum, registering the owner on the depot | +| **Staking** | `STAKING_REWARD` | outpost → depot | Per-staker reward credited to the `sysio.dclaim` claim ledger (validator-staking lifecycle types are reserved but deferred) | + +Cross-chain swaps routed through outposts, outpost reserves and outpost-side operator collateral have been removed. Swaps are a depot-local AMM (`sysio.swap`) with no outpost participation; operator collateral is held on the depot by `sysio.opreg`. --- ## Participant Roles ### Block Producers -Elected via Appointed Proof of Stake (APoS). They validate transactions and produce blocks on the Wire blockchain — the standard Antelope producer role. +Elected via Appointed Proof of Stake (APoS). They validate transactions and produce blocks on the Wire blockchain — the standard Antelope producer role. Producers register with `sysio.opreg` and post depot-native collateral there. ### Batch Operators -21 operators split into 3 groups of 7. One group is active per epoch. They run the `batch_operator_plugin` and are responsible for: -- Observing events on external chain Outposts -- Delivering inbound envelopes to `sysio.msgch` -- Building and submitting outbound envelopes to external chains -- Responding to challenges if disputed +Operators scheduled in groups of `operators_per_epoch` (default 21 operators in 3 groups of 7); one group is on duty per epoch, and the schedule slides forward every epoch. They run the `batch_operator_plugin` and are responsible for: +- Reading each outpost's outbound envelope and delivering it to `sysio.msgch` +- Relaying the depot's outbound envelopes to each outpost +- Cranking the depot: `sysio.msgch::chkcons` (epoch advance), `sysio.chalg::chkdispute`, `sysio.swap::tickyield`, `sysio.liq::queueyield` + +Batch operators post depot-native collateral on `sysio.opreg`; a non-canonical delivery is slashed and missed deliveries lead to termination. Signing keys for outbound submissions can be held locally (`KEY:`) or in a `kiod` daemon (`KIOD:`) — both built into `signature_provider_manager_plugin`. AWS KMS support (`KMS:` — secp256k1/ethereum keys only, remote signing so the key never leaves KMS) is provided by `signature_provider_kms_plugin`, enabled per config with `plugin = sysio::signature_provider_kms_plugin`. See `plugins/signature_provider_kms_plugin/test/README.md` for KMS key setup, IAM requirements, the `KMS:` spec format, and operational notes. AWS SSM Parameter Store support (`SSM:` — all key types) is `signature_provider_ssm_plugin`, enabled with `plugin = sysio::signature_provider_ssm_plugin`: the private key is fetched from a KMS-encrypted `SecureString` parameter once at startup and signing is local thereafter, fast enough for every signing path including block production. See `plugins/signature_provider_ssm_plugin/test/README.md` for parameter setup, IAM requirements, the `SSM:` spec format, and rotation notes. A `KMS:`/`SSM:` spec whose plugin is not enabled fails the boot with an error naming the `plugin =` line to add; host applications that embed the manager without the plugins can still register handlers from `main()` via `register_spec_handler()`. -### Underwriters -Independent operators running the `underwriter_plugin`. They provide collateral-backed liquidity for cross-chain swaps: -1. Monitor `sysio.msgch` for `SWAP` attestations -2. Verify deposits on external chains -3. Submit underwriting intent to `sysio.uwrit` with locked collateral -4. Earn fees on successful settlement; face slashing on failure +### Bond Providers (Underwriters) +Depot accounts that underwrite provable statements on `sysio.bond` with depot-native tokens (WIRE or shadow LIQ). Today the issuer is `sysio.synd`: each syndication envelope is released only after its statement is bonded. A bond provider accepts a share of a request, earns a pro-rata share of the bounty when the statement is approved, and forfeits the bond to the issuer if the statement is resolved invalid. No plugin or outpost interaction is involved. + +### Node Owners +Holders of a node-owner NFT who commit it on the Ethereum outpost (`NODE_OWNER_REG`). They are registered on `sysio.roa`, receive node-owner WIRE distributions from `sysio.system`, and form the Tier-1 electorate that votes on `sysio.chalg` envelope disputes. --- @@ -102,10 +104,20 @@ Independent operators running the `underwriter_plugin`. They provide collateral- | Contract | Role | |----------|------| -| **sysio.msgch** | Central message router — receives inbound envelopes, evaluates consensus, extracts attestations, queues outbound messages | -| **sysio.epoch** | Manages epoch cycles, assigns operator groups, registers outposts | -| **sysio.uwrit** | Underwriting ledger — tracks collateral, locks/releases funds, distributes fees, handles slashing | -| **sysio.chalg** | Challenge handler — manages dispute resolution with economic penalties | +| **sysio.msgch** | OPP channel — receives inbound envelopes, evaluates consensus, routes attestations, queues and builds outbound envelopes, cranks the epoch | +| **sysio.epoch** | Global epoch clock, sliding-window batch-operator schedule, emissions gate | +| **sysio.opreg** | Operator registry and depot-native collateral ledger (deposit, withdraw queue, pull claims, slashing, termination) | +| **sysio.chalg** | Envelope-dispute vote by Tier-1 node owners; slash execution | +| **sysio.synd** | Liquidity syndication ledger: syndication envelopes, bonded release, desyndication, rate limits | +| **sysio.bond** | Generic bonded underwriting of provable statements | +| **sysio.liq** | Shadow liq tokens (`LIQETH`, `LIQSOL`) minted against outpost custody, with a WIRE yield index | +| **sysio.swap** | Depot-local constant-product AMM; every pair's second leg is WIRE | +| **sysio.dclaim** | WIRE claim ledger (launch import, staking rewards) | +| **sysio.andon** | Emergency stop: one cord freezing depot fund egress | +| **sysio.roa** | Resources and node-owner registry | +| **sysio.authex** | Links a depot account to its external-chain keys | +| **sysio.chains** / **sysio.tokens** | Chain (outpost) and token registries | +| **sysio.system** | Emissions and producer / batch-operator / node-owner pay | --- @@ -113,20 +125,24 @@ Independent operators running the `underwriter_plugin`. They provide collateral- | Contract | Role | |----------|------| -| **OPP.sol** | Message sender — collects attestations in send mode, builds merkle trees, emits `OPPMessage` / `OPPEpoch` events | -| **OPPInbound.sol** | Message receiver — validates consensus, verifies merkle proofs, routes attestations to handlers | -| **OperatorRegistry.sol** | Maps operators to roles, keys, and status for identity validation | -| **BAR.sol** (Bond Agreement Registry) | Records validator / node-owner bonds; operator-collateral `OPERATOR_ACTION` emission has moved to `OperatorRegistry.sol` | -| **IOPPReceiver implementations** | Domain-specific handlers (Depositor, Pool, Pretoken) that process individual attestation types | +| **OPP.sol** | Outbound — queues attestations, seals and emits the outpost's per-epoch envelope | +| **OPPInbound.sol** | Inbound — accepts chunked `epochIn` deliveries, validates group consensus and the envelope chain, routes attestations to handlers | +| **SyndicationPool.sol** | liqETH custody: `syndicate` (`SYNDICATE_LIQ`), `realizeYield` (`LIQ_YIELD`), `DESYNDICATE_LIQ` payout | +| **BAR.sol** | Node-owner NFT escrow emitting `NODE_OWNER_REG` | +| **OutpostManager.sol** | OPP endpoint and role wiring | +| **StakingManager.sol** | Placeholder for the deferred staking track; its entry points revert | + +On Solana the outpost lives in the `liqsol-core` program (`instructions/opp/`), alongside liqSOL staking and syndication. --- ## Consensus & Security -- **Multi-signature consensus**: All 7 active operators must deliver identical envelope checksums for acceptance. -- **Merkle proofs**: Each message is a leaf in the epoch merkle tree; inbound processors verify proofs before accepting. -- **Challenge mechanism**: Any party can challenge a disputed epoch via `sysio.chalg`. Operators must respond with correct hashes or face slashing. -- **Collateralized underwriting**: Swaps are backed by locked collateral with a full lifecycle (intent → submit → confirm → release/slash). +- **Group consensus**: An envelope is accepted when every eligible operator in the active group delivers it identically, or by strict majority once the epoch boundary has passed. +- **Envelope hash chains**: Each envelope carries the digest of its predecessor on the same stream, and messages carry sequence numbers, so envelopes and messages cannot be reordered or replayed. +- **Disputes**: A post-boundary split with no strict majority opens a `sysio.chalg` dispute that pauses the epoch until Tier-1 node owners vote a canonical envelope. Operators that delivered a non-canonical envelope are slashed. +- **Bonded syndication**: Syndicated value is released on the depot only after `sysio.bond` underwriters bond the syndication statement; anyone may challenge it during the challenge window. +- **Emergency stop**: `sysio.andon` freezes depot fund egress; outposts pause independently (`SyndicationPool` pause on Ethereum, `frozen` on Solana). --- @@ -136,6 +152,6 @@ Wire Network implements a trustless, consensus-backed omnichain infrastructure w 1. **OPP** provides a single deterministic message encoding across all blockchains 2. **Batch operators** collectively relay and validate messages with no single point of failure -3. **Underwriters** provide collateral-backed liquidity for cross-chain swaps -4. **On-chain governance** handles scheduling (epochs), routing (message channel), and economic enforcement (underwriting + challenges) -5. **Atomic cross-chain settlement** — no bridging overhead, no wrapped tokens, no trusted intermediaries +3. **Bond providers** stand behind every syndication statement with slashable bonds +4. **On-chain governance** handles scheduling (epochs), routing (message channel), and economic enforcement (collateral, bonds, disputes) +5. **Atomic cross-chain settlement** — no bridging overhead, no trusted intermediaries diff --git a/PLUGINS.md b/PLUGINS.md index 4fce634a4b..78a5bf0c58 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -31,7 +31,7 @@ documents what it does, how it works, and how to configure it. The plugin lifecy | `query_engine_plugin` | Opt-in SQL queries over the node's current chain state, answered by an in-process C++ service and, when HTTP is enabled, `POST /v1/query/execute`; runs only on a non-producing node in head or irreversible read mode | [README](plugins/query_engine_plugin/README.md) | | `resource_monitor_plugin` | Watches free space on every file system nodeop writes to and gracefully shuts the node down before it runs out | [README](plugins/resource_monitor_plugin/README.md) | | `signature_provider_kms_plugin` | Adds the `KMS:` signature-provider scheme, signing Ethereum keys remotely through AWS KMS so the key never leaves AWS | [README](plugins/signature_provider_kms_plugin/README.md) | -| `signature_provider_manager_plugin` | Owns every signing key the node uses, parsing each `--signature-provider` spec and publishing the providers to the chain, producer, net, outpost, and underwriter plugins | [README](plugins/signature_provider_manager_plugin/README.md) | +| `signature_provider_manager_plugin` | Owns every signing key the node uses, parsing each `--signature-provider` spec and publishing the providers to the chain, producer, net, and outpost plugins | [README](plugins/signature_provider_manager_plugin/README.md) | | `signature_provider_ssm_plugin` | Adds the `SSM:` signature-provider scheme, fetching a private key once from an AWS SSM Parameter Store SecureString and signing locally thereafter | [README](plugins/signature_provider_ssm_plugin/README.md) | | `snapshot_api_plugin` | Public, read-only HTTP endpoints for snapshot discovery and download, so nodes can bootstrap from each other | [README](plugins/snapshot_api_plugin/README.md) | | `state_history_plugin` | Records per-block traces, chain-state deltas, and finality data to append-only logs and streams them to clients over a binary websocket protocol | [README](plugins/state_history_plugin/README.md) | @@ -39,7 +39,6 @@ documents what it does, how it works, and how to configure it. The plugin lifecy | `test_control_api_plugin` | HTTP surface for `test_control_plugin`, registering the `/v1/test_control/*` write endpoints in their own `test_control` API category | [README](plugins/test_control_api_plugin/README.md) | | `test_control_plugin` | Test-only plugin that arms deliberate node misbehavior: shut down inside a named producer's round, throw from a controller signal handler, or republish the next block with one action swapped | [README](plugins/test_control_plugin/README.md) | | `trace_api_plugin` | Full-history action trace store with HTTP endpoints for querying traces, transactions, actions, and token transfers | [trace_api_plugin.md](plugins/trace_api_plugin/trace_api_plugin.md) | -| `underwriter_plugin` | Autonomous underwriter daemon that polls pending swaps and submits signed underwrite-intent commits for the outpost legs that still lack one | [README](plugins/underwriter_plugin/README.md) | | `wallet_api_plugin` | Publishes the `kiod` key store as the `/v1/wallet/*` HTTP endpoints that `clio` and a `KIOD:` signature provider call. Linked and initialized by `kiod`, not `nodeop` | [README](plugins/wallet_api_plugin/README.md) | | `wallet_plugin` | The `kiod` key store: a locked directory of AES-encrypted wallet files with inactivity auto-lock and transaction and digest signing. Linked and initialized by `kiod`, not `nodeop` | [README](plugins/wallet_plugin/README.md) | diff --git a/contracts/sysio.chalg/README.md b/contracts/sysio.chalg/README.md index 89a10a1c91..4131b70e4f 100644 --- a/contracts/sysio.chalg/README.md +++ b/contracts/sysio.chalg/README.md @@ -53,6 +53,7 @@ OPP envelope dispute resolution and slash-execution contract. - Triggered by `sysio.msgch::evalcons`; dispatches the winning envelope via `sysio.msgch::resolvedisp` - Tier-1 electorate snapshot from `sysio.roa::nodeowners`; the active `network_gen` is read through the shared canonical ROA accessor -- Slashes via `sysio.opreg::slash` (opreg routes the unlocked bond to the matching LP and defers the - locked portion through `sysio.uwrit::release`) +- Slashes via `sysio.opreg::slash`: opreg marks the operator `SLASHED` and removes its whole + depot-native collateral balance from the operator's row; the slashed tokens stay in + `sysio.opreg`'s custody - Pauses / unpauses via `sysio.epoch::pause` / `sysio.epoch::unpause` diff --git a/contracts/sysio.epoch/README.md b/contracts/sysio.epoch/README.md index 803acc918a..011fbdec97 100644 --- a/contracts/sysio.epoch/README.md +++ b/contracts/sysio.epoch/README.md @@ -1,41 +1,58 @@ # sysio.epoch -Epoch lifecycle and batch operator scheduling contract for the OPP (Outpost Protocol) system. +Global epoch clock and batch operator scheduling contract for the OPP (Outpost Protocol) system. ## Responsibility -- Manages the synthetic epoch clock (default: 6-minute epochs) -- Maintains the batch operator roster (registration, warmup, cooldown, blacklist) -- Assigns operators into 3 rotation groups of 7 via `schbatchgps` -- Advances epochs with deterministic group rotation (`epoch_index % 3`) -- Supports in-place operator replacement when an operator is slashed or deregistered -- Registers external chain outposts (ETH, SOL, etc.) -- Provides global pause/unpause (controlled by `sysio.chalg`) +- Owns the single global epoch clock (`epoch_duration_sec`, default 360 s, bounded to 60 s .. 30 days) +- Builds the initial batch operator schedule with `schbatchgps` and maintains it as a sliding window + of `batch_op_groups` groups of `operators_per_epoch` members; the group on duty is always the front + of the window +- Advances epochs (`advance`), crank-driven by `sysio.msgch::chkcons` after genesis +- Gates every advance on emissions readiness: if `sysio.system` cannot pay the epoch, the advance is + recorded in `blocklog` and skipped (never thrown), and retried on the next `chkcons` +- Provides the global pause/unpause used by `sysio.chalg` while an envelope dispute is open + +Operator registration, collateral and status live on `sysio.opreg`; the outpost set lives on +`sysio.chains`. This contract reads both and mirrors neither. ## Tables | Table | Type | Description | |-------|------|-------------| -| `epochcfg` | Singleton | Epoch duration, group count, warmup/cooldown config | -| `epochstate` | Singleton | Current epoch index, active group, 3 groups of 7, pause flag | -| `operators` | Multi-index | Operator roster with type, status, collateral, group assignment | -| `outposts` | Multi-index | Registered external chain outposts | +| `epochcfg` | `kv::global` | `epoch_duration_sec`, `operators_per_epoch` (default 7), `batch_operator_minimum_active` (default 21), `batch_op_groups` (default 3), `epoch_retention_envelope_log_count` (default 200) | +| `epochstate` | `kv::global` | Current epoch index, current / next epoch start, active group cursor, the resident group window, last consensus hash, pause flag | +| `blocklog` | `kv::table` | One row per epoch the emissions gate blocked: reason, attempted emission, treasury and balance figures, first/last retry and retry count. Pruned when the gate passes | ## Actions | Action | Auth | Description | |--------|------|-------------| -| `setconfig` | `sysio.epoch` | Set epoch configuration | -| `regoperator` | `sysio.epoch` | Register an operator | -| `unregoper` | operator | Begin deregistration (cooldown) | -| `advance` | permissionless | Advance epoch if duration elapsed | -| `schbatchgps` | `sysio.epoch` | One-time group assignment (21 operators) | -| `replaceop` | `sysio.epoch` | Replace operator in-place within group | -| `regoutpost` | `sysio.epoch` | Register an outpost chain | -| `pause` | `sysio.chalg` | Set global pause | -| `unpause` | `sysio.chalg` | Clear global pause | +| `setconfig` | `sysio.epoch` | Set the epoch configuration. Requires `operators_per_epoch * batch_op_groups == batch_operator_minimum_active` and enforces the size caps (`MAX_OPERATORS_PER_EPOCH` 100, `MAX_BATCH_OP_GROUPS` 255, `MAX_SCHEDULED_BATCH_OPERATORS` 1000) | +| `schbatchgps` | `sysio.epoch` | Build the initial window from the `ACTIVE` batch operators on `sysio.opreg` (non-bootstrapped first, then bootstrapped, trimmed to `batch_operator_minimum_active`) | +| `advance` | permissionless at genesis (epoch 0 → 1); afterwards `sysio.msgch` or `sysio.epoch` | Advance the epoch once the wall-clock boundary has passed and the contract is not paused | +| `pause` | `sysio.chalg` | Set the global pause | +| `unpause` | `sysio.chalg` | Clear the global pause | + +## What `advance` does + +1. Emissions readiness gate (block-and-retry via `blocklog`, never a throw). +2. Delivery audit of the expiring group for every active outpost: every operator that delivered a + non-canonical envelope is slashed through `sysio.chalg::slashop`; each member's delivery result is + recorded with `sysio.opreg::recorddel`, followed by `sysio.opreg::termcheck`. +3. Bump the epoch index, drop the expired group from the front of the window and append a new tail + group drawn from `ACTIVE` batch operators that are not already resident (non-bootstrapped first). + The resident groups stay disjoint, so an operator serves at most once every `batch_op_groups` + epochs. +4. `sysio.opreg::flushwtdw` matures queued collateral withdrawals. +5. Queue `OPERATORS` and `BATCH_OPERATOR_GROUPS` to every active outpost through + `sysio.msgch::queueout` (an empty group is withheld), then `sysio.msgch::buildenv` per outpost. +6. `sysio.system::accrueepoch` and `rcrdbatch`, plus `payepoch` on pay epochs. ## Dependencies -- Notifies `sysio.msgch` on epoch advancement +- Advanced by `sysio.msgch` (`chkcons`, and `bootstrap` at epoch 0) +- Reads operators from `sysio.opreg`, authex links from `sysio.authex`, outposts from `sysio.chains` +- Calls `sysio.chalg::slashop`, `sysio.opreg::{recorddel,termcheck,flushwtdw}`, + `sysio.msgch::{queueout,buildenv}` and `sysio.system::{accrueepoch,rcrdbatch,payepoch}` - Pause/unpause controlled by `sysio.chalg` diff --git a/contracts/sysio.msgch/README.md b/contracts/sysio.msgch/README.md index fd0de66419..2c93121160 100644 --- a/contracts/sysio.msgch/README.md +++ b/contracts/sysio.msgch/README.md @@ -4,43 +4,65 @@ Inbound/outbound OPP message chain management and consensus tracking contract. ## Responsibility -- Manages inbound chain requests from external outposts -- Collects deliveries from all 7 elected batch operators per epoch -- Evaluates consensus (Option A: unanimous 7/7, Option B: 4+ majority at epoch boundary) -- Stores and routes individual messages by attestation type -- Queues outbound messages and builds OPP envelopes for delivery to outposts +- Accepts inbound OPP envelopes delivered by the batch operators of the active group, one delivery per + operator per outpost per epoch (`MAX_ENVELOPE_BYTES` = 32,768; larger deliveries are rejected) +- Evaluates consensus per (outpost, epoch): every eligible operator delivers an identical envelope + (fast path), or a strict majority of the group agrees once the epoch boundary has passed. A + post-boundary split with no strict majority opens a `sysio.chalg` dispute +- Validates the per-outpost envelope chain and message sequence, stores the accepted messages and + attestations, and routes each attestation to its depot handler +- Queues outbound attestations and packs them into one OPP envelope per outpost per epoch; whatever + does not fit under the envelope cap stays queued for the next epoch +- Drives the epoch: `chkcons` advances `sysio.epoch` once every active outpost has reached + consensus and the wall-clock boundary has passed ## Tables | Table | Type | Description | |-------|------|-------------| -| `inchainreq` | Multi-index | Inbound chain requests per outpost/epoch | -| `deliveries` | Multi-index | Per-operator delivery records with chain hashes | -| `messages` | Multi-index | Individual OPP messages (inbound + outbound) | -| `outenvelopes` | Multi-index | Built outbound envelopes pending delivery | +| `envelopes` | `kv::table` | Inbound deliveries, one row per batch operator per outpost per epoch (working state, pruned to the current and previous epoch) | +| `messages` | `kv::table` | Individual OPP messages extracted from consensus-verified envelopes | +| `attestations` | `kv::table` | Attestations extracted inbound, and outbound attestations queued (`READY`) for envelope packing | +| `outenvelopes` | `kv::table` | Built outbound envelopes awaiting relay, with their canonical epoch digest | +| `outpcons` | `kv::table` | Per-outpost consensus record: accepted envelope digest and inbound message-chain tip | +| `attseq` | `kv::table` | Attestation sequence counter | +| `envlog` | `kv::table` | Metadata-only envelope audit log, capped at `active_outposts * 2 * epoch_retention_envelope_log_count` rows | ## Actions | Action | Auth | Description | |--------|------|-------------| -| `crank` | permissionless | Main depot crank, advances processing | -| `createreq` | `sysio.msgch` | Create inbound chain request for an outpost | -| `deliver` | operator | Batch operator delivers a chain with hash + messages | -| `evalcons` | `sysio.msgch` | Evaluate consensus on a chain request | -| `processmsg` | `sysio.msgch` | Process a READY message (unpack, route attestations) | -| `queueout` | `sysio.epoch`, `sysio.opreg`, `sysio.uwrit`, `sysio.reserv`, `sysio.synd`, or `sysio.msgch` | Queue an outbound message to an outpost | -| `buildenv` | `sysio.msgch` | Build outbound envelope from queued messages | +| `bootstrap` | `sysio.msgch` | Epoch-0 only: asserts the `sysio.system` emissions config is set, then triggers the first `sysio.epoch::advance` | +| `deliver` | batch operator (resident active group, `ACTIVE` in `sysio.opreg`) | Deliver an inbound envelope for an outpost; calls `evalcons` inline | +| `evalcons` | `sysio.msgch` | Evaluate consensus for an (outpost, epoch); on consensus store and dispatch the attestations; on an eligible split open a `sysio.chalg` dispute | +| `chkcons` | permissionless | If no dispute is open, every active outpost has consensus and the boundary has passed, call `sysio.epoch::advance` | +| `resolvedisp` | `sysio.chalg` | Dispatch the winning envelope of a resolved dispute | +| `queueout` | `sysio.epoch`, `sysio.opreg`, `sysio.synd`, or `sysio.msgch` | Queue an outbound attestation for an outpost | +| `buildenv` | `sysio.epoch` | Pack `READY` attestations into the outpost's outbound envelope for the epoch | + +## Attestation routing + +Inbound (outpost → depot): + +- `SYNDICATE_LIQ` → `sysio.synd::onsynd`, `LIQ_YIELD` → `sysio.synd::onyield`. Each carries the + envelope's epoch and canonical digest, and is forwarded only once the payload's token is an active + `TOKEN_KIND_LIQ` row on `sysio.tokens` bound to the proven outpost and, for a syndication, the + user's key family is the outpost's own. After the envelope's last attestation it sends + `sysio.synd::closeenv`, only when at least one of them reached `sysio.synd`. +- `NODE_OWNER_REG` → `sysio.roa` (account creation and node-owner registration, recording the + outpost link). +- `STAKING_REWARD` → `sysio.dclaim::onreward`, after binding the reward's chain to the proven + delivering outpost. +- Every other type is dropped. Every refusal is a logged drop, never an abort. + +Outbound (depot → outpost), queued through `queueout`: + +- `OPERATORS` and `BATCH_OPERATOR_GROUPS` from `sysio.epoch::advance`, to every active outpost +- `DESYNDICATE_LIQ` from `sysio.synd::desyndicate` ## Dependencies -- Reads epoch state from `sysio.epoch` -- Notifies `sysio.chalg` on consensus failure -- Routes attestations to `sysio.epoch`, `sysio.uwrit`, `sysio.chalg` -- Routes `SYNDICATE_LIQ` and `LIQ_YIELD` to `sysio.synd` (`onsynd` for a syndication, - linked user or not, `onyield` for a yield report), each carrying the envelope's epoch and - canonical digest, once the payload's token is an active `TOKEN_KIND_LIQ` row on - `sysio.tokens` bound to the proven outpost and, for a syndication, the user's key family - is the outpost's own. After the envelope's last attestation it sends `sysio.synd::closeenv`, - only when at least one of them reached `sysio.synd`. Every refusal is a logged drop, never - an abort. `DESYNDICATE_LIQ` is outbound only: - `sysio.synd::desyndicate` queues it through `queueout`. +- Reads epoch state from `sysio.epoch` and operator status from `sysio.opreg` +- Opens disputes on `sysio.chalg` (`opendispute`); `sysio.chalg::chkdispute` calls back into `resolvedisp` +- Triggers `sysio.epoch::advance` from `chkcons` (and from `bootstrap` at epoch 0) +- Routes inbound attestations to `sysio.synd`, `sysio.roa` and `sysio.dclaim` diff --git a/contracts/sysio.system/EMISSIONS.md b/contracts/sysio.system/EMISSIONS.md index 9f17581828..ddddb3ec8c 100644 --- a/contracts/sysio.system/EMISSIONS.md +++ b/contracts/sysio.system/EMISSIONS.md @@ -53,8 +53,7 @@ the CATEGORY, not the trace: carrying neither category. And `epochlog` does not recover the split either. It records top-level PERIOD aggregates -— `total_emission`, `compute_amount`, `capex_amount`, `governance_amount`, -`fee_distributed` — where `compute_amount` is the COMBINED producer + batch-operator +— `total_emission`, `compute_amount`, `capex_amount`, `governance_amount` — where `compute_amount` is the COMBINED producer + batch-operator pool. Neither operator category's actually-credited total is stored, so once both kinds of credit accumulate in one `payclaims` row, **the producer-versus-batch-operator split is not available anywhere on chain**. What you can attribute: period totals per top-level @@ -79,21 +78,16 @@ 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. When roster history is complete and includes at least one -non-empty roster, swap-fee rewards from `sysio.reserv`'s `rewards_bucket` are -swept in (`drainrewards`) and allocated -**exclusively to the batch-operator -distribution**, on top of their emission share and weighted by that same -historical active-epoch count. Producers are not paid out of swap fees, so -`producer_bps` / `batch_op_bps` govern the emission split only. +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. Allocated is not the same as paid: only **eligible** shares are actually credited. Emission WIRE stays in the treasury when an **empty historical roster** owns an accrued epoch, when a **member is not opreg-ACTIVE**, as the **remainder** of the two integer divisions (per-roster weighting, then the even per-member split), or when roster history is incomplete and the whole batch -emission slice takes the bounded recovery path. Incomplete or entirely empty -history leaves swap fees in `sysio.reserv` as described below. Every +emission slice takes the bounded recovery path. Every `batchepochs` row represents one accrued epoch, so a zero-epoch historical roster cannot arise. @@ -127,8 +121,7 @@ largest accepted period, so even a malformed gapped table cannot turn recovery into an unbounded transaction; stale history drains monotonically. If `payepoch` sees missing, stale, non-contiguous, or over-cap history, it does not guess a roster or abort the enclosing `advance`: it retains that period's -batch-emission slice in the treasury, leaves the swap-fee bucket in -`sysio.reserv` for the next complete period, drains the unusable history within +batch-emission slice in the treasury, drains the unusable history within the cleanup bound, and begins clean history after stale rows are gone. A complete history remains required to credit batch-operator rewards. Every `epochlog` row records whether history was complete and the exact batch-emission amount @@ -137,12 +130,9 @@ retained, making recovery distinguishable from an ordinary zero-eligible payout. `epoch_log_retention_count` counts payment rows, not elapsed epoch indexes, so cadence values greater than one retain the configured number of audit records. -`epochlog.fee_distributed` records what was actually paid, while -`batch_fee_retained` records a swept amount left in treasury because an empty -roster alongside a non-empty roster, an inactive member, or a division remainder -prevented its distribution. It is zero for incomplete or entirely empty history -because those fees remain in `sysio.reserv` for a later payable period. The -analogous `batch_emission_retained` field records undistributed batch emission. +`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) @@ -249,5 +239,4 @@ period_emission - Driven inline by `sysio.epoch::advance` (`accrueepoch` + `rcrdbatch` + `payepoch`). - Reads producer eligibility and operator status from `sysio.opreg`. - Reads the canonical epoch duration from `sysio.epoch::epochcfg`. -- Folds swap-fee rewards from `sysio.reserv` (`drainrewards`). - Funds `sysio.dclaim` (staking rewards) and `sysio.liq` (the yield kicker) on demand via `fundclaim`. diff --git a/docs/becoming-a-block-producer.md b/docs/becoming-a-block-producer.md index ed6918cdd0..caf87df47a 100644 --- a/docs/becoming-a-block-producer.md +++ b/docs/becoming-a-block-producer.md @@ -9,21 +9,21 @@ There are two ways onto the schedule, and this guide covers the second: - **Genesis producers** are placed in the schedule when a chain is bootstrapped. They are registered as *bootstrapped* operators, which only the registry contract itself can do. -- **Collateral-backed producers** post a bond on the outpost chains and earn a schedule position - by rank. That is the open path, and it is the one described here. +- **Collateral-backed producers** post a bond in `sysio.opreg` on WIRE and earn a schedule + position by rank. That is the open path, and it is the one described here. ## What you need before you start | | | |---|---| | A WIRE account | The account that will produce blocks. Its `active` permission signs everything below. | -| An Ethereum wallet | Funded with the required bond plus gas. | -| A Solana keypair | Funded with the required bond plus fees. | +| An Ethereum key and a Solana key | Used only to link your WIRE account to each active outpost chain (step 1). No collateral is posted on those chains. | +| The required collateral, held on WIRE | The tokens `sysio.opreg`'s configuration lists, typically shadow LIQ (`LIQETH`, `LIQSOL`) and/or `WIRE`. | | A machine running `nodeop` | Reachable by the peer network, with your signing keys available to it. | -You must bond on **every** chain the network requires, not just one. The requirement lives in -`sysio.opreg`'s configuration as a per-chain minimum bond, and eligibility takes the **minimum** -across all of them. Posting extra on the cheapest chain buys you nothing. +You must bond **every** token the network requires, not just one. The requirement lives in +`sysio.opreg`'s configuration (`opconfig.req_prod_collat`) as a minimum per token, and +eligibility needs every one of them met. Posting extra of one token buys you nothing for another. ## Step 1 — Link your outpost addresses @@ -31,9 +31,9 @@ across all of them. Posting extra on the cheapest chain buys you nothing. sysio.authex::createlink(chain_kind, account, sig, pub_key, nonce) ``` -Sign this once per chain, with your Ethereum and Solana keys respectively. The link is what makes -a deposit you send on an outpost attributable to your WIRE account; without it the chain has no way -to know the bond is yours. +Sign this once per active outpost chain, with your Ethereum and Solana keys respectively. +`regoperator` refuses an account that lacks a link for any active outpost chain, and the links are +what the depot publishes for you in the `OPERATORS` roster it sends each outpost. The `nonce` is a millisecond timestamp and is rejected if it is more than ten minutes old, so generate it at signing time. @@ -51,24 +51,21 @@ the bootstrap process explicitly pushes those privileged `regoperator` actions a You are now registered but not yet eligible. Your status stays `UNKNOWN` until the bond arrives. -## Step 3 — Post your collateral on each outpost +## Step 3 — Post your collateral on WIRE -Deposit on the outpost chains themselves, signed by the wallets you linked in step 1: +Read `opconfig.req_prod_collat` for the tokens and minimums, hold those tokens on WIRE, and bond +each one: -- **Ethereum** — `OperatorRegistry.deposit(...)` -- **Solana** — the outpost program's `deposit` instruction - -Each deposit travels to WIRE over the cross-chain protocol and credits your balance in -`sysio.opreg`. +``` +sysio.opreg::deposit(account, token_code, amount) +``` -If the configured requirements include the native WIRE pair, separately call -`sysio.opreg::deposit(account, token_code, amount)` on WIRE with `token_code` `WIRE`, signed by -your WIRE account. This native action requires an existing non-bootstrapped operator row, -transfers the token inline, and credits only the matching WIRE-chain pair; it does not replace -the Ethereum or Solana outpost deposits. The same action bonds a shadow LIQ symbol such as -`LIQETH` or `LIQSOL` held on WIRE, one `token_code` per call. +Signed by your WIRE account, one `token_code` per call: `WIRE` or a shadow LIQ symbol such as +`LIQETH` or `LIQSOL`. The action requires an existing non-bootstrapped operator row, transfers the +token into `sysio.opreg` inline, and credits your balance. Collateral is held only on WIRE; +nothing is deposited on the outpost chains. -When every required chain is at or above its minimum, your operator status flips to `ACTIVE` on +When every required token is at or above its minimum, your operator status flips to `ACTIVE` on its own. No producer rank exists yet: the first score is written only after the producer row and active finalizer key are created in steps 4 and 5. @@ -176,7 +173,7 @@ arrive. Nobody has to vote them out, and there is no flag day. | Factor | What it measures | |---|---| -| Collateral | Your bond divided by the required minimum, taken as the **minimum** across every required chain. Linear and uncapped, so more collateral always outranks less. | +| Collateral | Your bond divided by the required minimum, taken as the **minimum** across every required token. Linear and uncapped, so more collateral always outranks less. | | Participation | Falls with each consecutive unserved round and recovers when you serve one. | | Snapshot service | Snapshot attestations that reached quorum in the current pay period. Weighted at a tenth of collateral, so it separates producers the bond has left tied rather than outranking a larger bond. | @@ -266,17 +263,11 @@ under it. It is not applied only to rounds missed from that point on. simply hold no schedule position. `regproducer` brings you back at the position your collateral earns after rechecking current admission; a minimum raised while you were parked requires a top-up first. -- **Withdraw** from the chain that holds the bond. An outpost bond is released through that - outpost's own withdrawal entry point, the counterpart of the deposit you made in step 3, which - travels to WIRE and settles against your registry balance. - `sysio.opreg::withdraw(account, token_code, amount)` is **not** that path: it applies only to - your WIRE-native balance in `token_code` (`WIRE` or a shadow LIQ symbol), so calling it for an - Ethereum or Solana bond fails for insufficient balance - and leaves the outpost collateral untouched. Once a WIRE-native withdrawal flushes, the funds - are credited to a per-token claim that you pull with `sysio.opreg::claimremit(account, - token_code)`. Either way the request is queued rather than immediate, and `cancelwtdw` - cancels it before it flushes. Once your balance falls below the minimum on any required chain you - leave `ACTIVE` and the schedule drops you at the next rebuild. +- **Withdraw** with `sysio.opreg::withdraw(account, token_code, amount)`. The request is queued + rather than immediate (`cancelwtdw` cancels it before it flushes); once it flushes at an epoch + advance, the funds are credited to a per-token claim that you pull with + `sysio.opreg::claimremit(account, token_code)`. Once your balance falls below the minimum for + any required token you leave `ACTIVE` and the schedule drops you at the next rebuild. - **Collect shadow yield.** Bonded shadow LIQ keeps earning WIRE yield: each bonded unit earns what one unit of that shadow token earns in `sysio.liq`, from the moment it is bonded until it leaves your balance. `sysio.opreg::claimyield(account, token_code)` credits what you have diff --git a/docs/contract-upgrade-order.md b/docs/contract-upgrade-order.md index 0c05505eb8..2a091600d5 100644 --- a/docs/contract-upgrade-order.md +++ b/docs/contract-upgrade-order.md @@ -1,7 +1,7 @@ # System-contract upgrade order The system contracts are not independent deployables. `sysio.epoch::advance` -inlines actions into six other contracts, and the emissions gate reads two more +inlines actions into four other contracts, and the emissions gate reads two contracts' tables, so a release's contract builds are only correct **as a set**. Upgrading them one at a time creates windows in which a new caller meets an old callee. @@ -16,22 +16,19 @@ Contracts outside the coupled set stage separately, in any order. ## The deployment recipe "Just `setcode` everything in one transaction" fails twice over — once on which -action deploys each account, once on transaction size — and both bite this -release specifically. +action deploys each account, once on transaction size. **1. The deploy action depends on WHICH account holds the contract — the transaction mixes both.** -*Separate system accounts* (`sysio.epoch`, `sysio.reserv`, `sysio.opreg`, -`sysio.uwrit`, …) deploy through **`sysio.roa::setsyscode(account, vmtype, -vmversion, code)` / `sysio.roa::setsysabi(account, abi)`**. Those set the +*Separate system accounts* (`sysio.epoch`, `sysio.opreg`, `sysio.msgch`, +`sysio.chalg`, `sysio.synd`, …) deploy through **`sysio.roa::setsyscode(account, +vmtype, vmversion, code)` / `sysio.roa::setsysabi(account, abi)`**. Those set the code/abi AND reconcile the account's gifted RAM to its exact new usage out of sysio's pool via `giftram`, measured after the write (re-callable: a smaller re-deploy reclaims). A raw `setcode` here skips that reconciliation and leaves -the account paying for the new size out of a finite quota it does not have — -not theoretical for this release, where `sysio.reserv.wasm` grows **6,040 -bytes** and `sysio.opreg.wasm` grows **2,447 bytes** against a system account's -small creation allowance. +the account paying for any growth out of a finite quota it does not have — a +system account only has a small creation allowance. *The root `sysio` account* — which holds `sysio.system` and **is the RAM pool itself** — deploys with the **native `setcode` / `setabi`**. `giftram` cannot @@ -45,37 +42,33 @@ lower than the actual quota, and it fails silently: sysio's limit is finite, so production bootstrap deploys `system` on `sysio` natively (the harness encodes the same split as `ContractSteps.DeployMode.raw` for bios/system/roa). -So the atomic transaction for this release's trio carries **native -`setcode`/`setabi` for `sysio`** plus **ROA `setsyscode`/`setsysabi` for -`sysio.epoch` and `sysio.reserv`**. +So an atomic transaction that includes `sysio.system` carries **native +`setcode`/`setabi` for `sysio`** plus **ROA `setsyscode`/`setsysabi` for every +other account in the set**. -**2. The whole release does not fit in one transaction.** The five changed -WASMs total **579,408 bytes**, already past the default -`max_transaction_net_usage` of **524,288** (`config::default_max_block_net_usage -/ 2`) before ABIs or action wrapping. `sysio.msig::propose` receives the -complete inner transaction before it chunks storage, so proposing through msig -does not dodge that input NET. - -What fits is the **compatibility-coupled trio** — `sysio.reserv`, -`sysio.epoch`, `sysio.system` — the three the edges below actually couple: +**2. A large set does not fit in one transaction.** The default +`max_transaction_net_usage` is **524,288** bytes (`config::default_max_block_net_usage +/ 2`), and `sysio.msig::propose` receives the complete inner transaction before it +chunks storage, so proposing through msig does not dodge that input NET. The +contracts on `advance`'s path are not small (sizes of the builds in this tree): | | code | abi | total | |---|---|---|---| -| `sysio.epoch` | 75,579 | 6,758 | 82,337 | -| `sysio.system` | 174,138 | 61,916 | 236,054 | -| `sysio.reserv` | 84,026 | 24,647 | 108,673 | -| **trio** | **333,743** | **93,321** | **427,064** | - -That leaves ~97 KB against the 524,288 ceiling — enough, but not by so much -that it can be assumed. **Preflight the packed size of the actual proposal** -rather than trusting this table, which is a snapshot of one release. - -`sysio.opreg` and `sysio.uwrit` are uncoupled (they gain no action another -contract inlines, and no other contract reads their new tables), so they stage -in their own transaction, before or after the trio. - -When a future release's coupled set does NOT fit, the options are: split off -whatever is genuinely uncoupled and stage it, or raise +| `sysio.epoch` | 95,314 | 6,758 | 102,072 | +| `sysio.system` | 277,387 | 66,071 | 343,458 | +| `sysio.opreg` | 115,210 | 27,979 | 143,189 | +| `sysio.msgch` | 177,916 | 21,421 | 199,337 | +| `sysio.chalg` | 32,009 | 6,797 | 38,806 | + +All five together exceed the ceiling, so a release that changes every one of +them cannot deploy them atomically. Put in the atomic set only the contracts a +release's changes actually couple (see [The cross-contract +edges](#the-cross-contract-edges)), and **preflight the packed size of the actual +proposal** rather than trusting this table, which is a snapshot of one build. + +When a release's coupled set does NOT fit, the options are: split off whatever is +genuinely uncoupled and stage it (see [Staged +rollout](#staged-rollout-when-one-transaction-is-not-possible)), or raise `max_transaction_net_usage` via `setparams` first — as a **tested** path, with the raise proposed and confirmed before the deploy proposal, never assumed to work on the day. @@ -121,17 +114,18 @@ Everything `sysio.epoch::advance` inlines, directly: | Callee | Actions | |---|---| -| `sysio.reserv` | `sweepclaims` | -| `sysio.uwrit` | `chklocks`, `pruneuwreqs`, `drainfwq` | | `sysio.opreg` | `recorddel`, `termcheck`, `flushwtdw` | | `sysio.chalg` | `slashop` | | `sysio.msgch` | `queueout`, `buildenv` | | `sysio` | `accrueepoch`, `rcrdbatch`, `payepoch` | -Those callees inline further (`drainfwq` → `sysio.reserv::refundwire`, -`termcheck` → the `sysio.opreg` remit path, `payepoch` → `sysio.token::transfer`), -so the transitive subtree — not just the table above — sits inside `advance`'s -abort surface. +Those callees inline further (`slashop` → `sysio.opreg::slash`, `termcheck` → the +`sysio.opreg` termination path that credits `remitclaims`, `payepoch` → +`sysio.token::transfer` for the pushed capex and governance buckets), so the +transitive subtree — not just the table above — sits inside `advance`'s abort +surface. `advance` itself is inlined by `sysio.msgch::chkcons` (and by +`sysio.msgch::bootstrap` at epoch 0), so `sysio.msgch` is both a caller and a +callee of `sysio.epoch`. Independently of inlines, the emissions readiness gate in `sysio.epoch` **reads** `sysio.system`'s `emitcfg`, `t5state` and `payclaimtot`, and `sysio.token`'s @@ -201,8 +195,7 @@ mixed or mid-period deployment. `rcrdbatch` prunes the exact oldest retained roster, and `payepoch` removes at most 20 history rows per payment, so even a malformed gapped table cannot turn cleanup into an unbounded `advance`. If `payepoch` sees missing, stale, non-contiguous, or over-cap history, it -retains that period's batch-emission slice in the treasury, leaves the swap-fee -bucket in `sysio.reserv` for the next complete period, records the retained +retains that period's batch-emission slice in the treasury, records the retained emission and incomplete-history status in `epochlog`, and drains stale history monotonically before resuming batch payouts. Producer, capex, and governance processing still completes. The runtime also shortens a legacy cadence when @@ -236,28 +229,25 @@ state that aborts every advance. ## Staged rollout (when one transaction is not possible) -For the SEC-150 claimable-payout release the order is: - -``` -sysio.reserv -> sysio.epoch -> sysio.system -``` - -`sysio.opreg` and `sysio.uwrit` are free to land anywhere in the sequence: they -gain no action any other contract inlines (`claimremit` is user-initiated), and -no other contract reads their new tables. +Derive the order from the two rules above for the edges a release actually +changes. For the edges on `advance`'s path today: | Edge | Why | |---|---| -| `sysio.reserv` before `sysio.epoch` | The new `advance` inlines `sysio.reserv::sweepclaims`, guarded only on the account existing. An old `sysio.reserv` build has the account and not the action, so the inline asserts and every advance aborts. | -| `sysio.epoch` before `sysio.system` | The new `payepoch` retains WIRE in `payclaims` and reserves it in `payclaimtot`. The old gate counts that backing as spendable, so a later pay period can double-commit it and leave credited claims underfunded. | +| `sysio.opreg`, `sysio.chalg`, `sysio.msgch` before `sysio.epoch`, when the release adds or changes an action `advance` inlines into them | Call edge: the new `advance` would otherwise reach a callee that cannot dispatch the action, and every advance aborts. | +| `sysio` (`sysio.system`) before `sysio.epoch`, when the release adds or changes `accrueepoch` / `rcrdbatch` / `payepoch` | Call edge — but an action `sysio.system` does not implement is silently ignored rather than asserted, so this skew drops the effect instead of stalling. | +| `sysio.epoch` before `sysio.system`, when `sysio.system` gains state the emissions gate must reserve (as `payclaimtot` reserves unclaimed `payclaims`) | Table edge: the old gate counts the new backing as spendable, so a later pay period can double-commit it and leave credited claims underfunded. | +| `sysio.epoch` before `sysio.msgch`, when the release adds or changes an action `chkcons` inlines into `sysio.epoch` | Call edge in the other direction. | + +When a release changes both directions of one pair, the two contracts are a +compatibility-coupled set and must deploy in one transaction; if they do not fit, +make the change layout-compatible so that either intermediate state is safe, or +raise `max_transaction_net_usage` first. -Every intermediate state of that order is safe. A new `sysio.reserv` under an old -`sysio.epoch` is simply never asked to sweep — the retention deadline then rests -on `credit_wire_claim`'s opportunistic sweep until epoch catches up. A new -`sysio.epoch` under an old `sysio.system` reads a `payclaimtot` whose KV key does -not exist yet, so `get_or_default` yields a zero reserve — the correct answer -while nothing is credited, and the absent-key case rather than the -short-decode one (see [above](#why-a-mixed-version-is-not-merely-degraded)). +A reader that meets a KV key the new writer has not created yet reads the +default from `get_or_default` — the correct answer while nothing is written, and +the absent-key case rather than the short-decode one (see +[above](#why-a-mixed-version-is-not-merely-degraded)). ## Downgrades @@ -271,13 +261,12 @@ short-decode one (see [above](#why-a-mixed-version-is-not-merely-degraded)). **A downgrade is not the upgrade run backwards. It is a data-migration problem first, and a code-ordering problem second** — because by the time you want to -roll back, all three claim tables may hold value that only the NEW code can pay -out: +roll back, the claim tables may hold value that only the NEW code can pay out: | Table | Contract | Paid out by | Stranded when that contract rolls back | |---|---|---|---| | `payclaims` | `sysio.system` | `claimpay` | earned epoch pay | -| `wireclaims` | `sysio.reserv` | `claimwire` | swap payouts + refunds already withheld from recipients | +| `wireclaims` | `sysio.bond` | `claimwire` | WIRE yield earned by underwriters and banked at `claim` | | `remitclaims` | `sysio.opreg` | `claimremit` | debited operator collateral | Every one of those balances is value already taken from someone's spendable @@ -292,14 +281,14 @@ resumes double-committing while the pile of unreachable claims grows. The safe procedure is therefore: 1. **Quiesce the credit writers** so no new claim rows appear. -2. **Drain or migrate all three claim tables** — claimants pull, or the balances +2. **Drain or migrate the claim tables** — claimants pull, or the balances are migrated. This step is the one that actually gates the rollback, and it cannot be completed unilaterally: a claimant who never claims holds it open. -3. **Roll back the coupled trio in the order `sysio.system` → `sysio.epoch` → - `sysio.reserv`** — the mirror of the upgrade order, so the writer is retired - before the reader that accounts for it, and the caller before the callee it - would otherwise inline into. -4. **Roll `sysio.opreg` back only once its remits are handled.** +3. **Roll back the coupled set in the mirror of the upgrade order** — the writer + is retired before the reader that accounts for it, and the caller before the + callee it would otherwise inline into. +4. **Roll `sysio.opreg` back only once its remits are handled, and `sysio.bond` + only once its `wireclaims` are drained.** **A live chain with uncooperative claimants is not safely downgradeable** by code order alone. If a rollback has to happen anyway, the outstanding balances diff --git a/docs/emergency-stop-playbook.md b/docs/emergency-stop-playbook.md index c86720edce..b8d10847e4 100644 --- a/docs/emergency-stop-playbook.md +++ b/docs/emergency-stop-playbook.md @@ -14,7 +14,7 @@ chain. The contracts behind it are described in [`sysio-synd.md`](sysio-synd.md) |---|---|---|---| | Depot | `sysio.andon` `cord` row, `pulled` | `sysio.andon::pull`: the panic account, `sysio`, or a registered puller | `sysio.andon::clear`: the panic account or `sysio` | | Solana | liqsol-core `GlobalState.frozen` | `set_frozen(true)`: `GlobalConfig.panic` or `GlobalConfig.admin` | `set_frozen(false)`: the same two | -| Ethereum | OpenZeppelin `paused()` on `SyndicationPool`, `ReserveManager`, `LiqEthToken`, `StakingModule` | `pause()`: any holder of the `panic` role (the deploy's `panicAccount` and the admin) | `unpause()`: the same | +| Ethereum | OpenZeppelin `paused()` on `SyndicationPool`, `LiqEthToken`, `StakingModule` | `pause()`: any holder of the `panic` role (the deploy's `panicAccount` and the admin) | `unpause()`: the same | The three are independent: pulling one changes nothing on the others. When unsure whether a fault crossed chains, pull all three. @@ -33,7 +33,7 @@ playbook; they do not change between incidents unless an account is replaced. | `SOL_ADMIN_KEYPAIR` | keypair file of the Solana admin (`GlobalConfig.admin`) | | `SOL_CRANK_KEYPAIR` | any funded Solana keypair: it signs `pay_pending_desyndication` | | `ETH_RPC_URL` | Ethereum RPC URL of the outpost's network | -| `SYNDICATION_POOL`, `RESERVE_MANAGER`, `OPP_INBOUND`, `OUTPOST_AUTHORITY`, `OUTPOST_MANAGER` | the `SyndicationPool`, `ReserveManager`, `OPPInbound`, `OutpostManagerAuthority` and `OutpostManager` entries of the outpost deploy's `addressFile` | +| `SYNDICATION_POOL`, `OPP_INBOUND`, `OUTPOST_AUTHORITY`, `OUTPOST_MANAGER` | the `SyndicationPool`, `OPPInbound`, `OutpostManagerAuthority` and `OutpostManager` entries of the outpost deploy's `addressFile` | | `LIQETH_TOKEN`, `STAKING_MODULE`, `LIQETH_AUTHORITY` | the `LiqEthToken`, `StakingModule` and `LiqEthAuthority` entries of the liqEth deploy's `addressFile` | A few values belong to the incident itself and are set when it happens: `REASON` (why the cord was @@ -79,9 +79,11 @@ Each item is a deploy or upgrade step. An item not done is a freeze that cannot (`set-panic`, [Solana commands](#solana-commands)) and read it back with `status`. 3. **Ethereum.** Set `panicAccount` in the deploy (or upgrade) config. A fresh deploy maps `pause` and `unpause` to the `panic` role from `permissions/outpost.yaml` and `permissions/liqeth.yaml` and - grants the role to `panicAccount` and to the applying admin. An environment deployed earlier gets - the mapping from the permissions-only steps `outpost_20260930.01_permissions` and - `liqeth_20260930.01_permissions` (wire-ethereum `docs/deploy.md`, Rolling the panic role out). + grants the role to `panicAccount` and to the applying admin. On the outpost only `SyndicationPool` + is pausable, and since it is not deployed anywhere yet its mapping always comes from + `permissions/outpost.yaml`. A liqEth environment deployed earlier gets the mapping from the + permissions-only step `liqeth_20260930.01_permissions` (wire-ethereum `docs/deploy.md`, Rolling the + panic role out). 4. **Solana relay funding.** Fund the Solana key each `outpost_solana_client` relay signs `dispatch_attestations` with (the public key in its `--signature-provider`, logged at start as `Signer public key:`) for the rent of the payouts the outpost may store: on a freeze, a custody @@ -114,7 +116,8 @@ collateral and the other depot payouts are a separate concern and are not frozen - `sysio.opreg::claimremit(account, token_code)` for a shadow LIQ `token_code` pays through `sysio.liq::transfer` to the operator, which is not a custody contract, so it IS refused while the cord is pulled; the `remitclaims` row stays and is claimed after the clear. -- The WIRE payouts of `sysio.chalg`, `sysio.uwrit` and `sysio.reserv` read no cord and run. +- The WIRE payouts of `sysio.system` (`claimpay`, `claimnodedis`) and `sysio.dclaim` (`claim`) read no + cord and run. If an incident involves one of these, the andon cord does not stop it; the owning contract's own controls (or a contract redeploy) do. @@ -143,7 +146,6 @@ the outpost have diverged. The relay skips the yield report of a frozen outpost | Contract | `pause()` stops | Keeps running | |---|---|---| | `SyndicationPool` | `syndicate` and `realizeYield` (`EnforcedPause`); a depot `DESYNDICATE_LIQ` is stored as a pending desyndication (`DesyndicationDeferred`, reason `OUTPOST_FROZEN`); `payPendingDesyndication` | OPP delivery; views | -| `ReserveManager` | `create_reserve`, `requestReserveCreateErc20WithPermit`, `requestReserveCreateErc20WithApproval`, `requestSwap`, `requestSwapErc20WithPermit`, `requestSwapErc20WithApproval`, `withdraw` | OPP delivery and attestation handling, including `SWAP_REMIT` and `SWAP_REVERT` payouts. While `LiqEthToken` is paused a liqETH payout fails: a `SWAP_REMIT` is skipped with `SwapRemitUnpayable` ("transfer failed") and a `SWAP_REVERT` refund with `SwapRevertError`. The depot counts both as paid. | | `LiqEthToken` | every liqETH transfer, mint and burn | views | | `StakingModule` | `batchDeposit`, `topUpValidator` | the rest | @@ -164,9 +166,8 @@ pauses itself and stores the release with reason `CUSTODY_SHORTFALL` `OUTPOST_FROZEN` for the whole freeze instead of attempting transfers against a paused token. **Pause `LiqEthToken` only when the token itself is at fault.** A paused liqETH blocks every transfer, -so `ReserveManager` cannot pay a liqETH `SWAP_REMIT` or `SWAP_REVERT` that arrives during the pause. It -skips the payout and logs it (`SwapRemitUnpayable`, `SwapRevertError`), while the depot counts it as -paid. Each one found in step 9 is reconciled by hand. +mint and burn, including the liqETH staking flows; desyndications arriving while only the token is +paused are stored as `SETTLEMENT_REFUSED` and paid by `payPendingDesyndication` after the unpause. ## Pulling the cord @@ -197,9 +198,8 @@ Pull `SyndicationPool` first: ```bash cast send --rpc-url "$ETH_RPC_URL" --account panic "$SYNDICATION_POOL" 'pause()' -cast send --rpc-url "$ETH_RPC_URL" --account panic "$RESERVE_MANAGER" 'pause()' cast send --rpc-url "$ETH_RPC_URL" --account panic "$STAKING_MODULE" 'pause()' -for c in "$SYNDICATION_POOL" "$RESERVE_MANAGER" "$STAKING_MODULE"; do +for c in "$SYNDICATION_POOL" "$STAKING_MODULE"; do cast call --rpc-url "$ETH_RPC_URL" "$c" 'paused()(bool)' done ``` @@ -274,8 +274,8 @@ share wait for the clear. `revokeRole` call to the authority. The liqEth side has no handoff, so its admin calls `LiqEthAuthority` directly: ```bash - # Outpost access manager (ReserveManager, SyndicationPool), through OutpostManager - ROLE=$(cast call --rpc-url "$ETH_RPC_URL" "$OUTPOST_AUTHORITY" 'getTargetFunctionRole(address,bytes4)(uint64)' "$RESERVE_MANAGER" "$(cast sig 'pause()')") + # Outpost access manager (SyndicationPool), through OutpostManager + ROLE=$(cast call --rpc-url "$ETH_RPC_URL" "$OUTPOST_AUTHORITY" 'getTargetFunctionRole(address,bytes4)(uint64)' "$SYNDICATION_POOL" "$(cast sig 'pause()')") cast send --rpc-url "$ETH_RPC_URL" --account admin "$OUTPOST_MANAGER" 'grantRole(uint64,address)' "$ROLE" "$NEW_ETH_PANIC" cast send --rpc-url "$ETH_RPC_URL" --account admin "$OUTPOST_MANAGER" 'execute(address,bytes)' "$OUTPOST_AUTHORITY" \ "$(cast calldata 'revokeRole(uint64,address)' "$ROLE" "$OLD_ETH_PANIC")" @@ -341,14 +341,6 @@ and `status` on Solana (`liq_sequence`, the pool ATA balance). What to check: outpost; a skipped one is reconciled by the recredit rule ([Reconciling](#reconciling-a-desyndication-the-outpost-did-not-pay)). - Between two readings during a freeze, no outpost custody balance falls. -- If `LiqEthToken` was paused: look for liqETH payouts `ReserveManager` skipped between `PAUSE_BLOCK` - and the unpause. The depot counts each of them as paid, so each one found is reconciled by hand: - ```bash - cast logs --rpc-url "$ETH_RPC_URL" --from-block "$PAUSE_BLOCK" --address "$RESERVE_MANAGER" \ - 'SwapRemitUnpayable(uint64 tokenCode, uint64 reserveCode, uint64 depotAmount, bytes32 originalId, string reason)' - cast logs --rpc-url "$ETH_RPC_URL" --from-block "$PAUSE_BLOCK" --address "$RESERVE_MANAGER" \ - 'SwapRevertError(address indexed depositor, uint64 tokenCode, uint64 reserveCode, uint256 amount, string amountUnit, bytes32 originalSwapMessageId, bytes errData)' - ``` ### Reconciling a desyndication the outpost did not pay @@ -466,7 +458,7 @@ cast logs --rpc-url "$ETH_RPC_URL" --address "$SYNDICATION_POOL" \ cast call --rpc-url "$ETH_RPC_URL" "$SYNDICATION_POOL" 'paused()(bool)' # true ``` -Only `SyndicationPool` is paused; pause `ReserveManager`, `StakingModule` or `LiqEthToken` by hand if the +Only `SyndicationPool` is paused; pause `StakingModule` or `LiqEthToken` by hand if the fault may reach them ([Pulling the cord](#pulling-the-cord)). `payPendingDesyndication` pays the stored release after `unpause`, with the same rule as Solana: unpause only once the pool covers the depot's outstanding plus every release still stored. @@ -504,7 +496,6 @@ Unpause `SyndicationPool` last, so the liqETH it pays out can move: ```bash cast send --rpc-url "$ETH_RPC_URL" --account panic "$LIQETH_TOKEN" 'unpause()' # only if it was paused cast send --rpc-url "$ETH_RPC_URL" --account panic "$STAKING_MODULE" 'unpause()' -cast send --rpc-url "$ETH_RPC_URL" --account panic "$RESERVE_MANAGER" 'unpause()' cast send --rpc-url "$ETH_RPC_URL" --account panic "$SYNDICATION_POOL" 'unpause()' ``` diff --git a/docs/opp-two-phase-dispatch-design.md b/docs/opp-two-phase-dispatch-design.md index b87924974c..90ffe689e8 100644 --- a/docs/opp-two-phase-dispatch-design.md +++ b/docs/opp-two-phase-dispatch-design.md @@ -3,12 +3,18 @@ Status: design, approved 2026-08-10. Supersedes the fused terminal-call design currently on `feat/resumable-opp-dispatch` (wire-sysio #552, wire-solana #419). +Cross-chain swaps through outposts (`SWAP_REMIT` / `SWAP_REVERT`) and outpost-side +operator collateral (`OPERATOR_ACTION` remits) have since been removed. The +remit-class inbound effect on Solana today is the `DESYNDICATE_LIQ` payout; the +dispatch mechanics below are unchanged by that. + ## Problem A Solana transaction cannot carry the effect accounts for every attestation in an OPP envelope. The packet limit is 1232 bytes serialized and every dynamic account -costs 32 bytes in the message, so a large envelope — many SPL swap remits across -distinct reserves — needs more accounts than one transaction can declare. Only +costs 32 bytes in the message, so a large envelope — many attestations whose +effects each touch their own accounts (desyndication payouts to distinct +recipients, for example) — needs more accounts than one transaction can declare. Only the relay can size a batch, because only the relay supplies the accounts and therefore knows the transaction's real byte cost. @@ -260,10 +266,9 @@ deliberately deleted the SEC-94 estimator allow-list, which was correct; the scaffolding to learn `chain.kind`, so it fell out as collateral rather than by decision. Restore the bare registration assert with no estimator and no `kind` branch. Separately, `buildenv`'s chains fetch now uses the bare `.get()` overload -and aborts with `"key not found"`; give it the message overload. Three stale -references describe the deleted guard and must be corrected or a future reader -will conclude msgch is the backstop: `sysio.reserv.cpp:93-95`, -`sysio.msgch_tests.cpp:164-170`, `sysio.reserv_tests.cpp:411-414`. +and aborts with `"key not found"`; give it the message overload. Any remaining +comment or test that describes the deleted guard (`sysio.msgch_tests.cpp` carried +one) must be corrected, or a future reader will conclude msgch is the backstop. Any change under `contracts/**` runs `contracts_unit_test -- --sys-vm` before the commit lands, and follows the rebuild + WASM/ABI copy sequence @@ -308,7 +313,8 @@ Order: nonzero cursor, stall break, round exhaustion alarming, and the terminal-shape packet-limit measurement. - Contracts: `contracts_unit_test -- --sys-vm` for the `queueout` change. -- Flow: a SOL swap-remit flow whose envelope needs ≥ 2 dispatch rounds, asserting +- Flow: a SOL flow whose inbound envelope needs ≥ 2 dispatch rounds (for example + many desyndication payouts in one epoch), asserting the epoch does not advance until the cursor drains. Run via the canonical pair (`run-flow.mjs` + `flow-heartbeat-monitor.mjs`) per `run-flows-via-canonical-scripts.md`. diff --git a/docs/platform-bootstrap-config.md b/docs/platform-bootstrap-config.md index ae1fbca36f..95444d6ed4 100644 --- a/docs/platform-bootstrap-config.md +++ b/docs/platform-bootstrap-config.md @@ -1,22 +1,14 @@ # Platform bootstrap configuration -Launch-day chains, tokens, reserves, and swap settings are described by a -single human-authored JSON file validated against a protobuf schema. The -bootstrap tool replays that file onto the chain inside the epoch-0 bootstrap -window, replacing the dataset that was previously hard-coded in the cluster -tooling. - -> This is the WIRE **platform** bootstrap config — the launch-day registry -> state (chains, tokens, reserves, global swap settings). The swap reserves it -> seeds are one part of that state; it is not itself a DEX. (Earlier drafts -> called the top-level message `DexConfig`; it is now `BootstrapPlatformConfig`.) - -This addresses three launch needs: +Launch-day chains, tokens, shadow-liq yield pools and pre-launch syndicated +positions are described by a single human-authored JSON file validated against +a protobuf schema. The bootstrap tool replays that file onto the chain inside +the epoch-0 bootstrap window (`sysio.epoch` `current_epoch_index == 0`), where +every registration is privilege-gated and lands active inline. | Need | Artifact | |---|---| -| A T5 allocation set aside to back the WIRE side of reserves | `BootstrapPlatformConfig.t5_reserve_allocation` — a genesis-config WIRE amount; config + bootstrap arithmetic, **no contract change** | -| A T5 allocation set aside for launch DEX integration | `BootstrapPlatformConfig.t5_dex_allocation` — reserved earmark (default 0); the on-chain DEX-seeding mechanism is TBD (launch total set by Kyle / Ken) | +| A T5 allocation set aside to back the WIRE side of the launch yield pools | `BootstrapPlatformConfig.t5_dex_allocation` — drained by `sysio.liq::regliqpool` | | A file the bootstrap tool reads for these settings | `etc/config/dex/dex-config.*.json` | | A defined input shape for that file | `libraries/opp/proto/sysio/opp/bootstrap/bootstrap.proto` → message `BootstrapPlatformConfig` | @@ -31,29 +23,30 @@ canonical protobuf-JSON encoding of `BootstrapPlatformConfig`. - `etc/config/dex/dex-config.launch.example.json` — strawman mainnet launch config (placeholder economics; mainnet token addresses marked VERIFY-BEFORE-LAUNCH). -- `etc/config/dex/dex-config.dev.json` — 1:1 mirror of the dev-cluster dataset - (9 tokens, 8 reserves), proving the schema carries the full set and driving - the test. +- `etc/config/dex/dex-config.dev.json` — the dev-cluster dataset (3 chains, + 9 tokens, 2 liq pools, 3 syndications). - `libraries/opp/test/test_bootstrap_platform_config.cpp` — strict-parse + invariant test in the `test_opp` binary. +- `contracts/tests/sysio.synd_tests.cpp` — parses the dev config the same + strict way to drive its launch-replay cases. ## Schema -`BootstrapPlatformConfig` holds the schema version, a deployment label, the two -T5 earmarks (`t5_reserve_allocation`, `t5_dex_allocation`), and repeated -`ChainSpec` / `TokenSpec` / `ReserveSpec` plus a single `UwritConfig`. +`BootstrapPlatformConfig` holds the schema version, a deployment label, the T5 +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` / `Reserve`), -which are wire messages with packed-uint64 codes, raw `bytes` addresses, and -lifecycle fields that are outputs. A hand-authored config wants the opposite: +`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: -- **Codes are strings** (`"ETHEREUM"`, `"USDC"`, `"PRIMARY"`); the tool packs +- **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 letter, ≤ 8 chars). -- **Addresses are strings** in chain-native display form (`0x`-hex for EVM, - base58 for SVM) so each is verifiable against a block explorer. `bytes` would - render as base64 in JSON. +- **Addresses and pubkeys are strings** in chain-native display form (`0x`-hex + for EVM, base58 for SVM) so each is verifiable against a block explorer. + `bytes` would render as base64 in JSON. - **Amounts are uint64 subunits** (9-decimal unless `precision` says otherwise). Proto3 canonical JSON renders 64-bit integers as **quoted strings** — amounts must be quoted in authored JSON, since launch-scale @@ -71,108 +64,74 @@ lifecycle fields that are outputs. A hand-authored config wants the opposite: |---|---| | `ChainSpec` | `sysio.chains::regchain(kind, code, external_chain_id, name, description)` | | `TokenSpec` | `sysio.tokens::regtoken(kind, code, symbol_name, description, precision, address)` then `sysio.tokens::regctok(chain_code, token_code, contract_addr, is_native)` | -| `ReserveSpec` | `sysio.reserv::regreserve(chain_code, token_code, reserve_code, name, description, initial_chain_amount, initial_wire_amount, source_token_precision, connector_weight_bps, is_private, owner)` — `source_token_precision` is **not** a `ReserveSpec` field: it comes from the referenced `TokenSpec.precision` (see below) | -| `UwritConfig` | `sysio.uwrit::setconfig(fee_bps, collateral_lock_duration_ms, min_fromwire_amount, fromwire_revert_fee_bps, uwreq_pending_timeout_epochs, uwreq_retention_epochs)` — the spec pins the first two; the caller supplies the rest (see below) | -| `t5_reserve_allocation` | none — feeds the `setemitcfg` arithmetic below | -| `t5_dex_allocation` | none yet — reserved earmark; carved out of T5 alongside `t5_reserve_allocation` once the DEX-seeding path lands | - -The `regreserve` signature (with `is_private` + `owner`) and the ms-based -`setconfig` are the reserve-and-swap-beta surface; this config slots directly -onto them. `owner` is a WIRE account name (`sysio::name`): empty for public -reserves, the owning account for private ones. - -`regreserve`'s eleven-argument signature includes `source_token_precision` as its -**eighth** argument, which no `ReserveSpec` field supplies. It is the -**depot-frame precision of the paired -token** — i.e. the referenced `TokenSpec.precision`, which is itself -`min(native precision, 9)`. So the mapping is a lookup, not a new config field: -resolve `(chain_code, token_code)` to its `TokenSpec` and pass that spec's -`precision`. `regreserve` rejects a value above 9 (`WIRE_PRECISION`) with -*"source_token_precision exceeds the depot frame (9) — the outpost must downscale -to min(native, 9)"*, which is why V4 bounds `precision` at 9 rather than 18: -a token declared at, say, 18 would satisfy a wider validator and then abort the -irreversible bootstrap at `regtoken`/`regreserve`. Tokens whose native precision -exceeds the frame (ETH at 18) declare the frame value and are downscaled at the -outpost boundary. - -`UwritConfig` carries only `fee_bps` and `collateral_lock_duration_ms`, while -`setconfig` takes six arguments. The remaining four — -`min_fromwire_amount`, `fromwire_revert_fee_bps`, `uwreq_pending_timeout_epochs`, -`uwreq_retention_epochs` — are **not part of this spec**: a bootstrap caller -passes the contract's `uw_config` in-struct defaults (5 WIRE floor, 500 bps -revert fee, and the two uwreq lifecycle windows) unless it has a reason to -override them. - -`fee_bps` is the **network** fee and not the whole effective swap fee. Each -participating non-WIRE leg's reserve independently charges its own -`owner_fee_bps` off the same WIRE leg; that rate is **not** in `ReserveSpec` and -is set post-bootstrap via `sysio.reserv::setrsvfee`. The network fee itself -splits 50/50 between the winning underwriter and a rewards pool -(`sysio.reserv::FEE_UNDERWRITER_SHARE_BPS`), and that pool splits again by the -optional `reservcfg` dial `fee_emissions_share_bps` — which nothing seeds, so it -reads zero until `sysio.reserv::setconfig` first persists a row. - -## T5 reserve earmark - -With `A` = the launch T5 allotment, `E` = `t5_reserve_allocation`, and -`W` = Σ `reserves[].initial_wire_amount`: - -`regreserve` drains each reserve's WIRE side from the `sysio` emissions -treasury at registration (a real inline `sysio.token::transfer` authorized by -`sysio@active`). The emissions formula gates on +| `LiqPoolSpec` | `sysio.liq::regliqpool(chain_code, token_code, pair_symbol, initial_chain_amount, initial_wire_amount, fee, locked_shares, conversion_horizon_sec, depth_cap_bps, clip_floor)` — mints the LCO shadow to `sysio`, deposits it with the WIRE side from the `sysio` treasury into `sysio.swap`, and creates the pair with the shadow as its yield leg. The shadow symbol must already exist; exactly one pool per shadow | +| `SyndicationSpec` | `sysio.synd::importsynd(chain_code, token_code, credits[] {pubkey, amount})`, batched by the tool; `sysio.synd::importdone` closes the import | +| `t5_dex_allocation` | none — feeds the `setemitcfg` arithmetic below | + +`TokenSpec.precision` is the **depot-frame precision**, `min(native precision, +9)`. `sysio.tokens::regtoken` rejects anything above 9, which is why V4 bounds +`precision` at 9 rather than 18: a token declared at, say, 18 would satisfy a +wider validator and then abort the irreversible bootstrap at `regtoken`. Tokens +whose native precision exceeds the frame (ETH at 18) declare the frame value and +are downscaled at the outpost boundary. + +`SyndicationSpec.pubkey` is the identity `SyndicateLIQ.user` carries: base58 of +the 32-byte Ed25519 key on SVM, 0x-hex of the 33-byte **compressed** secp256k1 +point on EVM (never the 20-byte address). Each credit mints to the account that +pubkey has linked through `sysio.authex`, or parks it until the link is made. + +### Defined but not consumed + +`bootstrap.proto` still defines `ReserveSpec`, `UwritConfig`, +`BootstrapPlatformConfig.reserves`, `BootstrapPlatformConfig.uwrit` and +`t5_reserve_allocation`, and both JSON files still carry values for them. The +contracts they targeted (`sysio.reserv`, `sysio.uwrit`) have been removed, so no +bootstrap action consumes them; only `test_bootstrap_platform_config.cpp` still +parses and validates them (V6–V9). + +## T5 DEX earmark + +With `A` = the launch T5 allotment, `D` = `t5_dex_allocation`, and +`W` = Σ `liq_pools[].initial_wire_amount`: + +`regliqpool` drains each pool's WIRE side from the `sysio` emissions treasury at +registration. The emissions formula gates on `t5_distributable − t5_floor − total_distributed`, and the per-epoch readiness gate independently checks the real treasury balance, so the earmark must sit **outside** the distributable pool: -1. Config invariant: `0 < W ≤ E`. -2. Bootstrap sets `setemitcfg.t5_distributable = A − E` (`t5_floor` unchanged — +1. Config invariant: `W ≤ D`, and `D > 0` whenever any pool is seeded. +2. Bootstrap sets `setemitcfg.t5_distributable = A − D` (`t5_floor` unchanged — the floor is inside the distributable pool). -3. End-of-bootstrap `sysio` balance ≥ `t5_distributable + E`; after reserves +3. End-of-bootstrap `sysio` balance ≥ `t5_distributable + D`; after the pools drain `W`, balance ≥ `t5_distributable`, so the readiness gate never trips - because of reserve funding. -4. Remainder `E − W` stays in the treasury, inert and outside emissions + because of pool funding. +4. Remainder `D − W` stays in the treasury, inert and outside emissions accounting. -Putting `E` *inside* `t5_distributable` instead would make the emissions math +Putting `D` *inside* `t5_distributable` instead would make the emissions math count WIRE that has physically left the treasury — the readiness gate blocks at launch scale, and effective emissions headroom silently shrinks by `W`. Hence the outside-the-pool earmark. -Each bootstrap `regreserve` transaction must carry `sysio@active` (for the -treasury drain) alongside `sysio.reserv@active`. - -## T5 DEX earmark - -`t5_dex_allocation` (`D`) is a second, parallel carve-out of the T5 allotment, -reserved for launch DEX integration — at least one DEX is seeded at launch, with -more added later by council vote. It follows the same outside-the-pool rule as -the reserve earmark, so the full T5 carve-out is `E + D` and -`t5_distributable = A − E − D`. - -It is **reserved**: the on-chain DEX-seeding mechanism is not finalized, so the -bootstrap tool does not yet drain `D` — the field is carried for forward -compatibility and defaults to `0` (earmark disabled). The launch total is a -per-deployment economics decision (Kyle / Ken own the figure). When the seeding -path lands, the `A − E` arithmetic above generalizes to `A − E − D` with no -shape change to this config. - ## Validation Parsing is **strict**: unknown / misspelled keys are rejected, not dropped, -because the file is hand-authored and drives irreversible actions. A validator -(reproduced in the test, and to be shared with the bootstrap tool) enforces: +because the file is hand-authored and drives irreversible actions. The +validator in `test_bootstrap_platform_config.cpp` enforces: | # | Invariant | |---|---| | V1 | `schema_version == 1`; `network` non-empty | | V2 | every code is a valid slug: `[A-Z][A-Z0-9_]{0,7}` -- leading character must be a letter, ≤ 8 chars | | V3 | chain codes unique; exactly one `CHAIN_KIND_WIRE` chain, code `WIRE` | -| V4 | token codes unique; `chain_code` declared; `precision` ∈ 1..9 (the depot frame — `sysio.tokens::regtoken` rejects anything higher, so a wider bound here would pass validation and then fail mid-bootstrap); native ⇔ kind `NATIVE` + empty address; non-native address well-formed for the chain kind (EVM `0x`+40 hex; SVM base58 → 32 bytes) | +| V4 | token codes unique; `chain_code` declared; `precision` ∈ 1..9 (the depot frame); native ⇔ kind `NATIVE` + empty address; non-native address well-formed for the chain kind (EVM `0x`+40 hex; SVM base58 → 32 bytes) | | V5 | exactly one native token per non-depot chain | -| V6 | reserve `(chain, token, code)` unique; references a declared binding; not on the depot; `0 < connector_weight_bps ≤ 9999`; amounts > 0 | -| V7 | Σ `initial_wire_amount` ≤ `t5_reserve_allocation` > 0 | -| V8 | `is_private` ⇒ `owner` a valid account name; else `owner` empty | -| V9 | `uwrit` present; `fee_bps ≤ 9999`; lock duration > 0 | +| V6–V9 | checks on the unconsumed `reserves` / `uwrit` / `t5_reserve_allocation` fields (see [Defined but not consumed](#defined-but-not-consumed)) | +| V10 | `t5_dex_allocation > 0` when any liq pool is seeded; Σ `liq_pools[].initial_wire_amount` ≤ `t5_dex_allocation` | +| V11 | each liq pool references a declared `TOKEN_KIND_LIQ` token on its chain and is unique; `pair_symbol` 1..7 characters `[A-Z]`; seeds > 0; `fee ≤ 9999`; `conversion_horizon_sec > 0`; `depth_cap_bps` ∈ 1..10000; `clip_floor > 0` | +| V12 | each syndication references a declared liq token on its chain; `pubkey` fits the chain family; `amount > 0` | +| V13 | a non-zero `custody_total` equals the pool's `initial_chain_amount` plus the sum of that token's `syndications` | Run the test: @@ -183,24 +142,17 @@ ninja -C build test_opp ## Follow-ups -- Wire the bootstrap tool to consume `BootstrapPlatformConfig` (the cluster phases become a - loop over the parsed config, same order: chains → tokens → bindings → - reserves → uwrit), injecting per-deployment contract addresses from - deployment artifacts into the dev config. -- Regenerate the TS/Solidity/Solana model bundles so the tool imports the - generated `BootstrapPlatformConfig` type. -- Fill in launch economics (`t5_reserve_allocation` + per-reserve amounts) and - verified mainnet addresses; freeze `dex-config.mainnet.json`. +- Wire the bootstrap tool to consume `BootstrapPlatformConfig` (the cluster + phases become a loop over the parsed config, in order: chains → tokens → + bindings → liq pools → syndication import), injecting per-deployment contract + addresses from deployment artifacts into the dev config. +- Fill in launch economics (`t5_dex_allocation` + per-pool amounts) and verified + mainnet addresses; freeze `dex-config.mainnet.json`. ## Open questions -- The earmark `E` and per-reserve amounts are economics decisions - (placeholders today). -- Launch token set: Solana SPL stables? Liquid-staking tokens (addresses exist - only once those contracts deploy)? -- Whether to seed any private reserves directly at launch, or create them all - post-launch via the authex-gated outpost flow (default assumption: all - post-launch). +- The earmark `D` and per-pool amounts are economics decisions (placeholders + today). - Whether to unify the emissions genesis numbers (`A`, `t5_distributable`, - `t5_floor`) with this config so the `A − E` arithmetic is checkable in one + `t5_floor`) with this config so the `A − D` arithmetic is checkable in one place. diff --git a/plugins/batch_operator_plugin/README.md b/plugins/batch_operator_plugin/README.md index 94edd7a820..0453c9441e 100644 --- a/plugins/batch_operator_plugin/README.md +++ b/plugins/batch_operator_plugin/README.md @@ -4,27 +4,34 @@ Cranks Depot and Outpost contracts, ferrying OPP message chains between the WIRE ## Overview -All 21 batch operators run this plugin in perpetuity. The epoch scheduler (`sysio.epoch`) assigns operators into 3 fixed groups of 7. Each epoch (every 6 minutes), one group is elected; those 7 execute the full epoch cycle. +Every batch operator runs this plugin. `sysio.epoch` keeps a sliding window of +`batch_op_groups` groups of `operators_per_epoch` operators (default 3 groups of 7, epochs +of 6 minutes); the group at the front of the window is elected for the current epoch and +its members execute the epoch cycle. Each poll the plugin also reads its own +`sysio.opreg` status and stops relaying once the operator is `SLASHED` or `TERMINATED`. ## Epoch Cycle -**Phase 1 — Outbound (WIRE → Outposts):** -1. Crank Depot (`sysio.msgch::crank`) to produce outbound OPP Message Chain -2. Read the produced chain from Depot tables -3. Deliver chain to Outpost contract (ETH: `OPPInbound.epochIn()`, SOL: `epoch_in()`) -4. All 7 independently verify the delivered chain +Run by every member of the elected group, for every active outpost chain: -**Phase 2 — Inbound (Outposts → WIRE):** +**Outbound (WIRE → Outposts):** +1. Read the outpost's outbound envelope that `sysio.epoch::advance` built + (`sysio.msgch::buildenv`) from the depot's `outenvelopes` table +2. Deliver it to the Outpost contract, chunked (ETH: `OPPInbound.epochIn()`, SOL: `epoch_in`) +3. The outpost reaches consensus once enough group members have delivered identical bytes + +**Inbound (Outposts → WIRE):** 1. The consensus-reaching delivery emits the outpost's outbound envelope 2. Read the latest outbound envelope from Outpost storage 3. Deliver its raw protobuf bytes to Depot (`sysio.msgch::deliver`) -4. Depot evaluates consensus across all 7 deliveries +4. Depot evaluates consensus across the group's deliveries ## Depot cranks Every epoch poll (`--batch-epoch-poll-ms`) also pushes the depot actions nothing on -chain schedules. `sysio.msgch::chkcons` comes only from the elected operator; the -rest come from every ACTIVE operator, because the elected one may be the operator +chain schedules. `sysio.msgch::chkcons` — which advances the epoch once every active +outpost has consensus and the boundary has passed — comes only from elected operators; +the rest come from every ACTIVE operator, because the elected one may be the operator that is offline, and each is a cheap no-op once its work is done: | Action | When | Why nothing else drives it | @@ -34,7 +41,8 @@ that is offline, and each is a cheap no-op once its work is done: | `sysio.liq::queueyield(sym)` | every shadow with yield pending from an outpost `LIQ_YIELD` report | the report lands in `sysio.liq`'s pending balance; queuing it into the swap is a separate, permissionless step | The two yield cranks stay idle, without logging a read failure per poll, until both -`sysio.swap` and `sysio.liq` are deployed on the depot. +`sysio.swap` and `sysio.liq` are deployed on the depot. The plugin does not crank +`sysio.synd::crank`; syndication release is driven by keepers. ## Configuration @@ -79,5 +87,5 @@ address rather than left pointing at the old one. - `chain_plugin` — blockchain state access - `cron_plugin` — irreversible block event subscription - `signature_provider_manager_plugin` — signing key management -- `outpost_ethereum_client_plugin` — ETH RPC calls (future) -- `outpost_solana_client_plugin` — SOL RPC calls (future) +- `outpost_ethereum_client_plugin` — Ethereum outpost relay +- `outpost_solana_client_plugin` — Solana outpost relay diff --git a/plugins/chain_interface/README.md b/plugins/chain_interface/README.md index ef0644b968..c4fbe6dd8b 100644 --- a/plugins/chain_interface/README.md +++ b/plugins/chain_interface/README.md @@ -39,9 +39,9 @@ returned `handle` for the lifetime of its subscription or registration. | `channels::applied_transaction` | `transaction_trace_ptr` | `chain_plugin`, relaying `controller::applied_transaction` | `low` | | `compat::channels::transaction_ack` | `std::pair` | `producer_plugin` after a speculative transaction is applied or rejected, and `chain_plugin`'s transaction-retry database | `low` | -`irreversible_block` is the one most non-chain plugins consume: `batch_operator_plugin` and -`underwriter_plugin` each subscribe to it as their sync gate, arming their work only once the node has caught -up. `net_plugin` subscribes to `compat::channels::transaction_ack` so it can relay or drop a peer's +`irreversible_block` is the one most non-chain plugins consume: `batch_operator_plugin` subscribes to it as +its sync gate, arming its work only once the node has caught up, and `status_monitor_plugin` renders one +document per irreversible block. `net_plugin` subscribes to `compat::channels::transaction_ack` so it can relay or drop a peer's transaction according to the result. ### Methods @@ -81,9 +81,8 @@ sync_gate_subscription = ``` [`usage_pattern.md`](../usage_pattern.md) lists connecting to other plugins' signals and registering -provider methods among the typical `plugin_initialize` actions; the sync-gate consumers in this tree -(`batch_operator_plugin`, `underwriter_plugin`) subscribe from `plugin_startup` instead, after their own state -is built. Either way, keep the returned handle — dropping it cancels the subscription. +provider methods among the typical `plugin_initialize` actions; the sync-gate consumer in this tree +(`batch_operator_plugin`) subscribes from `plugin_startup` instead, after its own state is built. Either way, keep the returned handle — dropping it cancels the subscription. ## Options @@ -113,6 +112,7 @@ to the ack channel. Both run under their own plugin's test target. this include path public to the rest of the tree. - `net_plugin` — subscribes to `transaction_ack`, and feeds every transaction it accepts from a peer into `transaction_async` via `chain_plugin::accept_transaction`. -- `batch_operator_plugin`, `underwriter_plugin` — subscribe to `irreversible_block` as their sync gate. +- `batch_operator_plugin` — subscribes to `irreversible_block` as its sync gate. +- `status_monitor_plugin` — subscribes to `irreversible_block` to ship one snapshot per irreversible block. - [`plugins/usage_pattern.md`](../usage_pattern.md) — when to connect, register, and release these handles across the appbase lifecycle. diff --git a/plugins/chain_plugin/README.md b/plugins/chain_plugin/README.md index f2546e0e78..84b21966dc 100644 --- a/plugins/chain_plugin/README.md +++ b/plugins/chain_plugin/README.md @@ -3,9 +3,9 @@ `chain_plugin` owns the blockchain itself. It constructs and configures the `controller` — block log, chain state database, fork database, WASM runtime, protocol features, whitelists and blacklists — brings it up from genesis, a snapshot, or existing state, and relays the controller's signals onto the appbase channels -declared in `chain_interface`. Those channels have few subscribers: `batch_operator_plugin` and -`underwriter_plugin` take `irreversible_block` as their sync gate, and nothing else subscribes to any of -them — `net_plugin` connects to the controller's signals directly instead. `nodeop` initializes it on every +declared in `chain_interface`. Those channels have few subscribers: `batch_operator_plugin` takes +`irreversible_block` as its sync gate and `status_monitor_plugin` ships one snapshot per irreversible block; +nothing else subscribes to any of them — `net_plugin` connects to the controller's signals directly instead. `nodeop` initializes it on every run alongside `resource_monitor_plugin`, `net_plugin`, and `producer_plugin`, so there is no `plugin =` line to add; it pulls in `signature_provider_manager_plugin` through `APPBASE_PLUGIN_REQUIRES`, which in turn pulls in `http_client_plugin`. It registers no HTTP endpoints of its own — `chain_api_plugin` publishes the diff --git a/plugins/cron_plugin/README.md b/plugins/cron_plugin/README.md index a8f4430203..bb9c39eba4 100644 --- a/plugins/cron_plugin/README.md +++ b/plugins/cron_plugin/README.md @@ -2,9 +2,9 @@ `cron_plugin` provides `nodeop` with an in-process cron scheduler: other plugins register functions against cron-style schedules and the plugin fires them on a worker pool. It is infrastructure, not an operator-facing -feature -- it exposes no HTTP endpoint and does nothing on its own. Operators rarely name it: both -`batch_operator_plugin` and `underwriter_plugin` declare it in `APPBASE_PLUGIN_REQUIRES`, so enabling either one -loads it automatically. It has no dependencies of its own (`APPBASE_PLUGIN_REQUIRES()` is empty). +feature -- it exposes no HTTP endpoint and does nothing on its own. Operators rarely name it: +`batch_operator_plugin` declares it in `APPBASE_PLUGIN_REQUIRES`, so enabling the batch operator loads it +automatically. It has no dependencies of its own (`APPBASE_PLUGIN_REQUIRES()` is empty). ## How it works @@ -74,7 +74,7 @@ discovered, so its OPP polling jobs do not contend with anything else on the sha ## Enabling / configuration -No `plugin =` line is normally needed -- `batch_operator_plugin` and `underwriter_plugin` both require it. +No `plugin =` line is normally needed -- `batch_operator_plugin` requires it. Naming it explicitly is harmless. `config.ini`: @@ -147,7 +147,5 @@ trigger pre-computation, the scheduler waking when a new job is added, and day-o - [`batch_operator_plugin`](../batch_operator_plugin) -- requires this plugin, and additionally creates its own private `cron_service` sized from the discovered outpost count for OPP polling jobs. -- [`underwriter_plugin`](../underwriter_plugin) -- requires this plugin and schedules its scan job on the shared - service. - [`resource_monitor_plugin`](../resource_monitor_plugin) -- a plugin with its own dedicated timer thread rather than a cron job; the two are independent. diff --git a/plugins/external_debugging_plugin/README.md b/plugins/external_debugging_plugin/README.md index f3468ecf4c..dcea6b8e11 100644 --- a/plugins/external_debugging_plugin/README.md +++ b/plugins/external_debugging_plugin/README.md @@ -175,5 +175,3 @@ it. No test dials a real server. plugin's `ext-debugging-*` triple layers over. - [`prometheus_plugin`](../prometheus_plugin) -- its `nodeop_outbound_http_*` counters include this plugin's outbound traffic, since all outbound callers share one transport. -- [`underwriter_plugin`](../underwriter_plugin) -- the other OPP-side plugin; it has its own - `/v1/underwriter/*` diagnostics rather than an external sink. diff --git a/plugins/http_plugin/README.md b/plugins/http_plugin/README.md index fb0d75c77f..194c535cc0 100644 --- a/plugins/http_plugin/README.md +++ b/plugins/http_plugin/README.md @@ -94,7 +94,6 @@ be named in a `plugin` option, or startup fails with `plugin_config_exception`. | `trace_api` | `sysio::trace_api_plugin` | | `prometheus` | `sysio::prometheus_plugin` | | `test_control` | `sysio::test_control_api_plugin` | -| `underwriter` | `sysio::underwriter_plugin` | `node` is the category of endpoints served on every listener; it is what `http-server-address` and `unix-socket-path` bind, and it is not a value accepted by `--http-category-address`. @@ -162,7 +161,7 @@ All of `http_plugin`'s options are config-file options, so each is equally valid |---|---|---| | `http-server-address` | `127.0.0.1:8888` | Local IP and port to listen on for incoming HTTP connections. Set to the literal `http-category-address` to enable the `http-category-address` option; leave blank to disable. Under `kiod` the option is registered with no default. | | `unix-socket-path` | unset | Filename, relative to the data dir, of a unix socket for HTTP RPC; blank disables it. Must not be set when `http-category-address` is used. Under `kiod` it defaults to `kiod.sock`. | -| `http-category-address` | unset | `category,address` pair binding one API category to one listen address; may be repeated. The address is `:port`, `:port`, or a unix socket path starting with `/`, `./`, or `../`. Valid categories are `chain_ro`, `chain_rw`, `db_size`, `net_ro`, `net_rw`, `producer_ro`, `producer_rw`, `snapshot`, `trace_api`, `prometheus`, `test_control`, `snapshot_ro`, and `underwriter`. `kiod` does not register this option. | +| `http-category-address` | unset | `category,address` pair binding one API category to one listen address; may be repeated. The address is `:port`, `:port`, or a unix socket path starting with `/`, `./`, or `../`. Valid categories are `chain_ro`, `chain_rw`, `db_size`, `net_ro`, `net_rw`, `producer_ro`, `producer_rw`, `snapshot`, `trace_api`, `prometheus`, `test_control`, and `snapshot_ro`. `kiod` does not register this option. | ### Limits and threads @@ -234,5 +233,5 @@ request-body reservation. - `producer_api_plugin` — registers the `producer_ro`, `producer_rw`, and `snapshot` endpoints. - [`snapshot_api_plugin`](../snapshot_api_plugin/README.md) — registers the `snapshot_ro` endpoints, and is the consumer of `add_raw_handler` for file downloads. -- `db_size_api_plugin`, `trace_api_plugin`, `prometheus_plugin`, `test_control_api_plugin`, - `underwriter_plugin` — the remaining category owners. +- `db_size_api_plugin`, `trace_api_plugin`, `prometheus_plugin`, `test_control_api_plugin` — the remaining + category owners. diff --git a/plugins/outpost_client_plugin/README.md b/plugins/outpost_client_plugin/README.md index 556684f25c..c3b385af5c 100644 --- a/plugins/outpost_client_plugin/README.md +++ b/plugins/outpost_client_plugin/README.md @@ -20,7 +20,7 @@ before a chain-specific client tries to resolve one. ### `sysio/outpost_client/outpost_client.hpp` — the SPI `outpost_client` is the abstract interface between the plugins that orchestrate OPP work -(`batch_operator_plugin`, `underwriter_plugin`) and the chain-specific concretes. An orchestrator holds an +(`batch_operator_plugin`) and the chain-specific concretes. An orchestrator holds an `outpost_client_ptr` (a `std::shared_ptr`) and calls only these virtuals; it never interprets an EVM address, a Solana public key, a PDA, or a signature-provider format. @@ -33,7 +33,7 @@ an EVM address, a Solana public key, a PDA, or a signature-provider format. | `to_string()` | `{chain_code}:{ChainKind_Name}:{chain_id}`. The default prints `chain_code()` as the raw packed integer, so the row registered as `ETH` against a local chain renders `23373212024832:CHAIN_KIND_EVM:31337`. Virtual, with a default derived from the three getters | | `deliver_outbound_envelope(epoch_index, envelope_bytes, deadline)` | OPP outbound — submit one envelope to the remote chain; returns the chain-native transaction id | | `read_inbound_envelope(epoch_index, deadline)` | OPP inbound — return the envelope the remote chain produced for this epoch, or an empty vector when the latest slot does not match | -| `uw_commit(uw_request_id, uic_bytes, deadline)` | Underwriter commit — relay a signed `UnderwriteIntentCommit` through the outpost; returns only after on-chain confirmation | +| `crank_outpost(epoch_index, deadline)` | Drive the outpost's permissionless per-epoch instructions right after this operator's delivery lands; best-effort, retried next epoch. The default cranks nothing; Solana overrides it with `report_liq_yield`, Ethereum with `SyndicationPool.realizeYield` | Two rules bind every implementation. Each RPC-bound call must enforce the `deadline` it is passed, so a hung remote chain cannot starve a cron worker; the protected helper `throw_if_past_deadline(deadline_abs, op)` @@ -136,4 +136,4 @@ so the base class's default implementation is not under test. Higher-fidelity mo - [`outpost_solana_client_plugin`](../outpost_solana_client_plugin/README.md) — the Solana concrete. - `signature_provider_manager_plugin` — required dependency; supplies the signers the concretes resolve by name. - `http_client_plugin` — owns the process-wide `--outbound-http-*` transport options this plugin's RPC policy reads. -- `batch_operator_plugin`, `underwriter_plugin` — the SPI's consumers. +- `batch_operator_plugin` — the SPI's consumer. diff --git a/plugins/outpost_ethereum_client_plugin/README.md b/plugins/outpost_ethereum_client_plugin/README.md index 097c8b1dfc..65b6f47c29 100644 --- a/plugins/outpost_ethereum_client_plugin/README.md +++ b/plugins/outpost_ethereum_client_plugin/README.md @@ -3,10 +3,10 @@ `outpost_ethereum_client_plugin` owns this node's connections to EVM chains. It builds one policy-enforcing `ethereum_client` per configured endpoint — each bound to a named Ethereum signature provider and to a verified numeric chain id — loads the contract ABIs those connections are driven through, and hands out -`outpost_client` concretes that speak the chain-agnostic OPP SPI on top of `OPP.sol`, `OPPInbound.sol`, and -`OperatorRegistry.sol`. An operator enables it whenever the node runs an OPP daemon against an EVM outpost: -`batch_operator_plugin` and `underwriter_plugin` both name it in `APPBASE_PLUGIN_REQUIRES`, so either of -those pulls it in, and it can also be loaded alone (as the bundled RPC tool does) to talk to an EVM endpoint +`outpost_client` concretes that speak the chain-agnostic OPP SPI on top of `OPP.sol` and `OPPInbound.sol` +(plus the outpost's `SyndicationPool.sol` for the per-epoch crank). An operator enables it whenever the node +runs an OPP daemon against an EVM outpost: `batch_operator_plugin` names it in `APPBASE_PLUGIN_REQUIRES`, so +the batch operator pulls it in, and it can also be loaded alone (as the bundled RPC tool does) to talk to an EVM endpoint directly. ## How it works @@ -27,19 +27,19 @@ plugin_initialize v publish the client map (only after every client succeeded) -batch_operator_plugin / underwriter_plugin - | create_outpost_client(client-id, chain_code, chain_id, opp, oppInbound, operatorRegistry) +batch_operator_plugin + | create_outpost_client(client-id, chain_code, chain_id, opp, oppInbound) v -outpost_ethereum_client -- deliver_outbound_envelope / read_inbound_envelope / uw_commit +outpost_ethereum_client -- deliver_outbound_envelope / read_inbound_envelope / crank_outpost ``` ### Endpoints, signers, and chain ids Clients come from exactly one of two sources. The check is an exclusive-or, so configuring both **and** configuring neither are rejected with the same message, `Configure exactly one of ---outpost-ethereum-client-config-file or --outpost-ethereum-client`. Because `batch_operator_plugin` and -`underwriter_plugin` name this plugin in `APPBASE_PLUGIN_REQUIRES`, a node that loads either of them must -configure an Ethereum client source or it will not start, even if it only intends to serve a Solana +--outpost-ethereum-client-config-file or --outpost-ethereum-client`. Because `batch_operator_plugin` names +this plugin in `APPBASE_PLUGIN_REQUIRES`, a node that loads it must configure an Ethereum client source or it +will not start, even if it only intends to serve a Solana outpost. A **command-line spec** is `,,[,]`. With three fields @@ -87,9 +87,9 @@ endpoint cannot occupy a cron worker past its budget. ### The contract clients -Three typed wrappers are built over the shared connection, each only when its address was supplied to -`create_outpost_client`; passing an empty string for the others is normal, and calling an SPI method whose -wrapper was not provisioned asserts with a message naming the missing address. A fourth, the syndication +Two typed wrappers are built over the shared connection, each only when its address was supplied to +`create_outpost_client`; passing an empty string for the other is normal, and calling an SPI method whose +wrapper was not provisioned asserts with a message naming the missing address. A third, the syndication pool's, is bound by the crank once the outpost names the pool (see [Outpost cranks](#outpost-cranks)). State-changing calls go through `create_tx_and_confirm`, which returns only after on-chain inclusion plus confirmations — OPP writes are consensus-critical and must not silently drop. @@ -98,7 +98,6 @@ confirmations — OPP writes are consensus-critical and must not silently drop. |---|---|---| | `opp_contract_client` | `OPP.sol` | `emitOutboundEnvelope(uint32)` (recovery-only write; no in-tree steady-state caller), `getLatestOutboundEnvelope()` view | | `opp_inbound_contract_client` | `OPPInbound.sol` | `epochIn(uint32,uint16,uint16,uint32,bytes)`, `discardEnvelopeChunks()`, `nextEpochIndex()` view, `envelopeChunkState(address)` view, `attestationHandlers(uint16)` view | -| `operator_registry_contract_client` | `OperatorRegistry.sol` | `commit(bytes)` | | `syndication_pool_contract_client` | `SyndicationPool.sol` (Wire-Network/wire-ethereum#207) | `realizeYield()` | ### Outbound delivery and chunking @@ -127,7 +126,7 @@ malformed state decodes as all-zero and degrades to starting fresh, which the co no-ops. A mid-sequence failure simply abandons the tick; the next one restarts from the on-chain high-water mark. -### Inbound reads and underwriter commits +### Inbound reads `read_inbound_envelope` makes one `getLatestOutboundEnvelope` view call at the **`finalized`** block tag, not `latest`. WIRE consensus on inbound is committed forward against this read, so an operator that read at @@ -135,11 +134,6 @@ mark. compared with the requested one and a mismatch returns an empty vector at debug level, since observing the preceding epoch is normal until the consensus-reaching delivery overwrites the slot. -`uw_commit` hex-encodes the canonical `UnderwriteIntentCommit` bytes and calls `OperatorRegistry.commit`, -returning the transaction hash only after confirmation. The outpost binds the signed EVM caller and the -claimed ACTIVE roster identity before queuing the unchanged bytes; the WIRE depot remains authoritative for -the embedded permission signature and bond. - ### Outpost cranks Once per epoch, right after this operator's envelope delivery lands, the outbound relay job calls @@ -264,7 +258,6 @@ Per-client runtime lines are prefixed with the SPI label `outpost_ethereum_clien - `resuming epoch=... delivery at chunk ...`, `discarded a superseded epoch=... staging header`, and `skipping epoch=... delivery — the outpost has ...` on the chunk-resume paths. - `read inbound envelope epoch=... bytes=...` after a successful inbound read. -- `uw_commit confirmed uwreq=... tx_hash=... bytes=...` after a confirmed underwriter commit. - Warning-level lines for every malformed view result (`envelopeChunkState returned non-string variant`, `latestOutboundEnvelope data_ not a string`, and siblings); an epoch mismatch on the inbound slot stays at debug level so steady-state polling is not noisy. @@ -313,4 +306,3 @@ counter address. - `signature_provider_manager_plugin` — required dependency; supplies the Ethereum signer resolved by name. - `http_client_plugin` — owns the process-wide `--outbound-http-*` transport fallbacks. - `batch_operator_plugin` — calls `create_outpost_client` for the OPP envelope path (`OPP.sol` + `OPPInbound.sol`). -- `underwriter_plugin` — calls `create_outpost_client` for the `OperatorRegistry.sol` commit relay. diff --git a/plugins/outpost_solana_client_plugin/README.md b/plugins/outpost_solana_client_plugin/README.md index 21902732b5..01855b4d67 100644 --- a/plugins/outpost_solana_client_plugin/README.md +++ b/plugins/outpost_solana_client_plugin/README.md @@ -94,9 +94,10 @@ Loads one or more Anchor IDL JSON files for use with `solana_program_client`: Anchor IDL program name of the Solana OPP outpost. When an outpost client is constructed, the loaded `--solana-idl-file` set is filtered to programs whose -IDL name matches this value. The default targets the standalone `opp_outpost` -program; the clean-room outpost implementation is hosted inside the -`liqsol-core` program, whose generated IDL is named `liqsol_core`: +IDL name matches this value. The default names a standalone `opp_outpost` +program, which wire-solana no longer builds: the OPP outpost is hosted inside the +`liqsol-core` program, whose generated IDL is named `liqsol_core`, so deployments +pass: ``` --solana-outpost-program-name liqsol_core @@ -451,25 +452,23 @@ Settlement of a consensus envelope is a separate instruction (`dispatch_attestat driven from the on-chain cursor, and every effect account a handler resolves out of `remaining_accounts` must be derived by this relay. `extract_inbound_effects` walks the envelope once, in dispatch order, and `build_dispatch_manifests` derives one manifest per -attestation. The shapes and what each derives: +attestation. Attestations that need no effect account (`OPERATORS`, `BATCH_OPERATOR_GROUPS`) +keep an empty manifest so the indices stay aligned with the on-chain cursor. The one shape +that needs effect accounts, and what it derives: | `effect_shape` | Attestation | Accounts derived | |---|---|---| -| `withdraw_remit`, `slash`, `deposit_revert` | `OPERATOR_ACTION` | the operator / depositor, their `CollateralPosition` PDA, and under SPL custody the collateral vault, the destination ATA and the token program | -| `swap_remit`, `swap_revert` | `SWAP_REMIT`, `SWAP_REVERT` | the `Reserve` PDA, and under SPL custody the reserve vault, the recipient's ATA, the custody mint, its token program and any transfer-hook metas | -| `reserve_ready`, `reserve_create_cancelled` | `RESERVE_READY`, `RESERVE_CREATE_CANCELLED` | the `Reserve` PDA, plus the creator's refund accounts for the cancel | | `desyndicate_liq` | `DESYNDICATE_LIQ` | the liqSOL pool's `GlobalState` and `DistributionState` singletons, the pool authority, the pool and user Token-2022 ATAs with their `UserRecord`s, the bucket ATA, the liqSOL mint, Token-2022, the bucket authority, the mint's transfer-hook program and extra-metas PDA, and liqsol-core itself; plus, for every non-zero request id and even when `DistributionState` cannot be read, the writable `PendingPayout` PDA (`["pending_desyndication", request_id_le8]`) the handler stores the payout at whenever it cannot pay inline: the outpost is frozen, the solvency check finds a custody shortfall, a share record is still the legacy layout, or the settlement meets any other chain-state refusal (wire-solana `PendingPayoutReason`: `OutpostFrozen`, `CustodyShortfall`, `LegacyUserRecord`, `SettlementRefused`). Without it any of these aborts the dispatch with `EffectAccountMissing` | -Custody is read from the account the on-chain handler branches on (`Reserve`, -`CollateralPosition`, `DistributionState`), never from the mutable `OutpostConfig` token -map. An absent account degrades to the accounts the handler needs to log-and-skip; a +Custody is read from the account the on-chain handler branches on (`DistributionState`), +never from the mutable `OutpostConfig` token map. An absent account degrades to the accounts the handler needs to log-and-skip; a present but unreadable one throws, because a guessed manifest is one the program is guaranteed to abort on. Each shape's account list is in lock-step with the program's `require_remaining_account` calls in wire-solana `inbound.rs`: a program-side change to what a handler requires and the matching shape here must move together. The relay -boot-checks the declarations it decodes (`Reserve`, `CollateralPosition`, -`EpochDeliveries`, `LatestOutboundEnvelope`, and `DistributionState` on a program that -declares it) so a drifted IDL fails at startup rather than on the first drain. +boot-checks the declarations it decodes (`EpochDeliveries`, `LatestOutboundEnvelope`, and +`DistributionState` on a program that declares it) so a drifted IDL fails at startup rather +than on the first drain. ## Outpost Cranks diff --git a/plugins/signature_provider_manager_plugin/README.md b/plugins/signature_provider_manager_plugin/README.md index 215616e7fa..cbce5bd6a9 100644 --- a/plugins/signature_provider_manager_plugin/README.md +++ b/plugins/signature_provider_manager_plugin/README.md @@ -3,7 +3,7 @@ `signature_provider_manager_plugin` owns every signing key the node uses. It parses each `--signature-provider` spec, builds the matching signer, and publishes the whole set through one lookup API (by name, by public key, by chain kind, by key type) that `chain_plugin`, `producer_plugin`, -`net_plugin`, the outpost clients, and `underwriter_plugin` query. An operator never enables it by hand: +`net_plugin`, and the outpost clients query. An operator never enables it by hand: every one of those plugins names it in `APPBASE_PLUGIN_REQUIRES`, so appbase loads and initializes it before any consumer. It requires `http_client_plugin`, which it uses for the `KIOD:` scheme. @@ -59,7 +59,7 @@ there is no flag to skip it. The provider set is immutable once the node is running. Every mutator asserts it was called before startup, and all mutation happens on the main thread during the sequential initialize/startup phases, so runtime -lookups from producer, batch-operator, underwriter, and outpost threads need no synchronization. +lookups from producer, batch-operator, and outpost threads need no synchronization. ### Generated default providers @@ -171,6 +171,6 @@ on the generated defaults file. The AWS-backed schemes are tested with their own the `kiod` side of a `KIOD:` provider. - `http_client_plugin` — the HTTP client a `KIOD:` provider signs through; required by this plugin. - `chain_plugin`, `producer_plugin`, `net_plugin`, `outpost_client_plugin`, - `outpost_ethereum_client_plugin`, `outpost_solana_client_plugin`, `underwriter_plugin` — consumers that - require this plugin and therefore load it automatically. + `outpost_ethereum_client_plugin`, `outpost_solana_client_plugin` — consumers that require this plugin and + therefore load it automatically. - `docs/signature-provider-manager-plugin.md` — the spec reference with worked examples. From b30b9d87c1db3f2e0b3cf828e2871f43f7729af6 Mon Sep 17 00:00:00 2001 From: David J Parrott Date: Tue, 6 Oct 2026 17:05:08 -0400 Subject: [PATCH 5/5] Restore unrelated contract WASM binaries Restore twelve contracts to the syndication parent artifacts where no executable source dependency changed. Retain the source-backed WASM updates and intended contract deletions. Validation: exact base-byte checks and all 39 contract suites passed using the corrected artifacts. Change-Id: I5a96160a281ddf2b4506705d644a7f830f3bb41f --- contracts/sysio.andon/sysio.andon.wasm | Bin 14067 -> 13756 bytes contracts/sysio.authex/sysio.authex.wasm | Bin 44293 -> 36689 bytes contracts/sysio.bios/sysio.bios.wasm | Bin 53891 -> 40789 bytes contracts/sysio.chains/sysio.chains.wasm | Bin 41528 -> 39121 bytes contracts/sysio.councl/sysio.councl.wasm | Bin 62727 -> 48345 bytes contracts/sysio.dclaim/sysio.dclaim.wasm | Bin 40384 -> 31229 bytes contracts/sysio.msig/sysio.msig.wasm | Bin 39453 -> 36752 bytes contracts/sysio.roa/sysio.roa.wasm | Bin 76719 -> 53726 bytes contracts/sysio.swap/sysio.swap.wasm | Bin 59215 -> 49661 bytes contracts/sysio.token/sysio.token.wasm | Bin 12623 -> 10465 bytes contracts/sysio.tokens/sysio.tokens.wasm | Bin 33639 -> 30245 bytes contracts/sysio.wrap/sysio.wrap.wasm | Bin 6814 -> 5033 bytes 12 files changed, 0 insertions(+), 0 deletions(-) diff --git a/contracts/sysio.andon/sysio.andon.wasm b/contracts/sysio.andon/sysio.andon.wasm index 38afcff125a1dec72b5b919a85455e9285f43695..a7f829daac5adb5862f05d148108b9a0aa353f29 100755 GIT binary patch literal 13756 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 XU9zr6z{h%vCblsO(1$ePNW1$lR6j0Y literal 14067 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 UfcoXNMI-$PRPeNuUh6vdpH={W$^ZZW diff --git a/contracts/sysio.authex/sysio.authex.wasm b/contracts/sysio.authex/sysio.authex.wasm index 7ff42952e2906d95a5f848558ec05016c3521756..28f7a317bec0c75339c8ffff0e1539dd003c49ff 100755 GIT binary patch literal 36689 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 literal 44293 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 II?ZhSKPU2LF8}}l diff --git a/contracts/sysio.bios/sysio.bios.wasm b/contracts/sysio.bios/sysio.bios.wasm index 08ab88e9c1f065f5a1d707f934746a52d46ff596..00e966b25d7b5d9968d902ab80ea0efa401c4348 100755 GIT binary patch literal 40789 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 L7jsjKJ@5YlzMD|4 literal 53891 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 diff --git a/contracts/sysio.chains/sysio.chains.wasm b/contracts/sysio.chains/sysio.chains.wasm index 829c7cc62c45ce54f4389386f0a104270b2f54a4..4602f132924a8c8565d7aa465739918eccc7e517 100755 GIT binary patch literal 39121 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 N746kZ4}Xv6{eRr%Z|DF3 literal 41528 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 F{{y^L$j1Nx diff --git a/contracts/sysio.dclaim/sysio.dclaim.wasm b/contracts/sysio.dclaim/sysio.dclaim.wasm index 7bbac61485c91807bc300fb1e98715732ffeed8f..d824580e6aefaf90ad4874145d403e75dca23b07 100755 GIT binary patch literal 31229 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 IWuEu{0h!DDi~s-t literal 40384 zcmeI53zS{gS>Mm=K4$KWbYzQUVkybF7t@YGN=0xaR|ujrP5g)hd0=QlQ{=Jc+Mb7I zq?wT{1*4f@#i-PPR_X+&m`0@zc9WRQT9{RpxQhu%z*%({E^)vV(~7PD39E~SMHaY= z6!-W4_CDu6=8lxad6>1T#B=uDXFtFFec%3Gd!Jx(=|~s^LHLK^D{l>gTf@`AtwDJD z)@0@M%1XF$YrJBQs(z9>eQUI`LMi*NczycTie8de6kDN<6&2-gqMAHi84^`%iqdtL zP{nA4e$`jtdJb-_oYq_Yv`57T5ZpRs&(r)Zdm~|G$)OLD>iRV8g&J>snq>68WM|x- zKQXe{K6ZS1v3+Rr`0|}WsI2tx@x{gV{PLm2_Tl#QiS}X;DZ4Ry;?UuR`Q^#!`K2IM zPW4Fp$l;@>g2aCCda1n}RP>`VdE3I`au6szvUF-`dg0LI(o&nOsceNJ}-L`Oie#&(ek6SSM!#XOg%kF+~;RJl&mHdJyJ+ea2ow1Z)_U!}D>m};YI z+)K-o%g2}8aMH=c%hL<9vRb=>)b=Z{{I=RlUV7!|*p;=HzT{=2 zqgU;C>B~l|)oSg^+D`tB^1q#Em~n{q5ivhM>C8L#&nUsycLXwdC;`MpTD^ky?!*z zqHRGi5;PiF(5TW*nz_d-+@sZaGGyjiphh(RJU;gN(Lkx)0l>)k-|SW@Gm}>6US=V$ z)O3(XgEHemnTg9B4I4n^?wMx9d#%SapVi8Y%j4{m72-4n`yMKLQnd_FwJVq~l|Wmjfe ziLc=1S@H8ssFPLb%GJ1jG@|tBp3(qd)6yE>PxO=qGMko8=o>f)d+r7fHlUHXktIy9 z(hNqTP7)aUT%lIH$;@o}0QDv&VKdGFWPPq#0gNCSGg}%6X=d3wk{GN8zQQIg9m9CbcRzjE1yp3)|NSvu3VY zWwr?<;zlJ%D?#I94r3>ReM}jtg8k(O7OnTN*=Z^LUMDp1j7t|vLIT5^dG)Ti{#9Z|XL*vUGpYM5Yk%+oJ?k_4`2YhCnS=e#!$qFSOrDUVE+pZROXvZIyk zi|YaxycRfmpgt_R1}^9I1TGHht+-fyHW0V^;xj+x`pH+L zbep)@jmGT~enY%eO>FD=?ccPmC(3^=5}e%fikbc#^S(j2Lgr%AD!>+VQ9e7CuO%NvJXS;Yb6oPYw!tt{oQJkj{OYE{4~ z{sTm-iNl}}DuE*Q#m~{FM~yEk8SaaP5nr^aelhllv1j7<-}C7YCCwO)lnCo$IOxOq z*m6F0JpT>m{IZ+jM)5@btR~0w;)&J2i|?5T-okoSnlYTWYO~L)46gcy9gMq0a<-Y! zQpFd3>u5|`ZD7c21`Buv&HNl57W{nNkY zSMGs$1}t#QG(TrQTD6JbhppCMIMIShhnlXN^cMq5M>FYnx03YL>L&kuB!<|k3JI&g zTbbk0m%>)nP=~yl@{;_yU_-$~%+sM5OIw2pYCsiQO<{H7kG33es{Edh2kF(66{8g} ztx4msi8!oyCLES|W{D9b*d-1t3a##qQo)&5#$m}5oq?SI+n8)s%`*bAS@Jxa)hJpF zF;F=_oO~S+&>$nr;(hTR9<_b(I1g|n<54kG5vRED*?0fWpZ>tU_{~gW^~s-p=vT$! z_Qf}fm9S0tskwHp%hNP#C62-T+Q;7iYVj5}t9>yYdqm*{Q&o$}vN=I;)!W7pGSvWf?N;oU*KlbQO= z{PVFbsP&L^RzB)kQ5b6i^_g^3pGp5=(DJ8YU2m zNhJl7N*FhY@b8{y5>%jItZhOY6$B_r1IAtFv0__nK0Y(?FMj2N@A+(9Kn(J1MwG48 zm`i!;HPRHlZJ=DEIwS0o7)7~>5Y$m_1m*HyLAlXvGomGy(a2nsn}~7~3VBIJwLRs6 zm>VfKgOi(FAaW@NyvGp(I6MF-n!5uEv>v2&I7k<8hOT15mxBaFLIa&JThfV!@FE4z z0JtC+>_wzwr;}jtsVJ(#-kFob14$5plrENDLS5Y<$sP$Nd&B@8QjF{Oc;|Pf-&yDP zn0}9yzlEEr1c#s;kk*FgZ8Ofl?3kSdYU(-G*My0HJ3)ROl-#Tt4~z4U2NDy-hmc8{ ztx8jDFhhhmnbkn_kkZU;J-B%X?~$P-ZzHZzCf*y}O{XH4R=uE-dc%;R?vO#Mm}(>~ zD2$DqCaifDn5OmSBn*$o-4yVB48a70#!tt{R~3no5+I;a4OhMSDT+A?u>nJ~0%p?R zFKS}})w^E!;)b~JlO!EB&SI2mEZMDG7x7op*D&4Q44Y9GQa6wRn6zyaevyq+!n6GP zCT{5{k+?-O7M^PPnm`ryuM{j+k0G~+j>899Iws*4(0FE$4Y{QT2}*7vzAtV9;#6G< zl+rK^Oo?AthNRWEZA44z_7P3p*rsrS68v$SPvm$Q57xFs>3gFH6NT0S?cPLPy@%Do zAV6yn{A!_I#u|nvs1+VVIt2m}F&;G~jXeFTOgta_2&-0^sF^&0DB-ZsSg6ob@^U6L z*=J}&S;IG^4fZ=BjoV*6GQfvrZm7M%M20_Oi-!4tvUMS943wLR+Uv^#Xj_qtgfdn| zYMC?zSB-waAWbg>GRaIjCy6f=X+ILh6_eb6MCNpxP>@K`OMrxU|90Dw*bK&_w8h{M z7-U;9L`aT5+N|cONmtUym|WEWQsW6gs##6472uHwfcVcIef_uJAJ0tnHE%U z0VOISAEqM^qH4Mw+$xdJzD$)&koqqn)*uQo;MEBA6yUxqg3kc*_egMeFT_Rw)lAOR z!UF-=tm4Y3lEKc^lUGq*+M9=Nhvb0s?ut0F>9Hf5@=9J3Bb@vuR&-lG(lwAFC?VgD-%UxZ4ZtFc0^?T3gMg6R# zRx(JBDNQ-$kCR3WB6Z!_GiT1gK%oHv2k`FFdb<@vwgKhBeAi5R)m08eyP#TAHpxa- z>j-h8a<*K1)V+LymeDjMfE246M7XLcP6qpGkMhMn<#=6>2nXbx2sc3a>WFZarfjYv zd-19b%9opBW}U)?Wz+I@5ayb(PC}fJJobwO{JszNXuq(cbdqOC=4V7p5JB43o(wP~ zcK9901Q_UgOw7rNj3&c$WE5BdwQbX5TT-vmvwP8X7bOhzJTDJxA@D9Ixx6UBUF!lj zpf6z!Q#H`?6kMT~ng{&X6~w?7(hQ|b!-M}Q4y*8B!voO0-h#4C5}VC1LJ28W*n))e z*oa%=Ho!C%yMpaF0>K_&o30fN*#--HD7#LKA*;WF1OO%`^@wId-^^E&OZ}otOvhN! z%xoHKIH+Q?txBc>`N|tdLz@`l03r!$HM^7eP<-=_{CWPdKiV_CX-BK-wm>0Z6&)P` zw3bz7yiFv`YfK(+$RH9Ho8-x3;3+ws2^zgYpCt$)0j>@pRR+coz`7t6$vpWC9MZDXJI8W&3zXfH;XZ~_knL|xv<)@sOukzNxt&W7a{ zOIa;!G}|i)DbsDS3{(=4UsYL|StLe435*e!s^*2FvZfYSsFTj5xAE9HlOAq7vn|Hq zTA@=*VwWv=nIuuj5~GI*ZNI6G%uv+wGc2zA8FPlKOq^fgBX&PpODc-^^*KS z`^yfU?)SVKB}LMyAyU1McL!Nryu0RJcJb~SVmlmI*i^|GOGo|2v`=eUtyI6U%~%^0 zf^aGl?x^k6Zzdw%9nla{QDHY@QIg%k1+BLAjQQv35XkU+29s7lXs(T(3Z+H}!aTsg z%>Yz)cn_%7qjzVfc{~xl`;4h=MW-g8{iWN8>F_D=QvM9r_mG|%%d9PvZSAe@y5Pz*3+$FQ4Eqz@v9)8L-JX_=6&B%1Qo&)&$*Cien-{U`RYB zVJr5so7Bn*=;ZfyVU}JREs^NXUPoVb`J}pENUId>Ax@TE(Tv0n%Py@S1^r@Q-Wxq8 z?f?aYqK*{yDX!a}%?%VK^eH6h;o~ma?r1 z<3!)8_)4-noePbmS$@|fR!l!mr7G|lSc|r`3+j{39vA77WY%3WV!~`ijSj9wYP`%d ziOI6Xd|ZpnPct7Ws6*p_Fl-LJs3?Rno(s!q0FwfCpiUj_hW8hoDZU~Y%Jrd4$1@D7 zGAglP74$^S-MxLR%TPaB@`o4I%r(&6LCPF7Eau7N!5@<1|CI&E8@ho$N~%?M%`JXd7IoE<=_Dt7k%;p=z`BLzMz-L$$p=(;T$> z!i3TT4oN^FfY<6l630bRnx6T-V7=IzM`f9sUXD*VVTW#^?z!W!cAxHg7fJ9ap5j_5NS~6J4w5eEg~2Z zzs-JVvWD{^Fj;c@-3UzLqkk*)O(hX~9*om(LylcBOV$8q0!*c&hX(La$63s_Zrnyz zq9PAP%JR^bIuNRQn%YE3MI01>x&$4(gKR6t6?>G_uuxJj<7Yrg4YdVHyWXgzvbhZXkFMiQ&F+2%04eq!MBTYW+2x}?lR2? zFQqNB+c1LsK8N!6hvpSAyo*Y6HMDRJ@4#6(u35=*Jhkb{feL;`VtRoXFxf1)63+BQ zDZ%l6z1NyW#AqH)6Ey>yjcb(G2BcPkq=qsB@;bF4z80G+hHB04f?8qUM2<_K zD=beY#yh=+1-TH(UuPX&5TjAis<9ZQa8`IW!a#)d%B1%T3GUq9O7dk=`cM8a?kU!g z&CAAX3MPbN=4=BEGnXCVkBL&m>TSUs7g4<`FhCUTWnashin*&Kit2rgGmXj=0t$J3QZPsWhC{-;i1X8t7ouz8kL|fEu zWcZDxfYAX_OE7Pe?N{6}KO{78q9ixkRvNf&2F#`9`ueqK`*=fd>pV~41VwajLn zpZ!FTzE)h!>eGy1;cBpE2LU-9Ur>xtV7ny)t>kMJ8Pj|Vr4`NPr$v5YP4neZ<*_cP zbw_|LV}2D;?*pvJS_KP4Fd>78{=GH}egeruQ)q?g(cus6xSLrTB+MOzSp0-^RZwJQ z23xNtz{H?9_Mr^~}`us3>^A4QR8y3$W8 z_i3#CHuW^WO76D_0nq?FZllnSnK9h+v4XTnJP@~PqV}4j_Da6$P6`}aE4IDc~NB}4vI(MTttJtm#*MSY0K_V0=9(NWT*+L)_m?DYC@fN+raVTHb zKr5@eV)Qyje=rB`*>Che8-f^J9L`$y?9Fja+0}(s1I+$dkzD^W`@8nbP{95H^qKvk zUwW0@A9mVj_=i_B(m{s*v)X#Zv`jOy@eQ{8`^jScQTvzzHZ}hX-eDY-;00R9OW)(LXhr}aC!xpehEi-PzDHYSl>p^ zsyd87tyi32$hg1CwJQYF7xUW5JB`;!tAz+}tH$%2vSfAJe6@ZoS>0`UPGXf%TJH#@ z7I=vx#JKyvjj)tUkPFN<4Q(N$!1K~suqPqlO#3h)2kgRulyB$Yf}+qI-W5yzs4@li85Y|h#yiviby!vrE#zmVjmXAi!OO5;dG-TIc4o7(n7Dv2HaQdi zDlCJx`EP~L1g^|F@SlKJezbyYnT12cRvjp zIB$aOTD4cZUn>NCP5r#2?ek?N+Li>}ug2T#i;{@ZRx6WB8McljRkHPrrGvX`_tr2y zcbA6H#UV^1s5`}=h<}sd9C|MPRVk`r)9jNWf;0cQ5J50%wi&V?3{c(W0NrnK=nAgo+JIFWutG@8V$XkHfj_3>N2~xW%N&^=$oLEm z!aRGxD~T9@cHk;Jc1-hUCLPv^VasLk9J@Kw0d5h!G zL2Fp7I%zeueTd06N3a%*W5u2LVRP7xD`i-4IwrnF!`kA8wWZm}wwN)* z0^)}NV>X;M{Ls>7D(0AGqZ*LfUyt9N(ZMwgwd1O^!E{;7sbWLPLXHw?6!;kIB$o}W zA|~tOXxAO0(zFb7?#l6 z)(yR3d3|YVo8T8@+nPGgDDY(hnT{V6}To z6E;f2D>uljQTQA!CfS-3FkA(thM@~V4Y}chAK+sVxL9N;}{#S$HFd zPJqz0$)F{8XeQjVCU`!hCMYojPGh8DYjJ#Ui>g@E z5uD94UL*S0%09z}JtXJ*Md}OZ*YpI)*{F&CP|f+Ug|Wdcie%aR>Azu%_oYCfwb&~F zW(2hrV8iMGh}tud^H+P!>otO>;})1kLqaHq4@SXM5q^g$1!}`Evf7VY0V5SJ8wv@4 zJb&qhzijk!KVDCg#f3!OdlKUX^H4tGW3lKZCsO{-Vm-tlo+Fyxu#~m=oUqYVar)k{ z27O=k5^M1}tqYU+ygd!R|BEEb3eI1vyV+_`Lo4@r%&xW?U!V2`rWbg=9a>mA{yT-Gvm;6?C_B@fE_l4q z9uLjDOR?RWEiu8cwKYQ{5|2?VgQ%FyN0_u&-Xs3`5YPM};=FC4(i^W5BH6HQg5x$> z*am?U4M0aA_;ai^yrZ>6t#8RkC6ZA@ z2mjazuE>UWG`9)0MZmRw&u?2M_n&}!6vp%7%_*kp4!{TdzRCYzCAGkDZFI*e0yDJ0;7BbVlS6-p}F?IQWqK!2g(sk1(f?-Xl~gC zxzJV_N4E=&l7n4nezL+3+P|C&ZS`B^LOb(KaG^=s9OOcCp;N#|+{dGR=|Z!}Ajbh@ z&}kWwz+GUxGU$rN%z=hyO22gS&m2@{u$G@gzcz7D!8g;}<)eZNKpMtQ%wdHi!CLM< zHUn~sLl_l{m75<*7zsK;TE_4&j_-0WFw|v^{?R%4G0u+yb)^zukm|R`n>1d)$2p^5 zcFq~q4~1gpf`SjM>W2b#nYX!m&`Zt=6gj(}ti3J3Lm}Fb!iDb(W0*MXI zNr0ND{gJj% zC)5J8NYdCUFOW*@@42YsuZTK<3&H?#g!B&DIB&9>L8;4dYL~(Ar(7VkADL1ef<_0`7-V5sX|TJ57mJ3)LUAN{FV;@gxV#r@ ztmDPX{NP0ZS}C{1z}vd~Q+u35Bk!AuF;Su`8~lYk=kU{1wcOXj#5qk3dTA-OZDOEx zVbjHoTn|_vIj|%toY%TQ2C?0Q%&L4zNXxQ7R$qhF7c-SLbXND8$Y&%y%cVHHA-g2M zE3RXZyzKZs^=#n;tenA8i@m)y1ZKLPD{SwT?w;?XYX;-HzOU0aUN;+;>zYz!&$d>` z8u+*vpS*3laf(?g;ono0T(EJr1XY_%G8$~TwXCKsbCtVz;-C1rW4`8l)k|766C zY%CGnk%soL0SKFrl^a0fPS!n!ysbbiY9$&$Z%*xkKU4K4HP@wiYo5jUR^7n1;s{G!aTq z7#&2f`eXe15=Iqi-}HLhWc4w`-!ntoS9zmqpLlzxXRSsuJb{&6)$ zDnIin66hq=Qscu8i$=->iJm5mKfnCuFIblUg& zy6bn`L&?|cKYWDWJM-whS#*YJ`(x!bUi>&?nSoJ?gd^LK8h<{QaxB8% zb+oQ#zzN^XaKzC#&yfe|0qtETpg*GEGnmzRJ*)ZoO%8(9L2bs(rqmoN>!Pt%O{1!C zhzU89C;&wQxPZy3S$;zu1RJ6+8PV1yFK~Ur6T*2}M5^Z9sG8-eI0%MqqdW*U)TgVW zpmlUr^uF>2r4Uw9anXrA2f;KfI|$a%RdH82UA1!%jM_drk%S@*eL9k(L5a5^{1gm3|&_N`N!2~Ko>Plv2i{hY%0ba;LoqoV$Yk0wcN5#7=;0GK8 zdyzpNqPA5pAm8YQd~pyA6N&6U4U={R`u>ApT838L!yht0hpqM;1gk143R{_Y2x_dE z7^-~-!8mhi2f>V%oG9xmkSzt%<&*NlhLuNrb@~2aiaMY?y}9i2AXq`a@Gvt}83lvR zI@l)9O{!#Z5X|;z4uTm=!RKdf5^U(y=c*k918&^6ppyp1k&1(0u3O5wgJ8lf2O4DKKp93|)u=BSqT4#CxJB7$a->WJfFKJ-9~%+I3d zy$KECw1Z%U8STYEA&l`{SWW|&6tDwz>>yZG0gkMH&lw+PBK{?zk7%L(gJ8VnoDS!4 zwS(2MDijcS(eVbuBhXP{<5lm^vL~Jbdp^NNpFzb;W^bT5e z@hf&IZQ{7GRIH903*@k>Jn@7v?o79^;Mr1Lx7#4k!_9;%>?c$b97HRWSJ|iVKUDLJ zI7&Kd4h7KeKCcFyW{!`^s2BH$n{#L;PuR=(u+PP%LlfdJCO!;sahV9yG42*6wW_3L zQGTOF_hc;mry=1#L**c(i8YidLib3zO2|Z{<;wbgp5A5U@ub6BvGNwg$GBIy2g@_(26Ji%<`?Z+ISthI6`@aOQUW~= zPi{N%|6VIL{y?=A{zMBENQjA;l?6YvBNJNUowB7_*AwumuuRjv>s4j~s|gWNxjdvD z6y@*w0nI|wB0R<9Mee0y$j;y8&qzCem%^yc-&L1YpzKVmg}=+%Z`J{8#hV2H7-j)> z4Eeik>%d#Z(*oZf2O+haT=37wc;=58LTFS!c$MOYZP+$J^LH^swb1c*k;LECeN@tB z(ZxoYn9kFx66@PxulT!^?~uTmN_(2|Rw!3NJ28`lntW6Kyd?^2Iyyo)el>s6dlsts zv(B@iO{-wiZhMBc_hxqSbjDG)vzG?N^6aI=`p)iGp1mBhOTJlpIW`)<7lsM9dTZZQ z@GQ4p>TV3Dh{&4f_ioG_Fb`}q6hZ~$TnuS32(H{h?lCDvrYJL#Xk$mSDY;`n47sBu z^}|J2Sc+7`tSD)^kMYZ{HO{6MN?qN_%?CY~Z3qEiHgF<}I$30sB8A4;7-YXPBaLhp zBeGwCsozrjl|xdd^HtlgwDmaq)qjleK*jzlU}v~NpOZd{Pe!&Hn{8_t=-K3*Lmksj z9remYJFa$i){0>aA!f87=(W#S_-9wmP3EDiGzUz@-Q$;WlV80I{jSP4x`uC* ziw)E7YGNJ}GMpFE%erpI8e@O5A0I!!;E#?rzBxYV86X_U9i@@x+h;x?UR@d}1}#Qi zT25p4kpGe5Mgj5!F|fy(s|_k_EW-c` zK(m1eF(^QCmqC_;NRbaE8*^(U>k4}n&C;O-cGVQvg>g`)M=%z;O-=Qjzk!CFMaI=o zQD^s)nOKr=QE(jll1E{5D!40KSm^}N;Te7x8=%l*cVKW)QgiIOD%CEMa7&!N)lg|X zItVIeiM~1j53}>-q||6+QtS6Z=v*%*i=T&YT)vinJL16FmoN4eF0T&JKTF#MGcnJ0ajpakw;2x!%12H+h5A7pEFGMW@+}9EoYV2 zJi1J%^LT z;w04e7d4>dY~&=&X5&rJ44^{Cy7##8*yR8u?FqQ|0O)JF&f@PC&@e6)i@xqnBqh|| z+Gu=oxRPK%cejhM0i9<93a{1<3$J+6t+DN#%Hp~cj7ub*-1Jt_Cwp%drFR%me{{sg z1IDzBJ5gcAmpfqmq_%GBU(RmVz9U|yg{Qe^0DVyzXJKUD@B_w-O7^)3NbwyZ_FIzQ zin~P*WaYYP;a3H>1D(r758#Ph)t4;D9`Cjn+WnWNoR}9rLmA~)aoZ@R&0yH9dM@!5 zoDjtX02GSr=)@_w+&jDE>E1#>>H6qH=X9@K4IWG85dYX9b1+13cTX0QVCHs8ltcRd zC+GUQ()Wwk+>d6jY4x1(uNWh=&AiP&&#wb+m)mfhbJIjIq_FMTFQ&Jz@-nz3lEX`TU?@Tlvv!(_pvX#7p3C ztJ{HDk=*W%LiAklG+bQpbQx}d1JmNgk!S~S1JrwHAA>DWMlgK?)CJH1pDwY)mmKGI zQ}{hmaq@VLC?0Kre)U4XdT_w<+emO29avF_0Dt_qkzgP0a|`X ziti;M^w{^3jC~QsBZlR_mt>{+UXn8SQ+_W=zukAFV9LqB?SH@vS$LE2a<&fp23nH7+@0=~pWZ&+XX-1$ z9Q3iB-Q4h-9}qQkpnzeEGNWnP$6&rPqCp?xl5x6A=rz`uEf%JV(aB;tNd*qRk?w!wXfMfifnl zNx5!bE)vORqe(FvCM9&kQ?M)Dy~(T;Th#c2OU}xL(T)rB<>b0ryA1e!?atH?Lwct+ zu1T0-W5d+MhsC-q$ZF8khyzUjQP*I)7tE&iU=r4}U>7+-Fkr=K8`k~~8)S5g8|#f< z1CnWshF~fXPuez}NVSDC zVu7;0Bx_Iy87aDsMSq$?iVIKQu7lm!vn`9(y)Xga*BLh}ulwNQYoc59-6Vr>#slPG zpBRr`hXTiLaAPwxiX-Ph3!Q2_x~^4&<xYy6MPF_D(n`V2K2S8zg+ue)~xT^TL zN;RF^tK3{%LGE`jYQ<1Q0{hxOE}FvPAiA+kMfLX;lOk^8GBbsBf1t2Z45H?Xfe`<% z!?1!ioh_*uvFgA@-=m-@%w1UHIL8C*|AT!=#TA075HKXQ7M#Un&Maj_eLgtG5_7b* z6)k7+qPSLGvrk$;GtPbKcoq=Weo4%zc29VG@&ginnIs>QuttBMs59&Zo~O}i0Zfs-jSajnxGrI*3JEn<2X-1ks1>m*)E;}D(Jh!8hykq{ICY5O ztVCf~Nngw2hfo$2lNxX*i?8VQSg+E5CMN?+ua0zMl5YO){=S{d)n$Nj?!L_BsmDqi z_te=l=>~a;;XP^9`-Zo% z`ewaLgnt3ObK7$dc4q*$bJS!D!b_9WpH zD>)ub`Or9$?`ZKtJkaiM<{YDUg9~Zo1_y*x5ogs&6{4s>4yysbwQ8jgoGYN654Su_8Bxd&YT@vd3BsQBkys zgl!q`9Z9H$$uH)r;fs8c)u`md(yPUO^dVE(rXeDIW={;IqR;fnV(vfF2Z4@Iw$}5R zKF6{u`X)e)raUj=5sU9Ds%?J|pfsA@-8+vl)w&x81x40sHb2QQdMl_Zf~WcZcL!X{ zX>4kD5bqDPVemHDA0QelRoNfl8BnUaKalsBRBeBNUx4X=RJLcbS2!`cDv>y#XFkAG ztRGyIP#TTYWLg4Hd*88KFksBDb)lvBjssP(TRLH`uhL~3bhs|71z&?^K(=xn6Z)9# zO>`u&UmvfvKJ`Jqzqblo1n zQCe)0Ym=PV0m0sAe2yAgGei`v*|q$VsHL-N>5oZ@LQ0Ku7<3^*iT}NBU!2hqTo7qV zLKA|wv@sfmx6mwqsDG$cO@D|SI7#4-H%LF#?*zUlNeY+5omi6MgJgp$gaeu8p(f1r zE)FF6yuqAIDw?^t49aTjfwhS>77S@Ty0*I`#&g{Z?1(0=wLMN9T~~Betamq)V-G|O z_#ctQDSYjcm9hG6$tv-Z71DzwtLT!FRWh589ce{{a23s_w>oi3-~H0!WbQ1Bed2`Q z3M6Njm_sEuQ%2uv*!-kvbw!3C_uPOW7+KK_4*+UBvW=I`OunO4TC+vP+?c+-#nQrv zwe0fq!Wc`5O_}l)%OTH5OcJs{1G$T}v^q$E+$h_LV$U1D7@6p53h3pa^{BiTN%dni30`xtI z%g)Vr?|fpcKHFVnO9H3y9|5{tBU%evQ*o7|a8{(0p%X=#>o=mLK~3ut>U;Ef33Z{5 zL!C&3LBIOAkmv_B1qc^d6Bsx_dq-QnpBeg)Fbi<-_|Cqs3>Ec32`H@m=8&kZX*kPB z{AY*iO}J^#XNSl^cqn8>aG`)#u`YrowK@yIB-q4J-7a6RXsd*M*Lql#vhdVJE?bJ7 zqo{)RUL;|7MG}3XNM7tD(Nc8 zv|o{Xp_5!kGJ`;RzQAN)A9C0Q9~ft`AzoSa*j1oH-lMX6{s&kwN!SNI#3n+2j9a)v zevQhaXUU@Elo!(BjWIsrM&ubJJm3b{w(ll^WWaHKEyIRF&wyH3sSzR9sZ|k)ah2-{ zhqa2}LIgHw&}KKZJ=QvNr?J(CmW-)@$e?y2UMYT%R=PU$J=xZ3K6#{AHrQ{qoy_%pWrHA08E>H*P(f>uZ`ux}3WM?_rw{n^7Xx2~P}5wdKYAf$ru2 zyabHR=D{0m^Z7H`;N1#HPg2Au2m|b~qxy?CsiEUfE$wW!&C!Z&5oW|l?0BRtOVM=C z2S`<0hwZe1*{0?go1qmAc8zHBYN(wZ8%9ync1m$5$NtM;rvDmV0#4slAbEJ$M(QAj zwWXpv()_1ZOSrR&1I#q#lrCO6^sQTrFmNMA$e_^&G{0@tdoLR7v6K|GB@3B+>=(=3 z^Cvrhcj(*O`$FFajYBOB8ixdm80PQrv0oS}#u2gDMFSzKlGD;+MrEKADri<+%?zB5 zoRtG-R6_WD-#`fWhAug{PYhz+qS47;HOg{7#rSm#VwX z11k%k^n*{c?Qk}k7*PYiBJy!<+6iMmX_3m_r4!NmzBMK7M08{Vlmr$wFzsU7IVp&I zC8W(_8O-d6rycK1a-E0gq8Tz-G6}1- zN3k7TJ=cBn#JJQF>@H5@iYQ?71R;Qc4waa{O+Pz#F34fU|I4duRA}79KQnQeGefs9 zJk9_ZtS+L^mq2{-F%9c6;HGBWMHDa)9gVO62wM>s3IWaZ-^$?1$RoEbVT0dHNvuW;N7&2^jJU2{@OTB@w8!fZ# z+`!X2%X0F-+-6WhLerCJblncR?+(M$6M_6ZwECtQ^JN72r}brKGUDZY{kQDexhBTL zu(7V&ChU7Kx_;m?^x&{w@#apOll0dA?MC|w41$SZL5RCUT$u&&Y_OPG(DLh+gt!xE z+Di%wLhld-&S`?lFG&P{>8`t0z=XRF{c9iu#q#y8zcA7fLicso+!fSC2%YP$v+jr| zuXDo0V*vq!k{--l4-qJvZ+Ey8D-|6?)T<2+o?%mDDZ9g+5_nPg0prK!dk|5N6}_tz@9Sg?zhF-$F`Hq;G+XbiPsFGI?EQMK?LdssZ3;If3efr@2a4 zmW;#>d}wG{b%*2Mf@h3dc!R1zmnhO3RSkI`G<(d7zl3BGq~EDNGj01~BgRUI9@4)L z{Q56{G9a#t0@1{8eC7*Zy72yA`hy8F__?}z{#V}nv!D3Pzg&3>1_k|Ied1R>@!`LD zXyvU~1X;WEN9E|4STCxT#s2E|K20l%vs9@c-s_FaPdmzobrGn{=Z= zyah96G;!|V|Ne(R_SrxF^=BC@${@AhVSTKw{<*K`%B`+``ggx*?G$7A=af_Vf9ju; z{`ofl{1H8U7#yE&CSo)XCc<<_qcIYsJ2L>!W`pD6R#bpM#*q)p^mEAr)KRGx3&h}I=-@ao~1Iq52 zUThzlTwXXbeK=?zIl4@3dvR!TYHG2)v=mHDE>A8kQ~yXdxxB0*Zqj!xPTP!`pk}~e zgWKAN=>Cu^y+6}XmKPSXxyi*l+Qmx^q`jCixc1`ha|?HE+P(?`(5~jG(Y`&KoEr{)$Wrvhf9RhVX>G?xX^EIDgBsWutN{N#~#aQM#2 z#mU1AM+F7{>3KtoyXd;}ZmF$>S+puk!SbEci_7iy{PLY$MX09+v%{0~YR3ARYXkU2 z20FQ%9bP!X=qGPi#!O&EeUC>M7ml_U=d}EbNBlCs>bqb4J=eZw^0vcM?c49Tb9!cW z?#TSY(PN8C%g0aLb@J3ZgCmo7%ug>LpK50dw`Y?}8sOv+P-nSB-8qmzUC(E){;|EU z4odK!I&#~>oSVE`7%;#V1Y}wa{P5e0lS_shoi}&1mjnA3xYu59Zu8G8{PRlx-0q*> q=ASR|&zJh=n18;^Kdd(S<8e&=_7|IY9H&bh(F{LwH7g7BBadkzG_f!Zni zFFbW1I&~^I5c9-+crrK;geUDWI8Zrt(o&<7B-ix7nn6pPHYX-9Iru- z8m}Mc^{`$i9wUb)7AE%3PkyWyG%b0qH*six>0QuL^4(OMr_S{jj?c~Pe{$l;abIzR zihFYt^Sxk1nQtp&6tEBVf(`Mf!}DPi8KOq5 zq5q;*i<&8*8cDx1CMCKf8m%Qq z1wkulHq)S4qn_j*ce&eLHZ`Zh@Z_GLU5Um4GtOQMJ}?sS9JLJYcqB-}cEvwr@1cYA zYB@;b>F3X+Q5rwrsXTqU9i?GfdD`-E7iZ&B&p+LcXfus=#0@T$9dXdC1_^y07K(x$ z@#VcuFkf%}WE_X5qLYS*bh;C7rgBg?Hg$g-bSk@pyX~=IkJUZtncYE5N_9_{I{@Hr zD|fWY-A?XQykhz8(B0=cwXE^vuHY^nYK-Qt;7+?6<8C8&RWj@f>XxQ_wIW}&$XDSm z5;(N*G|epjMf~wS-KS6McJbBv$M=j`>f+)nqaWYXc>1(nG|uo&3EYqAo_tF0a!+yY zcAhzX`t**V;VMtwlFG-ZykZEt&F+M>+wD%MN*EasmjFVRPHhgFZ+GJgvu22+>V+dXGzq^OQ=S0=8V9 z+UZV>dr$%H78kF*_F7;_6DD7Ko4fss*M!KLudJRIZRC{=J^O2}XWyWnL63SNi`BWUOsqNWqUb#T3g557)zj+(?u z;}2RIgN!t*G@HLa8Y6a3Rwc2{QKkZ?hDp_MLp5#PZJg3wElJz;v?7sqHqv!haK^6N zqtjf|`kv*}-*xJ9*={O+1<9d|FLT`!?1=sp$g*Ur(;)eka4PwxdsP__SGvmUivk*= z^ixihD(jz;^_%Z?*{Fid*TcP*>3Nr_n%1V;>M$$^GQ2r9;vLYU72l1BScNi(y)wPE2`ZKl?i--z(V?CCHm} zOtcOm_*)X6*4wV@Y|2wQMV`&7CAI9edrAD$fYj}ga(@+F1AcUSRkynW-CpV6E(+%R z<0U$#H)q`AMgMqCkJ+VA^3!EFZz-4iDeC`L1As9!@KTUc-5%|v-e|LiQX>P5X?;hu zn=Ik3;F!A{c9#S0@-P>n5Tpu;{BE7feglNOL)hIC90RE&(uZTBF;Wf}DTjT^fgZ0u-f z$c8)n^*{Lh9|Rv9krduK1r@lLwJcodr~rTO-;{k`nIIZ2?|9#)bSNE8wx9zIIaKO_ zwZ>cdL%ID<(`1GUY}3zJr>4usPGcH44j;|Nj?ne((KfIITo2Q=7L5-X(=YyZQWt6#pTJ5f&A~&UN&Kao-PJNT9I+c#;N451(G9QH?o|38L+qr;+cQtSAFX%j$Tmh>Wg3Mai~QPht0 zpvon?8_9NeA3}MF@F)e|iT4EGGZG}j?YNnJ?Wbh&SOEYz8zs%QfU|yLR0QvBekP1+ zt{J9Ww1R|ZE~kDlNzt%i(FkOehB5IplZRuP=fQn7`1^TN2(hMhkmR1%g71e`8+++kOGxa=_S(A%)exDf#q7@A0kfI|RIDn%t03i*9rto4R5YI|Bccj!P(x-$ zRZEDld(BAth^Xa=sG$xF6_WbkuIf+$C1h}rrk=9%Tn!A#cW~<{;n3Qah59r~cIDV! z*~oAI3{$F`YUDeMM$UwehL~c2f7RO+?u}d|uhP(4odP{H%wd!2MIgf5(g~B@lwx=q z9Y0vHxSOo}BPZM)i@Rgb-HoOj%RsCUhy@}y*E!5whw+zf4}x1<7&FEsFc};wdzg#O zklV2%$vy3guvd{R;zCUlXf~XBR379BRk^Xnk)R3hn=_(c`Am3|$o{SZ+27}#8p5vY zP&MT(WA1yQMj$XBosR`7pNWjhiFYZT*gKr&8iV98;h z|C1G;G?%|RC9>v#e&_*(cxr?Qa7bJBEKeF}<*nE%0c=qGhTGy&A#wWiz7 zpR=~lVF_fLJ5hSt4TVEa#{fkTOlRGrok~9Us!S)J4E^M*0vlM57>le6mGwu9z8dW@ zT9HPJ37Qk85Ufqqz5#HNDKnIWWpqJ^&vLnMDmm5MSM*~s;(hjGkwe)36vCQW-KYVs z7lId~DY#HR6{hiL+8WSPv}&#_ae`%`lI`SdKE6^rm?SmclV#-C!ZgK?hnlCEfTeS* zG{kA}EvQe2QWfZ8Pm5w(uPQzpg*B<6T!a|E0!f3sB8pCBIt}TQ(B!5|1}I8+ssrna zPw7OMD|nT`UPIKUWTPxVLNkzD=E2 zQX_V90#nP$e9S`-GzLa1Y0W@L`k)CaC@u$yZmQTuaBH|a6VfuuY@>b>M+A&FImn5! z^=#c2YoiQ{?3KqDWv@-05<$&Wk`J0<5Xn$@5hCwKOw65z7}2P3dphdiIHdjLKAA(v zM&|5Qsj1i$4);!9d?RSpYwCeMoQ9^D%ft;MlALVL4g};MgqgI*8-_$sSvv((KNG@f zR4l?Wa&Y4-A4hJ1iU{Z{V<`(1c4x?tQYv8r9(s^j;N-mVzpF{QLb)(P88N*!0ocgX zu}T{*z2yObde#|WkRh8g&gAWX+|~sVB@iG@p41 z^KERA(4aF!i}>a`y?Q+2+wro9_CF)18(K7-_qK<15nl}3 zG`02UNF<;9Rk>C)!+}f>cZPL2k`7N>=7h1I%J&b4d?%`;B{eM7_t zLG~=QswdgG`hez0U#d+}mCNAj)O@e1Ip8^_eoL~vlt-4PD&Z#CW9U~Z;>)EX^Y^ry z_|RJF0*T8t16`eX0dX{}ehkUzV&3GYk|G=DDx!T!WB*Ls-DtUH6V2tCbFMU1e8gs& z8%)6FCKKXPa)Qx!Z<+5cbVg`|zhbI6lutD`XsX#^^3z~g^Y%p}<@Sej0EdOu5pkQ% z`sj+_>I|g@&G2+*L(b~5vFSCSiPhtDINcytS3~U)%Pv|I;{&fMX%A_tH!Kck+9}O% zPrBfbvl(eihJtdxzywoF1tiOihO)m~}tt1y+zEsz$>qgHQPR>`7T#=kZ6RVpQ6bVq-*jNJK7{)5){u}G*1wi^2 zp!m7Es{@$=oA6fE8P<5YRJ^>f_T}!p;4IbT<+Uv3Oa|2jT`BLPRtXhALsq)y-OC@Kb zspM}u`%A9WVU-;d|049gVm3wT4G(7|3>)um#Dz&V3gGS<7VCO%uxH&|4GfEQM=;O$ zD_D29VBI0?3tx~Pm100Gn~kaENAO>af(`5YC5h1!vQNSO5B_Bb2Gj5B19M9Q%FMbeVBB!`TA{Qdl*%u>I+SF7lao=^#xbsoJ&O-2Cn)m)H4DrYS z-rL|AVFZETl&5%aM0T-|Q)zF|^;UR~8s8M&D~8yVp$5i=xM>S8?pH$99tYfLWU+=B zzrHrO2&>9FLXXZf><_U5hK|Fq%}qjyNM1g-rsSo5$t5cpQ1U{lB(pdPChr0vnZ_}%qYL6R$#ntvd>viz49M|v2@{~kfMYDV9fbD6WoT=LyGLCCQsxLEHD?+Px<2sMb@W{a%EL5+wxk>t0AYA%l)F|zPc*+?mYKy$Br{4jKH;;BMf3-u4$=G zqjI&Mb~R7C(oegRr(N!+UA8nl7q|`z)3;=1Zwao{i-xY$eM2wy(_YNemiuYTdD_K( z+QqsPp1EBv!TBr^LB%Y*3;^@DA{}<`Lk1`2Vi+k{0L((U&%Y&bK17CR7q=eX^WNP^ z+W9;!cR`YNE>AmAboGRllzXt>$`Ri-eo$)k)) zgRjsy-Xq*&eXaV*~U82u3(HJ<~0xCMBW$d7>E@sB{sA&TUBOjzYGjm zaV|w;C(E-_d3N^mz;+cP=5m&^JmbnEmEhYJvsJP_qXn6mtfF7fMmSL|rO#M_IY2F< z%rg||`j7$y7DFvazZom@@^#;MWxnSIVrzLEzK4#1;O>paq2Dj0u4HqvhjEenv!dlL z9xBQAsxw>Fm&aJ5$`|@=?V>>w^82$Q$AuL+sZ-W2Qd@IQe#NgN*gKXG2OciYU z{m%a$uZWUEZ7CK^Au&U}};diyOZQfp}^KS4HY?A*nw0mdU`s zQ;5K;750~eZhy(5mhiPODk!caM6>Hm==|EXdnlr+L{lAN^;~$Frj?P4tICV9@a(D} z1Q3_&sNy1U76_{fkG4wymueKqpxAFkG`Il=0yMV(LCL$zR(D~`hEajLP=@5|=xhbQ zyh_{dkx5SJ{$;`RvKGUWXHBof$v?6vi+D?5i<+)EfkMhovAJv6rAZamQj`$V}z}WtpiB8yACO15v0V? zh*(8VJhB97?sEUA0B8SJ7YJK(Fzt7W6@;uY{UQL^6AMlK+b;;LFNmCFYb=SPC71JP zX{slp!E6=XjcL`|Hs~;sHehg^>SY6wx9wB@?wx)u`!xen}P-YfN)PSyu^(owDT z5NfsVab4RtBitNfx>?VAv0gU|Je2w;s$8y!DjzJUQd?L=gNh?J72pukrzg3j{0nTn zGNE17ro8Z^h5KGA6WWr7W~oeQdUzc|Lu=&4AT+BY9YAR5I)nyU6;y`vB_}aq9b)px z2P{TgH1(V#q?B76{~|BlUJwKqOho>w6OpUXOe~LiZt`S}h#=C&FPC;>8d}rIf`(SX zpHt5&_#L?z@Q{f?&UF>o1+4&C5hs!JgYQ9~Jo=!EJT9#wkF+33yHl&2QJS4w-pBrnXa`6j0!pyDT2nq{gn z`B8|uveqQ?EDz^sYU1X{w1eoZ@WVXa7n4TA^lfzlyVGC;OW=Znz5QmhHyU$xl^Ivw z(upw;-J&zFdcQe#3<~G13my9-_B(4X=_d&In+Z=40vTzj2iW~20SB1w%{!2Bht}Cd z#MiXgu0KHl4{{*IU{N~}ZZK2$S6^~71)Jz_s znp>2HtRz3dbXv#K$3Xo9>O=_VQ;O@Gib^0#{4|GaX6z4%@*2m6)|<2@tno z@3L-*>bexlARGLTMlQ25-qPptRbnM5;qq^+YqPqS))mkU7YaQA)$pQUGTqH-WGX5AgdA<{XwW23l$8AvY)SkX7+q& z)=CN8uH8w*?6ufQMD}5!P$Pmz{#NOVn%a0?flDZfrxm`(lg*mde#-*8*eaN|khH`l z*w(aHi6O{djUm1J;Tine#nUDCgzUJkFn0|gd3>rpiEfJ6E?)NTkf?VxOkSkt-EGe&vg)1g3mVD4_9^6Hh+iduD-t6dM zoZO65=r{LC#KnUHxW6PJh~vt+-~4dmev9rJ_MCrqCFcW!IN#IhjTru{3-CHyV)$v| z-}3;kR=zTr(@Wh2c(n-dY86o;^&(1y*bxG}UWe0-t@{C9eJ(#+D|7j@b_uSG579=! z8?yM>fh_KL+jF`CygZAa>$CV-&*JA+u{hke!Z9_Fwa?!)YZt^(Vr|l1kG0PaU~OBD zDp-4IAZz>7K5OS8UT=)G0cS94gT+4go-Vle%v$b!rIz#kt0>7;d=JMJti7{f?QUM; zfl>kZ2FAYFXKXRFOaol(p0QzRZqMh@?A$wi!1YB?7;Ju;<&$BVmdw?;Di(Z85NKzR zdp5@FmwChmkRcfbz$+r3>JHR5C6TX~RkF<#n+z;30gDX%Gd`CoctYFZUO{74!b?P% znBO1=uSm`$ATlzVK)`Ehye(mOHjgB;dx;%s7}#EsWXM`${A*?-Q5@0E@3kVDg+eX# zF_%Pw0xeV4%-+~>hKZOJi(O$7Rsbd3C?d)5gt#D3^<8?WAsNw-z+&6P5rl!3p`*xSnL3+DN1)W0Gb5FX-SHH?7$F!TI_(e;`F}-cFH!e zJ}g!gNw&dB1+!wUPU_aEQKQ)mi5(X~C1)`FWi0dljM_$~Sd-oy(1d4&tT;_18Nwoz zWo!z_pM_A2LZ)GTzZ5WPsZKAzzKbNIwObt+%+0S4%q;j4Es&`9p#sh#faUk5^!U8;8)yMjwHl$-~^CY#vXu9<%LkmV+1iQE>lRLX5}463^y%V4=L zS?=Xkb-RcLW$#BdI2f-5d9&Pk_2u5U2nUtvM>)7R3LkQy_QQurxJXvKQ$p=h{}%t> zAmi)eih0%ffyS3l?HgZtywFV;U*62Lcp*@}Y%xDr7Q46=MUv*?fGo|&0c~MW9x~b> z-=#d?`F`4YOT#*6Mrs*Fd?lNW3Orq?0waAIa&bV`5OF}R@uhygr93UCDaxK<)+!ds zjXbsUs6xHklo?)|V zbved=DRwK7SA4Kf%-K~Y^It^9d+*Y+ZZX>~OYAQjwJe(ri9e~>5s82ELx$_Fs7bkU zV-kmq$+|9XX*=S|ZLR(@_|v>XFPno&$AqwEgwu}FMeSnZA69r;THAzQ(O3Dm9f(^C z2)hoz)X)A5gtV|9{bQ!eex#WE>L3#5J(C$j_e{iKcK`sgZ@>BHzNF2Sl0-h)>c|IU z(Uu7e(~GvENe508!(l;J>Qa%CAMdn!5WCUC-u)`yD~7a5RM|%i(3#LVeXkP_;k?SY zGV%MqC)G7hCLqB0%~;S>Jrc0isg)~xth55jKDU+K1qG1Xd5q;t^=G;y^2M62X9j@W zkUteG%;8SRe+-yQ9#LLde86L_hlDw~WoV?n>tkkDTkkqZgMdefhR_*HNEc3d_i8ME zg+H)(MNW%xdQ;-H7GE!3>xT>fe0iiEbiGIpIXL~g0_Sm*;B>EY$0% zJ72BmD45tXBkFN%#hPm6Y0$Qghbg^xXTc~xnB*jwJG8LC{E4xBcqQagylw~-8bi0m z5kDr=jgkX9Ko%RzHvvkEiyH{#n^Cz2abV@YczYbNy4TX$!gY|W$d^JIUh7LkGlmft zGKRBjCJnEeyn(btF^JH;ilCRB+xJR7S z)PO$98+6Dcx@?aghOfLFwW9)c@*K&Wr=$rsB2iBdxTnqJMf~sE9;NQ?!<}Jw7!((n zRwStKNq3%L%s;(*>FC&40eYe28eppnyQX>38QDAH1lF+GN{f+NqPXG&7`bAT85JZQv%xpY zHPA|3e(uC6!~Ep77AbNKB(i(008<3b1aQ)`5;U5-1{jy@D>TxQYak(BpTEL2&?;O5 z8`37Of!ox9uSNi{ezWAb2HFJYs4lGz4YLqQz`xR64S?3UeLGdr$)uQ`;JIY~g4+Q8 z)u?^D)xyp;)#4ll*Vt^(OmA~51m+8{I;mSDNR4PvZH~c*p_`Z3QNReNynjh;kNPG@ z)0VJD0xgdA-H2ZmuH+iv*goePa5iEw3YmuW{j%g=VJ8~B0NaIY;8q6)b^i5%xut=D z%w>_-0R!wn(E?vhr@)z`Pl6E>;JU8x@}hXxaiur(i%n(~?KOe(m`^y@0NfXwx!}n9 zCFz%(_vHR_o6IQo^&x(%d9N5^B!nMg4!31Ah!t?|fVDUM@fCX>aN0v7YzD7wMUCt@ z2g!tUmM)d~3}O%UW82ysw--`9Q$;`GDw2>cD!SOONE@sy-8QV{6**9;9Yu<_K;xg8>wc5c1NN>>NS6s>TIjGKsDHxqHMGZE1#p@j)s zY|7&n6w<*)Wa>c7ZL$Gq6(`~*G@&Lmq^lmq^8n{gi9T4g>Rjb{W)6NO}%fW-=+n6gC$w*o#}AB=VaQWVWaYlFOMa0YMk?whtXJo}o^gHrP=n+~Vi8DPUk=X3I4V z@1cT9G)YlFj*hz5#i5Dufd^6u##$2OHrHgWU{#zHX1bBs3`D~0$EH1>3ORj`i31R=J;Bj!1Sp@LW|2UmLFn$D4%1|9T9M<00~)otf|Jd7 z)b<&jkVXPAVvGkec-7fwwxn%bke_|#hXjU;9B980hjkAcQyQaHb{%mRmTlXi&sEoJ zXij`vpEk8?>2*TpI}L?z$L%48io_@-wv)3DWXRRq9QV<#NyMc!raN*L@{d|%>mr&f zoDa$!;?l`IPFa3f2S9;xVl>!?I+grHNaP9obweH_-A)t3A*!gWqJYh@;82qxtSiE$(m8N|_! zvY7$akS&e17_6|I-8P<`Rs}=1$i~KKr5-XAy&u z4^V2y7bv}_W3Distxq(i7}Q4d$17n&Lg5k&{w5HZ2eAcZ5!QgiV_c7nwz?)WT0@(t7^Ml<}{8PX2Z zg}HEPtu$3%kfjw<3huK$^ct6sNz@7wRfz&Im#Bzfd-f6)VJU(HZjNts5)ZK?p~~e} zg(@3NMBF7h-Aa#~JZ1_ogAg>jN5FDmgA=f8$YYSS6(MZRDN+x)+)aL{)LpNKO0x5G zG4^G2&?fC;~%)0-6CS`spTrYaJ{glNj#*U$D@-Ar}6fH*FPf?^VScriI;WNP$ri zDPUBH3pBtipv$Cil)rj>x2-?E-Q@5({aLnA{qfk*c1iWU;r={_tXf6e7bTkM(FLyR zl0K5VkpDPl9Q*TvhuHO zXj}B{AjBof;^f>H83X$AfztglGp60Hz*aU%u@y>7v1MjbY?;|oY?+x9TV}QtTV|&I zuKTot)&D?Ve=bwYLdL0{gT++w^*jN^j@X-Y1oZd0{?{m~a0oD}4f5pxbImERjcmhs zhtNE^EjSvRMM6{%>Sg+Qo6l&Gl7yOl%z*R!^)UlxG45jqRgQ$0#1AqKj-#@!mU`qc z+N4Le;%w5CrIH;`E|HcJ$3eWl-{xezl%2oR1*K(ArgCIRgr&d^_{V1mE<|Pwh$}@;C`nG&Z?2a9<)eCj}9(}GIT4E!;+)P(_u$04ltNn zc&yVwY(<`iN$D60s3Ja>0>YPM3W!w`DIoA7v5OK@edb0JAi0dSD=qAxZg#?bA{^`i zDYHA^@wE^qEC+c&POKcm{@_fPTp{ku6*m~f?=A@#Ymq^e5V*l09Qi`Dh zWo!!Rg#N=r5_S6U5aNwD?GTKG!Wb?^|NWt=Fop(qib{)yqAD}F4ueB|G(D_@sw{}0KG2IIVfj;;I$}wedb=pz*?K()o zMnK)JboCNe$i!UTBP;HKrerpyHD^bG!PQuUoVniorjT?6aWZL_1Lo6u?G>1=S| z92n&Y-pOsSkPI}z=L;J28g~0rW)W*UJc~Gcn!sP{=3<40<9%E zU%^U3vu-!P>i0_L%M#JJLucu$Fnnw$?SB|1>n3sG2<_T;S;T9Id1MbUEfT*rW>x!2 zk9{4clKr^i&e4HVYdz?C*kJcESVX*F6z+hOw zs1FAXI0{Q&6M{FP33{kJP?YqQ@vHx4)h{{fHDYpjw}TgcSRNVDLK-tM>TC$O`9RIycJiI-beTGktuVNgDnCi zO&XZH7hxo5K`PE|V`i$-x1W5*NH`wMg}1~+<4yvoR^K=a3G1=(AT|2gFw!@I`-(=E z06ou2+gf@hTjO2Ss3fff#P= zP&{bONZFPVB{zAY;b7q7 z#z4|fpabL`^F}JV?#Hyinf2Z=l-$O)j}LS}^TJ8;4>i$+rr`-AA5y(x`3^Cc8nZsK zwgQ0Gn!d`Fr&nzD`D?L-lDZR#R8zo=O)1vIa9 zPI#Mlh*?r9Q4esnXw@^)7aVO%!F+2j7enEn4EA;M!Di!0apLR!b40Ax%K;R$@90YA&Cbr;p4KB!}0 z-|&Vl0d*=uee5No`re@L zwn~YbQxS7=xCZD|NY}hrM(k2nJS@jWhe6#+QTvLTz1m8njG6au0FKYPQ(1Se$hwoP zjG1?J^s|mD>v*0ukhh6U{mn#y+)bqLTTF?yfvE3z5?=6SyUs_%sXtG@OjO;&xot0mD41g##H<&ffK z@-TuMnSc7aC?>l!k8vmxJ@-v&AB;-_Ep*;=y%{8f4MMu$4MP86WerSj5vqSHEkbuF zN}L~x;!VOWC5v!E@QC3fr6$|d%(BR5!LU+HksmnPZBINSV*Fs`Cb&2Q#o=_GUn0z@USb*Pe` z=W73e=DD0g<85Rnxl*S3$gaG#rc~=vsu+(HlCfPVRZK&nUuV_gcQE2;8PZInRhoB; z+WQU!IS$CVqxt8dopWX&E|t$YdlrmZ*rhB;`^xS>A0N?Uc}7rcg;)6+$-2+o&r{bq;7W?*66G9>M$D z7eDhKekb@oYL51(htGWZ4_|)%)Q2=XbT@BiEX^3UG=JtKl5 zOy=~RSN-GVYrpsOum1Y~{=@(DC#AZUt?s-o)xPxk{|F2ilazjv=qcKGAupHb`}{xu zxnKD7|9P2wno5PqN6Dw_aeqDGuSflLL05x)ars|deJ{HjvtUn{Y-~39ruLm;8A$8b z!)Eh0YIye+mxJa~?IRPDM|y|Sh1qoC$dTED6AQic*u>+#`CwsgVrG8g;KJnWOxinf zY;vx5D43aDuxuZ7B@P^$n>{u=-#d^_q~+`%om@Da9-N(7=$%;D@{ze-FLm|I2d==x zk<_v*9PXv_6GwaLjDC72Cg&GcwmWfr;qdI-HNYh-O+2n2bzVs6Z=)b>h9MuaZDBP ze$o{N69V}NaHg{#N#}Y`9PiC9P*I+79t;%^RNs4Ya`yQAk*CtfK_(f4gA+5Vp3A}B zk@Wb?!sHQ$T=bA z(yv*NGBLMc9X!-KGVxTfFgu&}Cgy;imxAQ@LN9o{w-6kfSeTe!U;vJ$6AKHyqsN2- z@@PmP9*`p}^%#Vslk@YFv{@kf;Njyl)9KdWvEF`$alfl&SE?aZb4MoT9`CJN|3~L0 zjXGbqfxtBbJoEFtqmR*p`N@y4FCKIBg8%>k literal 39453 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 Z?_~sw$KO9&0*y_70#|e!#p{yf{{i5kz7YTb diff --git a/contracts/sysio.roa/sysio.roa.wasm b/contracts/sysio.roa/sysio.roa.wasm index dc99f7e7333202073fd6a6399b7e8385eee53066..04e635895f34b990bbd9f74f390193e1fe9719b3 100755 GIT binary patch literal 53726 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 literal 76719 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 K&{+71vws71()yDC diff --git a/contracts/sysio.swap/sysio.swap.wasm b/contracts/sysio.swap/sysio.swap.wasm index 0fdeb2efb3817bb3c8a1870a6e3c478d93f1410a..32220dab95887b4bfcad69b07511da5584f49690 100755 GIT binary patch literal 49661 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

5frFj8IOix17d91&ZjbDuNlp42uw0 z9+8i#=3Wu?4(Bm`{Spg!!adQ=8u$QtIi!nPRZ7;}5yBq*z7-;E)q>8&6#<$x<3?#WUS#Ar2OW%HAZyDjFD~aW%UTx zmm|4ceSoLfsdE`!U=Tuwp|k>gb)td%f@HdtG(J`i_oRZDqKxgLX`U{bt7` zC}>MXw^=1Fd(KhD86%D{2(_|G*%V;ro9d(k&{l^fz&nsv*TK{dORyk(jzZvASJ&ti zUj|huPgzTBm)sg%e_ks|gD4tSl4_1eVis^#IlpaVk*zTUU1t%Q1>|I~DN+I~2aIPc zNo(|VC2a=I&RW(1Blg+vGMQLO8q`V>r8z`ED@h#g)N3V4{2_L7?8EIT7J@r0q?q$n zEYzEQ6r_R~g8HU1a{zPngXwM)fhUx1t9QgspF-Wq*RQQsgW})s7>vMJEc#;iGlUQ& zxp&`^WeizzJ|)1Vpfud%j?c{S5=Kv>cw~wRx!xR;dY5BKRbf=!!p-y31L^sy2Z{U+ zq$~hkZwvyx=|H7s&p?dxniF6Ttl06yM27D@_+3xl(QTlfXN*3my3be5*=@9eRI8lb zgCxq)t$(?*dr;2qNLI!P`GFr%65)$zjpV`}~}4wx_`?X1@8lH<0h;a~(Qi4xyWucUC6T zB{`QBZzyq3$Bn7kUO=i9Cs@3zB=akummG4L;Ay6_w8l?vx=Q^U!4p!i6{L!$Q$9xZ zc3@msc>jn0^MhX*jt{nJj!sRN?WZSM6{PjH7^eE0l z@WjZ+#b>Y2`PXOt^-L$*8OxSK0W3fXEOF5ZZmIfCQk*_bpDL+s{jo8$sP8=#dt~3C z?%5@EbTM+(AeVA3bLlG(gV}7Lp9pi~G;oIA}1o4U=9kUz! z!0E_-t3FY`-=^6tfA2(S# zK)}HYOS9p%DCBg;*Mc4k1B?$uND=IE1B}6>KAa~7UgLnW5Dy$(c{-G6kQiqTBR2qA^OP#vB(*D*D81_(wRFq$W5{F8c&TeUCb>)d1X zjv;J>n4m;25I}L`tDhgn4bZFiV}b|c?c%&@mOZZrQd2!hL1rFE*+ zt4*P-M?A=>a?xN#5vCK&s%{PkA1d0t6XsMUxh#|PaNu^w^Nh_|v0` zA&$+KYd1-CI5NFvg(ln9HPTRB#epVnhJB^Nj7e-9z``7_rk`8I-`gAieIMS%MUIR2 z8&Rb13uyU_i0Ynt%t`u}L>?4HfBIZ3=qbFudB9oW5@&drR>dy=?>)V=U4L8PR7H{(hmy%50+%PtKQ=KkYj!?->dppBs7xfk_ z-aFlR+3v;*3QFT;fl>@FsjR(vJ{~qfuPjb8cu{K(oF>z62{ZopQR#lbjo1iuq1`0u zeYx=}&{JmFv~v<_%^jPH(wgy!Q0|~h>?NyC>BOG;&y*aSc5K$I?7@!2lp7K)5^b1h zOk6FG0uAEr-N+VsE*n}UeS^b%^oC!RFhOD49r@U6WF^Y!ATv0G6&r~8f-B~kdZ-V$ zN&}Sj1z2skvoFAs$$ibQn##2zQx1M;uo|MiayEXeDu0H#ICNni!BRUoP_({wS2EQ7 zsr>rdg>u)ml!6I6nH~g;?gA=`k*crnau?kkSaj<3L40#yA-vZIrJDnNTZOmfn*$v* z$X(^;fD;CFSG_raKj@A%3l~B@bQgzY6E6fvw2U#F5lEgpH$!8H%b~X7ogAp4tm@iL zaBFT=Q@!I!lL;#eUA96sh1*fo41@}!ock`6aJvyHM!OM1Mn*bYIYpiZ=K5dBRhwuo z(u@#EF@L3ayJ}FP|EBc2zedQH&qUGd0v+iCu{C|6b_HlZ7u<{N(Ps{#`6Qlg!aONo zSOKjV`Ak_^{&Vz2(P)B1WQ9DkxKiv(ND^eFNu0GxrGx=gv8;{_c&&Z7V2)sj7W|_P z`352a2cIq#mC2C#qkIJbD;jDlK(}d^kJ<>{p#Hypr}^3@>Bt%ShqQjQkDit9CewM08oWIWudFOeMWmKfpr@8S-yt)!Dpce2t*Q~t zaR%(_T}6l9v@OD_h$5?Cn}TxPLE=$UuDo)}6|U`nxIfr?`bs#MC8 zJP8+dj!Mvmtw4sgAEDTr`(mr=a<4+s!g1A+Fv`h_F^qL9E+@Vy2DzkKzy+jI^Lq<9v0kE zw+W_i=c=Fu^@z<>L*SpK;{3BN#syCq!57VYOE&1z%@hYoba{B?nBtt#i^Kv;IR75@ zR_>{DjzIK5mj(6|ECHVt*hbu6V4GczZD16!4Ll{*UUbYR4#EOdctDdr>L9_U`eqiG zz-!uEq3ACJ#8tMKiXb}CIozQ@w}anGhuJtbxWjA*JIuC_eR$ok%M``fvT@tTtIiZV z+mG9XbvHxsR#2q2Gu!U6iwCYas;}QlPjN<|%yJ`pN@r@w+FP~E14dwhT(+pp7B|CR zmCOvkdznYOHQ-^U?wN$-V5v^q3m%4pW$25&-B{24xA}lsk{Do);PQY;ZVHp3 z_@KoUF%(>uY&VCM-)q50&*KvpOPHo^pZx#*wA(%)fQeNhT|vU!E{ED&Z%dND@+sv3 zERjo)yDnr<84oGhL7gXCRtIL5riz!mda>s61;AxeX;*9^1kjQ$$b`6&0$VoNilg_e zjoyD*#i3_Mw*BMy{r~Cs6<2N1d8>$xZ^yu044W`x<~blJo^2XDE`)>{a^(wOx*QK% zLk(u%{f<9@i}O+Un>M0Mjj*@eeY|VcX!9c+@u7j?MS>l8o{d1o59ys7mp-E;x<44* zCKxWyD3i4Zg`;JH!+pSDxhZ@Aq zx8^06g}PWv0eTtoCf0Q%j%O&xLk23ZGU+WrFXEenuedrb=*Z__FeAX9&5zl18 z9(tQ*lwm0C+41Y`;Hh9$%A6$Y7RQMr8f9)Q3SI}Q(ssE~K%d+SvN7lz`)Cbx7c-0} zBN)^zyIsmz9*7^47aQG!OQxUAm$9C+@6-u9svJf=bZ^&csMPLj#A+_)qI=rU5OwPd zLmlG;WEBt!&WZ^2L%Ws_ZLPSEM6YS=?1QakTUt1iFD>*9wh@cp`jW84N>)E?`88f= zsOo!3-=-Bxi|e5y3ySYyg_7ZKO~=~p$UWG|!s(tZZr0=#3~6~AukC|L7pm~ferBs< z`una<9D-;}Y;oKPt9xIHOr$D_L}ZXIS&hBqq0Hu@JUp0-LodL*jMDyw72-)e+9j4U zSBWpFF-}KzfSI0U@mu7x2xO`AkNC2+euY4I!KWhk2qiR-kP9>@T0xY8pG3s`CM`KK z+9HH=qgisdtcvE%_fZ%~8de3##JG}VVgSm#XFPe2o8trF(xIg2OnWG#FDw&m?@Uf_ z?e7U$Fgf*~bpj%*S(gmm`1=`mA|deWB44xo@5bt@W~H!0hcuFGc{y!n=@WIR6`{OT zA1POeFnobhE*r)$1%%l)(Rf6(=sBtI0?(bq`<;b$w~CXaeU+Xl4hc69#Pdluk(C74 zNP-1)$m107fC}CQQ9CO5vW%WdItqi%#(OV~$uE`Mn!KVdENqzxy^is9DtpUBcGo$o zmOiz|4`I;G~V#ri5!M=fFuWGb@kEwVm?M7!LyhH(6$jm@-HSoyuCGkI|cMie4*^ z5F^%Gan>mf!~9v$cDhsHVIw{{mS_ZO#82J|y@}{j-axcDx>l$QG7TCJvp>!>6Ihlx{Ofo?U&$x7udEN%-H$wZur`r{)0ASY9qR~%%ZMk$)aGXh) z+l&Fu*x<$S0JaPK!tdL}K>O@d4JE8uJ7xf~u^i^)LF86V^%7&mEsN^GEsMH4wgzYm zeb_i?N0F5L$scyUL7~O8yFFsObqWEiZ+XS|mw-cVrb3l0kJ)}6_O{Ze65q5>UvXdk z4@<@E4eB56PW|+aZCooh>k-?CjZAFg+BA5BmTzUYzvu(^DZS#zjbaG1L8my&KB0{1 zwZ3Jlf#Jqq(pL-c5|%Qo^o!114Tp=#)B(;frnJZw>U*oMF!v0Am(~piz>9$-3tBa% z=-2&F!c)?yv-}G29q;)~G#`#SqskrbVoScZ3Ot#X4xDUa?r3eAHE9?(=Ac#9nK6ul zcVaD8=VxyY)@D>-w@k3=uvyOejYq5cGQ2pRT@fo7)HdFfZEe~bb?eflR!Go8yuDEM z>T_(VS_|LZy|bM>&#M?Bp$ji=50DwMtc~M7CcZ$!pD`KKf4d-kR^jRI6-h?%(qxBB zEhZM_x0tv4xo7e)0YeTuQ9E7qEAM%it`Eb*qd_wiveLIsSm;2Iemamm3^=noUHqCI z>Ci-(g#Xo@Z~UQ^yT_1gF{n`Vh~f6d?H*%oSMwLEi(}!n9r&-?0qVLfYRak$>-|lf-%0FpX`Pa*DTwgY+DxNQ>3OR6<+B6p7h$m|7@% zXPCO68YBP!@N;jg!U+O0vmE*eOGzr59BT=mF##g0IFu%tQLX0C!l?V>uz098@<%c6 z1OsK`TU~DT>yJ9Z4&oK^rW5R66}(hwR*{HiBTKS$l@K#fBV^}Fk>P8*Oi8KS6(u2e zp`Tys>SqQ@*1Mi6)@<9(;HCw?-9ScTS8o{4*+Q7-5w&bKy$L|91G9`VF6@0)RR%_X z?c!|ZE+D#;%U|oLsFGt|9g19bR(Lw&sN^g()U;nyWwX$;l z7~ij{AY26aO@fS^xm@@dpEX&D@#F1`5X?R9ICQbl+9&I{ttSzV7&g z9%txxjz6HZ|E}s_()C@{PpCfzHDAo5Nsjw)D}U#$qpn9B6X7<_AOAIv*ivU@onx9x z?(i+hbH*|#!i&)=sx|7H_*DQDfbstW2LvLW5;?cyMPOm=}wsyk%LmAR4e7aUOMFIJWo87*If9 zM;I)Z(vN>SXhz0TD~>Su3%%%dghAw%beU}GIKlMea&WQ&V5dL&2OYJ^JlcIS!Foiv zlBG2WR}uC6JL`h~*n)LjJ<}NCEcbgim-KRu+lZ&4CzwSQVV(&R@Q^dEg_MR%G@B6Kc_+1|d4$=?m z1qYw&>~5ii(IYEORIC>*F5bu4ftX zN9mEPo@jQmj#xf@iC14#G?W*1UfcI`8L@v}_$-tTLw1fM+T-wHR+fO4@#_vM)q`+h z)YYh7^^K^E1(g$CvnsNwe`KFx!_dN6MEI@6^}2}PC|xjL026xi@9GlR0K*z%I$$C~ z>tS$tR>G_1FS`%DN`{cZw5rv@DatNowkeUqQBiTe){~V*3yBerbHEH=I(1BG zLeF;i0jty5UVO^O)AtKet1*C7u`fgwJ4cWF^eKZ@VmAX7-*@%W(2}`jibfzK&l?*7 zQo9iytWM$vot~ho(O3`B-@j8 z|CURxQwpe-!V!lepG|;yd*$~m^2V5#@NIL6B38UUl7Q2>K@tj*Xd@CP#B@sDn8AWT z@gFGvIUh@)5xm8!E2?tpu~C6!l%O=Bi2$WeEpTacf*2dlG=YqCZB=YIVM96hnGWkT zQ3;#|iVfggRPr4-G13Ti3KJ60SXpM}U<8?3Vj@ymWe8Q2rEG&Lvuyx9DIQjJ=xb@2 zpE~j7tbQr*B|(rQ=*-7Hr01;UKHt@av#wm0PhEfFi~oUg4nyakS$hB1zwu4V4O)w1 zlvB%l{Pk{sJ>ai1{@V7}d4HYu*WLblkH608>L{`N(Q|L$B)~Kn4I2%T4c89!nh1!k z_txvU=-25UrvCA;UjOlCB}lPV>JQZp%(Z8evE93;4@}J`d#2lS$<*|G(mpsoHy_+R zHomXDCz+p4#`f)--aR(oPG-jLY0m|F#_t>7(@ySsAbD4Nb~@NSHl;%Ivtv_pcT*h& z=Gy!24pgvr&6Dj1sB^YGw&wxY)?6?%Hm;JB?WvVuyh9-7pzR&Iubs>t*uB?f4#ubM z8{0R&C%N~)*wp;^`~!jN7C3j)r9Hv^19Jt)W~S%H=f|la&%LK@X;g-@UjLdwQ0FrD z!2Y|Y_a!s4?cL*Zw>U_qX>?Cws@{lKs=O z?PQ)D$&Ihx3X0~z*ByJu=Q`-&-oZDU%soKeH_Y8XHWOqJB?j?bpxVP4Oid?ujqMwo z+TBj3@0w`u&bs!%czfR-DDuGcfdc6aNC0RjyZ4Qa?+>Q$YtQbP9lL)jt7HX&TrGEl ztMRFOI%w`xGt+jjl*KYz1uLlc+UcpWd7()?gXry<`SzY*`u-_M*M|q@r+40U05;ff zthcJyo&Y)B9ZQbQ&9&!2vZxAjqL6%AU z*FgaiYm!dS?H!v%s&yKgWu)E*48>A8Hrog1cUn_B#~JdT%kq)9e|&0Z)`$FUkp~Nq zkL}rWNuX&7^tboy^mxnV3k8u;U0t~@$P;r&U*~a&oSS8^Ca-+mJKuW8Ti^J)w_I0* z(sky#=^52Hwl9&GdL`1TS4Zd>n3@A;hUT$c0{I~o+}(z056thKo<+{~8`g+4_zl`g zlJB0La^zbvj5B8I%A8RXX=q}E!gr6~b6{5dVzv1ZGzPESlT6=jBT{N37@dYKR)~h2 zXtzy)PP*;{O-FWg*z#3de)LtZ4yM}oXS66tea`|TdD5$rf7a0oH*QY8C%G|jthUp$ zTE;eTP^Zp5d5X-_qZT**O_(!s=Mhs-nncF$^b~4Pyp2L~oaBc?r-%7KvGF$72BCWl zcNv7LxI=;zNCL~_v|!mszN!Jaz8gejt=oUZ?#f}rq&PFTkyXJZQ>ah&{pe8?2GxmJ9m%G z?7VAcF6lzZ?tSAkJCWf~wJY6$KR>>ElI~>s#YP0WFQ<0KtHQx-`yT8yWa1u41J@}T zcGo2{VwQF?dtfS=1~^y+ESq#8-~EhZlz_Kql0EHtr1o6m`)C#o)Wf`>N^jd46<@I8 zdj8s|Urq;Vm*PqS6r5}ps5U=6o$Qh%bA8d8r zfm*|zipI+ou>1|Ly78ZX@2kh|+P$ZJ_dR>ZCnopppPHVzm*I2ZzWWb8@UCG0*gZ(D1K8OT zb+UU_nl>E?cH@t3y|JevzATCy@9x|^y{8>aPXXeA89)uvN6rQHqqW^)9@j?=T8A|7 zP$@H~(B1Bd*=X$WtyySSKOxYVTz7veVgZHz%6NbUHf!z)ZU{33&mA zMs7NEOrtz06ON#9?!eu5kMACrS!E_@a1X*J2txk!FLIY4_ScfXmi_e~FW(*vRD88n zf35ke=A8OB=q@&A)$Ndf9`@HY{<_v*8~(b^U)THV75=)xUti|0S296(-}#{a7WaIW lPu}RSFZb80{dJSSUgNLd<*(oEudnb|9BlQ{!j>TT{{Wm6310vJ diff --git a/contracts/sysio.token/sysio.token.wasm b/contracts/sysio.token/sysio.token.wasm index 054b06120a529c0364844aa027473bac23fbcc8a..dfecd815cad8ecf3d2f9f413f93b33899801e5af 100755 GIT binary patch literal 10465 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 literal 12623 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 C>F%)r diff --git a/contracts/sysio.tokens/sysio.tokens.wasm b/contracts/sysio.tokens/sysio.tokens.wasm index 446db5ad35876f9d9583934a54e7a48c2b5f2861..f69d2e89d80703fa2caf8d42ecc45ae123454617 100755 GIT binary patch literal 30245 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 N{{)r7sb2_!{|_dCSnU7+ literal 33639 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 P#=Ju6HKOEAp7(zMh(SbH diff --git a/contracts/sysio.wrap/sysio.wrap.wasm b/contracts/sysio.wrap/sysio.wrap.wasm index 2c7a443ea6eff451c54f12a7fb98c35f143983aa..bb767c7b05ad12c058d86e67adf87af10c8686c1 100755 GIT binary patch literal 5033 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 literal 6814 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

`|-84gCf#5{FWGpu$!0)v8<^ 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 diff --git a/contracts/sysio.councl/sysio.councl.wasm b/contracts/sysio.councl/sysio.councl.wasm index d74fd7d85a754ba9b88a82ed97790b17691a2ea2..95e6254ddac30bb3e7081ba008248eeda5f3d8fc 100755 GIT binary patch literal 62727 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 F{{y^L$j1Nx literal 48345 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 $ $ - $ $ $ ) diff --git a/contracts/sysio.dclaim/sysio.dclaim.wasm b/contracts/sysio.dclaim/sysio.dclaim.wasm index d824580e6aefaf90ad4874145d403e75dca23b07..7bbac61485c91807bc300fb1e98715732ffeed8f 100755 GIT binary patch literal 40384 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 IWuEu{0h!DDi~s-t diff --git a/contracts/sysio.epoch/CMakeLists.txt b/contracts/sysio.epoch/CMakeLists.txt index 99adff7dd7..533340f93c 100644 --- a/contracts/sysio.epoch/CMakeLists.txt +++ b/contracts/sysio.epoch/CMakeLists.txt @@ -36,8 +36,6 @@ if(BUILD_SYSTEM_CONTRACTS) $ $ $ - $ - $ $ $ ) diff --git a/contracts/sysio.epoch/include/sysio.epoch/sysio.epoch.hpp b/contracts/sysio.epoch/include/sysio.epoch/sysio.epoch.hpp index 3d98850aff..02c1216c82 100644 --- a/contracts/sysio.epoch/include/sysio.epoch/sysio.epoch.hpp +++ b/contracts/sysio.epoch/include/sysio.epoch/sysio.epoch.hpp @@ -125,8 +125,6 @@ namespace sysio { static constexpr name OPREG_ACCOUNT = "sysio.opreg"_n; static constexpr name AUTHEX_ACCOUNT = "sysio.authex"_n; static constexpr name CHAINS_ACCOUNT = "sysio.chains"_n; - static constexpr name UWRIT_ACCOUNT = "sysio.uwrit"_n; - static constexpr name RESERV_ACCOUNT = "sysio.reserv"_n; /// True while the depot is in its epoch-0 bootstrap window: no epoch state yet, or the /// current epoch index is still 0. Never throws. The one window test `sysio.liq`'s diff --git a/contracts/sysio.epoch/src/sysio.epoch.cpp b/contracts/sysio.epoch/src/sysio.epoch.cpp index e8711f0935..fa82d89db2 100644 --- a/contracts/sysio.epoch/src/sysio.epoch.cpp +++ b/contracts/sysio.epoch/src/sysio.epoch.cpp @@ -4,14 +4,6 @@ #include #include #include -// For uwrit::MAX_LOCK_RELEASE_PER_EPOCH and uwrit::MAX_UWREQ_PRUNE_PER_EPOCH — -// the per-epoch budgets advance hands to the inline `chklocks` and -// `pruneuwreqs` sweeps (both constants are owned by sysio.uwrit). -#include -// For reserve::MAX_CLAIM_SWEEP_PER_EPOCH — the per-epoch budget advance hands -// to the inline `sweepclaims` retention sweep (the constant is owned by -// sysio.reserv). -#include // Canonical sysio.system emissions types + compute_epoch_emission. The // [[sysio::contract("sysio.system")]] attribute on emission_config / t5_state // pins them to sysio.system's ABI; no readonly mirror needed here. @@ -391,44 +383,6 @@ void epoch::advance() { // eventually passes on a subsequent chkcons retry. const uint32_t target_epoch = state.current_epoch_index + 1; - // Bounded retention sweep of expired `sysio.reserv::wireclaims`: erase rows whose one-year - // window closed and return their WIRE to the treasury. - // - // Runs BEFORE the emissions gate, deliberately. This is maintenance, not economics, and the - // WIRE it reclaims lands in the very balance the gate measures -- so ordering it after the gate - // creates a deadlock: an epoch blocked as BALANCE_INSUFFICIENT returns below without ever - // reclaiming forfeited WIRE that could cover the shortfall, and every retry takes the same - // path. The contract would be sitting on the funds needed to unblock itself. `sweepclaims` - // takes only epoch or reserv authority, so no ordinary keeper could break that cycle either. - // - // Safe to run ahead of the gate because it is bounded, never-throwing past its auth check, and - // touches no epoch state -- a blocked epoch that sweeps and still cannot pay is exactly as - // blocked as before, minus some expired rows. - // - // `sysio.reserv` also sweeps opportunistically when crediting a new claim, but that only fires - // while settlement traffic arrives; this call is what makes the deadline hold when swaps stop. - // - // GUARDED on the account existing. Dispatching an inline action to an absent code account is a - // hard `action_validate_exception`, which inside `advance` is a chain-wide epoch stall. - // `sysio.reserv` is not a precondition for advancing an epoch -- a chain can advance before - // reserves are ever deployed -- and nothing can have accrued a wireclaim in that state, so - // skipping is precisely correct rather than merely defensive. - // - // That guard covers an ABSENT account, not a STALE one: an old `sysio.reserv` build has the - // account and not the action, and the CDT dispatcher asserts on an action it does not - // implement -- so deploying `sysio.epoch` ahead of `sysio.reserv` aborts every advance. The - // deploy is therefore one atomic `sysio.msig` transaction, or `sysio.reserv` first. Both this - // edge and the `sysio.epoch`-before-`sysio.system` one the emissions gate creates are written - // up in `docs/contract-upgrade-order.md`. - if (is_account(RESERV_ACCOUNT)) { - action( - permission_level{get_self(), "owner"_n}, - RESERV_ACCOUNT, - "sweepclaims"_n, - std::make_tuple(reserve::MAX_CLAIM_SWEEP_PER_EPOCH) - ).send(); - } - const auto gate = check_emissions_ready( cfg.epoch_duration_sec, cfg.operators_per_epoch, target_epoch); if (!gate.ready) { @@ -448,34 +402,6 @@ void epoch::advance() { // attempt blocked and we're now succeeding) and proceed. clear_gate_block(get_self(), target_epoch); - // FIRST post-gate step: sweep expired underwriter collateral locks. - // Locks are a wall-clock challenge window (12h default; see - // sysio.uwrit::uwconfig.collateral_lock_duration_ms) — they are never - // released by delivery, only by this sweep. Running it before the - // delivery evaluation + withdraw flushing below means collateral freed - // by the closing window is visible to this same advance's - // `available()`-gated paths (flushwtdw, eligibility). - action( - permission_level{get_self(), "owner"_n}, - UWRIT_ACCOUNT, - "chklocks"_n, - std::make_tuple(uwrit::MAX_LOCK_RELEASE_PER_EPOCH) - ).send(); - - // Bounded UWREQ lifecycle sweep (SEC-129 / WSA-223): erase terminal - // uwreqs whose retention window elapsed; expire + refund PENDING uwreqs - // whose race never resolved inside the pending timeout. Runs after - // chklocks so both uwrit maintenance sweeps stay adjacent, and before - // buildenv so any SWAP_REVERT the expiry path emits rides THIS epoch's - // outbound envelopes. Budget-bounded (never throws) — a backlog simply - // drains across subsequent epochs. - action( - permission_level{get_self(), "owner"_n}, - UWRIT_ACCOUNT, - "pruneuwreqs"_n, - std::make_tuple(uwrit::MAX_UWREQ_PRUNE_PER_EPOCH) - ).send(); - // Before incrementing: evaluate per-op delivery state for the EXPIRING // epoch. The active group of the expiring epoch (`current_batch_op_group` // BEFORE the increment) is the set of ops responsible for delivering @@ -970,19 +896,6 @@ void epoch::advance() { } } - // Drain the swap-from-WIRE queue: each row queued via - // `sysio.uwrit::swapfromwire` since the last advance is re-validated - // (target reserve ACTIVE + public, variance) and either becomes a - // PENDING uwreq for the single-leg underwriter race or is refunded. - // Runs before `buildenv` so this epoch's envelopes reflect any state - // the drain produced; never throws (refund-and-drop semantics). - action( - permission_level{get_self(), "owner"_n}, - UWRIT_ACCOUNT, - "drainfwq"_n, - std::make_tuple() - ).send(); - // Build outbound envelopes for each outpost { sysio::chains::chains_t chains_tbl(CHAINS_ACCOUNT); diff --git a/contracts/sysio.epoch/sysio.epoch.wasm b/contracts/sysio.epoch/sysio.epoch.wasm index acb01b1f33855e350fba3113974094c8f3db2628..ad4f53caee9e98cc2400e2160aa15156b085736a 100755 GIT binary patch literal 95314 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 Z=M#AD$8#RfC-Hm=52j4Hw8CD8{{_U0iAn$f literal 79865 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ⅈ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 DuYhgY diff --git a/contracts/sysio.liq/sysio.liq.wasm b/contracts/sysio.liq/sysio.liq.wasm index 51835d1afb2dd34d7733f67486361a96c7e14a28..2e63b08ee7abca49e57fcbcacdecee9212769384 100755 GIT binary patch literal 50926 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 diff --git a/contracts/sysio.msgch/src/sysio.msgch.cpp b/contracts/sysio.msgch/src/sysio.msgch.cpp index 1587fba272..3c01a35222 100644 --- a/contracts/sysio.msgch/src/sysio.msgch.cpp +++ b/contracts/sysio.msgch/src/sysio.msgch.cpp @@ -35,12 +35,12 @@ namespace { constexpr auto SYSTEM_ACCOUNT = "sysio"_n; constexpr auto EPOCH_ACCOUNT = "sysio.epoch"_n; constexpr auto OPREG_ACCOUNT = "sysio.opreg"_n; -constexpr auto UWRIT_ACCOUNT = "sysio.uwrit"_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; -constexpr auto RESERV_ACCOUNT = "sysio.reserv"_n; + constexpr auto ROA_ACCOUNT = "sysio.roa"_n; /// The syndication contract and the intake actions an accepted envelope's syndication value is @@ -490,179 +490,6 @@ std::optional to_checksum256_exact(const std::vector& bytes) return true; } -/// Decode an OperatorAction sub-message and dispatch to the appropriate -/// sysio.opreg action. Called from the inbound dispatch loop in `evalcons`. -/// -/// Sub-type routing (post data-model refactor — codenames everywhere): -/// * DEPOSIT_REQUEST → opreg::depositinle(account, chain_code, token_code, -/// amount, actor_chain, actor_addr, -/// msg_id) -/// * WITHDRAW_REQUEST → opreg::withdrawinle(account, chain_code, token_code, -/// amount) -/// * WITHDRAW_REMIT → outbound-only (depot → outpost); silently dropped if seen inbound -/// * SLASH → depot-internal; rejected if seen inbound. Slash decisions -/// originate from sysio.chalg → opreg::slash and never re-enter -/// the depot via OPP. A slash arriving inbound here is either an -/// outpost replaying its own outbound (no-op), or a malformed -/// attestation from a misbehaving operator (drop). -/// * UNKNOWN → no-op -/// -/// `chain_code` (the proven source outpost from `deliver`) is the escrow-holding chain the -/// inline `depositinle` / `withdrawinle` is dispatched against. The payload's own -/// `OperatorAction.chain_code` must equal it (WSA-005): the proto documents field 7 as "the -/// outpost holding the escrow", which for a DEPOSIT_REQUEST / WITHDRAW_REQUEST is exactly the -/// outpost that relayed this attestation. We dispatch using the proven `chain_code`, never the -/// payload's, and drop the attestation when they diverge. -/// -/// `original_message_id` is the OPP message id of the attestation's parent -/// Message — opreg::depositinle uses it to populate DEPOSIT_REVERT correlation -/// when refunding an unaccepted deposit. -void dispatch_operator_action(name self, const std::vector& data, - uint64_t chain_code, - const checksum256& original_message_id) { - opp::attestations::OperatorAction oa; - { - auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}}; - auto rc = in(oa); - if (rc != zpp::bits::errc{}) return; // malformed; skip silently - } - - // WSA-005: bind the payload's escrow chain to the proven source outpost before crediting or - // debiting any operator collateral. A consensus envelope proven from outpost A must not be able - // to deposit/withdraw against a different chain B's operator ledger. - if (!source_chain_binding_ok(chain_code, oa.chain_code, "dispatch_operator_action")) return; - - // Resolve the operator's WIRE account from `op_address` via authex's - // bypubkey index. Outposts emit the full chain pubkey (33 bytes for - // secp256k1, 32 for Ed25519); the depot's authex link table is the - // single source of truth that maps it back to a WIRE name. On miss - // (no authex link, malformed bytes, unsupported chain kind), drop — - // the OperatorRegistry that originated this deposit will see no - // corresponding state update on the depot side and can re-emit after - // the operator completes their authex registration. - name account = resolve_account_from_op_address(oa.op_address); - if (account == name{}) return; - - using AT = opp::attestations::OperatorAction; - // Dispatch against the PROVEN source chain (equal to `oa.chain_code`, enforced by the WSA-005 - // binding check above), never the payload's own copy. TokenAmount + ChainAddress get split into - // (chain_code, token_code, amount) / (kind, address) on the inline-action tuples per the - // no-proto-messages-in-actions rule. - const sysio::slug_name chain_code_slug{chain_code}; - const sysio::slug_name token_code{oa.amount.token_code}; - // A DEPOSIT_REQUEST carries outpost custody, so an unspellable token code must be - // REFUNDED, not dropped: opreg::depositinle rejects it with DEPOSIT_REVERT before - // touching the balance map. Every other action type is a state transition with no - // escrow to return, so dropping stays correct there. - if (oa.action_type != AT::ACTION_TYPE_DEPOSIT_REQUEST && - !payload_codes_canonical({token_code}, "dispatch_operator_action")) return; - // WSA-028: TokenAmount.amount is signed on the wire. Gate it through the - // shared fail-closed parser before any unsigned use — a negative or - // out-of-range amount is dropped here, never wrapped into a huge collateral - // credit. An impossible deposit/withdraw has no escrow to refund, so a silent - // drop (not a revert) is the correct fail-closed action; the originating - // OperatorRegistry sees no matching depot state change and can re-emit a - // well-formed action. Never-throw: drop, do not check(). - const std::optional amount_opt = - sysio::opp::safe::to_depot_amount(static_cast(oa.amount.amount)); - if (!amount_opt) return; - const uint64_t raw_amount = *amount_opt; - switch (oa.action_type) { - case AT::ACTION_TYPE_DEPOSIT_REQUEST: { - // opreg::depositinle checks require_auth(get_self()=opreg). msgch - // must therefore declare opreg's own permission on the inline action. - // Privileged sysio.msgch may declare the target contract's active - // permission on an inline action without a cross-contract active - // grant. Deployment must preserve msgch's privileged status. - action( - permission_level{OPREG_ACCOUNT, "active"_n}, - OPREG_ACCOUNT, "depositinle"_n, - std::make_tuple(account, chain_code_slug, token_code, raw_amount, - oa.op_address.kind, oa.op_address.address, - original_message_id) - ).send(); - break; - } - case AT::ACTION_TYPE_WITHDRAW_REQUEST: { - // Same privileged-system-contract boundary as DEPOSIT_REQUEST: no - // msgch@sysio.code grant on opreg.active is required. - action( - permission_level{OPREG_ACCOUNT, "active"_n}, - OPREG_ACCOUNT, "withdrawinle"_n, - std::make_tuple(account, chain_code_slug, token_code, raw_amount) - ).send(); - break; - } - case AT::ACTION_TYPE_WITHDRAW_REMIT: // outbound-only — never expected inbound - case AT::ACTION_TYPE_SLASH: // depot-internal; never accepted inbound - case AT::ACTION_TYPE_UNKNOWN: - default: - break; - } -} - -/// Dispatch an UNDERWRITE_INTENT_COMMIT to sysio.uwrit::rcrdcommit. -/// -/// The full UIC bytes are forwarded verbatim so the depot can reconstruct -/// the digest and verify the underwriter's signature at race resolution -/// time. We decode here to extract the routing scalars (uwreq id, -/// uw_account, chain_code, token_code, reserve_code — the latter triple -/// disambiguates same-chain swap legs and points at the precise reserve -/// covering this leg); the authoritative copy for verification is the -/// bytes themselves, stored on `commit_entry.{source,dest}_uic_bytes`. -/// -/// After the refactor: identity scalars on UIC are codenames (uint64). `chain_code` is the proven source -/// outpost from `deliver`; `uic.chain_code` is the leg this commit covers. WSA-005 requires the two -/// to be identical — each leg's underwrite commit is emitted on, and relayed by, that leg's own -/// outpost (a source-leg UIC rides the source outpost's envelope, a dest-leg UIC the dest outpost's; -/// see `sysio.dispatch_tests`). `rcrdcommit` routes the commit to its src/dst slot by the leg chain -/// code, so a forged `uic.chain_code` could misroute the commit onto the wrong leg; we bind it to the -/// proven outpost and drop on divergence. -void dispatch_underwrite_commit(name self, const std::vector& data, uint64_t chain_code) { - opp::attestations::UnderwriteIntentCommit uic; - { - auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}}; - auto rc = in(uic); - if (rc != zpp::bits::errc{}) { - sysio::print(UIC_DISPATCH_REJECTED_LOG_PREFIX, - ": chain_code=", chain_code, - ", reason=malformed_uic\n"); - return; - } - } - // Pre-validate the relayed account string before constructing `name` below. The CDT `name` - // ctor aborts on an empty, overlong, or out-of-charset string, and an abort here would revert - // the whole evalcons/apply_consensus delivery; a malformed name is dropped instead. - const auto underwriter = sysio::opp::safe::parse_wire_account_name(uic.uw_account.name); - if (!underwriter) { - sysio::print(UIC_DISPATCH_REJECTED_LOG_PREFIX, - ": chain_code=", chain_code, - ", reason=invalid_wire_account\n"); - return; - } - - // WSA-005: the leg's chain (uic.chain_code) must be the proven delivering outpost before the - // commit is recorded against a swap leg. - if (!source_chain_binding_ok(chain_code, uic.chain_code, "dispatch_underwrite_commit")) return; - - const sysio::slug_name uic_token_code{uic.token_code}; - const sysio::slug_name uic_reserve_code{uic.reserve_code}; - if (!payload_codes_canonical({uic_token_code, uic_reserve_code}, - "dispatch_underwrite_commit")) return; - - // Route with the proven `chain_code` (equal to `uic.chain_code`, enforced above) so the leg slot - // is keyed off provenance, not the payload's self-asserted chain. - action( - permission_level{self, "active"_n}, - UWRIT_ACCOUNT, "rcrdcommit"_n, - std::make_tuple(uic.uw_request_id, *underwriter, chain_code, - sysio::slug_name{chain_code}, - uic_token_code, - uic_reserve_code, - data) - ).send(); -} - /// True iff `(chain_code, token_code)` is an active TOKEN_KIND_LIQ registry row bound to an /// active chaintokens row: the only tokens the shadow ledger mints against. bool is_active_liq_token(sysio::slug_name chain_code, sysio::slug_name token_code) { @@ -734,95 +561,6 @@ bool dispatch_liq_yield(name self, const std::vector& data, uint64_t chain return true; } -/// Dispatch a RESERVE_CREATE attestation to sysio.reserv::oncrtreserve. -/// Inserts a PENDING reserve row on the depot. Per -/// `feedback_opp_handlers_never_throw`, decode failures silently no-op. -/// The downstream `oncrtreserve` is itself a never-throw handler — duplicate -/// reserves are logged + dropped on the depot side. -void dispatch_reserve_create(name self, const std::vector& data, uint64_t chain_code) { - opp::attestations::ReserveCreate rc; - { - auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}}; - auto err = in(rc); - if (err != zpp::bits::errc{}) return; - } - - // Reserve identity + the custodied amount travel together in - // `external_amount` (a `ReserveAmount`): the amount is ALREADY in the - // depot's canonical 9-decimal frame (the outpost converts chain-native - // units at the boundary, exactly like the swap paths). A negative OR - // out-of-range TokenAmount collapses to 0, which `oncrtreserve` routes into its - // cancel/refund flow — inserting a CANCELLED row and queueing - // RESERVE_CREATE_CANCELLED so the outpost releases the creator's escrow — - // instead of wrapping into a huge custodied amount. - const auto& ext = rc.external_amount; - - // WSA-005: `external_amount.chain_code` is documented in the proto as "the outpost's own chain". - // Bind it to the proven delivering outpost so an envelope proven from outpost A cannot register a - // reserve whose external custody is claimed against a different chain B. - if (!source_chain_binding_ok(chain_code, ext.chain_code, "dispatch_reserve_create")) return; - - // No canonicality drop here: the creator's escrow is already in outpost custody, so - // an unspellable token/reserve code must be REFUNDED. reserv::oncrtreserve rejects it - // with RESERVE_CREATE_CANCELLED before persisting anything. - const sysio::slug_name ext_token_code{ext.amount.token_code}; - const sysio::slug_name ext_reserve_code{ext.reserve_code}; - - const uint64_t ext_amount = - sysio::opp::safe::to_depot_amount(static_cast(ext.amount.amount)).value_or(0); - - action( - permission_level{self, "active"_n}, - RESERV_ACCOUNT, "oncrtreserve"_n, - std::make_tuple(sysio::slug_name{ext.chain_code}, - ext_token_code, - ext_reserve_code, - rc.name, - rc.description, - ext_amount, - rc.requested_wire_amount, - rc.source_token_precision, - rc.connector_weight_bps, - rc.creator_addr.kind, - rc.creator_addr.address, - rc.is_private, - rc.creator_pub_key) - ).send(); -} - -/// Dispatch a RESERVE_CREATE_CANCEL attestation to sysio.reserv::oncnclrsv. -/// The depot decides whether the creator won or lost the race against any -/// `matchreserve` call — see `oncnclrsv`. Per -/// `feedback_opp_handlers_never_throw`, decode failures silently no-op -/// and downstream race-loss is also a silent no-op on the reserv side. -void dispatch_reserve_create_cancel(name self, const std::vector& data, uint64_t chain_code) { - opp::attestations::ReserveCreateCancel cancel; - { - auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}}; - auto err = in(cancel); - if (err != zpp::bits::errc{}) return; - } - - // WSA-005: the cancel targets the reserve on `cancel.chain_code`; bind it to the proven - // delivering outpost so an envelope proven from outpost A cannot cancel a reserve on chain B. - if (!source_chain_binding_ok(chain_code, cancel.chain_code, "dispatch_reserve_create_cancel")) return; - - const sysio::slug_name cancel_token_code{cancel.token_code}; - const sysio::slug_name cancel_reserve_code{cancel.reserve_code}; - if (!payload_codes_canonical({cancel_token_code, cancel_reserve_code}, - "dispatch_reserve_create_cancel")) return; - - action( - permission_level{self, "active"_n}, - RESERV_ACCOUNT, "oncnclrsv"_n, - std::make_tuple(sysio::slug_name{cancel.chain_code}, - cancel_token_code, - cancel_reserve_code, - cancel.creator_addr.kind, - cancel.creator_addr.address) - ).send(); -} - /// Validate and parse `s` (from `WireAccount.name`) into a sysio::name. The name string /// constructor aborts on an out-of-charset character, so we pre-validate here to keep the depot /// dispatch non-throwing. Returns nullopt for an empty, over-long (>12), or out-of-charset name. @@ -973,19 +711,9 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) bool carried_syndication_value = false; switch (type) { case AttestationType::ATTESTATION_TYPE_OPERATOR_ACTION: - dispatch_operator_action(self, data, chain_code, original_message_id); - break; - case AttestationType::ATTESTATION_TYPE_SWAP_REQUEST: - action( - permission_level{self, "active"_n}, - UWRIT_ACCOUNT, "createuwreq"_n, - std::make_tuple(attestation_id, type, chain_code, data) - ).send(); - break; - case AttestationType::ATTESTATION_TYPE_UNDERWRITE_INTENT_COMMIT: - dispatch_underwrite_commit(self, data, chain_code); + // Retired external collateral and dual-commit swap ingress. break; case AttestationType::ATTESTATION_TYPE_SWAP_REMIT: @@ -1064,20 +792,8 @@ void dispatch_node_owner_reg(const std::vector& data, uint64_t chain_code) break; case AttestationType::ATTESTATION_TYPE_RESERVE_CREATE: - // Outpost-initiated reserve creation. Insert a PENDING row on - // `sysio.reserv` awaiting a depot-side `matchreserve` call. The - // creator's outpost-side custody is locked on the originating - // outpost; refund (on RESERVE_CREATE_CANCELLED) targets - // `creator_addr`. - dispatch_reserve_create(self, data, chain_code); - break; - case AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCEL: - // Creator cancellation of a still-PENDING reserve. If the race - // against `matchreserve` is lost the reserv contract no-ops; if - // won it flips status to CANCELLED + queues a RESERVE_CREATE_CANCELLED - // back to the originating outpost so the local custody is released. - dispatch_reserve_create_cancel(self, data, chain_code); + // Retired external reserve lifecycle. break; case AttestationType::ATTESTATION_TYPE_RESERVE_CREATE_CANCELLED: @@ -1899,8 +1615,8 @@ void msgch::queueout(uint64_t chain_code, // 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} // authority; get_self() permits msgch's own inline use and governance. - check(has_auth(EPOCH_ACCOUNT) || has_auth(OPREG_ACCOUNT) || has_auth(UWRIT_ACCOUNT) || - has_auth(RESERV_ACCOUNT) || has_auth(synd::account) || has_auth(get_self()), + check(has_auth(EPOCH_ACCOUNT) || has_auth(OPREG_ACCOUNT) || + has_auth(synd::account) || has_auth(get_self()), "queueout: caller not authorized to queue outbound attestations"); // The chains registry is the ONLY authority on which chain codes exist. @@ -2217,5 +1933,4 @@ void msgch::buildenv(uint64_t chain_code) { } } - } // namespace sysio diff --git a/contracts/sysio.msgch/sysio.msgch.wasm b/contracts/sysio.msgch/sysio.msgch.wasm index 38ed9ee979695eb1396ea275e271490b67232da7..42e3a7677a7b10980cdd1702746a06b82f858045 100755 GIT binary patch literal 177916 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

9FVp|Ja-^8QZJ8*C&zyILj1G}TC@dpo&PtW8N z)0R0kK7DZCL*u(C_kEoxdK2kB(i=!0A-$RO%cN>EeRCAuLHaJzYe}CY%}AqIf1>FB zy)}xiBW=AUiZ+toM|vaao`d^#-+SMo>3siT`n?a(4^B_aOguE6j~&>ZkIm%!50XE# zhhdCOPfTw;ba3LpOul>kz`^|!2gYU&PSM7#jD_Z-Go)&BdIlWqAE(^-kzM2CyQi)G z{;?wy`w#EW_l+NTU}jHr-}t?Z{od&raJhebEpti-^KeC|F6qYh>&?mz%ZhYVHeZo7)ak|2*!1-HOg=Ss04y9n zFn##Yp@UNzq@Vynd&dt%yHcPrdCk9s*|t@-frsR@%pJrhDBW=ccbT?eNi=X8Du{utlA z9X>h06b-o9H$KMP--R$ZxclJ@H% z2O~JV%a~^0__W3hvEK0yZ+i#GcxY_j#O{3h;r;g=+?O91+dtmdl{Fg>hb$PsKWC2T zjB(0D(B6l(Z+~Fl!TZMcZD%$goC0sOcX&LXp7`PMeD^`{E4uF=o7uI;NPqvtk@4MI rr^lxz==O)l#B+zn9^Q9w3@SU1(DL+ne=iS=&tOcBTDAP!qUiqv>{cn} literal 46200 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 N746kZ4}Xv6{eRr%Z|DF3 diff --git a/contracts/sysio.chalg/CMakeLists.txt b/contracts/sysio.chalg/CMakeLists.txt index 927d901305..d96f0ac10e 100644 --- a/contracts/sysio.chalg/CMakeLists.txt +++ b/contracts/sysio.chalg/CMakeLists.txt @@ -35,8 +35,6 @@ if(BUILD_SYSTEM_CONTRACTS) $ $ $ - $ - $ $ $ $ diff --git a/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp b/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp index d99d8a870a..0850d96710 100644 --- a/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp +++ b/contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp @@ -103,112 +103,13 @@ namespace sysio { /// 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. - enum class underwrite_fault_reason : uint8_t { - SOURCE_DEPOSIT_MISSING = 0, ///< the committed source deposit does not exist on the source chain - SOURCE_DEPOSIT_MISMATCH = 1, ///< the deposit exists but its parameters do not match the commit - NON_CANONICAL_ENVELOPE = 2, ///< the commit rides an envelope later adjudicated non-canonical - }; /// 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. - enum class uwchal_ballot : uint8_t { - UPHOLD = 0, ///< fault proven — slash the underwriter; bond returns to the challenger - REJECT_REFUND = 1, ///< no fault; honest mistake — bond returns to the challenger - REJECT_FORFEIT = 2, ///< no fault; frivolous or malicious — bond goes to the underwriter - }; /// Terminal verdict of a challenge (NONE while it is OPEN). - enum class uwchal_verdict : uint8_t { - NONE = 0, ///< challenge still open - UPHELD = 1, ///< underwriter slashed; bond returned to the challenger - REJECTED_REFUND = 2, ///< council rejected — honest mistake; bond returned to the challenger - REJECTED_FORFEIT = 3, ///< council rejected — frivolous; bond forfeited to the underwriter - LAPSED = 4, ///< lock window expired with no quorum — bond returned; nobody punished - }; - - /// Open a challenge against the WINNING underwriter of a CONFIRMED underwrite request, - /// alleging its commit was faulty. Permissionless — anyone may file — but time-gated to the - /// commitment's collateral-lock window and priced: the challenger escrows the WIRE value of - /// the challenged locks as a bond (`uwchalbond` quotes it; both run one shared formula, so - /// the quote can never drift from the charge — the `split_wire_fee` principle). - /// - /// Effects, in order: validates the commitment (uwreq CONFIRMED, `underwriter` is its - /// winner, no prior challenge for this commitment — a verdict is final per commitment; - /// other commitments by the same underwriter are independently challengeable), snapshots - /// the Tier-1 electorate + quorum exactly as `opendispute` does, escrows the bond (inline - /// `sysio.token::transfer` under the challenger's own authority), records the OPEN row, and - /// marks the locks via `sysio.uwrit::holdlocks` — a held lock is NOT released at expiry - /// while its challenge is open. uwrit re-validates lock liveness authoritatively, so a - /// stale filing aborts whole, bond escrow included. - /// - /// `reason` is the numeric `underwrite_fault_reason` value (checked through - /// `magic_enum::enum_cast` at this trust boundary); `detail` carries the challenger's - /// free-text evidence context for the council (source tx ids, observed amounts, notes). - [[sysio::action]] - void openuwchal(name challenger, - uint64_t uwreq_id, - name underwriter, - uint8_t reason, - std::string detail); - - /// Cast a Tier-1 ballot in an open underwriter challenge. `ballot` is the numeric - /// `uwchal_ballot` value. Same eligibility rules as `votedispute`: the voter must belong to - /// the challenge's snapshotted electorate, one vote per owner. Record-only — the tally - /// lives in `chkuwchal`, mirroring the votedispute/chkdispute split. - [[sysio::action]] - void voteuwchal(name owner, uint64_t chal_id, uint8_t ballot); - - /// Permissionless tally crank — `chkdispute`'s sibling. Nobody has to run it on a cadence: - /// `sysio.uwrit::chklocks` (inlined from every `sysio.epoch::advance`) pokes it for any - /// expired-but-challenged lock, which works precisely because the chain is NOT paused - /// during an underwriter challenge. A manual call after the deciding vote resolves sooner. - /// - /// Resolution rules (N/Q = the snapshotted electorate size / quorum, fixed at open): - /// * UPHOLD ballots reach Q, any time — UPHELD: `slashop` the underwriter, then - /// `sysio.uwrit::sweeplocks` releases the now-deferred-slash locks (the slash lands - /// first — inline actions run depth-first in send order — so `opreg::releaselock` - /// takes its SLASHED branch, debiting the locked collateral and emitting the outbound - /// SLASH attestations); the bond is credited back to the challenger. - /// * REJECT ballots (both flavours combined) reach Q, any time — REJECTED: disposition by - /// majority among the rejectors, a tie favouring refund; `sysio.uwrit::freelocks` - /// clears the hold and the locks live out their natural window. - /// * `deadline_ms` (the locks' own expiry) passes with neither threshold met — LAPSED: - /// bond credited back to the challenger, holds cleared, the locks release on the next - /// normal sweep. No verdict, nobody punished. - /// - /// The bond is CREDITED to a claimable balance, never transferred from here — see - /// `claimbond`. Resolution also compacts the challenge row and erases its ballots. - [[sysio::action]] - void chkuwchal(uint64_t chal_id); - - /// Withdraw the WIRE this contract owes `account` from resolved underwriter challenges — - /// a refunded challenger bond, or one forfeited to a wrongly-challenged underwriter. - /// Erases the credit row and transfers the whole balance. Auth: `account` (the - /// `sysio.dclaim::claim` pattern). - /// - /// Why the payout is a PULL and not a push: `chkuwchal` can run inline under the epoch tick - /// (`sysio.epoch::advance` -> `sysio.uwrit::chklocks` -> `chkuwchal`), and - /// `sysio.token::transfer` notifies its recipient — `require_recipient(to)` runs whatever - /// code that account has deployed. A challenger contract that allows its outgoing escrow - /// and asserts on the incoming refund would abort the entire `advance` transaction; the - /// rollback leaves the challenge OPEN with its locks still held, so every later sweep - /// re-pokes the same failing transfer and epoch advancement stalls chain-wide - /// (`epoch-stall-is-fatal`, the same hazard `opreg::releaselock` clamps for). Crediting a - /// balance keeps every account the crank touches system-owned: the only transaction a - /// hostile recipient can abort is its own claim, and only its own funds are stranded. - [[sysio::action]] - void claimbond(name account); - - /// Read-only: the WIRE bond `openuwchal` would require RIGHT NOW to challenge this - /// commitment — the winning underwriter's live lock amounts, each valued through its own - /// reserve's current books (`opp::amm::token_to_wire`), summed. Returns 0 when the - /// commitment is not currently challengeable (no CONFIRMED uwreq, `underwriter` is not its - /// winner, a challenge already exists, or no live unheld locks) or when a leg cannot be - /// priced. Shares its formula helper with `openuwchal`. - [[sysio::action, sysio::read_only]] - uint64_t uwchalbond(uint64_t uwreq_id, name underwriter); // ----------------------------------------------------------------------- // Tables @@ -296,11 +197,6 @@ namespace sysio { using chalgstate_t = sysio::kv::global<"chalgstate"_n, chalg_state>; /// Challenge primary key (auto-incrementing id). - struct uwchal_key { - uint64_t id; - uint64_t primary_key() const { return id; } - SYSLIB_SERIALIZE(uwchal_key, (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 @@ -316,87 +212,20 @@ namespace sysio { /// 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. - struct [[sysio::table("uwchals")]] uwchal_entry { - uint64_t id; - uint64_t uwreq_id; ///< the challenged commitment's uwreq - name underwriter; ///< the CONFIRMED winner under challenge - name challenger; ///< who filed and posted the bond - underwrite_fault_reason reason; ///< alleged fault class - /// Challenger's free-text evidence context, capped at `max_uwchal_detail_bytes` on - /// filing and CLEARED on resolution (it is council input, not a permanent record). - std::string detail; - opp::types::DisputeStatus status; ///< OPEN / RESOLVED — same lifecycle enum as `disputes` - uwchal_verdict verdict; ///< NONE while OPEN; the terminal outcome after - uint64_t bond_amount = 0; ///< WIRE the challenger escrowed (9-decimal units) - /// The challenged locks' `expires_at_ms` — the vote deadline. Past it an unresolved - /// challenge LAPSES (bond refunded); there is no after-deadline relaxed tally. - uint64_t deadline_ms = 0; - time_point opened_at{}; - uint8_t network_gen = 0; ///< roa network generation the electorate was drawn from - /// The Tier-1 electorate + quorum, snapshotted at open — same discipline as - /// `dispute_entry`: voter eligibility (`voteuwchal`) and the tally denominator - /// (`chkuwchal`) come from one list frozen when the challenge opened, so registrations - /// after open can neither join nor dilute it. CLEARED on resolution together with the - /// ballots it gated; `quorum` is kept as the fixed-width record of the threshold used. - std::vector electorate; - uint32_t quorum = 0; - - /// Uniqueness: one challenge EVER per (uwreq, underwriter) commitment. - uint128_t by_uwreq_underwriter() const { - return (static_cast(uwreq_id) << 64) | underwriter.value; - } - - SYSLIB_SERIALIZE(uwchal_entry, - (id)(uwreq_id)(underwriter)(challenger)(reason)(detail)(status)(verdict) - (bond_amount)(deadline_ms)(opened_at)(network_gen)(electorate)(quorum)) - }; - - using uwchals_t = sysio::kv::table<"uwchals"_n, uwchal_key, uwchal_entry, - sysio::kv::index<"byuwrequw"_n, - sysio::const_mem_fun> - >; /// 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. - struct uwchal_vote_key { - uint64_t owner; - uint64_t primary_key() const { return owner; } - SYSLIB_SERIALIZE(uwchal_vote_key, (owner)) - }; /// 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. - struct [[sysio::table("uwchalvote")]] uwchal_vote { - name owner; - uwchal_ballot ballot; - time_point voted_at{}; - - SYSLIB_SERIALIZE(uwchal_vote, (owner)(ballot)(voted_at)) - }; - - using uwchalvotes_t = - sysio::kv::scoped_table<"uwchalvote"_n, uwchal_vote_key, uwchal_vote>; /// Claimable-bond primary key (the account the payout is owed to). - struct bond_credit_key { - uint64_t account; - uint64_t primary_key() const { return account; } - SYSLIB_SERIALIZE(bond_credit_key, (account)) - }; /// 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. - struct [[sysio::table("bondcredits")]] bond_credit { - name account; - uint64_t amount = 0; ///< unclaimed WIRE, 9-decimal units - - SYSLIB_SERIALIZE(bond_credit, (account)(amount)) - }; - - using bondcredits_t = sysio::kv::table<"bondcredits"_n, bond_credit_key, bond_credit>; private: // Well-known accounts @@ -404,10 +233,6 @@ namespace sysio { static constexpr name MSGCH_ACCOUNT = "sysio.msgch"_n; static constexpr name OPREG_ACCOUNT = "sysio.opreg"_n; static constexpr name ROA_ACCOUNT = "sysio.roa"_n; - static constexpr name UWRIT_ACCOUNT = "sysio.uwrit"_n; - static constexpr name RESERV_ACCOUNT = "sysio.reserv"_n; - static constexpr name TOKEN_ACCOUNT = "sysio.token"_n; - static constexpr name LIQ_ACCOUNT = "sysio.liq"_n; using DisputeStatus = opp::types::DisputeStatus; using NodeOwnerTier = opp::types::NodeOwnerTier; @@ -417,13 +242,6 @@ namespace sysio { /// relaxes to a quorum of cast votes plus a strict majority of cast votes. static constexpr uint32_t dispute_deadline_sec = 24 * 60 * 60; - /// Upper bound on `openuwchal`'s caller-controlled `detail` note. Filing is permissionless - /// and the bond returns on every non-forfeit outcome, so recycled bond capital could - /// otherwise pin near-arbitrary payloads in RAM billed to `sysio`. This caps ONE note; - /// `chkuwchal`'s compaction of the resolved row is what caps the total (the two together - /// bound retained challenge bytes at `max_uwchal_detail_bytes` x concurrently-open - /// challenges). 1 KiB keeps the allegation note useful for the council. - static constexpr size_t max_uwchal_detail_bytes = 1024; }; } // namespace sysio diff --git a/contracts/sysio.chalg/src/sysio.chalg.cpp b/contracts/sysio.chalg/src/sysio.chalg.cpp index b8c48ab67b..0924d72d2f 100644 --- a/contracts/sysio.chalg/src/sysio.chalg.cpp +++ b/contracts/sysio.chalg/src/sysio.chalg.cpp @@ -1,12 +1,7 @@ #include #include // authoritative T1 electorate snapshot at open -#include // uwreq + lock reads for the underwriter challenge -#include // reserve books that price the challenge bond #include // operator status guard before slashing -#include // shadow stat rows naming a depot-native bucket's outpost pair -#include // token_to_wire — the bond's WIRE valuation -#include #include #include @@ -17,7 +12,6 @@ namespace sysio { using opp::types::DisputeStatus; using opp::types::NodeOwnerTier; using opp::types::OperatorStatus; -using opp::types::UnderwriteRequestStatus; // System-owned rows bill to the sysio RAM pool, not this contract account (privileged-contract // model, as sysio.token uses): the account stays finite at code+abi size; growth draws from the pool. @@ -29,70 +23,6 @@ namespace { constexpr const char* DISPUTE_REQUIRES_TWO_CANDIDATES = "a dispute requires at least two candidate envelope versions"; -/// Floor valuation, in WIRE atomic units, for a NONZERO collateral bucket the depot cannot price — -/// its pricing pair (see `pricing_pair_for`) carries no ACTIVE reserve, or the pair's books floor -/// the conversion to zero (dust). -/// -/// Pricing such a bucket at this floor rather than refusing the whole quote is deliberate, and the -/// direction matters. Collateral ingress (`opreg::depositinle`) accepts any positive amount for any -/// pair without requiring one that quotes, so refusing made unpriceability an ATTACKER-CONTROLLED -/// switch: an underwriter could seed a single dust or unreserved bucket and permanently immunize -/// every later CONFIRMED commitment against challenge. An understated stake weakens the deterrent -/// for one filing; an unchallengeable commitment defeats the mechanism outright and cannot be -/// recovered from. The floor is the strictly safer failure, and it is deterministic — the same -/// bucket always contributes the same amount, so `uwchalbond` and `openuwchal` still agree. -/// -/// Shares the provisional-pricing caveat on `compute_uwchal_bond`: acceptable while collateral is -/// small, and a real valuation for unreserved pairs is part of that same pre-launch revisit. A -/// depot-native shadow LIQ bucket is NOT such a pair: it is priced on its outpost pair's live -/// books, and it floors here only when that outpost pair has no ACTIVE reserve (or its books floor -/// the conversion to zero). `opreg::deposit` bonds only tokens its resolver finds a `sysio.liq` -/// stat row for, so a WIRE-chain bucket without one is not a bucket the operator-signed path can -/// create; `pricing_pair_for` still falls back to the bucket's own, unreserved pair for it rather -/// than abort. -constexpr uint64_t MIN_UWCHAL_BUCKET_WIRE = 1; - -/// The `(chain_code, token_code)` pair whose reserve books price one collateral bucket — the key -/// `compute_uwchal_bond` walks the `bychaintok` index by. -struct pricing_pair { - sysio::slug_name chain_code; ///< chain of the reserve that prices the bucket - sysio::slug_name token_code; ///< token of the reserve that prices the bucket - - /// The `sysio.reserv` `bychaintok` composite key for this pair (`reserve_row::by_chain_token`). - uint128_t by_chain_token() const { - return (static_cast(chain_code.value) << 64) | token_code.value; - } -}; - -/// The pair whose reserve prices collateral bucket `bal`. The native `(WIRE, WIRE)` bucket never -/// reaches here — `compute_uwchal_bond` counts it at face value first. -/// -/// * A depot-native shadow LIQ bucket, keyed `(opp::wire::chain_code, token_code)` by -/// `sysio.opreg`, mirrors one outpost liq token 1:1 in depot units. No reserve carries the -/// WIRE-chain key, so the bucket is priced on the outpost pair its `sysio.liq` `stat` row -/// names (`currency_stats::chain_code` / `token_code`), found through the `bytoken` index on -/// the bucket's registry token code — the lookup `sysio.opreg` resolves shadow custody with. -/// * A WIRE-chain bucket with no stat row, and every outpost-escrowed bucket, is priced on its -/// own pair; for the former that pair carries no reserve, so it floors at -/// `MIN_UWCHAL_BUCKET_WIRE`. -/// -/// Never throws: `uwchalbond`'s soft contract answers every unpriceable state with a number, not -/// an abort, and a throw here would let a bucket the depot cannot resolve block `openuwchal` — -/// the underwriter-controlled immunity switch `MIN_UWCHAL_BUCKET_WIRE` exists to close. A missing -/// stat row therefore degrades to the floor. -pricing_pair pricing_pair_for(name liq_account, const opreg::balance_entry& bal) { - if (bal.chain_code != opp::wire::chain_code) return {bal.chain_code, bal.token_code}; - const auto stat = liq::find_stat_by_token(liq_account, bal.token_code); - if (!stat) return {bal.chain_code, bal.token_code}; - return {stat->chain_code, stat->token_code}; -} - -/// Wall-clock now in ms — the clock `sysio.uwrit`'s lock window runs on -/// (`lock_entry.expires_at_ms`), so challenge deadlines compare like-for-like. -uint64_t current_time_ms() { - return static_cast(current_time_point().sec_since_epoch()) * 1000; -} - /// Snapshot the Tier-1 electorate: the Tier-1 rows of `sysio.roa::nodeowners` for `network_gen`, /// walked via the `bytier` index (bounded by the Tier-1 registration cap). Shared by /// `opendispute` and `openuwchal` so both case types freeze eligibility and quorum from the SAME @@ -109,151 +39,6 @@ std::vector snapshot_t1_electorate(name roa_account, uint8_t network_gen) return electorate; } -/// The bond quote for challenging one commitment — see `compute_uwchal_bond`. -struct uwchal_bond_quote { - uint64_t bond = 0; ///< Σ the underwriter's collateral quoted to WIRE; 0 = unquotable - uint64_t deadline_ms = 0; ///< the locks' shared `expires_at_ms` — becomes the vote deadline - uint32_t live_locks = 0; ///< locks counted into the bond -}; - -/// THE bond formula — one function behind both the read-only quote (`uwchalbond`) and the charge -/// (`openuwchal`), so the two can never drift (the `split_wire_fee` principle). -/// -/// The bond is the underwriter's ENTIRE collateral, quoted to WIRE (Jonathan, 2026-08-11: -/// *"sum their outpost deposits based on current quotes to convert TO WIRE … and use that amount -/// for now"*). That is what an upheld verdict actually costs them: `opreg::slash` drains every -/// bucket's slashable-now portion and `sweeplocks` deferred-slashes the locked remainder, so the -/// challenger stakes what the underwriter stands to lose. -/// -/// Deposits are per-outpost and per-token — native and ERC-20/SPL alike — so each bucket is -/// quoted on its pair's live reserve books and the quotes summed. A WIRE-denominated bucket, if -/// one exists, contributes directly. A depot-native shadow LIQ bucket `(WIRE, LIQETH)` is quoted -/// on the books of the outpost pair it mirrors 1:1 (`(ETH, LIQETH)`) — see `pricing_pair_for`. -/// -/// **REVISIT (Jonathan, 2026-08-11 — "make a note to revisit").** Two provisional choices here: -/// 1. A (chain, token) pair may carry SEVERAL active reserves — PRIMARY plus private ones — at -/// different depths and therefore different prices. This takes the first ACTIVE row from the -/// `bychaintok` index, which is arbitrary when more than one qualifies. -/// 2. The quote is a spot read of pool depth, so the bond a challenger is quoted can move -/// before they file. A configured WIRE figure would be stable; a curve quote is not. -/// Both are acceptable while collateral is small and pairs carry one public reserve; neither -/// survives a deep private-reserve market. Revisit before launch. -/// -/// The lock walk that remains is the LIVENESS gate, not the amount: filing is time-boxed to the -/// live lock window, a commitment already under challenge is not re-challengeable, and the locks -/// carry the shared `expires_at_ms` that becomes the vote deadline. -/// -/// Returns a zeroed quote (unchallengeable / unquotable) when: the winner has no locks for the -/// uwreq, any lock is already held by another challenge or already expired (filing is time-gated -/// to the live window), the operator row is gone, the operator holds no collateral at all, or the -/// sum exceeds `asset::max_amount`. -/// -/// A bucket the depot cannot price does NOT void the quote — it contributes -/// `MIN_UWCHAL_BUCKET_WIRE`. Voiding made unpriceability an underwriter-controlled immunity -/// switch, since collateral ingress never required a quoteable pair; that constant carries the -/// full argument. A shadow bucket is priced on its outpost pair and floors when that pair has no -/// ACTIVE reserve. -/// -/// The bound is the TRANSFERABLE asset range (2^62-1), not `uint64_t`'s: `openuwchal` escrows the -/// quote as `asset(static_cast(quote.bond), opp::wire::asset_symbol)`, and `asset`'s range -/// check -/// rejects anything above `max_amount`. Bounding at uint64 instead let a two-leg quote land in -/// (`asset::max_amount`, `uint64_t::max`] — `uwchalbond` would advertise a nonzero bond that -/// `openuwchal` could never escrow, so filing reverted before any lock was held and the -/// commitment became unchallengeable. Each leg is independently valid there; the SUM is what -/// overflows (source and destination may price against the same imbalanced reserve). Quoting zero -/// keeps the advertised bond and what filing can actually escrow in agreement — the same range -/// discipline `credit_bond` applies on the payout side. -uwchal_bond_quote compute_uwchal_bond(name uwrit_account, name reserv_account, name opreg_account, - name liq_account, uint64_t uwreq_id, name underwriter) { - uwchal_bond_quote quote; - const uint64_t now_ms = current_time_ms(); - - uwrit::locks_t locks(uwrit_account); - reserve::reserves_t reserves(reserv_account); - - // The lock walk is the LIVENESS gate, not the amount: filing is time-boxed to the live lock - // window, and a commitment already under challenge is not re-challengeable. It also carries - // the shared `expires_at_ms` that becomes the vote deadline. - auto by_uwreq = locks.get_index<"byuwreq"_n>(); - for (auto it = by_uwreq.lower_bound(uwreq_id); - it != by_uwreq.end() && it->uwreq_id == uwreq_id; ++it) { - if (it->underwriter != underwriter) continue; - if (it->challenge_id != 0 || now_ms >= it->expires_at_ms) return uwchal_bond_quote{}; - quote.deadline_ms = it->expires_at_ms; - ++quote.live_locks; - } - if (quote.live_locks == 0) return uwchal_bond_quote{}; - - // The bond is EVERYTHING the underwriter stands to lose: an upheld verdict drains every - // collateral bucket (`opreg::slash` takes each bucket's slashable-now portion, and - // `sweeplocks` deferred-slashes the locked remainder). Buckets are denominated per outpost - // in that leg's own token — native and ERC-20/SPL alike — so each is quoted to WIRE on its - // pair's live books and the quotes are summed. - opreg::operators_t ops(opreg_account); - auto op = ops.find(opreg::operator_key{underwriter.value}); - if (op == ops.end()) return uwchal_bond_quote{}; - - auto by_chain_token = reserves.template get_index<"bychaintok"_n>(); - opp::amm::u128 total = 0; - for (const auto& bal : op->balances) { - if (bal.balance == 0) continue; - if (opp::wire::is_native_asset(bal.chain_code, bal.token_code)) { - total += bal.balance; // already WIRE — no curve to ride - continue; - } - // FIRST ACTIVE reserve for the bucket's pricing pair — its own pair, or for a depot-native - // shadow bucket the outpost pair it mirrors. A pair may carry several (PRIMARY plus private - // ones) at different depths, so this pricing is deliberately provisional — see the - // revisit note on the declaration. - const uint128_t ck = pricing_pair_for(liq_account, bal).by_chain_token(); - uint64_t bucket_wire = 0; - for (auto it = by_chain_token.lower_bound(ck); - it != by_chain_token.end() && it->by_chain_token() == ck; ++it) { - if (it->status != opp::types::RESERVE_STATUS_ACTIVE) continue; - bucket_wire = opp::amm::token_to_wire(it->reserve_chain_amount, it->reserve_wire_amount, - it->connector_weight_bps, bal.balance); - break; - } - // A bucket the depot cannot price (no ACTIVE reserve for the pair, or books that floor the - // conversion to zero) contributes its floor instead of voiding the whole quote. Refusing - // here understated nothing but handed the underwriter a switch: seed one dust or unreserved - // bucket at ingress and every later commitment becomes unchallengeable. See - // `MIN_UWCHAL_BUCKET_WIRE` for why the understatement is the safer of the two. - total += (bucket_wire > 0 ? bucket_wire : MIN_UWCHAL_BUCKET_WIRE); - } - if (total == 0) return uwchal_bond_quote{}; - if (total > static_cast(asset::max_amount)) return uwchal_bond_quote{}; - quote.bond = static_cast(total); - return quote; -} - -/// Credit `amount` WIRE to `account`'s claimable-bond balance, accumulating onto an existing row. -/// -/// This is how a resolved challenge's escrow leaves `chkuwchal` — deliberately NOT a transfer. -/// `chkuwchal` can run inline under `sysio.epoch::advance`, and `sysio.token::transfer` runs the -/// recipient's code through `require_recipient(to)`; an asserting recipient there would abort the -/// whole advance and, with the challenge rolled back to OPEN and its locks still held, stall epoch -/// advancement chain-wide. Crediting touches only this contract's own table. See `chalg::claimbond`. -/// -/// Saturates at `asset::max_amount` (the `sysio.dclaim::add_wire_capped` discipline): the credit -/// is paid out as an `asset`, whose amount is a signed 62-bit quantity, so an unclamped sum could -/// build a row that `claimbond` can never construct a payout for. -void credit_bond(name self, name account, uint64_t amount) { - chalg::bondcredits_t credits(self); - const auto pk = chalg::bond_credit_key{account.value}; - auto it = credits.find(pk); - if (it == credits.end()) { - const uint64_t seed = std::min(amount, static_cast(asset::max_amount)); - credits.emplace(ram_payer, pk, chalg::bond_credit{ .account = account, .amount = seed }); - return; - } - credits.modify(same_payer, pk, [&](auto& r) { - const uint64_t room = static_cast(asset::max_amount) - r.amount; - r.amount += (amount <= room ? amount : room); - }); -} - } // anonymous namespace // --------------------------------------------------------------------------- @@ -466,299 +251,4 @@ void chalg::chkdispute(uint64_t dispute_id) { } } -// --------------------------------------------------------------------------- -// openuwchal — file an underwriter-fault challenge (WIRE-297) -// --------------------------------------------------------------------------- -void chalg::openuwchal(name challenger, uint64_t uwreq_id, name underwriter, - uint8_t reason, std::string detail) { - require_auth(challenger); - - // Trust boundary: the ABI carries the numeric value; the checked cast is the validation - // (never static_cast — out-of-range would be UB and hide bad input). - const auto fault = magic_enum::enum_cast(reason); - check(fault.has_value(), "openuwchal: unknown fault reason"); - check(detail.size() <= max_uwchal_detail_bytes, - "openuwchal: detail exceeds max_uwchal_detail_bytes"); - - // The challenged commitment: a CONFIRMED uwreq whose recorded winner is `underwriter`. - uwrit::uwreqs_t reqs(UWRIT_ACCOUNT); - auto rq = reqs.find(uwrit::id_key{uwreq_id}); - check(rq != reqs.end(), "openuwchal: underwrite request not found"); - check(rq->status == UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_CONFIRMED, - "openuwchal: underwrite request is not CONFIRMED"); - check(rq->winner == underwriter, "openuwchal: underwriter is not this request's winner"); - - // A verdict is final per commitment: at most one challenge EVER per (uwreq, underwriter). - // Other commitments — other swaps, any epoch — are independently challengeable. - uwchals_t chals(get_self()); - auto uq_idx = chals.get_index<"byuwrequw"_n>(); - const uint128_t composite = (static_cast(uwreq_id) << 64) | underwriter.value; - check(uq_idx.find(composite) == uq_idx.end(), - "openuwchal: this commitment has already been challenged"); - - // Price the bond off the SAME formula `uwchalbond` quotes. Zero live locks means the window - // has closed (or never opened); an unpriceable bond refuses the filing rather than guessing. - const auto quote = - compute_uwchal_bond(UWRIT_ACCOUNT, RESERV_ACCOUNT, OPREG_ACCOUNT, LIQ_ACCOUNT, uwreq_id, underwriter); - check(quote.live_locks > 0, - "openuwchal: no live collateral locks to challenge (the lock window has closed)"); - check(quote.bond > 0, "openuwchal: challenge bond cannot be priced"); - - const uint8_t network_gen = roa::current_network_gen(ROA_ACCOUNT); - auto electorate = snapshot_t1_electorate(ROA_ACCOUNT, network_gen); - // Same refusal as opendispute: an electorate that cannot vote could never resolve the - // challenge — it would only lapse at the window's end, having held the bond for nothing. - check(!electorate.empty(), "cannot open a challenge with no registered tier-1 node owners"); - const uint32_t quorum = static_cast(electorate.size()) / 2 + 1; - - // Escrow the bond under the challenger's OWN authority (the swapfromwire escrow pattern) — - // this contract holds it until resolution routes it per the verdict. - action( - permission_level{challenger, "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(challenger, get_self(), - asset(static_cast(quote.bond), opp::wire::asset_symbol), - std::string("sysio.chalg::openuwchal challenge bond")) - ).send(); - - const auto now = current_time_point(); - uint64_t next_id = std::max(1, chals.available_primary_key()); - - chals.emplace(ram_payer, uwchal_key{next_id}, uwchal_entry{ - .id = next_id, - .uwreq_id = uwreq_id, - .underwriter = underwriter, - .challenger = challenger, - .reason = *fault, - .detail = std::move(detail), - .status = DisputeStatus::DISPUTE_STATUS_OPEN, - .verdict = uwchal_verdict::NONE, - .bond_amount = quote.bond, - .deadline_ms = quote.deadline_ms, - .opened_at = now, - .network_gen = network_gen, - .electorate = std::move(electorate), - .quorum = quorum, - }); - - // Mark the locks held. uwrit owns the lock data and re-validates liveness authoritatively — - // a stale filing aborts the WHOLE open, bond escrow included. A held lock is skipped by - // chklocks until the challenge resolves or lapses. NOTE: deliberately NO epoch pause — an - // unresolved challenge is survivable, so the chain keeps advancing. - action( - permission_level{get_self(), "active"_n}, - UWRIT_ACCOUNT, "holdlocks"_n, - std::make_tuple(uwreq_id, underwriter, next_id) - ).send(); -} - -// --------------------------------------------------------------------------- -// voteuwchal — Tier-1 ballot in an open challenge (record-only, like votedispute) -// --------------------------------------------------------------------------- -void chalg::voteuwchal(name owner, uint64_t chal_id, uint8_t ballot) { - require_auth(owner); - - const auto cast_ballot = magic_enum::enum_cast(ballot); - check(cast_ballot.has_value(), "voteuwchal: unknown ballot value"); - - uwchals_t chals(get_self()); - auto c = chals.get(uwchal_key{chal_id}, "challenge not found"); - check(c.status == DisputeStatus::DISPUTE_STATUS_OPEN, "challenge is not open"); - - // Ballots are valid only inside the challenge window. Past deadline_ms the sole lawful - // outcome is LAPSED (there is deliberately no after-deadline relaxed tally), so a late - // quorum must not be assemblable for a manual chkuwchal crank in the gap between expiry - // and the next epoch-tick poke. - check(current_time_ms() < c.deadline_ms, "voteuwchal: the challenge window has expired"); - - // Voter eligibility: membership in the challenge's snapshotted Tier-1 electorate — the same - // list chkuwchal's quorum is measured against, so a voter the tally would not count can never - // cast a ballot. - check(std::find(c.electorate.begin(), c.electorate.end(), owner) != c.electorate.end(), - "voter is not in the challenge's tier-1 electorate"); - - // One ballot per owner (the vote table is scoped by chal_id). - uwchalvotes_t votes(get_self(), chal_id); - auto v_pk = uwchal_vote_key{owner.value}; - check(!votes.contains(v_pk), "owner has already voted in this challenge"); - - votes.emplace(ram_payer, v_pk, uwchal_vote{ - .owner = owner, - .ballot = *cast_ballot, - .voted_at = current_time_point(), - }); -} - -// --------------------------------------------------------------------------- -// chkuwchal — tally an open challenge; resolve or lapse (chkdispute's sibling) -// --------------------------------------------------------------------------- -void chalg::chkuwchal(uint64_t chal_id) { - // Permissionless crank — poked inline by `sysio.uwrit::chklocks` for any expired-but- - // challenged lock (the epoch tick IS the cadence; possible because the chain is not paused), - // or called manually right after the deciding vote for a faster resolution. - uwchals_t chals(get_self()); - auto c_pk = uwchal_key{chal_id}; - auto c = chals.get(c_pk, "challenge not found"); - check(c.status == DisputeStatus::DISPUTE_STATUS_OPEN, "challenge is not open"); - - const uint32_t N = static_cast(c.electorate.size()); - const uint32_t Q = c.quorum; - check(Q > 0 && Q <= N, "challenge has no electorate snapshot"); - - // Tally the cast ballots. - uint32_t uphold = 0, reject_refund = 0, reject_forfeit = 0; - uwchalvotes_t votes(get_self(), chal_id); - for (auto it = votes.begin(); it != votes.end(); ++it) { - switch (it->ballot) { - case uwchal_ballot::UPHOLD: ++uphold; break; - case uwchal_ballot::REJECT_REFUND: ++reject_refund; break; - case uwchal_ballot::REJECT_FORFEIT: ++reject_forfeit; break; - } - } - - // Resolution rules — quorum thresholds any time; the deadline only LAPSES (there is - // deliberately no after-deadline relaxed tally: a challenge must be voted before the lock - // window expires; envelope disputes relax only because a paused chain MUST resolve). - uwchal_verdict verdict = uwchal_verdict::NONE; - if (uphold >= Q) { - verdict = uwchal_verdict::UPHELD; - } else if (reject_refund + reject_forfeit >= Q) { - // Disposition by majority among the rejectors; a tie favours the refund — forfeiture - // only ever happens by explicit council judgment. - verdict = (reject_forfeit > reject_refund) ? uwchal_verdict::REJECTED_FORFEIT - : uwchal_verdict::REJECTED_REFUND; - } else if (current_time_ms() >= c.deadline_ms) { - verdict = uwchal_verdict::LAPSED; - } - if (verdict == uwchal_verdict::NONE) return; // keep waiting for ballots - - chals.modify(same_payer, c_pk, [&](auto& r) { - r.status = DisputeStatus::DISPUTE_STATUS_RESOLVED; - r.verdict = verdict; - // Compact to a fixed-width tombstone. Past resolution the row has exactly two on-chain - // jobs — the `byuwrequw` uniqueness gate (a verdict is final per commitment) and the - // verdict record — and neither needs the variable-length fields. `detail` was - // caller-controlled evidence FOR the council; `electorate` was the ballot roll. Both stay - // permanently readable in the filing's action trace, and dropping them here is what bounds - // this contract's 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 per-note cap alone cannot do that, because filing is - // permissionless and the bond comes back on every non-forfeit outcome. - r.detail.clear(); - r.electorate.clear(); - }); - - // The ballots go with it: they were the tally input above and the one-vote-per-owner gate, - // neither of which applies once the row leaves OPEN. Collect first, erase second (an erase - // invalidates the iterator) — bounded by the Tier-1 electorate this challenge snapshotted, the - // same bound the tally walk above already runs under. - std::vector ballot_owners; - ballot_owners.reserve(uphold + reject_refund + reject_forfeit); - for (auto it = votes.begin(); it != votes.end(); ++it) { - ballot_owners.push_back(it->owner.value); - } - for (uint64_t owner : ballot_owners) { - votes.erase(uwchal_vote_key{owner}); - } - - if (verdict == uwchal_verdict::UPHELD) { - // Slash FIRST, then sweep: inline actions run depth-first in send order, so the SLASHED - // status flip lands before sweeplocks' releaselock calls — which then take the - // deferred-slash branch, debiting the locked collateral and emitting the outbound SLASH - // attestations. Guard on live status: this can run inline from the epoch tick, where an - // aborted slash (already SLASHED/TERMINATED out-of-band) must not stall advancement. - opreg::operators_t ops(OPREG_ACCOUNT); - auto op = ops.find(opreg::operator_key{c.underwriter.value}); - const bool slashable = op != ops.end() && - op->status != OperatorStatus::OPERATOR_STATUS_SLASHED && - op->status != OperatorStatus::OPERATOR_STATUS_TERMINATED; - if (slashable) { - action( - permission_level{get_self(), "active"_n}, - get_self(), "slashop"_n, - std::make_tuple(c.underwriter, - std::string("underwriter fault upheld (") - + std::string(magic_enum::enum_name(c.reason)) - + "): challenge " + std::to_string(chal_id)) - ).send(); - } - action( - permission_level{get_self(), "active"_n}, - UWRIT_ACCOUNT, "sweeplocks"_n, - std::make_tuple(c.uwreq_id, c.underwriter) - ).send(); - } else { - // REJECTED or LAPSED: clear the hold; the locks live out their natural window and release - // on the next normal sweep (a healthy release — no collateral moves). - action( - permission_level{get_self(), "active"_n}, - UWRIT_ACCOUNT, "freelocks"_n, - std::make_tuple(c.uwreq_id, c.underwriter) - ).send(); - } - - // Route the bond per the verdict: forfeiture to the wrongly-challenged underwriter ONLY on an - // explicit REJECT_FORFEIT majority; every other outcome returns it to the challenger. - // - // CREDITED, never transferred. This whole function can run inline under - // `sysio.epoch::advance -> sysio.uwrit::chklocks`, where `sysio.token::transfer`'s - // `require_recipient(to)` would run the recipient's own code and let it abort epoch - // advancement — see `chalg::claimbond` for the full argument. The escrow stays in this - // contract's custody until the recipient pulls it. - if (c.bond_amount > 0) { - const bool forfeited = (verdict == uwchal_verdict::REJECTED_FORFEIT); - credit_bond(get_self(), forfeited ? c.underwriter : c.challenger, c.bond_amount); - } -} - -// --------------------------------------------------------------------------- -// claimbond — pull a resolved challenge's bond out of this contract's custody -// --------------------------------------------------------------------------- -void chalg::claimbond(name account) { - // The recipient's own authority, as `sysio.dclaim::claim` requires it: the transfer below - // notifies `account`, so only `account` can trigger code execution on its own behalf here. - require_auth(account); - - bondcredits_t credits(get_self()); - const auto pk = bond_credit_key{account.value}; - auto it = credits.find(pk); - check(it != credits.end(), "claimbond: no claimable bond"); - const uint64_t amount = it->amount; - check(amount > 0, "claimbond: zero claimable balance"); - - // Erase BEFORE sending: the inline transfer runs after this action returns and notifies - // `account`, which may re-enter `claimbond`. The row must already be gone when it does. - credits.erase(pk); - - action( - permission_level{get_self(), "active"_n}, - TOKEN_ACCOUNT, "transfer"_n, - std::make_tuple(get_self(), account, - asset(static_cast(amount), opp::wire::asset_symbol), - std::string("sysio.chalg challenge bond payout")) - ).send(); -} - -// --------------------------------------------------------------------------- -// uwchalbond — read-only quote of the bond openuwchal would charge right now -// --------------------------------------------------------------------------- -uint64_t chalg::uwchalbond(uint64_t uwreq_id, name underwriter) { - // Soft gating — a quote, not a filing: every not-currently-challengeable state answers 0 - // (callers treat 0 as "no quote"), mirroring `sysio.reserv::swapquote`'s contract. - uwrit::uwreqs_t reqs(UWRIT_ACCOUNT); - auto rq = reqs.find(uwrit::id_key{uwreq_id}); - if (rq == reqs.end()) return 0; - if (rq->status != UnderwriteRequestStatus::UNDERWRITE_REQUEST_STATUS_CONFIRMED) return 0; - if (rq->winner != underwriter) return 0; - - uwchals_t chals(get_self()); - auto uq_idx = chals.get_index<"byuwrequw"_n>(); - const uint128_t composite = (static_cast(uwreq_id) << 64) | underwriter.value; - if (uq_idx.find(composite) != uq_idx.end()) return 0; // already challenged — a verdict is final - - const auto quote = - compute_uwchal_bond(UWRIT_ACCOUNT, RESERV_ACCOUNT, OPREG_ACCOUNT, LIQ_ACCOUNT, uwreq_id, underwriter); - return quote.bond; -} - } // namespace sysio diff --git a/contracts/sysio.chalg/sysio.chalg.abi b/contracts/sysio.chalg/sysio.chalg.abi index e9b03b249a..a67f9a100b 100644 --- a/contracts/sysio.chalg/sysio.chalg.abi +++ b/contracts/sysio.chalg/sysio.chalg.abi @@ -3,30 +3,6 @@ "version": "sysio::abi/1.2", "types": [], "structs": [ - { - "name": "bond_credit", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - }, - { - "name": "amount", - "type": "uint64" - } - ] - }, - { - "name": "bond_credit_key", - "base": "", - "fields": [ - { - "name": "account", - "type": "uint64" - } - ] - }, { "name": "chalg_state", "base": "", @@ -47,26 +23,6 @@ } ] }, - { - "name": "chkuwchal", - "base": "", - "fields": [ - { - "name": "chal_id", - "type": "uint64" - } - ] - }, - { - "name": "claimbond", - "base": "", - "fields": [ - { - "name": "account", - "type": "name" - } - ] - }, { "name": "dispute_candidate", "base": "", @@ -187,32 +143,6 @@ } ] }, - { - "name": "openuwchal", - "base": "", - "fields": [ - { - "name": "challenger", - "type": "name" - }, - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - }, - { - "name": "reason", - "type": "uint8" - }, - { - "name": "detail", - "type": "string" - } - ] - }, { "name": "slashop", "base": "", @@ -227,120 +157,6 @@ } ] }, - { - "name": "uwchal_entry", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - }, - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - }, - { - "name": "challenger", - "type": "name" - }, - { - "name": "reason", - "type": "underwrite_fault_reason" - }, - { - "name": "detail", - "type": "string" - }, - { - "name": "status", - "type": "DisputeStatus" - }, - { - "name": "verdict", - "type": "uwchal_verdict" - }, - { - "name": "bond_amount", - "type": "uint64" - }, - { - "name": "deadline_ms", - "type": "uint64" - }, - { - "name": "opened_at", - "type": "time_point" - }, - { - "name": "network_gen", - "type": "uint8" - }, - { - "name": "electorate", - "type": "name[]" - }, - { - "name": "quorum", - "type": "uint32" - } - ] - }, - { - "name": "uwchal_key", - "base": "", - "fields": [ - { - "name": "id", - "type": "uint64" - } - ] - }, - { - "name": "uwchal_vote", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "ballot", - "type": "uwchal_ballot" - }, - { - "name": "voted_at", - "type": "time_point" - } - ] - }, - { - "name": "uwchal_vote_key", - "base": "", - "fields": [ - { - "name": "owner", - "type": "uint64" - } - ] - }, - { - "name": "uwchalbond", - "base": "", - "fields": [ - { - "name": "uwreq_id", - "type": "uint64" - }, - { - "name": "underwriter", - "type": "name" - } - ] - }, { "name": "votedispute", "base": "", @@ -358,24 +174,6 @@ "type": "checksum256" } ] - }, - { - "name": "voteuwchal", - "base": "", - "fields": [ - { - "name": "owner", - "type": "name" - }, - { - "name": "chal_id", - "type": "uint64" - }, - { - "name": "ballot", - "type": "uint8" - } - ] } ], "actions": [ @@ -384,56 +182,23 @@ "type": "chkdispute", "ricardian_contract": "" }, - { - "name": "chkuwchal", - "type": "chkuwchal", - "ricardian_contract": "" - }, - { - "name": "claimbond", - "type": "claimbond", - "ricardian_contract": "" - }, { "name": "opendispute", "type": "opendispute", "ricardian_contract": "" }, - { - "name": "openuwchal", - "type": "openuwchal", - "ricardian_contract": "" - }, { "name": "slashop", "type": "slashop", "ricardian_contract": "" }, - { - "name": "uwchalbond", - "type": "uwchalbond", - "ricardian_contract": "" - }, { "name": "votedispute", "type": "votedispute", "ricardian_contract": "" - }, - { - "name": "voteuwchal", - "type": "voteuwchal", - "ricardian_contract": "" } ], "tables": [ - { - "name": "bondcredits", - "type": "bond_credit", - "index_type": "i64", - "key_names": ["account"], - "key_types": ["uint64"], - "table_id": 30792 - }, { "name": "chalgstate", "type": "chalg_state", @@ -469,39 +234,11 @@ "key_names": ["scope","owner"], "key_types": ["name","uint64"], "table_id": 51768 - }, - { - "name": "uwchals", - "type": "uwchal_entry", - "index_type": "i64", - "key_names": ["id"], - "key_types": ["uint64"], - "table_id": 10118, - "secondary_indexes": [ - { - "name": "byuwrequw", - "key_type": "uint128", - "table_id": 26153 - } - ] - }, - { - "name": "uwchalvote", - "type": "uwchal_vote", - "index_type": "i64", - "key_names": ["scope","owner"], - "key_types": ["name","uint64"], - "table_id": 12100 } ], "ricardian_clauses": [], "variants": [], - "action_results": [ - { - "name": "uwchalbond", - "result_type": "uint64" - } - ], + "action_results": [], "enums": [ { "name": "DisputeStatus", @@ -520,68 +257,6 @@ "value": 2 } ] - }, - { - "name": "underwrite_fault_reason", - "type": "uint8", - "values": [ - { - "name": "SOURCE_DEPOSIT_MISSING", - "value": 0 - }, - { - "name": "SOURCE_DEPOSIT_MISMATCH", - "value": 1 - }, - { - "name": "NON_CANONICAL_ENVELOPE", - "value": 2 - } - ] - }, - { - "name": "uwchal_ballot", - "type": "uint8", - "values": [ - { - "name": "UPHOLD", - "value": 0 - }, - { - "name": "REJECT_REFUND", - "value": 1 - }, - { - "name": "REJECT_FORFEIT", - "value": 2 - } - ] - }, - { - "name": "uwchal_verdict", - "type": "uint8", - "values": [ - { - "name": "NONE", - "value": 0 - }, - { - "name": "UPHELD", - "value": 1 - }, - { - "name": "REJECTED_REFUND", - "value": 2 - }, - { - "name": "REJECTED_FORFEIT", - "value": 3 - }, - { - "name": "LAPSED", - "value": 4 - } - ] } ] } \ No newline at end of file diff --git a/contracts/sysio.chalg/sysio.chalg.wasm b/contracts/sysio.chalg/sysio.chalg.wasm index 0c1ef9a7ad6e8c27a96b22b4bb3abd9021b22056..1684683f117dd9b1c69b7cb142f792b59a675b66 100755 GIT binary patch literal 32009 zcmeI5dypm7ec!uJ_kGOXeKcr6D;Ar+SJ}I1_^hCS_vQ`pOqy%So*txLt z*qLGG?rKRWW)v-Clkf)>;}W)TWs~Ru*d*%|Nl_9`Mqwk97**CWa;;TZHL8#l6;ZCO z3W}*RG5LIdr@L?8nb|cUgq$ib#oW`U&*OJ~=l4Fp?heKl4u?SyginQ+9|(d2;l1JF z0k^mq9EcVd7b%DLQpshgiyC!S8-({Bh!^j*a$UazCGM$;4uKzyzjbTGKuurJ6mF5h zBf1BEFh8lybuV+Nq22Kf>?)s(zW1u{z5I3Gim!;eGe?_;#uoOE9qAnkLS1fLxO-t@ zcK_JILU+CwI91igZl9$bs9ZnXJ-pEE1yONvaPDq@(OT%vjPIYAnVy*G`hFYdyLTO# znD6@0WA)fPb#(v4__6)t-G$!#?A<|4m94yTrhBXx)K#+~ubJxJ9W+$2@!*m9`R+__ ze{bS&H)yIVDXQkX2fGtTyYs$UgB_fm>5WayECfSpSf}PrK;BSX2>zBXF3G34V*Ko4-5QsCyfAuebl;e0QwZ^#kQq3%#-4kp&OvCf{mcIR<=he}6T}*umb!?9Bf0 zvEJDJg^9n{4K`_pgrUdxSMCLyRef;|)b#9UyYu^RpFJ`&?jb5}%*{_62F(2>axU^f zR6E%AB>Mp7h z3Fsd01{Xy)-5AtsLA_q9)uY%;c+b-R@dGkfix!QZ2 zyKM93%QidyP|!b${Cm&#?Qt!f3&XG$g+W-qG+GR^rRAWt7-VOE$9`7$Y3z(ei?j20 zyI{k9k6Q>Z3S0a4AL@?H?Z16&p&Pl{*xcOo-N79j?)!dtV;m%%)_fQ(x@*&5Q;-E) zS{Z+b-Lp5OL6#itgp(aN!j-RGzM&JP!DO~&GMl@r6O6d!8|dh=q?ZOat2^azQti`_ zLOX16JG?f?7H`nq?B6X1N!UuGmJ3=VVYv8S=9ubmXT)u!GxKg7c0qsmro$d{yC?`Y2CY^awCaqLyx0G< z{m)Oeo(oxd8fXx$Ka1|VZd0Jr&H!Yj{5Q3Q&a9*p`kQIUEe#!HZoSHAy~@~EZerzO zcF$zn@$>gVjsVYM3Sf;kj-_{^Y;hrtve*CbpMG}ou59UZ|KRvB0oAg$!f5J%pdA^a z87bl(!vbmRCKdw*A{M~vSqFWzl1^+;cWO5Px8D|9Q(L>W$h@0^jgCR$v^Ld_v^%vR zVS~Qzu`?R%c3>iF(o!46z&2hM-eouncQ|XIRW&DB15HxsNX>>_OsBbz(vWgfuo