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
2 changes: 2 additions & 0 deletions contracts/sysio.msgch/include/sysio.msgch/sysio.msgch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ namespace sysio {
/// destination's `sysio.chains` `{kind, external_chain_id}`) so the
/// receiving outpost can reject an envelope a misconfigured relay
/// delivered to the wrong same-kind chain.
/// When the envelope carries DESYNDICATE_LIQ attestations, sends one inline
/// `sysio.synd::retsent` with the envelope's epoch and their request ids.
[[sysio::action]]
void buildenv(uint64_t chain_code);

Expand Down
28 changes: 27 additions & 1 deletion contracts/sysio.msgch/src/sysio.msgch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ constexpr auto action_onsynd = "onsynd"_n;
constexpr auto action_onyield = "onyield"_n;
/// Sent once after the envelope's last attestation, only when one of them reached `sysio.synd`.
constexpr auto action_closeenv = "closeenv"_n;
/// Sent once per outbound envelope that carried a DESYNDICATE_LIQ, with the envelope's epoch and the
/// request ids it carried.
constexpr auto action_retsent = "retsent"_n;
} // namespace synd

// System-owned rows bill to the sysio RAM pool, not this contract account (privileged-contract
Expand Down Expand Up @@ -469,6 +472,17 @@ bool dispatch_syndicate_liq(name self, const std::vector<char>& data, uint64_t c
return true;
}

/// The request id of the DESYNDICATE_LIQ attestation `data`, or `std::nullopt` when the bytes do not decode
/// or carry no id (0, which the outpost reads as "no id"). `sysio.synd` queues every DESYNDICATE_LIQ with a
/// nonzero id. Anything else is skipped rather than aborting, because `buildenv` runs inside
/// `sysio.epoch::advance`.
std::optional<uint64_t> desyndication_request_id(const std::vector<char>& data) {
opp::attestations::DesyndicateLIQ desyndication;
auto in = zpp::bits::in{std::span{data.data(), data.size()}, zpp::bits::no_size{}};
if (in(desyndication) != zpp::bits::errc{} || desyndication.request_id == 0) return std::nullopt;
return desyndication.request_id;
}

/// LIQ_YIELD: the outpost claimed yield for its syndicated pool since its last report. After the
/// same checks as a syndication it goes to `sysio.synd::onyield` with the envelope's identity,
/// which holds the amount as a number until the envelope is underwritten; nothing is minted on this
Expand Down Expand Up @@ -1707,9 +1721,15 @@ void msgch::buildenv(uint64_t chain_code) {
// The shipped rows' bytes are in `packed` now: erase them, and count the general-lane ones out of the
// outpost's queue. Rows the walk stopped before, or the trim loop popped, stay READY for the next
// epoch's `buildenv` call.
uint32_t general_shipped = 0;
uint32_t general_shipped = 0;
std::vector<uint64_t> shipped_returns;
for (size_t i = 0; i < included_ids.size(); ++i) {
if (lane_of(entries[i].type) == queue_lane::general) ++general_shipped;
if (entries[i].type == AttestationType::ATTESTATION_TYPE_DESYNDICATE_LIQ) {
if (const auto request_id = desyndication_request_id(entries[i].data)) {
shipped_returns.push_back(*request_id);
}
}
atts.erase(id_key{included_ids[i]});
}
if (general_shipped > 0) {
Expand All @@ -1719,6 +1739,12 @@ void msgch::buildenv(uint64_t chain_code) {
queue.queued -= std::min(queue.queued, general_shipped);
queues.set(ram_payer, outbound_queue_key{chain_code}, queue);
}
// sysio.synd records this envelope's epoch on each shipped desyndication's `returns` row. A row can wait
// in the queue past several envelopes, so only this build knows which envelope carried it.
if (!shipped_returns.empty()) {
action(permission_level{get_self(), "active"_n}, synd::account, synd::action_retsent,
std::make_tuple(sysio::slug_name{chain_code}, epoch, shipped_returns)).send();
}

// This envelope's canonical epoch digest: keccak256 over the packed bytes, which equal the
// digest preimage because `envelope_hash` is empty on the wire. This is the value the receiving
Expand Down
Binary file modified contracts/sysio.msgch/sysio.msgch.wasm
Binary file not shown.
32 changes: 25 additions & 7 deletions contracts/sysio.synd/include/sysio.synd/sysio.synd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
* flat part covers the external-chain gas the batch operators pay for the round trip. The rest is burned with
* `sysio.liq::burn`, queued as DESYNDICATE_LIQ to the token's outpost with the next request id, and
* retained in `returns` until governance establishes its external outcome. The bucket drops by the whole quantity.
* `sysio.msgch::buildenv` reports through `retsent` which outbound envelope carried each DESYNDICATE_LIQ,
* and the row records that envelope's depot epoch in `sent_epoch`. It stays 0 while the attestation waits
* in the outbound queue, which can hold it past several envelopes. The outpost has consumed the row's envelope
* once `sysio.msgch`'s `outpcons` epoch for the outpost reaches the row's `sent_epoch`: an outpost emits its
* envelope for an epoch only after accepting the depot's.
* The attestation carries `total_syndicated`, the depot's outstanding shadow of the symbol
* after the burn (`liq::outstanding_of`), for the outpost's custody check.
*
Expand Down Expand Up @@ -105,12 +110,12 @@
* Launch. `importsynd` replays pre-launch positions inside the epoch-0 bootstrap window, minting
* straight to a linked account or into `parked`; `importdone` closes the import for good.
*
* Never-throw: `onsynd`, `onyield` and `closeenv` run inside `sysio.msgch`'s consensus transaction.
* They `check` nothing on their input; a message that fails a rule is dropped with a `DROP`
* diagnostic on the console and consumes no sequence. The inline `sysio.liq::mint` of a held
* syndication is sent only after every condition that `mint` checks has been verified here. Receipt
* actions accept only self-inline calls; the queue step `closeenv` runs keeps the same discipline
* for every action it sends.
* Never-throw: `onsynd`, `onyield` and `closeenv` run inside `sysio.msgch`'s consensus transaction, and
* `retsent` inside `sysio.epoch::advance`. They `check` nothing on their input; a message that fails a
* rule is dropped with a `DROP` diagnostic on the console and consumes no sequence. The inline
* `sysio.liq::mint` of a held syndication is sent only after every condition that `mint` checks has been
* verified here. Receipt actions accept only self-inline calls; the queue step `closeenv` runs keeps the
* same discipline for every action it sends.
*
* Privileged (roa::setsyscode): every row bills the `sysio` RAM pool -- there is no signer to bill on
* the envelope path -- and inline actions carry this contract's own authority without a
Expand Down Expand Up @@ -449,6 +454,15 @@ namespace sysio {
[[sysio::action, sysio::read_only]]
desynd_quote quotedesynd(slug_name chain_code, slug_name token_code, uint64_t quantity);

/// The outbound envelope of outpost `chain_code` built for depot epoch `epoch_index` carried the
/// DESYNDICATE_LIQ attestations of `request_ids`. Each listed `returns` row of that outpost records
/// `epoch_index` as its `sent_epoch`, once: a row already stamped keeps its epoch. An id with no row (governance has
/// resolved it) or whose row belongs to another outpost is dropped with a diagnostic. Sent by
/// `sysio.msgch::buildenv` inside `sysio.epoch::advance`, only when the envelope carried a
/// DESYNDICATE_LIQ; never throws. Auth=sysio.msgch.
[[sysio::action]] void retsent(sysio::slug_name chain_code, uint32_t epoch_index,
std::vector<uint64_t> request_ids);

// -----------------------------------------------------------------------
// Launch ingestion (privileged caller, epoch-0 bootstrap window)
// -----------------------------------------------------------------------
Expand Down Expand Up @@ -658,7 +672,11 @@ namespace sysio {
opp::types::ChainKind chain_kind; ///< destination key family
std::vector<char> 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))
/// Depot epoch of the outbound envelope that carried the DESYNDICATE_LIQ, from `retsent`; 0 until
/// that envelope is built. No envelope is built for epoch 0.
uint32_t sent_epoch = 0;
SYSLIB_SERIALIZE(return_row,
(request_id)(holder)(chain_code)(token_code)(chain_kind)(pubkey)(amount)(sent_epoch))
};
/// Outstanding returns only; completed/refunded rows are erased atomically.
using returns_t = kv::table<"returns"_n, return_key, return_row>;
Expand Down
37 changes: 33 additions & 4 deletions contracts/sysio.synd/src/sysio.synd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,11 @@ constexpr std::string_view desynd_net_message = "desyndication net of the fee
constexpr std::string_view desynd_fee_flat_message =
"flat desyndication fee must fit an asset and be below the minimum desyndication";

/// Diagnostic path names of the intake actions.
constexpr std::string_view onsynd_path = "onsynd";
constexpr std::string_view onyield_path = "onyield";
constexpr std::string_view closeenv_path = "closeenv";
/// Diagnostic path names of the intake actions and of `retsent`.
constexpr std::string_view onsynd_path = "onsynd";
constexpr std::string_view onyield_path = "onyield";
constexpr std::string_view closeenv_path = "closeenv";
constexpr std::string_view retsent_path = "retsent";

/// Drop reasons, one per rule an intake message can fail.
constexpr std::string_view pubkey_misfit_reason = "pubkey does not fit the chain family";
Expand All @@ -147,6 +148,11 @@ constexpr std::string_view envelope_closed_reason = "envelope already closed or
constexpr std::string_view replay_reason = "replayed sequence";
constexpr std::string_view digest_reason = "envelope digest does not match; left OPEN";

/// Why `retsent` left a request id's row as it was.
constexpr std::string_view return_resolved_reason = "no outstanding return for the request id";
constexpr std::string_view return_outpost_reason = "the return belongs to another outpost";
constexpr std::string_view return_stamped_reason = "the return already records the envelope that carried it";

/// Why one step of the queue left an envelope or an item where it was.
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";
Expand Down Expand Up @@ -1589,6 +1595,29 @@ void synd::desyndicate(name holder, asset quantity, uint64_t max_fee) {
.send();
}

void synd::retsent(sysio::slug_name chain_code, uint32_t epoch_index, std::vector<uint64_t> request_ids) {
require_auth(MSGCH_ACCOUNT);
returns_t pending(get_self());
for (const uint64_t request_id : request_ids) {
const return_key key{request_id};
auto row = pending.try_get(key);
if (!row.has_value()) {
drop(retsent_path, return_resolved_reason);
continue;
}
if (row->chain_code != chain_code) {
drop(retsent_path, return_outpost_reason);
continue;
}
if (row->sent_epoch != 0) {
drop(retsent_path, return_stamped_reason);
continue;
}
row->sent_epoch = epoch_index;
pending.set(ram_payer, key, *row);
}
}

// ---------------------------------------------------------------------------
// Launch ingestion
// ---------------------------------------------------------------------------
Expand Down
27 changes: 27 additions & 0 deletions contracts/sysio.synd/sysio.synd.abi
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,24 @@
}
]
},
{
"name": "retsent",
"base": "",
"fields": [
{
"name": "chain_code",
"type": "slug_name"
},
{
"name": "epoch_index",
"type": "uint32"
},
{
"name": "request_ids",
"type": "uint64[]"
}
]
},
{
"name": "return_key",
"base": "",
Expand Down Expand Up @@ -947,6 +965,10 @@
{
"name": "amount",
"type": "uint64"
},
{
"name": "sent_epoch",
"type": "uint32"
}
]
},
Expand Down Expand Up @@ -1313,6 +1335,11 @@
"type": "refundreturn",
"ricardian_contract": ""
},
{
"name": "retsent",
"type": "retsent",
"ricardian_contract": ""
},
{
"name": "setconfig",
"type": "setconfig",
Expand Down
Binary file modified contracts/sysio.synd/sysio.synd.wasm
Binary file not shown.
Loading
Loading