diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index f067a101e8..c710495a7e 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -1174,13 +1174,13 @@ vector database_api::get_call_orders(asset_id_type a, uint32_ vector database_api_impl::get_call_orders(asset_id_type a, uint32_t limit)const { - const auto& call_index = _db.get_index_type().indices().get(); + const auto& call_index = _db.get_index_type().indices().get(); const asset_object& mia = _db.get(a); - price index_price = price::min(mia.bitasset_data(_db).options.short_backing_asset, mia.get_id()); + price index_price = price::min( mia.bitasset_data(_db).options.short_backing_asset, a ); 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); diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index f6d0f23062..d809983283 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -656,7 +656,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 @@ -707,7 +707,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 @@ -784,8 +784,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. @@ -809,7 +810,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 ); @@ -987,27 +988,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_maint.cpp b/libraries/chain/db_maint.cpp index 84e0b990bb..bd7aa1ff2e 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() @@ -946,6 +971,22 @@ void process_hf_1465( database& db ) } } +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 * @@ -967,6 +1008,7 @@ void process_hf_1465( database& db ) // * 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) ); @@ -1026,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 ); @@ -1238,20 +1280,25 @@ 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); @@ -1261,9 +1308,20 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g 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 8feae4e0ef..3d42f9abc9 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -62,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 @@ -94,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; }); @@ -104,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; }); @@ -181,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 @@ -438,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; @@ -450,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; } @@ -468,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(); @@ -485,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 ) @@ -591,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() ); @@ -617,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; @@ -851,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 @@ -948,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 ); @@ -963,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; @@ -986,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; @@ -1018,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_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 ); + } asset usd_for_sale = limit_order.amount_for_sale(); asset call_pays, call_receives, order_pays, order_receives; @@ -1085,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 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_1270.hf b/libraries/chain/hardfork.d/CORE_1270.hf new file mode 100644 index 0000000000..6894635f4e --- /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( 1580000000 )) // a temporary date in the future +#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/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 9bc3c67d3a..1537c1f122 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -362,8 +362,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. @@ -378,14 +380,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. @@ -406,6 +411,7 @@ namespace graphene { namespace chain { asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount); asset pay_market_fees( const asset_object& recv_asset, const asset& receives ); asset pay_market_fees( const account_object& seller, const asset_object& recv_asset, const asset& receives ); + ///@} ///@{ diff --git a/libraries/chain/include/graphene/chain/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/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index df3c6cb53e..f14119410d 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -226,6 +226,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; @@ -239,12 +242,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; @@ -269,11 +275,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; }); } @@ -296,8 +305,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 @@ -311,9 +319,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) ); } @@ -325,13 +335,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/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/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index fee840cad6..42046731b0 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -192,6 +192,7 @@ struct database_fixture { optional data_dir; bool skip_key_index_test = false; uint32_t anon_acct_count; + bool hf1270 = false; database_fixture(const fc::time_point_sec &initial_timestamp = fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP)); diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 4fc6097650..f4b9c334c5 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,10 @@ 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 ) + // need additional block to avoid prev: popping block would leave head block null error + generate_block( skip ); + + for( int i = 0; i < 8; ++i ) { idump( (i) ); int blocks = 0; @@ -939,6 +946,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 +1061,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 +1114,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 +1404,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/market_tests.cpp b/tests/tests/market_tests.cpp index 1f29f0c843..413ffd42f9 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,354 @@ 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("BTS"); + 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 + BOOST_CHECK_EQUAL((*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio, 1750); + BOOST_CHECK_EQUAL((*bitusd_id(db).bitasset_data_id)(db).feed_is_expired(db.head_block_time()), false); // should be true? + + // 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 ); + + BOOST_CHECK_EQUAL((*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio, 2000); + BOOST_CHECK_EQUAL((*bitusd_id(db).bitasset_data_id)(db).feed_is_expired(db.head_block_time()), false); + + // pass hardfork + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + + // feed is still valid + BOOST_CHECK_EQUAL((*bitusd_id(db).bitasset_data_id)(db).current_feed.maintenance_collateral_ratio, 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/swan_tests.cpp b/tests/tests/swan_tests.cpp index 5b9b5c3f20..7f68f4cf89 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) ); @@ -396,7 +411,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) ); @@ -423,7 +441,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 ); @@ -447,7 +468,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(); @@ -491,6 +515,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 )