Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
Binary file modified contracts/sysio.authex/sysio.authex.wasm
Binary file not shown.
Binary file modified contracts/sysio.chalg/sysio.chalg.wasm
Binary file not shown.
Binary file modified contracts/sysio.councl/sysio.councl.wasm
Binary file not shown.
Binary file modified contracts/sysio.msig/sysio.msig.wasm
Binary file not shown.
123 changes: 102 additions & 21 deletions contracts/sysio.opp.common/include/sysio.opp.common/amm_math.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,38 +172,113 @@ inline uint64_t wire_to_token(uint64_t reserve_wire_amount,
/// Basis-points denominator (10000 = 100%).
inline constexpr uint32_t BPS_TOTAL = 10000;

/// Decomposition of a swap fee taken out of the WIRE leg.
/// Decomposition of EVERY fee taken out of a swap's WIRE leg.
///
/// Two independent fees ride the same leg (WIRE-281): the NETWORK fee
/// (`sysio.uwrit::fee_bps`, split between the winning underwriter, batch
/// operators and optionally the emissions treasury) and each participating
/// RESERVE's own owner fee. A chain-to-chain swap therefore pays three fees —
/// two reserve owners plus the network — while a WIRE-endpoint swap touches one
/// reserve and pays two.
struct wire_fee {
uint64_t fee = 0; ///< total fee charged (WIRE)
uint64_t reward_share = 0; ///< portion routed to the rewards bucket
uint64_t emissions_share = 0; ///< portion returned to the emissions treasury
uint64_t net = 0; ///< wire_amount - fee (continues through the swap)
uint64_t fee = 0; ///< TOTAL of every fee charged on the leg
uint64_t underwriter_share = 0; ///< network fee: the swap's winning underwriter
uint64_t reward_share = 0; ///< network fee: rewards bucket (batch operators)
uint64_t emissions_share = 0; ///< network fee: the `sysio` emissions treasury
uint64_t src_reserve_share = 0; ///< the SOURCE reserve owner's fee
uint64_t dst_reserve_share = 0; ///< the DESTINATION reserve owner's fee
uint64_t net = 0; ///< wire_amount - fee (continues through the swap)
};

/// Split `wire_amount` into fee (`fee_bps`) + remainder, then split the fee into
/// a rewards share (`reward_share_bps`) and an emissions share (the rest). All
/// integer; `reward_share + emissions_share == fee` and `net + fee ==
/// wire_amount` exactly (no rounding leak). Computed in `u128` to avoid overflow.
inline wire_fee split_wire_fee(uint64_t wire_amount, uint32_t fee_bps, uint32_t reward_share_bps) {
/// The ONE fee decomposition for a swap's WIRE leg — used by BOTH the read-only
/// quote and settlement, so the two can never drift by a rounding subunit.
///
/// Every rate is applied to the SAME gross `wire_amount`, so the fees are purely
/// additive and order-independent — a user pays `fee_bps + src + dst` of the
/// leg, and no party's cut depends on who is computed first:
///
/// * **Reserve fees** — `src_reserve_fee_bps` / `dst_reserve_fee_bps`, each
/// the owner fee of the reserve supplying that leg (0 for a WIRE endpoint,
/// which has no reserve). Accrue to those reserves' owners.
/// * **Network fee** — `fee_bps`, itself split in two stages:
/// 1. `underwriter_share_bps` of it goes to the winning underwriter; the
/// remainder is the **rewards pool**.
/// 2. `emissions_share_bps` of that POOL (not of the whole fee) goes to the
/// `sysio` emissions treasury; the rest is the batch-operator share.
///
/// All integer, exact: the five shares sum to `fee` and `net + fee ==

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.

[Low] Qualify conservation to the non-clamped path. This states unconditionally that the five share fields sum to fee, but the paragraph below correctly says they can sum to more than fee after total_fee is clamped to wire_amount. Please state that five-share conservation holds only when total_fee < wire_amount; net + fee == wire_amount remains unconditional.

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.

Correct — fixed in 25092af3b4. The sentence contradicted the clamp paragraph directly below it, which is the worse form of the error: a reader who trusts the first statement has no reason to read on, and the clamp is precisely the case where the five shares stop summing to fee.

Split into the conditional and unconditional halves:

/// All integer, exact, because every stage takes a REMAINDER rather than a
/// second floored product. `net + fee == wire_amount` holds UNCONDITIONALLY.
/// Five-share conservation — the shares summing to `fee` — holds only on the
/// non-clamped path, i.e. when `total_fee < wire_amount`; once the total reaches
/// the leg `fee` is clamped to `wire_amount` and the shares sum to MORE than it
/// (see the clamp note below). Computed in `u128` to avoid overflow.

net + fee == wire_amount is called out as unconditional exactly as you say, since it holds on both paths — the clamp sets fee = wire_amount, net = 0, which satisfies it.

Comment only. See my note on the other three in this batch regarding test verification.

/// wire_amount`, because every stage takes a REMAINDER rather than a second
/// floored product. Computed in `u128` to avoid overflow.
///
/// **Callers must check `net > 0`.** With enough stacked rates the total can
/// reach or exceed the leg. When it does, `fee` is CLAMPED to `wire_amount` and
/// `net` is 0 — so in THAT case alone the per-share fields sum to more than
/// `fee`, which is harmless because the caller rejects the swap and no share is
/// ever accrued. The settlement paths already assert this, and
/// `sysio.reserv::setrsvfee` / `sysio.uwrit::setconfig` cap each rate so a

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.

[Medium] Treat zero-net as a valid configured rejection state. The paragraph first says stacked rates can consume the leg, then says the per-rate caps make that unrealistic, but valid settings can reach it directly—for example, network 9999 bps plus one reserve-owner 1 bps already totals 100%. The clamp and zero quote are therefore live behavior, not merely theoretical defense-in-depth. Please document it as an intentionally rejected configuration and sweep the matching MAX_FEE_BPS/"unreachable" claims in sysio.reserv.cpp, sysio.uwrit.hpp/.cpp, and amm_math_tests.cpp. Also note that paywire currently fails closed through the upstream zero-quote check rather than a local net > 0 assertion, so "the settlement paths already assert this" is not literally true.

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.

Confirmed, and your example is exact — fixed in cb31b67323. MAX_FEE_BPS = 9999 plus MIN_OWNER_FEE_BPS = 1 is 10000 precisely, and nothing cross-checks the sum: setconfig bounds the network rate, setrsvfee bounds each owner rate, and neither knows about the other. So a zero net is a configured rejection the callers must handle, and the paragraph claiming "a realistic combination cannot get there" was simply false.

I made it a test rather than only prose, since a comment is exactly what rotted here. New split_wire_fee_reaches_the_leg_under_valid_configuration pins:

  • 9999 + 1net == 0, fee == leg (your case, at the owner-fee FLOOR — no extreme setting needed)
  • both legs at the floor → still a consumed leg, no wrap or under-report
  • 9998 + 1net > 0 — so it is a genuine boundary, not a blanket refusal of high fees

Swept the matching claims: the applyswap and applyfromwire backstop comments (both said "unreachable"), both MAX_FEE_BPS comments in sysio.uwrit (hpp and cpp — each said the cap "keeps the remainder positive", true only for that rate in isolation), and the amm_math_tests suite header, which asserted setconfig makes net == 0 "unconstructible at settlement".

One claim I deliberately kept, because sweeping it would have been wrong: refundwire's "unreachable" is true. A refund leaves the trailing reserve rates at 0, so its total is the network fee alone at ≤ 99.99% and floor division always leaves net >= 1 — there is no second rate to stack. Its only defect was the cross-reference calling it "the same defense-in-depth pattern as applyswap", which stopped being true once applyswap's became live; it now states the path-specific reason instead.

You are also right about paywire, and it is now documented as the exception: it pays the caller's wire_out target rather than net, so it never reads the field. It fails closed on the upstream zero quote and on its own reserve_wire_amount >= wire_out + fee sufficiency check — which is also why it cannot drain a reserve at an arbitrary price the way applyswap/applyfromwire could, so the asymmetry is intentional rather than a missing assert.

contracts_unit_test: 584 cases, *** No errors detected. No wasm/abi delta.

/// realistic combination cannot get there.
///
/// Paths with no winning underwriter (a revert refund) pass 0 for
/// `underwriter_share_bps`, sending the whole network fee into the rewards pool.
/// The trailing rates default to 0, so a caller that charges only the network
/// fee — a revert, or a quote against a fee-free reserve — omits them.
inline wire_fee split_wire_fee(uint64_t wire_amount,
uint32_t fee_bps,
uint32_t underwriter_share_bps,
uint32_t emissions_share_bps = 0,
uint32_t src_reserve_fee_bps = 0,
uint32_t dst_reserve_fee_bps = 0) {
wire_fee r;
if (fee_bps > BPS_TOTAL) fee_bps = BPS_TOTAL;
if (reward_share_bps > BPS_TOTAL) reward_share_bps = BPS_TOTAL;
r.fee = static_cast<uint64_t>((static_cast<u128>(wire_amount) * fee_bps) / BPS_TOTAL);
r.reward_share = static_cast<uint64_t>((static_cast<u128>(r.fee) * reward_share_bps) / BPS_TOTAL);
r.emissions_share = r.fee - r.reward_share;
r.net = wire_amount - r.fee;
if (underwriter_share_bps > BPS_TOTAL) underwriter_share_bps = BPS_TOTAL;
if (emissions_share_bps > BPS_TOTAL) emissions_share_bps = BPS_TOTAL;
if (src_reserve_fee_bps > BPS_TOTAL) src_reserve_fee_bps = BPS_TOTAL;
if (dst_reserve_fee_bps > BPS_TOTAL) dst_reserve_fee_bps = BPS_TOTAL;

// Each reserve owner's cut, off the gross leg.
r.src_reserve_share = static_cast<uint64_t>((static_cast<u128>(wire_amount) * src_reserve_fee_bps) / BPS_TOTAL);
r.dst_reserve_share = static_cast<uint64_t>((static_cast<u128>(wire_amount) * dst_reserve_fee_bps) / BPS_TOTAL);

// The network fee, off the same gross leg, then its own two-stage split.
const uint64_t network_fee = static_cast<uint64_t>((static_cast<u128>(wire_amount) * fee_bps) / BPS_TOTAL);
r.underwriter_share = static_cast<uint64_t>((static_cast<u128>(network_fee) * underwriter_share_bps) / BPS_TOTAL);
const uint64_t rewards_pool = network_fee - r.underwriter_share;
r.emissions_share = static_cast<uint64_t>((static_cast<u128>(rewards_pool) * emissions_share_bps) / BPS_TOTAL);
r.reward_share = rewards_pool - r.emissions_share;

// Sum in u128. Three stacked rates carry past uint64 on an extreme leg, and a
// WRAPPED total is worse than a saturated one: it reports a small positive
// `net` for a swap that consumed the whole leg, so the caller's `net > 0`
// gate waves it through. Compare in u128 and narrow only when the total
// genuinely fits; otherwise clamp to the leg so a fully-consumed leg is
// reported as exactly that.
const u128 total_fee = static_cast<u128>(network_fee)
+ static_cast<u128>(r.src_reserve_share)
+ static_cast<u128>(r.dst_reserve_share);
if (total_fee >= static_cast<u128>(wire_amount)) {
r.fee = wire_amount;
r.net = 0;
} else {
r.fee = static_cast<uint64_t>(total_fee);
r.net = wire_amount - r.fee;
}
return r;
}

/// Post-fee swap quote along the depot curve. A WIRE endpoint (`src_is_wire` /
/// `dst_is_wire`) skips that side's reserve — the depot IS the WIRE side. For a
/// non-WIRE side pass that reserve's `(chain_amount, wire_amount, cw)`. The fee
/// (`fee_bps`) is charged on the WIRE leg. Returns the post-fee output the
/// recipient could receive, or 0 on degenerate input. Mirrors settlement
/// exactly so quotes and books agree.
/// non-WIRE side pass that reserve's `(chain_amount, wire_amount, cw)` AND its
/// `owner_fee_bps`; a WIRE endpoint has no reserve, so pass 0 for its fee.
///
/// Every fee — the network `fee_bps` plus each participating reserve's owner fee
/// — is charged on the WIRE leg through the SAME `split_wire_fee` the settlement
/// paths use, so quotes and books agree by construction. Returns the post-fee
/// output the recipient could receive, or 0 on degenerate input (including a
/// stacked-fee combination that leaves nothing).
inline uint64_t quote_swap(bool src_is_wire, uint64_t src_chain, uint64_t src_wire, uint32_t src_cw,
bool dst_is_wire, uint64_t dst_chain, uint64_t dst_wire, uint32_t dst_cw,
uint64_t amount_in, uint32_t fee_bps) {
uint64_t amount_in, uint32_t fee_bps,
uint32_t src_reserve_fee_bps = 0, uint32_t dst_reserve_fee_bps = 0) {
if (amount_in == 0) return 0;
if (src_is_wire && dst_is_wire) return amount_in; // WIRE->WIRE is a plain transfer

Expand All @@ -212,7 +287,13 @@ inline uint64_t quote_swap(bool src_is_wire, uint64_t src_chain, uint64_t src_wi
: token_to_wire(src_chain, src_wire, src_cw, amount_in);
if (wire_leg == 0) return 0;

const uint64_t wire_net = split_wire_fee(wire_leg, fee_bps, /*reward_share_bps*/0).net;
// A WIRE endpoint has no reserve on that side and therefore charges no
// reserve fee, whatever the caller passed.
const uint64_t wire_net = split_wire_fee(wire_leg, fee_bps, /*underwriter_share_bps*/0,
/*emissions_share_bps*/0,
src_is_wire ? 0 : src_reserve_fee_bps,
dst_is_wire ? 0 : dst_reserve_fee_bps).net;
if (wire_net == 0) return 0;
if (dst_is_wire) return wire_net; // user receives WIRE directly
return wire_to_token(dst_wire, dst_chain, dst_cw, wire_net);
}
Expand Down
Loading
Loading