depot v3: running balance guard + sysio.payer auth + clearmsgs admin action
Fixes: - yield_reward and wire_purchase transfers now check available WIRE balance before sending, preventing overdrawn balance errors during crank - Running balance tracker across process_ready_messages loop prevents deferred inline action overdraw (inline actions execute after caller returns) - sysio.payer permission_level added to transfer actions for Wire ROA model - clearmsgs admin action to flush garbage inbound messages (testnet only) Deploy tx: 5ab93b7c (wasm), b67ccc07 (wasm+abi) Code hash: eca6e8ee71c4b8d4e96f8543036f677775320bcea87aaf522b096f18a4cae9ac Wasm size: 113,014 bytes
This commit is contained in:
1066
artifacts/sysio.depot.v3-final.abi
Normal file
1066
artifacts/sysio.depot.v3-final.abi
Normal file
File diff suppressed because it is too large
Load Diff
BIN
artifacts/sysio.depot.v3-final.wasm
Executable file
BIN
artifacts/sysio.depot.v3-final.wasm
Executable file
Binary file not shown.
@@ -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<char>& payload);
|
||||
void process_assertion(uint64_t message_number, assertion_type_t type, const std::vector<char>& payload, int64_t& available_wire);
|
||||
void queue_outbound_message(assertion_type_t type, const std::vector<char>& 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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<int64_t>(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<int64_t>(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<char>& payload) {
|
||||
const std::vector<char>& 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<permission_level>{{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<permission_level>{{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
|
||||
|
||||
Reference in New Issue
Block a user