diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index c97db8273b..c39c7b072e 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -1272,14 +1273,13 @@ vector database_api_impl::get_call_orders(const std::string& { FC_ASSERT( limit <= 300 ); - const asset_id_type asset_a_id = get_asset_from_string(a)->id; - const auto& call_index = _db.get_index_type().indices().get(); - const asset_object& mia = _db.get(asset_a_id); - price index_price = price::min(mia.bitasset_data(_db).options.short_backing_asset, mia.get_id()); + const asset_object* mia = get_asset_from_string(a); + const auto& call_index = _db.get_index_type().indices().get(); + price index_price = price::min( mia->bitasset_data(_db).options.short_backing_asset, mia->get_id() ); vector< call_order_object> result; - auto itr_min = call_index.lower_bound(index_price.min()); - auto itr_max = call_index.lower_bound(index_price.max()); + auto itr_min = call_index.lower_bound(index_price); + auto itr_max = call_index.upper_bound(index_price.max()); while( itr_min != itr_max && result.size() < limit ) { result.emplace_back(*itr_min); @@ -2039,10 +2039,12 @@ set database_api::get_required_signatures( const signed_transac set database_api_impl::get_required_signatures( const signed_transaction& trx, const flat_set& available_keys )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); auto result = trx.get_required_signatures( _db.get_chain_id(), available_keys, [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); return result; } @@ -2058,6 +2060,7 @@ set
database_api::get_potential_address_signatures( const signed_transa set database_api_impl::get_potential_signatures( const signed_transaction& trx )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); set result; trx.get_required_signatures( _db.get_chain_id(), @@ -2076,6 +2079,7 @@ set database_api_impl::get_potential_signatures( const signed_t result.insert(k); return &auth; }, + allow_non_immediate_owner, _db.get_global_properties().parameters.max_authority_depth ); @@ -2123,10 +2127,12 @@ bool database_api::verify_authority( const signed_transaction& trx )const bool database_api_impl::verify_authority( const signed_transaction& trx )const { + bool allow_non_immediate_owner = ( _db.head_block_time() >= HARDFORK_CORE_584_TIME ); trx.verify_authority( _db.get_chain_id(), [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, - _db.get_global_properties().parameters.max_authority_depth ); + allow_non_immediate_owner, + _db.get_global_properties().parameters.max_authority_depth ); return true; } @@ -2148,7 +2154,8 @@ bool database_api_impl::verify_account_authority( const string& account_name_or_ { graphene::chain::verify_authority(ops, keys, [this]( account_id_type id ){ return &id(_db).active; }, - [this]( account_id_type id ){ return &id(_db).owner; } ); + [this]( account_id_type id ){ return &id(_db).owner; }, + true ); } catch (fc::exception& ex) { diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 3aa61e1cdf..1bcc0217c8 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 { @@ -48,6 +62,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); @@ -282,6 +298,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 @@ -643,7 +661,7 @@ static bool update_bitasset_object_options( const asset_update_bitasset_operation& op, database& db, asset_bitasset_data_object& bdo, const asset_object& asset_to_update ) { - const fc::time_point_sec& next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; + const fc::time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; bool after_hf_core_868_890 = ( next_maint_time > HARDFORK_CORE_868_890_TIME ); // If the minimum number of feeds to calculate a median has changed, we need to recalculate the median @@ -694,7 +712,7 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { const auto old_feed = bdo.current_feed; - bdo.update_median_feeds( db.head_block_time() ); + bdo.update_median_feeds( db.head_block_time(), next_maint_time ); // TODO review and refactor / cleanup after hard fork: // 1. if hf_core_868_890 and core-935 occurred at same time @@ -771,8 +789,9 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { try { database& d = db(); const auto head_time = d.head_block_time(); + const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; const asset_bitasset_data_object& bitasset_to_update = asset_to_update->bitasset_data(d); - d.modify( bitasset_to_update, [&o,head_time](asset_bitasset_data_object& a) { + d.modify( bitasset_to_update, [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { //This is tricky because I have a set of publishers coming in, but a map of publisher to feed is stored. //I need to update the map such that the keys match the new publishers, but not munge the old price feeds from //publishers who are being kept. @@ -796,7 +815,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f { a.feeds[acc]; } - a.update_median_feeds( head_time ); + a.update_median_feeds( head_time, next_maint_time ); }); // Process margin calls, allow black swan, not for a new limit order d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); @@ -974,27 +993,48 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope { try { database& d = db(); + const auto head_time = d.head_block_time(); + const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; const asset_object& base = *asset_ptr; const asset_bitasset_data_object& bad = *bitasset_ptr; auto old_feed = bad.current_feed; // Store medians for this asset - d.modify(bad , [&o,&d](asset_bitasset_data_object& a) { - a.feeds[o.publisher] = make_pair(d.head_block_time(), o.feed); - a.update_median_feeds(d.head_block_time()); + d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { + a.feeds[o.publisher] = make_pair( head_time, o.feed ); + a.update_median_feeds( head_time, next_maint_time ); }); if( !(old_feed == bad.current_feed) ) { - if( bad.has_settlement() ) // implies head_block_time > HARDFORK_CORE_216_TIME + // Check whether need to revive the asset and proceed if need + if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME + && !bad.current_feed.settlement_price.is_null() ) // has a valid feed { + bool should_revive = false; const auto& mia_dyn = base.dynamic_asset_data_id(d); - if( !bad.current_feed.settlement_price.is_null() - && ( mia_dyn.current_supply == 0 - || ~price::call_price(asset(mia_dyn.current_supply, o.asset_id), - asset(bad.settlement_fund, bad.options.short_backing_asset), - bad.current_feed.maintenance_collateral_ratio ) < bad.current_feed.settlement_price ) ) + if( mia_dyn.current_supply == 0 ) // if current supply is zero, revive the asset + should_revive = true; + else // if current supply is not zero, when collateral ratio of settlement fund is greater than MCR, revive the asset + { + if( next_maint_time <= HARDFORK_CORE_1270_TIME ) + { + // before core-1270 hard fork, calculate call_price and compare to median feed + if( ~price::call_price( asset(mia_dyn.current_supply, o.asset_id), + asset(bad.settlement_fund, bad.options.short_backing_asset), + bad.current_feed.maintenance_collateral_ratio ) < bad.current_feed.settlement_price ) + should_revive = true; + } + else + { + // after core-1270 hard fork, calculate collateralization and compare to maintenance_collateralization + if( price( asset( bad.settlement_fund, bad.options.short_backing_asset ), + asset( mia_dyn.current_supply, o.asset_id ) ) > bad.current_maintenance_collateralization ) + should_revive = true; + } + } + if( should_revive ) d.revive_bitasset(base); } // Process margin calls, allow black swan, not for a new limit order diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 47fd3c146b..c6b6ca0d82 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include @@ -43,16 +44,10 @@ share_type asset_bitasset_data_object::max_force_settlement_volume(share_type cu return volume.to_uint64(); } -/****** - * @brief calculate the median feed - * - * This calculates the median feed. It sets the current_feed_publication_time - * and current_feed member variables - * - * @param current_time the time to use in the calculations - */ -void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point_sec current_time) +void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_point_sec current_time, + time_point_sec next_maintenance_time ) { + bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue current_feed_publication_time = current_time; vector> current_feeds; // find feeds that were alive at current_time @@ -73,13 +68,18 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance current_feed_publication_time = current_time; current_feed = price_feed(); + if( after_core_hardfork_1270 ) + current_maintenance_collateralization = price(); return; } if( current_feeds.size() == 1 ) { if( current_feed.core_exchange_rate != current_feeds.front().get().core_exchange_rate ) feed_cer_updated = true; - current_feed = std::move(current_feeds.front()); + current_feed = current_feeds.front(); + // Note: perhaps can defer updating current_maintenance_collateralization for better performance + if( after_core_hardfork_1270 ) + current_maintenance_collateralization = current_feed.maintenance_collateralization(); return; } @@ -100,6 +100,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds(time_point if( current_feed.core_exchange_rate != median_feed.core_exchange_rate ) feed_cer_updated = true; current_feed = median_feed; + // Note: perhaps can defer updating current_maintenance_collateralization for better performance + if( after_core_hardfork_1270 ) + current_maintenance_collateralization = current_feed.maintenance_collateralization(); } @@ -157,7 +160,7 @@ asset asset_object::amount_from_string(string amount_string) const satoshis *= -1; return amount(satoshis); - } FC_CAPTURE_AND_RETHROW( (amount_string) ) } +} FC_CAPTURE_AND_RETHROW( (amount_string) ) } string asset_object::amount_to_string(share_type amount) const { 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_block.cpp b/libraries/chain/db_block.cpp index 163e54ba84..2f60296950 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -630,9 +630,14 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx if( !(skip & skip_transaction_signatures) ) { + bool allow_non_immediate_owner = ( head_block_time() >= HARDFORK_CORE_584_TIME ); auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; - trx.verify_authority( chain_id, get_active, get_owner, get_global_properties().parameters.max_authority_depth ); + trx.verify_authority( chain_id, + get_active, + get_owner, + allow_non_immediate_owner, + get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is @@ -652,6 +657,9 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx FC_ASSERT( trx.expiration <= now + chain_parameters.maximum_time_until_expiration, "", ("trx.expiration",trx.expiration)("now",now)("max_til_exp",chain_parameters.maximum_time_until_expiration)); FC_ASSERT( now <= trx.expiration, "", ("now",now)("trx.exp",trx.expiration) ); + FC_ASSERT( head_block_time() <= HARDFORK_CORE_1573_TIME + || trx.get_packed_size() <= chain_parameters.maximum_transaction_size, + "Transaction exceeds maximum transaction size." ); } //Insert transaction into unique transactions database. @@ -744,6 +752,7 @@ void database::_precompute_parallel( const Trx* trx, const size_t count, const u for( size_t i = 0; i < count; ++i, ++trx ) { trx->validate(); // TODO - parallelize wrt confidential operations + trx->get_packed_size(); if( !(skip&skip_transaction_dupe_check) ) trx->id(); if( !(skip&skip_transaction_signatures) ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 6d1d59a705..e077b4bf22 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -847,7 +847,9 @@ void database::process_bids( const asset_bitasset_data_object& bad ) _cancel_bids_and_revive_mpa( to_revive, bad ); } -void update_and_match_call_orders( database& db ) +/// Reset call_price of all call orders according to their remaining collateral and debt. +/// Do not update orders of prediction markets because we're sure they're up to date. +void update_call_orders_hf_343( database& db ) { // Update call_price wlog( "Updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); @@ -868,7 +870,30 @@ void update_and_match_call_orders( database& db ) abd->current_feed.maintenance_collateral_ratio ); }); } + wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); +} + +/// Reset call_price of all call orders to (1,1) since it won't be used in the future. +/// Update PMs as well. +void update_call_orders_hf_1270( database& db ) +{ + // Update call_price + wlog( "Updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) ); + for( const auto& call_obj : db.get_index_type().indices().get() ) + { + db.modify( call_obj, []( call_order_object& call ) { + call.call_price.base.amount = 1; + call.call_price.quote.amount = 1; + }); + } + wlog( "Done updating all call orders for hardfork core-1270 at block ${n}", ("n",db.head_block_num()) ); +} + +/// Match call orders for all bitAssets, including PMs. +void match_call_orders( database& db ) +{ // Match call orders + wlog( "Matching call orders at block ${n}", ("n",db.head_block_num()) ); const auto& asset_idx = db.get_index_type().indices().get(); auto itr = asset_idx.lower_bound( true /** market issued */ ); while( itr != asset_idx.end() ) @@ -878,7 +903,7 @@ void update_and_match_call_orders( database& db ) // be here, next_maintenance_time should have been updated already db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker } - wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); + wlog( "Done matching call orders at block ${n}", ("n",db.head_block_num()) ); } void database::process_bitassets() @@ -919,6 +944,49 @@ void database::process_bitassets() } } +/**** + * @brief a one-time data process to correct max_supply + */ +void process_hf_1465( database& db ) +{ + const auto head_num = db.head_block_num(); + wlog( "Processing hard fork core-1465 at block ${n}", ("n",head_num) ); + // for each market issued asset + const auto& asset_idx = db.get_index_type().indices().get(); + for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr ) + { + const auto& current_asset = *asset_itr; + graphene::chain::share_type current_supply = current_asset.dynamic_data(db).current_supply; + graphene::chain::share_type max_supply = current_asset.options.max_supply; + if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY) + { + wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.", + ("asset", current_asset.symbol) + ("current_supply", current_supply.value) + ("old", max_supply)); + db.modify( current_asset, [current_supply](asset_object& obj) { + obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY)); + }); + } + } +} + +void update_median_feeds(database& db) +{ + time_point_sec head_time = db.head_block_time(); + time_point_sec next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; + + const auto update_bitasset = [head_time, next_maint_time]( asset_bitasset_data_object &o ) + { + o.update_median_feeds( head_time, next_maint_time ); + }; + + for( const auto& d : db.get_index_type().indices() ) + { + db.modify( d, update_bitasset ); + } +} + /****** * @brief one-time data process for hard fork core-868-890 * @@ -940,6 +1008,7 @@ void database::process_bitassets() // * NOTE: the removal can't be applied to testnet void process_hf_868_890( database& db, bool skip_check_call_orders ) { + const auto next_maint_time = db.get_dynamic_global_properties().next_maintenance_time; const auto head_time = db.head_block_time(); const auto head_num = db.head_block_num(); wlog( "Processing hard fork core-868-890 at block ${n}", ("n",head_num) ); @@ -999,8 +1068,8 @@ void process_hf_868_890( database& db, bool skip_check_call_orders ) } // always update the median feed due to https://github.com/bitshares/bitshares-core/issues/890 - db.modify( bitasset_data, [&head_time]( asset_bitasset_data_object &obj ) { - obj.update_median_feeds( head_time ); + db.modify( bitasset_data, [head_time,next_maint_time]( asset_bitasset_data_object &obj ) { + obj.update_median_feeds( head_time, next_maint_time ); }); bool median_changed = ( old_feed.settlement_price != bitasset_data.current_feed.settlement_price ); @@ -1216,28 +1285,48 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) ) deprecate_annual_members(*this); - // To reset call_price of all call orders, then match by new rule - bool to_update_and_match_call_orders = false; + // To reset call_price of all call orders, then match by new rule, for hard fork core-343 + bool to_update_and_match_call_orders_for_hf_343 = false; if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) ) - to_update_and_match_call_orders = true; + to_update_and_match_call_orders_for_hf_343 = true; // Process inconsistent price feeds if( (dgpo.next_maintenance_time <= HARDFORK_CORE_868_890_TIME) && (next_maintenance_time > HARDFORK_CORE_868_890_TIME) ) - process_hf_868_890( *this, to_update_and_match_call_orders ); + process_hf_868_890( *this, to_update_and_match_call_orders_for_hf_343 ); // Explicitly call check_call_orders of all markets if( (dgpo.next_maintenance_time <= HARDFORK_CORE_935_TIME) && (next_maintenance_time > HARDFORK_CORE_935_TIME) - && !to_update_and_match_call_orders ) + && !to_update_and_match_call_orders_for_hf_343 ) process_hf_935( *this ); + // To reset call_price of all call orders, then match by new rule, for hard fork core-1270 + bool to_update_and_match_call_orders_for_hf_1270 = false; + if( (dgpo.next_maintenance_time <= HARDFORK_CORE_1270_TIME) && (next_maintenance_time > HARDFORK_CORE_1270_TIME) ) + to_update_and_match_call_orders_for_hf_1270 = true; + + // make sure current_supply is less than or equal to max_supply + if ( dgpo.next_maintenance_time <= HARDFORK_CORE_1465_TIME && next_maintenance_time > HARDFORK_CORE_1465_TIME ) + process_hf_1465(*this); + modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; d.accounts_registered_this_interval = 0; }); - // We need to do it after updated next_maintenance_time, to apply new rules here - if( to_update_and_match_call_orders ) - update_and_match_call_orders(*this); + // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-343 + if( to_update_and_match_call_orders_for_hf_343 ) + { + update_call_orders_hf_343(*this); + match_call_orders(*this); + } + + // We need to do it after updated next_maintenance_time, to apply new rules here, for hard fork core-1270. + if( to_update_and_match_call_orders_for_hf_1270 ) + { + update_call_orders_hf_1270(*this); + update_median_feeds(*this); + match_call_orders(*this); + } process_bitassets(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 6b8f67ea1c..3d42f9abc9 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 @@ -51,8 +62,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett const asset_dynamic_data_object& mia_dyn = mia.dynamic_asset_data_id(*this); auto original_mia_supply = mia_dyn.current_supply; - const call_order_index& call_index = get_index_type(); - const auto& call_price_index = call_index.indices().get(); + const auto& call_price_index = get_index_type().indices().get(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding @@ -83,9 +93,8 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett FC_ASSERT( fill_call_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker } - modify( bitasset, [&]( asset_bitasset_data_object& obj ){ - assert( collateral_gathered.asset_id == settlement_price.quote.asset_id ); - obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; //settlement_price; + modify( bitasset, [&mia,original_mia_supply,&collateral_gathered]( asset_bitasset_data_object& obj ){ + obj.settlement_price = mia.amount(original_mia_supply) / collateral_gathered; obj.settlement_fund = collateral_gathered.amount; }); @@ -93,7 +102,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett /// that is a lie, the supply didn't change. We need to capture the current supply before /// filling all call orders and then restore it afterward. Then in the force settlement /// evaluator reduce the supply - modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + modify( mia_dyn, [original_mia_supply]( asset_dynamic_data_object& obj ){ obj.current_supply = original_mia_supply; }); @@ -170,9 +179,15 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co call.borrower = bid.bidder; call.collateral = bid.inv_swan_price.base.amount + collateral_from_fund; call.debt = debt_covered; - call.call_price = price::call_price(asset(debt_covered, bid.inv_swan_price.quote.asset_id), - asset(call.collateral, bid.inv_swan_price.base.asset_id), - current_feed.maintenance_collateral_ratio); + // don't calculate call_price after core-1270 hard fork + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME ) + // bid.inv_swan_price is in collateral / debt + call.call_price = price( asset( 1, bid.inv_swan_price.base.asset_id ), + asset( 1, bid.inv_swan_price.quote.asset_id ) ); + else + call.call_price = price::call_price( asset(debt_covered, bid.inv_swan_price.quote.asset_id), + asset(call.collateral, bid.inv_swan_price.base.asset_id), + current_feed.maintenance_collateral_ratio ); }); // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders @@ -427,6 +442,9 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // 5. the call order's collateral ratio is below or equals to MCR // 6. the limit order provided a good price + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + bool to_check_call_orders = false; const asset_object& sell_asset = sell_asset_id( *this ); const asset_bitasset_data_object* sell_abd = nullptr; @@ -439,7 +457,10 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo && !sell_abd->has_settlement() && !sell_abd->current_feed.settlement_price.is_null() ) { - call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); + if( before_core_hardfork_1270 ) + call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270(); + else + call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); if( ~new_order_object.sell_price <= call_match_price ) // new limit order price is good enough to match a call to_check_call_orders = true; } @@ -457,7 +478,33 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); } - if( !finished ) + if( !finished && !before_core_hardfork_1270 ) // TODO refactor or cleanup duplicate code after core-1270 hard fork + { + // check if there are margin calls + const auto& call_collateral_idx = get_index_type().indices().get(); + auto call_min = price::min( recv_asset_id, sell_asset_id ); + while( !finished ) + { + // hard fork core-343 and core-625 took place at same time, + // always check call order with least collateral ratio + auto call_itr = call_collateral_idx.lower_bound( call_min ); + if( call_itr == call_collateral_idx.end() + || call_itr->debt_type() != sell_asset_id + // feed protected https://github.com/cryptonomex/graphene/issues/436 + || call_itr->collateralization() > sell_abd->current_maintenance_collateralization ) + break; + // hard fork core-338 and core-625 took place at same time, not checking HARDFORK_CORE_338_TIME here. + int match_result = match( new_order_object, *call_itr, call_match_price, + sell_abd->current_feed.settlement_price, + sell_abd->current_feed.maintenance_collateral_ratio, + sell_abd->current_maintenance_collateralization ); + // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. + // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. + if( match_result == 1 || match_result == 3 ) + finished = true; + } + } + else if( !finished ) // and before core-1270 hard fork { // check if there are margin calls const auto& call_price_idx = get_index_type().indices().get(); @@ -474,7 +521,8 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here. int match_result = match( new_order_object, *call_itr, call_match_price, sell_abd->current_feed.settlement_price, - sell_abd->current_feed.maintenance_collateral_ratio ); + sell_abd->current_feed.maintenance_collateral_ratio, + optional() ); // match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching. // since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2. if( match_result == 1 || match_result == 3 ) @@ -580,7 +628,8 @@ int database::match( const limit_order_object& usd, const limit_order_object& co } int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio ) + const price& feed_price, const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization ) { FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() ); @@ -606,7 +655,10 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // TODO if we're sure `before_core_hardfork_834` is always false, remove the check asset usd_to_buy = ( before_core_hardfork_834 ? ask.get_debt() : - asset( ask.get_max_debt_to_cover( match_price, feed_price, maintenance_collateral_ratio ), + asset( ask.get_max_debt_to_cover( match_price, + feed_price, + maintenance_collateral_ratio, + maintenance_collateralization ), ask.debt_type() ) ); asset call_pays, call_receives, order_pays, order_receives; @@ -775,7 +827,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 ); @@ -837,9 +892,17 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay collateral_freed = o.get_collateral(); o.collateral = 0; } - else if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_343_TIME ) - o.call_price = price::call_price( o.get_debt(), o.get_collateral(), - mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); + else + { + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + // update call_price after core-343 hard fork, + // but don't update call_price after core-1270 hard fork + if( maint_time <= HARDFORK_CORE_1270_TIME && maint_time > HARDFORK_CORE_343_TIME ) + { + o.call_price = price::call_price( o.get_debt(), o.get_collateral(), + mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); + } + } }); // update current supply @@ -934,12 +997,14 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + // looking for limit orders selling the most USD for the least CORE auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); // stop when limit orders are selling too little USD for too much CORE - auto min_price = bitasset.current_feed.max_short_squeeze_price(); + auto min_price = ( before_core_hardfork_1270 ? bitasset.current_feed.max_short_squeeze_price_before_hf_1270() + : bitasset.current_feed.max_short_squeeze_price() ); - assert( max_price.base.asset_id == min_price.base.asset_id ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); @@ -949,11 +1014,26 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + const auto& call_collateral_index = call_index.indices().get(); auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); - auto call_itr = call_price_index.lower_bound( call_min ); - auto call_end = call_price_index.upper_bound( call_max ); + + auto call_price_itr = call_price_index.begin(); + auto call_price_end = call_price_itr; + auto call_collateral_itr = call_collateral_index.begin(); + auto call_collateral_end = call_collateral_itr; + + if( before_core_hardfork_1270 ) + { + call_price_itr = call_price_index.lower_bound( call_min ); + call_price_end = call_price_index.upper_bound( call_max ); + } + else + { + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + call_collateral_end = call_collateral_index.upper_bound( call_max ); + } bool filled_limit = false; bool margin_called = false; @@ -972,15 +1052,18 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) // TODO perhaps improve performance by passing in iterators - && call_itr != call_end - && limit_itr != limit_end ) + && limit_itr != limit_end + && ( ( !before_core_hardfork_1270 && call_collateral_itr != call_collateral_end ) + || ( before_core_hardfork_1270 && call_price_itr != call_price_end ) ) ) { bool filled_call = false; - const call_order_object& call_order = *call_itr; + const call_order_object& call_order = ( before_core_hardfork_1270 ? *call_price_itr : *call_collateral_itr ); // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 - if( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) + if( ( !before_core_hardfork_1270 && bitasset.current_maintenance_collateralization < call_order.collateralization() ) + || ( before_core_hardfork_1270 + && after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) ) return margin_called; const limit_order_object& limit_order = *limit_itr; @@ -1004,10 +1087,19 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa return true; } - if( !before_core_hardfork_834 ) + if( !before_core_hardfork_1270 ) + { + usd_to_buy.amount = call_order.get_max_debt_to_cover( match_price, + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio, + bitasset.current_maintenance_collateralization ); + } + else if( !before_core_hardfork_834 ) + { usd_to_buy.amount = call_order.get_max_debt_to_cover( match_price, - bitasset.current_feed.settlement_price, - bitasset.current_feed.maintenance_collateral_ratio ); + bitasset.current_feed.settlement_price, + bitasset.current_feed.maintenance_collateral_ratio ); + } asset usd_for_sale = limit_order.amount_for_sale(); asset call_pays, call_receives, order_pays, order_receives; @@ -1071,11 +1163,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa order_pays = call_receives; if( filled_call && before_core_hardfork_343 ) - ++call_itr; + ++call_price_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_call_order( call_order, call_pays, call_receives, match_price, for_new_limit_order ); - if( !before_core_hardfork_343 ) - call_itr = call_price_index.lower_bound( call_min ); + if( !before_core_hardfork_1270 ) + call_collateral_itr = call_collateral_index.lower_bound( call_min ); + else if( !before_core_hardfork_343 ) + call_price_itr = call_price_index.lower_bound( call_min ); auto next_limit_itr = std::next( limit_itr ); // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker @@ -1109,10 +1203,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 +1215,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 +1230,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/db_update.cpp b/libraries/chain/db_update.cpp index 05aae9d058..29856165d7 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -192,23 +192,40 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s auto settle_price = bitasset.current_feed.settlement_price; if( settle_price.is_null() ) return false; // no feed - const call_order_index& call_index = get_index_type(); - const auto& call_price_index = call_index.indices().get(); + const call_order_object* call_ptr = nullptr; // place holder for the call order with least collateral ratio - auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); - auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); - auto call_itr = call_price_index.lower_bound( call_min ); - auto call_end = call_price_index.upper_bound( call_max ); + asset_id_type debt_asset_id = mia.id; + auto call_min = price::min( bitasset.options.short_backing_asset, debt_asset_id ); - if( call_itr == call_end ) return false; // no call orders + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue - price highest = settle_price; + if( before_core_hardfork_1270 ) // before core-1270 hard fork, check with call_price + { + const auto& call_price_index = get_index_type().indices().get(); + auto call_itr = call_price_index.lower_bound( call_min ); + if( call_itr == call_price_index.end() ) // no call order + return false; + call_ptr = &(*call_itr); + } + else // after core-1270 hard fork, check with collateralization + { + const auto& call_collateral_index = get_index_type().indices().get(); + auto call_itr = call_collateral_index.lower_bound( call_min ); + if( call_itr == call_collateral_index.end() ) // no call order + return false; + call_ptr = &(*call_itr); + } + if( call_ptr->debt_type() != debt_asset_id ) // no call order + return false; - const auto& dyn_prop = get_dynamic_global_properties(); - auto maint_time = dyn_prop.next_maintenance_time; - if( maint_time > HARDFORK_CORE_338_TIME ) + price highest = settle_price; + if( maint_time > HARDFORK_CORE_1270_TIME ) // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here highest = bitasset.current_feed.max_short_squeeze_price(); + else if( maint_time > HARDFORK_CORE_338_TIME ) + // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270(); const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -228,10 +245,10 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s highest = std::max( limit_itr->sell_price, highest ); } - auto least_collateral = call_itr->collateralization(); + auto least_collateral = call_ptr->collateralization(); if( ~least_collateral >= highest ) { - wdump( (*call_itr) ); + wdump( (*call_ptr) ); elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" " Least collateralized call: ${lc} ${~lc}\n" // " Highest Bid: ${hb} ${~hb}\n" @@ -460,6 +477,7 @@ void database::clear_expired_orders() void database::update_expired_feeds() { const auto head_time = head_block_time(); + const auto next_maint_time = get_dynamic_global_properties().next_maintenance_time; bool after_hardfork_615 = ( head_time >= HARDFORK_615_TIME ); const auto& idx = get_index_type().indices().get(); @@ -474,9 +492,9 @@ void database::update_expired_feeds() if( after_hardfork_615 || b.feed_is_expired_before_hardfork_615( head_time ) ) { auto old_median_feed = b.current_feed; - modify( b, [head_time,&update_cer]( asset_bitasset_data_object& abdo ) + modify( b, [head_time,next_maint_time,&update_cer]( asset_bitasset_data_object& abdo ) { - abdo.update_median_feeds( head_time ); + abdo.update_median_feeds( head_time, next_maint_time ); if( abdo.need_to_update_cer() ) { update_cer = true; diff --git a/libraries/chain/hardfork.d/CORE_1268.hf b/libraries/chain/hardfork.d/CORE_1268.hf new file mode 100644 index 0000000000..8d68a63821 --- /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( 1551362520 )) // Thursday, February 28, 2019 2:02:00 PM +#endif diff --git a/libraries/chain/hardfork.d/CORE_1270.hf b/libraries/chain/hardfork.d/CORE_1270.hf new file mode 100644 index 0000000000..a640289197 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1270.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #1270 Call price is inconsistent when MCR changed +#ifndef HARDFORK_CORE_1270_TIME +#define HARDFORK_CORE_1270_TIME (fc::time_point_sec( 1551362520 )) // Thursday, February 28, 2019 2:02:00 PM +#endif diff --git a/libraries/chain/hardfork.d/CORE_1465.hf b/libraries/chain/hardfork.d/CORE_1465.hf new file mode 100644 index 0000000000..ed8dea3c8e --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1465.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #1465 check max_supply before processing call_order_update +#ifndef HARDFORK_CORE_1465_TIME +#define HARDFORK_CORE_1465_TIME (fc::time_point_sec( 1551362520 )) // Thursday, February 28, 2019 2:02:00 PM +#define SOFTFORK_CORE_1465_TIME (fc::time_point_sec( 1545350400 )) // 2018-12-21 00:00:00 +#endif diff --git a/libraries/chain/hardfork.d/CORE_1573.hf b/libraries/chain/hardfork.d/CORE_1573.hf new file mode 100644 index 0000000000..a70342527f --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1573.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #1573 check transaction size +#ifndef HARDFORK_CORE_1573_TIME +#define HARDFORK_CORE_1573_TIME (fc::time_point_sec( 1551362520 )) // Thursday, February 28, 2019 2:02:00 PM +#endif diff --git a/libraries/chain/hardfork.d/CORE_584.hf b/libraries/chain/hardfork.d/CORE_584.hf new file mode 100644 index 0000000000..d2a0cca2f1 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_584.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #584 Owner keys of non-immediately required accounts can not authorize a transaction +// https://github.com/bitshares/bitshares-core/issues/584 +#ifndef HARDFORK_CORE_584_TIME +#define HARDFORK_CORE_584_TIME (fc::time_point_sec( 1551362520 )) // Thursday, February 28, 2019 2:02:00 PM +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 5f73e79ddd..38081dc40f 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -191,6 +191,9 @@ namespace graphene { namespace chain { price_feed current_feed; /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; + /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. + /// This value is derived from @ref current_feed for better performance and should be kept consistent. + price current_maintenance_collateralization; /// True if this asset implements a @ref prediction_market bool is_prediction_market = false; @@ -241,7 +244,19 @@ namespace graphene { namespace chain { { return feed_expiration_time() >= current_time; } bool feed_is_expired(time_point_sec current_time)const { return feed_expiration_time() <= current_time; } - void update_median_feeds(time_point_sec current_time); + + /****** + * @brief calculate the median feed + * + * This calculates the median feed from @ref feeds, feed_lifetime_sec + * in @ref options, and the given parameters. + * It may update the current_feed_publication_time, current_feed and + * current_maintenance_collateralization member variables. + * + * @param current_time the current time to use in the calculations + * @param next_maintenance_time the next chain maintenance time + */ + void update_median_feeds(time_point_sec current_time, time_point_sec next_maintenance_time); }; // key extractor for short backing asset @@ -305,6 +320,7 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db:: (feeds) (current_feed) (current_feed_publication_time) + (current_maintenance_collateralization) (options) (force_settled_volume) (is_prediction_market) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index d86293d690..142a70e1b2 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -121,7 +121,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "BTS2.181221" +#define GRAPHENE_CURRENT_DB_VERSION "BTS2.190225" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 059fec0707..437c50d8dd 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 @@ -301,6 +302,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. * @@ -318,6 +328,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 ); @@ -350,8 +361,10 @@ namespace graphene { namespace chain { * This function takes a new limit order, and runs the markets attempting to match it with existing orders * already on the books. */ + ///@{ bool apply_order_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan = true); bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); + ///@} /** * Matches the two orders, the first parameter is taker, the second is maker. @@ -366,14 +379,17 @@ namespace graphene { namespace chain { ///@{ int match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price, - const price& feed_price, const uint16_t maintenance_collateral_ratio ); + const price& feed_price, const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization ); + ///@} + + /// Matches the two orders, the first parameter is taker, the second is maker. /// @return the amount of asset settled asset match(const call_order_object& call, const force_settlement_object& settle, const price& match_price, asset max_settlement, const price& fill_price); - ///@} /** * @return true if the order was completely filled and thus freed. @@ -393,6 +409,8 @@ 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/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index 96a4ac07ed..af66323578 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -87,6 +87,7 @@ namespace graphene { namespace chain { const account_object* _paying_account = nullptr; const call_order_object* _order = nullptr; const asset_bitasset_data_object* _bitasset_data = nullptr; + const asset_dynamic_data_object* _dynamic_data_obj = nullptr; }; class bid_collateral_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 706d5ed313..168e2b6439 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -136,8 +136,20 @@ class call_order_object : public abstract_object return tmp; } - /// Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio. - share_type get_max_debt_to_cover( price match_price, price feed_price, const uint16_t maintenance_collateral_ratio )const; + /** + * Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio. + * + * @param match_price the matching price if this call order is margin called + * @param feed_price median settlement price of debt asset + * @param maintenance_collateral_ratio median maintenance collateral ratio of debt asset + * @param maintenance_collateralization maintenance collateralization of debt asset, + * should only be valid after core-1270 hard fork + * @return maximum amount of debt that can be called + */ + share_type get_max_debt_to_cover( price match_price, + price feed_price, + const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization = optional() )const; }; /** diff --git a/libraries/chain/include/graphene/chain/protocol/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp index 86d17892ff..b354d5ccac 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -193,15 +193,6 @@ namespace graphene { namespace chain { /** Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM */ uint16_t maximum_short_squeeze_ratio = GRAPHENE_DEFAULT_MAX_SHORT_SQUEEZE_RATIO; - /** - * When updating a call order the following condition must be maintained: - * - * debt * maintenance_price() < collateral - * debt * settlement_price < debt * maintenance - * debt * maintenance_price() < debt * max_short_squeeze_price() - price maintenance_price()const; - */ - /** When selling collateral to pay off debt, the least amount of debt to receive should be * min_usd = max_short_squeeze_price() * collateral * @@ -209,6 +200,13 @@ namespace graphene { namespace chain { * must be confirmed by having the max_short_squeeze_price() move below the black swan price. */ price max_short_squeeze_price()const; + /// Another implementation of max_short_squeeze_price() before the core-1270 hard fork + price max_short_squeeze_price_before_hf_1270()const; + + /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. + /// Calculation: ~settlement_price * maintenance_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM + price maintenance_collateralization()const; + ///@} friend bool operator == ( const price_feed& a, const price_feed& b ) 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/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index 84234afb9e..3d1e81c461 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -111,7 +111,11 @@ namespace graphene { namespace chain { return results; } - void get_required_authorities( flat_set& active, flat_set& owner, vector& other )const; + void get_required_authorities( flat_set& active, + flat_set& owner, + vector& other )const; + + virtual uint64_t get_packed_size()const; protected: // Calculate the digest used for signature validation @@ -146,13 +150,27 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; + /** + * Checks whether signatures in this signed transaction are sufficient to authorize the transaction. + * Throws an exception when failed. + * + * @param chain_id the ID of a block chain + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations in the transaction + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + */ void verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; /** @@ -161,12 +179,12 @@ namespace graphene { namespace chain { * some cases where get_required_signatures() returns a * non-minimal set. */ - set minimize_required_signatures( const chain_id_type& chain_id, const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH ) const; @@ -209,13 +227,32 @@ namespace graphene { namespace chain { virtual const transaction_id_type& id()const override; virtual void validate()const override; virtual const flat_set& get_signature_keys( const chain_id_type& chain_id )const override; + virtual uint64_t get_packed_size()const override; protected: mutable bool _validated = false; + mutable uint64_t _packed_size = 0; }; + /** + * Checks whether given public keys and approvals are sufficient to authorize given operations. + * Throws an exception when failed. + * + * @param ops a vector of operations + * @param sigs a set of public keys + * @param get_active callback function to retrieve active authorities of a given account + * @param get_owner callback function to retrieve owner authorities of a given account + * @param allow_non_immediate_owner whether to allow owner authority of non-immediately + * required accounts to authorize operations + * @param max_recursion maximum level of recursion when verifying, since an account + * can have another account in active authorities and/or owner authorities + * @param allow_committee whether to allow the special "committee account" to authorize the operations + * @param active_approvals accounts that approved the operations with their active authories + * @param owner_approvals accounts that approved the operations with their owner authories + */ void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, bool allow_committe = false, const flat_set& active_aprovals = flat_set(), 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/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 86f29ef434..17d1164362 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -160,8 +160,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope { try { database& d = db(); + auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time; + // TODO: remove this check and the assertion after hf_834 - if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_834_TIME ) + if( next_maintenance_time <= HARDFORK_CORE_834_TIME ) FC_ASSERT( !o.extensions.value.target_collateral_ratio.valid(), "Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." ); @@ -170,6 +172,29 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", ("sym", _debt_asset->symbol) ); + _dynamic_data_obj = &_debt_asset->dynamic_asset_data_id(d); + + /*** + * We have softfork code already in production to prevent exceeding MAX_SUPPLY between 2018-12-21 until HF 1465. + * But we must allow this in replays until 2018-12-21. The HF 1465 code will correct the problem. + * After HF 1465, we MAY be able to remove the cleanup code IF it never executes. We MAY be able to clean + * up the softfork code IF it never executes. We MAY be able to turn the hardfork code into regular code IF + * noone ever attempted this before HF 1465. + */ + if (next_maintenance_time <= SOFTFORK_CORE_1465_TIME) + { + if ( _dynamic_data_obj->current_supply + o.delta_debt.amount > _debt_asset->options.max_supply ) + ilog("Issue 1465... Borrowing and exceeding MAX_SUPPLY. Will be corrected at hardfork time."); + } + else + { + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount <= _debt_asset->options.max_supply, + "Borrowing this quantity would exceed MAX_SUPPLY" ); + } + + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0, + "This transaction would bring current supply below zero."); + _bitasset_data = &_debt_asset->bitasset_data(d); /// if there is a settlement for this asset, then no further margin positions may be taken and @@ -201,9 +226,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope d.adjust_balance( o.funding_account, o.delta_debt ); // Deduct the debt paid from the total supply of the debt asset. - d.modify(_debt_asset->dynamic_asset_data_id(d), [&](asset_dynamic_data_object& dynamic_asset) { + d.modify(*_dynamic_data_obj, [&](asset_dynamic_data_object& dynamic_asset) { dynamic_asset.current_supply += o.delta_debt.amount; - FC_ASSERT(dynamic_asset.current_supply >= 0); }); } @@ -220,6 +244,9 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope } } + const auto next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_1270 = ( next_maint_time <= HARDFORK_CORE_1270_TIME ); // call price caching issue + auto& call_idx = d.get_index_type().indices().get(); auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); const call_order_object* call_obj = nullptr; @@ -233,12 +260,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope FC_ASSERT( o.delta_collateral.amount > 0, "Delta collateral amount of new debt position should be positive" ); FC_ASSERT( o.delta_debt.amount > 0, "Delta debt amount of new debt position should be positive" ); - call_obj = &d.create( [&o,this]( call_order_object& call ){ + call_obj = &d.create( [&o,this,before_core_hardfork_1270]( call_order_object& call ){ call.borrower = o.funding_account; call.collateral = o.delta_collateral.amount; call.debt = o.delta_debt.amount; - call.call_price = price::call_price(o.delta_debt, o.delta_collateral, - _bitasset_data->current_feed.maintenance_collateral_ratio); + if( before_core_hardfork_1270 ) // before core-1270 hard fork, calculate call_price here and cache it + call.call_price = price::call_price( o.delta_debt, o.delta_collateral, + _bitasset_data->current_feed.maintenance_collateral_ratio ); + else // after core-1270 hard fork, set call_price to 1 + call.call_price = price( asset( 1, o.delta_collateral.asset_id ), asset( 1, o.delta_debt.asset_id ) ); call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); call_order_id = call_obj->id; @@ -263,11 +293,14 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope old_collateralization = call_obj->collateralization(); old_debt = call_obj->debt; - d.modify( *call_obj, [&o,new_debt,new_collateral,this]( call_order_object& call ){ + d.modify( *call_obj, [&o,new_debt,new_collateral,this,before_core_hardfork_1270]( call_order_object& call ){ call.collateral = new_collateral; call.debt = new_debt; - call.call_price = price::call_price( call.get_debt(), call.get_collateral(), - _bitasset_data->current_feed.maintenance_collateral_ratio ); + if( before_core_hardfork_1270 ) // don't update call_price after core-1270 hard fork + { + call.call_price = price::call_price( call.get_debt(), call.get_collateral(), + _bitasset_data->current_feed.maintenance_collateral_ratio ); + } call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); } @@ -290,8 +323,7 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope GRAPHENE_ASSERT( !call_obj, call_order_update_unfilled_margin_call, - "Updating call order would trigger a margin call that cannot be fully filled", - ("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price) + "Updating call order would trigger a margin call that cannot be fully filled" ); } else @@ -305,9 +337,11 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // aren't in margin call territory, or it may be because there // were no matching orders. In the latter case, we throw. GRAPHENE_ASSERT( + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here ~call_obj->call_price < _bitasset_data->current_feed.settlement_price, call_order_update_unfilled_margin_call, "Updating call order would trigger a margin call that cannot be fully filled", + // we know core-583 hard fork is before core-1270 hard fork, it's ok to use call_price here ("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price) ); } @@ -319,13 +353,13 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // if collateral ratio is not increased or debt is increased, we throw. // be here, we know no margin call was executed, // so call_obj's collateral ratio should be set only by op - FC_ASSERT( ( old_collateralization.valid() && call_obj->debt <= *old_debt - && call_obj->collateralization() > *old_collateralization ) - || ~call_obj->call_price < _bitasset_data->current_feed.settlement_price, + FC_ASSERT( ( !before_core_hardfork_1270 + && call_obj->collateralization() > _bitasset_data->current_maintenance_collateralization ) + || ( before_core_hardfork_1270 && ~call_obj->call_price < _bitasset_data->current_feed.settlement_price ) + || ( old_collateralization.valid() && call_obj->debt <= *old_debt + && call_obj->collateralization() > *old_collateralization ), "Can only increase collateral ratio without increasing debt if would trigger a margin call that " "cannot be fully filled", - ("new_call_price", ~call_obj->call_price ) - ("settlement_price", _bitasset_data->current_feed.settlement_price) ("old_debt", old_debt) ("new_debt", call_obj->debt) ("old_collateralization", old_collateralization) diff --git a/libraries/chain/market_object.cpp b/libraries/chain/market_object.cpp index 993df7924f..0c2e464906 100644 --- a/libraries/chain/market_object.cpp +++ b/libraries/chain/market_object.cpp @@ -25,6 +25,8 @@ #include +#include + using namespace graphene::chain; /* @@ -56,7 +58,8 @@ max_debt_to_cover = max_amount_to_sell * match_price */ share_type call_order_object::get_max_debt_to_cover( price match_price, price feed_price, - const uint16_t maintenance_collateral_ratio )const + const uint16_t maintenance_collateral_ratio, + const optional& maintenance_collateralization )const { try { // be defensive here, make sure feed_price is in collateral / debt format if( feed_price.base.asset_id != call_price.base.asset_id ) @@ -65,7 +68,23 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, FC_ASSERT( feed_price.base.asset_id == call_price.base.asset_id && feed_price.quote.asset_id == call_price.quote.asset_id ); - if( call_price > feed_price ) // feed protected. be defensive here, although this should be guaranteed by caller + bool after_core_hardfork_1270 = maintenance_collateralization.valid(); + + // be defensive here, make sure maintenance_collateralization is in collateral / debt format + if( after_core_hardfork_1270 ) + { + FC_ASSERT( maintenance_collateralization->base.asset_id == call_price.base.asset_id + && maintenance_collateralization->quote.asset_id == call_price.quote.asset_id ); + } + + // According to the feed protection rule (https://github.com/cryptonomex/graphene/issues/436), + // a call order should only be called when its collateral ratio is not higher than required maintenance collateral ratio. + // Although this should be guaranteed by the caller of this function, we still check here to be defensive. + // Theoretically this check can be skipped for better performance. + // + // Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization(). + if( ( !after_core_hardfork_1270 && call_price > feed_price ) + || ( after_core_hardfork_1270 && collateralization() > *maintenance_collateralization ) ) return 0; if( !target_collateral_ratio.valid() ) // target cr is not set @@ -73,6 +92,10 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio ); // use mcr if target cr is too small + price target_collateralization = ( after_core_hardfork_1270 ? + feed_price * ratio_type( tcr, GRAPHENE_COLLATERAL_RATIO_DENOM ) : + price() ); + // be defensive here, make sure match_price is in collateral / debt format if( match_price.base.asset_id != call_price.base.asset_id ) match_price = ~match_price; @@ -113,9 +136,22 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return debt; FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - // check collateral ratio after filled, if it's OK, we return - price new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) + // Check whether the collateral ratio after filled is high enough + // Before core-1270 hard fork, we check with call_price; afterwards, we check with collateralization(). + std::function result_is_good = after_core_hardfork_1270 ? + std::function( [this,&to_cover,&to_pay,target_collateralization]() -> bool + { + price new_collateralization = ( get_collateral() - to_pay ) / ( get_debt() - to_cover ); + return ( new_collateralization > target_collateralization ); + }) : + std::function( [this,&to_cover,&to_pay,tcr,feed_price]() -> bool + { + price new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); + return ( new_call_price > feed_price ); + }); + + // if the result is good, we return. + if( result_is_good() ) return to_cover.amount; // be here, to_cover is too small due to rounding. deal with the fraction @@ -209,8 +245,8 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return to_cover.amount; FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) // good + // Check whether the result is good + if( result_is_good() ) // good { if( to_pay.amount == max_to_pay.amount ) return to_cover.amount; @@ -255,11 +291,11 @@ share_type call_order_object::get_max_debt_to_cover( price match_price, return debt; } - // check + // defensive check FC_ASSERT( to_pay.amount < collateral && to_cover.amount < debt ); - new_call_price = price::call_price( get_debt() - to_cover, get_collateral() - to_pay, tcr ); - if( new_call_price > feed_price ) // good + // Check whether the result is good + if( result_is_good() ) // good return to_cover.amount; } diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index a6241acd86..813c804ea6 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -28,22 +28,37 @@ 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 { typedef void result_type; + const database& db; const fc::time_point_sec block_time; const fc::time_point_sec next_maintenance_time; - proposal_operation_hardfork_visitor( const fc::time_point_sec bt, const fc::time_point_sec nmt ) - : block_time(bt), next_maintenance_time(nmt) {} + proposal_operation_hardfork_visitor( const database& _db, const fc::time_point_sec bt ) + : db( _db ), block_time(bt), next_maintenance_time( db.get_dynamic_global_properties().next_maintenance_time ) {} template void operator()(const T &v) const {} - // TODO review and cleanup code below after hard fork - // hf_834 void operator()(const graphene::chain::call_order_update_operation &v) const { + + // TODO If this never ASSERTs before HF 1465, it can be removed + FC_ASSERT( block_time < SOFTFORK_CORE_1465_TIME + || block_time > HARDFORK_CORE_1465_TIME + || v.delta_debt.asset_id == asset_id_type(113) // CNY + || v.delta_debt.amount < 0 + || (v.delta_debt.asset_id( db ).bitasset_data_id + && (*(v.delta_debt.asset_id( db ).bitasset_data_id))( db ).is_prediction_market ) + , "Soft fork - preventing proposal with call_order_update!" ); + + // TODO review and cleanup code below after hard fork + // hf_834 if (next_maintenance_time <= HARDFORK_CORE_834_TIME) { FC_ASSERT( !v.extensions.value.target_collateral_ratio.valid(), "Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." ); @@ -55,6 +70,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 { @@ -111,8 +136,18 @@ struct proposal_operation_hardfork_visitor } // loop and self visit in proposals void operator()(const graphene::chain::proposal_create_operation &v) const { + bool already_contains_proposal_update = false; + for (const op_wrapper &op : v.proposed_ops) + { op.op.visit(*this); + // Do not allow more than 1 proposal_update in a proposal + if ( op.op.which() == operation::tag().value ) + { + FC_ASSERT( !already_contains_proposal_update, "At most one proposal update can be nested in a proposal!" ); + already_contains_proposal_update = true; + } + } } }; @@ -155,8 +190,7 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati // Calling the proposal hardfork visitor const fc::time_point_sec block_time = d.head_block_time(); - const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; - proposal_operation_hardfork_visitor vtor( block_time, next_maint_time ); + proposal_operation_hardfork_visitor vtor( d, block_time ); vtor( o ); if( block_time < HARDFORK_CORE_214_TIME ) { // cannot be removed after hf, unfortunately diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 7d7884e1c4..47d4d0db4a 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -22,6 +22,7 @@ * THE SOFTWARE. */ #include +#include #include namespace graphene { namespace chain { @@ -31,10 +32,12 @@ bool proposal_object::is_authorized_to_execute(database& db) const transaction_evaluation_state dry_run_eval(&db); try { + bool allow_non_immediate_owner = ( db.head_block_time() >= HARDFORK_CORE_584_TIME ); verify_authority( proposed_transaction.operations, available_key_approvals, [&]( account_id_type id ){ return &id(db).active; }, [&]( account_id_type id ){ return &id(db).owner; }, + allow_non_immediate_owner, db.get_global_properties().parameters.max_authority_depth, true, /* allow committeee */ available_active_approvals, diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index 531ea7f6f6..a9c1daf502 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -203,10 +203,11 @@ namespace graphene { namespace chain { * never go to 0 and the debt can never go more than GRAPHENE_MAX_SHARE_SUPPLY * * CR * DEBT/COLLAT or DEBT/(COLLAT/CR) + * + * Note: this function is only used before core-1270 hard fork. */ price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio) { try { - // TODO replace the calculation with new operator*() and/or operator/(), could be a hardfork change due to edge cases boost::rational swan(debt.amount.value,collateral.amount.value); boost::rational ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); auto cp = swan * ratio; @@ -239,9 +240,11 @@ namespace graphene { namespace chain { FC_ASSERT( maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); FC_ASSERT( maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO ); FC_ASSERT( maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); - max_short_squeeze_price(); // make sure that it doesn't overflow + // Note: there was code here calling `max_short_squeeze_price();` before core-1270 hard fork, + // in order to make sure that it doesn't overflow, + // but the code doesn't actually check overflow, and it won't overflow, so the code is removed. - //FC_ASSERT( maintenance_collateral_ratio >= maximum_short_squeeze_ratio ); + // Note: not checking `maintenance_collateral_ratio >= maximum_short_squeeze_ratio` since launch } FC_CAPTURE_AND_RETHROW( (*this) ) } bool price_feed::is_for( asset_id_type asset_id ) const @@ -258,17 +261,34 @@ namespace graphene { namespace chain { FC_CAPTURE_AND_RETHROW( (*this) ) } - price price_feed::max_short_squeeze_price()const + // This function is kept here due to potential different behavior in edge cases. + // TODO check after core-1270 hard fork to see if we can safely remove it + price price_feed::max_short_squeeze_price_before_hf_1270()const { - // TODO replace the calculation with new operator*() and/or operator/(), could be a hardfork change due to edge cases - boost::rational sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); //debt.amount.value,collateral.amount.value); + // settlement price is in debt/collateral + boost::rational sp( settlement_price.base.amount.value, settlement_price.quote.amount.value ); boost::rational ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio ); auto cp = sp * ratio; while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY ) - cp = boost::rational( (cp.numerator() >> 1)+(cp.numerator()&1), (cp.denominator() >> 1)+(cp.denominator()&1) ); + cp = boost::rational( (cp.numerator() >> 1)+(cp.numerator()&1), + (cp.denominator() >> 1)+(cp.denominator()&1) ); - return (asset( cp.numerator().convert_to(), settlement_price.base.asset_id ) / asset( cp.denominator().convert_to(), settlement_price.quote.asset_id )); + return ( asset( cp.numerator().convert_to(), settlement_price.base.asset_id ) + / asset( cp.denominator().convert_to(), settlement_price.quote.asset_id ) ); + } + + price price_feed::max_short_squeeze_price()const + { + // settlement price is in debt/collateral + return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio ); + } + + price price_feed::maintenance_collateralization()const + { + if( settlement_price.is_null() ) + return price(); + return ~settlement_price * ratio_type( maintenance_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); } // compile-time table of powers of 10 using template metaprogramming diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index b642dea72e..6b4d7f3235 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -59,6 +59,11 @@ void transaction::validate() const operation_validate(op); } +uint64_t transaction::get_packed_size() const +{ + return fc::raw::pack_size(*this); +} + const transaction_id_type& transaction::id() const { auto h = digest(); @@ -92,7 +97,9 @@ void transaction::set_reference_block( const block_id_type& reference_block ) ref_block_prefix = reference_block._hash[1]; } -void transaction::get_required_authorities( flat_set& active, flat_set& owner, vector& other )const +void transaction::get_required_authorities( flat_set& active, + flat_set& owner, + vector& other )const { for( const auto& op : operations ) operation_get_required_authorities( op, active, owner, other ); @@ -101,7 +108,6 @@ void transaction::get_required_authorities( flat_set& active, f } - const flat_set empty_keyset; struct sign_state @@ -161,7 +167,7 @@ struct sign_state bool check_authority( account_id_type id ) { if( approved_by.find(id) != approved_by.end() ) return true; - return check_authority( get_active(id) ); + return check_authority( get_active(id) ) || ( allow_non_immediate_owner && check_authority( get_owner(id) ) ); } /** @@ -196,7 +202,8 @@ struct sign_state { if( depth == max_recursion ) continue; - if( check_authority( get_active( a.first ), depth+1 ) ) + if( check_authority( get_active( a.first ), depth+1 ) + || ( allow_non_immediate_owner && check_authority( get_owner( a.first ), depth+1 ) ) ) { approved_by.insert( a.first ); total_weight += a.second; @@ -227,9 +234,16 @@ struct sign_state } sign_state( const flat_set& sigs, - const std::function& a, + const std::function& active, + const std::function& owner, + bool allow_owner, + uint32_t max_recursion_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH, const flat_set& keys = empty_keyset ) - :get_active(a),available_keys(keys) + : get_active(active), + get_owner(owner), + allow_non_immediate_owner(allow_owner), + max_recursion(max_recursion_depth), + available_keys(keys) { for( const auto& key : sigs ) provided_signatures[ key ] = false; @@ -237,17 +251,21 @@ struct sign_state } const std::function& get_active; - const flat_set& available_keys; + const std::function& get_owner; + + const bool allow_non_immediate_owner; + const uint32_t max_recursion; + const flat_set& available_keys; flat_map provided_signatures; flat_set approved_by; - uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH; }; void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth, bool allow_committe, const flat_set& active_aprovals, @@ -264,8 +282,7 @@ void verify_authority( const vector& ops, const flat_set signed_transaction::get_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion_depth )const { flat_set required_active; @@ -329,8 +347,7 @@ set signed_transaction::get_required_signatures( get_required_authorities( required_active, required_owner, other ); const flat_set& signature_keys = get_signature_keys( chain_id ); - sign_state s( signature_keys, get_active, available_keys ); - s.max_recursion = max_recursion_depth; + sign_state s( signature_keys, get_active, get_owner, allow_non_immediate_owner, max_recursion_depth, available_keys ); for( const auto& auth : other ) s.check_authority(&auth); @@ -356,6 +373,7 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion ) const { @@ -367,7 +385,7 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::chain::verify_authority( operations, result, get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, result, get_active, get_owner, allow_non_immediate_owner, max_recursion ); continue; // element stays erased if verify_authority is ok } catch( const tx_missing_owner_auth& e ) {} @@ -392,6 +410,13 @@ void precomputable_transaction::validate() const _validated = true; } +uint64_t precomputable_transaction::get_packed_size()const +{ + if( _packed_size == 0 ) + _packed_size = transaction::get_packed_size(); + return _packed_size; +} + const flat_set& precomputable_transaction::get_signature_keys( const chain_id_type& chain_id )const { // Strictly we should check whether the given chain ID is same as the one used to initialize the `signees` field. @@ -405,9 +430,15 @@ void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + bool allow_non_immediate_owner, uint32_t max_recursion )const { try { - graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, + get_signature_keys( chain_id ), + get_active, + get_owner, + allow_non_immediate_owner, + max_recursion ); } FC_CAPTURE_AND_RETHROW( (*this) ) } } } // graphene::chain 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/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index e069fbcdc3..d99d8bf0b5 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1469,7 +1469,7 @@ class wallet_api * @param broadcast true if you wish to broadcast the transaction */ signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, - string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, const uint32_t claim_period_seconds, bool broadcast = false ); /**** diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index db36a601eb..938a3e0d99 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1791,7 +1791,7 @@ class wallet_api_impl } signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, - string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, const uint32_t claim_period_seconds, bool broadcast = false ) { try @@ -3179,7 +3179,7 @@ uint64_t wallet_api::get_asset_count()const } signed_transaction wallet_api::htlc_create( string source, string destination, string amount, string asset_symbol, - string hash_algorithm, const std::string& preimage_hash, size_t preimage_size, + string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, const uint32_t claim_period_seconds, bool broadcast) { return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size, diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 7cfa6b9251..47d5755f89 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -23,6 +23,7 @@ */ #include #include +#include #include #include @@ -63,7 +64,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 { @@ -83,9 +84,13 @@ 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; + + if(boost::unit_test::framework::current_test_case().p_name.value == "hf_935_test") + genesis_state.initial_active_witnesses = 20; + else + genesis_state.initial_active_witnesses = 10; - genesis_state.initial_active_witnesses = 10; for( unsigned int i = 0; i < genesis_state.initial_active_witnesses; ++i ) { auto name = "init"+fc::to_string(i); @@ -406,7 +411,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() */ ) { @@ -472,7 +477,7 @@ const asset_object& database_fixture::create_bitasset( creator.issuer = issuer; creator.fee = asset(); creator.symbol = name; - creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY / 2; creator.precision = precision; creator.common_options.market_fee_percent = market_fee_percent; if( issuer == GRAPHENE_WITNESS_ACCOUNT ) @@ -540,8 +545,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; @@ -553,6 +560,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 ); @@ -626,7 +635,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()*/ ) { @@ -648,7 +657,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 @@ -658,6 +667,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); @@ -754,6 +765,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; @@ -1151,6 +1165,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 6c06e70feb..41bce5fd8a 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -155,7 +155,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) \ @@ -191,8 +191,10 @@ struct database_fixture { optional data_dir; bool skip_key_index_test = false; uint32_t anon_acct_count; + bool hf1270 = false; - 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); @@ -225,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() ); @@ -289,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 ); @@ -302,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() ); @@ -311,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 ); @@ -352,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/authority_tests.cpp b/tests/tests/authority_tests.cpp index bb32f806e9..ce9f1cadc5 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -1192,9 +1192,12 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( well_id, authority( 60, alice_id, 50, bob_id, 50 ) ); @@ -1306,9 +1309,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; auto chk_min = [&]( @@ -1318,9 +1324,12 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); - //wdump( (result_set)(ref_set) ); - return result_set == ref_set; + set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, false ); + set result_set2 = tx.minimize_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, true ); + //wdump( (result_set)(result_set2)(ref_set) ); + return result_set == ref_set && result_set2 == ref_set; } ; set_auth( roco_id, authority( 2, styx_id, 1, thud_id, 2 ) ); @@ -1337,9 +1346,11 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); } catch(fc::exception& e) { @@ -1348,15 +1359,42 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) } } +/* + * Active vs Owner https://github.com/bitshares/bitshares-core/issues/584 + * + * All weights and all thresholds are 1, so every single key should be able to sign if within max_depth + * + * Bob --+--(a)--+-- Alice --+--(a)--+-- Daisy --(a/o)-- Daisy_active_key / Daisy_owner_key + * | | | | + * | | | +-- Alice_active_key + * | | | + * | | +--(o)--+-- Cindy --(a/o)-- Cindy_active_key / Cindy_owner_key + * | | | + * | | +-- Alice_owner_key + * | | + * | +-- Bob_active_key + * | + * +--(o)--+-- Edwin --+--(a)--+-- Gavin --(a/o)-- Gavin_active_key / Gavin_owner_key + * | | | + * | | +-- Edwin_active_key + * | | + * | +--(o)--+-- Frank --(a/o)-- Frank_active_key / Frank_owner_key + * | | + * | +-- Edwin_owner_key + * | + * +-- Bob_owner_key + */ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) { try { ACTORS( - (alice)(bob) + (alice)(bob)(cindy)(daisy)(edwin)(frank)(gavin) ); - auto set_auth2 = [&]( + transfer( account_id_type(), bob_id, asset(100000) ); + + auto set_auth = [&]( account_id_type aid, const authority& active, const authority& owner @@ -1372,14 +1410,6 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) PUSH_TX( db, tx, database::skip_transaction_signatures ); } ; - auto set_auth = [&]( - account_id_type aid, - const authority& auth - ) - { - set_auth2( aid, auth, auth ); - } ; - auto get_active = [&]( account_id_type aid ) -> const authority* @@ -1396,22 +1426,60 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) auto chk = [&]( const signed_transaction& tx, + bool after_hf_584, flat_set available_keys, set ref_set ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, + get_active, get_owner, after_hf_584 ); //wdump( (result_set)(ref_set) ); return result_set == ref_set; } ; fc::ecc::private_key alice_active_key = fc::ecc::private_key::regenerate(fc::digest("alice_active")); fc::ecc::private_key alice_owner_key = fc::ecc::private_key::regenerate(fc::digest("alice_owner")); + fc::ecc::private_key bob_active_key = fc::ecc::private_key::regenerate(fc::digest("bob_active")); + fc::ecc::private_key bob_owner_key = fc::ecc::private_key::regenerate(fc::digest("bob_owner")); + fc::ecc::private_key cindy_active_key = fc::ecc::private_key::regenerate(fc::digest("cindy_active")); + fc::ecc::private_key cindy_owner_key = fc::ecc::private_key::regenerate(fc::digest("cindy_owner")); + fc::ecc::private_key daisy_active_key = fc::ecc::private_key::regenerate(fc::digest("daisy_active")); + fc::ecc::private_key daisy_owner_key = fc::ecc::private_key::regenerate(fc::digest("daisy_owner")); + fc::ecc::private_key edwin_active_key = fc::ecc::private_key::regenerate(fc::digest("edwin_active")); + fc::ecc::private_key edwin_owner_key = fc::ecc::private_key::regenerate(fc::digest("edwin_owner")); + fc::ecc::private_key frank_active_key = fc::ecc::private_key::regenerate(fc::digest("frank_active")); + fc::ecc::private_key frank_owner_key = fc::ecc::private_key::regenerate(fc::digest("frank_owner")); + fc::ecc::private_key gavin_active_key = fc::ecc::private_key::regenerate(fc::digest("gavin_active")); + fc::ecc::private_key gavin_owner_key = fc::ecc::private_key::regenerate(fc::digest("gavin_owner")); + public_key_type alice_active_pub( alice_active_key.get_public_key() ); public_key_type alice_owner_pub( alice_owner_key.get_public_key() ); - set_auth2( alice_id, authority( 1, alice_active_pub, 1 ), authority( 1, alice_owner_pub, 1 ) ); - set_auth( bob_id, authority( 1, alice_id, 1 ) ); + public_key_type bob_active_pub( bob_active_key.get_public_key() ); + public_key_type bob_owner_pub( bob_owner_key.get_public_key() ); + public_key_type cindy_active_pub( cindy_active_key.get_public_key() ); + public_key_type cindy_owner_pub( cindy_owner_key.get_public_key() ); + public_key_type daisy_active_pub( daisy_active_key.get_public_key() ); + public_key_type daisy_owner_pub( daisy_owner_key.get_public_key() ); + public_key_type edwin_active_pub( edwin_active_key.get_public_key() ); + public_key_type edwin_owner_pub( edwin_owner_key.get_public_key() ); + public_key_type frank_active_pub( frank_active_key.get_public_key() ); + public_key_type frank_owner_pub( frank_owner_key.get_public_key() ); + public_key_type gavin_active_pub( gavin_active_key.get_public_key() ); + public_key_type gavin_owner_pub( gavin_owner_key.get_public_key() ); + + set_auth( alice_id, authority( 1, alice_active_pub, 1, daisy_id, 1 ), authority( 1, alice_owner_pub, 1, cindy_id, 1 ) ); + set_auth( bob_id, authority( 1, bob_active_pub, 1, alice_id, 1 ), authority( 1, bob_owner_pub, 1, edwin_id, 1 ) ); + + set_auth( cindy_id, authority( 1, cindy_active_pub, 1 ), authority( 1, cindy_owner_pub, 1 ) ); + set_auth( daisy_id, authority( 1, daisy_active_pub, 1 ), authority( 1, daisy_owner_pub, 1 ) ); + + set_auth( edwin_id, authority( 1, edwin_active_pub, 1, gavin_id, 1 ), authority( 1, edwin_owner_pub, 1, frank_id, 1 ) ); + + set_auth( frank_id, authority( 1, frank_active_pub, 1 ), authority( 1, frank_owner_pub, 1 ) ); + set_auth( gavin_id, authority( 1, gavin_active_pub, 1 ), authority( 1, gavin_owner_pub, 1 ) ); + + generate_block(); signed_transaction tx; transfer_operation op; @@ -1419,17 +1487,369 @@ BOOST_FIXTURE_TEST_CASE( parent_owner_test, database_fixture ) op.to = alice_id; op.amount = asset(1); tx.operations.push_back( op ); + set_expiration( db, tx ); // https://github.com/bitshares/bitshares-core/issues/584 - BOOST_CHECK( chk( tx, { alice_owner_pub }, { } ) ); - BOOST_CHECK( chk( tx, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + // If not allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, false, { alice_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { cindy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { cindy_active_pub, cindy_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { daisy_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { edwin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, false, { frank_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { frank_active_pub, frank_owner_pub }, { } ) ); + + BOOST_CHECK( chk( tx, false, { gavin_owner_pub }, { } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, false, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + + // If allow non-immediate owner to authorize + BOOST_CHECK( chk( tx, true, { alice_owner_pub }, { alice_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub }, { alice_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { alice_active_pub, alice_owner_pub }, { alice_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { bob_owner_pub }, { bob_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub }, { bob_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { bob_active_pub, bob_owner_pub }, { bob_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { cindy_owner_pub }, { cindy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub }, { cindy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { cindy_active_pub, cindy_owner_pub }, { cindy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { daisy_owner_pub }, { daisy_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub }, { daisy_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { daisy_active_pub, daisy_owner_pub }, { daisy_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { edwin_owner_pub }, { edwin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub }, { edwin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { edwin_active_pub, edwin_owner_pub }, { edwin_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { frank_owner_pub }, { frank_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub }, { frank_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { frank_active_pub, frank_owner_pub }, { frank_active_pub } ) ); + + BOOST_CHECK( chk( tx, true, { gavin_owner_pub }, { gavin_owner_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub }, { gavin_active_pub } ) ); + BOOST_CHECK( chk( tx, true, { gavin_active_pub, gavin_owner_pub }, { gavin_active_pub } ) ); + sign( tx, alice_owner_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, alice_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, edwin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), fc::exception ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, tx, database::skip_transaction_dupe_check ), fc::exception ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); + tx.clear_signatures(); + sign( tx, gavin_active_key ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); tx.clear_signatures(); + + // proposal tests + auto new_proposal = [&]() -> proposal_id_type { + signed_transaction ptx; + + proposal_create_operation pop; + pop.proposed_ops.emplace_back(op); + pop.fee_paying_account = bob_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + ptx.operations.push_back(pop); + set_expiration( db, ptx ); + sign( ptx, bob_active_key ); + + return PUSH_TX( db, ptx, database::skip_transaction_dupe_check ).operation_results[0].get(); + }; + + auto approve_proposal = [&]( + proposal_id_type proposal, + account_id_type account, + bool approve_with_owner, + fc::ecc::private_key key + ) + { + signed_transaction ptx; + + proposal_update_operation pup; + pup.fee_paying_account = account; + pup.proposal = proposal; + if( approve_with_owner ) + pup.owner_approvals_to_add.insert( account ); + else + pup.active_approvals_to_add.insert( account ); + ptx.operations.push_back(pup); + set_expiration( db, ptx ); + sign( ptx, key ); + PUSH_TX( db, ptx, database::skip_transaction_dupe_check ); + }; + + proposal_id_type pid; + + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Cindy's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + // Frank's approval doesn't work + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); + + // pass the hard fork time + generate_blocks( HARDFORK_CORE_584_TIME, true, database::skip_transaction_dupe_check ); + set_expiration( db, tx ); + + // signing tests + sign( tx, alice_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, bob_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, cindy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, daisy_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, edwin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + sign( tx, edwin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, frank_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_owner_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + sign( tx, gavin_active_key ); + PUSH_TX( db, tx, database::skip_transaction_dupe_check ); + tx.clear_signatures(); + + // proposal tests + pid = new_proposal(); + approve_proposal( pid, alice_id, true, alice_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, alice_id, false, alice_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, true, bob_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, bob_id, false, bob_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, true, cindy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, cindy_id, false, cindy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, true, daisy_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, daisy_id, false, daisy_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, true, edwin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, edwin_id, false, edwin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, true, frank_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, frank_id, false, frank_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, true, gavin_owner_key ); + BOOST_CHECK( !db.find( pid ) ); + + pid = new_proposal(); + approve_proposal( pid, gavin_id, false, gavin_active_key ); + BOOST_CHECK( !db.find( pid ) ); + + generate_block( database::skip_transaction_dupe_check ); } catch(fc::exception& e) { @@ -1492,23 +1912,29 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_owner_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_owner_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_owner_auth ); // signed with alice's active key, should throw tx_missing_owner_auth sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_owner_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_owner_auth ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with both alice's owner key and active key, // it does not throw due to https://github.com/bitshares/bitshares-core/issues/580 sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // creating a transaction that needs active permission tx.clear(); @@ -1517,21 +1943,27 @@ BOOST_FIXTURE_TEST_CASE( missing_owner_auth_test, database_fixture ) tx.operations.push_back( op ); // not signed, should throw tx_missing_active_auth - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_missing_active_auth ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_missing_active_auth ); // signed with alice's active key, should not throw sign( tx, alice_active_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with alice's owner key, should not throw tx.clear_signatures(); sign( tx, alice_owner_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ); // signed with both alice's owner key and active key, should throw tx_irrelevant_sig sign( tx, alice_active_key ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, false ), + graphene::chain::tx_irrelevant_sig ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, true ), graphene::chain::tx_irrelevant_sig ); } diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 4fc6097650..03062fbe6e 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -453,6 +453,10 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) generate_blocks(HARDFORK_615_TIME, true, skip); // get around Graphene issue #615 feed expiration bug generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); + auto hf_time = HARDFORK_CORE_868_890_TIME; + if(hf1270) + hf_time = HARDFORK_CORE_1270_TIME; + for( int i=0; i<2; ++i ) { int blocks = 0; @@ -460,7 +464,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) if( i == 1 ) // go beyond hard fork { - blocks += generate_blocks(HARDFORK_CORE_868_890_TIME - mi, true, skip); + blocks += generate_blocks(hf_time - mi, true, skip); blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } set_expiration( db, trx ); @@ -526,7 +530,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) ba_op.asset_to_update = usd_id; ba_op.issuer = asset_to_update.issuer; ba_op.new_options = asset_to_update.bitasset_data(db).options; - ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_868_890_TIME.sec_since_epoch() + ba_op.new_options.feed_lifetime_sec = hf_time.sec_since_epoch() - db.head_block_time().sec_since_epoch() + mi + 1800; @@ -542,7 +546,7 @@ BOOST_AUTO_TEST_CASE( hf_890_test ) BOOST_CHECK( db.find( sell_id ) ); // go beyond hard fork - blocks += generate_blocks(HARDFORK_CORE_868_890_TIME - mi, true, skip); + blocks += generate_blocks(hf_time - mi, true, skip); blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } @@ -923,7 +927,7 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); generate_block( skip ); - for( int i = 0; i < 6; ++i ) + for( int i = 0; i < 8; ++i ) { idump( (i) ); int blocks = 0; @@ -939,6 +943,11 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) generate_blocks( HARDFORK_CORE_935_TIME - mi, true, skip ); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); } + else if( i == 6 ) // go beyond hard fork 1270 + { + generate_blocks( HARDFORK_CORE_1270_TIME - mi, true, skip ); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); + } set_expiration( db, trx ); ACTORS( (seller)(borrower)(feedproducer)(feedproducer2)(feedproducer3) ); @@ -1049,7 +1058,7 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) ba_op.asset_to_update = usd_id; ba_op.issuer = asset_to_update.issuer; ba_op.new_options = asset_to_update.bitasset_data(db).options; - ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_935_TIME.sec_since_epoch() + ba_op.new_options.feed_lifetime_sec = HARDFORK_CORE_1270_TIME.sec_since_epoch() + mi * 3 + 86400 * 2 - db.head_block_time().sec_since_epoch(); trx.operations.push_back(ba_op); @@ -1102,22 +1111,39 @@ BOOST_AUTO_TEST_CASE( hf_935_test ) blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } - // after hard fork 935, the limit order should be filled + // after hard fork 935, the limit order is filled only for the MSSR test + if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_935_TIME && + db.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_1270_TIME) { // check median BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); - if( i % 2 == 0 ) // MCR test, median MCR should be 350% - BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500 ); - else // MSSR test, MSSR should be 125% - BOOST_CHECK_EQUAL( usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250 ); - // the limit order should have been filled - // TODO FIXME this test case is failing for MCR test, - // because call_order's call_price didn't get updated after MCR changed - // BOOST_CHECK( !db.find( sell_id ) ); - if( i % 2 == 1 ) // MSSR test - BOOST_CHECK( !db.find( sell_id ) ); + if( i % 2 == 0) { // MCR test, median MCR should be 350% and order will not be filled except when i = 0 + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500); + if( affected_by_hf_343 ) + BOOST_CHECK(!db.find(sell_id)); + else + BOOST_CHECK(db.find(sell_id)); // MCR bug, order still there + } + else { // MSSR test, MSSR should be 125% and order is filled + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maximum_short_squeeze_ratio, 1250); + BOOST_CHECK(!db.find(sell_id)); // order filled + } + + // go beyond hard fork 1270 + blocks += generate_blocks(HARDFORK_CORE_1270_TIME - mi, true, skip); + blocks += generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); } + // after hard fork 1270, the limit order should be filled for MCR test + if( db.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_1270_TIME) + { + // check median + BOOST_CHECK( usd_id(db).bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + if( i % 2 == 0 ) { // MCR test, order filled + BOOST_CHECK_EQUAL(usd_id(db).bitasset_data(db).current_feed.maintenance_collateral_ratio, 3500); + BOOST_CHECK(!db.find(sell_id)); + } + } // undo above tx's and reset generate_block( skip ); @@ -1375,5 +1401,11 @@ BOOST_AUTO_TEST_CASE( reset_backing_asset_switching_to_witness_fed ) } } */ +BOOST_AUTO_TEST_CASE(hf_890_test_hf1270) +{ try { + hf1270 = true; + INVOKE(hf_890_test); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/database_api_tests.cpp b/tests/tests/database_api_tests.cpp index 7a37b2a318..41f5768084 100644 --- a/tests/tests/database_api_tests.cpp +++ b/tests/tests/database_api_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -36,7 +37,8 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE(database_api_tests, database_fixture) -BOOST_AUTO_TEST_CASE(is_registered) { +BOOST_AUTO_TEST_CASE(is_registered) +{ try { /*** * Arrange @@ -71,7 +73,8 @@ BOOST_AUTO_TEST_CASE(is_registered) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) { +BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) +{ try { fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); @@ -118,7 +121,151 @@ BOOST_AUTO_TEST_CASE( get_potential_signatures_owner_and_active ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) { +/// Testing get_potential_signatures and get_required_signatures for non-immediate owner authority issue. +/// https://github.com/bitshares/bitshares-core/issues/584 +BOOST_AUTO_TEST_CASE( get_signatures_non_immediate_owner ) +{ + try { + fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); + fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); + fc::ecc::private_key ashley_key1 = fc::ecc::private_key::regenerate(fc::digest("akey1")); + fc::ecc::private_key ashley_key2 = fc::ecc::private_key::regenerate(fc::digest("akey2")); + fc::ecc::private_key oliver_key1 = fc::ecc::private_key::regenerate(fc::digest("okey1")); + fc::ecc::private_key oliver_key2 = fc::ecc::private_key::regenerate(fc::digest("okey2")); + public_key_type pub_key_active( nathan_key1.get_public_key() ); + public_key_type pub_key_owner( nathan_key2.get_public_key() ); + public_key_type a_pub_key_active( ashley_key1.get_public_key() ); + public_key_type a_pub_key_owner( ashley_key2.get_public_key() ); + public_key_type o_pub_key_active( oliver_key1.get_public_key() ); + public_key_type o_pub_key_owner( oliver_key2.get_public_key() ); + const account_object& nathan = create_account("nathan", nathan_key1.get_public_key() ); + const account_object& ashley = create_account("ashley", ashley_key1.get_public_key() ); + const account_object& oliver = create_account("oliver", oliver_key1.get_public_key() ); + account_id_type nathan_id = nathan.id; + account_id_type ashley_id = ashley.id; + account_id_type oliver_id = oliver.id; + + try { + account_update_operation op; + op.account = nathan_id; + op.active = authority(1, pub_key_active, 1, ashley_id, 1); + op.owner = authority(1, pub_key_owner, 1, oliver_id, 1); + trx.operations.push_back(op); + sign(trx, nathan_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = ashley_id; + op.active = authority(1, a_pub_key_active, 1); + op.owner = authority(1, a_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, ashley_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + + op.account = oliver_id; + op.active = authority(1, o_pub_key_active, 1); + op.owner = authority(1, o_pub_key_owner, 1); + trx.operations.push_back(op); + sign(trx, oliver_key1); + PUSH_TX( db, trx, database::skip_transaction_dupe_check ); + trx.clear(); + } FC_CAPTURE_AND_RETHROW ((nathan.active)) + + // this transaction requires active + signed_transaction trx_a; + transfer_operation op; + op.from = nathan_id; + op.to = account_id_type(); + trx_a.operations.push_back(op); + + // get potential signatures + graphene::app::database_api db_api(db); + set pub_keys = db_api.get_potential_signatures( trx_a ); + + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // this op requires owner + signed_transaction trx_o; + account_update_operation auop; + auop.account = nathan_id; + auop.owner = authority(1, pub_key_owner, 1); + trx_o.operations.push_back(auop); + + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + // doesn't work due to https://github.com/bitshares/bitshares-core/issues/584 + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) == pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.empty() ); + + // go beyond hard fork + generate_blocks( HARDFORK_CORE_584_TIME, true ); + + // for the transaction that requires active + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_a ); + + // all authorities should be ok + BOOST_CHECK( pub_keys.find( pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_a, { a_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) != pub_keys.end() ); + pub_keys = db_api.get_required_signatures( trx_a, { o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // for the transaction that requires owner + // get potential signatures + pub_keys = db_api.get_potential_signatures( trx_o ); + + // active authorities doesn't help in this case + BOOST_CHECK( pub_keys.find( pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_active ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + + // owner authorities should help + BOOST_CHECK( pub_keys.find( pub_key_owner ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_active ) != pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + // get required signatures + pub_keys = db_api.get_required_signatures( trx_o, { a_pub_key_owner, o_pub_key_owner } ); + BOOST_CHECK( pub_keys.find( a_pub_key_owner ) == pub_keys.end() ); + BOOST_CHECK( pub_keys.find( o_pub_key_owner ) != pub_keys.end() ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) +{ try { fc::ecc::private_key priv_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); public_key_type pub_key1( priv_key1.get_public_key() ); @@ -138,7 +285,8 @@ BOOST_AUTO_TEST_CASE( get_potential_signatures_other ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) { +BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) +{ try { fc::ecc::private_key nathan_key1 = fc::ecc::private_key::regenerate(fc::digest("key1")); fc::ecc::private_key nathan_key2 = fc::ecc::private_key::regenerate(fc::digest("key2")); @@ -211,7 +359,8 @@ BOOST_AUTO_TEST_CASE( get_required_signatures_owner_or_active ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) { +BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) +{ try { fc::ecc::private_key morgan_key = fc::ecc::private_key::regenerate(fc::digest("morgan_key")); fc::ecc::private_key nathan_key = fc::ecc::private_key::regenerate(fc::digest("nathan_key")); @@ -602,7 +751,8 @@ BOOST_AUTO_TEST_CASE( get_required_signatures_partially_signed_or_not ) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( set_subscribe_callback_disable_notify_all_test ) { +BOOST_AUTO_TEST_CASE( set_subscribe_callback_disable_notify_all_test ) +{ try { ACTORS( (alice) ); @@ -792,7 +942,7 @@ BOOST_AUTO_TEST_CASE(get_account_limit_orders) limit_order_id_type(o.id), o.sell_price); BOOST_CHECK(results.size() == 71); BOOST_CHECK(results.front().id > o.id); - // NOTE 3: because of NOTE 1, here should be little than + // NOTE 3: because of NOTE 1, here should be little than BOOST_CHECK(results.front().sell_price < o.sell_price); for (size_t i = 0 ; i < results.size() - 1 ; ++i) { diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index fa5181da1f..cd8bff5a59 100644 --- a/tests/tests/htlc_tests.cpp +++ b/tests/tests/htlc_tests.cpp @@ -136,16 +136,13 @@ void set_committee_parameters(database_fixture* db_fixture) db_fixture->trx.operations.push_back(cop); graphene::chain::processed_transaction proc_trx =db_fixture->db.push_transaction(db_fixture->trx); + db_fixture->trx.clear(); proposal_id_type good_proposal_id = proc_trx.operation_results[0].get(); proposal_update_operation puo; puo.proposal = good_proposal_id; puo.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; - puo.active_approvals_to_add = { - db_fixture->get_account("init0").get_id(), db_fixture->get_account("init1").get_id(), - db_fixture->get_account("init2").get_id(), db_fixture->get_account("init3").get_id(), - db_fixture->get_account("init4").get_id(), db_fixture->get_account("init5").get_id(), - db_fixture->get_account("init6").get_id(), db_fixture->get_account("init7").get_id()}; + puo.key_approvals_to_add.emplace( db_fixture->init_account_priv_key.get_public_key() ); db_fixture->trx.operations.push_back(puo); db_fixture->sign( db_fixture->trx, db_fixture->init_account_priv_key ); db_fixture->db.push_transaction(db_fixture->trx); 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/market_tests.cpp b/tests/tests/market_tests.cpp index 1f29f0c843..c30505f87f 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -231,8 +231,14 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) */ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_343_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -325,7 +331,8 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // call's call_price will be updated after the match, to 741/31/1.75 CORE/USD = 2964/217 // it's above settlement price (10/1) so won't be margin called again - BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); + if(!hf1270) // can use call price only if we are before hf1270 + BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); // This would match with call before, but would match with call2 after #343 fixed BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6000) ) ); @@ -342,7 +349,8 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) BOOST_CHECK_EQUAL( 1000, call3.debt.value ); BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21 - BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); + if(!hf1270) // can use call price only if we are before hf1270 + BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); // it's above settlement price (10/1) so won't be margin called // at this moment, collateralization of call is 7410 / 310 = 23.9 @@ -406,8 +414,14 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) */ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_343_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -478,7 +492,6 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) // generate a block generate_block(); - } FC_LOG_AND_RETHROW() } /*** @@ -486,8 +499,14 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) */ BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_625_TIME - mi); // assume all hard forks occur at same time + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_625_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1195,8 +1214,14 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) */ BOOST_AUTO_TEST_CASE(target_cr_test_limit_call) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_834_TIME - mi); + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_834_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1373,8 +1398,14 @@ BOOST_AUTO_TEST_CASE(target_cr_test_limit_call) */ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit) { try { + auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_834_TIME - mi); + + if(hf1270) + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + else + generate_blocks(HARDFORK_CORE_834_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -1507,4 +1538,355 @@ BOOST_AUTO_TEST_CASE(target_cr_test_call_limit) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(mcr_bug_increase_before1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with mcr only + current_feed.maintenance_collateral_ratio = 2000; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + print_market(bitusd.symbol, core.symbol); + + // both calls are still there, no margin call, mcr bug + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_increase_after1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with mcr only + current_feed.maintenance_collateral_ratio = 2000; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998900 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999100 ); + + print_market(bitusd.symbol, core.symbol); + + // b1 is margin called + BOOST_CHECK( ! db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_decrease_before1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with the feed + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + + // getting out of margin call territory with mcr change + current_feed.maintenance_collateral_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998350 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 999650 ); + + print_market(bitusd.symbol, core.symbol); + + // margin call at b1, mcr bug + BOOST_CHECK( !db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_decrease_after1270) +{ try { + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + const call_order_object& b1 = *borrow( borrower, bitusd.amount(1000), asset(1800)); + auto b1_id = b1.id; + const call_order_object& b2 = *borrow( borrower2, bitusd.amount(1000), asset(2000) ); + auto b2_id = b2.id; + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), init_balance - 1800 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), init_balance - 2000 ); + + // move order to margin call territory with the feed + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(150); + publish_feed( bitusd, feedproducer, current_feed ); + + // getting out of margin call territory with mcr decrease + current_feed.maintenance_collateral_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + // attempt to trade the margin call + create_sell_order( borrower2, bitusd.amount(1000), core.amount(1100) ); + + BOOST_CHECK_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, bitusd ), 0 ); + BOOST_CHECK_EQUAL( get_balance( borrower , core ), 998200 ); + BOOST_CHECK_EQUAL( get_balance( borrower2, core ), 998000 ); + + print_market(bitusd.symbol, core.symbol); + + // both calls are there, no margin call, good + BOOST_CHECK( db.find( b1_id ) ); + BOOST_CHECK( db.find( b2_id ) ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(mcr_bug_cross1270) +{ try { + + INVOKE(mcr_bug_increase_before1270); + + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + + const asset_object& core = get_asset(GRAPHENE_SYMBOL); + const asset_object& bitusd = get_asset("USDBIT"); + const asset_id_type bitusd_id = bitusd.id; + const account_object& feedproducer = get_account("feedproducer"); + + // feed is expired + auto mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); + + // make new feed + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 2000; + current_feed.maximum_short_squeeze_ratio = 1100; + publish_feed( bitusd, feedproducer, current_feed ); + + mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, 2000); + + // pass hardfork + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // feed is still valid + mcr = (*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio; + BOOST_CHECK_EQUAL(mcr, 2000); + + // margin call is traded + print_market(asset_id_type(1)(db).symbol, asset_id_type()(db).symbol); + + // call b1 not there anymore + BOOST_CHECK( !db.find( call_order_id_type() ) ); + BOOST_CHECK( db.find( call_order_id_type(1) ) ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_338_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_338_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_453_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_453_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test_after_hf1270) +{ try { + hf1270 = true; + INVOKE(hardfork_core_625_big_limit_order_test); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_limit_call_after_hf1270) +{ try { + hf1270 = true; + INVOKE(target_cr_test_limit_call); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(target_cr_test_call_limit_after_hf1270) +{ try { + hf1270 = true; + INVOKE(target_cr_test_call_limit); + +} FC_LOG_AND_RETHROW() } + + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/network_broadcast_api_tests.cpp b/tests/tests/network_broadcast_api_tests.cpp index a566750b67..a40c112662 100644 --- a/tests/tests/network_broadcast_api_tests.cpp +++ b/tests/tests/network_broadcast_api_tests.cpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -71,4 +72,29 @@ BOOST_AUTO_TEST_CASE( broadcast_transaction_with_callback_test ) { } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( broadcast_transaction_too_large ) { + try { + + fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest("key") ); + const account_id_type cid_id = create_account( "cid", cid_key.get_public_key() ).id; + fund( cid_id(db) ); + + auto nb_api = std::make_shared< graphene::app::network_broadcast_api >( app ); + + generate_blocks( HARDFORK_CORE_1573_TIME + 10 ); + + set_expiration( db, trx ); + transfer_operation trans; + trans.from = cid_id; + trans.to = account_id_type(); + trans.amount = asset(1); + for(int i = 0; i < 250; ++i ) + trx.operations.push_back( trans ); + sign( trx, cid_key ); + + BOOST_CHECK_THROW( nb_api->broadcast_transaction( trx ), fc::exception ); + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b2edab15ad..c4fa17b8a9 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -592,7 +592,6 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) op.extensions.value.target_collateral_ratio = 65535; op.validate(); // still valid - } // Tests that target_cr option can't be set before hard fork core-834 @@ -2004,6 +2003,111 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test ) } } +BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) +{ + try + { + ACTORS( (alice) (bob) ); + transfer(committee_account, alice_id, asset(10000000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + const auto& core = asset_id_type()(db); + + // attempt to increase current supply beyond max_supply + const auto& bitjmj = create_bitasset( "JMJBIT", alice_id ); + auto bitjmj_id = bitjmj.get_id(); + share_type original_max_supply = bitjmj.options.max_supply; + + { + BOOST_TEST_MESSAGE( "Setting price feed to $100000 / 1" ); + update_feed_producers( bitjmj, {alice_id} ); + price_feed current_feed; + current_feed.settlement_price = bitjmj.amount( 100000 ) / core.amount(1); + publish_feed( bitjmj, alice, current_feed ); + } + + { + BOOST_TEST_MESSAGE( "Attempting a call_order_update that exceeds max_supply" ); + call_order_update_operation op; + op.funding_account = alice_id; + op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION ); + op.delta_debt = asset( bitjmj.options.max_supply + 1, bitjmj.id ); + transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); + generate_block(); + } + + // advance past hardfork + generate_blocks( HARDFORK_CORE_1465_TIME ); + set_expiration( db, trx ); + + // bitjmj should have its problem corrected + auto newbitjmj = bitjmj_id(db); + BOOST_REQUIRE_GT(newbitjmj.options.max_supply.value, original_max_supply.value); + + // now try with an asset after the hardfork + const auto& bitusd = create_bitasset( "USDBIT", alice_id ); + + { + BOOST_TEST_MESSAGE( "Setting price feed to $100000 / 1" ); + update_feed_producers( bitusd, {alice_id} ); + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100000 ) / core.amount(1); + publish_feed( bitusd, alice_id(db), current_feed ); + } + + { + BOOST_TEST_MESSAGE( "Attempting a call_order_update that exceeds max_supply" ); + call_order_update_operation op; + op.funding_account = alice_id; + op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION ); + op.delta_debt = asset( bitusd.options.max_supply + 1, bitusd.id ); + transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception ); + } + + { + BOOST_TEST_MESSAGE( "Creating 2 bitusd and transferring to bob (increases current supply)" ); + call_order_update_operation op; + op.funding_account = alice_id; + op.delta_collateral = asset( 100 * GRAPHENE_BLOCKCHAIN_PRECISION ); + op.delta_debt = asset( 2, bitusd.id ); + transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); + transfer( alice_id(db), bob_id(db), asset( 2, bitusd.id ) ); + } + + { + BOOST_TEST_MESSAGE( "Again attempting a call_order_update_operation that is max_supply - 1 (should throw)" ); + call_order_update_operation op; + op.funding_account = alice_id; + op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION ); + op.delta_debt = asset( bitusd.options.max_supply - 1, bitusd.id ); + transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception); + } + + { + BOOST_TEST_MESSAGE( "Again attempting a call_order_update_operation that equals max_supply (should work)" ); + call_order_update_operation op; + op.funding_account = alice_id; + op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION ); + op.delta_debt = asset( bitusd.options.max_supply - 2, bitusd.id ); + transaction tx; + tx.operations.push_back( op ); + set_expiration( db, tx ); + PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); + } + } FC_LOG_AND_RETHROW() +} + /** * This test demonstrates how using the call_order_update_operation to * trigger a margin call is legal if there is a matching order. diff --git a/tests/tests/swan_tests.cpp b/tests/tests/swan_tests.cpp index 515e6235f8..955b84e47c 100644 --- a/tests/tests/swan_tests.cpp +++ b/tests/tests/swan_tests.cpp @@ -114,6 +114,11 @@ struct swan_fixture : database_fixture { generate_blocks( HARDFORK_CORE_216_TIME ); generate_block(); } + void wait_for_hf_core_1270() { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_1270_TIME - mi); + wait_for_maintenance(); + } void wait_for_maintenance() { generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); @@ -141,6 +146,9 @@ BOOST_FIXTURE_TEST_SUITE( swan_tests, swan_fixture ) */ BOOST_AUTO_TEST_CASE( black_swan ) { try { + if(hf1270) + wait_for_hf_core_1270(); + init_standard_swan(); force_settle( borrower(), swan().amount(100) ); @@ -167,6 +175,7 @@ BOOST_AUTO_TEST_CASE( black_swan ) */ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) { try { + ACTORS((buyer)(seller)(borrower)(borrower2)(settler)(feeder)); const asset_object& core = asset_id_type()(db); @@ -246,11 +255,11 @@ BOOST_AUTO_TEST_CASE( black_swan_issue_346 ) force_settle( settler, bitusd.amount(100) ); // wait for forced settlement to execute - // this would throw on Sep.18 testnet, see #346 + // this would throw on Sep.18 testnet, see #346 (https://github.com/cryptonomex/graphene/issues/346) wait_for_settlement(); } - // issue 350 + // issue 350 (https://github.com/cryptonomex/graphene/issues/350) { // ok, new asset const asset_object& bitusd = setup_asset(); @@ -282,7 +291,10 @@ BOOST_AUTO_TEST_CASE( revive_recovered ) { try { init_standard_swan( 700 ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); // revive after price recovers set_feed( 700, 800 ); @@ -304,7 +316,10 @@ BOOST_AUTO_TEST_CASE( recollateralize ) // no hardfork yet GRAPHENE_REQUIRE_THROW( bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ), fc::exception ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); int64_t b2_balance = get_balance( borrower2(), back() ); bid_collateral( borrower2(), back().amount(1000), swan().amount(100) ); @@ -397,7 +412,10 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) { try { limit_order_id_type oid = init_standard_swan( 1000 ); - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); set_expiration( db, trx ); cancel_limit_order( oid(db) ); @@ -424,7 +442,10 @@ BOOST_AUTO_TEST_CASE( revive_empty_recovered ) */ BOOST_AUTO_TEST_CASE( revive_empty ) { try { - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); limit_order_id_type oid = init_standard_swan( 1000 ); @@ -448,7 +469,10 @@ BOOST_AUTO_TEST_CASE( revive_empty ) */ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) { try { - wait_for_hf_core_216(); + if(hf1270) + wait_for_hf_core_1270(); + else + wait_for_hf_core_216(); standard_users(); standard_asset(); @@ -493,6 +517,50 @@ BOOST_AUTO_TEST_CASE( revive_empty_with_bid ) } } +BOOST_AUTO_TEST_CASE(black_swan_after_hf1270) +{ try { + hf1270 = true; + INVOKE(black_swan); + +} FC_LOG_AND_RETHROW() } + +// black_swan_issue_346_hf1270 is skipped as it is already failing with HARDFORK_CORE_834_TIME + +BOOST_AUTO_TEST_CASE(revive_recovered_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(recollateralize_hf1270) +{ try { + hf1270 = true; + INVOKE(recollateralize); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_recovered_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty_recovered); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(revive_empty_with_bid_hf1270) +{ try { + hf1270 = true; + INVOKE(revive_empty_with_bid); + +} FC_LOG_AND_RETHROW() } + /** Creates a black swan, bids on more than outstanding debt */ BOOST_AUTO_TEST_CASE( overflow ) 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;