/* * Copyright (c) 2018 oxarbitrage and contributors. * * The MIT License * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include "../common/database_fixture.hpp" #include using namespace graphene::chain; using namespace graphene::chain::test; struct gpos_fixture: database_fixture { const worker_object& create_worker( const account_id_type owner, const share_type daily_pay, const fc::microseconds& duration ) { worker_create_operation op; op.owner = owner; op.daily_pay = daily_pay; op.initializer = vesting_balance_worker_initializer(1); op.work_begin_date = db.head_block_time(); op.work_end_date = op.work_begin_date + duration; trx.operations.push_back(op); set_expiration(db, trx); trx.validate(); processed_transaction ptx = db.push_transaction(trx, ~0); trx.clear(); return db.get(ptx.operation_results[0].get()); } const vesting_balance_object& create_vesting(const account_id_type owner, const asset amount, const vesting_balance_type type) { vesting_balance_create_operation op; op.creator = owner; op.owner = owner; op.amount = amount; op.balance_type = type; trx.operations.push_back(op); set_expiration(db, trx); processed_transaction ptx = PUSH_TX(db, trx, ~0); trx.clear(); return db.get(ptx.operation_results[0].get()); } void withdraw_gpos_vesting(const vesting_balance_id_type v_bid, const account_id_type owner, const asset amount, /*const vesting_balance_type type, */const fc::ecc::private_key& key) { vesting_balance_withdraw_operation op; op.vesting_balance = v_bid; op.owner = owner; op.amount = amount; //op.balance_type = type; trx.operations.push_back(op); set_expiration(db, trx); trx.validate(); sign(trx, key); PUSH_TX(db, trx); trx.clear(); } void update_payout_interval(std::string asset_name, fc::time_point start, uint32_t interval) { auto dividend_holder_asset_object = get_asset(asset_name); asset_update_dividend_operation op; op.issuer = dividend_holder_asset_object.issuer; op.asset_to_update = dividend_holder_asset_object.id; op.new_options.next_payout_time = start; op.new_options.payout_interval = interval; trx.operations.push_back(op); set_expiration(db, trx); PUSH_TX(db, trx, ~0); trx.operations.clear(); } void update_gpos_global(uint32_t vesting_period, uint32_t vesting_subperiod, fc::time_point_sec period_start) { db.modify(db.get_global_properties(), [vesting_period, vesting_subperiod, period_start](global_property_object& p) { p.parameters.extensions.value.gpos_period = vesting_period; p.parameters.extensions.value.gpos_subperiod = vesting_subperiod; p.parameters.extensions.value.gpos_period_start = period_start.sec_since_epoch(); p.parameters.extensions.value.gpos_vesting_lockin_period = vesting_subperiod; }); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), vesting_period); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), vesting_subperiod); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), period_start.sec_since_epoch()); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_vesting_lockin_period(), vesting_subperiod); } void update_maintenance_interval(uint32_t new_interval) { db.modify(db.get_global_properties(), [new_interval](global_property_object& p) { p.parameters.maintenance_interval = new_interval; }); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maintenance_interval, new_interval); } void vote_for(const account_id_type account_id, const vote_id_type vote_for, const fc::ecc::private_key& key) { account_update_operation op; op.account = account_id; op.new_options = account_id(db).options; op.new_options->votes.insert(vote_for); op.extensions.value.update_last_voting_time = true; trx.operations.push_back(op); set_expiration(db, trx); trx.validate(); sign(trx, key); PUSH_TX(db, trx); trx.clear(); } void fill_reserve_pool(const account_id_type account_id, asset amount) { asset_reserve_operation op; op.payer = account_id; op.amount_to_reserve = amount; trx.operations.push_back(op); trx.validate(); set_expiration(db, trx); PUSH_TX( db, trx, ~0 ); trx.clear(); } void advance_x_maint(int periods) { for(int i=0; i(ptx.operation_results[0].get()); // check created vesting amount and policy BOOST_CHECK_EQUAL(alice_vesting.balance.amount.value, 100); BOOST_CHECK_EQUAL(alice_vesting.policy.get().vesting_duration_seconds, db.get_global_properties().parameters.gpos_subperiod()); BOOST_CHECK_EQUAL(alice_vesting.policy.get().vesting_cliff_seconds, db.get_global_properties().parameters.gpos_subperiod()); // bob creates a gpos vesting with his custom policy { vesting_balance_create_operation op; op.creator = bob_id; op.owner = bob_id; op.amount = core.amount(200); op.balance_type = vesting_balance_type::gpos; op.policy = cdd_vesting_policy_initializer{ 60*60*24 }; trx.operations.push_back(op); set_expiration(db, trx); ptx = PUSH_TX(db, trx, ~0); trx.clear(); } auto bob_vesting = db.get(ptx.operation_results[0].get()); generate_block(); // policy is not the one defined by the user but default BOOST_CHECK_EQUAL(bob_vesting.balance.amount.value, 200); BOOST_CHECK_EQUAL(bob_vesting.policy.get().vesting_duration_seconds, db.get_global_properties().parameters.gpos_subperiod()); BOOST_CHECK_EQUAL(bob_vesting.policy.get().vesting_cliff_seconds, db.get_global_properties().parameters.gpos_subperiod()); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( dividends ) { ACTORS((alice)(bob)); try { // move to 1 week before hardfork generate_blocks( HARDFORK_GPOS_TIME - fc::days(7) ); generate_block(); const auto& core = asset_id_type()(db); // all core coins are in the committee_account BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 1000000000000000); // transfer half of the total stake to alice so not all the dividends will go to the committee_account transfer( committee_account, alice_id, core.amount( 500000000000000 ) ); generate_block(); // send some to bob transfer( committee_account, bob_id, core.amount( 1000 ) ); generate_block(); // committee balance BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999999000); // alice balance BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 500000000000000); // bob balance BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 1000); // get core asset object const auto& dividend_holder_asset_object = get_asset(GRAPHENE_SYMBOL); // by default core token pays dividends once per month const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 2592000); // 30 days // update the payout interval for speed purposes of the test update_payout_interval(core.symbol, db.head_block_time() + fc::minutes(1), 60 * 60 * 24); // 1 day generate_block(); BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 86400); // 1 day now // get the dividend distribution account const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); // transfering some coins to distribution account. // simulating the blockchain haves some dividends to pay. transfer( committee_account, dividend_distribution_account.id, core.amount( 100 ) ); generate_block(); // committee balance BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999998900 ); // distribution account balance BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, core), 100); // get when is the next payout time as we need to advance there auto next_payout_time = dividend_data.options.next_payout_time; // advance to next payout generate_blocks(*next_payout_time); wdump((*next_payout_time)); // advance to next maint after payout time arrives generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // check balances now, dividends are paid "normally" BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999998949 ); BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 500000000000050 ); BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 1000 ); BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, core), 1); // advance to hardfork generate_blocks( HARDFORK_GPOS_TIME ); // advance to next maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // send 99 to the distribution account so it will have 100 PPY again to share transfer( committee_account, dividend_distribution_account.id, core.amount( 99 ) ); generate_block(); // get when is the next payout time as we need to advance there next_payout_time = dividend_data.options.next_payout_time; // advance to next payout generate_blocks(*next_payout_time); // advance to next maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // make sure no dividends were paid "normally" BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999998850 ); BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 500000000000050 ); BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 1000 ); BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, core), 100); // create vesting balance create_vesting(bob_id, core.amount(100), vesting_balance_type::gpos); // need to vote to get paid auto witness1 = witness_id_type(1)(db); vote_for(bob_id, witness1.vote_id, bob_private_key); generate_block(); // check balances BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 900 ); BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, core), 100); // advance to next payout generate_blocks(*next_payout_time); // advance to next maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // check balances, dividends paid to bob BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 1000 ); BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, core), 0); } catch (fc::exception& e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( gpos_basic_dividend_distribution_to_core_asset ) { using namespace graphene; ACTORS((alice)(bob)(carol)(dave)); try { const auto& core = asset_id_type()(db); BOOST_TEST_MESSAGE("Creating test asset"); { asset_create_operation creator; creator.issuer = account_id_type(); creator.fee = asset(); creator.symbol = "TESTB"; creator.common_options.max_supply = 100000000; creator.precision = 2; creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; creator.common_options.flags = charge_market_fee; creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); trx.operations.push_back(std::move(creator)); set_expiration(db, trx); PUSH_TX( db, trx, ~0 ); trx.operations.clear(); } // pass hardfork generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); const auto& dividend_holder_asset_object = asset_id_type(0)(db); const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); const account_object& alice = get_account("alice"); const account_object& bob = get_account("bob"); const account_object& carol = get_account("carol"); const account_object& dave = get_account("dave"); const auto& test_asset_object = get_asset("TESTB"); auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) { asset_issue_operation op; op.issuer = asset_to_issue.issuer; op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); op.issue_to_account = destination_account.id; trx.operations.push_back( op ); set_expiration(db, trx); PUSH_TX( db, trx, ~0 ); trx.operations.clear(); }; auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, holder_account_obj.id, payout_asset_obj.id); BOOST_CHECK_EQUAL(pending_balance, expected_balance); }; auto advance_to_next_payout_time = [&]() { // Advance to the next upcoming payout time BOOST_REQUIRE(dividend_data.options.next_payout_time); fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; idump((next_payout_scheduled_time)); // generate blocks up to the next scheduled time generate_blocks(next_payout_scheduled_time); // if the scheduled time fell on a maintenance interval, then we should have paid out. // if not, we need to advance to the next maintenance interval to trigger the payout if (dividend_data.options.next_payout_time) { // we know there was a next_payout_time set when we entered this, so if // it has been cleared, we must have already processed payouts, no need to // further advance time. BOOST_REQUIRE(dividend_data.options.next_payout_time); if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // get the maintenance skip slots out of the way } idump((db.head_block_time())); }; // the first test will be testing pending balances, so we need to hit a // maintenance interval that isn't the payout interval. Payout is // every 3 days, maintenance interval is every 1 day. advance_to_next_payout_time(); // Set up the first test, issue alice, bob, and carol, and dave each 1/4 of the total // supply of the core asset. // Then deposit 400 TEST in the distribution account, and see that they // each are credited 100 TEST. transfer( committee_account(db), alice, asset( 250000000000000 ) ); transfer( committee_account(db), bob, asset( 250000000000000 ) ); transfer( committee_account(db), carol, asset( 250000000000000 ) ); transfer( committee_account(db), dave, asset( 250000000000000 ) ); // create vesting balance // bob has not vested anything create_vesting(alice_id, core.amount(25000000), vesting_balance_type::gpos); create_vesting(carol_id, core.amount(25000000), vesting_balance_type::gpos); create_vesting(dave_id, core.amount(25000000), vesting_balance_type::gpos); // need to vote to get paid // carol doesn't participate in voting auto witness1 = witness_id_type(1)(db); vote_for(alice_id, witness1.vote_id, alice_private_key); vote_for(bob_id, witness1.vote_id, bob_private_key); vote_for(dave_id, witness1.vote_id, dave_private_key); // issuing 30000 TESTB to the dividend account // alice and dave should receive 10000 TESTB as they have gpos vesting and // participated in voting // bob should not receive any TESTB as he doesn't have gpos vested // carol should not receive any TESTB as she doesn't participated in voting // remaining 10000 TESTB should be deposited in commitee_accoount. BOOST_TEST_MESSAGE("Issuing 30000 TESTB to the dividend account"); issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); generate_block(); BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // get the maintenance skip slots out of the way verify_pending_balance(alice, test_asset_object, 10000); verify_pending_balance(bob, test_asset_object, 0); verify_pending_balance(carol, test_asset_object, 0); verify_pending_balance(dave, test_asset_object, 10000); advance_to_next_payout_time(); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // get the maintenance skip slots out of the way auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) { BOOST_TEST_MESSAGE("Verifying the virtual op was created"); const account_transaction_history_index& hist_idx = db.get_index_type(); auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); BOOST_REQUIRE(account_history_range.first != account_history_range.second); const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); BOOST_CHECK(distribution_operation.account_id == destination_account.id); BOOST_CHECK(std::find(distribution_operation.amounts.begin(), distribution_operation.amounts.end(), expected_payout) != distribution_operation.amounts.end()); }; BOOST_TEST_MESSAGE("Verifying the payouts"); BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 10000); verify_dividend_payout_operations(alice, asset(10000, test_asset_object.id)); verify_pending_balance(alice, test_asset_object, 0); BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0); verify_pending_balance(bob, test_asset_object, 0); BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0); verify_pending_balance(carol, test_asset_object, 0); BOOST_CHECK_EQUAL(get_balance(dave, test_asset_object), 10000); verify_dividend_payout_operations(dave, asset(10000, test_asset_object.id)); verify_pending_balance(dave, test_asset_object, 0); BOOST_CHECK_EQUAL(get_balance(account_id_type(0)(db), test_asset_object), 10000); } catch(fc::exception& e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( votes_on_gpos_activation ) { ACTORS((alice)(bob)); try { const auto& core = asset_id_type()(db); // send some asset to alice and bob transfer( committee_account, alice_id, core.amount( 1000 ) ); transfer( committee_account, bob_id, core.amount( 1000 ) ); generate_block(); // update default gpos // 5184000 = 60x60x24x6 = 6 days // 864000 = 60x60x24x1 = 1 days update_gpos_global(518400, 86400, HARDFORK_GPOS_TIME); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), 518400); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), 86400); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), HARDFORK_GPOS_TIME.sec_since_epoch()); // no votes for witness 1 auto witness1 = witness_id_type(1)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 0); // no votes for witness 2 auto witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness2.total_votes, 0); // vote for witness1 and witness2 - this before GPOS period starts vote_for(alice_id, witness1.vote_id, alice_private_key); vote_for(bob_id, witness2.vote_id, bob_private_key); // go to maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // vote is the same as amount in the first subperiod since voting witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 1000); BOOST_CHECK_EQUAL(witness2.total_votes, 1000); update_maintenance_interval(3600); //update maintenance interval to 1hr to evaluate sub-periods BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maintenance_interval, 3600); // move to hardfork generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 1000); BOOST_CHECK_EQUAL(witness2.total_votes, 1000); // add some vesting to alice and don't add anything for Bob create_vesting(alice_id, core.amount(99), vesting_balance_type::gpos); generate_block(); vote_for(alice_id, witness1.vote_id, alice_private_key); generate_block(); advance_x_maint(1); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); //System needs to consider votes based on both regular balance + GPOS balance for 1/2 sub-period on GPOS activation BOOST_CHECK_EQUAL(witness1.total_votes, 1000); BOOST_CHECK_EQUAL(witness2.total_votes, 1000); advance_x_maint(6); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 1000); BOOST_CHECK_EQUAL(witness2.total_votes, 1000); advance_x_maint(5); generate_block(); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); //Since Alice has votes, votes should be based on GPOS balance i.e 99 //Since Bob not voted after GPOS activation, witness2 votes should be 0 after crossing 1/2 sub-period(12 maintanence intervals in this case) BOOST_CHECK_EQUAL(witness1.total_votes, 99); BOOST_CHECK_EQUAL(witness2.total_votes, 0); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( voting ) { ACTORS((alice)(bob)); try { // move to hardfork generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); auto now = HARDFORK_GPOS_TIME; const auto& core = asset_id_type()(db); // send some asset to alice and bob transfer( committee_account, alice_id, core.amount( 1000 ) ); transfer( committee_account, bob_id, core.amount( 1000 ) ); generate_block(); // default maintenance_interval is 1 day BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maintenance_interval, 86400); // add some vesting to alice and bob create_vesting(alice_id, core.amount(100), vesting_balance_type::gpos); create_vesting(bob_id, core.amount(100), vesting_balance_type::gpos); generate_block(); // default gpos values BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), 15552000); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), 2592000); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), HARDFORK_GPOS_TIME.sec_since_epoch()); // update default gpos for test speed // 5184000 = 60x60x24x60 = 60 days // 864000 = 60x60x24x10 = 10 days update_gpos_global(5184000, 864000, now); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), 5184000); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), 864000); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), now.sec_since_epoch()); // end global changes generate_block(); // no votes for witness 1 auto witness1 = witness_id_type(1)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 0); // no votes for witness 2 auto witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness2.total_votes, 0); // vesting performance is 0 for both alice and bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 0); // vote for witness1 and witness2 - sub-period 1 vote_for(alice_id, witness1.vote_id, alice_private_key); vote_for(bob_id, witness2.vote_id, bob_private_key); // after voting, vesting performance is 1 for both alice and bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); // go to maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // need to consider both gpos and regular balance for first 1/2 sub period witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 1000); BOOST_CHECK_EQUAL(witness2.total_votes, 1000); // vesting performance is 1 for both alice and bob during whole gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(6); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 100); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // vesting performance is 1 for both alice and bob during whole gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(3); // vesting performance is 1 for both alice and bob during whole gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 5/6 for both alice and bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); //Bob votes for witness2 - sub-period 2 vote_for(bob_id, witness2.vote_id, bob_private_key); // after voting, vesting performance is 5/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); // go to maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // vote decay as time pass witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 83); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // after voting, vesting performance is 5/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 4/6 for alice and 5/6 for bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); //Bob votes for witness2 - sub-period 3 vote_for(bob_id, witness2.vote_id, bob_private_key); // after voting, vesting performance is 4/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // decay more witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 66); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // after voting, vesting performance is 4/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 3/6 for alice and 5/6 for bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 3.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); // Bob votes for witness2 - sub-period 4 vote_for(bob_id, witness2.vote_id, bob_private_key); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // decay more witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 50); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // after voting, vesting performance is 3/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 3.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 3.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 3.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 2/6 for alice and 5/6 for bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 2.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); // Bob votes for witness2 - sub-period 5 vote_for(bob_id, witness2.vote_id, bob_private_key); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // decay more witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 33); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // after voting, vesting performance is 2/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 2.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 2.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(4); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 2.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 1/6 for alice and 5/6 for bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); // Bob votes for witness2 - sub-period 6 vote_for(bob_id, witness2.vote_id, bob_private_key); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // decay more witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 16); BOOST_CHECK_EQUAL(witness2.total_votes, 100); // after voting, vesting performance is 1/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); // we are still in gpos period 1 BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), now.sec_since_epoch()); advance_x_maint(8); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // a new GPOS period is in but vote from user is before the start. Whoever votes in 6th sub-period, votes will carry now = db.head_block_time(); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), HARDFORK_GPOS_TIME.sec_since_epoch() + db.get_global_properties().parameters.gpos_period()); // new gpos period and his first subperiod started, // vesting performance is decreased to 0 for alice, as she did not vote // but stays 1 for bob, as he voted in last subperiod in previous gpos period BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1 ); generate_block(); // we are in the second GPOS period, at subperiod 1, witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 0); //It's critical here, since bob votes in 6th sub-period of last vesting period, witness2 should retain his votes BOOST_CHECK_EQUAL(witness2.total_votes, 100); // lets vote here from alice to generate votes for witness 1 //vote from bob to reatin VF 1 vote_for(alice_id, witness1.vote_id, alice_private_key); vote_for(bob_id, witness2.vote_id, bob_private_key); // after voting, vesting performance is 1 for both alice and bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); // go to maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 100); BOOST_CHECK_EQUAL(witness2.total_votes, 100); advance_x_maint(8); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 5/6 for both alice and bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 83); BOOST_CHECK_EQUAL(witness2.total_votes, 83); vote_for(bob_id, witness2.vote_id, bob_private_key); generate_block(); // after voting, vesting performance is 5/6 for alice and 1 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(9); // vesting performance does not change during gpos subperiod BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 5.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 1); advance_x_maint(1); // new subperiod started, vesting performance is decreased to 4/6 for alice and 5/6 for bob, until they vote BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 4.0/6.0); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 66); BOOST_CHECK_EQUAL(witness2.total_votes, 83); // alice votes again, now for witness 2, her vote worth 100 now vote_for(alice_id, witness2.vote_id, alice_private_key); // after voting, vesting performance is 1 for alice and 5.0/6.0 for bob BOOST_CHECK_EQUAL(db.calculate_vesting_factor(alice_id(db)), 1); BOOST_CHECK_EQUAL(db.calculate_vesting_factor(bob_id(db)), 5.0/6.0); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); witness1 = witness_id_type(1)(db); witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 100); BOOST_CHECK_EQUAL(witness2.total_votes, 183); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( rolling_period_start ) { // period start rolls automatically after HF try { // update default gpos global parameters to make this thing faster update_gpos_global(518400, 86400, HARDFORK_GPOS_TIME); generate_blocks(HARDFORK_GPOS_TIME); update_maintenance_interval(3600); //update maintenance interval to 1hr to evaluate sub-periods BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maintenance_interval, 3600); auto vesting_period_1 = db.get_global_properties().parameters.gpos_period_start(); auto now = db.head_block_time(); // moving outside period: while( db.head_block_time() <= now + fc::days(6) ) { generate_block(); } generate_block(); auto vesting_period_2 = db.get_global_properties().parameters.gpos_period_start(); //difference between start of two consecutive vesting periods should be 6 days BOOST_CHECK_EQUAL(vesting_period_1 + 518400, vesting_period_2); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( worker_dividends_voting ) { try { // advance to HF fc::time_point_sec GPOS_HARDFORK_TIME = fc::time_point_sec(1581976800); // Use mainnet GPOS hardfork time generate_blocks(GPOS_HARDFORK_TIME); generate_block(); // update default gpos global parameters to 4 days auto now = db.head_block_time(); update_gpos_global(345600, 86400, now); generate_block(); set_expiration(db, trx); const auto& core = asset_id_type()(db); // get core asset object const auto& dividend_holder_asset_object = get_asset(GRAPHENE_SYMBOL); // by default core token pays dividends once per month const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 2592000); // 30 days // update the payout interval to 1 day for speed purposes of the test update_payout_interval(core.symbol, db.head_block_time() + fc::minutes(1), 60 * 60 * 24); // 1 day generate_block(); // get the dividend distribution account const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); // transfering some coins to distribution account. transfer( committee_account, dividend_distribution_account.id, core.amount( 100 ) ); generate_block(); ACTORS((nathan)(voter1)(voter2)(voter3)); transfer( committee_account, nathan_id, core.amount( 1000 ) ); transfer( committee_account, voter1_id, core.amount( 1000 ) ); transfer( committee_account, voter2_id, core.amount( 1000 ) ); generate_block(); upgrade_to_lifetime_member(nathan_id); auto worker = create_worker(nathan_id, 10, fc::days(6)); // add some vesting to voter1 create_vesting(voter1_id, core.amount(100), vesting_balance_type::gpos); // add some vesting to voter2 create_vesting(voter2_id, core.amount(100), vesting_balance_type::gpos); generate_block(); // vote for worker vote_for(voter1_id, worker.vote_for, voter1_private_key); // first maint pass, coefficient will be 1 generate_blocks(GPOS_HARDFORK_TIME + fc::hours(12)); //forward 1/2 sub-period so that it consider only gpos votes worker = worker_id_type()(db); BOOST_CHECK_EQUAL(worker.total_votes_for, 100); // here dividends are paid to voter1 and voter2 // voter1 get paid full dividend share as coefficent is at 1 here BOOST_CHECK_EQUAL(get_balance(voter1_id(db), core), 950); // voter2 didnt voted so he dont get paid BOOST_CHECK_EQUAL(get_balance(voter2_id(db), core), 900); // send some asset to the reserve pool so the worker can get paid fill_reserve_pool(account_id_type(), asset(GRAPHENE_MAX_SHARE_SUPPLY/2)); BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(worker.worker.get().balance(db).balance.amount.value, 0); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // worker is getting paid BOOST_CHECK_EQUAL(worker_id_type()(db).worker.get().balance(db).balance.amount.value, 5); BOOST_CHECK_EQUAL(worker.worker.get().balance(db).balance.amount.value, 5); // second maint pass, coefficient will be 0.75 worker = worker_id_type()(db); BOOST_CHECK_EQUAL(worker.total_votes_for, 75); // more decay generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); worker = worker_id_type()(db); BOOST_CHECK_EQUAL(worker.total_votes_for, 50); transfer( committee_account, dividend_distribution_account.id, core.amount( 100 ) ); generate_block(); BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999996850); // more decay generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); worker = worker_id_type()(db); BOOST_CHECK_EQUAL(worker.total_votes_for, 25); // here voter1 get paid again but less money by vesting coefficient BOOST_CHECK_EQUAL(get_balance(voter1_id(db), core), 962); BOOST_CHECK_EQUAL(get_balance(voter2_id(db), core), 900); // remaining dividends not paid by coeffcient are sent to committee account BOOST_CHECK_EQUAL(get_balance(committee_account(db), core), 499999999996938); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( account_multiple_vesting ) { try { // advance to HF fc::time_point_sec GPOS_HARDFORK_TIME = fc::time_point_sec(1581976800); // Use mainnet GPOS hardfork time generate_blocks(GPOS_HARDFORK_TIME); generate_block(); set_expiration(db, trx); // update default gpos global parameters to 4 days auto now = db.head_block_time(); update_gpos_global(345600, 86400, now); ACTORS((sam)(patty)); const auto& core = asset_id_type()(db); transfer( committee_account, sam_id, core.amount( 300 ) ); transfer( committee_account, patty_id, core.amount( 100 ) ); // add some vesting to sam create_vesting(sam_id, core.amount(100), vesting_balance_type::gpos); // have another balance with 200 more create_vesting(sam_id, core.amount(200), vesting_balance_type::gpos); // patty also have vesting balance create_vesting(patty_id, core.amount(100), vesting_balance_type::gpos); // get core asset object const auto& dividend_holder_asset_object = get_asset(GRAPHENE_SYMBOL); const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); // update the payout interval update_payout_interval(core.symbol, db.head_block_time() + fc::minutes(1), 60 * 60 * 24); // 1 day // get the dividend distribution account const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); // transfering some coins to distribution account. transfer( committee_account, dividend_distribution_account.id, core.amount( 100 ) ); generate_block(); // vote for a votable object auto witness1 = witness_id_type(1)(db); vote_for(sam_id, witness1.vote_id, sam_private_key); vote_for(patty_id, witness1.vote_id, patty_private_key); generate_blocks(GPOS_HARDFORK_TIME + fc::hours(12)); //forward 1/2 sub-period so that it consider only gpos votes // amount in vested balanced will sum up as voting power witness1 = witness_id_type(1)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 400); // sam get paid dividends BOOST_CHECK_EQUAL(get_balance(sam_id(db), core), 75); // patty also BOOST_CHECK_EQUAL(get_balance(patty_id(db), core), 25); // total vote not decaying generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); witness1 = witness_id_type(1)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 300); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( Withdraw_gpos_vesting_balance ) { try { // advance to HF generate_blocks(HARDFORK_GPOS_TIME); generate_block(); set_expiration(db, trx); // update default gpos global parameters to 4 days auto now = db.head_block_time(); update_gpos_global(345600, 86400, now); ACTORS((alice)(bob)); graphene::app::database_api db_api1(db); const auto& core = asset_id_type()(db); transfer( committee_account, alice_id, core.amount( 500 ) ); transfer( committee_account, bob_id, core.amount( 99 ) ); // add some vesting to Alice, Bob vesting_balance_object vbo1, vbo2; vbo1 = create_vesting(alice_id, core.amount(150), vesting_balance_type::gpos); vbo2 = create_vesting(bob_id, core.amount(99), vesting_balance_type::gpos); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_blocks(db.get_global_properties().parameters.gpos_vesting_lockin_period()); BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 350); withdraw_gpos_vesting(vbo1.id, alice_id, core.amount(50), /*vesting_balance_type::gpos, */alice_private_key); withdraw_gpos_vesting(vbo2.id, bob_id, core.amount(99), /*vesting_balance_type::gpos, */bob_private_key); generate_block(); // verify charles balance BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 400); BOOST_CHECK_EQUAL(get_balance(bob_id(db), core), 99); // Add more 50 and 73 vesting objects and withdraw 90 from // total vesting balance of user vbo1 = create_vesting(alice_id, core.amount(50), vesting_balance_type::gpos); vbo2 = create_vesting(alice_id, core.amount(73), vesting_balance_type::gpos); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); vector vbos = db_api1.get_vesting_balances("alice"); asset total_vesting; for (const vesting_balance_object& vbo : vbos) { if (vbo.balance_type == vesting_balance_type::gpos && vbo.balance.asset_id == asset_id_type()) total_vesting += vbo.balance; } // total vesting balance of alice BOOST_CHECK_EQUAL(total_vesting.amount.value, core.amount(223).amount.value); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_blocks(db.get_global_properties().parameters.gpos_vesting_lockin_period()); BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 277); withdraw_gpos_vesting(vbo1.id, alice_id, core.amount(90), /*vesting_balance_type::gpos,*/ alice_private_key); generate_block(); // verify alice balance BOOST_CHECK_EQUAL(get_balance(alice_id(db), core), 367); // verify remaining vesting balance vbos = db_api1.get_vesting_balances("alice"); asset remaining_vesting; for (const vesting_balance_object& vbo : vbos) { if (vbo.balance_type == vesting_balance_type::gpos && vbo.balance.asset_id == asset_id_type()) remaining_vesting += vbo.balance; } // remaining vesting balance of alice BOOST_CHECK_EQUAL(remaining_vesting.amount.value, core.amount(133).amount.value); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } /* BOOST_AUTO_TEST_CASE( competing_proposals ) { try { // advance to HF generate_blocks(HARDFORK_GPOS_TIME); generate_block(); set_expiration(db, trx); ACTORS((voter1)(voter2)(worker1)(worker2)); const auto& core = asset_id_type()(db); transfer( committee_account, worker1_id, core.amount( 1000 ) ); transfer( committee_account, worker2_id, core.amount( 1000 ) ); transfer( committee_account, voter1_id, core.amount( 1000 ) ); transfer( committee_account, voter2_id, core.amount( 1000 ) ); create_vesting(voter1_id, core.amount(200), vesting_balance_type::gpos); create_vesting(voter2_id, core.amount(300), vesting_balance_type::gpos); generate_block(); auto now = db.head_block_time(); update_gpos_global(518400, 86400, now); update_payout_interval(core.symbol, fc::time_point::now() + fc::minutes(1), 60 * 60 * 24); // 1 day upgrade_to_lifetime_member(worker1_id); upgrade_to_lifetime_member(worker2_id); // create 2 competing proposals asking a lot of token // todo: maybe a refund worker here so we can test with smaller numbers auto w1 = create_worker(worker1_id, 100000000000, fc::days(10)); auto w1_id_instance = w1.id.instance(); auto w2 = create_worker(worker2_id, 100000000000, fc::days(10)); auto w2_id_instance = w2.id.instance(); fill_reserve_pool(account_id_type(), asset(GRAPHENE_MAX_SHARE_SUPPLY/2)); // vote for the 2 workers vote_for(voter1_id, w1.vote_for, voter1_private_key); vote_for(voter2_id, w2.vote_for, voter2_private_key); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); w1 = worker_id_type(w1_id_instance)(db); w2 = worker_id_type(w2_id_instance)(db); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // only w2 is getting paid as it haves more votes and money is only enough for 1 BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 100000000000); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 150000000000); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); w1 = worker_id_type(w1_id_instance)(db); w2 = worker_id_type(w2_id_instance)(db); // as votes decay w1 is still getting paid as it always have more votes than w1 BOOST_CHECK_EQUAL(w1.total_votes_for, 100); BOOST_CHECK_EQUAL(w2.total_votes_for, 150); BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 200000000000); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); w1 = worker_id_type(w1_id_instance)(db); w2 = worker_id_type(w2_id_instance)(db); BOOST_CHECK_EQUAL(w1.total_votes_for, 66); BOOST_CHECK_EQUAL(w2.total_votes_for, 100); // worker is sil getting paid as days pass BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 250000000000); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); w1 = worker_id_type(w1_id_instance)(db); w2 = worker_id_type(w2_id_instance)(db); BOOST_CHECK_EQUAL(w1.total_votes_for, 33); BOOST_CHECK_EQUAL(w2.total_votes_for, 50); BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 300000000000); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); w1 = worker_id_type(w1_id_instance)(db); w2 = worker_id_type(w2_id_instance)(db); // worker2 will not get paid anymore as it haves 0 votes BOOST_CHECK_EQUAL(w1.total_votes_for, 0); BOOST_CHECK_EQUAL(w2.total_votes_for, 0); BOOST_CHECK_EQUAL(w1.worker.get().balance(db).balance.amount.value, 0); BOOST_CHECK_EQUAL(w2.worker.get().balance(db).balance.amount.value, 300000000000); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } */ BOOST_AUTO_TEST_CASE( proxy_voting ) { ACTORS((alice)(bob)); try { // move to hardfork generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); // database api graphene::app::database_api db_api(db); const auto& core = asset_id_type()(db); // send some asset to alice and bob transfer( committee_account, alice_id, core.amount( 1000 ) ); transfer( committee_account, bob_id, core.amount( 1000 ) ); generate_block(); // add some vesting to alice and bob create_vesting(alice_id, core.amount(100), vesting_balance_type::gpos); generate_block(); // total balance is 100 rest of data at 0 auto gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 100); create_vesting(bob_id, core.amount(100), vesting_balance_type::gpos); generate_block(); gpos_info = db_api.get_gpos_info(bob_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); auto now = db.head_block_time(); update_gpos_global(518400, 86400, now); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period(), 518400); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_subperiod(), 86400); BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), now.sec_since_epoch()); // alice assign bob as voting account graphene::chain::account_update_operation op; op.account = alice_id; op.new_options = alice_id(db).options; op.new_options->voting_account = bob_id; trx.operations.push_back(op); set_expiration(db, trx); trx.validate(); sign(trx, alice_private_key); PUSH_TX( db, trx, ~0 ); trx.clear(); generate_block(); // vote for witness1 auto witness1 = witness_id_type(1)(db); vote_for(bob_id, witness1.vote_id, bob_private_key); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // check vesting factor of current subperiod BOOST_CHECK_EQUAL(db_api.get_gpos_info(alice_id).vesting_factor, 1); BOOST_CHECK_EQUAL(db_api.get_gpos_info(bob_id).vesting_factor, 1); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // GPOS 2nd subperiod started. // vesting factor decay BOOST_CHECK_EQUAL(db_api.get_gpos_info(alice_id).vesting_factor, 0.83333333333333337); BOOST_CHECK_EQUAL(db_api.get_gpos_info(bob_id).vesting_factor, 0.83333333333333337); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // GPOS 3rd subperiod started // vesting factor decay BOOST_CHECK_EQUAL(db_api.get_gpos_info(alice_id).vesting_factor, 0.66666666666666663); BOOST_CHECK_EQUAL(db_api.get_gpos_info(bob_id).vesting_factor, 0.66666666666666663); // vote for witness2 auto witness2 = witness_id_type(2)(db); vote_for(bob_id, witness2.vote_id, bob_private_key); // vesting factor should be 1 for both alice and bob for the current subperiod BOOST_CHECK_EQUAL(db_api.get_gpos_info(alice_id).vesting_factor, 1); BOOST_CHECK_EQUAL(db_api.get_gpos_info(bob_id).vesting_factor, 1); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); generate_block(); // vesting factor decay BOOST_CHECK_EQUAL(db_api.get_gpos_info(alice_id).vesting_factor, 0.83333333333333337); BOOST_CHECK_EQUAL(db_api.get_gpos_info(bob_id).vesting_factor, 0.83333333333333337); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( no_proposal ) { try { } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( dividend_distribution_fee ) { try { // advance to HF generate_blocks(HARDFORK_FEES_AS_DIVIDENDS_TIME); generate_block(); // update default gpos global parameters to 4 days auto now = db.head_block_time(); update_gpos_global(345600, 86400, now); generate_block(); set_expiration(db, trx); ACTORS((nathan)(alice)(voter1)(voter2)); const auto& core = asset_id_type()(db); // get core asset object const auto& dividend_holder_asset_object = get_asset(GRAPHENE_SYMBOL); // by default core token pays dividends once per month const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 2592000); // 30 days // update the payout interval to 1 day for speed purposes of the test update_payout_interval(core.symbol, db.head_block_time() + fc::minutes(1), 60 * 60 * 24); // 1 day // get the dividend distribution account const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); // transfer to voters and nathan without fee. We can't enable fee yet, since create worker is a custom // function which doesn't take the fee in transaction so by enabling it we will end up with not sufficient fee transfer( committee_account, voter1_id, core.amount( 2000 * GRAPHENE_BLOCKCHAIN_PRECISION ) ); transfer( committee_account, voter2_id, core.amount( 3000 * GRAPHENE_BLOCKCHAIN_PRECISION ) ); transfer( committee_account, nathan_id, core.amount( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION ) ); // create some vesting for voters create_vesting(voter1_id, core.amount(100 * GRAPHENE_BLOCKCHAIN_PRECISION ), vesting_balance_type::gpos); create_vesting(voter2_id, core.amount(100 * GRAPHENE_BLOCKCHAIN_PRECISION ), vesting_balance_type::gpos); // create worker upgrade_to_lifetime_member(nathan_id); auto worker = create_worker(nathan_id, 50 * GRAPHENE_BLOCKCHAIN_PRECISION, fc::days(2)); generate_block(); // vote for worker vote_for(voter1_id, worker.vote_for, voter1_private_key); // for now on enable fees enable_fees(); // transfering some coins to alice. for(int i = 0; i < 50; i++) { transfer( nathan_id, alice_id, core.amount( 100 * GRAPHENE_BLOCKCHAIN_PRECISION ) ); } generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // on the dividend account from total fee should be only network cut which is 20% of total fee. // note that we did 50 transactions. // Each transaction takes for fee 20 * GRAPHENE_BLOCKCHAIN_PRECISION int dividend_distribution_account_balance = get_balance(dividend_distribution_account, core); BOOST_CHECK_EQUAL(dividend_distribution_account_balance, 50 * 20 * GRAPHENE_BLOCKCHAIN_PRECISION * 20 / 100); // voters are not yet paid from dividend account, it will be on next maintenance BOOST_CHECK_EQUAL(get_balance(voter1_id(db), core), 1900 * GRAPHENE_BLOCKCHAIN_PRECISION); BOOST_CHECK_EQUAL(get_balance(voter2_id(db), core), 2900 * GRAPHENE_BLOCKCHAIN_PRECISION); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // verify that voter_1 is payed from dividend account and voter_2 is not since voter_2 didn't vote // note that we pass two maintenance intervals, so vesting factor is not anymore 1, instead it is 0.75 int payout_to_voter1 = (dividend_distribution_account_balance / 2 ) * 0.75; BOOST_CHECK_EQUAL(get_balance(voter1_id(db), core), 1900 * GRAPHENE_BLOCKCHAIN_PRECISION + payout_to_voter1); BOOST_CHECK_EQUAL(get_balance(voter2_id(db), core), 2900 * GRAPHENE_BLOCKCHAIN_PRECISION); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_CASE( database_api ) { ACTORS((alice)(bob)); try { // move to hardfork generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); // database api graphene::app::database_api db_api(db); const auto& core = asset_id_type()(db); // send some asset to alice and bob transfer( committee_account, alice_id, core.amount( 1000 ) ); transfer( committee_account, bob_id, core.amount( 1000 ) ); generate_block(); // add some vesting to alice and bob create_vesting(alice_id, core.amount(100), vesting_balance_type::gpos); generate_block(); // total balance is 100 rest of data at 0 auto gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 100); create_vesting(bob_id, core.amount(100), vesting_balance_type::gpos); generate_block(); // total gpos balance is now 200 gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); // update default gpos and dividend interval to 10 days auto now = db.head_block_time(); update_gpos_global(5184000, 864000, now); // 10 days subperiods update_payout_interval(core.symbol, now + fc::minutes(1), 60 * 60 * 24 * 10); // 10 days generate_block(); // no votes for witness 1 auto witness1 = witness_id_type(1)(db); BOOST_CHECK_EQUAL(witness1.total_votes, 0); // no votes for witness 2 auto witness2 = witness_id_type(2)(db); BOOST_CHECK_EQUAL(witness2.total_votes, 0); // transfering some coins to distribution account. const auto& dividend_holder_asset_object = get_asset(GRAPHENE_SYMBOL); const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); transfer( committee_account, dividend_distribution_account.id, core.amount( 100 ) ); generate_block(); // award balance is now 100 gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 100); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); // vote for witness1 vote_for(alice_id, witness1.vote_id, alice_private_key); vote_for(bob_id, witness1.vote_id, bob_private_key); // go to maint generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // payment for alice and bob is done, distribution account is back in 0 gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 1); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); advance_x_maint(10); // alice vesting coeffcient decay gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0.83333333333333337); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); advance_x_maint(10); // vesting factor for alice decaying more gpos_info = db_api.get_gpos_info(alice_id); BOOST_CHECK_EQUAL(gpos_info.vesting_factor, 0.66666666666666663); BOOST_CHECK_EQUAL(gpos_info.award.amount.value, 0); BOOST_CHECK_EQUAL(gpos_info.total_amount.value, 200); } catch (fc::exception &e) { edump((e.to_detail_string())); throw; } } BOOST_AUTO_TEST_SUITE_END()