From 5f22b40ae9bf5f983141fcbf398e37446785a009 Mon Sep 17 00:00:00 2001 From: Wang Zhi Date: Tue, 23 Jul 2019 14:54:01 +0800 Subject: [PATCH] Revert "remove noused contract" This reverts commit cefc7c98347cbd96a19b5ed89a47ae9a4ba159aa. --- contracts/CMakeLists.txt | 2 + contracts/eosio.system_dev/CMakeLists.txt | 8 + contracts/eosio.system_dev/README.md | 84 +++ .../eosio.system_dev/delegate_bandwidth.cpp | 431 ++++++++++++ contracts/eosio.system_dev/eosio.system.abi | 625 ++++++++++++++++++ contracts/eosio.system_dev/eosio.system.hpp | 314 +++++++++ .../eosio.system_dev/eosio.system_dev.cpp | 210 ++++++ contracts/eosio.system_dev/exchange_state.cpp | 85 +++ contracts/eosio.system_dev/exchange_state.hpp | 40 ++ contracts/eosio.system_dev/native.hpp | 112 ++++ contracts/eosio.system_dev/producer_pay.cpp | 176 +++++ contracts/eosio.system_dev/voting.cpp | 442 +++++++++++++ 12 files changed, 2529 insertions(+) create mode 100644 contracts/eosio.system_dev/CMakeLists.txt create mode 100644 contracts/eosio.system_dev/README.md create mode 100644 contracts/eosio.system_dev/delegate_bandwidth.cpp create mode 100644 contracts/eosio.system_dev/eosio.system.abi create mode 100644 contracts/eosio.system_dev/eosio.system.hpp create mode 100644 contracts/eosio.system_dev/eosio.system_dev.cpp create mode 100644 contracts/eosio.system_dev/exchange_state.cpp create mode 100644 contracts/eosio.system_dev/exchange_state.hpp create mode 100644 contracts/eosio.system_dev/native.hpp create mode 100644 contracts/eosio.system_dev/producer_pay.cpp create mode 100644 contracts/eosio.system_dev/voting.cpp diff --git a/contracts/CMakeLists.txt b/contracts/CMakeLists.txt index 3c7cc495e..ce37d8789 100644 --- a/contracts/CMakeLists.txt +++ b/contracts/CMakeLists.txt @@ -33,6 +33,8 @@ add_subdirectory(integration_test) add_subdirectory(hddpool) add_subdirectory(hdddeposit) +add_subdirectory(eosio.system_dev) + file(GLOB SKELETONS RELATIVE ${CMAKE_SOURCE_DIR}/contracts "skeleton/*") diff --git a/contracts/eosio.system_dev/CMakeLists.txt b/contracts/eosio.system_dev/CMakeLists.txt new file mode 100644 index 000000000..ec8cb8381 --- /dev/null +++ b/contracts/eosio.system_dev/CMakeLists.txt @@ -0,0 +1,8 @@ +file(GLOB ABI_FILES "*.abi") +configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY) + +add_wast_executable(TARGET eosio.system_dev + INCLUDE_FOLDERS ${STANDARD_INCLUDE_FOLDERS} + LIBRARIES libc++ libc eosiolib eosio.token + DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR} +) diff --git a/contracts/eosio.system_dev/README.md b/contracts/eosio.system_dev/README.md new file mode 100644 index 000000000..9e5c31600 --- /dev/null +++ b/contracts/eosio.system_dev/README.md @@ -0,0 +1,84 @@ +eosio.system +---------- + +This contract enables users to stake tokens, and then configure and vote on producers and worker proposals. + +Users can also proxy their voting influence to other users. + +The state of this contract is read to determine the 21 active block producers. + +Actions: +The naming convention is codeaccount::actionname followed by a list of paramters. + +Indicates that a particular account wishes to become a producer +## eosio.system::cfgproducer account config + - **account** the producer account to update + - updates the configuration settings for a particular producer, these + + Storage changes are billed to 'account' + +## eosio.system::okproducer account producer vote + - **account** the account which is doing the voting + - **producer** the producer which is being voted for (or unvoted for) + - **vote** true if the producer should be voted for, false if not + + Each account has a maximum number of votes it can maintain. Storage changes will be billed to 'account' + +## eosio.system::setproxy account proxy + - **account** the account which is updating it's proxy + - **proxy** the account which will have the power to vote account's stake + + All current votes are removed and a new proxy link is created. The votes for every producer the proxy + has voted for are updated immediately. + + Storage changes will be billed to 'account' + +## eosio.system::unstake account quantity + - **account** - the account which is requesting their balance be unstaked + - **quantity** - the quantity which will be unstaked, unstaked tokens lose voting influence immediately + + - in order to unstake tokens, an account must request them. The user will receive them over + time via weekly withdraws. The length of time will be configured by the median time as set by + the active producers. + + - If this is called while in the process of unstaking, the currently pending unstake is canceled as if + quantity were 0, then it is applied as if a new unstake request was made. + + - all producers this 'from' has voted for will have their votes updated immediately. + + - bandwidth and storage for the deferred transaction will be billed to 'from' + +## eosio.system::withdraw account + - this action can only be triggered by eosio.system via a deferred transaction generated by unstake + - this will generate an inline eosio.token::transfer call from=eosio.system to=account and amount equal to the next withdraw increment + - this will generate another deferred withdraw to continue the process if there are still withdraws to be made + + +## eosio.system::transfer from to amount memo + - decrements balance of from if amount <= balance + - increments balance of to by amount + - memo is ignored + +## eosio.system::stakevote account amount + - the primary currency of eosio is controlled by the token contract which enables users to transfer + balances from one user to another. The receiver is notified on incoming balances and the vote contract + will stake the tokens on receipt. + + - all producers this 'from' has voted for will have their votes updated immediately. + + +## eosio.system::onblock account blocktime blocknum + - this special action is triggered when a block is applied by the given producer and cannot be generated from + any other source. It is used to pay producers and calculate missed blocks of other producers. + + - producer pay is deposited into the producer's stake balance and can be withdrawn over time. + + - if blocknum is the start of a new round this may update the active producer config from the producer votes. + +## eosio.system::freeze producer accounts true|false + - requires permission of the @producers account + - if an account is frozen, all authorizations of that account are rejected and the code for that account will not be run + +## eosio.system::paystandby producer + - every block some amount of tokens is paid + - at most once per day a producer may claim a percentage of the standby pay equal to their totalvotes / allvotes diff --git a/contracts/eosio.system_dev/delegate_bandwidth.cpp b/contracts/eosio.system_dev/delegate_bandwidth.cpp new file mode 100644 index 000000000..16f4d087f --- /dev/null +++ b/contracts/eosio.system_dev/delegate_bandwidth.cpp @@ -0,0 +1,431 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#include "eosio.system.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + + +#include +#include + +namespace eosiosystem { + using eosio::asset; + using eosio::indexed_by; + using eosio::const_mem_fun; + using eosio::bytes; + using eosio::print; + using eosio::permission_level; + using std::map; + using std::pair; + + static constexpr time refund_delay = 3*24*3600; + static constexpr time refund_expiration_time = 3600; + + //##YTA-Change start: + /* + struct user_resources { + account_name owner; + asset net_weight; + asset cpu_weight; + int64_t ram_bytes = 0; + + uint64_t primary_key()const { return owner; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_bytes) ) + };*/ + //##YTA-Change end: + + + /** + * Every user 'from' has a scope/table that uses every receipient 'to' as the primary key. + */ + struct delegated_bandwidth { + account_name from; + account_name to; + asset net_weight; + asset cpu_weight; + + uint64_t primary_key()const { return to; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight) ) + + }; + + struct refund_request { + account_name owner; + time request_time; + eosio::asset net_amount; + eosio::asset cpu_amount; + + uint64_t primary_key()const { return owner; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(net_amount)(cpu_amount) ) + }; + + /** + * These tables are designed to be constructed in the scope of the relevant user, this + * facilitates simpler API for per-user queries + */ + //##YTA-Change start: + //typedef eosio::multi_index< N(userres), user_resources> user_resources_table; + //##YTA-Change end: + typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table; + typedef eosio::multi_index< N(refunds), refund_request> refunds_table; + + + + /** + * This action will buy an exact amount of ram and bill the payer the current market price. + */ + void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { + auto itr = _rammarket.find(S(4,RAMCORE)); + auto tmp = *itr; + auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL ); + + buyram( payer, receiver, eosout ); + } + + + /** + * When buying ram the payer irreversiblly transfers quant to system contract and only + * the receiver may reclaim the tokens via the sellram action. The receiver pays for the + * storage of all database records associated with this action. + * + * RAM is a scarce resource whose supply is defined by global properties max_ram_size. RAM is + * priced using the bancor algorithm such that price-per-byte with a constant reserve ratio of 100:1. + */ + void system_contract::buyram( account_name payer, account_name receiver, asset quant ) + { + require_auth( payer ); + eosio_assert( quant.amount > 0, "must purchase a positive amount" ); + + auto fee = quant; + fee.amount = ( fee.amount + 199 ) / 200; /// .5% fee (round up) + // fee.amount cannot be 0 since that is only possible if quant.amount is 0 which is not allowed by the assert above. + // If quant.amount == 1, then fee.amount == 1, + // otherwise if quant.amount > 1, then 0 < fee.amount < quant.amount. + auto quant_after_fee = quant; + quant_after_fee.amount -= fee.amount; + // quant_after_fee.amount should be > 0 if quant.amount > 1. + // If quant.amount == 1, then quant_after_fee.amount == 0 and the next inline transfer will fail causing the buyram action to fail. + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + { payer, N(eosio.ram), quant_after_fee, std::string("buy ram") } ); + + if( fee.amount > 0 ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + { payer, N(eosio.ramfee), fee, std::string("ram fee") } ); + } + + int64_t bytes_out; + + const auto& market = _rammarket.get(S(4,RAMCORE), "ram market does not exist"); + _rammarket.modify( market, 0, [&]( auto& es ) { + bytes_out = es.convert( quant_after_fee, S(0,RAM) ).amount; + }); + + eosio_assert( bytes_out > 0, "must reserve a positive amount" ); + + _gstate.total_ram_bytes_reserved += uint64_t(bytes_out); + _gstate.total_ram_stake += quant_after_fee.amount; + + user_resources_table userres( _self, receiver ); + auto res_itr = userres.find( receiver ); + if( res_itr == userres.end() ) { + res_itr = userres.emplace( receiver, [&]( auto& res ) { + res.owner = receiver; + res.ram_bytes = bytes_out; + }); + } else { + userres.modify( res_itr, receiver, [&]( auto& res ) { + res.ram_bytes += bytes_out; + }); + } + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + } + + + /** + * The system contract now buys and sells RAM allocations at prevailing market prices. + * This may result in traders buying RAM today in anticipation of potential shortages + * tomorrow. Overall this will result in the market balancing the supply and demand + * for RAM over time. + */ + void system_contract::sellram( account_name account, int64_t bytes ) { + require_auth( account ); + eosio_assert( bytes > 0, "cannot sell negative byte" ); + + user_resources_table userres( _self, account ); + auto res_itr = userres.find( account ); + eosio_assert( res_itr != userres.end(), "no resource row" ); + eosio_assert( res_itr->ram_bytes >= bytes, "insufficient quota" ); + + asset tokens_out; + auto itr = _rammarket.find(S(4,RAMCORE)); + _rammarket.modify( itr, 0, [&]( auto& es ) { + /// the cast to int64_t of bytes is safe because we certify bytes is <= quota which is limited by prior purchases + tokens_out = es.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL); + }); + + eosio_assert( tokens_out.amount > 1, "token amount received from selling ram is too low" ); + + _gstate.total_ram_bytes_reserved -= static_cast(bytes); // bytes > 0 is asserted above + _gstate.total_ram_stake -= tokens_out.amount; + + //// this shouldn't happen, but just in case it does we should prevent it + eosio_assert( _gstate.total_ram_stake >= 0, "error, attempt to unstake more tokens than previously staked" ); + + userres.modify( res_itr, account, [&]( auto& res ) { + res.ram_bytes -= bytes; + }); + set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.ram),N(active)}, + { N(eosio.ram), account, asset(tokens_out), std::string("sell ram") } ); + + auto fee = ( tokens_out.amount + 199 ) / 200; /// .5% fee (round up) + // since tokens_out.amount was asserted to be at least 2 earlier, fee.amount < tokens_out.amount + + if( fee > 0 ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {account,N(active)}, + { account, N(eosio.ramfee), asset(fee), std::string("sell ram fee") } ); + } + } + + void validate_b1_vesting( int64_t stake ) { + const int64_t base_time = 1527811200; /// 2018-06-01 + const int64_t max_claimable = 100'000'000'0000ll; + const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (10*seconds_per_year) ); + + eosio_assert( max_claimable - claimable <= stake, "b1 can only claim their tokens over 10 years" ); + } + + void system_contract::changebw( account_name from, account_name receiver, + const asset stake_net_delta, const asset stake_cpu_delta, bool transfer ) + { + require_auth( from ); + eosio_assert( stake_net_delta != asset(0) || stake_cpu_delta != asset(0), "should stake non-zero amount" ); + eosio_assert( std::abs( (stake_net_delta + stake_cpu_delta).amount ) + >= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ), + "net and cpu deltas cannot be opposite signs" ); + + account_name source_stake_from = from; + if ( transfer ) { + from = receiver; + } + + // update stake delegated from "from" to "receiver" + { + del_bandwidth_table del_tbl( _self, from); + auto itr = del_tbl.find( receiver ); + if( itr == del_tbl.end() ) { + itr = del_tbl.emplace( from, [&]( auto& dbo ){ + dbo.from = from; + dbo.to = receiver; + dbo.net_weight = stake_net_delta; + dbo.cpu_weight = stake_cpu_delta; + }); + } + else { + del_tbl.modify( itr, 0, [&]( auto& dbo ){ + dbo.net_weight += stake_net_delta; + dbo.cpu_weight += stake_cpu_delta; + }); + } + eosio_assert( asset(0) <= itr->net_weight, "insufficient staked net bandwidth" ); + eosio_assert( asset(0) <= itr->cpu_weight, "insufficient staked cpu bandwidth" ); + if ( itr->net_weight == asset(0) && itr->cpu_weight == asset(0) ) { + del_tbl.erase( itr ); + } + } // itr can be invalid, should go out of scope + + // update totals of "receiver" + { + user_resources_table totals_tbl( _self, receiver ); + auto tot_itr = totals_tbl.find( receiver ); + if( tot_itr == totals_tbl.end() ) { + tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { + tot.owner = receiver; + tot.net_weight = stake_net_delta; + tot.cpu_weight = stake_cpu_delta; + }); + } else { + totals_tbl.modify( tot_itr, from == receiver ? from : 0, [&]( auto& tot ) { + tot.net_weight += stake_net_delta; + tot.cpu_weight += stake_cpu_delta; + }); + } + eosio_assert( asset(0) <= tot_itr->net_weight, "insufficient staked total net bandwidth" ); + eosio_assert( asset(0) <= tot_itr->cpu_weight, "insufficient staked total cpu bandwidth" ); + + set_resource_limits( receiver, tot_itr->ram_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); + + if ( tot_itr->net_weight == asset(0) && tot_itr->cpu_weight == asset(0) && tot_itr->ram_bytes == 0 ) { + totals_tbl.erase( tot_itr ); + } + } // tot_itr can be invalid, should go out of scope + + // create refund or update from existing refund + if ( N(eosio.stake) != source_stake_from ) { //for eosio both transfer and refund make no sense + refunds_table refunds_tbl( _self, from ); + auto req = refunds_tbl.find( from ); + + //create/update/delete refund + auto net_balance = stake_net_delta; + auto cpu_balance = stake_cpu_delta; + bool need_deferred_trx = false; + + + // net and cpu are same sign by assertions in delegatebw and undelegatebw + // redundant assertion also at start of changebw to protect against misuse of changebw + bool is_undelegating = (net_balance.amount + cpu_balance.amount ) < 0; + bool is_delegating_to_self = (!transfer && from == receiver); + + if( is_delegating_to_self || is_undelegating ) { + if ( req != refunds_tbl.end() ) { //need to update refund + refunds_tbl.modify( req, 0, [&]( refund_request& r ) { + if ( net_balance < asset(0) || cpu_balance < asset(0) ) { + r.request_time = now(); + } + r.net_amount -= net_balance; + if ( r.net_amount < asset(0) ) { + net_balance = -r.net_amount; + r.net_amount = asset(0); + } else { + net_balance = asset(0); + } + r.cpu_amount -= cpu_balance; + if ( r.cpu_amount < asset(0) ){ + cpu_balance = -r.cpu_amount; + r.cpu_amount = asset(0); + } else { + cpu_balance = asset(0); + } + }); + + eosio_assert( asset(0) <= req->net_amount, "negative net refund amount" ); //should never happen + eosio_assert( asset(0) <= req->cpu_amount, "negative cpu refund amount" ); //should never happen + + if ( req->net_amount == asset(0) && req->cpu_amount == asset(0) ) { + refunds_tbl.erase( req ); + need_deferred_trx = false; + } else { + need_deferred_trx = true; + } + + } else if ( net_balance < asset(0) || cpu_balance < asset(0) ) { //need to create refund + refunds_tbl.emplace( from, [&]( refund_request& r ) { + r.owner = from; + if ( net_balance < asset(0) ) { + r.net_amount = -net_balance; + net_balance = asset(0); + } // else r.net_amount = 0 by default constructor + if ( cpu_balance < asset(0) ) { + r.cpu_amount = -cpu_balance; + cpu_balance = asset(0); + } // else r.cpu_amount = 0 by default constructor + r.request_time = now(); + }); + need_deferred_trx = true; + } // else stake increase requested with no existing row in refunds_tbl -> nothing to do with refunds_tbl + } /// end if is_delegating_to_self || is_undelegating + + if ( need_deferred_trx ) { + eosio::transaction out; + out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from ); + out.delay_sec = refund_delay; + cancel_deferred( from ); // TODO: Remove this line when replacing deferred trxs is fixed + out.send( from, from, true ); + } else { + cancel_deferred( from ); + } + + auto transfer_amount = net_balance + cpu_balance; + if ( asset(0) < transfer_amount ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {source_stake_from, N(active)}, + { source_stake_from, N(eosio.stake), asset(transfer_amount), std::string("stake bandwidth") } ); + } + } + + // update voting power + { + asset total_update = stake_net_delta + stake_cpu_delta; + auto from_voter = _voters.find(from); + if( from_voter == _voters.end() ) { + from_voter = _voters.emplace( from, [&]( auto& v ) { + v.owner = from; + v.staked = total_update.amount; + }); + } else { + _voters.modify( from_voter, 0, [&]( auto& v ) { + v.staked += total_update.amount; + }); + } + eosio_assert( 0 <= from_voter->staked, "stake for voting cannot be negative"); + if( from == N(b1) ) { + validate_b1_vesting( from_voter->staked ); + } + + if( from_voter->producers.size() || from_voter->proxy ) { + update_votes( from, from_voter->proxy, from_voter->producers, false ); + } + } + } + + void system_contract::delegatebw( account_name from, account_name receiver, + asset stake_net_quantity, + asset stake_cpu_quantity, bool transfer ) + { + eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" ); + eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" ); + eosio_assert( stake_net_quantity + stake_cpu_quantity > asset(0), "must stake a positive amount" ); + eosio_assert( !transfer || from != receiver, "cannot use transfer flag if delegating to self" ); + + changebw( from, receiver, stake_net_quantity, stake_cpu_quantity, transfer); + } // delegatebw + + void system_contract::undelegatebw( account_name from, account_name receiver, + asset unstake_net_quantity, asset unstake_cpu_quantity ) + { + eosio_assert( asset() <= unstake_cpu_quantity, "must unstake a positive amount" ); + eosio_assert( asset() <= unstake_net_quantity, "must unstake a positive amount" ); + eosio_assert( asset() < unstake_cpu_quantity + unstake_net_quantity, "must unstake a positive amount" ); + eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); + + changebw( from, receiver, -unstake_net_quantity, -unstake_cpu_quantity, false); + } // undelegatebw + + + void system_contract::refund( const account_name owner ) { + require_auth( owner ); + + refunds_table refunds_tbl( _self, owner ); + auto req = refunds_tbl.find( owner ); + eosio_assert( req != refunds_tbl.end(), "refund request not found" ); + eosio_assert( req->request_time + refund_delay <= now(), "refund is not available yet" ); + // Until now() becomes NOW, the fact that now() is the timestamp of the previous block could in theory + // allow people to get their tokens earlier than the 3 day delay if the unstake happened immediately after many + // consecutive missed blocks. + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.stake),N(active)}, + { N(eosio.stake), req->owner, req->net_amount + req->cpu_amount, std::string("unstake") } ); + + refunds_tbl.erase( req ); + } + + +} //namespace eosiosystem diff --git a/contracts/eosio.system_dev/eosio.system.abi b/contracts/eosio.system_dev/eosio.system.abi new file mode 100644 index 000000000..9483a69fd --- /dev/null +++ b/contracts/eosio.system_dev/eosio.system.abi @@ -0,0 +1,625 @@ +{ + "version": "eosio::abi/1.0", + "types": [{ + "new_type_name": "account_name", + "type": "name" + },{ + "new_type_name": "permission_name", + "type": "name" + },{ + "new_type_name": "action_name", + "type": "name" + },{ + "new_type_name": "transaction_id_type", + "type": "checksum256" + },{ + "new_type_name": "weight_type", + "type": "uint16" + }], + "____comment": "eosio.bios structs: set_account_limits, setpriv, set_global_limits, producer_key, set_producers, require_auth are provided so abi available for deserialization in future.", + "structs": [{ + "name": "permission_level", + "base": "", + "fields": [ + {"name":"actor", "type":"account_name"}, + {"name":"permission", "type":"permission_name"} + ] + },{ + "name": "key_weight", + "base": "", + "fields": [ + {"name":"key", "type":"public_key"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "name": "prod_meta", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"total_votes", "type":"float64"} + ] + },{ + "name": "bidname", + "base": "", + "fields": [ + {"name":"bidder", "type":"account_name"}, + {"name":"newname", "type":"account_name"}, + {"name":"bid", "type":"asset"} + ] + },{ + "name": "permission_level_weight", + "base": "", + "fields": [ + {"name":"permission", "type":"permission_level"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "name": "wait_weight", + "base": "", + "fields": [ + {"name":"wait_sec", "type":"uint32"}, + {"name":"weight", "type":"weight_type"} + ] + },{ + "name": "authority", + "base": "", + "fields": [ + {"name":"threshold", "type":"uint32"}, + {"name":"keys", "type":"key_weight[]"}, + {"name":"accounts", "type":"permission_level_weight[]"}, + {"name":"waits", "type":"wait_weight[]"} + ] + },{ + "name": "newaccount", + "base": "", + "fields": [ + {"name":"creator", "type":"account_name"}, + {"name":"name", "type":"account_name"}, + {"name":"owner", "type":"authority"}, + {"name":"active", "type":"authority"} + ] + },{ + "name": "setcode", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"vmtype", "type":"uint8"}, + {"name":"vmversion", "type":"uint8"}, + {"name":"code", "type":"bytes"} + ] + },{ + "name": "setabi", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"abi", "type":"bytes"} + ] + },{ + "name": "updateauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"permission", "type":"permission_name"}, + {"name":"parent", "type":"permission_name"}, + {"name":"auth", "type":"authority"} + ] + },{ + "name": "deleteauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"permission", "type":"permission_name"} + ] + },{ + "name": "linkauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"code", "type":"account_name"}, + {"name":"type", "type":"action_name"}, + {"name":"requirement", "type":"permission_name"} + ] + },{ + "name": "unlinkauth", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"code", "type":"account_name"}, + {"name":"type", "type":"action_name"} + ] + },{ + "name": "canceldelay", + "base": "", + "fields": [ + {"name":"canceling_auth", "type":"permission_level"}, + {"name":"trx_id", "type":"transaction_id_type"} + ] + },{ + "name": "onerror", + "base": "", + "fields": [ + {"name":"sender_id", "type":"uint128"}, + {"name":"sent_trx", "type":"bytes"} + ] + },{ + "name": "buyrambytes", + "base": "", + "fields": [ + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"bytes", "type":"uint32"} + ] + },{ + "name": "sellram", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"bytes", "type":"uint64"} + ] + },{ + "name": "buyram", + "base": "", + "fields": [ + {"name":"payer", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"quant", "type":"asset"} + ] + },{ + "name": "delegatebw", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"stake_net_quantity", "type":"asset"}, + {"name":"stake_cpu_quantity", "type":"asset"}, + {"name":"transfer", "type":"bool"} + ] + },{ + "name": "undelegatebw", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"receiver", "type":"account_name"}, + {"name":"unstake_net_quantity", "type":"asset"}, + {"name":"unstake_cpu_quantity", "type":"asset"} + ] + },{ + "name": "refund", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"} + ] + },{ + "name": "delegated_bandwidth", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"}, + {"name":"to", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"} + ] + },{ + "name": "user_resources", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"}, + {"name":"ram_bytes", "type":"uint64"} + ] + },{ + "name": "total_resources", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"net_weight", "type":"asset"}, + {"name":"cpu_weight", "type":"asset"}, + {"name":"ram_bytes", "type":"uint64"} + ] + },{ + "name": "refund_request", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"request_time", "type":"time_point_sec"}, + {"name":"net_amount", "type":"asset"}, + {"name":"cpu_amount", "type":"asset"} + ] + },{ + "name": "blockchain_parameters", + "base": "", + "fields": [ + + {"name":"max_block_net_usage", "type":"uint64"}, + {"name":"target_block_net_usage_pct", "type":"uint32"}, + {"name":"max_transaction_net_usage", "type":"uint32"}, + {"name":"base_per_transaction_net_usage", "type":"uint32"}, + {"name":"net_usage_leeway", "type":"uint32"}, + {"name":"context_free_discount_net_usage_num", "type":"uint32"}, + {"name":"context_free_discount_net_usage_den", "type":"uint32"}, + {"name":"max_block_cpu_usage", "type":"uint32"}, + {"name":"target_block_cpu_usage_pct", "type":"uint32"}, + {"name":"max_transaction_cpu_usage", "type":"uint32"}, + {"name":"min_transaction_cpu_usage", "type":"uint32"}, + {"name":"max_transaction_lifetime", "type":"uint32"}, + {"name":"deferred_trx_expiration_window", "type":"uint32"}, + {"name":"max_transaction_delay", "type":"uint32"}, + {"name":"max_inline_action_size", "type":"uint32"}, + {"name":"max_inline_action_depth", "type":"uint16"}, + {"name":"max_authority_depth", "type":"uint16"} + + ] + },{ + "name": "eosio_global_state", + "base": "blockchain_parameters", + "fields": [ + {"name":"max_ram_size", "type":"uint64"}, + {"name":"total_ram_bytes_reserved", "type":"uint64"}, + {"name":"total_ram_stake", "type":"int64"}, + {"name":"last_producer_schedule_update", "type":"block_timestamp_type"}, + {"name":"last_pervote_bucket_fill", "type":"uint64"}, + {"name":"pervote_bucket", "type":"int64"}, + {"name":"perblock_bucket", "type":"int64"}, + {"name":"total_unpaid_blocks", "type":"uint32"}, + {"name":"total_activated_stake", "type":"int64"}, + {"name":"thresh_activated_stake_time", "type":"uint64"}, + {"name":"last_producer_schedule_size", "type":"uint16"}, + {"name":"total_producer_vote_weight", "type":"float64"}, + {"name":"last_name_close", "type":"block_timestamp_type"} + ] + },{ + "name": "eosio_global_count", + "base": "", + "fields": [ + {"name":"total_accounts", "type":"uint64"} + ] + },{ + "name": "producer_info", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"total_votes", "type":"float64"}, + {"name":"producer_key", "type":"public_key"}, + {"name":"is_active", "type":"bool"}, + {"name":"url", "type":"string"}, + {"name":"unpaid_blocks", "type":"uint32"}, + {"name":"last_claim_time", "type":"uint64"}, + {"name":"location", "type":"uint16"} + ] + },{ + "name": "producer_info_ext", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"seq_num", "type":"uint16"} + ] + },{ + "name": "producers_seq", + "base": "", + "fields": [ + {"name":"seq_num", "type":"uint16"}, + {"name":"prods_l1", "type":"prod_meta"} + {"name":"prods_l2", "type":"prod_meta[]"} + {"name":"prods_l3", "type":"prod_meta[]"} + ] + },{ + "name": "regproducer", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"}, + {"name":"producer_key", "type":"public_key"}, + {"name":"url", "type":"string"}, + {"name":"location", "type":"uint16"} + ] + },{ + "name": "unregprod", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"} + ] + },{ + "name": "setram", + "base": "", + "fields": [ + {"name":"max_ram_size", "type":"uint64"} + ] + },{ + "name": "regproxy", + "base": "", + "fields": [ + {"name":"proxy", "type":"account_name"}, + {"name":"isproxy", "type":"bool"} + ] + },{ + "name": "voteproducer", + "base": "", + "fields": [ + {"name":"voter", "type":"account_name"}, + {"name":"proxy", "type":"account_name"}, + {"name":"producers", "type":"account_name[]"} + ] + },{ + "name": "voter_info", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"proxy", "type":"account_name"}, + {"name":"producers", "type":"account_name[]"}, + {"name":"staked", "type":"int64"}, + {"name":"last_vote_weight", "type":"float64"}, + {"name":"proxied_vote_weight", "type":"float64"}, + {"name":"is_proxy", "type":"bool"} + ] + },{ + "name": "claimrewards", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"} + ] + },{ + "name": "setpriv", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"is_priv", "type":"int8"} + ] + },{ + "name": "rmvproducer", + "base": "", + "fields": [ + {"name":"producer", "type":"account_name"} + ] + },{ + "name": "set_account_limits", + "base": "", + "fields": [ + {"name":"account", "type":"account_name"}, + {"name":"ram_bytes", "type":"int64"}, + {"name":"net_weight", "type":"int64"}, + {"name":"cpu_weight", "type":"int64"} + ] + },{ + "name": "set_global_limits", + "base": "", + "fields": [ + {"name":"cpu_usec_per_period", "type":"int64"} + ] + },{ + "name": "producer_key", + "base": "", + "fields": [ + {"name":"producer_name", "type":"account_name"}, + {"name":"block_signing_key", "type":"public_key"} + ] + },{ + "name": "set_producers", + "base": "", + "fields": [ + {"name":"schedule", "type":"producer_key[]"} + ] + },{ + "name": "require_auth", + "base": "", + "fields": [ + {"name":"from", "type":"account_name"} + ] + },{ + "name": "setparams", + "base": "", + "fields": [ + {"name":"params", "type":"blockchain_parameters"} + ] + },{ + "name": "connector", + "base": "", + "fields": [ + {"name":"balance", "type":"asset"}, + {"name":"weight", "type":"float64"} + ] + },{ + "name": "exchange_state", + "base": "", + "fields": [ + {"name":"supply", "type":"asset"}, + {"name":"base", "type":"connector"}, + {"name":"quote", "type":"connector"} + ] + }, { + "name": "namebid_info", + "base": "", + "fields": [ + {"name":"newname", "type":"account_name"}, + {"name":"high_bidder", "type":"account_name"}, + {"name":"high_bid", "type":"int64"}, + {"name":"last_bid_time", "type":"uint64"} + ] + } + ], + "actions": [{ + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "" + },{ + "name": "setcode", + "type": "setcode", + "ricardian_contract": "" + },{ + "name": "setabi", + "type": "setabi", + "ricardian_contract": "" + },{ + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "" + },{ + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "" + },{ + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "" + },{ + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "" + },{ + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "" + },{ + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + },{ + "name": "buyrambytes", + "type": "buyrambytes", + "ricardian_contract": "" + },{ + "name": "buyram", + "type": "buyram", + "ricardian_contract": "" + },{ + "name": "sellram", + "type": "sellram", + "ricardian_contract": "" + },{ + "name": "delegatebw", + "type": "delegatebw", + "ricardian_contract": "" + },{ + "name": "undelegatebw", + "type": "undelegatebw", + "ricardian_contract": "" + },{ + "name": "refund", + "type": "refund", + "ricardian_contract": "" + },{ + "name": "regproducer", + "type": "regproducer", + "ricardian_contract": "" + },{ + "name": "setram", + "type": "setram", + "ricardian_contract": "" + },{ + "name": "bidname", + "type": "bidname", + "ricardian_contract": "" + },{ + "name": "unregprod", + "type": "unregprod", + "ricardian_contract": "" + },{ + "name": "regproxy", + "type": "regproxy", + "ricardian_contract": "" + },{ + "name": "voteproducer", + "type": "voteproducer", + "ricardian_contract": "" + },{ + "name": "claimrewards", + "type": "claimrewards", + "ricardian_contract": "" + },{ + "name": "setpriv", + "type": "setpriv", + "ricardian_contract": "" + },{ + "name": "rmvproducer", + "type": "rmvproducer", + "ricardian_contract": "" + },{ + "name": "setalimits", + "type": "set_account_limits", + "ricardian_contract": "" + },{ + "name": "setglimits", + "type": "set_global_limits", + "ricardian_contract": "" + },{ + "name": "setprods", + "type": "set_producers", + "ricardian_contract": "" + },{ + "name": "reqauth", + "type": "require_auth", + "ricardian_contract": "" + },{ + "name": "setparams", + "type": "setparams", + "ricardian_contract": "" + }], + "tables": [{ + "name": "producers2", + "type": "producer_info_ext", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "prods_seq", + "type": "producers_seq", + "index_type": "i64", + "key_names" : ["seq_num"], + "key_types" : ["uint64"] + },{ + "name": "producers", + "type": "producer_info", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "global", + "type": "eosio_global_state", + "index_type": "i64", + "key_names" : [], + "key_types" : [] + },{ + "name": "gcount", + "type": "eosio_global_count", + "index_type": "i64", + "key_names" : [], + "key_types" : [] + },{ + "name": "voters", + "type": "voter_info", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["account_name"] + },{ + "name": "userres", + "type": "user_resources", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "delband", + "type": "delegated_bandwidth", + "index_type": "i64", + "key_names" : ["to"], + "key_types" : ["uint64"] + },{ + "name": "rammarket", + "type": "exchange_state", + "index_type": "i64", + "key_names" : ["supply"], + "key_types" : ["uint64"] + },{ + "name": "refunds", + "type": "refund_request", + "index_type": "i64", + "key_names" : ["owner"], + "key_types" : ["uint64"] + },{ + "name": "namebids", + "type": "namebid_info", + "index_type": "i64", + "key_names" : ["newname"], + "key_types" : ["account_name"] + } + ], + "ricardian_clauses": [], + "abi_extensions": [] +} diff --git a/contracts/eosio.system_dev/eosio.system.hpp b/contracts/eosio.system_dev/eosio.system.hpp new file mode 100644 index 000000000..2b5e3f5f7 --- /dev/null +++ b/contracts/eosio.system_dev/eosio.system.hpp @@ -0,0 +1,314 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace eosiosystem { + + using eosio::asset; + using eosio::indexed_by; + using eosio::const_mem_fun; + using eosio::block_timestamp; + + struct name_bid { + account_name newname; + account_name high_bidder; + int64_t high_bid = 0; ///< negative high_bid == closed auction waiting to be claimed + uint64_t last_bid_time = 0; + + auto primary_key()const { return newname; } + uint64_t by_high_bid()const { return static_cast(-high_bid); } + }; + + typedef eosio::multi_index< N(namebids), name_bid, + indexed_by > + > name_bid_table; + + + struct eosio_global_state : eosio::blockchain_parameters { + uint64_t free_ram()const { return max_ram_size - total_ram_bytes_reserved; } + + uint64_t max_ram_size = 64ll*1024 * 1024 * 1024; + uint64_t total_ram_bytes_reserved = 0; + int64_t total_ram_stake = 0; + + block_timestamp last_producer_schedule_update; + uint64_t last_pervote_bucket_fill = 0; + int64_t pervote_bucket = 0; + int64_t perblock_bucket = 0; + uint32_t total_unpaid_blocks = 0; /// all blocks which have been produced but not paid + int64_t total_activated_stake = 0; + uint64_t thresh_activated_stake_time = 0; + uint16_t last_producer_schedule_size = 0; + double total_producer_vote_weight = 0; /// the sum of all producer votes + block_timestamp last_name_close; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE_DERIVED( eosio_global_state, eosio::blockchain_parameters, + (max_ram_size)(total_ram_bytes_reserved)(total_ram_stake) + (last_producer_schedule_update)(last_pervote_bucket_fill) + (pervote_bucket)(perblock_bucket)(total_unpaid_blocks)(total_activated_stake)(thresh_activated_stake_time) + (last_producer_schedule_size)(total_producer_vote_weight)(last_name_close) ) + }; + + //##YTA-Change start: + struct eosio_global_count { + uint64_t total_accounts = 1; + + EOSLIB_SERIALIZE( eosio_global_count, (total_accounts) ) + }; + //##YTA-Change end: + + struct producer_info { + account_name owner; + double total_votes = 0; + eosio::public_key producer_key; /// a packed public key object + bool is_active = true; + std::string url; + uint32_t unpaid_blocks = 0; + uint64_t last_claim_time = 0; + uint16_t location = 0; + + uint64_t primary_key()const { return owner; } + double by_votes()const { return is_active ? -total_votes : total_votes; } + bool active()const { return is_active; } + void deactivate() { producer_key = public_key(); is_active = false; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( producer_info, (owner)(total_votes)(producer_key)(is_active)(url) + (unpaid_blocks)(last_claim_time)(location) ) + }; + + //##YTA-Change start: + struct producer_info_ext { + account_name owner; + uint16_t seq_num = 1; // from 1 to 21 + + uint64_t primary_key()const { return owner; } + + EOSLIB_SERIALIZE( producer_info_ext, (owner)(seq_num) ) + + }; + + struct prod_meta { + account_name owner; + double total_votes = 0; + + EOSLIB_SERIALIZE( prod_meta, (owner)(total_votes) ) + }; + + struct producers_seq { + uint16_t seq_num = 1; // from 1 to 21 + prod_meta prods_l1; // only one + std::vector prods_l2; //max 5 + std::vector prods_l3; + + uint64_t primary_key()const { return seq_num; } + + EOSLIB_SERIALIZE( producers_seq, (seq_num) ) + + }; + + //##YTA-Change end: + + + 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 + int64_t staked = 0; + + /** + * 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 = 0; /// 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 reserved1 = 0; + time reserved2 = 0; + eosio::asset reserved3; + + 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)(reserved1)(reserved2)(reserved3) ) + }; + + //##YTA-Change start: + struct user_resources { + account_name owner; + asset net_weight; + asset cpu_weight; + int64_t ram_bytes = 0; + + uint64_t primary_key()const { return owner; } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_bytes) ) + }; + //##YTA-Change end: + + //##YTA-Change start: + typedef eosio::multi_index< N(userres), user_resources> user_resources_table; + //##YTA-Change end: + + typedef eosio::multi_index< N(voters), voter_info> voters_table; + + + typedef eosio::multi_index< N(producers), producer_info, + indexed_by > + > producers_table; + + //##YTA-Change start: + typedef eosio::multi_index< N(producers2), producer_info_ext> producers_ext_table; + typedef eosio::multi_index< N(prods_seq), producers_seq> producers_seq_table; + //##YTA-Change end: + + typedef eosio::singleton global_state_singleton; + + //##YTA-Change start: + typedef eosio::singleton global_count_singleton; + //##YTA-Change end: + + // static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation + static constexpr uint32_t seconds_per_day = 24 * 3600; + static constexpr uint64_t system_token_symbol = CORE_SYMBOL; + + class system_contract : public native { + private: + voters_table _voters; + producers_table _producers; + //##YTA-Change start: + producers_ext_table _producers2; + //##YTA-Change end: + global_state_singleton _global; + eosio_global_state _gstate; + rammarket _rammarket; + + public: + system_contract( account_name s ); + ~system_contract(); + + // Actions: + void onblock( block_timestamp timestamp, account_name producer ); + // const block_header& header ); /// only parse first 3 fields of block header + + // functions defined in delegate_bandwidth.cpp + + /** + * Stakes SYS from the balance of 'from' for the benfit of 'receiver'. + * If transfer == true, then 'receiver' can unstake to their account + * Else 'from' can unstake at any time. + */ + void delegatebw( account_name from, account_name receiver, + asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); + + + /** + * Decreases the total tokens delegated by from to receiver and/or + * frees the memory associated with the delegation if there is nothing + * left to delegate. + * + * This will cause an immediate reduction in net/cpu bandwidth of the + * receiver. + * + * A transaction is scheduled to send the tokens back to 'from' after + * the staking period has passed. If existing transaction is scheduled, it + * will be canceled and a new transaction issued that has the combined + * undelegated amount. + * + * The 'from' account loses voting power as a result of this call and + * all producer tallies are updated. + */ + void undelegatebw( account_name from, account_name receiver, + asset unstake_net_quantity, asset unstake_cpu_quantity ); + + + /** + * Increases receiver's ram quota based upon current price and quantity of + * tokens provided. An inline transfer from receiver to system contract of + * tokens will be executed. + */ + void buyram( account_name buyer, account_name receiver, asset tokens ); + void buyrambytes( account_name buyer, account_name receiver, uint32_t bytes ); + + /** + * Reduces quota my bytes and then performs an inline transfer of tokens + * to receiver based upon the average purchase price of the original quota. + */ + void sellram( account_name receiver, int64_t bytes ); + + /** + * This action is called after the delegation-period to claim all pending + * unstaked tokens belonging to owner + */ + void refund( account_name owner ); + + // functions defined in voting.cpp + + void regproducer( const account_name producer, const public_key& producer_key, const std::string& url, uint16_t location ); + + void unregprod( const account_name producer ); + + void setram( uint64_t max_ram_size ); + + void voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ); + + void regproxy( const account_name proxy, bool isproxy ); + + void setparams( const eosio::blockchain_parameters& params ); + + // functions defined in producer_pay.cpp + void claimrewards( const account_name& owner ); + + void setpriv( account_name account, uint8_t ispriv ); + + void rmvproducer( account_name producer ); + + void bidname( account_name bidder, account_name newname, asset bid ); + private: + void update_elected_producers( block_timestamp timestamp ); + + //##YTA-Change start: + /// whether a procuder has qulification to produce block in next loop + bool is_qualification_producers( account_name name ); + void update_producers_seq_totalvotes( uint64_t seq_num, account_name owner, double total_votes); + std::pair getProducerForSeq(uint64_t seq_num ); + std::pair genProducerData( account_name owner ); + //##YTA-Change end: + + // Implementation details: + + //defind in delegate_bandwidth.cpp + void changebw( account_name from, account_name receiver, + asset stake_net_quantity, asset stake_cpu_quantity, bool transfer ); + + //defined in voting.hpp + static eosio_global_state get_default_parameters(); + + void update_votes( const account_name voter, const account_name proxy, const std::vector& producers, bool voting ); + + // defined in voting.cpp + void propagate_weight_change( const voter_info& voter ); + }; + +} /// eosiosystem diff --git a/contracts/eosio.system_dev/eosio.system_dev.cpp b/contracts/eosio.system_dev/eosio.system_dev.cpp new file mode 100644 index 000000000..a81c964ba --- /dev/null +++ b/contracts/eosio.system_dev/eosio.system_dev.cpp @@ -0,0 +1,210 @@ +#include "eosio.system.hpp" +#include + +#include "producer_pay.cpp" +#include "delegate_bandwidth.cpp" +#include "voting.cpp" +#include "exchange_state.cpp" + + +namespace eosiosystem { + + system_contract::system_contract( account_name s ) + :native(s), + _voters(_self,_self), + _producers(_self,_self), + _producers2(_self,_self), + _global(_self,_self), + _rammarket(_self,_self) + { + //print( "construct system\n" ); + _gstate = _global.exists() ? _global.get() : get_default_parameters(); + + auto itr = _rammarket.find(S(4,RAMCORE)); + + if( itr == _rammarket.end() ) { + auto system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; + if( system_token_supply > 0 ) { + itr = _rammarket.emplace( _self, [&]( auto& m ) { + m.supply.amount = 100000000000000ll; + m.supply.symbol = S(4,RAMCORE); + m.base.balance.amount = int64_t(_gstate.free_ram()); + m.base.balance.symbol = S(0,RAM); + m.quote.balance.amount = system_token_supply / 1000; + m.quote.balance.symbol = CORE_SYMBOL; + }); + } + } else { + //print( "ram market already created" ); + } + } + + eosio_global_state system_contract::get_default_parameters() { + eosio_global_state dp; + get_blockchain_parameters(dp); + return dp; + } + + + system_contract::~system_contract() { + //print( "destruct system\n" ); + _global.set( _gstate, _self ); + //eosio_exit(0); + } + + void system_contract::setram( uint64_t max_ram_size ) { + require_auth( _self ); + + eosio_assert( _gstate.max_ram_size < max_ram_size, "ram may only be increased" ); /// decreasing ram might result market maker issues + eosio_assert( max_ram_size < 1024ll*1024*1024*1024*1024, "ram size is unrealistic" ); + eosio_assert( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" ); + + auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size); + auto itr = _rammarket.find(S(4,RAMCORE)); + + /** + * Increase or decrease the amount of ram for sale based upon the change in max + * ram size. + */ + _rammarket.modify( itr, 0, [&]( auto& m ) { + m.base.balance.amount += delta; + }); + + _gstate.max_ram_size = max_ram_size; + _global.set( _gstate, _self ); + } + + void system_contract::setparams( const eosio::blockchain_parameters& params ) { + require_auth( N(eosio) ); + (eosio::blockchain_parameters&)(_gstate) = params; + eosio_assert( 3 <= _gstate.max_authority_depth, "max_authority_depth should be at least 3" ); + set_blockchain_parameters( params ); + } + + void system_contract::setpriv( account_name account, uint8_t ispriv ) { + require_auth( _self ); + set_privileged( account, ispriv ); + } + + void system_contract::rmvproducer( account_name producer ) { + require_auth( _self ); + auto prod = _producers.find( producer ); + eosio_assert( prod != _producers.end(), "producer not found" ); + _producers.modify( prod, 0, [&](auto& p) { + p.deactivate(); + }); + } + + void system_contract::bidname( account_name bidder, account_name newname, asset bid ) { + require_auth( bidder ); + eosio_assert( eosio::name_suffix(newname) == newname, "you can only bid on top-level suffix" ); + eosio_assert( newname != 0, "the empty name is not a valid account name to bid on" ); + eosio_assert( (newname & 0xFull) == 0, "13 character names are not valid account names to bid on" ); + eosio_assert( (newname & 0x1F0ull) == 0, "accounts with 12 character names and no dots can be created without bidding required" ); + eosio_assert( !is_account( newname ), "account already exists" ); + eosio_assert( bid.symbol == asset().symbol, "asset must be system token" ); + eosio_assert( bid.amount > 0, "insufficient bid" ); + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {bidder,N(active)}, + { bidder, N(eosio.names), bid, std::string("bid name ")+(name{newname}).to_string() } ); + + name_bid_table bids(_self,_self); + print( name{bidder}, " bid ", bid, " on ", name{newname}, "\n" ); + auto current = bids.find( newname ); + if( current == bids.end() ) { + bids.emplace( bidder, [&]( auto& b ) { + b.newname = newname; + b.high_bidder = bidder; + b.high_bid = bid.amount; + b.last_bid_time = current_time(); + }); + } else { + eosio_assert( current->high_bid > 0, "this auction has already closed" ); + eosio_assert( bid.amount - current->high_bid > (current->high_bid / 10), "must increase bid by 10%" ); + eosio_assert( current->high_bidder != bidder, "account is already highest bidder" ); + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.names),N(active)}, + { N(eosio.names), current->high_bidder, asset(current->high_bid), + std::string("refund bid on name ")+(name{newname}).to_string() } ); + + bids.modify( current, bidder, [&]( auto& b ) { + b.high_bidder = bidder; + b.high_bid = bid.amount; + b.last_bid_time = current_time(); + }); + } + } + + /** + * Called after a new account is created. This code enforces resource-limits rules + * for new accounts as well as new account naming conventions. + * + * Account names containing '.' symbols must have a suffix equal to the name of the creator. + * This allows users who buy a premium name (shorter than 12 characters with no dots) to be the only ones + * who can create accounts with the creator's name as a suffix. + * + */ + void native::newaccount( account_name creator, + account_name newact + /* no need to parse authorities + const authority& owner, + const authority& active*/ ) { + + if( creator != _self ) { + auto tmp = newact >> 4; + bool has_dot = false; + + for( uint32_t i = 0; i < 12; ++i ) { + has_dot |= !(tmp & 0x1f); + tmp >>= 5; + } + if( has_dot ) { // or is less than 12 characters + auto suffix = eosio::name_suffix(newact); + if( suffix == newact ) { + name_bid_table bids(_self,_self); + auto current = bids.find( newact ); + eosio_assert( current != bids.end(), "no active bid for name" ); + eosio_assert( current->high_bidder == creator, "only highest bidder can claim" ); + eosio_assert( current->high_bid < 0, "auction for name is not closed yet" ); + bids.erase( current ); + } else { + eosio_assert( creator == suffix, "only suffix may create this account" ); + } + } + } + + user_resources_table userres( _self, newact); + + userres.emplace( newact, [&]( auto& res ) { + res.owner = newact; + }); + + set_resource_limits( newact, 0, 0, 0 ); + + //##YTA-Change start: + global_count_singleton global(_self, _self); + eosio_global_count gstate; + if(global.exists()) { + gstate = global.get(); + } + gstate.total_accounts += 1; + global.set( gstate, _self ); + //##YTA-Change end: + + } + +} /// eosio.system + + +EOSIO_ABI( eosiosystem::system_contract, + // native.hpp (newaccount definition is actually in eosio.system.cpp) + (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror) + // eosio.system.cpp + (setram)(setparams)(setpriv)(rmvproducer)(bidname) + // delegate_bandwidth.cpp + (buyrambytes)(buyram)(sellram)(delegatebw)(undelegatebw)(refund) + // voting.cpp + (regproducer)(unregprod)(voteproducer)(regproxy) + // producer_pay.cpp + (onblock)(claimrewards) +) diff --git a/contracts/eosio.system_dev/exchange_state.cpp b/contracts/eosio.system_dev/exchange_state.cpp new file mode 100644 index 000000000..621d3e714 --- /dev/null +++ b/contracts/eosio.system_dev/exchange_state.cpp @@ -0,0 +1,85 @@ +#include + +namespace eosiosystem { + asset exchange_state::convert_to_exchange( connector& c, asset in ) { + + real_type R(supply.amount); + real_type C(c.balance.amount+in.amount); + real_type F(c.weight/1000.0); + real_type T(in.amount); + real_type ONE(1.0); + + real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); + //print( "E: ", E, "\n"); + int64_t issued = int64_t(E); + + supply.amount += issued; + c.balance.amount += in.amount; + + return asset( issued, supply.symbol ); + } + + asset exchange_state::convert_from_exchange( connector& c, asset in ) { + eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" ); + + real_type R(supply.amount - in.amount); + real_type C(c.balance.amount); + real_type F(1000.0/c.weight); + real_type E(in.amount); + real_type ONE(1.0); + + + // potentially more accurate: + // The functions std::expm1 and std::log1p are useful for financial calculations, for example, + // when calculating small daily interest rates: (1+x)n + // -1 can be expressed as std::expm1(n * std::log1p(x)). + // real_type T = C * std::expm1( F * std::log1p(E/R) ); + + real_type T = C * (std::pow( ONE + E/R, F) - ONE); + //print( "T: ", T, "\n"); + int64_t out = int64_t(T); + + supply.amount -= in.amount; + c.balance.amount -= out; + + return asset( out, c.balance.symbol ); + } + + asset exchange_state::convert( asset from, symbol_type to ) { + auto sell_symbol = from.symbol; + auto ex_symbol = supply.symbol; + auto base_symbol = base.balance.symbol; + auto quote_symbol = quote.balance.symbol; + + //print( "From: ", from, " TO ", asset( 0,to), "\n" ); + //print( "base: ", base_symbol, "\n" ); + //print( "quote: ", quote_symbol, "\n" ); + //print( "ex: ", supply.symbol, "\n" ); + + if( sell_symbol != ex_symbol ) { + if( sell_symbol == base_symbol ) { + from = convert_to_exchange( base, from ); + } else if( sell_symbol == quote_symbol ) { + from = convert_to_exchange( quote, from ); + } else { + eosio_assert( false, "invalid sell" ); + } + } else { + if( to == base_symbol ) { + from = convert_from_exchange( base, from ); + } else if( to == quote_symbol ) { + from = convert_from_exchange( quote, from ); + } else { + eosio_assert( false, "invalid conversion" ); + } + } + + if( to != from.symbol ) + return convert( from, to ); + + return from; + } + + + +} /// namespace eosiosystem diff --git a/contracts/eosio.system_dev/exchange_state.hpp b/contracts/eosio.system_dev/exchange_state.hpp new file mode 100644 index 000000000..3705a9b8b --- /dev/null +++ b/contracts/eosio.system_dev/exchange_state.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace eosiosystem { + using eosio::asset; + using eosio::symbol_type; + + typedef double real_type; + + /** + * Uses Bancor math to create a 50/50 relay between two asset types. The state of the + * bancor exchange is entirely contained within this struct. There are no external + * side effects associated with using this API. + */ + struct exchange_state { + asset supply; + + struct connector { + asset balance; + double weight = .5; + + EOSLIB_SERIALIZE( connector, (balance)(weight) ) + }; + + connector base; + connector quote; + + uint64_t primary_key()const { return supply.symbol; } + + asset convert_to_exchange( connector& c, asset in ); + asset convert_from_exchange( connector& c, asset in ); + asset convert( asset from, symbol_type to ); + + EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) ) + }; + + typedef eosio::multi_index rammarket; + +} /// namespace eosiosystem diff --git a/contracts/eosio.system_dev/native.hpp b/contracts/eosio.system_dev/native.hpp new file mode 100644 index 000000000..e2bcb3195 --- /dev/null +++ b/contracts/eosio.system_dev/native.hpp @@ -0,0 +1,112 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eosiosystem { + using eosio::permission_level; + using eosio::public_key; + + typedef std::vector bytes; + + struct permission_level_weight { + permission_level permission; + weight_type weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( permission_level_weight, (permission)(weight) ) + }; + + struct key_weight { + public_key key; + weight_type weight; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( key_weight, (key)(weight) ) + }; + + struct authority { + uint32_t threshold; + uint32_t delay_sec; + std::vector keys; + std::vector accounts; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( authority, (threshold)(delay_sec)(keys)(accounts) ) + }; + + struct block_header { + uint32_t timestamp; + account_name producer; + uint16_t confirmed = 0; + block_id_type previous; + checksum256 transaction_mroot; + checksum256 action_mroot; + uint32_t schedule_version = 0; + eosio::optional new_producers; + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE(block_header, (timestamp)(producer)(confirmed)(previous)(transaction_mroot)(action_mroot) + (schedule_version)(new_producers)) + }; + + + /* + * Method parameters commented out to prevent generation of code that parses input data. + */ + class native : public eosio::contract { + public: + + using eosio::contract::contract; + + /** + * Called after a new account is created. This code enforces resource-limits rules + * for new accounts as well as new account naming conventions. + * + * 1. accounts cannot contain '.' symbols which forces all acccounts to be 12 + * characters long without '.' until a future account auction process is implemented + * which prevents name squatting. + * + * 2. new accounts must stake a minimal number of tokens (as set in system parameters) + * therefore, this method will execute an inline buyram from receiver for newacnt in + * an amount equal to the current new account creation fee. + */ + void newaccount( account_name creator, + account_name newact + /* no need to parse authorites + const authority& owner, + const authority& active*/ ); + + + void updateauth( /*account_name account, + permission_name permission, + permission_name parent, + const authority& data*/ ) {} + + void deleteauth( /*account_name account, permission_name permission*/ ) {} + + void linkauth( /*account_name account, + account_name code, + action_name type, + permission_name requirement*/ ) {} + + void unlinkauth( /*account_name account, + account_name code, + action_name type*/ ) {} + + void canceldelay( /*permission_level canceling_auth, transaction_id_type trx_id*/ ) {} + + void onerror( /*const bytes&*/ ) {} + + }; +} diff --git a/contracts/eosio.system_dev/producer_pay.cpp b/contracts/eosio.system_dev/producer_pay.cpp new file mode 100644 index 000000000..f055909e2 --- /dev/null +++ b/contracts/eosio.system_dev/producer_pay.cpp @@ -0,0 +1,176 @@ +#include "eosio.system.hpp" +#include +#include + +namespace eosiosystem { + + const int64_t min_pervote_daily_pay = 100'0000; + //##YTA-Change start: + //Change total vote rate from 15% to 1% for test network + //const int64_t min_activated_stake = 150'000'000'0000; + const int64_t min_activated_stake = 10'000'000'0000; + //##YTA-Change end: + const uint32_t blocks_per_year = 52*7*24*2*3600; // half seconds per year + const uint32_t seconds_per_year = 52*7*24*3600; + const uint32_t blocks_per_day = 2 * 24 * 3600; + const uint32_t blocks_per_hour = 2 * 3600; + const uint64_t useconds_per_day = 24 * 3600 * uint64_t(1000000); + const uint64_t useconds_per_year = seconds_per_year*1000000ll; + + const int64_t block_initial_timestamp = 1551369600ll; // epoch year 2019.03.01 unix timestamp 1551369600s + //yta seo total= yta_seo_year[i] * YTA_SEO_BASE + const uint32_t YTA_SEO_BASE = 10'0000; + const double YTA_PRECISION =10000.0000; + const uint32_t yta_seo_year[62] = { + 1000, 900, 800, 700, + 600, 600, 500, 500, + 400, 400, 300, 300, + 200, 200, 200, + 100, 100, 100, + 90, 90, 90, + 80, 80, 80, + 70, 70, 70, 70, + 60, 60, 60, 60, + 50, 50, 50, 50, 50, + 40, 40, 40, 40, 40, + 30, 30, 30, 30, 30, + 20, 20, 20, 20, 20, + 10, 10, 10, 10, 10, + 9, 9, 9, 9, 9 + }; + + void system_contract::onblock( block_timestamp timestamp, account_name producer ) { + using namespace eosio; + + require_auth(N(eosio)); + + /** until activated stake crosses this threshold no new rewards are paid */ + if( _gstate.total_activated_stake < min_activated_stake ) + return; + + if( _gstate.last_pervote_bucket_fill == 0 ) /// start the presses + _gstate.last_pervote_bucket_fill = current_time(); + + + /** + * At startup the initial producer may not be one that is registered / elected + * and therefore there may be no producer object for them. + */ + auto prod = _producers.find(producer); + if ( prod != _producers.end() ) { + _gstate.total_unpaid_blocks++; + _producers.modify( prod, 0, [&](auto& p ) { + p.unpaid_blocks++; + }); + } + + //##YTA-Change start: + /// only update block producers once every minute, block_timestamp is in half seconds + //if( timestamp.slot - _gstate.last_producer_schedule_update.slot > 120 ) { + /// update block producers once every two minute due to election strategy is more complex than before + if( timestamp.slot - _gstate.last_producer_schedule_update.slot > 240 ) { + //##YTA-Change end: + + update_elected_producers( timestamp ); + + if( (timestamp.slot - _gstate.last_name_close.slot) > blocks_per_day ) { + name_bid_table bids(_self,_self); + auto idx = bids.get_index(); + auto highest = idx.begin(); + if( highest != idx.end() && + highest->high_bid > 0 && + highest->last_bid_time < (current_time() - useconds_per_day) && + _gstate.thresh_activated_stake_time > 0 && + (current_time() - _gstate.thresh_activated_stake_time) > 14 * useconds_per_day ) { + _gstate.last_name_close = timestamp; + idx.modify( highest, 0, [&]( auto& b ){ + b.high_bid = -b.high_bid; + }); + } + } + } + } + + using namespace eosio; + void system_contract::claimrewards( const account_name& owner ) { + require_auth(owner); + + const auto& prod = _producers.get( owner ); + eosio_assert( prod.active(), "producer does not have an active key" ); + + eosio_assert( _gstate.total_activated_stake >= min_activated_stake, + "cannot claim rewards until the chain is activated (at least 15% of all tokens participate in voting)" ); + + auto ct = current_time(); + + //eosio_assert( ct - prod.last_claim_time > useconds_per_day, "already claimed rewards within past day" ); + + const asset token_supply = token( N(eosio.token)).get_supply(symbol_type(system_token_symbol).name() ); + const auto usecs_since_last_fill = ct - _gstate.last_pervote_bucket_fill; + + print("usecs_since_last_fill: ", usecs_since_last_fill, "\n"); + print("_gstate.last_pervote_bucket_fill: ", _gstate.last_pervote_bucket_fill, "\n"); + print("now(): ", now(), "\n"); + + int idx_year = (int)((now()- block_initial_timestamp) / seconds_per_year); + auto seo_token = yta_seo_year[idx_year] * YTA_SEO_BASE; + + print("idx_year: ", idx_year, "\n"); + print("yta_seo_year[idx_year]: ", yta_seo_year[idx_year], "\n"); + print( "token_supply: ", token_supply, "\n"); + print("seo_token: ", seo_token, "\n"); + + if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > 0 ) { + auto new_tokens = static_cast(seo_token * YTA_PRECISION * double(usecs_since_last_fill)/double(useconds_per_year)); + print("new_token: ", new_tokens, "\n"); + auto to_producers = new_tokens; + auto to_per_block_pay = to_producers / 4; + auto to_per_vote_pay = to_producers - to_per_block_pay; + + INLINE_ACTION_SENDER(eosio::token, issue)( N(eosio.token), {{N(eosio),N(active)}}, + {N(eosio), asset(new_tokens), std::string("issue tokens for producer pay and savings")} ); + + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), N(eosio.bpay), asset(to_per_block_pay), "fund per-block bucket" } ); + + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, + { N(eosio), N(eosio.vpay), asset(to_per_vote_pay), "fund per-vote bucket" } ); + + _gstate.pervote_bucket += to_per_vote_pay; + _gstate.perblock_bucket += to_per_block_pay; + + _gstate.last_pervote_bucket_fill = ct; + } + + int64_t producer_per_block_pay = 0; + if( _gstate.total_unpaid_blocks > 0 ) { + producer_per_block_pay = (_gstate.perblock_bucket * prod.unpaid_blocks) / _gstate.total_unpaid_blocks; + } + int64_t producer_per_vote_pay = 0; + if( _gstate.total_producer_vote_weight > 0 ) { + producer_per_vote_pay = int64_t((_gstate.pervote_bucket * prod.total_votes ) / _gstate.total_producer_vote_weight); + } + if( producer_per_vote_pay < min_pervote_daily_pay ) { + producer_per_vote_pay = 0; + } + _gstate.pervote_bucket -= producer_per_vote_pay; + _gstate.perblock_bucket -= producer_per_block_pay; + _gstate.total_unpaid_blocks -= prod.unpaid_blocks; + + _producers.modify( prod, 0, [&](auto& p) { + p.last_claim_time = ct; + p.unpaid_blocks = 0; + }); + + if( producer_per_block_pay > 0 ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.bpay),N(active)}, + { N(eosio.bpay), owner, asset(producer_per_block_pay), std::string("producer block pay") } ); + } + if( producer_per_vote_pay > 0 ) { + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.vpay),N(active)}, + { N(eosio.vpay), owner, asset(producer_per_vote_pay), std::string("producer vote pay") } ); + } + } + +} //namespace eosiosystem diff --git a/contracts/eosio.system_dev/voting.cpp b/contracts/eosio.system_dev/voting.cpp new file mode 100644 index 000000000..ae833f425 --- /dev/null +++ b/contracts/eosio.system_dev/voting.cpp @@ -0,0 +1,442 @@ +/** + * @file + * @copyright defined in eos/LICENSE + */ +#include "eosio.system.hpp" + +#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; + using std::swap; + /** + * This method will create a producer_config and producer_info object for 'producer' + * + * @pre producer is not already registered + * @pre producer to register is an account + * @pre authority of producer to register + * + */ + void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url, uint16_t location ) { + eosio_assert( url.size() < 512, "url too long" ); + eosio_assert( producer_key != eosio::public_key(), "public key should not be the default value" ); + require_auth( producer ); + + auto prod = _producers.find( producer ); + + if ( prod != _producers.end() ) { + _producers.modify( prod, producer, [&]( producer_info& info ){ + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; + }); + } else { + _producers.emplace( producer, [&]( producer_info& info ){ + info.owner = producer; + info.total_votes = 0; + info.producer_key = producer_key; + info.is_active = true; + info.url = url; + info.location = location; + }); + } + + //##YTA-Change start: + auto prod_ext = _producers2.find( producer ); + if ( prod_ext == _producers2.end() ) { + _producers2.emplace( producer, [&]( producer_info_ext& info ){ + info.owner = producer; + info.seq_num = 1; //This should have some policy to generate the seq_num + }); + } + //##YTA-Change end: + } + + void system_contract::unregprod( const account_name producer ) { + require_auth( producer ); + + const auto& prod = _producers.get( producer, "producer not found" ); + + _producers.modify( prod, 0, [&]( producer_info& info ){ + info.deactivate(); + }); + } + + //##YTA-Change start: +/* + void system_contract::update_elected_producers( block_timestamp block_time ) { + _gstate.last_producer_schedule_update = block_time; + + auto idx = _producers.get_index(); + + std::vector< std::pair > top_producers; + top_producers.reserve(21); + + for ( auto it = idx.cbegin(); it != idx.cend() && top_producers.size() < 21 && 0 < it->total_votes && it->active(); ++it ) { + top_producers.emplace_back( std::pair({{it->owner, it->producer_key}, it->location}) ); + } + + if ( top_producers.size() < _gstate.last_producer_schedule_size ) { + return; + } + + /// sort by producer name + std::sort( top_producers.begin(), top_producers.end() ); + + std::vector producers; + + producers.reserve(top_producers.size()); + for( const auto& item : top_producers ) + producers.push_back(item.first); + + bytes packed_schedule = pack(producers); + + if( set_proposed_producers( packed_schedule.data(), packed_schedule.size() ) >= 0 ) { + _gstate.last_producer_schedule_size = static_cast( top_producers.size() ); + } + } +*/ + + void system_contract::update_elected_producers( block_timestamp block_time ) { + _gstate.last_producer_schedule_update = block_time; + + + std::vector< std::pair > top_producers; + top_producers.reserve(21); + + for( uint16_t seq_num = 1; seq_num <= 21 ; seq_num++ ) { + std::pair ppinfo = getProducerForSeq( seq_num ); + if( ppinfo.first.producer_name != 0 ) { + top_producers.emplace_back( ppinfo ); + } + } + + if ( top_producers.size() < _gstate.last_producer_schedule_size ) { + return; + } + + /// sort by producer name + std::sort( top_producers.begin(), top_producers.end() ); + + std::vector producers; + + producers.reserve(top_producers.size()); + for( const auto& item : top_producers ) + producers.push_back(item.first); + + bytes packed_schedule = pack(producers); + + if( set_proposed_producers( packed_schedule.data(), packed_schedule.size() ) >= 0 ) { + _gstate.last_producer_schedule_size = static_cast( top_producers.size() ); + } + } + + std::pair system_contract::getProducerForSeq(uint64_t seq_num ) { + producers_seq_table prodseq(_self, seq_num); + auto ps_itr = prodseq.find (seq_num); + if( ps_itr == prodseq.end() ) + return std::pair({{0, eosio::public_key{}}, 0}); + + if(is_qualification_producers(ps_itr->prods_l1.owner)) { + return genProducerData(ps_itr->prods_l1.owner); + } + + + prodseq.modify( ps_itr, _self, [&]( producers_seq& info ){ + info.prods_l3.push_back(info.prods_l1); + + sort(info.prods_l2.begin(), info.prods_l2.end() ,[](const prod_meta &a, const prod_meta &b) { return a.total_votes < b.total_votes; }); + if( info.prods_l2.begin() != info.prods_l2.end() ) { + info.prods_l1 = *(info.prods_l2.begin()); + info.prods_l2.erase(info.prods_l2.begin()); + } + + sort(info.prods_l3.begin(), info.prods_l3.end() ,[](const prod_meta &a, const prod_meta &b) { return a.total_votes < b.total_votes; }); + if( info.prods_l3.begin() != info.prods_l3.end() ) { + info.prods_l1 = *(info.prods_l3.begin()); + info.prods_l3.erase(info.prods_l3.begin()); + } + }); + + return genProducerData(ps_itr->prods_l1.owner); + } + + std::pair system_contract::genProducerData(account_name owner) { + auto prod = _producers.find(owner); + if ( prod == _producers.end() ) { + return std::pair({{0, eosio::public_key{}}, 0}); + } + return std::pair({{prod->owner , prod->producer_key}, prod->location}); + } + + bool system_contract::is_qualification_producers( account_name name ) { + auto prod = _producers.find(name); + if ( prod == _producers.end() ) { + return false; + } + if( !prod->active()) + return false; + + user_resources_table userres( _self, name ); + auto res_itr = userres.find( name ); + if( res_itr == userres.end() ) { + return false; + } + asset totalbw = res_itr->cpu_weight + res_itr->net_weight; + asset threshold{ 5000000000, CORE_SYMBOL }; + if( totalbw < threshold) + return false; + + return true; + } + + + void system_contract::update_producers_seq_totalvotes( uint64_t seq_num, account_name owner, double total_votes) { + if(seq_num == 0 || seq_num > 21) + return; + + producers_seq_table prodseq(_self, seq_num); + auto ps_itr = prodseq.find (seq_num); + + prodseq.modify( ps_itr, _self, [&]( producers_seq& info ){ + if( info.prods_l1.owner == owner ) { + info.prods_l1.total_votes = total_votes; + return; + } + + for(auto it = info.prods_l2.begin(); it != info.prods_l2.end(); it++) { + if(it->owner == owner) { + it->total_votes = total_votes; + return; + } + } + + for(auto it = info.prods_l3.begin(); it != info.prods_l3.end(); it++) { + if(it->owner == owner) { + it->total_votes = total_votes; + return; + } + } + + }); + } + //##YTA-Change end: + + double stake2vote( int64_t staked ) { + /// TODO subtract 2080 brings the large numbers closer to this decade + double weight = int64_t( (now() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) ) / double( 52 ); + return double(staked) * std::pow( 2, weight ); + } + /** + * @pre producers must be sorted from lowest to highest and must be registered and active + * @pre if proxy is set then no producers can be voted for + * @pre if proxy is set then proxy account must exist and be registered as a proxy + * @pre every listed producer or proxy must have been previously registered + * @pre voter must authorize this action + * @pre voter must have previously staked some EOS for voting + * @pre voter->staked must be up to date + * + * @post every producer previously voted for will have vote reduced by previous vote weight + * @post every producer newly voted for will have vote increased by new vote amount + * @post prior proxy will proxied_vote_weight decremented by previous vote weight + * @post new proxy will proxied_vote_weight incremented by new vote weight + * + * If voting for a proxy, the producer votes will not change until the proxy updates their own vote. + */ + void system_contract::voteproducer( const account_name voter_name, const account_name proxy, const std::vector& producers ) { + require_auth( voter_name ); + update_votes( voter_name, proxy, producers, true ); + } + + void system_contract::update_votes( const account_name voter_name, const account_name proxy, const std::vector& producers, bool voting ) { + //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 { + //##YTA-Change start: + //eosio_assert( producers.size() <= 30, "attempt to vote for too many producers" ); + // One voter can only vote for one producer + eosio_assert( producers.size() <= 1, "attempt to vote for too many producers" ); + //##YTA-Change end: + for( size_t i = 1; i < producers.size(); ++i ) { + eosio_assert( producers[i-1] < producers[i], "producer votes must be unique and sorted" ); + } + } + + auto voter = _voters.find(voter_name); + eosio_assert( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object + eosio_assert( !proxy || !voter->is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + + /** + * The first time someone votes we calculate and set last_vote_weight, since they cannot unstake until + * after total_activated_stake hits threshold, we can use last_vote_weight to determine that this is + * their first vote and should consider their stake activated. + */ + if( voter->last_vote_weight <= 0.0 ) { + _gstate.total_activated_stake += voter->staked; + if( _gstate.total_activated_stake >= min_activated_stake && _gstate.thresh_activated_stake_time == 0 ) { + _gstate.thresh_activated_stake_time = current_time(); + } + } + + auto new_vote_weight = stake2vote( voter->staked ); + if( voter->is_proxy ) { + new_vote_weight += voter->proxied_vote_weight; + } + + boost::container::flat_map > producer_deltas; + if ( voter->last_vote_weight > 0 ) { + if( voter->proxy ) { + auto old_proxy = _voters.find( voter->proxy ); + eosio_assert( old_proxy != _voters.end(), "old proxy not found" ); //data corruption + _voters.modify( old_proxy, 0, [&]( auto& vp ) { + vp.proxied_vote_weight -= voter->last_vote_weight; + }); + propagate_weight_change( *old_proxy ); + } else { + for( const auto& p : voter->producers ) { + auto& d = producer_deltas[p]; + d.first -= voter->last_vote_weight; + d.second = false; + } + } + } + + if( proxy ) { + auto new_proxy = _voters.find( proxy ); + eosio_assert( new_proxy != _voters.end(), "invalid proxy specified" ); //if ( !voting ) { data corruption } else { wrong vote } + eosio_assert( !voting || new_proxy->is_proxy, "proxy not found" ); + if ( new_vote_weight >= 0 ) { + _voters.modify( new_proxy, 0, [&]( auto& vp ) { + vp.proxied_vote_weight += new_vote_weight; + }); + propagate_weight_change( *new_proxy ); + } + } else { + if( new_vote_weight >= 0 ) { + for( const auto& p : producers ) { + auto& d = producer_deltas[p]; + d.first += new_vote_weight; + d.second = true; + } + } + } + + for( const auto& pd : producer_deltas ) { + double total_votes = 0; + auto pitr = _producers.find( pd.first ); + if( pitr != _producers.end() ) { + eosio_assert( !voting || pitr->active() || !pd.second.second /* not from new set */, "producer is not currently registered" ); + _producers.modify( pitr, 0, [&]( auto& p ) { + p.total_votes += pd.second.first; + if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers + p.total_votes = 0; + } + _gstate.total_producer_vote_weight += pd.second.first; + //eosio_assert( p.total_votes >= 0, "something bad happened" ); + total_votes = p.total_votes; + }); + } else { + eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption + } + + //##YTA-Change start: + auto pitr2 = _producers2.find( pd.first ); + if( pitr2 != _producers2.end() ) { + //pitr2->seq_num + update_producers_seq_totalvotes(pitr2->seq_num, pd.first, total_votes); + } else { + eosio_assert( !pd.second.second /* not from new set */, "producer is not registered" ); //data corruption + } + //##YTA-Change end: + } + + _voters.modify( voter, 0, [&]( auto& av ) { + av.last_vote_weight = new_vote_weight; + av.producers = producers; + av.proxy = proxy; + }); + } + + /** + * 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. + * + * @param isproxy - true if proxy wishes to vote on behalf of others, false otherwise + * @pre proxy must have something staked (existing row in voters table) + * @pre new state must be different than current state + */ + void system_contract::regproxy( const account_name proxy, bool isproxy ) { + require_auth( proxy ); + + auto pitr = _voters.find(proxy); + if ( pitr != _voters.end() ) { + eosio_assert( isproxy != pitr->is_proxy, "action has no effect" ); + eosio_assert( !isproxy || !pitr->proxy, "account that uses a proxy is not allowed to become a proxy" ); + _voters.modify( pitr, 0, [&]( auto& p ) { + p.is_proxy = isproxy; + }); + propagate_weight_change( *pitr ); + } else { + _voters.emplace( proxy, [&]( auto& p ) { + p.owner = proxy; + p.is_proxy = isproxy; + }); + } + } + + void system_contract::propagate_weight_change( const voter_info& voter ) { + eosio_assert( voter.proxy == 0 || !voter.is_proxy, "account registered as a proxy is not allowed to use a proxy" ); + double new_weight = stake2vote( voter.staked ); + if ( voter.is_proxy ) { + new_weight += voter.proxied_vote_weight; + } + + /// don't propagate small changes (1 ~= epsilon) + if ( fabs( new_weight - voter.last_vote_weight ) > 1 ) { + if ( voter.proxy ) { + auto& proxy = _voters.get( voter.proxy, "proxy not found" ); //data corruption + _voters.modify( proxy, 0, [&]( auto& p ) { + p.proxied_vote_weight += new_weight - voter.last_vote_weight; + } + ); + propagate_weight_change( proxy ); + } else { + auto delta = new_weight - voter.last_vote_weight; + for ( auto acnt : voter.producers ) { + auto& pitr = _producers.get( acnt, "producer not found" ); //data corruption + _producers.modify( pitr, 0, [&]( auto& p ) { + p.total_votes += delta; + _gstate.total_producer_vote_weight += delta; + }); + } + } + } + _voters.modify( voter, 0, [&]( auto& v ) { + v.last_vote_weight = new_weight; + } + ); + } + +} /// namespace eosiosystem -- GitLab