diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9194a02d25..f6d0f23062 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -35,6 +35,20 @@ #include namespace graphene { namespace chain { +namespace detail { + // TODO review and remove code below and links to it after hf_1268 + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( !options.extensions.value.reward_percent.valid(), + "Asset extension reward percent is only available after HARDFORK_1268_TIME!"); + + FC_ASSERT( !options.extensions.value.whitelist_market_fee_sharing.valid(), + "Asset extension whitelist_market_fee_sharing is only available after HARDFORK_1268_TIME!"); + } + } +} void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { @@ -45,6 +59,8 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); + detail::check_asset_options_hf_1268(d.head_block_time(), op.common_options); + // Check that all authorities do exist for( auto id : op.common_options.whitelist_authorities ) d.get_object(id); @@ -277,6 +293,8 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + detail::check_asset_options_hf_1268(d.head_block_time(), o.new_options); + if( (d.head_block_time() < HARDFORK_572_TIME) || (a.dynamic_asset_data_id(d).current_supply != 0) ) { // new issuer_permissions must be subset of old issuer permissions diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index caa1eff63b..2a48679e3e 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -81,9 +82,81 @@ void database::adjust_balance(account_id_type account, asset delta ) } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } +namespace detail { + + /** + * Used as a key to search vesting_balance_object in the index + */ + struct vbo_mfs_key + { + account_id_type account_id; + asset_id_type asset_id; + + vbo_mfs_key(const account_id_type& account, const asset_id_type& asset): + account_id(account), + asset_id(asset) + {} + + bool operator()(const vbo_mfs_key& k, const vesting_balance_object& vbo)const + { + return ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) && + ( k.asset_id == vbo.balance.asset_id ) && + ( k.account_id == vbo.owner ); + } + + uint64_t operator()(const vbo_mfs_key& k)const + { + return vbo_mfs_hash(k.account_id, k.asset_id); + } + }; +} //detail + +asset database::get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id) +{ + auto& vesting_balances = get_index_type().indices().get(); + const auto& key = detail::vbo_mfs_key{account_id, asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); + + if( vbo_it == vesting_balances.end() ) + { + return asset(0, asset_id); + } + return vbo_it->balance; +} + +void database::deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta) +{ try { + FC_ASSERT( delta.amount >= 0, "Invalid negative value for balance"); + + if( delta.amount == 0 ) + return; + + auto& vesting_balances = get_index_type().indices().get(); + const auto& key = detail::vbo_mfs_key{account_id, delta.asset_id}; + auto vbo_it = vesting_balances.find(key, key, key); + + auto block_time = head_block_time(); + + if( vbo_it == vesting_balances.end() ) + { + create([&account_id, &delta, &block_time](vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance = delta; + vbo.balance_type = vesting_balance_type::market_fee_sharing; + vbo.policy = instant_vesting_policy{}; + }); + } else { + modify( *vbo_it, [&block_time, &delta]( vesting_balance_object& vbo ) + { + vbo.deposit_vested(block_time, delta); + }); + } +} FC_CAPTURE_AND_RETHROW( (account_id)(delta) ) } + optional< vesting_balance_id_type > database::deposit_lazy_vesting( const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ) { @@ -117,6 +190,7 @@ optional< vesting_balance_id_type > database::deposit_lazy_vesting( { _vbo.owner = req_owner; _vbo.balance = amount; + _vbo.balance_type = balance_type; cdd_vesting_policy policy; policy.vesting_seconds = req_vesting_seconds; @@ -152,6 +226,7 @@ void database::deposit_cashback(const account_object& acct, share_type amount, b acct.cashback_vb, amount, get_global_properties().parameters.cashback_vesting_period_seconds, + vesting_balance_type::cashback, acct.id, require_vesting ); @@ -179,6 +254,7 @@ void database::deposit_witness_pay(const witness_object& wit, share_type amount) wit.pay_vb, amount, get_global_properties().parameters.witness_pay_vesting_seconds, + vesting_balance_type::witness, wit.witness_account, true ); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 6b8f67ea1c..8feae4e0ef 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -28,10 +28,21 @@ #include #include #include +#include #include -namespace graphene { namespace chain { +namespace graphene { namespace chain { namespace detail { + + uint64_t calculate_percent(const share_type& value, uint16_t percent) + { + fc::uint128 a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a.to_uint64(); + } + +} //detail /** * All margin positions are force closed at the swan price @@ -775,7 +786,10 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p const account_object& seller = order.seller(*this); const asset_object& recv_asset = receives.asset_id(*this); - auto issuer_fees = pay_market_fees( recv_asset, receives ); + auto issuer_fees = ( head_block_time() < HARDFORK_1268_TIME ) ? + pay_market_fees(recv_asset, receives) : + pay_market_fees(seller, recv_asset, receives); + pay_order( seller, receives - issuer_fees, pays ); assert( pays.asset_id != receives.asset_id ); @@ -1109,10 +1123,8 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass if( trade_asset.options.market_fee_percent == 0 ) return trade_asset.amount(0); - fc::uint128 a(trade_amount.amount.value); - a *= trade_asset.options.market_fee_percent; - a /= GRAPHENE_100_PERCENT; - asset percent_fee = trade_asset.amount(a.to_uint64()); + auto value = detail::calculate_percent(trade_amount.amount, trade_asset.options.market_fee_percent); + asset percent_fee = trade_asset.amount(value); if( percent_fee.amount > trade_asset.options.max_market_fee ) percent_fee.amount = trade_asset.options.max_market_fee; @@ -1123,7 +1135,7 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass asset database::pay_market_fees( const asset_object& recv_asset, const asset& receives ) { auto issuer_fees = calculate_market_fee( recv_asset, receives ); - assert(issuer_fees <= receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); //Don't dirty undo state if not actually collecting any fees if( issuer_fees.amount > 0 ) @@ -1138,4 +1150,55 @@ asset database::pay_market_fees( const asset_object& recv_asset, const asset& re return issuer_fees; } +asset database::pay_market_fees(const account_object& seller, const asset_object& recv_asset, const asset& receives ) +{ + const auto issuer_fees = calculate_market_fee( recv_asset, receives ); + FC_ASSERT( issuer_fees <= receives, "Market fee shouldn't be greater than receives"); + //Don't dirty undo state if not actually collecting any fees + if ( issuer_fees.amount > 0 ) + { + // calculate and pay rewards + asset reward = recv_asset.amount(0); + + auto is_rewards_allowed = [&recv_asset, &seller]() { + const auto &white_list = recv_asset.options.extensions.value.whitelist_market_fee_sharing; + return ( !white_list || (*white_list).empty() || ( (*white_list).find(seller.registrar) != (*white_list).end() ) ); + }; + + if ( is_rewards_allowed() ) + { + const auto reward_percent = recv_asset.options.extensions.value.reward_percent; + if ( reward_percent && *reward_percent ) + { + const auto reward_value = detail::calculate_percent(issuer_fees.amount, *reward_percent); + if ( reward_value > 0 && is_authorized_asset(*this, seller.registrar(*this), recv_asset) ) + { + reward = recv_asset.amount(reward_value); + FC_ASSERT( reward < issuer_fees, "Market reward should be less than issuer fees"); + // cut referrer percent from reward + const auto referrer_rewards_percentage = seller.referrer_rewards_percentage; + const auto referrer_rewards_value = detail::calculate_percent(reward.amount, referrer_rewards_percentage); + auto registrar_reward = reward; + + if ( referrer_rewards_value > 0 && is_authorized_asset(*this, seller.referrer(*this), recv_asset)) + { + FC_ASSERT ( referrer_rewards_value <= reward.amount, "Referrer reward shouldn't be greater than total reward" ); + const asset referrer_reward = recv_asset.amount(referrer_rewards_value); + registrar_reward -= referrer_reward; + deposit_market_fee_vesting_balance(seller.referrer, referrer_reward); + } + deposit_market_fee_vesting_balance(seller.registrar, registrar_reward); + } + } + } + + const auto& recv_dyn_data = recv_asset.dynamic_asset_data_id(*this); + modify( recv_dyn_data, [&issuer_fees, &reward]( asset_dynamic_data_object& obj ){ + obj.accumulated_fees += issuer_fees.amount - reward.amount; + }); + } + + return issuer_fees; +} + } } diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf new file mode 100644 index 0000000000..352463c043 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1268.hf @@ -0,0 +1,4 @@ +// #1268 Distribute Asset Market Fees to Referral Program +#ifndef HARDFORK_1268_TIME +#define HARDFORK_1268_TIME (fc::time_point_sec( 1577880000 )) // Wednesday, January 1, 2020 12:00:00 PM +#endif diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 7939fa703b..9bc3c67d3a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -47,6 +47,7 @@ namespace graphene { namespace chain { class transaction_evaluation_state; struct budget_record; + enum class vesting_balance_type; /** * @class database @@ -302,6 +303,15 @@ namespace graphene { namespace chain { */ void adjust_balance(account_id_type account, asset delta); + void deposit_market_fee_vesting_balance(const account_id_type &account_id, const asset &delta); + /** + * @brief Retrieve a particular account's market fee vesting balance in a given asset + * @param owner Account whose balance should be retrieved + * @param asset_id ID of the asset to get balance in + * @return owner's balance in asset + */ + asset get_market_fee_vesting_balance(const account_id_type &account_id, const asset_id_type &asset_id); + /** * @brief Helper to make lazy deposit to CDD VBO. * @@ -319,6 +329,7 @@ namespace graphene { namespace chain { const optional< vesting_balance_id_type >& ovbid, share_type amount, uint32_t req_vesting_seconds, + vesting_balance_type balance_type, account_id_type req_owner, bool require_vesting ); @@ -394,6 +405,7 @@ namespace graphene { namespace chain { asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); + asset pay_market_fees( const account_object& seller, const asset_object& recv_asset, const asset& receives ); ///@{ diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index 3a045a30c9..9c6fca3c9c 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -27,6 +27,13 @@ namespace graphene { namespace chain { + struct additional_asset_options + { + fc::optional reward_percent; + fc::optional> whitelist_market_fee_sharing; + }; + typedef extension additional_asset_options_t; + bool is_valid_symbol( const string& symbol ); /** @@ -75,7 +82,7 @@ namespace graphene { namespace chain { * size of description. */ string description; - extensions_type extensions; + additional_asset_options_t extensions; /// Perform internal consistency checks. /// @throws fc::exception if any check fails @@ -535,7 +542,7 @@ FC_REFLECT( graphene::chain::bitasset_options, (extensions) ) - +FC_REFLECT( graphene::chain::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing) ) FC_REFLECT( graphene::chain::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_global_settle_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_settle_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/protocol/vesting.hpp b/libraries/chain/include/graphene/chain/protocol/vesting.hpp index 4915b62ec6..b5d03585de 100644 --- a/libraries/chain/include/graphene/chain/protocol/vesting.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vesting.hpp @@ -42,8 +42,15 @@ namespace graphene { namespace chain { cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){} }; - typedef fc::static_variant vesting_policy_initializer; + struct instant_vesting_policy_initializer + { + }; + typedef fc::static_variant< + linear_vesting_policy_initializer, + cdd_vesting_policy_initializer, + instant_vesting_policy_initializer + > vesting_policy_initializer; /** @@ -117,4 +124,5 @@ FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_b FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) ) FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy_initializer ) FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 210c6c5870..ec3ab0c850 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -26,6 +26,9 @@ #include #include #include +#include +#include +#include #include #include @@ -119,11 +122,34 @@ namespace graphene { namespace chain { void on_withdraw(const vesting_policy_context& ctx); }; + /** + * @brief instant vesting policy + * + * This policy allows to withdraw everything that is on a balance immediately + * + */ + struct instant_vesting_policy + { + 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&); + void on_withdraw(const vesting_policy_context& ctx); + }; + typedef fc::static_variant< linear_vesting_policy, - cdd_vesting_policy + cdd_vesting_policy, + instant_vesting_policy > vesting_policy; + enum class vesting_balance_type { unspecified, + cashback, + worker, + witness, + market_fee_sharing }; /** * Vesting balance object is a balance that is locked by the blockchain for a period of time. */ @@ -140,6 +166,8 @@ namespace graphene { namespace chain { asset balance; /// The vesting policy stores details on when funds vest, and controls when they may be withdrawn vesting_policy policy; + /// type of the vesting balance + vesting_balance_type balance_type = vesting_balance_type::unspecified; vesting_balance_object() {} @@ -171,12 +199,75 @@ namespace graphene { namespace chain { * @ingroup object_index */ struct by_account; + // by_vesting_type index MUST NOT be used for iterating because order is not well-defined. + struct by_vesting_type; + +namespace detail { + + /** + Calculate a hash for account_id_type and asset_id. + Use 48 bit value (see object_id.hpp) for account_id and XOR it with 24 bit for asset_id + */ + inline uint64_t vbo_mfs_hash(const account_id_type& account_id, const asset_id_type& asset_id) + { + return (asset_id.instance.value << 40) ^ account_id.instance.value; + } + + /** + * Used as CompatibleHash + Calculate a hash vesting_balance_object + if vesting_balance_object.balance_type is market_fee_sharing + calculate has as vbo_mfs_hash(vesting_balance_object.owner, hash(vbo.balance.asset_id) (see vbo_mfs_hash) + otherwise: hash_value(vesting_balance_object.id); + */ + struct vesting_balance_object_hash + { + uint64_t operator()(const vesting_balance_object& vbo) const + { + if ( vbo.balance_type == vesting_balance_type::market_fee_sharing ) + { + return vbo_mfs_hash(vbo.owner, vbo.balance.asset_id); + } + return hash_value(vbo.id); + } + }; + + /** + * Used as CompatiblePred + * Compares two vesting_balance_objects + * if vesting_balance_object.balance_type is a market_fee_sharing + * compare owners' ids and assets' ids + * otherwise: vesting_balance_object.id + */ + struct vesting_balance_object_equal + { + bool operator() (const vesting_balance_object& lhs, const vesting_balance_object& rhs) const + { + if ( ( lhs.balance_type == vesting_balance_type::market_fee_sharing ) && + ( lhs.balance_type == rhs.balance_type ) && + ( lhs.owner == rhs.owner ) && + ( lhs.balance.asset_id == rhs.balance.asset_id) + ) + { + return true; + } + return ( lhs.id == rhs.id ); + } + }; +} // detail + typedef multi_index_container< vesting_balance_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, member< object, object_id_type, &object::id > + >, ordered_non_unique< tag, member + >, + hashed_unique< tag, + identity, + detail::vesting_balance_object_hash, + detail::vesting_balance_object_equal > > > vesting_balance_multi_index_type; @@ -201,10 +292,16 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy, (coin_seconds_earned_last_update) ) +FC_REFLECT_EMPTY( graphene::chain::instant_vesting_policy ) + FC_REFLECT_TYPENAME( graphene::chain::vesting_policy ) FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object), (owner) (balance) (policy) + (balance_type) ) + +FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(cashback)(worker)(witness)(market_fee_sharing) ) + diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 4e35c0234a..348688d605 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -30,6 +30,10 @@ namespace graphene { namespace chain { +namespace detail { + void check_asset_options_hf_1268(const fc::time_point_sec& block_time, const asset_options& options); + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy); +} struct proposal_operation_hardfork_visitor { @@ -57,6 +61,16 @@ struct proposal_operation_hardfork_visitor static const std::locale &loc = std::locale::classic(); FC_ASSERT(isalpha(v.symbol.back(), loc), "Asset ${s} must end with alpha character before hardfork 620", ("s", v.symbol)); } + + detail::check_asset_options_hf_1268(block_time, v.common_options); + } + // hf_1268 + void operator()(const graphene::chain::asset_update_operation &v) const { + detail::check_asset_options_hf_1268(block_time, v.new_options); + } + // hf_1268 + void operator()(const graphene::chain::vesting_balance_create_operation &v) const { + detail::check_vesting_balance_policy_hf_1268(block_time, v.policy); } // hf_199 void operator()(const graphene::chain::asset_update_issuer_operation &v) const { diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index ee918fd16d..586045e4e6 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -26,8 +26,20 @@ #include #include #include +#include namespace graphene { namespace chain { +namespace detail { + // TODO review and remove code below and links to it after hf_1268 + void check_vesting_balance_policy_hf_1268(const fc::time_point_sec& block_time, const vesting_policy_initializer& policy) + { + if( block_time < HARDFORK_1268_TIME ) + { + FC_ASSERT( policy.which() != vesting_policy_initializer::tag::value, + "Instant vesting policy is only available after HARDFORK_1268_TIME!"); + } + } +} void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance_create_operation& op ) { try { @@ -42,6 +54,8 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount ); FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() ); + detail::check_vesting_balance_policy_hf_1268(d.head_block_time(), op.policy); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -76,6 +90,12 @@ struct init_policy_visitor policy.coin_seconds_earned_last_update = now; p = policy; } + + void operator()( const instant_vesting_policy_initializer& i )const + { + p = instant_vesting_policy{}; + } + }; object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op ) diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index 73448e04c8..8735a674f5 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -157,6 +157,36 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c return (ctx.amount <= get_allowed_withdraw(ctx)); } +asset instant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const +{ + return ctx.balance; +} + +void instant_vesting_policy::on_deposit(const vesting_policy_context& ctx) +{ +} + +void instant_vesting_policy::on_deposit_vested(const vesting_policy_context&) +{ + +} + +bool instant_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); +} + +void instant_vesting_policy::on_withdraw(const vesting_policy_context& ctx) +{ +} + +bool instant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const +{ + return (ctx.amount.asset_id == ctx.balance.asset_id) + && (ctx.amount <= get_allowed_withdraw(ctx)); +} + #define VESTING_VISITOR(NAME, MAYBE_CONST) \ struct NAME ## _visitor \ { \ diff --git a/libraries/chain/worker_evaluator.cpp b/libraries/chain/worker_evaluator.cpp index b5aea8f3b4..240f9723fa 100644 --- a/libraries/chain/worker_evaluator.cpp +++ b/libraries/chain/worker_evaluator.cpp @@ -58,6 +58,7 @@ struct worker_init_visitor w.balance = db.create([&](vesting_balance_object& b) { b.owner = worker.worker_account; b.balance = asset(0); + b.balance_type = vesting_balance_type::worker; cdd_vesting_policy policy; policy.vesting_seconds = fc::days(i.pay_vesting_period_days).to_seconds(); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 4b930681ec..d1af826d39 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include #include @@ -64,7 +65,7 @@ void clearable_block::clear() _block_id = block_id_type(); } -database_fixture::database_fixture() +database_fixture::database_fixture(const fc::time_point_sec &initial_timestamp) : app(), db( *app.chain_database() ) { try { @@ -84,7 +85,7 @@ database_fixture::database_fixture() boost::program_options::variables_map options; - genesis_state.initial_timestamp = time_point_sec( GRAPHENE_TESTING_GENESIS_TIMESTAMP ); + genesis_state.initial_timestamp = initial_timestamp; genesis_state.initial_active_witnesses = 10; for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i ) @@ -393,7 +394,7 @@ account_create_operation database_fixture::make_account( const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 */, public_key_type key /* = public_key_type() */ ) { @@ -527,8 +528,10 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na return db.get(ptx.operation_results[0].get()); } -const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, uint16_t flags, - const price& core_exchange_rate, uint16_t precision) +const asset_object& database_fixture::create_user_issued_asset( const string& name, const account_object& issuer, + uint16_t flags, const price& core_exchange_rate, + uint8_t precision, uint16_t market_fee_percent, + additional_asset_options_t additional_options) { asset_create_operation creator; creator.issuer = issuer.id; @@ -540,6 +543,8 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; creator.common_options.flags = flags; creator.common_options.issuer_permissions = flags; + creator.common_options.market_fee_percent = market_fee_percent; + creator.common_options.extensions = std::move(additional_options); trx.operations.clear(); trx.operations.push_back(std::move(creator)); set_expiration( db, trx ); @@ -613,7 +618,7 @@ const account_object& database_fixture::create_account( const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent /* = 100 */, + uint16_t referrer_percent /* = 100 (1%)*/, const public_key_type& key /*= public_key_type()*/ ) { @@ -635,7 +640,7 @@ const account_object& database_fixture::create_account( const private_key_type& key, const account_id_type& registrar_id /* = account_id_type() */, const account_id_type& referrer_id /* = account_id_type() */, - uint8_t referrer_percent /* = 100 */ + uint16_t referrer_percent /* = 100 (1%)*/ ) { try @@ -645,6 +650,8 @@ const account_object& database_fixture::create_account( account_create_operation account_create_op; account_create_op.registrar = registrar_id; + account_create_op.referrer = referrer_id; + account_create_op.referrer_percent = referrer_percent; account_create_op.name = name; account_create_op.owner = authority(1234, public_key_type(key.get_public_key()), 1234); account_create_op.active = authority(5678, public_key_type(key.get_public_key()), 5678); @@ -741,6 +748,9 @@ const limit_order_object* database_fixture::create_sell_order( const account_obj const time_point_sec order_expiration, const price& fee_core_exchange_rate ) { + set_expiration( db, trx ); + trx.operations.clear(); + limit_order_create_operation buy_order; buy_order.seller = user.id; buy_order.amount_to_sell = amount; @@ -1138,6 +1148,16 @@ int64_t database_fixture::get_balance( const account_object& account, const asse return db.get_balance(account.get_id(), a.get_id()).amount.value; } +int64_t database_fixture::get_market_fee_reward( account_id_type account_id, asset_id_type asset_id)const +{ + return db.get_market_fee_vesting_balance(account_id, asset_id).amount.value; +} + +int64_t database_fixture::get_market_fee_reward( const account_object& account, const asset_object& asset )const +{ + return get_market_fee_reward(account.get_id(), asset.get_id()); +} + vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const { vector< operation_history_object > result; diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 2a0b00347a..fee840cad6 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -156,7 +156,7 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; #define ACTOR(name) \ PREP_ACTOR(name) \ - const auto& name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ + const auto name = create_account(BOOST_PP_STRINGIZE(name), name ## _public_key); \ graphene::chain::account_id_type name ## _id = name.id; (void)name ## _id; #define GET_ACTOR(name) \ @@ -193,7 +193,8 @@ struct database_fixture { bool skip_key_index_test = false; uint32_t anon_acct_count; - database_fixture(); + database_fixture(const fc::time_point_sec &initial_timestamp = + fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP)); ~database_fixture(); static fc::ecc::private_key generate_private_key(string seed); @@ -226,7 +227,7 @@ struct database_fixture { const std::string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, public_key_type key = public_key_type() ); @@ -290,7 +291,9 @@ struct database_fixture { const account_object& issuer, uint16_t flags, const price& core_exchange_rate = price(asset(1, asset_id_type(1)), asset(1)), - uint16_t precision = 2 /* traditional precision for tests */); + uint8_t precision = 2 /* traditional precision for tests */, + uint16_t market_fee_percent = 0, + additional_asset_options_t options = additional_asset_options_t()); void issue_uia( const account_object& recipient, asset amount ); void issue_uia( account_id_type recipient_id, asset amount ); @@ -303,7 +306,7 @@ struct database_fixture { const string& name, const account_object& registrar, const account_object& referrer, - uint8_t referrer_percent = 100, + uint16_t referrer_percent = 100, const public_key_type& key = public_key_type() ); @@ -312,7 +315,7 @@ struct database_fixture { const private_key_type& key, const account_id_type& registrar_id = account_id_type(), const account_id_type& referrer_id = account_id_type(), - uint8_t referrer_percent = 100 + uint16_t referrer_percent = 100 ); const committee_member_object& create_committee_member( const account_object& owner ); @@ -353,6 +356,10 @@ struct database_fixture { void print_joint_market( const string& syma, const string& symb )const; int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; + + int64_t get_market_fee_reward( account_id_type account, asset_id_type asset )const; + int64_t get_market_fee_reward( const account_object& account, const asset_object& asset )const; + vector< operation_history_object > get_operation_history( account_id_type account_id )const; vector< graphene::market_history::order_history_object > get_market_order_history( asset_id_type a, asset_id_type b )const; }; diff --git a/tests/performance/market_fee_sharing_tests.cpp b/tests/performance/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..5c431595ea --- /dev/null +++ b/tests/performance/market_fee_sharing_tests.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Bitshares Foundation, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_CASE(mfs_performance_test, database_fixture) +{ + try + { + ACTORS((issuer)); + + const unsigned int accounts = 3000; + const unsigned int iterations = 20; + + std::vector registrators; + for (unsigned int i = 0; i < accounts; ++i) + { + auto account = create_account("registrar" + std::to_string(i)); + transfer(committee_account, account.get_id(), asset(1000000)); + upgrade_to_lifetime_member(account); + + registrators.push_back(std::move(account)); + } + + generate_blocks(HARDFORK_1268_TIME); + generate_block(); + + additional_asset_options_t options; + options.value.reward_percent = 2 * GRAPHENE_1_PERCENT; + + const auto usd = create_user_issued_asset( + "USD", + issuer, + charge_market_fee, + price(asset(1, asset_id_type(1)), asset(1)), + 1, + 20 * GRAPHENE_1_PERCENT, + options); + + issue_uia(issuer, usd.amount(iterations * accounts * 2000)); + + std::vector traders; + for (unsigned int i = 0; i < accounts; ++i) + { + std::string name = "account" + std::to_string(i); + auto account = create_account(name, registrators[i], registrators[i], GRAPHENE_1_PERCENT); + transfer(committee_account, account.get_id(), asset(1000000)); + transfer(issuer, account, usd.amount(iterations * 2000)); + + traders.push_back(std::move(account)); + } + + using namespace std::chrono; + + const auto start = high_resolution_clock::now(); + + for (unsigned int i = 0; i < iterations; ++i) + { + for (unsigned int j = 0; j < accounts; ++j) + { + create_sell_order(traders[j], usd.amount(2000), asset(1)); + create_sell_order(traders[accounts - j - 1], asset(1), usd.amount(1000)); + } + } + + const auto end = high_resolution_clock::now(); + + const auto elapsed = duration_cast(end - start); + wlog("Elapsed: ${c} ms", ("c", elapsed.count())); + + for (unsigned int i = 0; i < accounts; ++i) + { + const auto reward = get_market_fee_reward(registrators[i], usd); + BOOST_CHECK_GT(reward, 0); + } + } + FC_LOG_AND_RETHROW() +} diff --git a/tests/tests/market_fee_sharing_tests.cpp b/tests/tests/market_fee_sharing_tests.cpp new file mode 100644 index 0000000000..c8df1f56ac --- /dev/null +++ b/tests/tests/market_fee_sharing_tests.cpp @@ -0,0 +1,975 @@ +#include + +#include +#include +#include +#include +#include + + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +namespace fc +{ + template + std::basic_ostream& operator<<(std::basic_ostream& os, safe const& sf) + { + os << sf.value; + return os; + } +} + +struct reward_database_fixture : database_fixture +{ + using whitelist_market_fee_sharing_t = fc::optional>; + + reward_database_fixture() + : database_fixture(HARDFORK_1268_TIME - 100) + { + } + + void update_asset( const account_id_type& issuer_id, + const fc::ecc::private_key& private_key, + const asset_id_type& asset_id, + uint16_t reward_percent, + const whitelist_market_fee_sharing_t &whitelist_market_fee_sharing = whitelist_market_fee_sharing_t{}, + const flat_set &blacklist = flat_set()) + { + asset_update_operation op; + op.issuer = issuer_id; + op.asset_to_update = asset_id; + op.new_options = asset_id(db).options; + op.new_options.extensions.value.reward_percent = reward_percent; + op.new_options.extensions.value.whitelist_market_fee_sharing = whitelist_market_fee_sharing; + op.new_options.blacklist_authorities = blacklist; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + } + + void asset_update_blacklist_authority(const account_id_type& issuer_id, + const asset_id_type& asset_id, + const account_id_type& authority_account_id, + const fc::ecc::private_key& issuer_private_key) + { + asset_update_operation uop; + uop.issuer = issuer_id; + uop.asset_to_update = asset_id; + uop.new_options = asset_id(db).options; + uop.new_options.blacklist_authorities.insert(authority_account_id); + + signed_transaction tx; + tx.operations.push_back( uop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + + void add_account_to_blacklist(const account_id_type& authorizing_account_id, + const account_id_type& blacklisted_account_id, + const fc::ecc::private_key& authorizing_account_private_key) + { + account_whitelist_operation wop; + wop.authorizing_account = authorizing_account_id; + wop.account_to_list = blacklisted_account_id; + wop.new_listing = account_whitelist_operation::black_listed; + + signed_transaction tx; + tx.operations.push_back( wop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, authorizing_account_private_key ); + PUSH_TX( db, tx); + } + + void generate_blocks_past_hf1268() + { + database_fixture::generate_blocks( HARDFORK_1268_TIME ); + database_fixture::generate_block(); + } + + asset core_asset(int64_t x ) + { + return asset( x*core_precision ); + }; + + const share_type core_precision = asset::scaled_precision( asset_id_type()(db).precision ); + + void create_vesting_balance_object(const account_id_type& account_id, vesting_balance_type balance_type ) + { + auto block_time = db.head_block_time(); + + db.create([&account_id, &block_time, balance_type] (vesting_balance_object &vbo) { + vbo.owner = account_id; + vbo.balance_type = balance_type; + }); + }; +}; + +BOOST_FIXTURE_TEST_SUITE( fee_sharing_tests, reward_database_fixture ) + +BOOST_AUTO_TEST_CASE(cannot_create_asset_with_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + GRAPHENE_CHECK_THROW(create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options), + fc::assert_exception); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_asset_with_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + generate_blocks_past_hf1268(); + + uint16_t reward_percent = 100; + flat_set whitelist = {issuer_id}; + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 100; + + additional_asset_options_t options; + options.value.reward_percent = reward_percent; + options.value.whitelist_market_fee_sharing = whitelist; + + asset_object usd_asset = create_user_issued_asset("USD", + issuer, + charge_market_fee, + price, + 2, + market_fee_percent, + options); + + additional_asset_options usd_options = usd_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *usd_options.reward_percent); + BOOST_CHECK(whitelist == *usd_options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(cannot_update_additional_options_before_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + flat_set whitelist = {issuer_id}; + GRAPHENE_CHECK_THROW( + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), 40, whitelist), + fc::assert_exception ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_additional_options_after_hf) +{ + try + { + ACTOR(issuer); + + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + generate_blocks_past_hf1268(); + + uint16_t reward_percent = 40; + flat_set whitelist = {issuer_id}; + update_asset(issuer_id, issuer_private_key, usd_asset.get_id(), reward_percent, whitelist); + + asset_object updated_asset = usd_asset.get_id()(db); + additional_asset_options options = updated_asset.options.extensions.value; + BOOST_CHECK_EQUAL(reward_percent, *options.reward_percent); + BOOST_CHECK(whitelist == *options.whitelist_market_fee_sharing); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(asset_rewards_test) +{ + try + { + ACTORS((registrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + auto register_account = [&](const string& name, const account_object& referrer) -> const account_object& + { + uint16_t referrer_percent = GRAPHENE_1_PERCENT; + fc::ecc::private_key _private_key = generate_private_key(name); + public_key_type _public_key = _private_key.get_public_key(); + return create_account(name, registrar, referrer, referrer_percent, _public_key); + }; + + // Izzy issues asset to Alice + // Jill issues asset to Bob + // Alice and Bob trade in the market and pay fees + // Bob's and Alice's referrers can get reward + upgrade_to_lifetime_member(registrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobreferrer); + + auto alice = register_account("alice", alicereferrer); + auto bob = register_account("bob", bobreferrer); + + transfer( committee_account, alice.id, core_asset(1000000) ); + transfer( committee_account, bob.id, core_asset(1000000) ); + transfer( committee_account, izzy_id, core_asset(1000000) ); + transfer( committee_account, jill_id, core_asset(1000000) ); + + constexpr auto izzycoin_reward_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 20*GRAPHENE_1_PERCENT; + + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + + asset_id_type izzycoin_id = create_bitasset( "IZZYCOIN", izzy_id, izzycoin_market_percent ).id; + asset_id_type jillcoin_id = create_bitasset( "JILLCOIN", jill_id, jillcoin_market_percent ).id; + + generate_blocks_past_hf1268(); + + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + const share_type izzy_prec = asset::scaled_precision( asset_id_type(izzycoin_id)(db).precision ); + const share_type jill_prec = asset::scaled_precision( asset_id_type(jillcoin_id)(db).precision ); + + auto _izzy = [&]( int64_t x ) -> asset + { return asset( x*izzy_prec, izzycoin_id ); }; + auto _jill = [&]( int64_t x ) -> asset + { return asset( x*jill_prec, jillcoin_id ); }; + + update_feed_producers( izzycoin_id(db), { izzy_id } ); + update_feed_producers( jillcoin_id(db), { jill_id } ); + + // Izzycoin is worth 100 BTS + price_feed feed; + feed.settlement_price = price( _izzy(1), core_asset(100) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( izzycoin_id(db), izzy, feed ); + + // Jillcoin is worth 30 BTS + feed.settlement_price = price( _jill(1), core_asset(30) ); + feed.maintenance_collateral_ratio = 175 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + feed.maximum_short_squeeze_ratio = 150 * GRAPHENE_COLLATERAL_RATIO_DENOM / 100; + publish_feed( jillcoin_id(db), jill, feed ); + + enable_fees(); + + // Alice and Bob create some coins + borrow( alice.id, _izzy( 1500), core_asset( 600000) ); + borrow( bob.id, _jill(2000), core_asset(180000) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, _izzy(1000), _jill(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, _jill(1500), _izzy(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + // Bob's and Alice's referrers should get rewards + share_type bob_refereer_reward = get_market_fee_reward( bob.referrer, izzycoin_id ); + share_type alice_refereer_reward = get_market_fee_reward( alice.referrer, jillcoin_id ); + + // Bob's and Alice's registrars should get rewards + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + share_type alice_registrar_reward = get_market_fee_reward( alice.registrar, jillcoin_id ); + + auto calculate_percent = [](const share_type& value, uint16_t percent) + { + auto a(value.value); + a *= percent; + a /= GRAPHENE_100_PERCENT; + return a; + }; + + BOOST_CHECK_GT( bob_refereer_reward, 0 ); + BOOST_CHECK_GT( alice_refereer_reward, 0 ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); + BOOST_CHECK_GT( alice_registrar_reward, 0 ); + + const auto izzycoin_market_fee = calculate_percent(_izzy(1000).amount, izzycoin_market_percent); + const auto izzycoin_reward = calculate_percent(izzycoin_market_fee, izzycoin_reward_percent); + BOOST_CHECK_EQUAL( izzycoin_reward, bob_refereer_reward + bob_registrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(izzycoin_reward, bob.referrer_rewards_percentage), bob_refereer_reward ); + + const auto jillcoin_market_fee = calculate_percent(_jill(1500).amount, jillcoin_market_percent); + const auto jillcoin_reward = calculate_percent(jillcoin_market_fee, jillcoin_reward_percent); + BOOST_CHECK_EQUAL( jillcoin_reward, alice_refereer_reward + alice_registrar_reward ); + BOOST_CHECK_EQUAL( calculate_percent(jillcoin_reward, alice.referrer_rewards_percentage), alice_refereer_reward ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(asset_claim_reward_test) +{ + try + { + ACTORS((jill)(izzy)); + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + + upgrade_to_lifetime_member(izzy); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const account_object alice = create_account("alice", izzy, izzy, 50/*0.5%*/); + const account_object bob = create_account("bob", izzy, izzy, 50/*0.5%*/); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzy.get_id(), core_asset(1000) ); + + generate_blocks_past_hf1268(); + // update_asset: set referrer percent + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent); + + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const int64_t izzy_reward = get_market_fee_reward( izzy, jillcoin ); + const int64_t izzy_balance = get_balance( izzy, jillcoin ); + + BOOST_CHECK_GT(izzy_reward, 0); + + auto claim_reward = [&]( account_object referrer, asset amount_to_claim, fc::ecc::private_key private_key ) + { + vesting_balance_withdraw_operation op; + op.vesting_balance = vesting_balance_id_type(0); + op.owner = referrer.get_id(); + op.amount = amount_to_claim; + + signed_transaction tx; + tx.operations.push_back( op ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, private_key ); + PUSH_TX( db, tx ); + }; + + const int64_t amount_to_claim = 3; + claim_reward( izzy, jillcoin.amount(amount_to_claim), izzy_private_key ); + + BOOST_CHECK_EQUAL(get_balance( izzy, jillcoin ), izzy_balance + amount_to_claim); + BOOST_CHECK_EQUAL(get_market_fee_reward( izzy, jillcoin ), izzy_reward - amount_to_claim); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_actors) +{ + try + { + ACTORS((jill)(izzyregistrar)(izzyreferrer)); + + upgrade_to_lifetime_member(izzyregistrar); + upgrade_to_lifetime_member(izzyreferrer); + + price price(asset(1, asset_id_type(1)), asset(1)); + uint16_t market_fee_percent = 20 * GRAPHENE_1_PERCENT; + auto obj = jill_id(db); + const asset_object jillcoin = create_user_issued_asset( "JCOIN", jill, charge_market_fee, price, 2, market_fee_percent ); + + const account_object alice = create_account("alice", izzyregistrar, izzyreferrer, 50/*0.5%*/); + const account_object bob = create_account("bob", izzyregistrar, izzyreferrer, 50/*0.5%*/); + + // prepare users' balance + issue_uia( alice, jillcoin.amount( 20000000 ) ); + + transfer( committee_account, alice.get_id(), core_asset(1000) ); + transfer( committee_account, bob.get_id(), core_asset(1000) ); + transfer( committee_account, izzyregistrar.get_id(), core_asset(1000) ); + transfer( committee_account, izzyreferrer.get_id(), core_asset(1000) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_is_empty_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + flat_set whitelist; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyregistrar_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + const auto izzyregistrar_reward = get_market_fee_reward( izzyregistrar, jillcoin ); + const auto izzyreferrer_reward = get_market_fee_reward( izzyreferrer, jillcoin ); + BOOST_CHECK_GT(izzyregistrar_reward , 0); + BOOST_CHECK_GT(izzyreferrer_reward , 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_contains_referrer_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + flat_set whitelist = {jill_id, izzyreferrer_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(alice); + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(white_list_doesnt_contain_registrar_test) +{ + try + { + INVOKE(create_actors); + + generate_blocks_past_hf1268(); + GET_ACTOR(jill); + + constexpr auto jillcoin_reward_percent = 2*GRAPHENE_1_PERCENT; + const asset_object &jillcoin = get_asset("JCOIN"); + + GET_ACTOR(alice); + flat_set whitelist = {jill_id, alice_id}; + + update_asset(jill_id, jill_private_key, jillcoin.get_id(), jillcoin_reward_percent, whitelist); + + GET_ACTOR(izzyregistrar); + GET_ACTOR(izzyreferrer); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0 ); + + GET_ACTOR(bob); + // Alice and Bob place orders which match + create_sell_order( alice, jillcoin.amount(200000), core_asset(1) ); + create_sell_order( bob, core_asset(1), jillcoin.amount(100000) ); + + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyregistrar, jillcoin ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( izzyreferrer, jillcoin ), 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(create_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + price core_exchange_rate(asset(1, asset_id_type(1)), asset(1)); + + asset_create_operation create_op; + create_op.issuer = issuer.id; + create_op.fee = asset(); + create_op.symbol = "ASSET"; + create_op.common_options.max_supply = 0; + create_op.precision = 2; + create_op.common_options.core_exchange_rate = core_exchange_rate; + create_op.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + create_op.common_options.flags = charge_market_fee; + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + create_op.common_options.extensions = std::move(options);; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(update_asset_via_proposal_test) +{ + try + { + ACTOR(issuer); + asset_object usd_asset = create_user_issued_asset("USD", issuer, charge_market_fee); + + additional_asset_options_t options; + options.value.reward_percent = 100; + options.value.whitelist_market_fee_sharing = flat_set{issuer_id}; + + asset_update_operation update_op; + update_op.issuer = issuer_id; + update_op.asset_to_update = usd_asset.get_id(); + asset_options new_options; + update_op.new_options = usd_asset.options; + update_op.new_options.extensions = std::move(options); + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = issuer_id; + prop.proposed_ops.emplace_back( update_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, issuer_private_key ); + PUSH_TX( db, tx ); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(issue_asset){ + try + { + ACTORS((alice)(bob)(izzy)(jill)); + // Izzy issues asset to Alice (Izzycoin market percent - 10%) + // Jill issues asset to Bob (Jillcoin market percent - 20%) + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + asset_object izzycoin = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee, price, 2, izzycoin_market_percent ); + + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + asset_object jillcoin = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee, price, 2, jillcoin_market_percent ); + + // Alice and Bob create some coins + issue_uia( alice, izzycoin.amount( 100000 ) ); + issue_uia( bob, jillcoin.amount( 100000 ) ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_before_hf_test) +{ + try + { + INVOKE(issue_asset); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_hf1268(); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(accumulated_fees_with_additional_options_after_hf_test) +{ + try + { + INVOKE(issue_asset); + + generate_blocks_past_hf1268(); + + GET_ACTOR(jill); + GET_ACTOR(izzy); + + const asset_object &jillcoin = get_asset("JILLCOIN"); + const asset_object &izzycoin = get_asset("IZZYCOIN"); + + uint16_t reward_percent = 0; + update_asset(jill_id, jill_private_key, jillcoin.get_id(), reward_percent); + update_asset(izzy_id, izzy_private_key, izzycoin.get_id(), reward_percent); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // Alice and Bob place orders which match + create_sell_order( alice_id, izzycoin.amount(100), jillcoin.amount(300) ); // Alice is willing to sell her Izzy's for 3 Jill + create_sell_order( bob_id, jillcoin.amount(700), izzycoin.amount(200) ); // Bob is buying up to 200 Izzy's for up to 3.5 Jill + + // 100 Izzys and 300 Jills are matched, so the fees should be + // 10 Izzy (10%) and 60 Jill (20%). + BOOST_CHECK( izzycoin.dynamic_asset_data_id(db).accumulated_fees == izzycoin.amount(10).amount ); + BOOST_CHECK( jillcoin.dynamic_asset_data_id(db).accumulated_fees == jillcoin.amount(60).amount ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_before_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + sign(trx, alice_private_key); + + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_after_hf1268_test ) +{ try { + + ACTOR(alice); + fund(alice); + + generate_blocks_past_hf1268(); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation op; + op.fee = core.amount( 0 ); + op.creator = alice_id; + op.owner = alice_id; + op.amount = core.amount( 100 ); + op.policy = instant_vesting_policy_initializer{}; + + trx.operations.push_back(op); + set_expiration( db, trx ); + + processed_transaction ptx = PUSH_TX( db, trx, ~0 ); + const vesting_balance_id_type& vbid = ptx.operation_results.back().get(); + + auto withdraw = [&](const asset& amount) { + vesting_balance_withdraw_operation withdraw_op; + withdraw_op.vesting_balance = vbid; + withdraw_op.owner = alice_id; + withdraw_op.amount = amount; + + signed_transaction withdraw_tx; + withdraw_tx.operations.push_back( withdraw_op ); + set_expiration( db, withdraw_tx ); + sign(withdraw_tx, alice_private_key); + PUSH_TX( db, withdraw_tx ); + }; + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw(op.amount.amount + 1), fc::exception); + //to withdraw all that is on the balance + withdraw(op.amount); + // try to withdraw more then it is on the balance + GRAPHENE_REQUIRE_THROW(withdraw( core.amount(1) ), fc::exception); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( create_vesting_balance_with_instant_vesting_policy_via_proposal_test ) +{ try { + + ACTOR(actor); + fund(actor); + + const asset_object& core = asset_id_type()(db); + + vesting_balance_create_operation create_op; + create_op.fee = core.amount( 0 ); + create_op.creator = actor_id; + create_op.owner = actor_id; + create_op.amount = core.amount( 100 ); + create_op.policy = instant_vesting_policy_initializer{}; + + const auto& curfees = *db.get_global_properties().parameters.current_fees; + const auto& proposal_create_fees = curfees.get(); + proposal_create_operation prop; + prop.fee_paying_account = actor_id; + prop.proposed_ops.emplace_back( create_op ); + prop.expiration_time = db.head_block_time() + fc::days(1); + prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); + + { + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + GRAPHENE_CHECK_THROW(PUSH_TX( db, tx ), fc::exception); + } + + generate_blocks_past_hf1268(); + + { + prop.expiration_time = db.head_block_time() + fc::days(1); + signed_transaction tx; + tx.operations.push_back( prop ); + db.current_fee_schedule().set_fee( tx.operations.back() ); + set_expiration( db, tx ); + sign( tx, actor_private_key ); + PUSH_TX( db, tx ); + } +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(white_list_asset_rewards_test) +{ + try + { + ACTORS((aliceregistrar)(bobregistrar)(alicereferrer)(bobreferrer)(izzy)(jill)); + + // Izzy issues white_list asset to Alice + // Jill issues white_list asset to Bob + // Bobreferrer added to blacklist for izzycoin asset + // Aliceregistrar added to blacklist for jillcoin asset + // Alice and Bob trade in the market and pay fees + // Check registrar/referrer rewards + upgrade_to_lifetime_member(aliceregistrar); + upgrade_to_lifetime_member(alicereferrer); + upgrade_to_lifetime_member(bobregistrar); + upgrade_to_lifetime_member(bobreferrer); + upgrade_to_lifetime_member(izzy); + upgrade_to_lifetime_member(jill); + + const account_object alice = create_account("alice", aliceregistrar, alicereferrer, 20*GRAPHENE_1_PERCENT); + const account_object bob = create_account("bob", bobregistrar, bobreferrer, 20*GRAPHENE_1_PERCENT); + + fund( alice, core_asset(1000000) ); + fund( bob, core_asset(1000000) ); + fund( izzy, core_asset(1000000) ); + fund( jill, core_asset(1000000) ); + + price price(asset(1, asset_id_type(1)), asset(1)); + constexpr auto izzycoin_market_percent = 10*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_market_percent = 20*GRAPHENE_1_PERCENT; + const asset_id_type izzycoin_id = create_user_issued_asset( "IZZYCOIN", izzy, charge_market_fee|white_list, price, 0, izzycoin_market_percent ).id; + const asset_id_type jillcoin_id = create_user_issued_asset( "JILLCOIN", jill, charge_market_fee|white_list, price, 0, jillcoin_market_percent ).id; + + // Alice and Bob create some coins + issue_uia( alice, izzycoin_id(db).amount( 200000 ) ); + issue_uia( bob, jillcoin_id(db).amount( 200000 ) ); + + generate_blocks_past_hf1268(); + + constexpr auto izzycoin_reward_percent = 50*GRAPHENE_1_PERCENT; + constexpr auto jillcoin_reward_percent = 50*GRAPHENE_1_PERCENT; + + update_asset(izzy_id, izzy_private_key, izzycoin_id, izzycoin_reward_percent); + update_asset(jill_id, jill_private_key, jillcoin_id, jillcoin_reward_percent); + + BOOST_TEST_MESSAGE( "Attempting to blacklist bobreferrer for izzycoin asset" ); + asset_update_blacklist_authority(izzy_id, izzycoin_id, izzy_id, izzy_private_key); + add_account_to_blacklist(izzy_id, bobreferrer_id, izzy_private_key); + BOOST_CHECK( !(is_authorized_asset( db, bobreferrer_id(db), izzycoin_id(db) )) ); + + BOOST_TEST_MESSAGE( "Attempting to blacklist aliceregistrar for jillcoin asset" ); + asset_update_blacklist_authority(jill_id, jillcoin_id, jill_id, jill_private_key); + add_account_to_blacklist(jill_id, aliceregistrar_id, jill_private_key); + BOOST_CHECK( !(is_authorized_asset( db, aliceregistrar_id(db), jillcoin_id(db) )) ); + + // Alice and Bob place orders which match + create_sell_order( alice.id, izzycoin_id(db).amount(1000), jillcoin_id(db).amount(1500) ); // Alice is willing to sell her 1000 Izzy's for 1.5 Jill + create_sell_order( bob.id, jillcoin_id(db).amount(1500), izzycoin_id(db).amount(1000) ); // Bob is buying up to 1500 Izzy's for up to 0.6 Jill + + // 1000 Izzys and 1500 Jills are matched, so the fees should be + // 100 Izzy (10%) and 300 Jill (20%). + + // Only Bob's registrar should get rewards + share_type bob_registrar_reward = get_market_fee_reward( bob.registrar, izzycoin_id ); + BOOST_CHECK_GT( bob_registrar_reward, 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( bob.referrer, izzycoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.registrar, jillcoin_id ), 0 ); + BOOST_CHECK_EQUAL( get_market_fee_reward( alice.referrer, jillcoin_id ), 0 ); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( create_vesting_balance_object_test ) +{ + /** + * Test checks that an account could have duplicates VBO (with the same asset_type) + * for any type of vesting_balance_type + * except vesting_balance_type::market_fee_sharing + */ + try { + + ACTOR(actor); + + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + create_vesting_balance_object(actor_id, vesting_balance_type::unspecified); + + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + create_vesting_balance_object(actor_id, vesting_balance_type::cashback); + + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + create_vesting_balance_object(actor_id, vesting_balance_type::witness); + + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + create_vesting_balance_object(actor_id, vesting_balance_type::worker); + + create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing); + GRAPHENE_CHECK_THROW(create_vesting_balance_object(actor_id, vesting_balance_type::market_fee_sharing), fc::exception); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/uia_tests.cpp b/tests/tests/uia_tests.cpp index 56b2116a44..0efc4c5ef9 100644 --- a/tests/tests/uia_tests.cpp +++ b/tests/tests/uia_tests.cpp @@ -58,6 +58,7 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) creator.common_options.flags = charge_market_fee|white_list|override_authority|disable_confidential; creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); creator.common_options.whitelist_authorities = creator.common_options.blacklist_authorities = {account_id_type()}; + trx.operations.push_back(std::move(creator)); PUSH_TX( db, trx, ~0 ); @@ -73,6 +74,7 @@ BOOST_AUTO_TEST_CASE( create_advanced_uia ) BOOST_CHECK(test_asset_dynamic_data.current_supply == 0); BOOST_CHECK(test_asset_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_asset_dynamic_data.fee_pool == 0); + } catch(fc::exception& e) { edump((e.to_detail_string())); throw;