From 4080a2e7ee983f2dbccc585f37a81573c80e9afc Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 10 Jun 2015 15:21:54 -0400 Subject: [PATCH 1/5] Add missing fee for 2-character account names --- libraries/chain/include/graphene/chain/types.hpp | 2 ++ libraries/chain/operations.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/libraries/chain/include/graphene/chain/types.hpp b/libraries/chain/include/graphene/chain/types.hpp index 504a059c..91ae6dde 100644 --- a/libraries/chain/include/graphene/chain/types.hpp +++ b/libraries/chain/include/graphene/chain/types.hpp @@ -357,6 +357,7 @@ namespace graphene { namespace chain { uint32_t account_len5_fee; uint32_t account_len4_fee; uint32_t account_len3_fee; + uint32_t account_len2_fee; uint32_t account_premium_fee; ///< accounts with premium names; i.e. @ref is_cheap_name returns false uint32_t account_whitelist_fee; ///< the fee to whitelist an account uint32_t delegate_create_fee; ///< fixed fee for registering as a delegate; used to discourage frivioulous delegates @@ -538,6 +539,7 @@ FC_REFLECT( graphene::chain::fee_schedule_type, (account_len5_fee) (account_len4_fee) (account_len3_fee) + (account_len2_fee) (account_premium_fee) (account_whitelist_fee) (delegate_create_fee) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 3c0f8f0d..8805895f 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -127,6 +127,8 @@ share_type account_create_operation::calculate_fee( const fee_schedule_type& sch core_fee_required = schedule.account_len4_fee; else if( s == 3 ) core_fee_required = schedule.account_len3_fee; + else if( s == 2 ) + core_fee_required = schedule.account_len2_fee; return core_fee_required; } From b7436cffce0236539ed1c58876501afd86bc52c7 Mon Sep 17 00:00:00 2001 From: Vikram Rajkumar Date: Wed, 10 Jun 2015 16:31:07 -0400 Subject: [PATCH 2/5] Move docs to submodule for wiki https://github.com/cryptonomex/graphene/wiki --- .gitmodules | 15 ++-- DEV_README.md | 29 -------- build-ubuntu.md | 21 ------ doc/draft-license/HEADER.draft | 14 ---- doc/draft-license/LICENSE.draft | 19 ----- doc/witness-rng.md | 37 --------- doc/witness-scheduler.md | 128 -------------------------------- docs | 1 + 8 files changed, 10 insertions(+), 254 deletions(-) delete mode 100644 DEV_README.md delete mode 100644 build-ubuntu.md delete mode 100644 doc/draft-license/HEADER.draft delete mode 100644 doc/draft-license/LICENSE.draft delete mode 100644 doc/witness-rng.md delete mode 100644 doc/witness-scheduler.md create mode 160000 docs diff --git a/.gitmodules b/.gitmodules index 3e39da4a..c78c21f3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,11 @@ +[submodule "docs"] + path = docs + url = git@github.com:cryptonomex/graphene.wiki.git [submodule "libraries/fc"] - path = libraries/fc - url = git@github.com:cryptonomex/fc - ignore = dirty + path = libraries/fc + url = git@github.com:cryptonomex/fc.git + ignore = dirty [submodule "libraries/leveldb"] - path = libraries/leveldb - url = https://github.com/bitcoin/leveldb.git - ignore = dirty + path = libraries/leveldb + url = git@github.com:bitcoin/leveldb.git + ignore = dirty diff --git a/DEV_README.md b/DEV_README.md deleted file mode 100644 index b8133fea..00000000 --- a/DEV_README.md +++ /dev/null @@ -1,29 +0,0 @@ - -How to use fc async to do recurring tasks ------------------------------------------ - - _my_task = fc::async( callable, "My Task" ); - _my_task = fc::schedule( callable, "My Task 2", exec_time ); - -Stuff to know about the code ----------------------------- - -`static_variant` is a *union type* which says "this variable may be either t1 or t2." It is serializable if t1 and t2 are both serializable. - -The file `operations.hpp` documents the available operations, and `database_fixture.hpp` is a good reference for building and submitting transactions for processing. - -Tests also show the way to do many things, but are often cluttered with code that generates corner cases to try to break things in every possible way. - -Visitors are at the end of `operations.hpp` after the large typedef for `operation` as a `static_variant`. TODO: They should be refactored into a separate header. - -Downcasting stuff ------------------ - -- You have an `object_id_type` and want to downcast it to a `key_id_type` : `key_id_type( object_id )` -- You have an `operation_result` and want to downcast it to an `object_id_type` : `op_result.get()` -- Since `operation_result` is a `static_variant`, the above is also how you downcast `static_variant` - -Debugging FC exceptions with GDB --------------------------------- - -- `catch throw` diff --git a/build-ubuntu.md b/build-ubuntu.md deleted file mode 100644 index 5f02ff82..00000000 --- a/build-ubuntu.md +++ /dev/null @@ -1,21 +0,0 @@ - -The Boost which ships with Ubuntu 14.04 LTS is too old. You need to download the Boost tarball for Boost 1.57.0 -(Note, 1.58.0 requires C++14 and will not build on Ubuntu LTS; this requirement was an accident, see ). Build Boost as follows: - - # tarball available at http://sourceforge.net/projects/boost/files/boost/1.57.0/boost_1_57_0.tar.bz2/download - # sha256sum is 910c8c022a33ccec7f088bd65d4f14b466588dda94ba2124e78b8c57db264967 - - BOOST_ROOT=$(echo ~/opt/boost_1_57_0) - - # build Boost from source - cd ~/src/boost_1_57_0 - ./bootstrap.sh --prefix=$BOOST_ROOT - ./b2 link=static variant=debug threading=multi stage - ./b2 link=static variant=debug threading=multi install - -Then we need to tell `cmake` to use the Boost we just built, instead of using the system-wide Boost: - - cd ~/src/graphene - [ -e ./doc/build-ubuntu.md ] && sh -c 'cmake -DBOOST_ROOT="$BOOST_ROOT" -DCMAKE_BUILD_TYPE=Debug .' - -If all goes well, you should see the correct Boost version in the output messages to the above command. diff --git a/doc/draft-license/HEADER.draft b/doc/draft-license/HEADER.draft deleted file mode 100644 index b8e211bd..00000000 --- a/doc/draft-license/HEADER.draft +++ /dev/null @@ -1,14 +0,0 @@ -/* - * This is draft license text currently under development. This license does not apply to anything right now. - * - * Copyright (c) 2015, Cryptonomex, Inc - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * - * 1. No distribution of binaries compiled from modified versions of the code - * - * 2. Any modifications become property of Cryptonomex, Inc and must be licensed under these same terms - * - * 3. Any hosting provider is authorized to delete any version of this code at the request Cryptonomex, Inc. - */ diff --git a/doc/draft-license/LICENSE.draft b/doc/draft-license/LICENSE.draft deleted file mode 100644 index 90c85fd5..00000000 --- a/doc/draft-license/LICENSE.draft +++ /dev/null @@ -1,19 +0,0 @@ -This is draft license text currently under development. This license does not apply to anything right now. - -Copyright (c) 2015, Cryptonomex, Inc -All rights reserved. - -The graphene source code is proprietary and may not be used for any purpose without written permission from Cryptonomex, Inc. The source code is -provided publicly for the sole purpose of allowing users to audit the code and compile their own executables. Blockchain technology depends upon -a strong consensus on the rules of an open protocol; this source code is provided to completely document the protocol rules (bugs and all). - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. No distribution of binaries compiled from modified versions of the code - -2. Any modifications made to the code become property of Cryptonomex, Inc and must be licensed under these same terms - -3. The software may be forked/cloned on github for the purpose of submitting bug fixes and pull requests. All forked repositories are property of Cryptonomex and by -forking you authorize Github.com to delete your fork at the request Cryptonomex, Inc. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/doc/witness-rng.md b/doc/witness-rng.md deleted file mode 100644 index 672adad2..00000000 --- a/doc/witness-rng.md +++ /dev/null @@ -1,37 +0,0 @@ - -Witness scheduler RNG ---------------------- - -The witness scheduler RNG is a random number generator which uses the -blockchain random number generator state as its seed. - -The witness scheduler RNG creates an infinite stream of random bytes -by computing `sha256( sha256( seed ) + little_endian_64bit(i) )`, increasing -`i` from 0 to 1 to 2, etc. The RNG only runs during a scheduling block, -and `i` starts from `0` in each scheduling block (relying on different -seeds to produce different results). - -This infinite stream of random bytes is equivalent to an infinite -stream of random bits in little bit-endian order. Given a bound `B`, -the bitstream can be used to produce a random number uniformly -distributed in the range `[0, B)` using a sample-and-reject algorithm: - -- Let `n` be the smallest integer such that `2^n >= B`. -- Let `x` be the next `n` bits from the bitstream, interpreted as an integer in little bit-endian order. -- If `x <= B`, return `x`. Otherwise, throw `x` away and repeat. - -The worst-case running time is unbounded, but each iteration has a -termination probability greater than one half. Thus the average-case -running time is `2` iterations, and a running time of more than `N` -iterations will occur (on average) at most once every `2^N` -RNG queries (assuming a worst-case choice of e.g. `B = 2^63+1` for all -queries). Since each RNG query schedules a witness, the query rate -is (over the long term) equal to the block production rate (although -in practice many queries are all performed at once in scheduling -blocks). So while it is, in theory, possible for the algorithm to -require more than 1000 iterations, in practice this will occur on average -only once every `2^1000` blocks (again assuming all queries have -worst-case `B`). - -The sample-and-reject algorithm is totally unbiased; every `x` value -has equal probability. diff --git a/doc/witness-scheduler.md b/doc/witness-scheduler.md deleted file mode 100644 index 3308a393..00000000 --- a/doc/witness-scheduler.md +++ /dev/null @@ -1,128 +0,0 @@ - -Turn/Token witness scheduling algorithm ---------------------------------------- - -The algorithm which determines the order of witnesses is referred -to as the *witness scheduling algorithm*. - -This was designed by a community bounty in thread -https://bitsharestalk.org/index.php/topic,15547.0 -however, Graphene has an additional requirement which -is not taken into account by the solutions in the thread: - -The membership and length of the list of witnesses may change over -time. - -So in this article I'll describe my solution. - -Turns and tokens ----------------- - -The solution is based on terms of *turns* and *tokens*. - -- Newly inserted witnesses start out with a turn and a token. -- In order for a witness to be scheduled, it must have a turn and a token. -- The scheduler maintains a FIFO of witnesses without tokens. -- If no witness has a turn, then the scheduler gives a turn to all witnesses. This is called "emitting a turn." -- While less than half of the witnesses have tokens, give a token to the first witness in the FIFO and remove it from the FIFO. -- Schedule a witness by picking randomly from all witnesses with both a turn and token. -- When a witness is scheduled, it loses its turn and token. - -The generic scheduler ---------------------- - -The generic scheduler implements turns and tokens. It only depends -on the C++11 stdlib and boost (not even using fc). Types provided -by Graphene are template parameters. - -The generic far future scheduler --------------------------------- - -The far future scheduler is implemented with the following rules: - -- Run until you emit a turn. -- Record all witnesses produced. -- Run until you emit a second turn. -- The witnesses produced between the emission of the first turn (exclusive) -and emission of the second turn (inclusive) are called the *far future schedule*. - -Then the schedule for the rest of time is determined by repeating -the future schedule indefinitely. The far future scheduler is required -to give the scheduling algorithm bounded runtime and memory usage even -in chains involving very long gaps. - -Slots ------ - -Due to dynamic block interval, we must carefully keep in mind -the difference between schedule slots and timestamps. A -*schedule slot number* is a positive integer. A slot number of `n` -represents the `n`th next block-interval-aligned timestamp after -the head block. - -Note that the mapping between slot numbers and timestamps will change -if the block interval changes. - -Scheduling blocks ------------------ - -When each block is produced, the blockchain must determine whether -the scheduler needs to be run. If fewer than `num_witnesses` are -scheduled, the scheduler will run until `2*num_witnesses` are scheduled. -A block in which the scheduler runs is called a *scheduling block*. - -Changes in the set of active witnesses do not modify the existing -schedule. Rather, they will be incorporated into new schedule entries -when the scheduler runs in the next scheduling block. Thus, a witness -that has lost an election may still produce 1-2 blocks. Such a witness -is called a *lame duck*. - -Near vs. far schedule ---------------------- - -From a particular chain state, it must be possible to specify a -mapping from slots to witnesses, called the *total witness schedule*. -The total witness schedule is partitioned into a prefix, called the -*near schedule*; the remainder is the *far schedule*. - -When a block occurs, `n` entries are *drained* (removed) from the head -of the total schedule, where `n` is the slot number of the new block -according to its parent block. - -If the block is a scheduling block, the total schedule is further -transformed. The new near schedule contains `2*num_witnesses` entries, -with the previous near schedule as a prefix. The rest of the near -schedule is determined by the current blockchain RNG. - -The new far schedule is determined by running the far future scheduler, -as described above. The far future scheduler also obtains entropy -from the current blockchain RNG. - -As an optimization, the implementation does not run the far future -scheduler until a far-future slot is actually queried. With this -optimization, the only circumstance under which validating nodes must -run the far future scheduler is when a block gap longer than `num_witnesses` -occurs (an extremely rare condition). - -Minimizing impact of selective dropout --------------------------------------- - -The ability of any single malicious witness to affect the results of the -shuffle algorithm is limited because the RNG is based on bit commitment -of the witnesses. However, a malicious witness *is* able to -refuse to produce a block. A run of `m` consecutively scheduled -malicious witnesses can independently make `m` independent choices -of whether to refuse to produce a block. Basically they are able to -control `m` bits of entropy in the shuffle algorithm's output. - -It is difficult-to-impossible to entirely eliminate "the last person -being evil" problem in trustless distributed RNG's. But we can at least -mitigate this vector by rate-limiting changes to the total witness -schedule to a very slow rate. - -If every block schedules a witness, our adversary with `m` malicious -witnesses gets `m` chances per round to selectively drop out in order -to manipulate the shuffle order, allowing `m` attacks per round. -If witnesses are only scheduled once per round, -a selective dropout requires the malicious witness to produce the -scheduling block, limiting the probability to `m/n` attacks per round. diff --git a/docs b/docs new file mode 160000 index 00000000..1b53a8ec --- /dev/null +++ b/docs @@ -0,0 +1 @@ +Subproject commit 1b53a8eca77783d073ce7cc95991447c3f34b927 From e638efdbcb94ee0965bd09ebd7e672b220842331 Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 10:34:34 -0400 Subject: [PATCH 3/5] More thorough .gitignore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 2b57e790..7fdb5211 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,21 @@ CMakeCache.txt CMakeFiles Makefile compile_commands.json +*.cmake libraries/utilities/git_revision.cpp +programs/cli_wallet/cli_wallet +programs/js_operation_serializer/js_operation_serializer +programs/witness_node/witness_node + tests/app_test tests/chain_bench tests/chain_test +tests/intense_test +tests/performance_test /doxygen + +witness_node_data_dir +wallet.json From c6a7cdf5a3b40992b4edc2287e8cff53f779e3ef Mon Sep 17 00:00:00 2001 From: theoreticalbts Date: Thu, 11 Jun 2015 10:31:19 -0400 Subject: [PATCH 4/5] intense_tests: Implement delegate_groups_mc_test --- tests/intense/block_tests.cpp | 74 +++++++++++++++++++++++++++++++++ tests/tests/operation_tests.cpp | 19 --------- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/tests/intense/block_tests.cpp b/tests/intense/block_tests.cpp index f6bca83e..f603fb74 100644 --- a/tests/intense/block_tests.cpp +++ b/tests/intense/block_tests.cpp @@ -16,6 +16,7 @@ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include @@ -236,4 +237,77 @@ BOOST_FIXTURE_TEST_CASE( update_account_keys, database_fixture ) } } +/** + * To have a secure random number we need to ensure that the same + * delegate does not get to produce two blocks in a row. There is + * always a chance that the last delegate of one round will be the + * first delegate of the next round. + * + * This means that when we shuffle delegates we need to make sure + * that there is at least N/2 delegates between consecutive turns + * of the same delegate. This means that durring the random + * shuffle we need to restrict the placement of delegates to maintain + * this invariant. + * + * This test checks the requirement using Monte Carlo approach + * (produce lots of blocks and check the invariant holds). + */ +BOOST_FIXTURE_TEST_CASE( delegate_groups_mc_test, database_fixture ) +{ + try { + size_t num_witnesses = db.get_global_properties().active_witnesses.size(); + size_t dmin = num_witnesses >> 1; + + vector< witness_id_type > cur_round; + vector< witness_id_type > full_schedule; + // if we make the maximum witness count testable, + // we'll need to enlarge this. + std::bitset< 0x40 > witness_seen; + size_t total_blocks = 1000000; + + cur_round.reserve( num_witnesses ); + full_schedule.reserve( total_blocks ); + + // we assert so the test doesn't continue, which would + // corrupt memory + assert( num_witnesses <= witness_seen.size() ); + + while( full_schedule.size() < total_blocks ) + { + witness_id_type wid = db.get_scheduled_witness( 1 ).first; + full_schedule.push_back( wid ); + cur_round.push_back( wid ); + if( cur_round.size() == num_witnesses ) + { + wdump( (cur_round) ); + // check that the current round contains exactly 1 copy + // of each witness + witness_seen.reset(); + for( const witness_id_type& w : cur_round ) + { + uint64_t inst = w.instance.value; + BOOST_CHECK( !witness_seen.test( inst ) ); + assert( !witness_seen.test( inst ) ); + witness_seen.set( inst ); + } + cur_round.clear(); + } + generate_block(); + } + + for( size_t i=0,m=full_schedule.size(); iaccumulated_income.value, 0); } FC_LOG_AND_RETHROW() } -/** - * To have a secure random number we need to ensure that the same - * delegate does not get to produce two blocks in a row. There is - * always a chance that the last delegate of one round will be the - * first delegate of the next round. - * - * This means that when we shuffle delegates we need to make sure - * that there is at least N/2 delegates between consecutive turns - * of the same delegate. This means that durring the random - * shuffle we need to restrict the placement of delegates to maintain - * this invariant. - */ -BOOST_AUTO_TEST_CASE_EXPECTED_FAILURES( unimp_delegate_groups_test, 1 ) -BOOST_AUTO_TEST_CASE( unimp_delegate_groups_test ) -{ - BOOST_FAIL( "not implemented" ); -} - - /** * This test should simulate a prediction market which means the following: * From a185f864fc10cc8b49e1e7d36cd750996ba276d8 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Thu, 11 Jun 2015 13:54:42 -0400 Subject: [PATCH 5/5] Progress #31: Implement threshold for vesting fees --- libraries/chain/account_object.cpp | 20 +++ libraries/chain/account_operations.cpp | 23 --- libraries/chain/db_balance.cpp | 9 +- libraries/chain/db_maint.cpp | 130 ++++++++-------- libraries/chain/evaluator.cpp | 5 +- .../include/graphene/chain/account_object.hpp | 14 +- .../chain/include/graphene/chain/database.hpp | 2 +- .../graphene/chain/vesting_balance_object.hpp | 85 +++++----- libraries/chain/vesting_balance_object.cpp | 145 ++++++++++-------- tests/common/database_fixture.cpp | 2 +- 10 files changed, 234 insertions(+), 201 deletions(-) delete mode 100644 libraries/chain/account_operations.cpp diff --git a/libraries/chain/account_object.cpp b/libraries/chain/account_object.cpp index 5775713c..59ba30aa 100644 --- a/libraries/chain/account_object.cpp +++ b/libraries/chain/account_object.cpp @@ -36,4 +36,24 @@ void account_balance_object::adjust_balance(const asset& delta) balance += delta.amount; } +uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_parameters& params) const +{ + uint64_t bulk_discount_percent = 0; + if( lifetime_fees_paid >= params.bulk_discount_threshold_max ) + bulk_discount_percent = params.max_bulk_discount_percent_of_fee; + else if(params.bulk_discount_threshold_max.value != + params.bulk_discount_threshold_min.value) + { + bulk_discount_percent = + (params.max_bulk_discount_percent_of_fee * + (lifetime_fees_paid.value - + params.bulk_discount_threshold_min.value)) / + (params.bulk_discount_threshold_max.value - + params.bulk_discount_threshold_min.value); + } + assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); + + return bulk_discount_percent; +} + } } // graphene::chain diff --git a/libraries/chain/account_operations.cpp b/libraries/chain/account_operations.cpp deleted file mode 100644 index efd8e65b..00000000 --- a/libraries/chain/account_operations.cpp +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) 2015, Cryptonomex, Inc. - * All rights reserved. - * - * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and - * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification, - * are permitted until September 8, 2015, provided that the following conditions are met: - * - * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include - -namespace graphene { namespace chain { -} } diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index 2c8736e6..791d8488 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -89,7 +89,7 @@ void database::adjust_core_in_orders( const account_object& acnt, asset delta ) } } -void database::deposit_cashback( const account_object& acct, share_type amount ) +void database::deposit_cashback(const account_object& acct, share_type amount, bool require_vesting) { // If we don't have a VBO, or if it has the wrong maturity // due to a policy change, cut it loose. @@ -112,7 +112,10 @@ void database::deposit_cashback( const account_object& acct, share_type amount ) modify( cashback_vb, [&]( vesting_balance_object& obj ) { - obj.deposit( now, amount ); + if( require_vesting ) + obj.deposit(now, amount); + else + obj.deposit_vested(now, amount); } ); return; } @@ -124,7 +127,7 @@ void database::deposit_cashback( const account_object& acct, share_type amount ) cdd_vesting_policy policy; policy.vesting_seconds = global_vesting_seconds; - policy.coin_seconds_earned = 0; + policy.coin_seconds_earned = require_vesting? 0 : amount.value * policy.vesting_seconds; policy.coin_seconds_earned_last_update = now; obj.policy = policy; diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 84a3597d..0db0b6f6 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -357,86 +357,81 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g process_fees_helper(database& d, const global_property_object& gpo) : d(d), props(gpo) {} + share_type cut_fee(share_type a, uint16_t p)const + { + if( a == 0 || p == 0 ) + return 0; + if( p == GRAPHENE_100_PERCENT ) + return a; + + fc::uint128 r(a.value); + r *= p; + r /= GRAPHENE_100_PERCENT; + return r.to_uint64(); + } + + void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting) + { + share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage); + assert( network_cut <= core_fee_total ); + share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); + share_type accumulated = network_cut - burned; + assert( accumulated + burned == network_cut ); + share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage); + share_type referral = core_fee_total - network_cut - lifetime_cut; + + d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { + d.accumulated_fees += network_cut; + }); + + // Potential optimization: Skip some of this math and object lookups by special casing on the account type. + // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to + // it directly. + share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage); + share_type registrar_cut = referral - referrer_cut; + + d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting); + d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting); + d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting); + + assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total ); + } + void operator()(const account_object& a) { const account_statistics_object& stats = a.statistics(d); - auto cut_fee = [](share_type a, uint16_t p) -> share_type - { - if( a == 0 || p == 0 ) - return 0; - if( p == GRAPHENE_100_PERCENT ) - return a; - - fc::uint128 r(a.value); - r *= p; - r /= GRAPHENE_100_PERCENT; - return r.to_uint64(); - }; - if( stats.pending_fees > 0 ) { - share_type core_fee_subtotal(stats.pending_fees); - share_type bulk_cashback = share_type(0); + share_type vesting_fee_subtotal(stats.pending_fees); + share_type vested_fee_subtotal(stats.pending_vested_fees); + share_type vesting_cashback, vested_cashback; + if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && a.is_member(d.head_block_time()) ) { - uint64_t bulk_discount_percent = 0; - if( stats.lifetime_fees_paid >= props.parameters.bulk_discount_threshold_max ) - bulk_discount_percent = props.parameters.max_bulk_discount_percent_of_fee; - else if(props.parameters.bulk_discount_threshold_max.value != - props.parameters.bulk_discount_threshold_min.value) - { - bulk_discount_percent = - (props.parameters.max_bulk_discount_percent_of_fee * - (stats.lifetime_fees_paid.value - - props.parameters.bulk_discount_threshold_min.value)) / - (props.parameters.bulk_discount_threshold_max.value - - props.parameters.bulk_discount_threshold_min.value); - } - assert( bulk_discount_percent <= GRAPHENE_100_PERCENT ); - assert( bulk_discount_percent >= 0 ); + auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters); + vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate); + vesting_fee_subtotal -= vesting_cashback; - bulk_cashback = cut_fee(core_fee_subtotal, bulk_discount_percent); - assert( bulk_cashback <= core_fee_subtotal ); + vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate); + vested_fee_subtotal -= vested_cashback; } - share_type core_fee_total = core_fee_subtotal - bulk_cashback; - share_type network_cut = cut_fee(core_fee_total, a.network_fee_percentage); - assert( network_cut <= core_fee_total ); - share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); - share_type accumulated = network_cut - burned; - assert( accumulated + burned == network_cut ); - share_type lifetime_cut = cut_fee(core_fee_total, a.lifetime_referrer_fee_percentage); - share_type referral = core_fee_total - network_cut - lifetime_cut; + pay_out_fees(a, vesting_fee_subtotal, true); + d.deposit_cashback(a, vesting_cashback, true); + pay_out_fees(a, vested_fee_subtotal, false); + d.deposit_cashback(a, vested_cashback, false); - d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { - d.accumulated_fees += network_cut; - }); - - d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) { - s.lifetime_fees_paid += core_fee_total; + d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) { + s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal; s.pending_fees = 0; + s.pending_vested_fees = 0; }); - - d.deposit_cashback( a, bulk_cashback ); - - // Potential optimization: Skip some of this math and object lookups by special casing on the account type. - // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to - // it directly. - share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage); - share_type registrar_cut = referral - referrer_cut; - - d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut); - d.deposit_cashback(d.get(a.referrer), referrer_cut); - d.deposit_cashback(d.get(a.registrar), registrar_cut); - - idump((referrer_cut)(registrar_cut)(bulk_cashback)(accumulated)(burned)(lifetime_cut)(core_fee_subtotal)); - assert( referrer_cut + registrar_cut + bulk_cashback + accumulated + burned + lifetime_cut == core_fee_subtotal ); - } + } } - } fees_helper(*this, gpo); + } fee_helper(*this, gpo); - perform_account_maintenance(std::tie(tally_helper, fees_helper)); + perform_account_maintenance(std::tie(tally_helper, fee_helper)); struct clear_canary { clear_canary(vector& target): target(target){} @@ -451,9 +446,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g update_active_witnesses(); update_active_delegates(); - const global_property_object& global_properties = get_global_properties(); - if( global_properties.pending_parameters ) - modify(get_global_properties(), [](global_property_object& p) { + if( gpo.pending_parameters ) + modify(gpo, [](global_property_object& p) { p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); }); @@ -472,7 +466,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g } auto next_maintenance_time = get(dynamic_global_property_id_type()).next_maintenance_time; - auto maintenance_interval = get_global_properties().parameters.maintenance_interval; + auto maintenance_interval = gpo.parameters.maintenance_interval; if( next_maintenance_time <= next_block.timestamp ) { diff --git a/libraries/chain/evaluator.cpp b/libraries/chain/evaluator.cpp index 74cf5c14..46117211 100644 --- a/libraries/chain/evaluator.cpp +++ b/libraries/chain/evaluator.cpp @@ -67,7 +67,10 @@ namespace graphene { namespace chain { d.fee_pool -= core_fee_paid; }); db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) { - s.pending_fees += core_fee_paid; + if( core_fee_paid > db().get_global_properties().parameters.cashback_vesting_threshold ) + s.pending_fees += core_fee_paid; + else + s.pending_vested_fees += core_fee_paid; }); } FC_CAPTURE_AND_RETHROW() } diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index be74e87d..c41a15fc 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -62,11 +62,20 @@ namespace graphene { namespace chain { /** * Tracks the fees paid by this account which have not been disseminated to the various parties that receive * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid - * doing massive amounts of uint128 math on each and every operation. + * doing massive amounts of uint128 arithmetic on each and every operation. * - * These fees will be paid out and this counter will reset during the maintenance interval. + *These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance + *interval. */ share_type pending_fees; + /** + * Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately + * available for withdrawal) rather than requiring the normal vesting period. + */ + share_type pending_vested_fees; + + /// @brief Calculate the percentage discount this user receives on his fees + uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const; }; /** @@ -290,5 +299,6 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain (most_recent_op) (total_core_in_orders) (lifetime_fees_paid) + (pending_fees)(pending_vested_fees) ) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 020ae45d..469a78ad 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -254,7 +254,7 @@ namespace graphene { namespace chain { void adjust_core_in_orders( const account_object& acnt, asset delta ); // helper to handle cashback rewards - void deposit_cashback( const account_object& acct, share_type amount ); + void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true); //////////////////// db_debug.cpp //////////////////// diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index f2465573..c0e32347 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -35,9 +35,9 @@ namespace graphene { namespace chain { vesting_policy_context( asset _balance, fc::time_point_sec _now, - asset _amount ) - : balance( _balance ), now( _now ), amount( _amount ) {} - + asset _amount) + : balance(_balance), now(_now), amount(_amount) {} + asset balance; fc::time_point_sec now; asset amount; @@ -53,11 +53,14 @@ namespace graphene { namespace chain { share_type begin_balance; // same asset as balance share_type total_withdrawn; // same asset as balance - asset get_allowed_withdraw( const vesting_policy_context& ctx )const; - bool is_deposit_allowed( const vesting_policy_context& ctx )const; - bool is_withdraw_allowed( const vesting_policy_context& ctx )const; - void on_deposit( const vesting_policy_context& ctx ); - void on_withdraw( const vesting_policy_context& ctx ); + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; } + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context&) + { FC_THROW( "May not deposit vested into a linear vesting balance." ); } + void on_withdraw(const vesting_policy_context& ctx); }; struct cdd_vesting_policy @@ -71,20 +74,22 @@ namespace graphene { namespace chain { * non-destructively figure out how many coin seconds * are available. */ - fc::uint128_t compute_coin_seconds_earned( const vesting_policy_context& ctx )const; + fc::uint128_t compute_coin_seconds_earned(const vesting_policy_context& ctx)const; /** * Update coin_seconds_earned and * coin_seconds_earned_last_update fields; called by both * on_deposit() and on_withdraw(). */ - void update_coin_seconds_earned( const vesting_policy_context& ctx ); + void update_coin_seconds_earned(const vesting_policy_context& ctx); - asset get_allowed_withdraw( const vesting_policy_context& ctx )const; - bool is_deposit_allowed( const vesting_policy_context& ctx )const; - bool is_withdraw_allowed( const vesting_policy_context& ctx )const; - void on_deposit( const vesting_policy_context& ctx ); - void on_withdraw( const vesting_policy_context& ctx ); + asset get_allowed_withdraw(const vesting_policy_context& ctx)const; + bool is_deposit_allowed(const vesting_policy_context& ctx)const; + bool is_deposit_vested_allowed(const vesting_policy_context& ctx)const; + bool is_withdraw_allowed(const vesting_policy_context& ctx)const; + void on_deposit(const vesting_policy_context& ctx); + void on_deposit_vested(const vesting_policy_context& ctx); + void on_withdraw(const vesting_policy_context& ctx); }; typedef fc::static_variant< @@ -102,17 +107,21 @@ namespace graphene { namespace chain { static const uint8_t space_id = protocol_ids; static const uint8_t type_id = vesting_balance_object_type; - account_id_type owner; - asset balance; - vesting_policy policy; + account_id_type owner; + asset balance; + vesting_policy policy; vesting_balance_object() {} /** * Used to increase existing vesting balances. */ - void deposit( const fc::time_point_sec& now, const asset& amount ); - bool is_deposit_allowed( const fc::time_point_sec& now, const asset& amount )const; + void deposit(const fc::time_point_sec& now, const asset& amount); + bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const; + + /// @brief Deposit amount into vesting balance, making the new funds vest immediately + void deposit_vested(const fc::time_point_sec& now, const asset& amount); + bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const; /** * Used to remove a vesting balance from the VBO. As well @@ -122,27 +131,27 @@ namespace graphene { namespace chain { * The money doesn't "go" anywhere; the caller is responsible * for crediting it to the proper account. */ - void withdraw( const fc::time_point_sec& now, const asset& amount ); - bool is_withdraw_allowed( const fc::time_point_sec& now, const asset& amount )const; + void withdraw(const fc::time_point_sec& now, const asset& amount); + bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const; }; } } // graphene::chain -FC_REFLECT( graphene::chain::linear_vesting_policy, - (vesting_seconds) - (begin_date) - (begin_balance) - (total_withdrawn) -) +FC_REFLECT(graphene::chain::linear_vesting_policy, + (vesting_seconds) + (begin_date) + (begin_balance) + (total_withdrawn) + ) -FC_REFLECT( graphene::chain::cdd_vesting_policy, - (vesting_seconds) - (coin_seconds_earned) - (coin_seconds_earned_last_update) -) +FC_REFLECT(graphene::chain::cdd_vesting_policy, + (vesting_seconds) + (coin_seconds_earned) + (coin_seconds_earned_last_update) + ) -FC_REFLECT_DERIVED( graphene::chain::vesting_balance_object, (graphene::db::object), - (owner) - (balance) - (policy) -) +FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), + (owner) + (balance) + (policy) + ) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 022e892d..bfc91de5 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -20,66 +20,64 @@ namespace graphene { namespace chain { -inline bool sum_below_max_shares( const asset& a, const asset& b ) +inline bool sum_below_max_shares(const asset& a, const asset& b) { - assert( GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY ); - return ( a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) + assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY); + return (a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY) ; } -asset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx ) const +asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const { - if( ctx.now <= begin_date ) - return asset( 0, ctx.balance.asset_id ); - if( vesting_seconds == 0 ) + if(ctx.now <= begin_date) + return asset(0, ctx.balance.asset_id); + if(vesting_seconds == 0) return ctx.balance; int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds(); // if elapsed_seconds <= 0, then ctx.now <= begin_date, // and we should have returned above. - assert( elapsed_seconds > 0 ); + assert(elapsed_seconds > 0); fc::uint128_t total_allowed = begin_balance.value; - total_allowed *= uint64_t( elapsed_seconds ); + total_allowed *= uint64_t(elapsed_seconds); total_allowed /= vesting_seconds; - if( total_allowed <= total_withdrawn.value ) - return asset( 0, ctx.balance.asset_id ); + if(total_allowed <= total_withdrawn.value) + return asset(0, ctx.balance.asset_id); total_allowed -= total_withdrawn.value; - FC_ASSERT( total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY ); - return asset( total_allowed.to_uint64(), ctx.balance.asset_id ); + FC_ASSERT(total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY); + return asset(total_allowed.to_uint64(), ctx.balance.asset_id); } -void linear_vesting_policy::on_deposit( const vesting_policy_context& ctx ) +void linear_vesting_policy::on_deposit(const vesting_policy_context& ctx) { - return; } -bool linear_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const +bool linear_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const { return (ctx.amount.asset_id == ctx.balance.asset_id) - && sum_below_max_shares( ctx.amount, ctx.balance ); + && sum_below_max_shares(ctx.amount, ctx.balance); } -void linear_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) +void linear_vesting_policy::on_withdraw(const vesting_policy_context& ctx) { total_withdrawn += ctx.amount.amount; - return; } -bool linear_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const +bool linear_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const { - return ( ctx.amount <= get_allowed_withdraw( ctx ) ); + return (ctx.amount <= get_allowed_withdraw(ctx)); } -fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_policy_context& ctx )const +fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned(const vesting_policy_context& ctx)const { - assert( ctx.now >= coin_seconds_earned_last_update ); + assert(ctx.now >= coin_seconds_earned_last_update); int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds(); - assert( delta_seconds >= 0 ); + assert(delta_seconds >= 0); fc::uint128_t delta_coin_seconds = ctx.balance.amount.value; delta_coin_seconds *= delta_seconds; @@ -87,113 +85,132 @@ fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_pol fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value; coin_seconds_earned_cap *= vesting_seconds; - return std::min( - coin_seconds_earned + delta_coin_seconds, - coin_seconds_earned_cap - ); + return std::min(coin_seconds_earned + delta_coin_seconds, coin_seconds_earned_cap); } -void cdd_vesting_policy::update_coin_seconds_earned( const vesting_policy_context& ctx ) +void cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context& ctx) { - coin_seconds_earned = compute_coin_seconds_earned( ctx ); + coin_seconds_earned = compute_coin_seconds_earned(ctx); coin_seconds_earned_last_update = ctx.now; - return; } -asset cdd_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +asset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const { - fc::uint128_t cs_earned = compute_coin_seconds_earned( ctx ); + fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx); fc::uint128_t withdraw_available = cs_earned / vesting_seconds; - assert( withdraw_available <= ctx.balance.amount.value ); - return asset( withdraw_available.to_uint64(), ctx.balance.asset_id ); + assert(withdraw_available <= ctx.balance.amount.value); + return asset(withdraw_available.to_uint64(), ctx.balance.asset_id); } -void cdd_vesting_policy::on_deposit( const vesting_policy_context& ctx ) +void cdd_vesting_policy::on_deposit(const vesting_policy_context& ctx) { - update_coin_seconds_earned( ctx ); - return; + update_coin_seconds_earned(ctx); } -void cdd_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) +void cdd_vesting_policy::on_deposit_vested(const vesting_policy_context& ctx) { - update_coin_seconds_earned( ctx ); + on_deposit(ctx); + coin_seconds_earned += ctx.amount.amount.value * vesting_seconds; +} + +void cdd_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ + update_coin_seconds_earned(ctx); fc::uint128_t coin_seconds_needed = ctx.amount.amount.value; coin_seconds_needed *= vesting_seconds; // is_withdraw_allowed should forbid any withdrawal that // would trigger this assert - assert( coin_seconds_needed <= coin_seconds_earned ); + assert(coin_seconds_needed <= coin_seconds_earned); coin_seconds_earned -= coin_seconds_needed; - return; } -bool cdd_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const +bool cdd_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const { return (ctx.amount.asset_id == ctx.balance.asset_id) - && sum_below_max_shares( ctx.amount, ctx.balance ); + && sum_below_max_shares(ctx.amount, ctx.balance); } -bool cdd_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const +bool cdd_vesting_policy::is_deposit_vested_allowed(const vesting_policy_context& ctx) const { - return ( ctx.amount <= get_allowed_withdraw( ctx ) ); + return is_deposit_allowed(ctx); } -#define VESTING_VISITOR( NAME, MAYBE_CONST ) \ +bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount <= get_allowed_withdraw(ctx)); +} + +#define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ typedef decltype( \ std::declval().NAME( \ std::declval()) \ - ) result_type; \ + ) result_type; \ \ NAME ## _visitor( \ const asset& balance, \ const time_point_sec& now, \ const asset& amount \ - ) \ - : ctx( balance, now, amount ) {} \ + ) \ + : ctx(balance, now, amount) {} \ \ template< typename Policy > \ result_type \ - operator()( MAYBE_CONST Policy& policy ) MAYBE_CONST \ + operator()(MAYBE_CONST Policy& policy) MAYBE_CONST \ { \ - return policy.NAME( ctx ); \ + return policy.NAME(ctx); \ } \ \ vesting_policy_context ctx; \ } -VESTING_VISITOR( on_deposit, ); -VESTING_VISITOR( on_withdraw, ); -VESTING_VISITOR( is_deposit_allowed, const ); -VESTING_VISITOR( is_withdraw_allowed, const ); +VESTING_VISITOR(on_deposit,); +VESTING_VISITOR(on_deposit_vested,); +VESTING_VISITOR(on_withdraw,); +VESTING_VISITOR(is_deposit_allowed, const); +VESTING_VISITOR(is_deposit_vested_allowed, const); +VESTING_VISITOR(is_withdraw_allowed, const); bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const { - return policy.visit( is_deposit_allowed_visitor( balance, now, amount ) ); + return policy.visit(is_deposit_allowed_visitor(balance, now, amount)); } bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const { - bool result = policy.visit( is_withdraw_allowed_visitor( balance, now, amount ) ); + bool result = policy.visit(is_withdraw_allowed_visitor(balance, now, amount)); // if some policy allows you to withdraw more than your balance, // there's a programming bug in the policy algorithm - assert( (amount <= balance) || (!result) ); + assert((amount <= balance) || (!result)); return result; } void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount) { - on_deposit_visitor vtor( balance, now, amount ); - policy.visit( vtor ); + on_deposit_visitor vtor(balance, now, amount); + policy.visit(vtor); balance += amount; } +void vesting_balance_object::deposit_vested(const time_point_sec& now, const asset& amount) +{ + on_deposit_vested_visitor vtor(balance, now, amount); + policy.visit(vtor); + balance += amount; +} + +bool vesting_balance_object::is_deposit_vested_allowed(const time_point_sec& now, const asset& amount) const +{ + return policy.visit(is_deposit_vested_allowed_visitor(balance, now, amount)); +} + void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount) { - assert( amount <= balance ); - on_withdraw_visitor vtor( balance, now, amount ); - policy.visit( vtor ); + assert(amount <= balance); + on_withdraw_visitor vtor(balance, now, amount); + policy.visit(vtor); balance -= amount; } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 6f74f777..1717a04a 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -119,7 +119,7 @@ void database_fixture::verify_asset_supplies( )const for( const account_statistics_object& a : statistics_index ) { reported_core_in_orders += a.total_core_in_orders; - total_balances[asset_id_type()] += a.pending_fees; + total_balances[asset_id_type()] += a.pending_fees + a.pending_vested_fees; } for( const limit_order_object& o : db.get_index_type().indices() ) {