diff --git a/contracts/eosio.system/delegate_bandwidth.cpp b/contracts/eosio.system/delegate_bandwidth.cpp index 095374445e35ef1839a032215363adcdd21a4100..851bd81c8f4ae115dd431b67c4819e3d152737b9 100644 --- a/contracts/eosio.system/delegate_bandwidth.cpp +++ b/contracts/eosio.system/delegate_bandwidth.cpp @@ -71,6 +71,10 @@ namespace eosiosystem { EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(amount) ) }; + /** + * These tables are designed to be constructed in the scope of the relevant user, this + * facilitates simpler API for per-user queries + */ typedef eosio::multi_index< N(userres), user_resources> user_resources_table; typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table; typedef eosio::multi_index< N(refunds), refund_request> refunds_table; @@ -93,15 +97,12 @@ namespace eosiosystem { INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, { payer, N(eosio), quant, std::string("buy ram") } ); - global_state_singleton gstate_table( _self, _self ); - auto gstate = gstate_table.exists() ? gstate_table.get() : get_default_parameters(); - const double system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - const double unstaked_token_supply = system_token_supply - gstate.total_storage_stake.amount; + const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount; const double E = quant.amount; const double R = unstaked_token_supply - E; - const double C = gstate.free_ram(); //free_ram; + const double C = _gstate.free_ram(); //free_ram; const double F = .10; /// 10% reserve ratio pricing, assumes only 10% of tokens will ever want to stake for ram const double ONE(1.0); @@ -111,10 +112,8 @@ namespace eosiosystem { eosio_assert( bytes_out > 0, "must reserve a positive amount" ); - gstate.total_storage_bytes_reserved += uint64_t(bytes_out); - gstate.total_storage_stake.amount += quant.amount; - - gstate_table.set( gstate, _self ); + _gstate.total_storage_bytes_reserved += uint64_t(bytes_out); + _gstate.total_storage_stake.amount += quant.amount; user_resources_table userres( _self, receiver ); auto res_itr = userres.find( receiver ); @@ -143,14 +142,11 @@ namespace eosiosystem { eosio_assert( res_itr != userres.end(), "no resource row" ); eosio_assert( res_itr->storage_bytes >= bytes, "insufficient quota" ); - global_state_singleton gstate_table( _self, _self ); - auto gstate = gstate_table.exists() ? gstate_table.get() : get_default_parameters(); - const double system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - const double unstaked_token_supply = system_token_supply - gstate.total_storage_stake.amount; + const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount; const double R = unstaked_token_supply; - const double C = gstate.free_ram() + bytes; + const double C = _gstate.free_ram() + bytes; const double F = .10; const double T = bytes; const double ONE(1.0); @@ -163,10 +159,8 @@ namespace eosiosystem { int64_t tokens_out = int64_t(E); eosio_assert( tokens_out > 0, "must free at least one token" ); - gstate.total_storage_bytes_reserved -= bytes; - gstate.total_storage_stake.amount -= tokens_out; - - gstate_table.set( gstate, _self ); + _gstate.total_storage_bytes_reserved -= bytes; + _gstate.total_storage_stake.amount -= tokens_out; userres.modify( res_itr, account, [&]( auto& res ) { res.storage_bytes -= bytes; diff --git a/contracts/eosio.system/eosio.system.cpp b/contracts/eosio.system/eosio.system.cpp index 08a52754a1d29b6b19c22aa02ed8d82532124582..e8908d6a270b4046371d7db7ce9355c9d51ab924 100644 --- a/contracts/eosio.system/eosio.system.cpp +++ b/contracts/eosio.system/eosio.system.cpp @@ -5,20 +5,35 @@ #include "producer_pay.cpp" #include "voting.cpp" +system_contract::system_contract( account_name s ) +:native(s), + _voters(_self,_self), + _producers(_self,_self), + _global(_self,_self) +{ + _gstate = _global.exists() ? _global.get() : get_default_parameters(); +} + +system_contract::~system_contract() { + _global.set( _gstate, _self ); + eosio_exit(0); +} + + EOSIO_ABI( eosiosystem::system_contract, - (setparams) - // delegate_bandwith.cpp - (delegatebw)(undelegatebw)(refund) - (buyram)(sellram) - (regproxy) - // voting.cpp - (unregproxy)(regproducer)(unregprod)(voteproducer) - // producer_pay.cpp - (claimrewards) - // native.hpp - //XXX - (onblock) - (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(postrecovery)(passrecovery)(vetorecovery)(onerror)(canceldelay) - // defined in eosio.system.hpp - (nonce) + (setparams) + // delegate_bandwith.cpp + (delegatebw)(undelegatebw)(refund) + (buyram)(sellram) + (regproxy) + // voting.cpp + (unregproxy)(regproducer)(unregprod)(voteproducer) + // producer_pay.cpp + (claimrewards) + // native.hpp + //XXX + (onblock) + (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(postrecovery)(passrecovery)(vetorecovery)(onerror)(canceldelay) + // defined in eosio.system.hpp + (nonce) ) diff --git a/contracts/eosio.system/eosio.system.hpp b/contracts/eosio.system/eosio.system.hpp index 48fb5ca041eb57d5d18df89507f3a90a1c302bea..e08eb698d353f2a3147c63c139d4f8fc6fd07ce5 100644 --- a/contracts/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/eosio.system.hpp @@ -65,6 +65,40 @@ namespace eosiosystem { (time_became_active)(last_produced_block_time) ) }; + struct voter_info { + account_name owner = 0; /// the voter + account_name proxy = 0; /// the proxy set by the voter, if any + std::vector producers; /// the producers approved by this voter if no proxy set + uint64_t staked; + + /** + * Every time a vote is cast we must first "undo" the last vote weight, before casting the + * new vote weight. Vote weight is calculated as: + * + * stated.amount * 2 ^ ( weeks_since_launch/weeks_per_year) + */ + double last_vote_weight; /// the vote weight cast the last time the vote was updated + + /** + * Total vote weight delegated to this voter. + */ + double proxied_vote_weight= 0; /// the total vote weight delegated to this voter as a proxy + bool is_proxy = 0; /// whether the voter is a proxy for others + + + uint32_t deferred_trx_id = 0; /// the ID of the 3-day delay deferred transaction + time last_unstake_time = 0; /// the time when the deferred_trx_id was sent + eosio::asset unstaking; /// the total unstaking (pending 3 day delay) + + uint64_t primary_key()const { return owner; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(deferred_trx_id)(last_unstake_time)(unstaking) ) + }; + + typedef eosio::multi_index< N(voters), voter_info> voters_table; + + typedef eosio::multi_index< N(producerinfo), producer_info, indexed_by > > producers_table; @@ -76,8 +110,16 @@ namespace eosiosystem { static constexpr uint64_t system_token_symbol = S(4,EOS); class system_contract : public native { + private: + voters_table _voters; + producers_table _producers; + global_state_singleton _global; + + eosio_global_state _gstate; + public: - using native::native; + system_contract( account_name s ); + ~system_contract(); // Actions: void onblock( const block_id_type&, uint32_t timestamp_slot, account_name producer ); diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index 889f941bd8af9c634e517a99580781bb6f390d15..c59419088c4f75643f57ac6a536c274c1cdbfb86 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -30,26 +30,6 @@ namespace eosiosystem { static constexpr uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year static constexpr uint32_t blocks_per_producer = 12; - struct voter_info { - account_name owner = 0; - account_name proxy = 0; - time last_update = 0; - uint32_t is_proxy = 0; - eosio::asset staked; /// total staked across all delegations - eosio::asset unstaking; - eosio::asset unstake_per_week; - uint128_t proxied_votes = 0; - std::vector producers; - uint32_t deferred_trx_id = 0; - time last_unstake_time = 0; //uint32 - - uint64_t primary_key()const { return owner; } - - // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(last_update)(is_proxy)(staked)(unstaking)(unstake_per_week)(proxied_votes)(producers)(deferred_trx_id)(last_unstake_time) ) - }; - - typedef eosio::multi_index< N(voters), voter_info> voters_table; /** * This method will create a producer_config and producer_info object for 'producer' @@ -64,131 +44,73 @@ namespace eosiosystem { //eosio::print("produce_key: ", producer_key.size(), ", sizeof(public_key): ", sizeof(public_key), "\n"); require_auth( producer ); - producers_table producers_tbl( _self, _self ); - auto prod = producers_tbl.find( producer ); + auto prod = _producers.find( producer ); - if ( prod != producers_tbl.end() ) { + if ( prod != _producers.end() ) { if( producer_key != prod->producer_key ) { - producers_tbl.modify( prod, producer, [&]( producer_info& info ){ + _producers.modify( prod, producer, [&]( producer_info& info ){ info.producer_key = producer_key; - }); + }); } } else { - producers_tbl.emplace( producer, [&]( producer_info& info ){ + _producers.emplace( producer, [&]( producer_info& info ){ info.owner = producer; info.total_votes = 0; info.producer_key = producer_key; - }); + }); } } void system_contract::unregprod( const account_name producer ) { require_auth( producer ); - producers_table producers_tbl( _self, _self ); - auto prod = producers_tbl.find( producer ); + auto prod = _producers.find( producer ); eosio_assert( prod != producers_tbl.end(), "producer not found" ); - producers_tbl.modify( prod, 0, [&]( producer_info& info ){ - info.producer_key = public_key(); - }); + _producers.modify( prod, 0, [&]( producer_info& info ){ + info.producer_key = public_key(); + }); } void system_contract::adjust_voting_power( account_name acnt, int64_t delta ) { - voters_table voters_tbl( _self, _self ); - auto voter = voters_tbl.find( acnt ); - - if( voter == voters_tbl.end() ) { - voter = voters_tbl.emplace( acnt, [&]( voter_info& a ) { - a.owner = acnt; - a.last_update = now(); - a.staked.amount = delta; - eosio_assert( a.staked.amount >= 0, "underflow" ); - }); - } else { - voters_tbl.modify( voter, 0, [&]( auto& av ) { - av.last_update = now(); - av.staked.amount += delta; - eosio_assert( av.staked.amount >= 0, "underflow" ); - }); - } + auto voter = _voters.find( acnt ); - const std::vector* producers = nullptr; - if ( voter->proxy ) { - /* TODO: disabled until we can switch proxied votes to double - auto proxy = voters_tbl.find( voter->proxy ); - eosio_assert( proxy != voters_tbl.end(), "selected proxy not found" ); //data corruption - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes += delta; } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - */ + if( voter == _voters.end() ) { + voter = _voters.emplace( acnt, [&]( voter_info& a ) { + a.owner = acnt; + a.staked.amount = delta; + }); + } - } else { - producers = &voter->producers; - } + auto weight = int64_t(now() / (seconds_per_day * 7)) / double( 52 ); + double new_vote_weight = double(voter->staked.amount + delta) * std::pow(2,weight) + voter->proxied_vote_weight; - if ( producers ) { - producers_table producers_tbl( _self, _self ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes += delta; - }); - } - } - } + auto delta_vote_weight = new_vote_weight - voter->last_vote_weight; - /* - void system_contract::decrease_voting_power( account_name acnt, const eosio::asset& amount ) { - require_auth( acnt ); - voters_table voters_tbl( _self, _self ); - auto voter = voters_tbl.find( acnt ); - eosio_assert( voter != voters_tbl.end(), "stake not found" ); + _voters.modify( voter, 0, [&]( auto& av ) { + av.staked.amount += delta; + av.last_vote_weight = new_vote_weight; + eosio_assert( av.staked.amount >= 0, "underflow" ); + }); - if ( 0 < amount.amount ) { - eosio_assert( amount <= voter->staked, "cannot unstake more than total stake amount" ); - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked -= amount; - a.last_update = now(); - }); + const std::vector* producers = nullptr; + if ( voter->proxy ) { + auto proxy = voters_tbl.find( voter->proxy ); + eosio_assert( proxy != voters_tbl.end(), "selected proxy not found" ); - const std::vector* producers = nullptr; - if ( voter->proxy ) { - auto proxy = voters_tbl.find( voter->proxy ); - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes -= uint64_t(amount.amount); } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - } else { - producers = &voter->producers; - } + _voters.modify( proxy, 0, [&](voter_info& a) { + a.proxied_vote_weight += delta_vote_weight; + } ); - if ( producers ) { - producers_table producers_tbl( _self, _self ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes -= uint64_t(amount.amount); - }); - } - } } else { - if (voter->deferred_trx_id) { - //XXX cancel_deferred_transaction(voter->deferred_trx_id); - } - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked += a.unstaking; - a.unstaking.amount = 0; - a.unstake_per_week.amount = 0; - a.deferred_trx_id = 0; - a.last_update = now(); + for( auto p : voter->producers ) { + auto prod = _producers.find( p ); + _producers.modify( prod, 0, [&]( auto& pro ) { + pro.total_votes += delta_vote_weight; }); + } } } - */ eosio_global_state system_contract::get_default_parameters() { eosio_global_state dp; @@ -196,6 +118,7 @@ namespace eosiosystem { return dp; } + eosio::asset system_contract::payment_per_block(uint32_t percent_of_max_inflation_rate) { const eosio::asset token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()); const double annual_rate = double(max_inflation_rate * percent_of_max_inflation_rate) / double(10000); @@ -205,8 +128,7 @@ namespace eosiosystem { } void system_contract::update_elected_producers(time cycle_time) { - producers_table producers_tbl( _self, _self ); - auto idx = producers_tbl.template get_index(); + auto idx = _producers.get_index(); eosio::producer_schedule schedule; schedule.producers.reserve(21); @@ -227,29 +149,25 @@ namespace eosiosystem { bytes packed_schedule = pack(schedule); set_active_producers( packed_schedule.data(), packed_schedule.size() ); - global_state_singleton gs( _self, _self ); - auto parameters = gs.exists() ? gs.get() : get_default_parameters(); - // not voted on - parameters.first_block_time_in_cycle = cycle_time; + _gstate.first_block_time_in_cycle = cycle_time; // derived parameters - auto half_of_percentage = parameters.percent_of_max_inflation_rate / 2; - auto other_half_of_percentage = parameters.percent_of_max_inflation_rate - half_of_percentage; - parameters.payment_per_block = payment_per_block(half_of_percentage); - parameters.payment_to_eos_bucket = payment_per_block(other_half_of_percentage); - parameters.blocks_per_cycle = blocks_per_producer * schedule.producers.size(); - - if ( parameters.max_storage_size < parameters.total_storage_bytes_reserved ) { - parameters.max_storage_size = parameters.total_storage_bytes_reserved; + auto half_of_percentage = _gstate.percent_of_max_inflation_rate / 2; + auto other_half_of_percentage = _gstate.percent_of_max_inflation_rate - half_of_percentage; + _gstate.payment_per_block = payment_per_block(half_of_percentage); + _gstate.payment_to_eos_bucket = payment_per_block(other_half_of_percentage); + _gstate.blocks_per_cycle = blocks_per_producer * schedule.producers.size(); + + if (_gstate.max_storage_size <_gstate.total_storage_bytes_reserved ) { + _gstate.max_storage_size =_gstate.total_storage_bytes_reserved; } - auto issue_quantity = parameters.blocks_per_cycle * (parameters.payment_per_block + parameters.payment_to_eos_bucket); + auto issue_quantity =_gstate.blocks_per_cycle * (_gstate.payment_per_block +_gstate.payment_to_eos_bucket); INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, {N(eosio), issue_quantity, std::string("producer pay")} ); - set_blockchain_parameters( parameters ); - gs.set( parameters, _self ); + set_blockchain_parameters( _gstate ); } /** @@ -259,12 +177,13 @@ namespace eosiosystem { * @pre voter must authorize this action * @pre voter must have previously staked some EOS for voting */ - void system_contract::voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ) { - require_auth( voter ); + void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { + require_auth( voter_name ); //validate input if ( proxy ) { eosio_assert( producers.size() == 0, "cannot vote for producers and proxy at same time" ); + eosio_assert( voter_name != proxy, "cannot proxy to self" ); require_recipient( proxy ); } else { eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); @@ -273,8 +192,56 @@ namespace eosiosystem { } } - voters_table voters_tbl( _self, _self ); - auto voter_it = voters_tbl.find( voter ); + auto voter = _voters.find(voter_name); + eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object + + auto weight = int64_t(now() / (seconds_per_day * 7)) / double( 52 ); + double new_vote_weight = double(voter->staked) * std::pow(2,weight) + voter->proxied_vote_weight; + + + flat_map producer_deltas; + for( const auto& p : voter->producers ) { + producer_deltas[p] -= voter->last_vote_weight; + } + + for( const auto& p : producers ) { + producer_deltas[p] += voter->last_vote_weight; + } + + if( voter->proxy ) { + if( voter->proxy != proxy ) { + + } else { + + } + } else { + + } + + + _voters.modify( voter, 0, [&]( auto& av ) { + av.last_vote_weight = new_vote_weight; + av.producers = producers; + av.proxy = proxy; + }); + + + + auto voter_it = _voters.find( voter ); + + /// remove all votes from existing producers or proxy + adjust_voting_power( voter, -voter_it->staked.amount ); + + /// update producer list + + + /// add all votes to new producers or proxy + adjust_voting_power( voter, voter_it->staked.amount ); + + + + + eosio_assert( 0 <= voter_it->staked.amount, "negative stake" ); eosio_assert( voter_it != voters_tbl.end() && ( 0 < voter_it->staked.amount || ( voter_it->is_proxy && 0 < voter_it->proxied_votes ) ), "no stake to vote" ); diff --git a/contracts/eosio.system/voting.hpp b/contracts/eosio.system/voting.hpp deleted file mode 100644 index acd7fc183b11d6d978d5e039dbca26edd0a12609..0000000000000000000000000000000000000000 --- a/contracts/eosio.system/voting.hpp +++ /dev/null @@ -1,531 +0,0 @@ -/** - * @file - * @copyright defined in eos/LICENSE.txt - */ -#pragma once -#include "common.hpp" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace eosiosystem { - using eosio::indexed_by; - using eosio::const_mem_fun; - using eosio::bytes; - using eosio::print; - using eosio::singleton; - using eosio::transaction; - - - template - class voting { - public: - static constexpr account_name system_account = SystemAccount; - using currency = typename common::currency; - using system_token_type = typename common::system_token_type; - using eosio_parameters = typename common::eosio_parameters; - using global_state_singleton = typename common::global_state_singleton; - - static constexpr uint32_t max_inflation_rate = common::max_inflation_rate; - static constexpr uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year - - struct producer_info { - account_name owner; - uint128_t total_votes = 0; - // eosio_parameters prefs; - eosio::bytes packed_key; /// a packed public key object - system_token_type per_block_payments; - time last_rewards_claim = 0; - time time_became_active = 0; - time last_produced_block_time = 0; - - uint64_t primary_key()const { return owner; } - uint128_t by_votes()const { return total_votes; } - bool active() const { return packed_key.size() == sizeof(public_key); } - - EOSLIB_SERIALIZE( producer_info, (owner)(total_votes)(packed_key) - (per_block_payments)(last_rewards_claim) - (time_became_active)(last_produced_block_time) ) - }; - - typedef eosio::multi_index< N(producerinfo), producer_info, - indexed_by > - > producers_table; - - - struct voter_info { - account_name owner = 0; - account_name proxy = 0; - time last_update = 0; - uint32_t is_proxy = 0; - system_token_type staked; - system_token_type unstaking; - system_token_type unstake_per_week; - uint128_t proxied_votes = 0; - std::vector producers; - uint32_t deferred_trx_id = 0; - time last_unstake_time = 0; //uint32 - - uint64_t primary_key()const { return owner; } - - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(last_update)(is_proxy)(staked)(unstaking)(unstake_per_week)(proxied_votes)(producers)(deferred_trx_id)(last_unstake_time) ) - }; - - typedef eosio::multi_index< N(voters), voter_info> voters_table; - - - static void increase_voting_power( account_name acnt, system_token_type amount ) { - voters_table voters_tbl( SystemAccount, SystemAccount ); - auto voter = voters_tbl.find( acnt ); - - if( voter == voters_tbl.end() ) { - voter = voters_tbl.emplace( acnt, [&]( voter_info& a ) { - a.owner = acnt; - a.last_update = now(); - a.staked = amount; - }); - } else { - voters_tbl.modify( voter, 0, [&]( auto& av ) { - av.last_update = now(); - av.staked += amount; - }); - } - - const std::vector* producers = nullptr; - if ( voter->proxy ) { - auto proxy = voters_tbl.find( voter->proxy ); - eosio_assert( proxy != voters_tbl.end(), "selected proxy not found" ); //data corruption - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes += amount.quantity; } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - } else { - producers = &voter->producers; - } - - if ( producers ) { - producers_table producers_tbl( SystemAccount, SystemAccount ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes += amount.quantity; - }); - } - } - } - - static void decrease_voting_power( account_name acnt, system_token_type amount ) { - require_auth( acnt ); - voters_table voters_tbl( SystemAccount, SystemAccount ); - auto voter = voters_tbl.find( acnt ); - eosio_assert( voter != voters_tbl.end(), "stake not found" ); - - if ( 0 < amount.quantity ) { - eosio_assert( amount <= voter->staked, "cannot unstake more than total stake amount" ); - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked -= amount; - a.last_update = now(); - }); - - const std::vector* producers = nullptr; - if ( voter->proxy ) { - auto proxy = voters_tbl.find( voter->proxy ); - voters_tbl.modify( proxy, 0, [&](voter_info& a) { a.proxied_votes -= amount.quantity; } ); - if ( proxy->is_proxy ) { //only if proxy is still active. if proxy has been unregistered, we update proxied_votes, but don't propagate to producers - producers = &proxy->producers; - } - } else { - producers = &voter->producers; - } - - if ( producers ) { - producers_table producers_tbl( SystemAccount, SystemAccount ); - for( auto p : *producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& v ) { - v.total_votes -= amount.quantity; - }); - } - } - } else { - if (voter->deferred_trx_id) { - //XXX cancel_deferred_transaction(voter->deferred_trx_id); - } - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.staked += a.unstaking; - a.unstaking.quantity = 0; - a.unstake_per_week.quantity = 0; - a.deferred_trx_id = 0; - a.last_update = now(); - }); - } - } - - static system_token_type payment_per_block(uint32_t percent_of_max_inflation_rate) { - const system_token_type token_supply = currency::get_total_supply(); - const double annual_rate = double(max_inflation_rate * percent_of_max_inflation_rate) / double(10000); - double continuous_rate = std::log1p(annual_rate); - uint64_t payment = static_cast((continuous_rate * double(token_supply.quantity)) / double(blocks_per_year)); - return (system_token_type(payment)); - } - - static void update_elected_producers(time cycle_time); - -#if 0 - static void update_elected_producers(time cycle_time) { - producers_table producers_tbl( SystemAccount, SystemAccount ); - auto idx = producers_tbl.template get_index(); - - std::array max_block_net_usage; - std::array target_block_net_usage_pct; - std::array base_per_transaction_net_usage; - std::array max_transaction_net_usage; - std::array context_free_discount_net_usage_num; - std::array context_free_discount_net_usage_den; - - std::array max_block_cpu_usage; - std::array target_block_cpu_usage_pct; - std::array max_transaction_cpu_usage; - std::array base_per_transaction_cpu_usage; - std::array base_per_action_cpu_usage; - std::array base_setcode_cpu_usage; - std::array per_signature_cpu_usage; - std::array context_free_discount_cpu_usage_num; - std::array context_free_discount_cpu_usage_den; - - std::array max_transaction_lifetime; - std::array deferred_trx_expiration_window; - std::array max_transaction_delay; - std::array max_inline_action_size; - std::array max_inline_action_depth; - std::array max_authority_depth; - std::array max_generated_transaction_count; - - std::array max_storage_size; - std::array percent_of_max_inflation_rate; - std::array storage_reserve_ratio; - - std::vector schedule; - schedule.reserve(21); - size_t n = 0; - for ( auto it = idx.crbegin(); it != idx.crend() && n < 21 && 0 < it->total_votes; ++it ) { - if ( it->active() ) { - schedule.emplace_back(); - schedule.back().producer_name = it->owner; - eosio_assert( sizeof(schedule.back().block_signing_key) == it->packed_key.size(), "size mismatch" ); - std::copy( it->packed_key.begin(), it->packed_key.end(), schedule.back().block_signing_key.data ); - - max_block_net_usage[n] = it->prefs.max_block_net_usage; - target_block_net_usage_pct[n] = it->prefs.target_block_net_usage_pct; - max_transaction_net_usage[n] = it->prefs.max_transaction_net_usage; - base_per_transaction_net_usage[n] = it->prefs.base_per_transaction_net_usage; - context_free_discount_net_usage_num[n] = it->prefs.context_free_discount_net_usage_num; - context_free_discount_net_usage_den[n] = it->prefs.context_free_discount_net_usage_den; - - max_block_cpu_usage[n] = it->prefs.max_block_cpu_usage; - target_block_cpu_usage_pct[n] = it->prefs.target_block_cpu_usage_pct; - max_transaction_cpu_usage[n] = it->prefs.max_transaction_cpu_usage; - base_per_transaction_cpu_usage[n] = it->prefs.base_per_transaction_cpu_usage; - base_per_action_cpu_usage[n] = it->prefs.base_per_action_cpu_usage; - base_setcode_cpu_usage[n] = it->prefs.base_setcode_cpu_usage; - per_signature_cpu_usage[n] = it->prefs.per_signature_cpu_usage; - context_free_discount_cpu_usage_num[n] = it->prefs.context_free_discount_cpu_usage_num; - context_free_discount_cpu_usage_den[n] = it->prefs.context_free_discount_cpu_usage_den; - - max_transaction_lifetime[n] = it->prefs.max_transaction_lifetime; - deferred_trx_expiration_window[n] = it->prefs.deferred_trx_expiration_window; - max_transaction_delay[n] = it->prefs.max_transaction_delay; - max_inline_action_size[n] = it->prefs.max_inline_action_size; - max_inline_action_depth[n] = it->prefs.max_inline_action_depth; - max_authority_depth[n] = it->prefs.max_authority_depth; - max_generated_transaction_count[n] = it->prefs.max_generated_transaction_count; - - max_storage_size[n] = it->prefs.max_storage_size; - storage_reserve_ratio[n] = it->prefs.storage_reserve_ratio; - percent_of_max_inflation_rate[n] = it->prefs.percent_of_max_inflation_rate; - ++n; - } - } - if ( n == 0 ) { //no active producers with votes > 0 - return; - } - if ( 1 < n ) { - std::sort( max_block_net_usage.begin(), max_block_net_usage.begin()+n ); - std::sort( target_block_net_usage_pct.begin(), target_block_net_usage_pct.begin()+n ); - std::sort( max_transaction_net_usage.begin(), max_transaction_net_usage.begin()+n ); - std::sort( base_per_transaction_net_usage.begin(), base_per_transaction_net_usage.begin()+n ); - std::sort( context_free_discount_net_usage_num.begin(), context_free_discount_net_usage_num.begin()+n ); - std::sort( context_free_discount_net_usage_den.begin(), context_free_discount_net_usage_den.begin()+n ); - - std::sort( max_block_cpu_usage.begin(), max_block_cpu_usage.begin()+n ); - std::sort( target_block_cpu_usage_pct.begin(), target_block_cpu_usage_pct.begin()+n ); - std::sort( max_transaction_cpu_usage.begin(), max_transaction_cpu_usage.begin()+n ); - std::sort( base_per_transaction_cpu_usage.begin(), base_per_transaction_cpu_usage.begin()+n ); - std::sort( base_per_action_cpu_usage.begin(), base_per_action_cpu_usage.begin()+n ); - std::sort( base_setcode_cpu_usage.begin(), base_setcode_cpu_usage.begin()+n ); - std::sort( per_signature_cpu_usage.begin(), per_signature_cpu_usage.begin()+n ); - std::sort( context_free_discount_cpu_usage_num.begin(), context_free_discount_cpu_usage_num.begin()+n ); - std::sort( context_free_discount_cpu_usage_den.begin(), context_free_discount_cpu_usage_den.begin()+n ); - - std::sort( max_transaction_lifetime.begin(), max_transaction_lifetime.begin()+n ); - std::sort( deferred_trx_expiration_window.begin(), deferred_trx_expiration_window.begin()+n ); - std::sort( max_transaction_delay.begin(), max_transaction_delay.begin()+n ); - std::sort( max_inline_action_size.begin(), max_inline_action_size.begin()+n ); - std::sort( max_inline_action_depth.begin(), max_inline_action_depth.begin()+n ); - std::sort( max_authority_depth.begin(), max_authority_depth.begin()+n ); - std::sort( max_generated_transaction_count.begin(), max_generated_transaction_count.begin()+n ); - - std::sort( max_storage_size.begin(), max_storage_size.begin()+n ); - std::sort( storage_reserve_ratio.begin(), storage_reserve_ratio.begin()+n ); - std::sort( percent_of_max_inflation_rate.begin(), percent_of_max_inflation_rate.begin()+n ); - } - - bytes packed_schedule = pack(schedule); - set_active_producers( packed_schedule.data(), packed_schedule.size() ); - size_t median = n/2; - - auto parameters = global_state_singleton::exists() ? global_state_singleton::get() - : common::get_default_parameters(); - - parameters.max_block_net_usage = max_block_net_usage[median]; - parameters.target_block_net_usage_pct = target_block_net_usage_pct[median]; - parameters.max_transaction_net_usage = max_transaction_net_usage[median]; - parameters.base_per_transaction_net_usage = base_per_transaction_net_usage[median]; - parameters.context_free_discount_net_usage_num = context_free_discount_net_usage_num[median]; - parameters.context_free_discount_net_usage_den = context_free_discount_net_usage_den[median]; - - parameters.max_block_cpu_usage = max_block_cpu_usage[median]; - parameters.target_block_cpu_usage_pct = target_block_cpu_usage_pct[median]; - parameters.max_transaction_cpu_usage = max_transaction_cpu_usage[median]; - parameters.base_per_transaction_cpu_usage = base_per_transaction_cpu_usage[median]; - parameters.base_per_action_cpu_usage = base_per_action_cpu_usage[median]; - parameters.base_setcode_cpu_usage = base_setcode_cpu_usage[median]; - parameters.per_signature_cpu_usage = per_signature_cpu_usage[median]; - parameters.context_free_discount_cpu_usage_num = context_free_discount_cpu_usage_num[median]; - parameters.context_free_discount_cpu_usage_den = context_free_discount_cpu_usage_den[median]; - - parameters.max_transaction_lifetime = max_transaction_lifetime[median]; - parameters.deferred_trx_expiration_window = deferred_trx_expiration_window[median]; - parameters.max_transaction_delay = max_transaction_delay[median]; - parameters.max_inline_action_size = max_inline_action_size[median]; - parameters.max_inline_action_depth = max_inline_action_depth[median]; - parameters.max_authority_depth = max_authority_depth[median]; - parameters.max_generated_transaction_count = max_generated_transaction_count[median]; - - parameters.max_storage_size = max_storage_size[median]; - parameters.storage_reserve_ratio = storage_reserve_ratio[median]; - parameters.percent_of_max_inflation_rate = percent_of_max_inflation_rate[median]; - - // not voted on - parameters.first_block_time_in_cycle = cycle_time; - - // derived parameters - auto half_of_percentage = parameters.percent_of_max_inflation_rate / 2; - auto other_half_of_percentage = parameters.percent_of_max_inflation_rate - half_of_percentage; - parameters.payment_per_block = payment_per_block(half_of_percentage); - parameters.payment_to_eos_bucket = payment_per_block(other_half_of_percentage); - parameters.blocks_per_cycle = common::blocks_per_producer * schedule.size(); - - if ( parameters.max_storage_size < parameters.total_storage_bytes_reserved ) { - parameters.max_storage_size = parameters.total_storage_bytes_reserved; - } - - auto issue_quantity = parameters.blocks_per_cycle * (parameters.payment_per_block + parameters.payment_to_eos_bucket); - currency::inline_issue(SystemAccount, issue_quantity); - set_blockchain_parameters(parameters); - global_state_singleton::set(parameters); - } -#endif - - ACTION( SystemAccount, voteproducer ) { - account_name voter; - account_name proxy; - std::vector producers; - - EOSLIB_SERIALIZE( voteproducer, (voter)(proxy)(producers) ) - }; - - /** - * @pre vp.producers must be sorted from lowest to highest - * @pre if proxy is set then no producers can be voted for - * @pre every listed producer or proxy must have been previously registered - * @pre vp.voter must authorize this action - * @pre voter must have previously staked some EOS for voting - */ - static void on( const voteproducer& vp ) { - require_auth( vp.voter ); - - //validate input - if ( vp.proxy ) { - eosio_assert( vp.producers.size() == 0, "cannot vote for producers and proxy at same time" ); - require_recipient( vp.proxy ); - } else { - eosio_assert( vp.producers.size() <= 30, "attempt to vote for too many producers" ); - for( size_t i = 1; i < vp.producers.size(); ++i ) { - eosio_assert( vp.producers[i-1] < vp.producers[i], "producer votes must be unique and sorted" ); - } - } - - voters_table voters_tbl( SystemAccount, SystemAccount ); - auto voter = voters_tbl.find( vp.voter ); - - eosio_assert( voter != voters_tbl.end() && ( 0 < voter->staked.quantity || ( voter->is_proxy && 0 < voter->proxied_votes ) ), "no stake to vote" ); - if ( voter->is_proxy ) { - eosio_assert( vp.proxy == 0 , "account registered as a proxy is not allowed to use a proxy" ); - } - - //find old producers, update old proxy if needed - const std::vector* old_producers = nullptr; - if( voter->proxy ) { - if ( voter->proxy == vp.proxy ) { - return; // nothing changed - } - auto old_proxy = voters_tbl.find( voter->proxy ); - eosio_assert( old_proxy != voters_tbl.end(), "old proxy not found" ); //data corruption - voters_tbl.modify( old_proxy, 0, [&](auto& a) { a.proxied_votes -= voter->staked.quantity; } ); - if ( old_proxy->is_proxy ) { //if proxy stoped being proxy, the votes were already taken back from producers by on( const unregister_proxy& ) - old_producers = &old_proxy->producers; - } - } else { - old_producers = &voter->producers; - } - - //find new producers, update new proxy if needed - const std::vector* new_producers = nullptr; - if ( vp.proxy ) { - auto new_proxy = voters_tbl.find( vp.proxy ); - eosio_assert( new_proxy != voters_tbl.end() && new_proxy->is_proxy, "proxy not found" ); - voters_tbl.modify( new_proxy, 0, [&](auto& a) { a.proxied_votes += voter->staked.quantity; } ); - new_producers = &new_proxy->producers; - } else { - new_producers = &vp.producers; - } - - producers_table producers_tbl( SystemAccount, SystemAccount ); - uint128_t votes = voter->staked.quantity; - if ( voter->is_proxy ) { - votes += voter->proxied_votes; - } - - if ( old_producers ) { //old_producers == nullptr if proxy has stopped being a proxy and votes were taken back from the producers at that moment - //revoke votes only from no longer elected - std::vector revoked( old_producers->size() ); - auto end_it = std::set_difference( old_producers->begin(), old_producers->end(), new_producers->begin(), new_producers->end(), revoked.begin() ); - for ( auto it = revoked.begin(); it != end_it; ++it ) { - auto prod = producers_tbl.find( *it ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes -= votes; } ); - } - } - - //update newly elected - std::vector elected( new_producers->size() ); - auto end_it = elected.begin(); - if( old_producers ) { - end_it = std::set_difference( new_producers->begin(), new_producers->end(), old_producers->begin(), old_producers->end(), elected.begin() ); - } else { - end_it = std::copy( new_producers->begin(), new_producers->end(), elected.begin() ); - } - for ( auto it = elected.begin(); it != end_it; ++it ) { - auto prod = producers_tbl.find( *it ); - eosio_assert( prod != producers_tbl.end(), "producer is not registered" ); - if ( vp.proxy == 0 ) { //direct voting, in case of proxy voting update total_votes even for inactive producers - eosio_assert( prod->active(), "producer is not currently registered" ); - } - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes += votes; } ); - } - - // save new values to the account itself - voters_tbl.modify( voter, 0, [&](voter_info& a) { - a.proxy = vp.proxy; - a.last_update = now(); - a.producers = vp.producers; - }); - } - - ACTION( SystemAccount, regproxy ) { - account_name proxy; - - EOSLIB_SERIALIZE( regproxy, (proxy) ) - }; - - static void on( const regproxy& reg ) { - require_auth( reg.proxy ); - - voters_table voters_tbl( SystemAccount, SystemAccount ); - auto proxy = voters_tbl.find( reg.proxy ); - if ( proxy != voters_tbl.end() ) { - eosio_assert( proxy->is_proxy == 0, "account is already a proxy" ); - eosio_assert( proxy->proxy == 0, "account that uses a proxy is not allowed to become a proxy" ); - voters_tbl.modify( proxy, 0, [&](voter_info& a) { - a.is_proxy = 1; - a.last_update = now(); - //a.proxied_votes may be > 0, if the proxy has been unregistered, so we had to keep the value - }); - if ( 0 < proxy->proxied_votes ) { - producers_table producers_tbl( SystemAccount, SystemAccount ); - for ( auto p : proxy->producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes += proxy->proxied_votes; }); - } - } - } else { - voters_tbl.emplace( reg.proxy, [&]( voter_info& a ) { - a.owner = reg.proxy; - a.last_update = now(); - a.proxy = 0; - a.is_proxy = 1; - a.proxied_votes = 0; - a.staked.quantity = 0; - }); - } - } - - ACTION( SystemAccount, unregproxy ) { - account_name proxy; - - EOSLIB_SERIALIZE( unregproxy, (proxy) ) - }; - - static void on( const unregproxy& reg ) { - require_auth( reg.proxy ); - - voters_table voters_tbl( SystemAccount, SystemAccount ); - auto proxy = voters_tbl.find( reg.proxy ); - eosio_assert( proxy != voters_tbl.end(), "proxy not found" ); - eosio_assert( proxy->is_proxy == 1, "account is not a proxy" ); - - voters_tbl.modify( proxy, 0, [&](voter_info& a) { - a.is_proxy = 0; - a.last_update = now(); - //a.proxied_votes should be kept in order to be able to reenable this proxy in the future - }); - - if ( 0 < proxy->proxied_votes ) { - producers_table producers_tbl( SystemAccount, SystemAccount ); - for ( auto p : proxy->producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( prod != producers_tbl.end(), "never existed producer" ); //data corruption - producers_tbl.modify( prod, 0, [&]( auto& pi ) { pi.total_votes -= proxy->proxied_votes; }); - } - } - } - - }; -}