diff --git a/contracts/eosio.system/eosio.system.abi b/contracts/eosio.system/eosio.system.abi index 79e6714208f4e00a600253d78f1cd687bc6e4fa9..946fc1080c58afb3b7f2ee72f3b3f98f47f9bbdb 100644 --- a/contracts/eosio.system/eosio.system.abi +++ b/contracts/eosio.system/eosio.system.abi @@ -106,7 +106,7 @@ "name": "producer_info", "base": "", "fields": [ - {"name":"owner", "type":"uint64"}, + {"name":"owner", "type":"account_name"}, {"name":"total_votes", "type":"uint128"}, {"name":"prefs", "type":"eosio_parameters"}, {"name":"packed_key", "type":"uint8[]"} diff --git a/contracts/eosio.system/voting.hpp b/contracts/eosio.system/voting.hpp index 259b676bed1a8ae7fee4f0bb449754a1180d977c..08d44a8ecd7b6b08c6e24a2695036950cbcadeea 100644 --- a/contracts/eosio.system/voting.hpp +++ b/contracts/eosio.system/voting.hpp @@ -427,7 +427,7 @@ namespace eosiosystem { voters_table voters_tbl( SystemAccount, SystemAccount ); auto voter = voters_tbl.find( vp.voter ); - eosio_assert( bool(voter), "no stake to vote" ); + eosio_assert( bool(voter) && ( 0 < voter->staked.quantity || ( voter->is_proxy && 0 < voter->proxied_votes ) ), "no stake to vote" ); if ( voter->is_proxy ) { eosio_assert( vp.proxy == 0 , "accounts elected to be proxy are not allowed to use another proxy" ); } @@ -460,6 +460,10 @@ namespace eosiosystem { } 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 == 0 if proxy has stoped being a proxy and votes were taken back from producers at that moment //revoke votes only from no longer elected @@ -468,7 +472,7 @@ namespace eosiosystem { for ( auto it = revoked.begin(); it != end_it; ++it ) { auto prod = producers_tbl.find( *it ); eosio_assert( bool(prod), "never existed producer" ); //data corruption - producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes -= voter->staked.quantity; }); + producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes -= votes; } ); } } @@ -481,7 +485,7 @@ namespace eosiosystem { 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.update( *prod, 0, [&]( auto& pi ) { pi.total_votes += voter->staked.quantity; }); + producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes += votes; } ); } // save new values to the account itself @@ -502,15 +506,23 @@ namespace eosiosystem { require_auth( reg.proxy ); voters_table voters_tbl( SystemAccount, SystemAccount ); - auto voter = voters_tbl.find( reg.proxy ); - if ( voter ) { - eosio_assert( voter->is_proxy == 0, "account is already a proxy" ); - eosio_assert( voter->proxy == 0, "account that uses a proxy is not allowed to become a proxy" ); - voters_tbl.update( *voter, 0, [&](voter_info& a) { + auto proxy = voters_tbl.find( reg.proxy ); + if ( proxy ) { + 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.update( *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( bool(prod), "never existed producer" ); //data corruption + producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes += proxy->proxied_votes; }); + } + } } else { voters_tbl.emplace( reg.proxy, [&]( voter_info& a ) { a.owner = reg.proxy; @@ -537,18 +549,20 @@ namespace eosiosystem { eosio_assert( bool(proxy), "proxy not found" ); eosio_assert( proxy->is_proxy == 1, "account is already a proxy" ); - producers_table producers_tbl( SystemAccount, SystemAccount ); - for ( auto p : proxy->producers ) { - auto prod = producers_tbl.find( p ); - eosio_assert( bool(prod), "never existed producer" ); //data corruption - producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes -= proxy->proxied_votes; }); - } - voters_tbl.update( *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( bool(prod), "never existed producer" ); //data corruption + producers_tbl.update( *prod, 0, [&]( auto& pi ) { pi.total_votes -= proxy->proxied_votes; }); + } + } } }; diff --git a/tests/wasm_tests/eosio.system_tests.cpp b/tests/wasm_tests/eosio.system_tests.cpp index 9dd95c6eda3a899a8d063111c9b246a7904021d4..3b1ea4e52be716a300ee0c9f6212cc9fe25176c9 100644 --- a/tests/wasm_tests/eosio.system_tests.cpp +++ b/tests/wasm_tests/eosio.system_tests.cpp @@ -26,7 +26,7 @@ public: eosio_system_tester() { produce_blocks( 2 ); - create_accounts( { N(alice), N(bob), N(carol), N(donald) } ); + create_accounts( { N(alice), N(bob), N(carol) } ); produce_blocks( 1000 ); set_code( config::system_account_name, eosio_system_wast ); @@ -45,7 +45,7 @@ public: */ } - action_result push_action(const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) { + action_result push_action( const account_name& signer, const action_name &name, const variant_object &data, bool auth = true ) { string action_type_name = abi_ser.get_action_type(name); action act; @@ -56,7 +56,7 @@ public: return base_tester::push_action( std::move(act), auth ? uint64_t(signer) : 0 ); } - action_result stake(const account_name& from, const account_name& to, const string& net, const string& cpu, const string& storage) { + action_result stake( const account_name& from, const account_name& to, const string& net, const string& cpu, const string& storage ) { return push_action( name(from), N(delegatebw), mvo() ("from", from) ("receiver", to) @@ -66,11 +66,11 @@ public: ); } - inline action_result stake(const account_name& acnt, const string& net, const string& cpu, const string& storage) { - return stake( acnt, acnt, net, cpu, storage); + action_result stake( const account_name& acnt, const string& net, const string& cpu, const string& storage ) { + return stake( acnt, acnt, net, cpu, storage ); } - action_result unstake(const account_name& from, const account_name& to, const string& net, const string& cpu, uint64_t bytes) { + action_result unstake( const account_name& from, const account_name& to, const string& net, const string& cpu, uint64_t bytes ) { return push_action( name(from), N(undelegatebw), mvo() ("from", from) ("receiver", to) @@ -80,10 +80,37 @@ public: ); } - inline action_result unstake(const account_name& acnt, const string& net, const string& cpu, uint64_t bytes) { + action_result unstake( const account_name& acnt, const string& net, const string& cpu, uint64_t bytes ) { return unstake( acnt, acnt, net, cpu, bytes ); } + static fc::variant producer_parameters_example( int n ) { + return mutable_variant_object() + ("target_block_size", 1024 * 1024 + n) + ("max_block_size", 10 * 1024 + n) + ("target_block_acts_per_scope", 1000 + n) + ("max_block_acts_per_scope", 10000 + n) + ("target_block_acts", 1100 + n) + ("max_block_acts", 11000 + n) + ("max_storage_size", 2000 + n) + ("max_transaction_lifetime", 3600 + n) + ("max_transaction_exec_time", 9900 + n) + ("max_authority_depth", 6 + n) + ("max_inline_depth", 4 + n) + ("max_inline_action_size", 4096 + n) + ("max_generated_transaction_size", 64*1024 + n) + ("inflation_rate", 1050 + n) + ("storage_reserve_ratio", 100 + n); + } + + action_result regproducer( const account_name& acnt, int params_fixture = 1 ) { + return push_action( acnt, N(regproducer), mvo() + ("producer", name(acnt).to_string() ) + ("producer_key", fc::raw::pack( get_public_key( acnt, "active" ) ) ) + ("prefs", producer_parameters_example( params_fixture ) ) + ); + } + uint32_t last_block_time() const { return time_point_sec( control->head_block_time() ).sec_since_epoch(); } @@ -377,26 +404,6 @@ BOOST_FIXTURE_TEST_CASE( adding_stake_partial_unstake, eosio_system_tester ) try // Tests for voting -static fc::variant producer_parameters_example(int n) { - return mutable_variant_object() - ("target_block_size", 1024 * 1024 + n) - ("max_block_size", 10 * 1024 + n) - ("target_block_acts_per_scope", 1000 + n) - ("max_block_acts_per_scope", 10000 + n) - ("target_block_acts", 1100 + n) - ("max_block_acts", 11000 + n) - ("max_storage_size", 2000 + n) - ("max_transaction_lifetime", 3600 + n) - ("max_transaction_exec_time", 9900 + n) - ("max_authority_depth", 6 + n) - ("max_inline_depth", 4 + n) - ("max_inline_action_size", 4096 + n) - ("max_generated_transaction_size", 64*1024 + n) - ("inflation_rate", 1050 + n) - ("storage_reserve_ratio", 100 + n); -} - - BOOST_FIXTURE_TEST_CASE( producer_register_unregister, eosio_system_tester ) try { issue( "alice", "1000.0000 EOS", config::system_account_name ); @@ -778,7 +785,7 @@ BOOST_FIXTURE_TEST_CASE( vote_for_two_producers, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( proxy_register_unregister, eosio_system_tester ) try { +BOOST_FIXTURE_TEST_CASE( proxy_register_unregister_keeps_stake, eosio_system_tester ) try { //register proxy by first action for this user ever BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(regproxy), mvo() ("proxy", "alice") @@ -857,13 +864,112 @@ BOOST_FIXTURE_TEST_CASE( proxy_stake_unstake_keeps_proxy_flag, eosio_system_test } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( proxy_users, eosio_system_tester ) try { +BOOST_FIXTURE_TEST_CASE( proxy_actions_affect_producers, eosio_system_tester ) try { + create_accounts( { N(producer1), N(producer2), N(producer3) } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); + + //register as a proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ) + ); + + //accumulate proxied votes + issue( "bob", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bob", "100.0002 EOS", "50.0001 EOS", "50.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(bob), N(voteproducer), mvo() + ("voter", "bob") + ("proxy", "alice" ) + ("producers", vector() ) + ) + ); + REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 1500003 ), get_voter_info( "alice" ) ); + + //vote for producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer2) } ) + ) + ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + + //vote for another producers + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer3) } ) + ) + ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + + //unregister proxy + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(unregproxy), mvo() + ("proxy", "alice") + ) + ); + //REQUIRE_MATCHING_OBJECT( voter( "alice" )( "proxied_votes", 1500003 ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + + //register proxy again + BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() + ("proxy", "alice") + ) + ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 1500003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + + //stake increase by proxy itself affects producers + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS", "50.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + + //stake decrease by proxy itself affects producers + BOOST_REQUIRE_EQUAL( success(), unstake( "alice", "10.0001 EOS", "10.0001 EOS", 0 ) ); + BOOST_REQUIRE_EQUAL( 1800003, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 1800003, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( voters_actions_affect_proxy_and_producers, eosio_system_tester ) try { + create_accounts( { N(donald), N(producer1), N(producer2), N(producer3) } ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer1", 1) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer2", 2) ); + BOOST_REQUIRE_EQUAL( success(), regproducer( "producer3", 3) ); + + //alice becomes a producer BOOST_REQUIRE_EQUAL( success(), push_action( N(alice), N(regproxy), mvo() ("proxy", "alice") ) ); REQUIRE_MATCHING_OBJECT( proxy( "alice" ), get_voter_info( "alice" ) ); + //alice makes stake and votes + issue( "alice", "1000.0000 EOS", config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "alice", "30.0001 EOS", "20.0001 EOS", "50.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), push_action(N(alice), N(voteproducer), mvo() + ("voter", "alice") + ("proxy", name(0).to_string() ) + ("producers", vector{ N(producer1), N(producer2) } ) + ) + ); + BOOST_REQUIRE_EQUAL( 500002, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 500002, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( success(), push_action( N(donald), N(regproxy), mvo() ("proxy", "donald") ) @@ -879,27 +985,33 @@ BOOST_FIXTURE_TEST_CASE( proxy_users, eosio_system_tester ) try { ("producers", vector() ) ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 1500003 ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 1500003, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 2000005, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); //carol chooses alice as a proxy issue( "carol", "1000.0000 EOS", config::system_account_name ); - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "50.0001 EOS", "20.0001 EOS", "80.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "30.0001 EOS", "20.0001 EOS", "80.0000 EOS" ) ); BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() ("voter", "carol") ("proxy", "alice" ) ("producers", vector() ) ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 2200005 ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 2000005, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 2500007, get_producer_info( "producer1" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 2500007, get_producer_info( "producer2" )["total_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 0, get_producer_info( "producer3" )["total_votes"].as_uint64() ); - //carol increases stake - BOOST_REQUIRE_EQUAL( success(), stake( "carol", "50.0000 EOS", "50.0000 EOS", "0.0000 EOS" ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 3200005 ), get_voter_info( "alice" ) ); + //proxied voter (carol) increases stake + BOOST_REQUIRE_EQUAL( success(), stake( "carol", "50.0000 EOS", "70.0000 EOS", "0.0000 EOS" ) ); + BOOST_REQUIRE_EQUAL( 3200005, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); //bob decreases stake BOOST_REQUIRE_EQUAL( success(), unstake( "bob", "50.0001 EOS", "50.0001 EOS", 0 ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 2200003 ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 2200003, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); //carol chooses another proxy BOOST_REQUIRE_EQUAL( success(), push_action( N(carol), N(voteproducer), mvo() @@ -908,8 +1020,8 @@ BOOST_FIXTURE_TEST_CASE( proxy_users, eosio_system_tester ) try { ("producers", vector() ) ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 500001 ), get_voter_info( "alice" ) ); - REQUIRE_MATCHING_OBJECT( proxy( "donald" )( "proxied_votes", 1700002 ), get_voter_info( "donald" ) ); + BOOST_REQUIRE_EQUAL( 500001, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); + BOOST_REQUIRE_EQUAL( 1700002, get_voter_info( "donald" )["proxied_votes"].as_uint64() ); //bob chooses direct voting BOOST_REQUIRE_EQUAL( success(), push_action( N(bob), N(voteproducer), mvo() @@ -918,7 +1030,10 @@ BOOST_FIXTURE_TEST_CASE( proxy_users, eosio_system_tester ) try { ("producers", vector() ) ) ); - REQUIRE_MATCHING_OBJECT( proxy( "alice" )( "proxied_votes", 0 ), get_voter_info( "alice" ) ); + BOOST_REQUIRE_EQUAL( 0, get_voter_info( "alice" )["proxied_votes"].as_uint64() ); + + //new proxied user affects producers + } FC_LOG_AND_RETHROW()