diff --git a/artifacts/sysio.depot.v3-final.abi b/artifacts/sysio.depot.v3-final.abi new file mode 100644 index 0000000..652e831 --- /dev/null +++ b/artifacts/sysio.depot.v3-final.abi @@ -0,0 +1,1066 @@ +{ + "____comment": "This file was generated with sysio-abigen. DO NOT EDIT ", + "version": "sysio::abi/1.2", + "types": [ + { + "new_type_name": "assertion_type_t", + "type": "uint16" + }, + { + "new_type_name": "chain_kind_t", + "type": "uint8" + }, + { + "new_type_name": "chain_status_t", + "type": "uint8" + }, + { + "new_type_name": "challenge_status_t", + "type": "uint8" + }, + { + "new_type_name": "depot_state_t", + "type": "uint8" + }, + { + "new_type_name": "message_direction_t", + "type": "uint8" + }, + { + "new_type_name": "message_status_t", + "type": "uint8" + }, + { + "new_type_name": "operator_status_t", + "type": "uint8" + }, + { + "new_type_name": "operator_type_t", + "type": "uint8" + }, + { + "new_type_name": "underwrite_status_t", + "type": "uint8" + } + ], + "structs": [ + { + "name": "activateop", + "base": "", + "fields": [ + { + "name": "wire_account", + "type": "name" + } + ] + }, + { + "name": "bootstrap", + "base": "", + "fields": [] + }, + { + "name": "challenge", + "base": "", + "fields": [ + { + "name": "challenger", + "type": "name" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "evidence", + "type": "bytes" + } + ] + }, + { + "name": "challenge_info", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "status", + "type": "challenge_status_t" + }, + { + "name": "round", + "type": "uint8" + }, + { + "name": "challenge_data", + "type": "bytes" + } + ] + }, + { + "name": "chalresolve", + "base": "", + "fields": [ + { + "name": "proposer", + "type": "name" + }, + { + "name": "challenge_id", + "type": "uint64" + }, + { + "name": "original_hash", + "type": "checksum256" + }, + { + "name": "round1_hash", + "type": "checksum256" + }, + { + "name": "round2_hash", + "type": "checksum256" + } + ] + }, + { + "name": "chalresp", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "challenge_id", + "type": "uint64" + }, + { + "name": "response_data", + "type": "bytes" + } + ] + }, + { + "name": "chalvote", + "base": "", + "fields": [ + { + "name": "voter", + "type": "name" + }, + { + "name": "challenge_id", + "type": "uint64" + }, + { + "name": "approve", + "type": "bool" + } + ] + }, + { + "name": "clearmsgs", + "base": "", + "fields": [] + }, + { + "name": "crank", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + } + ] + }, + { + "name": "depot_global_state", + "base": "", + "fields": [ + { + "name": "state", + "type": "depot_state_t" + }, + { + "name": "chain_id", + "type": "chain_kind_t" + }, + { + "name": "current_epoch", + "type": "uint64" + }, + { + "name": "next_epoch", + "type": "uint64" + }, + { + "name": "next_msg_out", + "type": "uint64" + }, + { + "name": "last_crank_time", + "type": "time_point_sec" + }, + { + "name": "token_contract", + "type": "name" + }, + { + "name": "initialized", + "type": "bool" + } + ] + }, + { + "name": "emitchain", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "epoch_number", + "type": "uint64" + } + ] + }, + { + "name": "epoch_vote", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "operator_id", + "type": "uint64" + }, + { + "name": "chain_hash", + "type": "checksum256" + }, + { + "name": "submitted_at", + "type": "time_point_sec" + } + ] + }, + { + "name": "exitop", + "base": "", + "fields": [ + { + "name": "wire_account", + "type": "name" + } + ] + }, + { + "name": "getquote", + "base": "", + "fields": [ + { + "name": "source_sym", + "type": "symbol" + }, + { + "name": "target_sym", + "type": "symbol" + }, + { + "name": "amount", + "type": "asset" + } + ] + }, + { + "name": "init", + "base": "", + "fields": [ + { + "name": "chain_id", + "type": "chain_kind_t" + }, + { + "name": "token_contract", + "type": "name" + } + ] + }, + { + "name": "known_operator", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "op_type", + "type": "operator_type_t" + }, + { + "name": "status", + "type": "operator_status_t" + }, + { + "name": "wire_account", + "type": "name" + }, + { + "name": "secp256k1_pubkey", + "type": "bytes" + }, + { + "name": "ed25519_pubkey", + "type": "bytes" + }, + { + "name": "collateral", + "type": "asset" + }, + { + "name": "registered_at", + "type": "time_point_sec" + }, + { + "name": "status_changed_at", + "type": "time_point_sec" + } + ] + }, + { + "name": "message_chain", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "direction", + "type": "message_direction_t" + }, + { + "name": "status", + "type": "chain_status_t" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "merkle_root", + "type": "checksum256" + }, + { + "name": "epoch_hash", + "type": "checksum256" + }, + { + "name": "prev_epoch_hash", + "type": "checksum256" + }, + { + "name": "payload", + "type": "bytes" + }, + { + "name": "operator_signature", + "type": "bytes" + }, + { + "name": "operator_id", + "type": "uint64" + }, + { + "name": "created_at", + "type": "time_point_sec" + } + ] + }, + { + "name": "oneshot", + "base": "", + "fields": [ + { + "name": "beneficiary", + "type": "name" + }, + { + "name": "amount", + "type": "asset" + } + ] + }, + { + "name": "op_schedule", + "base": "", + "fields": [ + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "elected_operator_ids", + "type": "uint64[]" + }, + { + "name": "created_at", + "type": "time_point_sec" + } + ] + }, + { + "name": "opp_epoch_in", + "base": "", + "fields": [ + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "start_message", + "type": "uint64" + }, + { + "name": "end_message", + "type": "uint64" + }, + { + "name": "epoch_merkle", + "type": "checksum256" + }, + { + "name": "challenge_flag", + "type": "bool" + } + ] + }, + { + "name": "opp_epoch_out", + "base": "", + "fields": [ + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "start_message", + "type": "uint64" + }, + { + "name": "end_message", + "type": "uint64" + }, + { + "name": "merkle_root", + "type": "checksum256" + } + ] + }, + { + "name": "opp_fork", + "base": "", + "fields": [ + { + "name": "fork_id", + "type": "uint64" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "end_message_id", + "type": "uint64" + }, + { + "name": "merkle_root", + "type": "checksum256" + } + ] + }, + { + "name": "opp_fork_vote", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "fork_id", + "type": "uint64" + }, + { + "name": "voter", + "type": "name" + }, + { + "name": "vote_state", + "type": "uint8" + } + ] + }, + { + "name": "opp_message_in", + "base": "", + "fields": [ + { + "name": "message_number", + "type": "uint64" + }, + { + "name": "assertion_type", + "type": "assertion_type_t" + }, + { + "name": "status", + "type": "message_status_t" + }, + { + "name": "payload", + "type": "bytes" + } + ] + }, + { + "name": "opp_message_out", + "base": "", + "fields": [ + { + "name": "message_number", + "type": "uint64" + }, + { + "name": "assertion_type", + "type": "assertion_type_t" + }, + { + "name": "payload", + "type": "bytes" + } + ] + }, + { + "name": "regoperator", + "base": "", + "fields": [ + { + "name": "wire_account", + "type": "name" + }, + { + "name": "op_type", + "type": "operator_type_t" + }, + { + "name": "secp256k1_pubkey", + "type": "bytes" + }, + { + "name": "ed25519_pubkey", + "type": "bytes" + }, + { + "name": "collateral", + "type": "asset" + } + ] + }, + { + "name": "reserve_balance", + "base": "", + "fields": [ + { + "name": "reserve_total", + "type": "asset" + }, + { + "name": "wire_equivalent", + "type": "asset" + } + ] + }, + { + "name": "setreserve", + "base": "", + "fields": [ + { + "name": "authority", + "type": "name" + }, + { + "name": "reserve_total", + "type": "asset" + }, + { + "name": "wire_equivalent", + "type": "asset" + } + ] + }, + { + "name": "slashop", + "base": "", + "fields": [ + { + "name": "wire_account", + "type": "name" + }, + { + "name": "reason", + "type": "string" + } + ] + }, + { + "name": "submitchain", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "epoch_hash", + "type": "checksum256" + }, + { + "name": "prev_epoch_hash", + "type": "checksum256" + }, + { + "name": "merkle_root", + "type": "checksum256" + }, + { + "name": "signature", + "type": "bytes" + } + ] + }, + { + "name": "underwrite_entry", + "base": "", + "fields": [ + { + "name": "id", + "type": "uint64" + }, + { + "name": "operator_id", + "type": "uint64" + }, + { + "name": "status", + "type": "underwrite_status_t" + }, + { + "name": "source_amount", + "type": "asset" + }, + { + "name": "target_amount", + "type": "asset" + }, + { + "name": "source_chain", + "type": "chain_kind_t" + }, + { + "name": "target_chain", + "type": "chain_kind_t" + }, + { + "name": "exchange_rate_bps", + "type": "uint64" + }, + { + "name": "unlock_at", + "type": "time_point_sec" + }, + { + "name": "created_at", + "type": "time_point_sec" + }, + { + "name": "source_tx_hash", + "type": "checksum256" + }, + { + "name": "target_tx_hash", + "type": "checksum256" + } + ] + }, + { + "name": "unregop", + "base": "", + "fields": [ + { + "name": "wire_account", + "type": "name" + } + ] + }, + { + "name": "updreserve", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "token_sym", + "type": "symbol" + }, + { + "name": "delta", + "type": "int64" + } + ] + }, + { + "name": "uploadmsgs", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "epoch_number", + "type": "uint64" + }, + { + "name": "messages", + "type": "bytes" + }, + { + "name": "merkle_proofs", + "type": "bytes" + } + ] + }, + { + "name": "uwcancel", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "ledger_entry_id", + "type": "uint64" + }, + { + "name": "reason", + "type": "string" + } + ] + }, + { + "name": "uwconfirm", + "base": "", + "fields": [ + { + "name": "operator_account", + "type": "name" + }, + { + "name": "ledger_entry_id", + "type": "uint64" + } + ] + }, + { + "name": "uwexpire", + "base": "", + "fields": [] + }, + { + "name": "uwintent", + "base": "", + "fields": [ + { + "name": "underwriter", + "type": "name" + }, + { + "name": "message_id", + "type": "uint64" + }, + { + "name": "source_amount", + "type": "asset" + }, + { + "name": "target_amount", + "type": "asset" + }, + { + "name": "source_chain", + "type": "chain_kind_t" + }, + { + "name": "target_chain", + "type": "chain_kind_t" + }, + { + "name": "source_sig", + "type": "bytes" + }, + { + "name": "target_sig", + "type": "bytes" + } + ] + } + ], + "actions": [ + { + "name": "activateop", + "type": "activateop", + "ricardian_contract": "" + }, + { + "name": "bootstrap", + "type": "bootstrap", + "ricardian_contract": "" + }, + { + "name": "challenge", + "type": "challenge", + "ricardian_contract": "" + }, + { + "name": "chalresolve", + "type": "chalresolve", + "ricardian_contract": "" + }, + { + "name": "chalresp", + "type": "chalresp", + "ricardian_contract": "" + }, + { + "name": "chalvote", + "type": "chalvote", + "ricardian_contract": "" + }, + { + "name": "clearmsgs", + "type": "clearmsgs", + "ricardian_contract": "" + }, + { + "name": "crank", + "type": "crank", + "ricardian_contract": "" + }, + { + "name": "emitchain", + "type": "emitchain", + "ricardian_contract": "" + }, + { + "name": "exitop", + "type": "exitop", + "ricardian_contract": "" + }, + { + "name": "getquote", + "type": "getquote", + "ricardian_contract": "" + }, + { + "name": "init", + "type": "init", + "ricardian_contract": "" + }, + { + "name": "oneshot", + "type": "oneshot", + "ricardian_contract": "" + }, + { + "name": "regoperator", + "type": "regoperator", + "ricardian_contract": "" + }, + { + "name": "setreserve", + "type": "setreserve", + "ricardian_contract": "" + }, + { + "name": "slashop", + "type": "slashop", + "ricardian_contract": "" + }, + { + "name": "submitchain", + "type": "submitchain", + "ricardian_contract": "" + }, + { + "name": "unregop", + "type": "unregop", + "ricardian_contract": "" + }, + { + "name": "updreserve", + "type": "updreserve", + "ricardian_contract": "" + }, + { + "name": "uploadmsgs", + "type": "uploadmsgs", + "ricardian_contract": "" + }, + { + "name": "uwcancel", + "type": "uwcancel", + "ricardian_contract": "" + }, + { + "name": "uwconfirm", + "type": "uwconfirm", + "ricardian_contract": "" + }, + { + "name": "uwexpire", + "type": "uwexpire", + "ricardian_contract": "" + }, + { + "name": "uwintent", + "type": "uwintent", + "ricardian_contract": "" + } + ], + "tables": [ + { + "name": "challenges", + "type": "challenge_info", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "depotstate", + "type": "depot_global_state", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "epochvotes", + "type": "epoch_vote", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "knownops", + "type": "known_operator", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "msgchains", + "type": "message_chain", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppepochin", + "type": "opp_epoch_in", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppepochout", + "type": "opp_epoch_out", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppforks", + "type": "opp_fork", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppforkvote", + "type": "opp_fork_vote", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppin", + "type": "opp_message_in", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "oppout", + "type": "opp_message_out", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "opschedule", + "type": "op_schedule", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "reserves", + "type": "reserve_balance", + "index_type": "i64", + "key_names": [], + "key_types": [] + }, + { + "name": "uwledger", + "type": "underwrite_entry", + "index_type": "i64", + "key_names": [], + "key_types": [] + } + ], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/artifacts/sysio.depot.v3-final.wasm b/artifacts/sysio.depot.v3-final.wasm new file mode 100755 index 0000000..95c93bf Binary files /dev/null and b/artifacts/sysio.depot.v3-final.wasm differ diff --git a/contracts/sysio.depot/include/sysio.depot/sysio.depot.hpp b/contracts/sysio.depot/include/sysio.depot/sysio.depot.hpp index 3728637..cd06604 100644 --- a/contracts/sysio.depot/include/sysio.depot/sysio.depot.hpp +++ b/contracts/sysio.depot/include/sysio.depot/sysio.depot.hpp @@ -16,6 +16,11 @@ namespace sysio { [[sysio::action]] void bootstrap(); + // ── Admin: clear all inbound messages (testnet only) ──────────────── + [[sysio::action]] + void clearmsgs(); + using clearmsgs_action = sysio::action_wrapper<"clearmsgs"_n, &depot::clearmsgs>; + // ── FR-800: Crank Execution ─────────────────────────────────────────── [[sysio::action]] void crank(name operator_account); @@ -146,7 +151,7 @@ namespace sysio { void process_ready_messages(); void expire_underwriting_locks(); void elect_operators_for_epoch(uint64_t next_epoch); - void process_assertion(uint64_t message_number, assertion_type_t type, const std::vector& payload); + void process_assertion(uint64_t message_number, assertion_type_t type, const std::vector& payload, int64_t& available_wire); void queue_outbound_message(assertion_type_t type, const std::vector& payload); void mark_epoch_valid(uint64_t chain_scope, uint64_t epoch_number); void slash_minority_operators(uint64_t chain_scope, uint64_t epoch_number, diff --git a/contracts/sysio.depot/src/opp_inbound.cpp b/contracts/sysio.depot/src/opp_inbound.cpp index 403413d..02e54b5 100644 --- a/contracts/sysio.depot/src/opp_inbound.cpp +++ b/contracts/sysio.depot/src/opp_inbound.cpp @@ -194,8 +194,9 @@ void depot::uploadmsgs(name operator_account, }); // FR-108: Process challenge messages immediately + int64_t unused_wire = 0; if (is_challenge) { - process_assertion(msg_num - 1, assertion, payload); + process_assertion(msg_num - 1, assertion, payload, unused_wire); } } diff --git a/contracts/sysio.depot/src/sysio.depot.cpp b/contracts/sysio.depot/src/sysio.depot.cpp index 3770d8b..3eeb37c 100644 --- a/contracts/sysio.depot/src/sysio.depot.cpp +++ b/contracts/sysio.depot/src/sysio.depot.cpp @@ -56,6 +56,26 @@ void depot::bootstrap() { elect_operators_for_epoch(s.current_epoch); } +// ── clearmsgs (testnet admin): flush all inbound messages ──────────────────── + +void depot::clearmsgs() { + require_auth(get_self()); + + auto s = get_state(); + uint64_t chain_scope = uint64_t(s.chain_id); + + opp_in_table msgs(get_self(), chain_scope); + auto it = msgs.begin(); + uint32_t count = 0; + while (it != msgs.end()) { + it = msgs.erase(it); + ++count; + } + // Reset message numbering in epoch state + // (Messages have been discarded, epoch state should reflect this) +} + + // ── crank (FR-801) ────────────────────────────────────────────────────────── void depot::crank(name operator_account) { @@ -86,18 +106,54 @@ void depot::crank(name operator_account) { set_state(s); } +// Helper: read a big-endian uint64 from a byte buffer +static uint64_t read_be_u64(const char* p) { + return (uint64_t(uint8_t(p[0])) << 56) | (uint64_t(uint8_t(p[1])) << 48) | + (uint64_t(uint8_t(p[2])) << 40) | (uint64_t(uint8_t(p[3])) << 32) | + (uint64_t(uint8_t(p[4])) << 24) | (uint64_t(uint8_t(p[5])) << 16) | + (uint64_t(uint8_t(p[6])) << 8) | uint64_t(uint8_t(p[7])); +} + +static int64_t read_be_i64(const char* p) { + return static_cast(read_be_u64(p)); +} + +static uint16_t read_be_u16(const char* p) { + return (uint16_t(uint8_t(p[0])) << 8) | uint16_t(uint8_t(p[1])); +} + +// Helper: read depot's own WIRE balance from sysio.token accounts table +struct token_account { + asset balance; + uint64_t primary_key() const { return balance.symbol.code().raw(); } +}; +typedef multi_index<"accounts"_n, token_account> token_accounts_table; + +static int64_t get_wire_balance(name token_contract, name owner) { + token_accounts_table accts(token_contract, owner.value); + auto it = accts.find(symbol_code("WIRE").raw()); + if (it == accts.end()) return 0; + return it->balance.amount; +} + + + // ── process_ready_messages (internal) ──────────────────────────────────────── void depot::process_ready_messages() { auto s = get_state(); uint64_t chain_scope = uint64_t(s.chain_id); + // Read depot WIRE balance once — inline transfers are deferred so we must + // track the running balance ourselves to avoid overdrawn errors. + int64_t available_wire = get_wire_balance(s.token_contract, get_self()); + opp_in_table msgs(get_self(), chain_scope); auto by_status = msgs.get_index<"bystatus"_n>(); auto it = by_status.lower_bound(uint64_t(message_status_ready)); while (it != by_status.end() && it->status == message_status_ready) { - process_assertion(it->message_number, it->assertion_type, it->payload); + process_assertion(it->message_number, it->assertion_type, it->payload, available_wire); it = by_status.erase(it); } } @@ -130,24 +186,8 @@ void depot::process_ready_messages() { // slash_operator (0xEE05): // [operator_id(8) | reason_len(2) | reason(N)] -// Helper: read a big-endian uint64 from a byte buffer -static uint64_t read_be_u64(const char* p) { - return (uint64_t(uint8_t(p[0])) << 56) | (uint64_t(uint8_t(p[1])) << 48) | - (uint64_t(uint8_t(p[2])) << 40) | (uint64_t(uint8_t(p[3])) << 32) | - (uint64_t(uint8_t(p[4])) << 24) | (uint64_t(uint8_t(p[5])) << 16) | - (uint64_t(uint8_t(p[6])) << 8) | uint64_t(uint8_t(p[7])); -} - -static int64_t read_be_i64(const char* p) { - return static_cast(read_be_u64(p)); -} - -static uint16_t read_be_u16(const char* p) { - return (uint16_t(uint8_t(p[0])) << 8) | uint16_t(uint8_t(p[1])); -} - void depot::process_assertion(uint64_t message_number, assertion_type_t type, - const std::vector& payload) { + const std::vector& payload, int64_t& available_wire) { auto s = get_state(); uint64_t chain_scope = uint64_t(s.chain_id); @@ -273,15 +313,16 @@ void depot::process_assertion(uint64_t message_number, assertion_type_t type, } // Issue WIRE to beneficiary if they have an account and amount > 0 - if (wire_reward > 0 && is_account(beneficiary)) { + if (wire_reward > 0 && is_account(beneficiary) && available_wire >= wire_reward) { asset wire_payout(wire_reward, symbol("WIRE", 4)); action( - permission_level{get_self(), "active"_n}, + std::vector{{get_self(), "sysio.payer"_n}, {get_self(), "active"_n}}, s.token_contract, "transfer"_n, std::make_tuple(get_self(), beneficiary, wire_payout, std::string("yield reward distribution")) ).send(); + available_wire -= wire_reward; } break; } @@ -333,15 +374,16 @@ void depot::process_assertion(uint64_t message_number, assertion_type_t type, }); // Transfer WIRE to buyer - if (is_account(buyer)) { + if (is_account(buyer) && available_wire >= wire_output) { asset wire_payout(wire_output, symbol("WIRE", 4)); action( - permission_level{get_self(), "active"_n}, + std::vector{{get_self(), "sysio.payer"_n}, {get_self(), "active"_n}}, s.token_contract, "transfer"_n, std::make_tuple(get_self(), buyer, wire_payout, std::string("wire purchase via OPP")) ).send(); + available_wire -= wire_output; } // Queue outbound confirmation with the actual WIRE amount delivered