Wire Sysio Wire Sysion 1.0.0
Loading...
Searching...
No Matches
powerup.cpp
Go to the documentation of this file.
2#include <sysio/action.hpp>
4#include <algorithm>
5#include <cmath>
6
7namespace sysiosystem {
8
9void update_weight(time_point_sec now, powerup_state_resource& res, int64_t& delta_available);
10
17void update_utilization(time_point_sec now, powerup_state_resource& res);
18
19void system_contract::adjust_resources(name payer, name account, symbol core_symbol, int64_t net_delta,
20 int64_t cpu_delta, bool must_not_be_managed) {
21 if (!net_delta && !cpu_delta)
22 return;
23
24 user_resources_table totals_tbl(get_self(), account.value);
25 auto tot_itr = totals_tbl.find(account.value);
26 if (tot_itr == totals_tbl.end()) {
27 tot_itr = totals_tbl.emplace(payer, [&](auto& tot) {
28 tot.owner = account;
29 tot.net_weight = asset{ net_delta, core_symbol };
30 tot.cpu_weight = asset{ cpu_delta, core_symbol };
31 });
32 } else {
33 totals_tbl.modify(tot_itr, same_payer, [&](auto& tot) {
34 tot.net_weight.amount += net_delta;
35 tot.cpu_weight.amount += cpu_delta;
36 });
37 }
38 check(0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth");
39 check(0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth");
40
41 {
42 bool ram_managed = false;
43 bool net_managed = false;
44 bool cpu_managed = false;
45
46 auto voter_itr = _voters.find(account.value);
47 if (voter_itr != _voters.end()) {
48 ram_managed = has_field(voter_itr->flags1, voter_info::flags1_fields::ram_managed);
49 net_managed = has_field(voter_itr->flags1, voter_info::flags1_fields::net_managed);
50 cpu_managed = has_field(voter_itr->flags1, voter_info::flags1_fields::cpu_managed);
51 }
52
53 if (must_not_be_managed)
54 sysio::check(!net_managed && !cpu_managed, "something is managed which shouldn't be");
55
56 if (!(net_managed && cpu_managed)) {
57 int64_t ram_bytes, net, cpu;
58 get_resource_limits(account, ram_bytes, net, cpu);
59 set_resource_limits(
60 account, ram_managed ? ram_bytes : std::max(tot_itr->ram_bytes + ram_gift_bytes, ram_bytes),
61 net_managed ? net : tot_itr->net_weight.amount, cpu_managed ? cpu : tot_itr->cpu_weight.amount);
62 }
63 }
64
65 if (tot_itr->is_empty()) {
66 totals_tbl.erase(tot_itr);
67 }
68} // system_contract::adjust_resources
69
70void system_contract::process_powerup_queue(time_point_sec now, symbol core_symbol, powerup_state& state,
71 powerup_order_table& orders, uint32_t max_items, int64_t& net_delta_available,
72 int64_t& cpu_delta_available) {
73 update_utilization(now, state.net);
74 update_utilization(now, state.cpu);
75 auto idx = orders.get_index<"byexpires"_n>();
76 while (max_items--) {
77 auto it = idx.begin();
78 if (it == idx.end() || it->expires > now)
79 break;
80 net_delta_available += it->net_weight;
81 cpu_delta_available += it->cpu_weight;
82 adjust_resources(get_self(), it->owner, core_symbol, -it->net_weight, -it->cpu_weight);
83 idx.erase(it);
84 }
85 state.net.utilization -= net_delta_available;
86 state.cpu.utilization -= cpu_delta_available;
87 update_weight(now, state.net, net_delta_available);
88 update_weight(now, state.cpu, cpu_delta_available);
89}
90
92 if (now >= res.target_timestamp) {
94 } else {
96 int128_t(res.target_weight_ratio - res.initial_weight_ratio) *
97 (now.utc_seconds - res.initial_timestamp.utc_seconds) /
98 (res.target_timestamp.utc_seconds - res.initial_timestamp.utc_seconds);
99 }
100 int64_t new_weight = res.assumed_stake_weight * int128_t(powerup_frac) / res.weight_ratio - res.assumed_stake_weight;
101 delta_available += new_weight - res.weight;
102 res.weight = new_weight;
103}
104
106 if (now <= res.utilization_timestamp) return;
107
108 if (res.utilization >= res.adjusted_utilization) {
110 } else {
112 int64_t delta = diff * std::exp(-double(now.utc_seconds - res.utilization_timestamp.utc_seconds) / double(res.decay_secs));
113 delta = std::clamp( delta, 0ll, diff);
114 res.adjusted_utilization = res.utilization + delta;
115 }
116 res.utilization_timestamp = now;
117}
118
120 require_auth(get_self());
121 time_point_sec now = sysio::current_time_point();
122 auto core_symbol = get_core_symbol();
123 powerup_state_singleton state_sing{ get_self(), 0 };
124 auto state = state_sing.get_or_default();
125
126 sysio::check(sysio::is_account(reserve_account), "sysio.reserv account must first be created"); // cspell:disable-line
127
128 int64_t net_delta_available = 0;
129 int64_t cpu_delta_available = 0;
130 if (state_sing.exists()) {
131 update_utilization(now, state.net);
132 update_utilization(now, state.cpu);
133 update_weight(now, state.net, net_delta_available);
134 update_weight(now, state.cpu, cpu_delta_available);
135 } else {
136 state.net.utilization_timestamp = now;
137 state.cpu.utilization_timestamp = now;
138 }
139
140 auto is_default_asset = []( const sysio::asset& a ) -> bool {
141 return a.amount == 0 && a.symbol == symbol{};
142 };
143
144 auto update = [&](auto& state, auto& args) {
145 if (!args.current_weight_ratio) {
146 if (state.weight_ratio) {
147 *args.current_weight_ratio = state.weight_ratio;
148 } else {
149 *args.current_weight_ratio = state.initial_weight_ratio;
150 }
151 }
152
153 if (!args.target_weight_ratio) {
154 *args.target_weight_ratio = state.target_weight_ratio;
155 }
156
157 if (!args.assumed_stake_weight) {
158 sysio::check(state.assumed_stake_weight != 0, "assumed_stake_weight does not have a default value");
159 *args.assumed_stake_weight = state.assumed_stake_weight;
160 }
161
162 if (*args.current_weight_ratio == *args.target_weight_ratio) {
163 *args.target_timestamp = now;
164 } else {
165 if (!args.target_timestamp) {
166 sysio::check(state.target_timestamp.utc_seconds != 0, "target_timestamp does not have a default value");
167 *args.target_timestamp = state.target_timestamp;
168 }
169 sysio::check(*args.target_timestamp > now, "target_timestamp must be in the future");
170 }
171
172 if (!args.exponent) {
173 *args.exponent = state.exponent;
174 }
175
176 if (!args.decay_secs) {
177 *args.decay_secs = state.decay_secs;
178 }
179
180 if (!args.max_price) {
181 sysio::check(!is_default_asset(state.max_price), "max_price does not have a default value");
182 *args.max_price = state.max_price;
183 }
184
185 if (!args.min_price) {
186 if (is_default_asset(state.min_price)) {
187 *args.min_price = *args.max_price; // just to copy symbol of max_price
188 args.min_price->amount = 0; // min_price has a default of zero.
189 } else {
190 *args.min_price = state.min_price;
191 }
192 }
193
194 sysio::check(*args.current_weight_ratio > 0, "current_weight_ratio is too small");
195 sysio::check(*args.current_weight_ratio <= powerup_frac, "current_weight_ratio is too large");
196 sysio::check(*args.target_weight_ratio > 0, "target_weight_ratio is too small");
197 sysio::check(*args.target_weight_ratio <= *args.current_weight_ratio, "weight can't grow over time");
198 sysio::check(*args.assumed_stake_weight >= 1,
199 "assumed_stake_weight must be at least 1; a much larger value is recommended");
200 sysio::check(*args.assumed_stake_weight * int128_t(powerup_frac) / *args.target_weight_ratio <=
201 std::numeric_limits<int64_t>::max(),
202 "assumed_stake_weight/target_weight_ratio is too large");
203 sysio::check(*args.exponent >= 1.0, "exponent must be >= 1");
204 sysio::check(*args.decay_secs >= 1, "decay_secs must be >= 1");
205 sysio::check(args.max_price->symbol == core_symbol, "max_price doesn't match core symbol");
206 sysio::check(args.max_price->amount > 0, "max_price must be positive");
207 sysio::check(args.min_price->symbol == core_symbol, "min_price doesn't match core symbol");
208 sysio::check(args.min_price->amount >= 0, "min_price must be non-negative");
209 sysio::check(args.min_price->amount <= args.max_price->amount, "min_price cannot exceed max_price");
210 if (*args.exponent == 1.0) {
211 sysio::check(args.min_price->amount == args.max_price->amount, "min_price and max_price must be the same if the exponent is 1");
212 }
213
214 state.assumed_stake_weight = *args.assumed_stake_weight;
215 state.initial_weight_ratio = *args.current_weight_ratio;
216 state.target_weight_ratio = *args.target_weight_ratio;
217 state.initial_timestamp = now;
218 state.target_timestamp = *args.target_timestamp;
219 state.exponent = *args.exponent;
220 state.decay_secs = *args.decay_secs;
221 state.min_price = *args.min_price;
222 state.max_price = *args.max_price;
223 };
224
225 if (!args.powerup_days) {
226 *args.powerup_days = state.powerup_days;
227 }
228
229 if (!args.min_powerup_fee) {
230 sysio::check(!is_default_asset(state.min_powerup_fee), "min_powerup_fee does not have a default value");
231 *args.min_powerup_fee = state.min_powerup_fee;
232 }
233
234 sysio::check(*args.powerup_days > 0, "powerup_days must be > 0");
235 sysio::check(args.min_powerup_fee->symbol == core_symbol, "min_powerup_fee doesn't match core symbol");
236 sysio::check(args.min_powerup_fee->amount > 0, "min_powerup_fee must be positive");
237
238 state.powerup_days = *args.powerup_days;
239 state.min_powerup_fee = *args.min_powerup_fee;
240
241 update(state.net, args.net);
242 update(state.cpu, args.cpu);
243
244 update_weight(now, state.net, net_delta_available);
245 update_weight(now, state.cpu, cpu_delta_available);
246 sysio::check(state.net.weight >= state.net.utilization, "weight can't shrink below utilization");
247 sysio::check(state.cpu.weight >= state.cpu.utilization, "weight can't shrink below utilization");
248 state.net.adjusted_utilization = std::min(state.net.adjusted_utilization, state.net.weight);
249 state.cpu.adjusted_utilization = std::min(state.cpu.adjusted_utilization, state.cpu.weight);
250
251 adjust_resources(get_self(), reserve_account, core_symbol, net_delta_available, cpu_delta_available, true);
252 state_sing.set(state, get_self());
253}
254
263 if( utilization_increase <= 0 ) return 0;
264
265 // Let p(u) = price as a function of the utilization fraction u which is defined for u in [0.0, 1.0].
266 // Let f(u) = integral of the price function p(x) from x = 0.0 to x = u, again defined for u in [0.0, 1.0].
267
268 // In particular we choose f(u) = min_price * u + ((max_price - min_price) / exponent) * (u ^ exponent).
269 // And so p(u) = min_price + (max_price - min_price) * (u ^ (exponent - 1.0)).
270
271 // Returns f(double(end_utilization)/state.weight) - f(double(start_utilization)/state.weight) which is equivalent to
272 // the integral of p(x) from x = double(start_utilization)/state.weight to x = double(end_utilization)/state.weight.
273 // @pre 0 <= start_utilization <= end_utilization <= state.weight
274 auto price_integral_delta = [&state](int64_t start_utilization, int64_t end_utilization) -> double {
275 double coefficient = (state.max_price.amount - state.min_price.amount) / state.exponent;
276 double start_u = double(start_utilization) / state.weight;
277 double end_u = double(end_utilization) / state.weight;
278 return state.min_price.amount * end_u - state.min_price.amount * start_u +
279 coefficient * std::pow(end_u, state.exponent) - coefficient * std::pow(start_u, state.exponent);
280 };
281
282 // Returns p(double(utilization)/state.weight).
283 // @pre 0 <= utilization <= state.weight
284 auto price_function = [&state](int64_t utilization) -> double {
285 double price = state.min_price.amount;
286 // state.exponent >= 1.0, therefore the exponent passed into std::pow is >= 0.0.
287 // Since the exponent passed into std::pow could be 0.0 and simultaneously so could double(utilization)/state.weight,
288 // the safest thing to do is handle that as a special case explicitly rather than relying on std::pow to return 1.0
289 // instead of triggering a domain error.
290 double new_exponent = state.exponent - 1.0;
291 if (new_exponent <= 0.0) {
292 return state.max_price.amount;
293 } else {
294 price += (state.max_price.amount - state.min_price.amount) * std::pow(double(utilization) / state.weight, new_exponent);
295 }
296
297 return price;
298 };
299
300 double fee = 0.0;
301 int64_t start_utilization = state.utilization;
302 int64_t end_utilization = start_utilization + utilization_increase;
303
304 if (start_utilization < state.adjusted_utilization) {
305 fee += price_function(state.adjusted_utilization) *
306 std::min(utilization_increase, state.adjusted_utilization - start_utilization) / state.weight;
307 start_utilization = state.adjusted_utilization;
308 }
309
310 if (start_utilization < end_utilization) {
311 fee += price_integral_delta(start_utilization, end_utilization);
312 }
313
314 return std::ceil(fee);
315}
316
318 require_auth(user);
319 powerup_state_singleton state_sing{ get_self(), 0 };
320 powerup_order_table orders{ get_self(), 0 };
321 sysio::check(state_sing.exists(), "powerup hasn't been initialized");
322 auto state = state_sing.get();
323 time_point_sec now = sysio::current_time_point();
324 auto core_symbol = get_core_symbol();
325
326 int64_t net_delta_available = 0;
327 int64_t cpu_delta_available = 0;
328 process_powerup_queue(now, core_symbol, state, orders, max, net_delta_available, cpu_delta_available);
329
330 adjust_resources(get_self(), reserve_account, core_symbol, net_delta_available, cpu_delta_available, true);
331 state_sing.set(state, get_self());
332}
333
334void system_contract::powerup(const name& payer, const name& receiver, uint32_t days, int64_t net_frac, int64_t cpu_frac,
335 const asset& max_payment) {
336 require_auth(payer);
337 powerup_state_singleton state_sing{ get_self(), 0 };
338 powerup_order_table orders{ get_self(), 0 };
339 sysio::check(state_sing.exists(), "powerup hasn't been initialized");
340 auto state = state_sing.get();
341 time_point_sec now = sysio::current_time_point();
342 auto core_symbol = get_core_symbol();
343 sysio::check(max_payment.symbol == core_symbol, "max_payment doesn't match core symbol");
344 sysio::check(days == state.powerup_days, "days doesn't match configuration");
345 sysio::check(net_frac >= 0, "net_frac can't be negative");
346 sysio::check(cpu_frac >= 0, "cpu_frac can't be negative");
347 sysio::check(net_frac <= powerup_frac, "net can't be more than 100%");
348 sysio::check(cpu_frac <= powerup_frac, "cpu can't be more than 100%");
349
350 int64_t net_delta_available = 0;
351 int64_t cpu_delta_available = 0;
352 process_powerup_queue(now, core_symbol, state, orders, 2, net_delta_available, cpu_delta_available);
353
354 sysio::asset fee{ 0, core_symbol };
355 auto process = [&](int64_t frac, int64_t& amount, powerup_state_resource& state) {
356 if (!frac)
357 return;
358 amount = int128_t(frac) * state.weight / powerup_frac;
359 sysio::check(state.weight, "market doesn't have resources available");
360 sysio::check(state.utilization + amount <= state.weight, "market doesn't have enough resources available");
361 int64_t f = calc_powerup_fee(state, amount);
362 sysio::check(f > 0, "calculated fee is below minimum; try powering up with more resources");
363 fee.amount += f;
364 state.utilization += amount;
365 };
366
367 int64_t net_amount = 0;
368 int64_t cpu_amount = 0;
369 process(net_frac, net_amount, state.net);
370 process(cpu_frac, cpu_amount, state.cpu);
371 if (fee > max_payment) {
372 std::string error_msg = "max_payment is less than calculated fee: ";
373 error_msg += fee.to_string();
374 sysio::check(false, error_msg);
375 }
376 sysio::check(fee >= state.min_powerup_fee, "calculated fee is below minimum; try powering up with more resources");
377
378 orders.emplace(payer, [&](auto& order) {
379 order.id = orders.available_primary_key();
380 order.owner = receiver;
381 order.net_weight = net_amount;
382 order.cpu_weight = cpu_amount;
383 order.expires = now + sysio::days(days);
384 });
385 net_delta_available -= net_amount;
386 cpu_delta_available -= cpu_amount;
387
388 adjust_resources(payer, receiver, core_symbol, net_amount, cpu_amount, true);
389 adjust_resources(get_self(), reserve_account, core_symbol, net_delta_available, cpu_delta_available, true);
390 channel_to_rex(payer, fee, true);
391 state_sing.set(state, get_self());
392
393 // inline noop action
394 powup_results::powupresult_action powupresult_act{ reserve_account, std::vector<sysio::permission_level>{ } };
395 powupresult_act.send( fee, net_amount, cpu_amount );
396}
397
398} // namespace sysiosystem
action_wrapper<"powupresult"_n, &powup_results::powupresult > powupresult_action
void powerupexec(const name &user, uint16_t max)
Definition powerup.cpp:317
void powerup(const name &payer, const name &receiver, uint32_t days, int64_t net_frac, int64_t cpu_frac, const asset &max_payment)
Definition powerup.cpp:334
static symbol get_core_symbol(name system_account="sysio"_n)
void cfgpowerup(powerup_config &args)
Definition powerup.cpp:119
static constexpr sysio::name reserve_account
void diff(const std::string &a, const std::string &b)
Definition jmp.cpp:18
constexpr microseconds days(int64_t d)
Definition time.hpp:36
void update_utilization(time_point_sec now, powerup_state_resource &res)
Definition powerup.cpp:105
constexpr int64_t powerup_frac
sysio::singleton<"powup.state"_n, powerup_state > powerup_state_singleton
void update_weight(time_point_sec now, powerup_state_resource &res, int64_t &delta_available)
Definition powerup.cpp:91
sysio::multi_index< "userres"_n, user_resources > user_resources_table
int64_t calc_powerup_fee(const powerup_state_resource &state, int64_t utilization_increase)
Definition powerup.cpp:262
sysio::multi_index< "powup.order"_n, powerup_order, indexed_by<"byowner"_n, const_mem_fun< powerup_order, uint64_t, &powerup_order::by_owner > >, indexed_by<"byexpires"_n, const_mem_fun< powerup_order, uint64_t, &powerup_order::by_expires > > > powerup_order_table
const GenericPointer< typename T::ValueType > T2 T::AllocatorType & a
Definition pointer.h:1181
unsigned short uint16_t
Definition stdint.h:125
signed __int64 int64_t
Definition stdint.h:135
unsigned int uint32_t
Definition stdint.h:126
Immutable except for fc::from_variant.
Definition name.hpp:43
powerup_config_resource cpu
powerup_config_resource net
std::optional< asset > min_powerup_fee
std::optional< uint32_t > powerup_days