diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9a99dbdd97..9194a02d25 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -730,7 +730,8 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse }); if( to_check_call_orders ) - db_conn.check_call_orders( asset_being_updated ); + // Process margin calls, allow black swan, not for a new limit order + db_conn.check_call_orders( asset_being_updated, true, false, bitasset_to_update ); return void_result(); @@ -741,9 +742,8 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat { try { database& d = db(); - FC_ASSERT( o.new_feed_producers.size() <= d.get_global_properties().parameters.maximum_asset_feed_publishers ); - for( auto id : o.new_feed_producers ) - d.get_object(id); + FC_ASSERT( o.new_feed_producers.size() <= d.get_global_properties().parameters.maximum_asset_feed_publishers, + "Cannot specify more feed producers than maximum allowed" ); const asset_object& a = o.asset_to_update(d); @@ -751,18 +751,33 @@ void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_updat FC_ASSERT(!(a.options.flags & committee_fed_asset), "Cannot set feed producers on a committee-fed asset."); FC_ASSERT(!(a.options.flags & witness_fed_asset), "Cannot set feed producers on a witness-fed asset."); - const asset_bitasset_data_object& b = a.bitasset_data(d); - bitasset_to_update = &b; - FC_ASSERT( a.issuer == o.issuer ); + FC_ASSERT( a.issuer == o.issuer, "Only asset issuer can update feed producers of an asset" ); + + asset_to_update = &a; + + // Make sure all producers exist. Check these after asset because account lookup is more expensive + for( auto id : o.new_feed_producers ) + d.get_object(id); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_feed_producers_evaluator::operation_type& o) { try { - db().modify(*bitasset_to_update, [&](asset_bitasset_data_object& a) { + database& d = db(); + const auto head_time = d.head_block_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) { //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. + + // TODO possible performance optimization: + // Since both the map and the set are ordered by account already, we can iterate through them only once + // and avoid lookups while iterating by maintaining two iterators at same time. + // However, this operation is not used much, and both the set and the map are small, + // so likely we won't gain much with the optimization. + //First, remove any old publishers who are no longer publishers for( auto itr = a.feeds.begin(); itr != a.feeds.end(); ) { @@ -772,12 +787,14 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f ++itr; } //Now, add any new publishers - for( auto itr = o.new_feed_producers.begin(); itr != o.new_feed_producers.end(); ++itr ) - if( !a.feeds.count(*itr) ) - a.feeds[*itr]; - a.update_median_feeds(db().head_block_time()); + for( const account_id_type acc : o.new_feed_producers ) + { + a.feeds[acc]; + } + a.update_median_feeds( head_time ); }); - db().check_call_orders( o.asset_to_update(db()) ); + // Process margin calls, allow black swan, not for a new limit order + d.check_call_orders( *asset_to_update, true, false, &bitasset_to_update ); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } @@ -786,20 +803,19 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle { try { const database& d = db(); asset_to_settle = &op.asset_to_settle(d); - FC_ASSERT(asset_to_settle->is_market_issued()); - FC_ASSERT(asset_to_settle->can_global_settle()); - FC_ASSERT(asset_to_settle->issuer == op.issuer ); - FC_ASSERT(asset_to_settle->dynamic_data(d).current_supply > 0); + FC_ASSERT( asset_to_settle->is_market_issued(), "Can only globally settle market-issued assets" ); + FC_ASSERT( asset_to_settle->can_global_settle(), "The global_settle permission of this asset is disabled" ); + FC_ASSERT( asset_to_settle->issuer == op.issuer, "Only asset issuer can globally settle an asset" ); + FC_ASSERT( asset_to_settle->dynamic_data(d).current_supply > 0, "Can not globally settle an asset with zero supply" ); const asset_bitasset_data_object& _bitasset_data = asset_to_settle->bitasset_data(d); // if there is a settlement for this asset, then no further global settle may be taken FC_ASSERT( !_bitasset_data.has_settlement(), "This asset has settlement, cannot global settle again" ); const auto& idx = d.get_index_type().indices().get(); - assert( !idx.empty() ); - auto itr = idx.lower_bound(boost::make_tuple(price::min(asset_to_settle->bitasset_data(d).options.short_backing_asset, - op.asset_to_settle))); - assert( itr != idx.end() && itr->debt_type() == op.asset_to_settle ); + FC_ASSERT( !idx.empty(), "Internal error: no debt position found" ); + auto itr = idx.lower_bound( price::min( _bitasset_data.options.short_backing_asset, op.asset_to_settle ) ); + FC_ASSERT( itr != idx.end() && itr->debt_type() == op.asset_to_settle, "Internal error: no debt position found" ); const call_order_object& least_collateralized_short = *itr; FC_ASSERT(least_collateralized_short.get_debt() * op.settle_price <= least_collateralized_short.get_collateral(), "Cannot force settle at supplied price: least collateralized short lacks sufficient collateral to settle."); @@ -810,7 +826,7 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle void_result asset_global_settle_evaluator::do_apply(const asset_global_settle_evaluator::operation_type& op) { try { database& d = db(); - d.globally_settle_asset( op.asset_to_settle(db()), op.settle_price ); + d.globally_settle_asset( *asset_to_settle, op.settle_price ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -897,7 +913,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ const asset_object& base = o.asset_id(d); //Verify that this feed is for a market-issued asset and that asset is backed by the base - FC_ASSERT(base.is_market_issued()); + FC_ASSERT( base.is_market_issued(), "Can only publish price feeds for market-issued assets" ); const asset_bitasset_data_object& bitasset = base.bitasset_data(d); if( bitasset.is_prediction_market || d.head_block_time() <= HARDFORK_CORE_216_TIME ) @@ -906,37 +922,46 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ } // the settlement price must be quoted in terms of the backing asset - FC_ASSERT( o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset ); + FC_ASSERT( o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset, + "Quote asset type in settlement price should be same as backing asset of this asset" ); if( d.head_block_time() > HARDFORK_480_TIME ) { if( !o.feed.core_exchange_rate.is_null() ) { - FC_ASSERT( o.feed.core_exchange_rate.quote.asset_id == asset_id_type() ); + FC_ASSERT( o.feed.core_exchange_rate.quote.asset_id == asset_id_type(), + "Quote asset in core exchange rate should be CORE asset" ); } } else { if( (!o.feed.settlement_price.is_null()) && (!o.feed.core_exchange_rate.is_null()) ) { - FC_ASSERT( o.feed.settlement_price.quote.asset_id == o.feed.core_exchange_rate.quote.asset_id ); + // Old buggy code, but we have to live with it + FC_ASSERT( o.feed.settlement_price.quote.asset_id == o.feed.core_exchange_rate.quote.asset_id, "Bad feed" ); } } //Verify that the publisher is authoritative to publish a feed if( base.options.flags & witness_fed_asset ) { - FC_ASSERT( d.get(GRAPHENE_WITNESS_ACCOUNT).active.account_auths.count(o.publisher) ); + FC_ASSERT( d.get(GRAPHENE_WITNESS_ACCOUNT).active.account_auths.count(o.publisher), + "Only active witnesses are allowed to publish price feeds for this asset" ); } else if( base.options.flags & committee_fed_asset ) { - FC_ASSERT( d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher) ); + FC_ASSERT( d.get(GRAPHENE_COMMITTEE_ACCOUNT).active.account_auths.count(o.publisher), + "Only active committee members are allowed to publish price feeds for this asset" ); } else { - FC_ASSERT(bitasset.feeds.count(o.publisher)); + FC_ASSERT( bitasset.feeds.count(o.publisher), + "The account is not in the set of allowed price feed producers of this asset" ); } + asset_ptr = &base; + bitasset_ptr = &bitasset; + return void_result(); } FC_CAPTURE_AND_RETHROW((o)) } @@ -945,8 +970,8 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope database& d = db(); - const asset_object& base = o.asset_id(d); - const asset_bitasset_data_object& bad = base.bitasset_data(d); + 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 @@ -967,7 +992,8 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope bad.current_feed.maintenance_collateral_ratio ) < bad.current_feed.settlement_price ) ) d.revive_bitasset(base); } - db().check_call_orders(base); + // Process margin calls, allow black swan, not for a new limit order + d.check_call_orders( base, true, false, bitasset_ptr ); } return void_result(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index beb010641c..6b8f67ea1c 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -42,12 +42,6 @@ namespace graphene { namespace chain { */ void database::globally_settle_asset( const asset_object& mia, const price& settlement_price ) { try { - /* - elog( "BLACK SWAN!" ); - debug_dump(); - edump( (mia.symbol)(settlement_price) ); - */ - const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); @@ -137,7 +131,9 @@ void database::_cancel_bids_and_revive_mpa( const asset_object& bitasset, const // cancel remaining bids const auto& bid_idx = get_index_type< collateral_bid_index >().indices().get(); - auto itr = bid_idx.lower_bound( boost::make_tuple( bitasset.id, price::max( bad.options.short_backing_asset, bitasset.id ), collateral_bid_id_type() ) ); + auto itr = bid_idx.lower_bound( boost::make_tuple( bitasset.id, + price::max( bad.options.short_backing_asset, bitasset.id ), + collateral_bid_id_type() ) ); while( itr != bid_idx.end() && itr->inv_swan_price.quote.asset_id == bitasset.id ) { const collateral_bid_object& bid = *itr; @@ -167,7 +163,8 @@ void database::cancel_bid(const collateral_bid_object& bid, bool create_virtual_ remove(bid); } -void database::execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, const price_feed& current_feed ) +void database::execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, + const price_feed& current_feed ) { const call_order_object& call_obj = create( [&](call_order_object& call ){ call.borrower = bid.bidder; @@ -178,8 +175,9 @@ void database::execute_bid( const collateral_bid_object& bid, share_type debt_co current_feed.maintenance_collateral_ratio); }); + // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders if( bid.inv_swan_price.base.asset_id == asset_id_type() ) - modify(bid.bidder(*this).statistics(*this), [&](account_statistics_object& stats) { + modify( get_account_stats_by_owner(bid.bidder), [&](account_statistics_object& stats) { stats.total_core_in_orders += call_obj.collateral; }); @@ -822,11 +820,11 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p bool database::fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, const price& fill_price, const bool is_maker ) { try { - //idump((pays)(receives)(order)); - FC_ASSERT( order.get_debt().asset_id == receives.asset_id ); - FC_ASSERT( order.get_collateral().asset_id == pays.asset_id ); - FC_ASSERT( order.get_collateral() >= pays ); + FC_ASSERT( order.debt_type() == receives.asset_id ); + FC_ASSERT( order.collateral_type() == pays.asset_id ); + FC_ASSERT( order.collateral >= pays.amount ); + // TODO pass in mia and bitasset_data for better performance const asset_object& mia = receives.asset_id(*this); FC_ASSERT( mia.is_market_issued() ); @@ -842,33 +840,29 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay 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 ); - }); + }); + // update current supply const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); - modify( mia_ddo, [&]( asset_dynamic_data_object& ao ){ - //idump((receives)); - ao.current_supply -= receives.amount; + modify( mia_ddo, [&receives]( asset_dynamic_data_object& ao ){ + ao.current_supply -= receives.amount; }); - const account_object& borrower = order.borrower(*this); - if( collateral_freed.valid() || pays.asset_id == asset_id_type() ) - { - const account_statistics_object& borrower_statistics = borrower.statistics(*this); - if( collateral_freed.valid() ) - adjust_balance(borrower.get_id(), *collateral_freed); - - modify( borrower_statistics, [&]( account_statistics_object& b ){ - if( collateral_freed.valid() && collateral_freed->amount > 0 && collateral_freed->asset_id == asset_id_type() ) - b.total_core_in_orders -= collateral_freed->amount; - if( pays.asset_id == asset_id_type() ) - b.total_core_in_orders -= pays.amount; + // Adjust balance + if( collateral_freed.valid() ) + adjust_balance( order.borrower, *collateral_freed ); - assert( b.total_core_in_orders >= 0 ); - }); + // Update account statistics. We know that order.collateral_type() == pays.asset_id + if( pays.asset_id == asset_id_type() ) + { + modify( get_account_stats_by_owner(order.borrower), [&collateral_freed,&pays]( account_statistics_object& b ){ + b.total_core_in_orders -= pays.amount; + if( collateral_freed.valid() ) + b.total_core_in_orders -= collateral_freed->amount; + }); } - assert( pays.asset_id != receives.asset_id ); push_applied_operation( fill_order_operation( order.id, order.borrower, pays, receives, asset(0, pays.asset_id), fill_price, is_maker ) ); @@ -915,6 +909,7 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a * @param enable_black_swan - when adjusting collateral, triggering a black swan is invalid and will throw * if enable_black_swan is not set to true. * @param for_new_limit_order - true if this function is called when matching call orders with a new limit order + * @param bitasset_ptr - an optional pointer to the bitasset_data object of the asset * * @return true if a margin call was executed. */ @@ -936,9 +931,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( bitasset.is_prediction_market ) return false; if( bitasset.current_feed.settlement_price.is_null() ) return false; - const call_order_index& call_index = get_index_type(); - const auto& call_price_index = call_index.indices().get(); - const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -955,6 +947,9 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( limit_itr == limit_end ) return false; + const call_order_index& call_index = get_index_type(); + const auto& call_price_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 ); @@ -976,34 +971,30 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option - while( !check_for_blackswan( mia, enable_black_swan, &bitasset ) && call_itr != call_end ) + 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 ) { bool filled_call = false; - price match_price; - asset usd_for_sale; - if( limit_itr != limit_end ) - { - assert( limit_itr != limit_price_index.end() ); - match_price = limit_itr->sell_price; - usd_for_sale = limit_itr->amount_for_sale(); - } - else return margin_called; - match_price.validate(); + const call_order_object& call_order = *call_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_itr->call_price ) ) + if( after_hardfork_436 && bitasset.current_feed.settlement_price > ~call_order.call_price ) return margin_called; + const limit_order_object& limit_order = *limit_itr; + price match_price = limit_order.sell_price; + // There was a check `match_price.validate();` here, which is removed now because it always passes + // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 - if( before_core_hardfork_606 && match_price > ~call_itr->call_price ) + if( before_core_hardfork_606 && match_price > ~call_order.call_price ) return margin_called; margin_called = true; - auto usd_to_buy = call_itr->get_debt(); - - if( usd_to_buy * match_price > call_itr->get_collateral() ) + auto usd_to_buy = call_order.get_debt(); + if( usd_to_buy * match_price > call_order.get_collateral() ) { elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", ("id",mia.id)("symbol",mia.symbol)("b",head_num) ); @@ -1014,10 +1005,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa } if( !before_core_hardfork_834 ) - usd_to_buy.amount = call_itr->get_max_debt_to_cover( match_price, + 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; if( usd_to_buy > usd_for_sale ) { // fill order @@ -1078,17 +1070,16 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa call_pays = order_receives; order_pays = call_receives; - auto old_call_itr = call_itr; if( filled_call && before_core_hardfork_343 ) ++call_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_call_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); + 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 ); 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 - bool really_filled = fill_limit_order( *limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); + bool really_filled = fill_limit_order( limit_order, order_pays, order_receives, true, match_price, !for_new_limit_order ); if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) limit_itr = next_limit_itr; diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 7ab6cfdab4..e2573356b0 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -112,7 +112,7 @@ namespace graphene { namespace chain { void_result do_evaluate( const operation_type& o ); void_result do_apply( const operation_type& o ); - const asset_bitasset_data_object* bitasset_to_update = nullptr; + const asset_object* asset_to_update = nullptr; }; class asset_fund_fee_pool_evaluator : public evaluator @@ -155,7 +155,8 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_publish_feed_operation& o ); void_result do_apply( const asset_publish_feed_operation& o ); - std::map,price_feed> median_feed_values; + const asset_object* asset_ptr = nullptr; + const asset_bitasset_data_object* bitasset_ptr = nullptr; }; class asset_claim_fees_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/protocol/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp index 2bab4e7581..86d17892ff 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -114,7 +114,7 @@ namespace graphene { namespace chain { */ struct price { - explicit price(const asset& _base = asset(), const asset _quote = asset()) + explicit price(const asset& _base = asset(), const asset& _quote = asset()) : base(_base),quote(_quote){} asset base; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 56c9db82e8..ed7c55351b 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -171,21 +171,19 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope /// if there is a settlement for this asset, then no further margin positions may be taken and /// all existing margin positions should have been closed va database::globally_settle_asset - FC_ASSERT( !_bitasset_data->has_settlement() ); + FC_ASSERT( !_bitasset_data->has_settlement(), "Cannot update debt position when the asset has been globally settled" ); - FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset ); + FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset, + "Collateral asset type should be same as backing asset of debt asset" ); if( _bitasset_data->is_prediction_market ) - FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount ); + FC_ASSERT( o.delta_collateral.amount == o.delta_debt.amount, + "Debt amount and collateral amount should be same when updating debt position in a prediction market" ); else if( _bitasset_data->current_feed.settlement_price.is_null() ) FC_THROW_EXCEPTION(insufficient_feeds, "Cannot borrow asset with no price feed."); - if( o.delta_collateral.amount > 0 ) - { - FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.delta_collateral, - "Cannot increase collateral by ${c} when payer only has ${b}", ("c", o.delta_collateral.amount) - ("b", d.get_balance(*_paying_account, o.delta_collateral.asset_id(d)).amount) ); - } + // Note: there was code here checking whether the account has enough balance to increase delta collateral, + // which is now removed since the check is implicitly done later by `adjust_balance()` in `do_apply()`. return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } @@ -197,7 +195,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat if( o.delta_debt.amount != 0 ) { - d.adjust_balance( o.funding_account, o.delta_debt ); + 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) { @@ -219,7 +217,6 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat } } - 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; @@ -227,53 +224,47 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat optional old_collateralization; optional old_debt; - optional new_target_cr = o.extensions.value.target_collateral_ratio; - - if( itr == call_idx.end() ) + if( itr == call_idx.end() ) // creating new debt position { - FC_ASSERT( o.delta_collateral.amount > 0 ); - FC_ASSERT( o.delta_debt.amount > 0 ); + 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( [&](call_order_object& call ){ + call_obj = &d.create( [&o,this]( 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); - call.target_collateral_ratio = new_target_cr; + call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; }); } - else + else // updating existing debt position { call_obj = &*itr; - old_collateralization = call_obj->collateralization(); - old_debt = call_obj->debt; auto new_collateral = call_obj->collateral + o.delta_collateral.amount; - auto new_debt = *old_debt + o.delta_debt.amount; + auto new_debt = call_obj->debt + o.delta_debt.amount; - // Forbid zero collateral with nonzero debt (avoids divide by zero when calculating call price below) - FC_ASSERT(!(new_collateral == 0 && new_debt != 0), "Cannot have zero collateral and nonzero debt"); + if( new_debt == 0 ) + { + FC_ASSERT( new_collateral == 0, "Should claim all collateral when closing debt position" ); + d.remove( *call_obj ); + return void_result(); + } - d.modify( *call_obj, [&]( call_order_object& call ){ - call.collateral += o.delta_collateral.amount; - call.debt += o.delta_debt.amount; - if( call.debt > 0 ) - { - call.call_price = price::call_price(call.get_debt(), call.get_collateral(), - _bitasset_data->current_feed.maintenance_collateral_ratio); - } - call.target_collateral_ratio = new_target_cr; - }); - } + FC_ASSERT( new_collateral > 0 && new_debt > 0, + "Both collateral and debt should be positive after updated a debt position if not to close it" ); - if( call_obj->debt == 0 ) - { - FC_ASSERT( call_obj->collateral == 0 ); - d.remove( *call_obj ); - return void_result(); - } + old_collateralization = call_obj->collateralization(); + old_debt = call_obj->debt; - FC_ASSERT(call_obj->collateral > 0 && call_obj->debt > 0); + d.modify( *call_obj, [&o,new_debt,new_collateral,this]( 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 ); + call.target_collateral_ratio = o.extensions.value.target_collateral_ratio; + }); + } // then we must check for margin calls and other issues if( !_bitasset_data->is_prediction_market ) @@ -282,11 +273,11 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat // check to see if the order needs to be margin called now, but don't allow black swans and require there to be // limit orders available that could be used to fill the order. - // Note: due to https://github.com/bitshares/bitshares-core/issues/649, + // Note: due to https://github.com/bitshares/bitshares-core/issues/649, before core-343 hard fork, // the first call order may be unable to be updated if the second one is undercollateralized. - if( d.check_call_orders( *_debt_asset, false ) ) + if( d.check_call_orders( *_debt_asset, false, false, _bitasset_data ) ) // don't allow black swan, not for new limit order { - const auto call_obj = d.find(call_order_id); + call_obj = d.find(call_order_id); // before hard fork core-583: if we filled at least one call order, we are OK if we totally filled. // after hard fork core-583: we want to allow increasing collateral // Note: increasing collateral won't get the call order itself matched (instantly margin called) @@ -301,7 +292,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat } else { - const auto call_obj = d.find(call_order_id); + call_obj = d.find(call_order_id); // we know no black swan event has occurred FC_ASSERT( call_obj, "no margin call was executed and yet the call object was deleted" ); if( d.head_block_time() <= HARDFORK_CORE_583_TIME ) // TODO remove after hard fork core-583 @@ -327,7 +318,8 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat 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, - "Can only increase collateral ratio without increasing debt if would trigger a margin call that cannot be fully filled", + "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) @@ -396,6 +388,8 @@ void_result bid_collateral_evaluator::do_apply(const bid_collateral_operation& o bid.inv_swan_price = o.additional_collateral / o.debt_covered; }); + // Note: CORE asset in collateral_bid_object is not counted in account_stats.total_core_in_orders + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index fa0b523e9d..531ea7f6f6 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -115,17 +115,20 @@ namespace graphene { namespace chain { auto ocp = cp; bool shrinked = false; + bool using_max = false; static const int128_t max( GRAPHENE_MAX_SHARE_SUPPLY ); while( cp.numerator() > max || cp.denominator() > max ) { if( cp.numerator() == 1 ) { cp = boost::rational( 1, max ); + using_max = true; break; } else if( cp.denominator() == 1 ) { cp = boost::rational( max, 1 ); + using_max = true; break; } else @@ -168,10 +171,13 @@ namespace graphene { namespace chain { price np = asset( cp.numerator().convert_to(), p.base.asset_id ) / asset( cp.denominator().convert_to(), p.quote.asset_id ); - if( ( r.numerator() > r.denominator() && np < p ) - || ( r.numerator() < r.denominator() && np > p ) ) - // even with an accurate result, if p is out of valid range, return it - np = p; + if( shrinked || using_max ) + { + if( ( r.numerator() > r.denominator() && np < p ) + || ( r.numerator() < r.denominator() && np > p ) ) + // even with an accurate result, if p is out of valid range, return it + np = p; + } np.validate(); return np; @@ -201,7 +207,6 @@ namespace graphene { namespace chain { 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 - //wdump((debt)(collateral)(collateral_ratio)); boost::rational swan(debt.amount.value,collateral.amount.value); boost::rational ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); auto cp = swan * ratio; @@ -209,7 +214,8 @@ namespace graphene { namespace chain { while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY ) cp = boost::rational( (cp.numerator() >> 1)+1, (cp.denominator() >> 1)+1 ); - return ~(asset( cp.numerator().convert_to(), debt.asset_id ) / asset( cp.denominator().convert_to(), collateral.asset_id )); + return ( asset( cp.denominator().convert_to(), collateral.asset_id ) + / asset( cp.numerator().convert_to(), debt.asset_id ) ); } FC_CAPTURE_AND_RETHROW( (debt)(collateral)(collateral_ratio) ) } bool price::is_null() const