In eos network, eosio.system contract enable users to 1) stake tokens, and then vote on producers (or worker proposals), 2) proxy their voting influence to other users, 3) register producers, 4) claim producer rewards, 5) delegate resources (net, cpu & ram) and push other necessary actions to keep blockchain system running.
In this series, we will go through What is the contract, What are included in the contract and How the contract can be used.
In this article, we will be discussing detailed flows/steps on Producer Registration, Token Staking, Voting on BP , and Changing/Withdrawing Vote successively.
All codes present are based on commit of 44e7d3e
x
amount of weighted votes, which can be used to vote up to 30 producers and each selected producer will get x
amount of votes repectivelyAccounts should register themselves as producer first before they can be voted. This process is done by pushing. This process is done by pushing a system_contract::regproducer
action.
producerinfo
table.void system_contract::regproducer( const account_name producer,
const eosio::public_key& producer_key, const std::string& url ) {
//, const eosio_parameters& prefs ) {
...
if ( prod != _producers.end() ) {
if( producer_key != prod->producer_key ) {
_producers.modify( prod, producer, [&]( producer_info& info ){
info.producer_key = producer_key;
});
}
} else {
_producers.emplace( producer, [&]( producer_info& info ){
info.owner = producer;
info.total_votes = 0;
info.producer_key = producer_key;
});
}
}
*This part of code is under rapid development, we will keep updating it if significant changes are found.
Token holders can only vote after they have staked their tokens on net and cpu. Staking process is done by pushing a system_contract::delegatebw
action. Inside delegatebw
action, voter's tokens are staked and cannot be transferred until refunded.
If a user has not staked before, insert a record for this account in the table deltable
. If a user has staked, add newly amount to the existing amount.
Set resource limits for stake receiver (same with sender when voting). Transfer corresponding amount as stake to a public account eosio
.
void system_contract::delegatebw( account_name from, account_name receiver,
asset stake_net_quantity,
asset stake_cpu_quantity )
{
require_auth( from );
...
set_resource_limits( tot_itr->owner, tot_itr->ram_bytes,
tot_itr->net_weight.amount, tot_itr->cpu_weight.amount );
if( N(eosio) != from) {
INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)},
{
from, N(eosio), asset(total_stake), std::string("stake bandwidth")
} );
}
Update voter's staked amount.
voters
table, if not exist, insert a new record of this voter.staked
attribute.voteproducer
action to update vote results. This means if the push sender has voted before, on new delegatebw
action, votes will be updated for last voting producers (or lasting voting proxy)....
print( "voters \n" );
auto from_voter = _voters.find(from);
if( from_voter == _voters.end() ) {
print( " create voter \n" );
from_voter = _voters.emplace( from, [&]( auto& v ) {
v.owner = from;
v.staked = uint64_t(total_stake);
print( " vote weight: ", v.last_vote_weight, "\n" );
});
} else {
_voters.modify( from_voter, 0, [&]( auto& v ) {
v.staked += uint64_t(total_stake);
print( " vote weight: ", v.last_vote_weight, "\n" );
});
}
print( "voteproducer\n" );
if( from_voter->producers.size() || from_voter->proxy ) {
voteproducer( from, from_voter->proxy, from_voter->producers );
}
} // delegatebw
****Note that user can also delegate net & cpu to other accounts, making resource transfer to be possible. We will talk about user resources in depth in the upcoming blog.***
Stake holders (token holders who have their tokens staked) can vote for producers (or proxy, who will vote on behalf of push sender), all stakes will convert to weighted x votes and then add up to 30 producers by x votes.
*Leaving proxy
arguments to be empty
Validation:
Calculate current vote weight based on the following formula:
*The weight increasing could be treated as a linear growing with time within a short period.
If the voter is a proxy, proxied_vote_weight
of the voter will also be updated.
Reduce last_vote_weight
(if ever), and then add current vote weight.
void system_contract::voteproducer( const account_name voter_name,
const account_name proxy, const std::vector<account_name>& producers ) {
require_auth( voter_name );
...
boost::container::flat_map<account_name, double> producer_deltas;
for( const auto& p : voter->producers ) {
producer_deltas[p] -= voter->last_vote_weight;
}
if( new_vote_weight >= 0 ) {
for( const auto& p : producers ) {
producer_deltas[p] += new_vote_weight;
}
}
...
}
Record voting results.
voters
table, update vote weight & voting producers (or proxy) respectively.producerinfo
table, update producer's votes. ...
_voters.modify( voter, 0, [&]( auto& av ) {
print( "new_vote_weight: ", new_vote_weight, "\n" );
av.last_vote_weight = new_vote_weight;
av.producers = producers;
av.proxy = proxy;
print( " vote weight: ", av.last_vote_weight, "\n" );
});
for( const auto& pd : producer_deltas ) {
auto pitr = _producers.find( pd.first );
if( pitr != _producers.end() ) {
_producers.modify( pitr, 0, [&]( auto& p ) {
p.total_votes += pd.second;
eosio_assert( p.total_votes >= 0, "something bad happened" );
eosio_assert( p.active(), "producer is not active" );
});
}
}
}
*Leaving producers
arguments to be empty
An account marked as a proxy can vote with the weight of other accounts which have selected it as a proxy. Other accounts must refresh their voteproducer to update the proxy's weight.
system_contract::regproxy
. ...
if( voter->proxy != account_name() ) {
auto old_proxy = _voters.find( voter->proxy );
_voters.modify( old_proxy, 0, [&]( auto& vp ) {
vp.proxied_vote_weight -= voter->last_vote_weight;
print( " vote weight: ", vp.last_vote_weight, "\n" );
});
}
if( proxy != account_name() && new_vote_weight > 0 ) {
auto new_proxy = _voters.find( voter->proxy );
eosio_assert( new_proxy != _voters.end() && new_proxy->is_proxy, "invalid proxy specified" );
_voters.modify( new_proxy, 0, [&]( auto& vp ) {
vp.proxied_vote_weight += new_vote_weight;
print( " vote weight: ", vp.last_vote_weight, "\n" );
});
}
Voters are able to change voted producers (or proxy) by pushing voteproducer
actions again, details have been discussed in the previous section.
Voters can withdraw their votes by pushing by pushing system_contract::undelegatebw
actions with any amount that is no bigger than the net & cpu been staked & delegated. Undelegated stakes will be available for system_contract::refund
after 3 days.
staked
column of voter
table.totals_tbl
table and update resource limits for the account.refunds
table with unstaked amount void system_contract::undelegatebw( account_name from, account_name receiver,
asset unstake_net_quantity, asset unstake_cpu_quantity )
{
...
auto req = refunds_tbl.find( from );
if ( req != refunds_tbl.end() ) {
refunds_tbl.modify( req, 0, [&]( refund_request& r ) {
r.amount += unstake_net_quantity + unstake_cpu_quantity;
r.request_time = now();
});
} else {
refunds_tbl.emplace( from, [&]( refund_request& r ) {
r.owner = from;
r.amount = unstake_net_quantity + unstake_cpu_quantity;
r.request_time = now();
});
}
...
system_contract::refund
transaction & update voting results.
refund_delay = 3*24*3600
, i.e. 3 days.voteproducer
to deduct corresponding votes from voted producers. ...
eosio::transaction out;
out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
out.delay_sec = refund_delay;
out.send( from, receiver );
const auto& fromv = _voters.get( from );
if( fromv.producers.size() || fromv.proxy ) {
voteproducer( from, fromv.proxy, fromv.producers );
}
} // undelegatebw
In the following article, we are going to talk about some detailed implementation about user resources, including delegate cpu & net, buy & sell ram, new account, producer voting and proxy related stuff.