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
87 changes: 30 additions & 57 deletions contracts/sysio.roa/sysio.roa.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ namespace sysio {
/// creators, and sysio.roa is privileged, so node-owner claims must refuse them here.
constexpr std::string_view RESERVED_SYSTEM_NAME_PREFIX = "sysio.";

/// sysio's account-creation RAM pool takes 1/10 of every tier-1 allocation, carved out at activateroa.
constexpr int64_t SYSIO_POOL_SHARE_DIVISOR{10};

/// Maximum number of generated account names checked before newuser gives up.
constexpr uint32_t MAX_ACCOUNT_NAME_ATTEMPTS{100};

Expand Down Expand Up @@ -77,6 +80,14 @@ namespace sysio {
}
}

// The slice of one owner's tier allocation carved out for sysio's pool. Only tier 1 contributes:
// it is the tier that creates accounts (newuser). The owner's budget is the remainder, so
// activateroa's carve-out and get_allocation_for_tier always partition the same total.
static int64_t tier_sysio_share(uint8_t tier, int64_t total_amount) {
if (tier != 1) return 0;
return tier_sys_allocation(tier, total_amount) / SYSIO_POOL_SHARE_DIVISOR;
}

// Every policy weight must be denominated in the core SYS symbol. asset arithmetic only checks
// that operands share a symbol, and the affordability gate compares raw amounts, so without this
// a weight in a different symbol/precision would be silently accepted and mis-scale the reserve
Expand Down Expand Up @@ -207,6 +218,12 @@ namespace sysio {
// Allocated sum
int64_t allocated = t1_total + t2_total + t3_total;

// sysio's share of every tier-1 slot, registered or not. It stays inside `allocated`; the owners'
// budgets (get_allocation_for_tier) are the tier allocation minus this share.
int64_t sysio_carve = tier_sysio_share(1, total_amount) * sysiosystem::emissions::T1_MAX_NODE_OWNERS
+ tier_sysio_share(2, total_amount) * sysiosystem::emissions::T2_MAX_NODE_OWNERS
+ tier_sysio_share(3, total_amount) * sysiosystem::emissions::T3_MAX_NODE_OWNERS;

// Leftover
int64_t leftover = total_amount - allocated;

Expand All @@ -217,19 +234,18 @@ namespace sysio {
// (positivity and the upper bound are already checked above), so `leftover` is non-negative.
check(allocated <= total_amount, "Total SYS too small: node-owner reserve exceeds supply");

// Convert the leftover (SYS units) to bytes and partition it so the grand total of all
// reslimits stays exactly total_sys * bytes_per_unit — nothing is minted on top:
// T = node-owner reserve (allocated above) + roa allocation + sysio pool.
// sysio.roa keeps half the leftover for its own (growing) bookkeeping tables; sysio gets
// the rest as THE pool that funds account creation and every other system contract's RAM
// (deployed via setsyscode/setsysabi, which gift the exact bytes out of this pool). Other
// system contracts are deliberately NOT pre-allocated here — they self-fund exactly. The
// only deduction is the sysio.acct account-creation bucket seed, taken out of sysio's
// share so it stays conserved.
// Partition so the grand total of all reslimits stays exactly total_sys * bytes_per_unit —
// nothing is minted on top:
// T = node-owner budgets (allocated - sysio_carve) + roa allocation + sysio pool.
// sysio.roa keeps half the leftover for its own (growing) bookkeeping tables. sysio gets the
// other half plus sysio_carve as THE pool that funds account creation and every other system
// contract's RAM (setsyscode/setsysabi gift the exact bytes out of it). The sysio.acct
// account-creation bucket seed is taken out of sysio's share so it stays conserved.
// bytes_per_unit divides newaccount_ram, so these products stay far inside uint64/int64.
uint64_t leftover_bytes = (uint64_t)leftover * bytes_per_unit; // leftover >= 0, guarded above
uint64_t roa_ram_bytes = leftover_bytes / 2;
const uint64_t acct_seed_bytes = sysiosystem::newaccount_ram;
uint64_t sysio_gross = leftover_bytes - roa_ram_bytes;
uint64_t sysio_gross = leftover_bytes - roa_ram_bytes + (uint64_t)sysio_carve * bytes_per_unit;
check(sysio_gross > acct_seed_bytes, "Leftover RAM too small for the account-creation seed");
uint64_t sysio_ram_bytes = sysio_gross - acct_seed_bytes;

Expand Down Expand Up @@ -874,7 +890,7 @@ namespace sysio {
check(nodeowner_count(get_self(), state.network_gen, tier) < tier_cap,
"node owner tier cap reached");

// Get the total SYS allocation for this tier
// The owner's budget: the tier allocation net of sysio's carve-out
asset total_sys_allocation = get_allocation_for_tier(tier);

// Only a tier-1 owner is provisioned a personal allocation here. Tier 1 is the sole tier
Expand Down Expand Up @@ -905,12 +921,6 @@ namespace sysio {
allocated_sys += personal_ram_weight;
allocated_ram += personal_ram_weight; // RAM allocation

// 10% of total SYS goes to sysio for RAM
int64_t sysio_alloc_amount = total_sys_allocation.amount / 10;
asset sysio_allocation(sysio_alloc_amount, total_sys_allocation.symbol);
allocated_sys += sysio_allocation;
allocated_ram += sysio_allocation; // Also RAM allocation since it's for sysio policy

// Minimal default net/cpu for a tier-1 owner: 0.0500 SYS each. Zero for tiers 2 and 3.
// Adding a zero asset below is a no-op, so the nodeowners totals stay correct for every
// tier without branching the accounting.
Expand All @@ -921,12 +931,6 @@ namespace sysio {
policies_t policies(get_self(), owner.value);
auto pol_key = policy_key{owner.value};

name sysio_account = "sysio"_n;
auto sysio_pol_key = policy_key{sysio_account.value};
asset zero_asset(0, state.total_sys.symbol);

// Guard the two policies independently: the sysio RAM grant is created for every tier, so
// it must not sit behind the presence of the tier-1-only personal policy.
if (provision_personal && !policies.contains(pol_key)) {
// Create personal policy
policies.emplace(get_self(), pol_key, roa::policies{
Expand All @@ -940,20 +944,6 @@ namespace sysio {
});
}

if (!policies.contains(sysio_pol_key)) {
// Create sysio policy for RAM. Every tier contributes 10% of its allocation to the
// network RAM pool that funds newaccount_ram, so this is not tier-gated.
policies.emplace(get_self(), sysio_pol_key, roa::policies{
.owner = sysio_account,
.issuer = owner,
.net_weight = zero_asset,
.cpu_weight = zero_asset,
.ram_weight = sysio_allocation,
.bytes_per_unit = state.bytes_per_unit,
.time_block = UINT32_MAX, // do not allow to be extended
});
}

// Owner reslimits. Stack the node-owner personal allocation onto whatever the account already
// holds. increase_reslimit CREATES the row when absent -- folding in the one-time newaccount_ram
// gift, so a fresh node owner gets exactly newaccount_ram + personal_ram_bytes (identical to the
Expand All @@ -974,24 +964,6 @@ namespace sysio {
(int64_t)personal_ram_bytes, /*require_to_exist=*/false);
set_resource_limits(owner, (int64_t)owner_res.ram_bytes, owner_res.net.amount, owner_res.cpu.amount);

// Sysio reslimit
reslimit_t sysioreslimit(get_self());
auto sysio_res_key = reslimit_key{sysio_account.value};
auto sysio_res = sysioreslimit.get(sysio_res_key, "sysio reslimit does not exist.");

uint64_t sysio_bytes = sysio_allocation.amount * state.bytes_per_unit;
sysioreslimit.modify(get_self(), sysio_res_key, [&](auto& row) {
// Saturating add, matching increase_reslimit -- this is the one reslimit-row accumulator that
// does not route through that helper, so harden it the same way (no-op for realistic values).
row.ram_bytes = opp::safe::add_sat_u64(row.ram_bytes, sysio_bytes);
});

// Re-read to get updated value for set_resource_limits
sysio_res = sysioreslimit.get(sysio_res_key);

// Update the RAM allocation, sysio is a system account so -1, -1 for net and cpu to maintain unlimited.
set_resource_limits(sysio_account, sysio_res.ram_bytes, -1, -1);

// Finally, record the node owner entry with the new fields
nodeowners.emplace(get_self(), node_key, roa::nodeowners{
.owner = owner,
Expand Down Expand Up @@ -1030,8 +1002,9 @@ namespace sysio {
// Ensure the contract is active
check(state.is_active, "Contract not active yet.");

// Same fractions/rounding as activateroa's reserve sizing (shared helper).
int64_t allocation_amount = tier_sys_allocation(tier, state.total_sys.amount);
// Same fractions/rounding as activateroa's reserve sizing, net of sysio's carve-out (shared helpers).
int64_t allocation_amount = tier_sys_allocation(tier, state.total_sys.amount)
- tier_sysio_share(tier, state.total_sys.amount);
return asset(allocation_amount, state.total_sys.symbol);
};

Expand Down
15 changes: 9 additions & 6 deletions contracts/sysio.roa/sysio.roa.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ namespace sysio {
/**
* @brief Initializes sysio.roa, should be called as last step in Bios Boot Sequence, activating the ROA resource management system.
*
* Reserves every node-owner tier slot and carves 1/10 of each tier-1 slot's allocation out to
* `sysio` up front as the account-creation RAM pool; node owners later register against the rest.
*
* @param total_sys The total starting SYS of the network.
* @param bytes_per_unit The amount of bytes .0001 SYS is worth, set in roastate table. If SYS precision is different, same concept applies, the single smallest unit of the core token.
*/
Expand Down Expand Up @@ -273,7 +276,7 @@ namespace sysio {
struct [[sysio::table("nodeowners")]] nodeowners {
name owner; // Node Owners account name.
uint8_t tier; // Represents what tier they hold: 1, 2, or 3
asset total_sys; // Total SYS alloted based on tier.
asset total_sys; // SYS alloted based on tier, net of sysio's tier-1 carve-out.
asset allocated_sys; // Total SYS allocated via policies they issued.
asset allocated_bw; // Total SYS allocated to CPU / NET.
asset allocated_ram; // Total SYS allocated to RAM.
Expand Down Expand Up @@ -452,11 +455,10 @@ namespace sysio {

/**
* @brief Registers 'owner' as a Node Owner scoped by network_gen, granting the tier's SYS
* allotment and contributing 10% of it to the network RAM pool.
* allotment (net of sysio's tier-1 share, which activateroa already carved out).
*
* Every tier gets a `nodeowners` row (the budget and the membership that gates policy
* issuance), a reslimit row, and a policy granting 10% of the tier allocation to `sysio`
* for the account-creation RAM pool.
* issuance) and a reslimit row.
*
* Only tier 1 additionally gets a personal self-issued policy. It is the only tier that can
* call `newuser`, whose `sponsors` / `sponsorcount` rows are the sole writes in this
Expand Down Expand Up @@ -519,9 +521,10 @@ namespace sysio {


/**
* @brief A simple getter for totall allotted SYS based on tier number: 1, 2, 3. Matches rounding and logic used in activation.
* @brief A node owner's SYS budget for tier 1, 2, or 3: the tier allocation, net of sysio's
* carve-out for tier 1. Matches the rounding activateroa uses to size the reserve.
*
* @return An asset containing the amount of SYS this tier gets
* @return An asset containing the amount of SYS a node owner of this tier may issue
*/
asset get_allocation_for_tier(uint8_t tier);

Expand Down
Binary file modified contracts/sysio.roa/sysio.roa.wasm
Binary file not shown.
Loading
Loading