diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 4480d48530..70dff71538 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -424,4 +424,55 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator: return {}; } FC_RETHROW_EXCEPTIONS( error, "Unable to upgrade account '${a}'", ("a",o.account_to_upgrade(db()).name) ) } +void_result account_update_votes_evaluator::do_evaluate(const account_update_votes_operation& o) +{ try { + database& d = db(); + FC_ASSERT(d.head_block_time() >= HARDFORK_BSIP_47_TIME, "Not allowed until BSIP47 HARDFORK"); // can remove after HF + account = &o.account(d); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (o) ) } + +void_result account_update_votes_evaluator::do_apply(const account_update_votes_operation& o) +{ try { + database& d = db(); + const auto& chain_parameters = d.get_global_properties().parameters; + + d.modify(*account, [o, chain_parameters](account_object& a) { + + if(o.voting_account.valid()) + a.options.voting_account = *o.voting_account; + + if(o.committee_voting_account.valid() || o.witness_voting_account.valid() || o.worker_voting_account.valid()) + a.options.voting_account = GRAPHENE_PROXY_PER_CATEGORY_ACCOUNT; + + if(o.worker_voting_account.valid()) + a.options.extensions.value.worker_voting_account = *o.worker_voting_account; + if(o.witness_voting_account.valid()) + a.options.extensions.value.witness_voting_account = *o.witness_voting_account; + if(o.committee_voting_account.valid()) + a.options.extensions.value.committee_voting_account = *o.committee_voting_account; + + if(o.num_witness.valid()) + a.options.num_witness = std::min(*o.num_witness, chain_parameters.maximum_witness_count); + if(o.num_committee.valid()) + a.options.num_committee = std::min(*o.num_committee, chain_parameters.maximum_committee_count); + + auto current_votes = a.options.votes; + if(o.votes_to_add.valid()) { + for(auto const& add: *o.votes_to_add) { + current_votes.insert(add); + a.options.votes = current_votes; + } + } + if(o.votes_to_remove.valid()) { + for (auto const &remove: *o.votes_to_remove) { + current_votes.erase(remove); + a.options.votes = current_votes; + } + } + }); + return void_result(); + +} FC_CAPTURE_AND_RETHROW( (o) ) } + } } // graphene::chain diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 67a0fb8201..4d65b119c3 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -185,6 +185,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 1bdcc67d1a..b57b62efec 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -1110,28 +1110,59 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g d._total_voting_stake = 0; } + void fill_buffer(flat_set votes, uint64_t stake, optional type = {}) + { + for (vote_id_type id : votes) { + if (!type.valid() || (type.valid() && id.type() == type->type())) { + uint32_t offset = id.instance(); + // if they somehow managed to specify an illegal offset, ignore it. + if (offset < d._vote_tally_buffer.size()) + d._vote_tally_buffer[offset] += stake; + } + } + } + void operator()( const account_object& stake_account, const account_statistics_object& stats ) { if( props.parameters.count_non_member_votes || stake_account.is_member(d.head_block_time()) ) { // There may be a difference between the account whose stake is voting and the one specifying opinions. - // Usually they're the same, but if the stake account has specified a voting_account, that account is the one - // specifying the opinions. - const account_object& opinion_account = - (stake_account.options.voting_account == - GRAPHENE_PROXY_TO_SELF_ACCOUNT)? stake_account - : d.get(stake_account.options.voting_account); + // Furthermore after BSIP47 users can delegate opinions for each referendum category + // If the stake account has specified a voting_account, that account is the one specifying the opinions. + // If the stake account has specified a committee_voting_account, witness_voting_account or + // worker_voting_account, those will be the accounts voting for the corresponding referendum category. + // A user specifying 1 or 2 category voting accounts will delegate opinion only for specified referendums + // while can express own opinions in the others. + + const auto& dgpo = d.get_dynamic_global_properties(); + const auto& opinion_account = (stake_account.options.voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT) ? + stake_account : d.get(stake_account.options.voting_account); uint64_t voting_stake = stats.total_core_in_orders.value + (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0) + stats.core_in_balance.value; - for( vote_id_type id : opinion_account.options.votes ) - { - uint32_t offset = id.instance(); - // if they somehow managed to specify an illegal offset, ignore it. - if( offset < d._vote_tally_buffer.size() ) - d._vote_tally_buffer[offset] += voting_stake; + if(dgpo.next_maintenance_time >= HARDFORK_BSIP_47_TIME && + stake_account.options.voting_account == GRAPHENE_PROXY_PER_CATEGORY_ACCOUNT) { + const auto& extensions = stake_account.options.extensions.value; + if (extensions.committee_voting_account.valid()) { + auto committee_voting_account = *extensions.committee_voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT ? + stake_account : d.get(*extensions.committee_voting_account); + fill_buffer(committee_voting_account.options.votes, voting_stake, vote_id_type::committee); + } + if (extensions.witness_voting_account.valid()) { + auto witness_voting_account = *extensions.witness_voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT ? + stake_account : d.get(*extensions.witness_voting_account); + fill_buffer(witness_voting_account.options.votes, voting_stake, vote_id_type::witness); + } + if (extensions.worker_voting_account.valid()) { + auto worker_voting_account = *extensions.worker_voting_account == GRAPHENE_PROXY_TO_SELF_ACCOUNT ? + stake_account : d.get(*extensions.worker_voting_account); + fill_buffer(worker_voting_account.options.votes, voting_stake, vote_id_type::worker); + } + } + else { + fill_buffer(opinion_account.options.votes, voting_stake); } if( opinion_account.options.num_witness <= props.parameters.maximum_witness_count ) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 82c5097176..f70366ab5d 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -305,6 +305,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // account } + void operator()( const account_update_votes_operation& op ) + { + _impacted.insert( op.fee_payer() ); + } }; } // namespace detail diff --git a/libraries/chain/hardfork.d/BSIP_47.hf b/libraries/chain/hardfork.d/BSIP_47.hf new file mode 100644 index 0000000000..2cbf44b3da --- /dev/null +++ b/libraries/chain/hardfork.d/BSIP_47.hf @@ -0,0 +1,4 @@ +// BSIP47 - Vote Proxies for Different Referendum Categories and explicit voting operation +#ifndef HARDFORK_BSIP_47_TIME +#define HARDFORK_BSIP_47_TIME (fc::time_point_sec( 1600000000 ) ) // Sep 2020 +#endif diff --git a/libraries/chain/include/graphene/chain/account_evaluator.hpp b/libraries/chain/include/graphene/chain/account_evaluator.hpp index e543c90dab..524a339bd8 100644 --- a/libraries/chain/include/graphene/chain/account_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/account_evaluator.hpp @@ -69,4 +69,15 @@ class account_whitelist_evaluator : public evaluator +{ +public: + typedef account_update_votes_operation operation_type; + + void_result do_evaluate( const account_update_votes_operation& o); + void_result do_apply( const account_update_votes_operation& o); + + const account_object* account; +}; + } } // graphene::chain diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 20e589179c..e643e36670 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -90,6 +90,9 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::custom_authority_delete_operation&) const { FC_ASSERT( HARDFORK_BSIP_40_PASSED(block_time), "Not allowed until hardfork BSIP 40" ); } + void operator()(const graphene::chain::account_update_votes_operation &op) const { + FC_ASSERT( block_time >= HARDFORK_BSIP_47_TIME, "Not allowed until hardfork BSIP 47" ); + } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { bool already_contains_proposal_update = false; diff --git a/libraries/protocol/account.cpp b/libraries/protocol/account.cpp index 72c3adf5ed..f23cc8af06 100644 --- a/libraries/protocol/account.cpp +++ b/libraries/protocol/account.cpp @@ -271,6 +271,11 @@ void account_transfer_operation::validate()const FC_ASSERT( fee.amount >= 0 ); } +void account_update_votes_operation::validate()const +{ + FC_ASSERT( fee.amount >= 0 ); +} + } } // graphene::protocol GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_options ) @@ -279,8 +284,10 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_votes_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_votes_operation ) diff --git a/libraries/protocol/include/graphene/protocol/account.hpp b/libraries/protocol/include/graphene/protocol/account.hpp index 02aba3a3f7..7c89870946 100644 --- a/libraries/protocol/include/graphene/protocol/account.hpp +++ b/libraries/protocol/include/graphene/protocol/account.hpp @@ -31,6 +31,13 @@ #include namespace graphene { namespace protocol { + struct additional_account_options + { + optional committee_voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + optional witness_voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + optional worker_voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + }; + typedef extension additional_account_options_t; bool is_valid_name( const string& s ); bool is_cheap_name( const string& n ); @@ -56,7 +63,7 @@ namespace graphene { namespace protocol { /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this /// account's balance of core asset. flat_set votes; - extensions_type extensions; + additional_account_options_t extensions; /// Whether this account is voting inline bool is_voting() const @@ -268,8 +275,53 @@ namespace graphene { namespace protocol { void validate()const; }; + /* + * @brief explicit operation for voting + * @ingroup operations + * + * Vote for the 3 referendum categories(committee, witness, worker), the number of witnesses + * and committee members the blockchain can have, update your proxy. + * + * Additionally the operation will allow the user to select specific proxies for each of the 3 referendum categories. + */ + struct account_update_votes_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + + asset fee; + + /// The account to update + account_id_type account; + + /// Votes to add or remove + optional> votes_to_add; + optional> votes_to_remove; + + // A new voting account + optional voting_account; + + // Voting accounts by referendum category + optional committee_voting_account; + optional witness_voting_account; + optional worker_voting_account; + + // A new number of witness + optional num_witness; + + // A new number of committee member + optional num_committee; + + // For future extensions + extensions_type extensions; + + account_id_type fee_payer()const { return account; } + void validate()const; + }; + } } // graphene::protocol +FC_REFLECT( graphene::protocol::additional_account_options, + (committee_voting_account)(witness_voting_account)(worker_voting_account)) FC_REFLECT(graphene::protocol::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions)) FC_REFLECT_ENUM( graphene::protocol::account_whitelist_operation::account_listing, (no_listing)(white_listed)(black_listed)(white_and_black_listed)) @@ -298,17 +350,26 @@ FC_REFLECT( graphene::protocol::account_whitelist_operation::fee_parameters_type FC_REFLECT( graphene::protocol::account_update_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::protocol::account_upgrade_operation::fee_parameters_type, (membership_annual_fee)(membership_lifetime_fee) ) FC_REFLECT( graphene::protocol::account_transfer_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::account_update_votes_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::protocol::account_transfer_operation, (fee)(account_id)(new_owner)(extensions) ) +FC_REFLECT( graphene::protocol::account_update_votes_operation, (fee)(account)(votes_to_add) + (votes_to_remove)(voting_account) + (committee_voting_account)(witness_voting_account) + (worker_voting_account)(num_witness)(num_committee) + (extensions)) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::additional_account_options ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_options ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_votes_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_whitelist_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_upgrade_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_transfer_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::account_update_votes_operation ) diff --git a/libraries/protocol/include/graphene/protocol/config.hpp b/libraries/protocol/include/graphene/protocol/config.hpp index b535e67158..28d74f6bd4 100644 --- a/libraries/protocol/include/graphene/protocol/config.hpp +++ b/libraries/protocol/include/graphene/protocol/config.hpp @@ -133,6 +133,8 @@ #define GRAPHENE_TEMP_ACCOUNT (graphene::protocol::account_id_type(4)) /// Represents the canonical account for specifying you will vote directly (as opposed to a proxy) #define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::protocol::account_id_type(5)) +/// Represents the canonical account for specifying you will vote per referendum category +#define GRAPHENE_PROXY_PER_CATEGORY_ACCOUNT (graphene::protocol::account_id_type(6)) /// Sentinel value used in the scheduler. #define GRAPHENE_NULL_WITNESS (graphene::protocol::witness_id_type(0)) ///@} diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index 9506628699..1a592c829e 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -103,10 +103,11 @@ namespace graphene { namespace protocol { htlc_redeemed_operation, // VIRTUAL htlc_extend_operation, htlc_refund_operation, // VIRTUAL + account_update_votes_operation, custom_authority_create_operation, custom_authority_update_operation, custom_authority_delete_operation - > operation; + > operation; /// @} // operations group diff --git a/tests/tests/voting_tests.cpp b/tests/tests/voting_tests.cpp index 1d61c65bc9..bbf02278e0 100644 --- a/tests/tests/voting_tests.cpp +++ b/tests/tests/voting_tests.cpp @@ -578,4 +578,360 @@ BOOST_AUTO_TEST_CASE(last_voting_date_proxy) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(simple_account_update_votes_operation) +{ + try + { + generate_blocks(HARDFORK_BSIP_47_TIME); + + generate_block(); + set_expiration( db, trx ); + + ACTORS((alice)(bob)); + fund(alice); + + auto alice_object = get_account("alice"); + + BOOST_CHECK_EQUAL(alice_object.options.num_witness , 0); + BOOST_CHECK_EQUAL(alice_object.options.num_committee , alice_object.options.votes.size()); + + // a few votable objects + auto witness1 = witness_id_type(1)(db); + auto witness2 = witness_id_type(2)(db); + auto witness3 = witness_id_type(3)(db); + auto committee1 = committee_member_id_type(1)(db); + + // add votes + { + flat_set add; + add.insert(witness1.vote_id); + add.insert(witness2.vote_id); + add.insert(witness3.vote_id); + add.insert(committee1.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.num_committee = 6; + op.num_witness = 6; + op.votes_to_add = add; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + alice_object = get_account("alice"); + + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 5); + + auto itr = alice_object.options.votes.find(witness1.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(witness2.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(witness3.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(committee1.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + BOOST_CHECK_EQUAL(alice_object.options.num_witness , 6); + BOOST_CHECK_EQUAL(alice_object.options.num_committee , 6); + + set_expiration( db, trx ); + + // remove votes + { + flat_set remove; + remove.insert(witness2.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.votes_to_remove = remove; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + alice_object = get_account("alice"); + + itr = alice_object.options.votes.find(witness1.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(witness2.vote_id); + BOOST_CHECK(itr == alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(witness3.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + itr = alice_object.options.votes.find(committee1.vote_id); + BOOST_CHECK(itr != alice_object.options.votes.end()); + + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 5); + + // change voting account + { + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.voting_account = bob_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + alice_object = get_account("alice"); + + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, bob_id.instance.value); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(proxy_account_update_votes_operation) +{ + try + { + generate_blocks(HARDFORK_BSIP_47_TIME); + + generate_block(); + set_expiration( db, trx ); + + ACTORS((alice)(proxycommittee)(proxywitness)); + fund(alice); + + auto alice_object = get_account("alice"); + + // add a committee proxy + { + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.committee_voting_account = proxycommittee_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + alice_object = get_account("alice"); + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 6); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.committee_voting_account->instance.value, + proxycommittee_id.instance.value); + + // proxy votes for something + auto committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 0); + { + flat_set add; + add.insert(committee1.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = proxycommittee_id; + op.votes_to_add = add; + trx.operations.push_back(op); + sign(trx, proxycommittee_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 500000); + + set_expiration( db, trx ); + // add a witness proxy to alice + { + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.witness_voting_account = proxywitness_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + alice_object = get_account("alice"); + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 6); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.witness_voting_account->instance.value, + proxywitness_id.instance.value); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.committee_voting_account->instance.value, + proxycommittee_id.instance.value); + + set_expiration( db, trx ); + // proxy votes for something + auto witness1 = witness_id_type(1)(db); + BOOST_CHECK_EQUAL(witness1.total_votes, 0); + { + flat_set add; + add.insert(witness1.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = proxywitness_id; + op.votes_to_add = add; + trx.operations.push_back(op); + sign(trx, proxywitness_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + witness1 = witness_id_type(1)(db); + BOOST_CHECK_EQUAL(witness1.total_votes, 500000); + + set_expiration( db, trx ); + // proxy removes witness vote + { + flat_set remove; + remove.insert(witness1.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = proxywitness_id; + op.votes_to_remove = remove; + trx.operations.push_back(op); + sign(trx, proxywitness_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + witness1 = witness_id_type(1)(db); + BOOST_CHECK_EQUAL(witness1.total_votes, 0); + committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 500000); + + // get more stake + fund(alice); + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 1000000); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(self_and_proxies_combined) +{ + try + { + generate_blocks(HARDFORK_BSIP_47_TIME); + + generate_block(); + set_expiration( db, trx ); + + ACTORS((alice)(proxycommittee)); + fund(alice); + + auto alice_object = get_account("alice"); + + // add a committee proxy + { + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.committee_voting_account = proxycommittee_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + alice_object = get_account("alice"); + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 6); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.committee_voting_account->instance.value, + proxycommittee_id.instance.value); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.witness_voting_account->instance.value, 5); // self + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.worker_voting_account->instance.value, 5); // self + + set_expiration( db, trx ); + + // remove all default votes from proxy + { + auto remove = proxycommittee_id(db).options.votes; + + graphene::chain::account_update_votes_operation op; + op.account = proxycommittee_id; + op.votes_to_remove = remove; + trx.operations.push_back(op); + sign(trx, proxycommittee_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + BOOST_CHECK_EQUAL(proxycommittee_id(db).options.votes.size(), 0); + + auto witness1 = witness_id_type(1)(db); + auto committee1 = committee_member_id_type(1)(db); + + set_expiration( db, trx ); + + // alice votes for a committee and a witness + BOOST_CHECK_EQUAL(witness1.total_votes, 0); + { + flat_set add; + add.insert(witness1.vote_id); + add.insert(committee1.vote_id); + + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.votes_to_add = add; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + alice_object = get_account("alice"); + BOOST_CHECK_EQUAL(alice_object.options.voting_account.instance.value, 6); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.committee_voting_account->instance.value, + proxycommittee_id.instance.value); + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.witness_voting_account->instance.value, 5); // self + BOOST_CHECK_EQUAL(alice_object.options.extensions.value.worker_voting_account->instance.value, 5); // self + + witness1 = witness_id_type(1)(db); + BOOST_CHECK_EQUAL(witness1.total_votes, 500000); // counted, voted by my own + + committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 0); // not counted, proxy is not voting for it + + set_expiration( db, trx ); + + // remove committee proxy + { + graphene::chain::account_update_votes_operation op; + op.account = alice_id; + op.committee_voting_account = GRAPHENE_PROXY_TO_SELF_ACCOUNT; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + witness1 = witness_id_type(1)(db); + BOOST_CHECK_EQUAL(witness1.total_votes, 500000); // counted, voted by my own + + committee1 = committee_member_id_type(1)(db); + BOOST_CHECK_EQUAL(committee1.total_votes, 500000); // counted, voted by my own + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END()