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: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
124 changes: 70 additions & 54 deletions OVERVIEW.md

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,14 @@ 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) |
| `status_monitor_plugin` | Ships one `/v1/chain/get_info` snapshot per irreversible block to an OpenSearch/Elasticsearch `_bulk` endpoint, rendered through an operator JSON template | [README](plugins/status_monitor_plugin/README.md) |
| `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) |

Expand Down
1 change: 0 additions & 1 deletion cmake/chain-tools.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions contracts/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
93 changes: 88 additions & 5 deletions contracts/sysio.bond/include/sysio.bond/sysio.bond.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
#include <sysio/slug_name.hpp>
#include <sysio/symbol.hpp>
#include <sysio/time.hpp>
#include <sysio/asset.hpp>
#include <sysio.opp.common/depot_native_token.hpp>
#include <string_view>
#include <sysio.opp.common/shadow_custody_types.hpp>

#include <magic_enum/magic_enum.hpp>
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<char> 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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -271,6 +311,34 @@ namespace sysio {
using requests_t = kv::table<"requests"_n, request_key, request_row,
kv::index<STATEMENT_INDEX, const_mem_fun<request_row, checksum256, &request_row::by_statement>>>;

/// Resolve the stable issuer-and-statement identity without exposing index layout to clients.
static std::optional<request_row> find_request(name code, name issuer, name schema,
const std::vector<char>& statement) {
requests_t requests(code);
const auto index = requests.get_index<STATEMENT_INDEX>();
const auto it = index.find(statement_key_of(issuer, statement_digest_of(schema, statement)));
return it == index.end() ? std::nullopt : std::optional<request_row>{*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<char>& 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<uint64_t>(asset::max_amount) || bounty > static_cast<uint64_t>(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
Expand Down Expand Up @@ -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<escrow_kind>()) {
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
Expand Down
Loading
Loading