Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
09aee7c
feat(chain): make slug_name a first-class ABI type
heifner Sep 15, 2026
b50882e
fix(chain): carry a non-canonical slug_name across the JSON text boun…
heifner Sep 16, 2026
f2cd62e
docs: drop the "v6" label, and decouple the struct-key fixture from s…
heifner Sep 16, 2026
e66cc36
fix(chain): reject a signed slug spelling, and pin builtin-over-struc…
heifner Sep 16, 2026
f831d61
feat(chain): give slug_name one canonical JSON carrier
heifner Sep 16, 2026
459de80
fix(fc): validate the slug_name object arm's packed value
heifner Sep 17, 2026
5415b86
feat(fc): a slug_name code must start with a letter
heifner Sep 17, 2026
9f89933
feat(contracts): use CDT's slug_name and validate payload codes
heifner Sep 21, 2026
5ebfd5e
chore(contracts): rebuild sysio.epoch against wire-cdt #119
heifner Sep 21, 2026
8c14fde
Merge remote-tracking branch 'origin/master' into feature/slug-name-a…
heifner Sep 21, 2026
6f148db
chore(contracts): rebuild reenter_deposit against wire-cdt #119
heifner Sep 21, 2026
444962c
fix(contracts): validate slug_name codes at the writers that persist …
heifner Sep 21, 2026
7e1a5bd
fix(libfc): bind Traits::alphabet to a view before using it
heifner Sep 22, 2026
db999fb
chore(contracts): rebuild chalg/opreg/uwrit against wire-cdt #119
heifner Sep 22, 2026
e7f462d
Merge remote-tracking branch 'origin/master' into feature/slug-name-a…
heifner Sep 22, 2026
1881aee
fix(contracts): refund value-bearing messages with an unspellable code
heifner Sep 22, 2026
af01d74
fix(chain): convert a slug_name totally — never throw, never fall bac…
heifner Sep 22, 2026
b6cfee0
fix(chain): name a slug key only when it can be named; hex otherwise
heifner Sep 22, 2026
d74ef99
fix(chain_plugin): keep json=true fallback cursors scope-relative
heifner Sep 23, 2026
4d27537
refactor(libfc): derive slug_name; move is_canonical onto it
heifner Sep 23, 2026
f3dc7e0
refactor(libfc): drop the slug_name shape pins
heifner Sep 23, 2026
a21b966
fix(libfc): give the derived slug_name its own std::hash
heifner Sep 23, 2026
4242ff5
fix(chain_plugin): tag raw cursors absolute; dispatch bounds past whi…
heifner Sep 23, 2026
2dbca9c
fix(chain_plugin): keep an absolute bound inside the scope it names
heifner Sep 23, 2026
cd0246e
docs(slug_name): correct the non-canonical rendering contract
heifner Sep 23, 2026
07ebfc9
docs(slug_name): finish the total-renderer sweep
heifner Sep 23, 2026
88c97b0
docs(slug_name): correct the number-carrier rationale and keep all th…
heifner Sep 23, 2026
fc6ed3d
fix(chain_plugin): make a scope-only displayed key replayable; correc…
heifner Sep 24, 2026
17890b6
Merge remote-tracking branch 'origin/master' into feature/slug-name-a…
heifner Sep 24, 2026
59263b0
test(sysio.liq): read registry slug codes as their canonical string
heifner Sep 24, 2026
4d61929
test(opp): validate reserve codes in the bootstrap config preflight
heifner Sep 24, 2026
8a85586
Merge remote-tracking branch 'origin/master' into feature/slug-name-a…
heifner Sep 24, 2026
1e66942
fix(query_engine): treat slug_name as the builtin primitive it now is
heifner Sep 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ namespace sysio {
* @param chain_kind The chain identifier from `opp::types::ChainKind`
* (CHAIN_KIND_EVM / CHAIN_KIND_SVM).
* Wire-side legacy `fc::crypto::chain_kind_t` is host-only.
* TODO @jglanz: SUI variant removed in v6; revisit when
* TODO @jglanz: SUI variant removed in the data-model refactor; revisit when
* SUI outpost is added.
* @param account The WIRE account name of the user which the address is being linked to.
* @param sig A valid signature for the target chain converted to Wire's standard.
Expand Down
5 changes: 3 additions & 2 deletions contracts/sysio.authex/src/sysio.authex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,9 @@ namespace sysio {
require_auth(account);

// ——— Chain kind validation ———
// TODO @jglanz: SUI removed in v6; restore when SUI outpost is added.
check(chain_kind == ChainKind::CHAIN_KIND_EVM || chain_kind == ChainKind::CHAIN_KIND_SVM,
// TODO @jglanz: SUI removed in the data-model refactor; restore when SUI outpost is added.
check(chain_kind == ChainKind::CHAIN_KIND_EVM
|| chain_kind == ChainKind::CHAIN_KIND_SVM,
"Invalid chain_kind. Supported: CHAIN_KIND_EVM(2), CHAIN_KIND_SVM(3).");
check(is_supported_chain_key_pair(chain_kind, pub_key), "chain_kind and pub_key must pair as EVM/EM or SVM/ED");

Expand Down
8 changes: 5 additions & 3 deletions contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <sysio/system.hpp>
#include <sysio/privileged.hpp>
#include <sysio/opp/types/types.pb.hpp>
#include <sysio.opp.common/slug_name.hpp>
#include <sysio/slug_name.hpp>
#include <sysio.opp.common/opp_table_types.hpp>

namespace sysio {
Expand Down Expand Up @@ -76,8 +76,10 @@ namespace sysio {
/// inline; else `active=false`.
///
/// Validation:
/// * `code` slug_name format already enforced by the type itself at
/// deserialization (alphabet `[A-Z0-9_]+`, ≤8 chars).
/// * `code` canonicality is enforced by the WRITER guard, not by the type:
/// reflected/raw action deserialization writes the packed member directly
/// and validates nothing. A spelling supplied as a string goes through the
/// validating constructor (`[A-Z][A-Z0-9_]{0,7}`); a raw uint64 does not.
/// * `code` must be unique.
/// * `kind=WIRE` may appear at most once (the depot self-row).
/// * `kind=EVM` rows must carry a unique `external_chain_id` — the pair
Expand Down
6 changes: 6 additions & 0 deletions contracts/sysio.chains/src/sysio.chains.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <sysio.chains/sysio.chains.hpp>
#include <sysio.epoch/sysio.epoch.hpp>
#include <sysio.opp.common/registry_codes.hpp>
#include <sysio.opp.common/registry_metadata.hpp>
#include <sysio.opp.common/wire_asset.hpp>

Expand Down Expand Up @@ -142,6 +143,11 @@ void chains::regchain(opp::types::ChainKind kind,

sysio::check(kind != opp::types::CHAIN_KIND_UNKNOWN,
"sysio.chains: kind must not be UNKNOWN");
// The code is this row's PRIMARY KEY and is rendered as a string by every reader.
// Rendering is total, so an uncanonical one still produces text -- text that either
// fails validation on the way back, or silently re-parses as a DIFFERENT chain.
// Refuse it before it becomes a permanent row.
opp::registry::check_codes({code}, "sysio.chains");
// Both strings persist into a `sysio`-billed row -- bound them before emplace.
opp::registry::check_metadata(name, description, "sysio.chains");
validate_outpost_addrs(kind, outpost);
Expand Down
10 changes: 0 additions & 10 deletions contracts/sysio.chains/sysio.chains.abi
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,6 @@
"type": "outpost_addrs"
}
]
},
{
"name": "slug_name",
"base": "",
"fields": [
{
"name": "value",
"type": "uint64"
}
]
}
],
"actions": [
Expand Down
Binary file modified contracts/sysio.chains/sysio.chains.wasm
Binary file not shown.
Binary file modified contracts/sysio.chalg/sysio.chalg.wasm
Binary file not shown.
10 changes: 5 additions & 5 deletions contracts/sysio.dclaim/src/sysio.dclaim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ uint64_t next_id(name self, Pick pick) {
/// Non-throwing validation of a string destined for `name(std::string_view)`. Shared with every
/// other OPP inbound handler via `sysio.opp.common/safe_ops.hpp` so the never-throw name domain is
/// defined and audited in exactly one place.
using sysio::opp::safe::is_valid_name_string;

/// Saturating WIRE credit. `asset::operator+=` aborts on overflow past `asset::max_amount`
/// (2^62-1); credit_wire runs inside the never-throw OPP inbound path (via onreward), so cap at
Expand Down Expand Up @@ -283,10 +282,11 @@ void dclaim::onreward(uint64_t chain_code,
}

name wacct; // value 0 == not yet AuthX-linked
// Validate the cross-chain-supplied account string before constructing name(): an invalid or
// oversized string is treated as unlinked (credit parked by native address) rather than
// aborting the inbound dispatch via name()'s internal check(). See is_valid_name_string.
if (!staker_wire_account.empty() && is_valid_name_string(staker_wire_account)) {
// Validate the cross-chain-supplied account string before constructing name(): an invalid
// string is treated as unlinked (credit parked by native address) rather than aborting the
// inbound dispatch via name()'s internal check(). `is_valid_literal` is name's OWN predicate --
// the one its constructor uses -- so it cannot drift from what the constructor accepts.
if (!staker_wire_account.empty() && sysio::name::is_valid_literal(staker_wire_account)) {
wacct = name(staker_wire_account);
}

Expand Down
Binary file modified contracts/sysio.dclaim/sysio.dclaim.wasm
Binary file not shown.
Binary file modified contracts/sysio.epoch/sysio.epoch.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion contracts/sysio.liq/include/sysio.liq/sysio.liq.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include <sysio/kv_scoped_table.hpp>
#include <sysio/opp/types/types.pb.hpp>
#include <sysio.opp.common/shadow_yield.hpp>
#include <sysio.opp.common/slug_name.hpp>
#include <sysio/slug_name.hpp>
#include <sysio.opp.common/wire_asset.hpp>

#include <magic_enum/magic_enum.hpp>
Expand Down
10 changes: 0 additions & 10 deletions contracts/sysio.liq/sysio.liq.abi
Original file line number Diff line number Diff line change
Expand Up @@ -472,16 +472,6 @@
}
]
},
{
"name": "slug_name",
"base": "",
"fields": [
{
"name": "value",
"type": "uint64"
}
]
},
{
"name": "sweep",
"base": "",
Expand Down
Binary file modified contracts/sysio.liq/sysio.liq.wasm
Binary file not shown.
4 changes: 2 additions & 2 deletions contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <sysio/crypto.hpp>
#include <sysio/system.hpp>
#include <sysio/opp/types/types.pb.hpp>
#include <sysio.opp.common/slug_name.hpp>
#include <sysio/slug_name.hpp>
#include <sysio.opp.common/opp_table_types.hpp>
#include <sysio.opp.common/opp_keys.hpp>

Expand Down Expand Up @@ -72,7 +72,7 @@ namespace sysio {
/// * `sysio.reserv::oncnclrsv` — `RESERVE_CREATE_CANCELLED` to
/// the reserve's owning outpost on race-win cancel.
/// * `sysio.opreg::*` — `OPERATOR_ACTION` family (WITHDRAW_REMIT,
/// SLASH) — once the v6 reserve-flow lands the same pattern
/// SLASH) — once the reserve-flow work lands the same pattern
/// reaches every depot-authorised outbound.
///
/// Gated to the depot's own system contracts (sysio.epoch / .opreg /
Expand Down
77 changes: 63 additions & 14 deletions contracts/sysio.msgch/src/sysio.msgch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#include <sysio.chalg/sysio.chalg.hpp> // dispute trigger + open-dispute gate (disputes table)
#include <sysio.opreg/sysio.opreg.hpp> // operator-status delivery gate (operators table)
#include <sysio.roa.hpp> // authoritative Tier-1 electorate preflight
#include <sysio/slug_name.hpp>
#include <sysio.opp.common/evm_address.hpp>
#include <sysio.opp.common/slug_name.hpp>
#include <sysio.opp.common/safe_ops.hpp> // to_depot_amount — WSA-028 fail-closed TokenAmount gate
#include <sysio.opp.common/name_ops.hpp> // parse_wire_account_name — never-throw account-name parse
#include <sysio.opp.common/opp_canonical_codec.hpp> // canonical envelope encoding + keccak epoch digest
Expand Down Expand Up @@ -53,7 +53,7 @@ constexpr name ram_payer = "sysio"_n;
/// always WIRE.
constexpr uint32_t WIRE_CHAIN_ID = 1;

using sysio::slug_name_literals::operator""_s;
// `operator""_s` is declared at global scope by <sysio/slug_name.hpp>.

/// Codename of the Ethereum outpost — the sole source of node-owner NFT (ERC1155) deposits, which
/// occur on Ethereum mainnet only. This is the `ChainSpec.code` the launch and dev bootstrap configs
Expand Down Expand Up @@ -302,6 +302,33 @@ name resolve_account_from_op_address(const opp::types::ChainAddress& op_address)
return false;
}

/// Are a payload's FORGEABLE code fields canonical slug_names?
///
/// `chain_code` is proven — `source_chain_binding_ok` binds it to the delivering
/// outpost. `token_code` / `reserve_code` are NOT: they arrive as raw protobuf
/// uint64s and reach a slug_name through the non-validating raw constructor, so a
/// forged payload can carry a value that does not round-trip through its spelling.
/// Such a value can never have been registered, and rendering is total, so it still
/// produces text: text that either FAILS validation on the way back, or silently
/// re-parses as a DIFFERENT, real code.
///
/// Drop the attestation instead; never check(), per
/// feedback_opp_handlers_never_throw — a check() here halts evalcons and stalls
/// consensus.
///
/// `path` labels the dispatch path in the diagnostic. True iff every code is canonical.
[[nodiscard]] bool payload_codes_canonical(std::initializer_list<sysio::slug_name> codes,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Validate SwapRequest codes before persisting the UWREQ

This gate still misses ATTESTATION_TYPE_SWAP_REQUEST: dispatch_attestation forwards its opaque bytes directly to createuwreq, which constructs the source/target token and reserve slugs from raw protobuf uint64s. Its missing/inactive-reserve zero-quote path intentionally continues and persists the request, so a swap with a valid target chain but token/reserve value 7 still creates an unrenderable row. Rendering that row makes values_only expose the fallback scalar, and the underwriter's unconditional row.get_object() drops the entire scan cycle. Validate the decoded SwapRequest codes inside createuwreq before lookup/persistence, emit SwapRevert so the source deposit is refunded, and add a SWAP_REQUEST regression; the new test only covers OperatorAction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right, and the gap is structural rather than a miss in the gate: msgch's dispatch arm forwards SWAP_REQUEST to uwrit::createuwreq as opaque bytes, so none of the seven payload_codes_canonical sites can see it.

Fixed in 444962ccca. createuwreq now validates the four payload codes immediately after the provenance check and emits SwapRevert — refund, not drop, exactly as you said, because the deposit is escrowed on the source outpost and a silent skip would strand it. emit_swap_revert packs raw .value uint64s, so the revert still works when the offending code is the unspellable one.

Your reading of the zero-quote path was exactly right, and I verified it rather than assuming. Removing the new guard and re-running the regression shows the row IS created, and that reading it back throws inside the depot's own ABI unpack:

unpack_exception: Unable to unpack built-in type 'slug_name'
                  while processing 'uw_request_t.dst_token_code'
s.is_canonical(): slug_name 7 is not a code and has no string spelling

required_reserves_active is false when the reserve is MISSING, so the fail-closed guard never fires and reqs.emplace stores the code verbatim.

The regression is sysio_dispatch_tests/swap_request_uncanonical_code_is_refunded: a bad target token and a bad source reserve, both with a valid target chain — your case — plus a canonical control proving the guard rejects the code rather than the shape of the request.

I validate four codes, not six. The two chain codes are covered by the registry: src_chain_code is bound to the proven delivering outpost, dst_chain_code must pass chain_registered_active, and the P2 fix below makes a registered code a renderable one by construction. That dependency is why both landed together.

const char* path) {
for (const sysio::slug_name code : codes) {
if (!code.is_canonical()) {
sysio::print("msgch::", path, ": DROP attestation -- payload code ", code.value,
" has no canonical slug_name spelling\n");
return false;
}
}
return true;
}

/// The syndicating user's key family must be the proven outpost's own: an outpost of family F
/// verifies and emits F-family keys only, so a key of another family is a forgery whatever it
/// resolves to. `sysio.liq::park` refuses the other family for an unlinked key; this refuses it
Expand Down Expand Up @@ -431,7 +458,7 @@ std::optional<checksum256> to_checksum256_exact(const std::vector<char>& bytes)
/// 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 v6 data-model refactor — codenames everywhere):
/// 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)
Expand Down Expand Up @@ -488,6 +515,12 @@ void dispatch_operator_action(name self, const std::vector<char>& data,
// 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
Expand Down Expand Up @@ -543,7 +576,7 @@ void dispatch_operator_action(name self, const std::vector<char>& data,
/// covering this leg); the authoritative copy for verification is the
/// bytes themselves, stored on `commit_entry.{source,dest}_uic_bytes`.
///
/// Post v6: identity scalars on UIC are codenames (uint64). `chain_code` is the proven source
/// 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;
Expand Down Expand Up @@ -577,15 +610,20 @@ void dispatch_underwrite_commit(name self, const std::vector<char>& data, uint64
// 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},
sysio::slug_name{uic.token_code},
sysio::slug_name{uic.reserve_code},
uic_token_code,
uic_reserve_code,
data)
).send();
}
Expand Down Expand Up @@ -686,15 +724,21 @@ void dispatch_reserve_create(name self, const std::vector<char>& data, uint64_t
// 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<int64_t>(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},
sysio::slug_name{ext.amount.token_code},
sysio::slug_name{ext.reserve_code},
ext_token_code,
ext_reserve_code,
rc.name,
rc.description,
ext_amount,
Expand Down Expand Up @@ -725,12 +769,17 @@ void dispatch_reserve_create_cancel(name self, const std::vector<char>& data, ui
// 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},
sysio::slug_name{cancel.token_code},
sysio::slug_name{cancel.reserve_code},
cancel_token_code,
cancel_reserve_code,
cancel.creator_addr.kind,
cancel.creator_addr.address)
).send();
Expand Down Expand Up @@ -912,10 +961,10 @@ void dispatch_attestation(name self, uint64_t attestation_id,
// longer exists; any stray inbound falls through to the default drop below.

case AttestationType::ATTESTATION_TYPE_STAKING_REWARD:
// Per-staker staking reward -> sysio.dclaim claim ledger. The v6
// Per-staker staking reward -> sysio.dclaim claim ledger. The
// staking-reward path does not deposit back to a reserve (the
// external-pool credit and native -> WIRE conversion are
// outpost-side), so the pre-v6 reserv::onreward leg is dropped and
// outpost-side), so the pre-refactor reserv::onreward leg is dropped and
// reward_amount.amount is forwarded as the WIRE-denominated credit.
{
opp::attestations::StakingReward sr;
Expand Down Expand Up @@ -1400,7 +1449,7 @@ void msgch::deliver(name batch_op_name, uint64_t chain_code, std::vector<char> d

// Verify outpost exists on the new `sysio.chains::chains` table.
// `chain_code` is the originating chain's slug_name value (uint64) per
// the v6 data-model refactor — the chain row's PK is `code.value`.
// the data-model refactor — the chain row's PK is `code.value`.
// Reject deliveries from the depot self-row (`is_depot==true`) and
// from inactive chains; both are protocol invariants.
sysio::chains::chains_t chains_tbl(CHAINS_ACCOUNT);
Expand Down Expand Up @@ -1646,7 +1695,7 @@ void msgch::chkcons() {

// Check all active outposts have consensus for the current epoch.
// Outpost set is sourced from `sysio.chains::chains` filtered to
// active && !is_depot per the v6 data-model refactor; outpost ids
// active && !is_depot per the data-model refactor; outpost ids
// in `outpcons` are slug_name values (chain_row::code.value).
outpost_consensus_t opcons(get_self());
sysio::chains::chains_t chains_tbl(CHAINS_ACCOUNT);
Expand Down
Binary file modified contracts/sysio.msgch/sysio.msgch.wasm
Binary file not shown.
33 changes: 19 additions & 14 deletions contracts/sysio.opp.common/include/sysio.opp.common/name_ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
*/

#include <sysio/name.hpp>
#include <sysio.opp.common/safe_ops.hpp> // is_valid_name_string
#include <optional>
#include <string_view>

Expand All @@ -32,23 +31,29 @@ namespace sysio::opp::safe {
/// Parse an inbound account string into a validated `sysio::name`, or
/// `std::nullopt` when the string is empty or not a canonical account name.
///
/// Validation is delegated to `is_valid_name_string`, which mirrors CDT
/// `basic_name`'s charset, length, and final-symbol rules exactly, so the returned
/// `name{s}` is guaranteed to construct without aborting — in particular a
/// legitimate 13-byte name is accepted where a naive `size() > 12` cap would
/// wrongly reject it. Callers on the OPP dispatch path MUST treat `std::nullopt`
/// as "drop this message" and `return`, never as a reason to `check()`-abort.
/// Validation is delegated to `name::is_valid_literal` — the type's OWN predicate,
/// the one its constructor and its `_n` literal use — so it cannot drift from what
/// the constructor accepts. A legitimate 13-byte name whose final symbol fits the
/// 4-bit final slot is accepted, where a naive `size() > 12` cap would wrongly
/// reject it. Callers on the OPP dispatch path MUST treat `std::nullopt` as "drop
/// this message" and `return`, never as a reason to `check()`-abort.
///
/// @param s the candidate account string (no leading/trailing trimming).
/// @return the constructed `name` iff `s` is a nonempty canonical account name.
inline std::optional<sysio::name> parse_wire_account_name(std::string_view s) {
if (s.empty() || !is_valid_name_string(s)) return std::nullopt;
const sysio::name parsed{s};
// CDT's numeric name encoding discards trailing dots. Require the exact
// round trip so aliases such as `underwriter.` cannot name the same roster
// principal differently on the depot and on different outpost runtimes.
if (parsed.to_string() != s) return std::nullopt;
return parsed;
// ASK FIRST, CONSTRUCT SECOND. `name`'s constructor aborts on any spelling it
// will not accept, and an abort on this path reverts the whole
// evalcons/apply_consensus delivery — so the candidate is never handed to the
// constructor until it is known good.
//
// `is_valid_literal` is the type's OWN validation predicate, the same one its
// constructor and its `_n` literal use, so this cannot drift from what the
// constructor accepts. It subsumes both checks this helper used to make by
// hand: the charset/length mirror, AND the round trip that rejected aliases
// like `underwriter.` (a trailing dot is discarded by the encoding, so it
// would otherwise name the same roster principal two ways).
if (s.empty() || !sysio::name::is_valid_literal(s)) return std::nullopt;
return sysio::name{s};
}

} // namespace sysio::opp::safe
Loading
Loading