peerplays_migrated/tests/tests/gpos_tests.cpp
pbattu123 be14592ea8
Merge Plugins and graphene update changes from beatrice TESTNET to master (#317)
* increase delay for node connection

* remove cache from cli get_account

* add cli tests framework

* Adjust newly merged code to new API

* Merged changes from Bitshares PR 1036

* GRPH-76 - Short-cut long sequences of missed blocks

Fixes database::update_global_dynamic_data to speed up counting missed blocks.
(This also fixes a minor issue with counting - the previous algorithm would skip missed blocks for the witness who signed the first block after the gap.)

* Improved resilience of block database against corruption

* Moved reindex logic into database / chain_database, make use of additional blocks in block_database

Fixed tests wrt db.open

* Enable undo + fork database for final blocks in a replay

Dont remove blocks from block db when popping blocks, handle edge case in replay wrt fork_db, adapted unit tests

* Log starting block number of replay

* Prevent unsigned integer underflow

* Fixed lock detection

* Dont leave _data_dir empty if db is locked

* Writing the object_database is now almost atomic

* Improved consistency check for block_log

* Cut back block_log index file if inconsistent

* Fixed undo_database

* Added test case for broken merge on empty undo_db

* exclude second undo_db.enable() call in some cases

* Add missing change

* change bitshares to core in message

* Merge pull request #938 from bitshares/fix-block-storing

Store correct block ID when switching forks

* Fixed integer overflow issue

* Fix for for history ID mismatch ( Bitshares PR #875 )

* Update the FC submodule with the changes for GRPH-4

* Merged Bitshares PR #1462 and compilation fixes

* Support/gitlab (#123)

* Updated gitlab process

* Fix undefined references in cli test

* Updated GitLab CI

* Fix #436 object_database created outside of witness data directory

* supplement more comments on database::_opened variable

* prevent segfault when destructing application obj

* Fixed test failures and compilation issue

* minor performance improvement

* Added comment

* Fix compilation in debug mode

* Fixed duplicate ops returned from get_account_history

* Fixed account_history_pagination test

* Removed unrelated comment

* Update to fixed version of fc

* Skip auth check when pushing self-generated blocks

* Extract public keys before pushing a transaction

* Dereference chain_database shared_ptr

* Updated transaction::signees to mutable

and
* updated get_signature_keys() to return a const reference,
* get_signature_keys() will update signees on first call,
* modified test cases and wallet.cpp accordingly,
* no longer construct a new signed_transaction object before pushing

* Added get_asset_count API

* No longer extract public keys before pushing a trx

and removed unused new added constructor and _get_signature_keys() function from signed_transaction struct

* changes to withdraw_vesting feature(for both cdd and GPOS)

* Comments update

* update to GPOS hardfork ref

* Remove leftover comment from merge

* fix for get_vesting_balance API call

* braces update

* Allow sufficient space for new undo_session

* Throw for deep nesting

* node.cpp: Check the attacker/buggy client before updating items ids

The peer is an attacker or buggy, which means the item_hashes_received is
not correct.

Move the check before updating items ids to save some time in this case.

* Create .gitlab-ci.yml

* Added cli_test to CI

* fixing build errors (#150)

* fixing build errors

vest type correction

* fixing build errors

vest type correction

* fixes 

new Dockerfile

* vesting_balance_type correction

vesting_balance_type changed to normal

* gcc5 support to Dockerfile

gcc5 support to Dockerfile

* use random port numbers in app_test (#154)

* Changes to compiple with GCC 7(Ubuntu 18.04)

* proposal fail_reason bug fixed (#157)

* Added Sonarcloud code_quality to CI (#159)

* Added sonarcloud analysis (#158)

* changes to have separate methods and single withdrawl fee for multiple vest objects

* 163-fix, Return only non-zero vesting balances

* Support/gitlab develop (#168)

* Added code_quality to CI

* Update .gitlab-ci.yml

* Point to PBSA/peerplays-fc commit f13d063 (#167)

* [GRPH-3] Additional cli tests (#155)

* Additional cli tests

* Compatible with latest fc changes

* Fixed Spacing issues

* [GRPH-106] Added voting tests (#136)

* Added more voting tests

* Added additional option

* Adjust p2p log level (#180)

* Added submodule sync to peerplays compile process

* merge gpos to develop (#186)

* issue - 154: Don't allow to vote when vesting balance is 0

* changes to withdraw_vesting feature(for both cdd and GPOS)

* Comments update

* update to GPOS hardfork ref

* fix for get_vesting_balance API call

* braces update

* Create .gitlab-ci.yml

* fixing build errors (#150)

* fixing build errors

vest type correction

* fixing build errors

vest type correction

* fixes 

new Dockerfile

* vesting_balance_type correction

vesting_balance_type changed to normal

* gcc5 support to Dockerfile

gcc5 support to Dockerfile

* Changes to compiple with GCC 7(Ubuntu 18.04)

* changes to have separate methods and single withdrawl fee for multiple vest objects

* 163-fix, Return only non-zero vesting balances

* Revert "Revert "GPOS protocol""

This reverts commit 67616417b7.

* add new line needed to gpos hardfork file

* comment temporally cli_vote_for_2_witnesses until refactor or delete

* fix gpos tests

* fix gitlab-ci conflict

* Fixed few error messages

* error message corrections at other places

* Updated FC repository to peerplays-network/peerplays-fc (#189)

Point to fc commit hash 6096e94 [latest-fc branch]

* Project name update in Doxyfile (#146)

* changes to allow user to vote in each sub-period

* Fixed GPOS vesting factor issue when proxy is set

* Added unit test for proxy voting

* Review changes

* changes to update last voting time

* resolve merge conflict

* unit test changes and also separated GPOS test suite

* delete unused variables

* removed witness check

* eliminate time gap between two consecutive vesting periods

* deleted GPOS specific test suite and updated gpos tests

* updated GPOS hf

* Fixed dividend distribution issue and added test case

* fix flag

* clean newlines gpos_tests

* adapt gpos_tests to changed flag

* Fix to roll in GPOS rules, carry votes from 6th sub-period

* check was already modified

* comments updated

* updated comments to the benefit of reviewer

* Added token symbol name in error messages

* Added token symbol name in error messages (#204)

* case 1: Fixed last voting time issue

* get_account bug fixed

* Fixed flag issue

* Fixed spelling issue

* remove non needed gcc5 changes to dockerfile

* GRPH134- High CPU Issue, websocket changes (#213)

* update submodule branch to refer to the latest commit on latest-fc branch (#214)

* Improve account maintenance performance (#130)

* Improve account maintenance performance

* merge fixes

* Fixed merge issue

* Fixed indentations and extra ';'

* Update CI for syncing gitmodules (#216)

* Added logging for the old update_expired_feeds bug

The old bug is https://github.com/cryptonomex/graphene/issues/615 .

Due to the bug, `update_median_feeds()` and `check_call_orders()`
will be called when a feed is not actually expired, normally this
should not affect consensus since calling them should not change
any data in the state.

However, the logging indicates that `check_call_orders()` did
change some data under certain circumstances, specifically, when
multiple limit order matching issue (#453) occurred at same block.
* https://github.com/bitshares/bitshares-core/issues/453

* Minor performance improvement for price::is_null()

* Use static refs in db_getter for immutable objects

* Minor performance improvement for db_maint

* Minor code updates for asset_evaluator.cpp

* changed an `assert()` to `FC_ASSERT()`
* replaced one `db.get(asset_id_type())` with `db.get_core_asset()`
* capture only required variables for lambda

* Improve update_expired_feeds performance #1093

* Change static refs to member pointers of db class

* Added getter for witness schedule object

* Added getter for core dynamic data object

* Use getters

* Removed unused variable

* Add comments for update_expired_feeds in db_block

* Minor refactory asset_create_evaluator::do_apply()

* Added FC_ASSERT for dynamic data id of core asset

* Added header inclusions in db_management.cpp

* fix global objects usage during replay

* Logging config parsing issue

* added new files

* compilation fix

* Simplified code in database::pay_workers()

* issue with withdrawl

* Added unit test for empty account history

* set extensions default values

* Update GPOS hardfork date and don't allow GPOS features before hardfork time

* refer to latest commit of latest-fc branch (#224)

* account name or id support in all database api

* asset id or name support in all asset APIs

* Fixed compilation issues

* Fixed alignment issues

* Externalized some API templates

* Externalize serialization of blocks, tx, ops

* Externalized db objects

* Externalized genesis serialization

* Externalized serialization in protocol library

* Undo superfluous change

* remove default value for extension parameter

* fix compilation issues

* GRPH-46-Quit_command_cliwallet

* removed multiple function definition

* Fixed chainparameter update proposal issue

* Move GPOS withdraw logic to have single transaction(also single fee) and update API

* Added log for authorization failure of proposal operations

* Votes consideration on GPOS activation

* bump fc version

* fix gpos tests

* Bump fc version

* Updated gpos/voting_tests

* Fixed withdraw vesting bug

* Added unit test

* Update hardfork date for TESTNET, sync fc module and update logs

* avoid wlog as it filling up space

* Beatrice hot fix(sync issue fix)

* gpos tests fix

* Set hardfork date to Jan5th on TESTNET

* Merge Elasticplugin, snapshot plugin and graphene updates to beatrice (#304)

* check witness signature before adding block to fork db

* Replace verify_no_send_in_progress with no_parallel_execution_guard

* fixed cli_wallet log issue

* Port plugin sanitization code

* avoid directly overwriting wallet file

* Implemented "plugins" config variable

* allow plugin to have descriptions

* Merge pull request #444 from oxarbitrage/elasticsearch

Elasticsearch plugin

* Merge pull request #500 from oxarbitrage/elasticsearch-extras

es_objects plugin

* Merge pull request #873 from pmconrad/585_fix_history_ids

Fix history ids

* Merge pull request #1201 from oxarbitrage/elasticsearch_tests2

Elasticsearch refactor

* Merge pull request #1271 from oxarbitrage/es_objects

refine es_objects plugin

* Merge pull request #1429 from oxarbitrage/es_objects_templates

Add an adaptor to es_objects and template function to reduce code

* Merge pull request #1458 from oxarbitrage/issue1455

add option elasticsearch-start-es-after-block to es plugin

* Merge pull request #1541 from oxarbitrage/es_objects_start_after_block

add es-objects-start-es-after-block option

* explicitly cleanup external library facilities

* Merge pull request #1717 from oxarbitrage/issue1652

add genesis data to es_objects

* Merge pull request #1073 from xiangxn/merge-impacted

merge impacted into db_notify

* Merge pull request #1725 from oxarbitrage/issue1682

elasticsearch history api #1682

* change ES index prefixes to Peerplays-specific

* sync develop with beatrice

* fix the data writing to ES during sync issues

* fix CLI tests

* brought updates from mainnet branch (#285)

* Fix unit test failures (#289)

* fixed unit test failures from the recent merges

* fixed unit test failures from the recent merges

* enable snapshot plugin (#288)

* sync fc branch(build optimization changes)

* update to es plugin

* fix verify witness signature method (#295)

* enable mandatory plugins to have smooth transition for next release

* updated tests to keep in-line with plugin changes

Co-authored-by: Sandip Patel <sandip@knackroot.com>
Co-authored-by: Peter Conrad <conrad@quisquis.de>
Co-authored-by: Alfredo <oxarbitrage@gmail.com>
Co-authored-by: Abit <abitmore@users.noreply.github.com>
Co-authored-by: crypto-ape <43807588+crypto-ape@users.noreply.github.com>
Co-authored-by: gladcow <s.gladkov@pbsa.info>

* sync latest fc commit on beatrice

* sweeps winner_ticket_id changes

Co-authored-by: Bobinson K B <bobinson@gmail.com>
Co-authored-by: gladcow <s.gladkov@pbsa.info>
Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
Co-authored-by: Miha Čančula <miha@noughmad.eu>
Co-authored-by: Ronak Patel <r.patel@pbsa.info>
Co-authored-by: Srdjan Obucina <obucinac@gmail.com>
Co-authored-by: Peter Conrad <conrad@quisquis.de>
Co-authored-by: Peter Conrad <cyrano@quisquis.de>
Co-authored-by: Abit <abitmore@users.noreply.github.com>
Co-authored-by: Roshan Syed <r.syed@pbsa.info>
Co-authored-by: cifer <maintianyu@gmail.com>
Co-authored-by: John Jones <jmjatlanta@gmail.com>
Co-authored-by: Sandip Patel <sandip@knackroot.com>
Co-authored-by: Wei Yang <richard.weiyang@gmail.com>
Co-authored-by: gladcow <jahr@yandex.ru>
Co-authored-by: satyakoneru <satyakoneru.iiith@gmail.com>
Co-authored-by: crypto-ape <43807588+crypto-ape@users.noreply.github.com>
2020-04-15 20:34:15 +05:30

1453 lines
56 KiB
C++

/*
* 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 <boost/test/unit_test.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/balance_object.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include "../common/database_fixture.hpp"
#include <graphene/app/database_api.hpp>
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<worker_object>(ptx.operation_results[0].get<object_id_type>());
}
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<vesting_balance_object>(ptx.operation_results[0].get<object_id_type>());
}
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<periods; i++)
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
}
};
BOOST_FIXTURE_TEST_SUITE( gpos_tests, gpos_fixture )
BOOST_AUTO_TEST_CASE(gpos_vesting_type)
{
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();
// gpos balance creation is not allowed before HF
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = core.amount(100);
op.balance_type = vesting_balance_type::gpos;
trx.operations.push_back(op);
set_expiration(db, trx);
GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception );
trx.clear();
// pass hardfork
generate_blocks( HARDFORK_GPOS_TIME );
generate_block();
// repeat operation
trx.operations.push_back(op);
set_expiration(db, trx);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
generate_block();
auto alice_vesting = db.get<vesting_balance_object>(ptx.operation_results[0].get<object_id_type>());
// check created vesting amount and policy
BOOST_CHECK_EQUAL(alice_vesting.balance.amount.value, 100);
BOOST_CHECK_EQUAL(alice_vesting.policy.get<linear_vesting_policy>().vesting_duration_seconds,
db.get_global_properties().parameters.gpos_subperiod());
BOOST_CHECK_EQUAL(alice_vesting.policy.get<linear_vesting_policy>().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<vesting_balance_object>(ptx.operation_results[0].get<object_id_type>());
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<linear_vesting_policy>().vesting_duration_seconds,
db.get_global_properties().parameters.gpos_subperiod());
BOOST_CHECK_EQUAL(bob_vesting.policy.get<linear_vesting_policy>().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<account_transaction_history_index>();
auto account_history_range = hist_idx.indices().get<by_seq>().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<asset_dividend_distribution_operation>();
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
auto now = db.head_block_time();
// 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();
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
auto now = db.head_block_time();
// 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);
// 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);
// 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);
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);
advance_x_maint(4);
//Bob votes for witness2 - sub-period 2
vote_for(bob_id, witness2.vote_id, bob_private_key);
// 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);
advance_x_maint(10);
//Bob votes for witness2 - sub-period 3
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, 66);
BOOST_CHECK_EQUAL(witness2.total_votes, 100);
advance_x_maint(10);
// 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);
advance_x_maint(10);
// 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);
advance_x_maint(10);
// 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);
// 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(5);
// 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(), now.sec_since_epoch());
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);
generate_block();
// 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(10);
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();
advance_x_maint(10);
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);
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
generate_blocks(HARDFORK_GPOS_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(HARDFORK_GPOS_TIME + fc::hours(12));
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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(worker.worker.get<vesting_balance_worker_type>().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<vesting_balance_worker_type>().balance(db).balance.amount.value, 5);
BOOST_CHECK_EQUAL(worker.worker.get<vesting_balance_worker_type>().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
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((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(HARDFORK_GPOS_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<vesting_balance_object> 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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 100000000000);
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
generate_block();
BOOST_CHECK_EQUAL(w1.worker.get<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().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<vesting_balance_worker_type>().balance(db).balance.amount.value, 0);
BOOST_CHECK_EQUAL(w2.worker.get<vesting_balance_worker_type>().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( 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()