Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ OPP uses a universal chain-agnostic address system:

- **ChainKind** enum: `WIRE`, `EVM`, `SVM`
- **ChainAddress**: `(ChainKind, raw_bytes_address)` — no translation or mapping needed
- **ChainSignature**: `(actor, key_type, signature_bytes)` — supports native chain signatures

This allows a single unified message format across all supported chains.

Expand Down
4 changes: 2 additions & 2 deletions contracts/sysio.authex/include/sysio.authex/sysio.authex.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ namespace sysio {
* data. It avoids visually ambiguous characters, making it suitable for
* financial identifiers, cryptocurrency addresses, and similar contexts.
*
* Defined `inline` in the header so cross-contract callers (e.g. sysio.msgch
* Defined `inline` in the header so cross-contract callers (e.g. sysio.synd
* resolving an authex link by pubkey) can call it without linking against
* sysio.authex.cpp's WASM.
*
Expand Down Expand Up @@ -93,7 +93,7 @@ namespace sysio {
* @brief Render a public_key into its canonical "PUB_<curve>_<encoding>" string.
*
* Mirrors eosjs-ecc's spelling so that off-chain signers, the authex contract,
* and any cross-contract caller (e.g. sysio.msgch's bypubkey lookup) all hash
* and any cross-contract caller (e.g. sysio.synd's bypubkey lookup) all hash
* the same byte sequence into the secondary-index key.
*
* Supported variants:
Expand Down
2 changes: 1 addition & 1 deletion contracts/sysio.authex/src/sysio.authex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ namespace sysio {
// external wallet can hold several WireNodes NFTs and therefore legitimately back several Wire
// accounts, so one ETH key -> many accounts is allowed on this path. The operator path
// (createlink) keeps its 1:1 `bypubkey` check, which also guarantees an operator's link is the
// lowest-primary-key row for that key; sysio.msgch's resolve_account_from_op_address does a single
// lowest-primary-key row for that key; sysio.synd's linked_account does a single
// `bypubkey.find()` (lowest-primary-key match), so it still lands on the operator's account rather
// than a later node-owner duplicate.
auto existing = by_namechain.find(to_namechain_key(account, chain_kind));
Expand Down
21 changes: 8 additions & 13 deletions contracts/sysio.chains/include/sysio.chains/sysio.chains.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,22 @@ namespace sysio {
* Grouped into one struct so `regchain` keeps a readable signature and
* `setoutpost` replaces the whole set atomically. Encoding by kind:
* * `EVM` — each field is a distinct `0x`-prefixed 20-byte hex address:
* the OPP contract, the OPPInbound contract, the OperatorRegistry
* (the `uw_commit` target), and the contract that emits the source
* swap-deposit event scanned by the underwriter's verify path.
* the OPP contract and the OPPInbound contract.
* * `SVM` — `opp_addr` is the outpost program id (base58). The single
* program serves every role, so the other three MUST be empty; the
* daemons substitute `opp_addr` wherever they need one of them.
* program receives inbound envelopes too, so `opp_inbound_addr` MUST be
* empty.
* * `WIRE` — all empty; the depot self-row has no remote deployment.
*
* Fields may be empty at registration (the remote contract is not deployed
* yet) and filled in later via `setoutpost`. Both operator daemons fail
* closed: a chain whose address they need but do not have is skipped, so an
* yet) and filled in later via `setoutpost`. The batch-operator relay fails
* closed: a chain whose address it needs but does not have is skipped, so an
* unconfigured row never rides on another chain's deployment.
*/
struct outpost_addrs {
std::string opp_addr;
std::string opp_inbound_addr;
std::string operator_registry_addr;
std::string source_deposit_addr;

SYSLIB_SERIALIZE(outpost_addrs,
(opp_addr)(opp_inbound_addr)(operator_registry_addr)(source_deposit_addr))
SYSLIB_SERIALIZE(outpost_addrs, (opp_addr)(opp_inbound_addr))
};

class [[sysio::contract("sysio.chains")]] chains : public contract {
Expand Down Expand Up @@ -136,8 +131,8 @@ namespace sysio {
uint64_t registered_at_ms = 0;
uint64_t activated_at_ms = 0;
/// Remote outpost contract identities for this chain — see {@link outpost_addrs}.
/// Read by batch_operator_plugin and underwriter_plugin; every operator
/// therefore relays through the same deployment without per-node config.
/// Read by batch_operator_plugin; every operator therefore relays through
/// the same deployment without per-node config.
outpost_addrs outpost;

uint64_t by_kind() const { return magic_enum::enum_integer(kind); }
Expand Down
29 changes: 11 additions & 18 deletions contracts/sysio.chains/src/sysio.chains.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ void require_priv_caller() {
// ---------------------------------------------------------------------------
// Remote-address format validation
//
// These addresses are consensus facts: every batch operator and underwriter
// reads the same row, so one malformed value breaks relay for the whole
// These addresses are consensus facts: every batch operator reads the same
// row, so one malformed value breaks relay for the whole
// network rather than for a single misconfigured node. Validate the format at
// the ingress boundary, where the caller can still fix it.
//
// Empty is allowed -- a chain may be registered before its remote contracts
// are deployed and filled in later via `setoutpost`; both daemons fail closed
// and skip a row whose address they need but do not have. The exception is a
// are deployed and filled in later via `setoutpost`; the batch-operator relay
// fails closed and skips a row whose address it needs but does not have. The exception is a
// field that is structurally meaningless for the kind, which must be empty.
// ---------------------------------------------------------------------------

Expand Down Expand Up @@ -93,8 +93,7 @@ void check_empty(const std::string& addr, const char* label, const char* why) {
/// must match the kind's format; structurally-unused fields must be empty.
void validate_outpost_addrs(opp::types::ChainKind kind, const outpost_addrs& o) {
// Applies to every kind, including ones with no format rule yet.
for (const auto* f : {&o.opp_addr, &o.opp_inbound_addr,
&o.operator_registry_addr, &o.source_deposit_addr}) {
for (const auto* f : {&o.opp_addr, &o.opp_inbound_addr}) {
sysio::check(f->size() <= ADDR_MAX_BYTES,
"sysio.chains: outpost address exceeds "
+ std::to_string(ADDR_MAX_BYTES) + " bytes");
Expand All @@ -103,26 +102,20 @@ void validate_outpost_addrs(opp::types::ChainKind kind, const outpost_addrs& o)
switch (kind) {
case opp::types::CHAIN_KIND_WIRE: {
constexpr auto why = "the WIRE depot self-row has no remote deployment";
check_empty(o.opp_addr, "opp_addr", why);
check_empty(o.opp_inbound_addr, "opp_inbound_addr", why);
check_empty(o.operator_registry_addr, "operator_registry_addr", why);
check_empty(o.source_deposit_addr, "source_deposit_addr", why);
check_empty(o.opp_addr, "opp_addr", why);
check_empty(o.opp_inbound_addr, "opp_inbound_addr", why);
break;
}
case opp::types::CHAIN_KIND_EVM:
// Each role is its own contract on an EVM chain.
if (!o.opp_addr.empty()) check_evm_addr(o.opp_addr, "opp_addr");
if (!o.opp_inbound_addr.empty()) check_evm_addr(o.opp_inbound_addr, "opp_inbound_addr");
if (!o.operator_registry_addr.empty()) check_evm_addr(o.operator_registry_addr, "operator_registry_addr");
if (!o.source_deposit_addr.empty()) check_evm_addr(o.source_deposit_addr, "source_deposit_addr");
if (!o.opp_addr.empty()) check_evm_addr(o.opp_addr, "opp_addr");
if (!o.opp_inbound_addr.empty()) check_evm_addr(o.opp_inbound_addr, "opp_inbound_addr");
break;
case opp::types::CHAIN_KIND_SVM: {
// One program serves every role; the daemons substitute opp_addr.
// One program sends and receives envelopes, named by opp_addr.
constexpr auto why = "an SVM outpost is a single program, named by opp_addr";
if (!o.opp_addr.empty()) check_svm_addr(o.opp_addr, "opp_addr");
check_empty(o.opp_inbound_addr, "opp_inbound_addr", why);
check_empty(o.operator_registry_addr, "operator_registry_addr", why);
check_empty(o.source_deposit_addr, "source_deposit_addr", why);
check_empty(o.opp_inbound_addr, "opp_inbound_addr", why);
break;
}
default:
Expand Down
8 changes: 0 additions & 8 deletions contracts/sysio.chains/sysio.chains.abi
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,6 @@
{
"name": "opp_inbound_addr",
"type": "string"
},
{
"name": "operator_registry_addr",
"type": "string"
},
{
"name": "source_deposit_addr",
"type": "string"
}
]
},
Expand Down
Binary file modified contracts/sysio.chains/sysio.chains.wasm
Binary file not shown.
63 changes: 0 additions & 63 deletions contracts/sysio.chalg/include/sysio.chalg/sysio.chalg.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,38 +79,6 @@ namespace sysio {
[[sysio::action]]
void chkdispute(uint64_t dispute_id);

// -----------------------------------------------------------------------
// Underwriter-fault challenge (WIRE-297)
// -----------------------------------------------------------------------
//
// The OPP envelope dispute's sibling. Same adjudication machinery — Tier-1 electorate
// snapshotted at open, fixed quorum floor(N/2)+1, record-only votes, permissionless tally
// crank, resolution through the `slashop` -> `opreg::slash` chokepoint — with the
// differences the problem forces:
//
// * A human FILES it (the depot can observe envelope divergence itself; a source-chain
// fault it cannot — someone must allege it), so filing is permissionless and priced
// with a challenger bond.
// * The ballot is a VERDICT (uphold / reject), not a choice among candidate versions.
// * It never pauses the epoch: an unresolved challenge is survivable — the challenged
// collateral lock simply lapses back to a normal release — so the chain keeps advancing.
// * The vote deadline is the commitment's own collateral-lock expiry, and there is NO
// after-deadline relaxed tally: a challenge "must be voted on before the window
// expires" (Jonathan, 2026-07-27); past it the challenge LAPSES with a full bond
// refund. Envelope disputes relax after their deadline only because a paused chain
// MUST eventually resolve.

/// The fault a challenger alleges against the winning underwriter's commit. The council
/// adjudicates the allegation against source-chain state (not visible to the depot) and
/// votes; the enum classifies the case for the audit row and indexers.

/// A Tier-1 voter's ballot in an underwriter challenge. The two reject flavours let the
/// council separate an honest mistake (bond refunded to the challenger) from a frivolous or
/// malicious challenge (bond forfeited to the wrongly-challenged underwriter) — forfeiture
/// only ever happens by explicit council judgment, never by default.

/// Terminal verdict of a challenge (NONE while it is OPEN).

// -----------------------------------------------------------------------
// Tables
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -196,37 +164,6 @@ namespace sysio {

using chalgstate_t = sysio::kv::global<"chalgstate"_n, chalg_state>;

/// Challenge primary key (auto-incrementing id).

/// An underwriter-fault challenge. Opened permissionlessly against one CONFIRMED
/// commitment; resolved by a Tier-1 vote or lapsed at the lock window's end. The row
/// survives resolution as the guard that a commitment is challenged at most once, ever
/// (mirrors one-dispute-per-(outpost, epoch)) and as the verdict record.
///
/// It does NOT survive at full size: `chkuwchal` compacts it to a fixed-width tombstone on
/// resolution, clearing the two variable-length fields (`detail`, `electorate`) and erasing
/// the challenge's ballot rows. Filing is permissionless and the bond comes back on every
/// non-forfeit outcome, so recycled bond capital could otherwise pin unbounded
/// caller-controlled bytes in RAM billed to `sysio`; compacting bounds the retained
/// variable-length state by the number of CONCURRENTLY OPEN challenges — each backed by a
/// live lock set and an escrowed bond — rather than by every challenge ever filed. The full
/// filing and every ballot stay permanently readable in the action-trace history that
/// indexers consume.

/// Challenge-vote primary key (the voting Tier-1 owner). The vote table is scoped by
/// `chal_id`, so the owner alone is unique within a challenge.

/// One Tier-1 ballot in an underwriter challenge. Scoped by `chal_id`. Live only while the
/// challenge is OPEN — it is the tally input and the one-vote-per-owner gate, and neither
/// applies to a resolved challenge, so `chkuwchal` erases the scope on resolution.

/// Claimable-bond primary key (the account the payout is owed to).

/// WIRE this contract holds on an account's behalf out of a resolved challenge's escrow.
/// `chkuwchal` credits it; `claimbond` pays it out and erases the row. Credits ACCUMULATE
/// per account, so the table is bounded by the number of accounts with an unclaimed payout
/// — not by the number of challenges ever resolved.

private:
// Well-known accounts
static constexpr name EPOCH_ACCOUNT = "sysio.epoch"_n;
Expand Down
12 changes: 5 additions & 7 deletions contracts/sysio.chalg/src/sysio.chalg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,9 @@ void chalg::slashop(name operator_acct, std::string reason) {
check(has_auth(get_self()) || has_auth(EPOCH_ACCOUNT),
"slashop requires sysio.chalg or sysio.epoch authority");

// Slash via sysio.opreg — the canonical bond ledger. opreg routes the slashable portion
// (`balance - sum(active locks)`) to the matching LP on each (chain, token_kind) the operator has
// bond on, marks the operator SLASHED, and lets sysio.uwrit::release deferred-slash the locked
// portion as each underwriter lock resolves.
// Slash via sysio.opreg — the canonical bond ledger. opreg removes every collateral balance the
// operator holds, keeps the custody with the registry, marks the operator SLASHED, and records
// one OPERATOR_ACTION(SLASH) audit entry per (chain, token) balance.
action(
permission_level{get_self(), "active"_n},
OPREG_ACCOUNT,
Expand Down Expand Up @@ -172,9 +171,8 @@ void chalg::votedispute(name owner, uint64_t dispute_id, checksum256 chosen_chec
void chalg::chkdispute(uint64_t dispute_id) {
// Permissionless crank, driven by `batch_operator_plugin`'s epoch tick
// (`--batch-epoch-poll-ms`, 15s default) from every ACTIVE batch operator.
// That cadence is this action's ONLY driver: unlike `chkuwchal`, which
// `sysio.uwrit::chklocks` pokes from every `sysio.epoch::advance`, a dispute
// pauses `advance` itself, so no inline poke can reach here.
// That cadence is this action's ONLY driver: a dispute pauses
// `sysio.epoch::advance` itself, so no inline poke from the epoch cycle can reach here.
disputes_t disputes(get_self());
auto d_pk = dispute_key{dispute_id};
auto d = disputes.get(d_pk, "dispute not found");
Expand Down
8 changes: 4 additions & 4 deletions contracts/sysio.epoch/src/sysio.epoch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -676,10 +676,10 @@ void epoch::advance() {

// Drain matured rows from `sysio.opreg::wtdwqueue`. Operators that queued
// a withdrawal at least WITHDRAW_WAIT_EPOCHS ago are now eligible — opreg
// subtracts from the balance and emits OPERATOR_ACTION(WITHDRAW_REMIT) to
// the matching outpost (or, for WIRE-direct withdraws, CREDITS the operator's
// `sysio.opreg::remitclaims` row, which it pulls with `claimremit` — nothing
// is transferred from this path, precisely because it runs inline from here).
// subtracts from the balance, records a WITHDRAW_REMIT entry in the
// operator's audit log, and CREDITS the operator's `sysio.opreg::remitclaims`
// row, which it pulls with `claimremit` — nothing is transferred from this
// path, precisely because it runs inline from here. No attestation is sent.
// Slashed-during-the-wait rows are dropped silently inside
// opreg's flushwtdw. See CLAUDE-WIRE-OPERATOR-COLLATERAL-IMPL-PLAN.md §3.3.
action(
Expand Down
Binary file modified contracts/sysio.epoch/sysio.epoch.wasm
Binary file not shown.
Binary file modified contracts/sysio.liq/sysio.liq.wasm
Binary file not shown.
20 changes: 5 additions & 15 deletions contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,11 @@ namespace sysio {
/// targeted depot → outpost envelopes:
/// * `sysio.epoch::advance` — `OPERATORS`, `BATCH_OPERATOR_GROUPS`
/// fanout to every active outpost.
/// * `sysio.reserv::matchreserve` — `RESERVE_READY` to the
/// reserve's owning outpost (chain_code).
/// * `sysio.reserv::oncnclrsv` — `RESERVE_CREATE_CANCELLED` to
/// the reserve's owning outpost on race-win cancel.
/// * `sysio.opreg::*` — `OPERATOR_ACTION` family (WITHDRAW_REMIT,
/// SLASH) — once the reserve-flow work lands the same pattern
/// reaches every depot-authorised outbound.
/// * `sysio.synd::desyndicate` — `DESYNDICATE_LIQ` releases from
/// the destination's syndicated LIQ custody.
///
/// Gated to the depot's own system contracts (sysio.epoch / .opreg /
/// .uwrit / .reserv, plus msgch itself): each sends under its own
/// Gated to sysio.epoch, sysio.opreg, sysio.synd, and msgch itself:
/// each sends under its own
/// {self, active} authority. The gate is required because a forged
/// READY attestation rides out inside the next group-signed outbound
/// envelope, which the outpost authenticates by the group signature —
Expand Down Expand Up @@ -295,11 +290,7 @@ namespace sysio {
/// also carry `status = PROCESSED` (they go straight to dispatch),
/// so the cleanup drains both. Once the atts table is empty,
/// `available_primary_key()` resets to 0, our `std::max(1, ...)`
/// floor bumps it to 1 — and the next inbound `SwapRequest` gets
/// the SAME attestation_id as a prior phase. The downstream
/// `sysio.uwrit::createuwreq` idempotency guard
/// (`reqs.contains(pk)`) then short-circuits the second phase,
/// silently dropping the new swap.
/// floor bumps it to 1, reusing a prior attestation's audit identity.
///
/// `att_seq` is a one-row table holding `next` — the next id to
/// mint. `mint_att_id()` reads + bumps it atomically. Cleanup
Expand Down Expand Up @@ -350,7 +341,6 @@ namespace sysio {
private:

using ChainKind = opp::types::ChainKind;
using ChainRequestStatus = opp::types::ChainRequestStatus;
using MessageDirection = opp::types::MessageDirection;
using MessageStatus = opp::types::MessageStatus;
using EnvelopeStatus = opp::types::EnvelopeStatus;
Expand Down
Loading
Loading