fixes bts mid 2018

This commit is contained in:
sierra19XX 2021-03-12 12:25:46 +11:00
parent 4d8f3725b0
commit 463c812a33
9 changed files with 588 additions and 173 deletions

View file

@ -685,7 +685,7 @@ void_result asset_global_settle_evaluator::do_evaluate(const asset_global_settle
FC_ASSERT(asset_to_settle->dynamic_data(d).current_supply > 0);
const asset_bitasset_data_object* bitasset_data = &asset_to_settle->bitasset_data(d);
if( bitasset.is_prediction_market ) {
if( bitasset_data.is_prediction_market ) {
/// if there is a settlement for this asset, then no further global settle may be taken and
FC_ASSERT( !bitasset_data->has_settlement(),"This asset has settlement, cannot global settle twice" );
}

View file

@ -61,24 +61,28 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
const call_order_index& call_index = get_index_type<call_order_index>();
const auto& call_price_index = call_index.indices().get<by_price>();
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
// cancel all call orders and accumulate it into collateral_gathered
auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
asset pays;
while( call_itr != call_end )
{
auto pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order
if( before_core_hardfork_342 )
{
pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order
// Be here, the call order can be paying nothing
if( pays.amount == 0 && !bitasset.is_prediction_market ) // TODO remove this warning after hard fork core-342
wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) );
}
else
pays = call_itr->get_debt() ^ settlement_price; // round up, in favor of global settlement fund
if( pays > call_itr->get_collateral() )
pays = call_itr->get_collateral();
// Be here, the call order can be paying nothing, in this case, we take at least 1 Satoshi from call order
if( pays.amount == 0 && !bitasset.is_prediction_market )
{
if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME )
pays.amount = 1;
else // TODO remove this warning after hard fork core-184
wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) );
}
collateral_gathered += pays;
const auto& order = *call_itr;
@ -382,16 +386,23 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo
// check if there are margin calls
const auto& call_price_idx = get_index_type<call_order_index>().indices().get<by_price>();
auto call_min = price::min( recv_asset_id, sell_asset_id );
auto call_itr = call_price_idx.lower_bound( call_min );
// feed protected https://github.com/cryptonomex/graphene/issues/436
auto call_end = call_price_idx.upper_bound( ~sell_abd->current_feed.settlement_price );
while( !finished && call_itr != call_end )
while( !finished )
{
auto old_call_itr = call_itr;
++call_itr; // would be safe, since we'll end the loop if a call order is partially matched
// match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop.
// assume hard fork core-343 and core-625 will take place at same time, always check call order with least call_price
auto call_itr = call_price_idx.lower_bound( call_min );
if( call_itr == call_price_idx.end()
|| call_itr->debt_type() != sell_asset_id
// feed protected https://github.com/cryptonomex/graphene/issues/436
|| call_itr->call_price > ~sell_abd->current_feed.settlement_price )
break;
// assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here.
finished = ( match( new_order_object, *old_call_itr, call_match_price ) != 2 );
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 );
// 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;
}
}
}
@ -428,19 +439,39 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo
*/
int database::match( const limit_order_object& usd, const limit_order_object& core, const price& match_price )
{
assert( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id );
assert( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id );
assert( usd.for_sale > 0 && core.for_sale > 0 );
FC_ASSERT( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id );
FC_ASSERT( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id );
FC_ASSERT( usd.for_sale > 0 && core.for_sale > 0 );
auto usd_for_sale = usd.amount_for_sale();
auto core_for_sale = core.amount_for_sale();
asset usd_pays, usd_receives, core_pays, core_receives;
if( usd_for_sale <= core_for_sale * match_price )
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
bool cull_taker = false;
if( usd_for_sale <= core_for_sale * match_price ) // rounding down here should be fine
{
core_receives = usd_for_sale;
usd_receives = usd_for_sale * match_price; // round down, in favor of bigger order
// Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.
// In this case, we see it as filled and cancel it later
if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME )
return 1;
if( before_core_hardfork_342 )
core_receives = usd_for_sale;
else
{
// The remaining amount in order `usd` would be too small,
// so we should cull the order in fill_limit_order() below.
// The order would receive 0 even at `match_price`, so it would receive 0 at its own price,
// so calling maybe_cull_small() will always cull it.
core_receives = usd_receives ^ match_price;
cull_taker = true;
}
}
else
{
@ -448,85 +479,102 @@ int database::match( const limit_order_object& usd, const limit_order_object& co
//This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp
//Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price
//Removing the assert seems to be safe -- apparently no asset is created or destroyed.
usd_receives = core_for_sale;
// The maker won't be paying something for nothing, since if it would, it would have been cancelled already.
core_receives = core_for_sale * match_price; // round down, in favor of bigger order
if( before_core_hardfork_342 )
usd_receives = core_for_sale;
else
// The remaining amount in order `core` would be too small,
// so the order will be culled in fill_limit_order() below
usd_receives = core_receives ^ match_price;
}
core_pays = usd_receives;
usd_pays = core_receives;
assert( usd_pays == usd.amount_for_sale() ||
core_pays == core.amount_for_sale() );
// Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.
// In this case, we see it as filled and cancel it later
// The maker won't be paying something for nothing, since if it would, it would have been cancelled already.
if( usd_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME )
return 1;
if( before_core_hardfork_342 )
FC_ASSERT( usd_pays == usd.amount_for_sale() ||
core_pays == core.amount_for_sale() );
int result = 0;
result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // the first param is taker
result |= fill_limit_order( usd, usd_pays, usd_receives, cull_taker, match_price, false ); // the first param is taker
result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker
assert( result != 0 );
FC_ASSERT( result != 0 );
return result;
}
int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price )
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 )
{
FC_ASSERT( bid.sell_asset_id() == ask.debt_type() );
FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() );
FC_ASSERT( bid.for_sale > 0 && ask.debt > 0 && ask.collateral > 0 );
bool filled_limit = false;
bool filled_call = false;
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
// TODO remove when we're sure it's always false
bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
if( before_core_hardfork_184 )
ilog( "match(limit,call) is called before hardfork core-184 at block #${block}", ("block",head_block_num()) );
if( before_core_hardfork_342 )
ilog( "match(limit,call) is called before hardfork core-342 at block #${block}", ("block",head_block_num()) );
// TODO remove when we're sure it's always false
bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option
bool cull_taker = false;
asset usd_for_sale = bid.amount_for_sale();
asset usd_to_buy = ask.get_debt();
// 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 ),
ask.debt_type() ) );
asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy >= usd_for_sale )
if( usd_to_buy > usd_for_sale )
{ // fill limit order
call_receives = usd_for_sale;
order_receives = usd_for_sale * match_price; // round down here, in favor of call order
call_pays = order_receives;
order_pays = usd_for_sale;
filled_limit = true;
filled_call = ( usd_to_buy == usd_for_sale );
// Be here, it's possible that taker is paying something for nothing due to partially filled in last loop.
// In this case, we see it as filled and cancel it later
// TODO remove hardfork check when we're sure it's always true (but keep the zero amount check)
if( order_receives.amount == 0 && !before_core_hardfork_184 )
return 1;
if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else)
call_receives = usd_for_sale;
else
{
// The remaining amount in the limit order would be too small,
// so we should cull the order in fill_limit_order() below.
// The order would receive 0 even at `match_price`, so it would receive 0 at its own price,
// so calling maybe_cull_small() will always cull it.
call_receives = order_receives ^ match_price;
cull_taker = true;
}
}
else
{ // fill call order
call_receives = usd_to_buy;
order_receives = usd_to_buy * match_price; // round down here, in favor of call order
call_pays = order_receives;
order_pays = usd_to_buy;
filled_call = true;
}
FC_ASSERT( filled_call || filled_limit );
// Be here, it's possible that taker is paying something for nothing.
// The maker won't be paying something for nothing according to code above
if( order_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME )
{
// It's possible that taker is paying something for nothing due to call order too small.
// In this case, let the call pay 1 Satoshi
if( filled_call )
if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else)
{
order_receives.amount = 1;
call_pays = order_receives;
order_receives = usd_to_buy * match_price; // round down here, in favor of call order
// TODO remove hardfork check when we're sure it's always true (but keep the zero amount check)
if( order_receives.amount == 0 && !before_core_hardfork_184 )
return 1;
}
else
// It's possible that taker is paying something for nothing due to partially filled in last loop.
// In this case, we see it as filled and cancel it later
return 1;
else // has hardfork core-342
order_receives = usd_to_buy ^ match_price; // round up here, in favor of limit order
}
call_pays = order_receives;
order_pays = call_receives;
int result = 0;
result |= fill_limit_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker
result |= fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); // the limit order is taker
result |= fill_call_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker
FC_ASSERT( result != 0 );
// result can be 0 when call order has target_collateral_ratio option set.
return result;
}
@ -540,16 +588,21 @@ asset database::match( const call_order_object& call,
FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id );
FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
auto settle_for_sale = std::min(settle.balance, max_settlement);
auto call_debt = call.get_debt();
asset call_receives = std::min(settle_for_sale, call_debt);
asset call_pays = call_receives * match_price; // round down here, in favor of call order
asset call_pays = call_receives * match_price; // round down here, in favor of call order, for first check
// TODO possible optimization: check need to round up or down first
// Be here, the call order may be paying nothing.
bool cull_settle_order = false; // whether need to cancel dust settle order
if( call_pays.amount == 0 )
{
if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME )
if( maint_time > HARDFORK_CORE_184_TIME )
{
if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order
{
@ -563,14 +616,40 @@ asset database::match( const call_order_object& call,
wlog( "Something for nothing issue (#184, variant C-2) handled at block #${block}", ("block",head_block_num()) );
cancel_settle_order( settle );
}
else // neither order will be completely filled, perhaps due to max_settlement too small
wlog( "Something for nothing issue (#184, variant C-3) handled at block #${block}", ("block",head_block_num()) );
// else do nothing: neither order will be completely filled, perhaps due to max_settlement too small
return asset( 0, settle.balance.asset_id );
}
}
else
wlog( "Something for nothing issue (#184, variant C) occurred at block #${block}", ("block",head_block_num()) );
}
else // the call order is not paying nothing, but still possible it's paying more than minimum required due to rounding
{
if( !before_core_hardfork_342 )
{
if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order
{
call_pays = call_receives ^ match_price; // round up here, in favor of settle order
// be here, we should have: call_pays <= call_collateral
}
else
{
// be here, call_pays has been rounded down
// be here, we should have: call_pays <= call_collateral
if( call_receives == settle.balance ) // the settle order will be completely filled, assuming we need to cull it
cull_settle_order = true;
// else do nothing, since we can't cull the settle order
call_receives = call_pays ^ match_price; // round up here to mitigate rouding issue (core-342)
if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull
cull_settle_order = false;
// else do nothing, since we still need to cull the settle order or still can't cull the settle order
}
}
}
asset settle_pays = call_receives;
asset settle_receives = call_pays;
@ -582,13 +661,23 @@ asset database::match( const call_order_object& call,
* can trigger a black swan. So now we must cancel the forced settlement
* object.
*/
GRAPHENE_ASSERT( call_pays < call.get_collateral(), black_swan_exception, "" );
if( before_core_hardfork_342 )
{
auto call_collateral = call.get_collateral();
if( call_pays == call_collateral )
wlog( "Incorrectly captured black swan event at block #${block}", ("block",head_block_num()) );
GRAPHENE_ASSERT( call_pays < call_collateral, black_swan_exception, "" );
assert( settle_pays == settle_for_sale || call_receives == call.get_debt() );
assert( settle_pays == settle_for_sale || call_receives == call.get_debt() );
}
// else do nothing, since black swan event won't happen, and the assertion is no longer true
fill_call_order( call, call_pays, call_receives, fill_price, true ); // call order is maker
fill_settle_order( settle, settle_pays, settle_receives, fill_price, false ); // force settlement order is taker
if( cull_settle_order )
cancel_settle_order( settle );
return call_receives;
} FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) }
@ -678,14 +767,14 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
});
const account_object& borrower = order.borrower(*this);
if( collateral_freed || pays.asset_id == asset_id_type() )
if( collateral_freed.valid() || pays.asset_id == asset_id_type() )
{
const account_statistics_object& borrower_statistics = borrower.statistics(*this);
if( collateral_freed )
if( collateral_freed.valid() )
adjust_balance(borrower.get_id(), *collateral_freed);
modify( borrower_statistics, [&]( account_statistics_object& b ){
if( collateral_freed && collateral_freed->amount > 0 && collateral_freed->asset_id == asset_id_type())
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;
@ -698,7 +787,7 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
push_applied_operation( fill_order_operation( order.id, order.borrower, pays, receives,
asset(0, pays.asset_id), fill_price, is_maker ) );
if( collateral_freed )
if( collateral_freed.valid() )
remove( order );
return collateral_freed.valid();
@ -746,6 +835,11 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a
bool database::check_call_orders( const asset_object& mia, bool enable_black_swan, bool for_new_limit_order,
const asset_bitasset_data_object* bitasset_ptr )
{ try {
auto head_time = head_block_time();
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
if( for_new_limit_order )
FC_ASSERT( maint_time <= HARDFORK_CORE_625_TIME ); // `for_new_limit_order` is only true before HF 338 / 625
if( !mia.is_market_issued() ) return false;
const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) );
@ -780,19 +874,20 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
auto call_itr = call_price_index.lower_bound( call_min );
auto call_end = call_price_index.upper_bound( call_max );
bool filled_limit = false;
bool margin_called = false;
auto head_time = head_block_time();
auto head_num = head_block_num();
bool after_hardfork_436 = ( head_time > HARDFORK_436_TIME );
auto head_time = head_block_time();
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
bool before_core_hardfork_343 = ( maint_time <= HARDFORK_CORE_343_TIME ); // update call_price after partially filled
bool before_core_hardfork_453 = ( maint_time <= HARDFORK_CORE_453_TIME ); // multiple matching issue
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 )
{
bool filled_limit_in_loop = false;
bool filled_call = false;
price match_price;
asset usd_for_sale;
@ -807,12 +902,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
match_price.validate();
// Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436
if( ( head_time > HARDFORK_436_TIME )
&& ( bitasset.current_feed.settlement_price > ~call_itr->call_price ) )
if( after_hardfork_436 && ( bitasset.current_feed.settlement_price > ~call_itr->call_price ) )
return margin_called;
// Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606
if( maint_time <= HARDFORK_CORE_606_TIME && match_price > ~call_itr->call_price )
if( before_core_hardfork_606 && match_price > ~call_itr->call_price )
return margin_called;
margin_called = true;
@ -822,75 +916,82 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
if( usd_to_buy * match_price > call_itr->get_collateral() )
{
elog( "black swan detected on asset ${symbol} (${id}) at block ${b}",
("id",mia.id)("symbol",mia.symbol)("b",head_num) );
("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) );
edump((enable_black_swan));
FC_ASSERT( enable_black_swan );
globally_settle_asset(mia, bitasset.current_feed.settlement_price );
return true;
}
asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy >= usd_for_sale )
{ // fill order
call_receives = usd_for_sale;
order_receives = usd_for_sale * match_price; // round down, in favor of call order
call_pays = order_receives;
order_pays = usd_for_sale;
if( !before_core_hardfork_834 )
usd_to_buy.amount = call_itr->get_max_debt_to_cover( match_price,
bitasset.current_feed.settlement_price,
bitasset.current_feed.maintenance_collateral_ratio );
filled_limit_in_loop = true;
asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy > usd_for_sale )
{ // fill order
order_receives = usd_for_sale * match_price; // round down, in favor of call order
// Be here, the limit order won't be paying something for nothing, since if it would, it would have
// been cancelled elsewhere already (a maker limit order won't be paying something for nothing):
// * after hard fork core-625, the limit order will be always a maker if entered this function;
// * before hard fork core-625,
// * when the limit order is a taker, it could be paying something for nothing only when
// the call order is smaller and is too small
// * when the limit order is a maker, it won't be paying something for nothing
if( order_receives.amount == 0 ) // TODO this should not happen. remove the warning after confirmed
{
if( before_core_hardfork_184 )
wlog( "Something for nothing issue (#184, variant D-1) occurred at block #${block}", ("block",head_block_num()) );
else
wlog( "Something for nothing issue (#184, variant D-2) occurred at block #${block}", ("block",head_block_num()) );
}
if( before_core_hardfork_342 )
call_receives = usd_for_sale;
else
// The remaining amount in the limit order would be too small,
// so we should cull the order in fill_limit_order() below.
// The order would receive 0 even at `match_price`, so it would receive 0 at its own price,
// so calling maybe_cull_small() will always cull it.
call_receives = order_receives ^ match_price;
filled_limit = true;
filled_call = (usd_to_buy == usd_for_sale);
} else { // fill call
call_receives = usd_to_buy;
order_receives = usd_to_buy * match_price; // round down, in favor of call order
call_pays = order_receives;
order_pays = usd_to_buy;
if( before_core_hardfork_342 )
{
order_receives = usd_to_buy * match_price; // round down, in favor of call order
filled_call = true;
if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME )
// Be here, the limit order would be paying something for nothing
if( order_receives.amount == 0 ) // TODO remove warning after hard fork core-342
wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) );
}
else
order_receives = usd_to_buy ^ match_price; // round up, in favor of limit order
filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hard fork core-343)
if( usd_to_buy == usd_for_sale )
filled_limit = true;
else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) // TODO remove warning after hard fork core-453
wlog( "Multiple limit match problem (issue 453) occurred at block #${block}", ("block",head_block_num()) );
}
FC_ASSERT( filled_call || filled_limit );
FC_ASSERT( filled_call || filled_limit_in_loop );
// Be here, the call order won't be paying something for nothing according to code above.
// After hard fork core-625, the limit order will be always a maker if entered this function,
// so it won't be paying something for nothing, since if it would, it would have been cancelled already.
// However, we need to check if it's culled after partially filled.
// Before hard fork core-625,
// when the limit order is a taker, it could be paying something for nothing here;
// when the limit order is a maker, it won't be paying something for nothing,
// however, if it's culled after partially filled, `limit_itr` may be invalidated so should not be dereferenced
if( order_receives.amount == 0 )
{
if( maint_time > HARDFORK_CORE_184_TIME )
{
if( filled_call ) // call would be completely filled // should always be true
{
order_receives.amount = 1; // round up to 1 Satoshi
call_pays = order_receives;
}
// else do nothing, since the limit order should have already been cancelled elsewhere
else // TODO remove warning after confirmed
wlog( "Something for nothing issue (#184, variant D-1) occurred at block #${block}", ("block",head_block_num()) );
}
else // TODO remove warning after hard fork core-184
wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) );
}
call_pays = order_receives;
order_pays = call_receives;
auto old_call_itr = call_itr;
if( filled_call && maint_time <= HARDFORK_CORE_343_TIME )
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 );
if( maint_time > HARDFORK_CORE_343_TIME )
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 );
if( really_filled || ( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) )
if( really_filled || ( filled_limit && before_core_hardfork_453 ) )
limit_itr = next_limit_itr;
} // while call_itr != call_end

View file

@ -289,15 +289,17 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
if( ~least_collateral >= highest )
{
wdump( (*call_itr) );
elog( "Black Swan detected: \n"
elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n"
" Least collateralized call: ${lc} ${~lc}\n"
// " Highest Bid: ${hb} ${~hb}\n"
" Settle Price: ${~sp} ${sp}\n"
" Max: ${~h} ${h}\n",
("id",mia.id)("symbol",mia.symbol)("b",head_block_num())
("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real())
// ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real())
("sp",settle_price.to_real())("~sp",(~settle_price).to_real())
("h",highest.to_real())("~h",(~highest).to_real()) );
edump((enable_black_swan));
FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" );
if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price )
// globol settle at feed price if possible
@ -311,25 +313,30 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
void database::clear_expired_orders()
{ try {
//Cancel expired limit orders
auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();
while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() )
{
const limit_order_object& order = *limit_index.begin();
auto base_asset = order.sell_price.base.asset_id;
auto quote_asset = order.sell_price.quote.asset_id;
cancel_limit_order( order );
// check call orders
if( head_block_time() > HARDFORK_CORE_604_TIME )
{
// Comments below are copied from limit_order_cancel_evaluator::do_apply(...)
// Possible optimization: order can be called by cancelling a limit order
// if the canceled order was at the top of the book.
// Do I need to check calls in both assets?
check_call_orders( base_asset( *this ) );
check_call_orders( quote_asset( *this ) );
}
}
//Cancel expired limit orders
auto head_time = head_block_time();
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call
auto& limit_index = get_index_type<limit_order_index>().indices().get<by_expiration>();
while( !limit_index.empty() && limit_index.begin()->expiration <= head_time )
{
const limit_order_object& order = *limit_index.begin();
auto base_asset = order.sell_price.base.asset_id;
auto quote_asset = order.sell_price.quote.asset_id;
cancel_limit_order( order );
if( before_core_hardfork_606 )
{
// check call orders
// Comments below are copied from limit_order_cancel_evaluator::do_apply(...)
// Possible optimization: order can be called by cancelling a limit order
// if the canceled order was at the top of the book.
// Do I need to check calls in both assets?
check_call_orders( base_asset( *this ) );
check_call_orders( quote_asset( *this ) );
}
}
//Process expired force settlement orders
auto& settlement_index = get_index_type<force_settlement_index>().indices().get<by_expiration>();
@ -338,6 +345,7 @@ void database::clear_expired_orders()
asset_id_type current_asset = settlement_index.begin()->settlement_asset_id();
asset max_settlement_volume;
price settlement_fill_price;
price settlement_price;
bool current_asset_finished = false;
bool extra_dump = false;
@ -390,7 +398,7 @@ void database::clear_expired_orders()
}
// Has this order not reached its settlement date?
if( order.settlement_date > head_block_time() )
if( order.settlement_date > head_time() )
{
if( next_asset() )
{
@ -432,23 +440,23 @@ void database::clear_expired_orders()
break;
}
auto& pays = order.balance;
auto receives = (order.balance * mia.current_feed.settlement_price);
receives.amount = (fc::uint128_t(receives.amount.value) *
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64();
assert(receives <= order.balance * mia.current_feed.settlement_price);
price settlement_price = pays / receives;
// Calculate fill_price with a bigger volume to reduce impacts of rounding
if( settlement_fill_price.base.asset_id != pays.asset_id )
settlement_fill_price = mia.current_feed.settlement_price
/ ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent,
GRAPHENE_100_PERCENT );
if( before_core_hardfork_342 )
{
asset tmp_pays = max_settlement_volume;
asset tmp_receives = tmp_pays * mia.current_feed.settlement_price;
tmp_receives.amount = (fc::uint128_t(tmp_receives.amount.value) *
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64();
settlement_fill_price = tmp_pays / tmp_receives;
auto& pays = order.balance;
auto receives = (order.balance * mia.current_feed.settlement_price);
receives.amount = ( fc::uint128_t(receives.amount.value) *
(GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) /
GRAPHENE_100_PERCENT ).to_uint64();
assert(receives <= order.balance * mia.current_feed.settlement_price);
settlement_price = pays / receives;
}
else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset
settlement_price = settlement_fill_price;
auto& call_index = get_index_type<call_order_index>().indices().get<by_collateral>();
asset settled = mia_object.amount(mia.force_settled_volume);
@ -469,16 +477,27 @@ void database::clear_expired_orders()
}
try {
asset new_settled = match(*itr, order, settlement_price, max_settlement, settlement_fill_price);
if( maint_time > HARDFORK_CORE_184_TIME && new_settled.amount == 0 ) // unable to fill this settle order
if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order
{
if( find_object( order_id ) ) // the settle order hasn't been cancelled
current_asset_finished = true;
break;
}
settled += new_settled;
// before hard fork core-342, if new_settled > 0, we'll have:
// * call order is completely filled (thus itr will change in next loop), or
// * settle order is completely filled (thus find_object(order_id) will be false so will break out), or
// * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out).
//
// after hard fork core-342, if new_settled > 0, we'll have:
// * call order is completely filled (thus itr will change in next loop), or
// * settle order is completely filled (thus find_object(order_id) will be false so will break out), or
// * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement,
// in this case, new_settled will be zero in next iteration of the loop, so no need to check here.
}
catch ( const black_swan_exception& e ) {
wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) );
wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}",
("o", order)("e", e.to_detail_string()) );
cancel_settle_order( order );
break;
}

View file

@ -0,0 +1,4 @@
// bitshares-core issue #834 "BSIP38: add target CR option to short positions"
#ifndef HARDFORK_CORE_834_TIME
#define HARDFORK_CORE_834_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -433,7 +433,8 @@ 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 );
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 );
/// @return the amount of asset settled
asset match(const call_order_object& call,
const force_settlement_object& settle,

View file

@ -125,12 +125,16 @@ class call_order_object : public abstract_object<call_order_object>
share_type debt; ///< call_price.quote.asset_id, access via get_debt
price call_price; ///< Collateral / Debt
optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call
pair<asset_id_type,asset_id_type> get_market()const
{
auto tmp = std::make_pair( call_price.base.asset_id, call_price.quote.asset_id );
if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );
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;
};
/**
@ -215,7 +219,7 @@ FC_REFLECT_DERIVED( graphene::chain::limit_order_object,
)
FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object),
(borrower)(collateral)(debt)(call_price) )
(borrower)(collateral)(debt)(call_price)(target_collateral_ratio) )
FC_REFLECT_DERIVED( graphene::chain::force_settlement_object,
(graphene::db::object),

View file

@ -24,6 +24,7 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/protocol/ext.hpp>
namespace graphene { namespace chain {
@ -111,6 +112,15 @@ namespace graphene { namespace chain {
*/
struct call_order_update_operation : public base_operation
{
/**
* Options to be used in @ref call_order_update_operation.
*
* @note this struct can be expanded by adding more options in the end.
*/
struct options_type
{
optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call
};
/** this is slightly more expensive than limit orders, this pricing impacts prediction markets */
struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };
@ -118,6 +128,7 @@ namespace graphene { namespace chain {
account_id_type funding_account; ///< pays fee, collateral, and cover
asset delta_collateral; ///< the amount of collateral to add to the margin position
asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt
typedef extension<options_type> extensions_type; // note: this will be jsonified to {...} but no longer [...]
extensions_type extensions;
account_id_type fee_payer()const { return funding_account; }
@ -173,6 +184,10 @@ FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_accou
FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(delta_collateral)(delta_debt)(extensions) )
FC_REFLECT( graphene::chain::fill_order_operation, (fee)(order_id)(account_id)(pays)(receives)(fill_price)(is_maker) )
FC_REFLECT( graphene::chain::call_order_update_operation::options_type, (target_collateral_ratio) )
FC_REFLECT_TYPENAME( graphene::chain::call_order_update_operation::extensions_type
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_create_operation::fee_parameters_type )
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::limit_order_cancel_operation::fee_parameters_type )
GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::call_order_update_operation::fee_parameters_type )

View file

@ -158,6 +158,11 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope
{ try {
database& d = db();
// TODO: remove this check and the assertion after hf_834
if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_834_TIME )
FC_ASSERT( !o.extensions.value.target_collateral_ratio.valid(),
"Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." );
_paying_account = &o.funding_account(d);
_debt_asset = &o.delta_debt.asset_id(d);
FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.",
@ -227,6 +232,8 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) );
const call_order_object* call_obj = nullptr;
optional<uint16_t> new_target_cr = o.extensions.value.target_collateral_ratio;
if( itr == call_idx.end() )
{
FC_ASSERT( o.delta_collateral.amount > 0 );
@ -238,6 +245,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
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;
});
}
@ -246,13 +254,15 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
call_obj = &*itr;
d.modify( *call_obj, [&]( call_order_object& call ){
call.collateral += o.delta_collateral.amount;
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);
_bitasset_data->current_feed.maintenance_collateral_ratio);
}
call.target_collateral_ratio = new_target_cr;
});
}

View file

@ -0,0 +1,261 @@
/*
* Copyright (c) 2018 Abit More, and contributors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <graphene/chain/market_object.hpp>
#include <boost/multiprecision/cpp_int.hpp>
using namespace graphene::chain;
/*
target_CR = max( target_CR, MCR )
target_CR = new_collateral / ( new_debt / feed_price )
= ( collateral - max_amount_to_sell ) * feed_price
/ ( debt - amount_to_get )
= ( collateral - max_amount_to_sell ) * feed_price
/ ( debt - round_down(max_amount_to_sell * match_price ) )
= ( collateral - max_amount_to_sell ) * feed_price
/ ( debt - (max_amount_to_sell * match_price - x) )
Note: x is the fraction, 0 <= x < 1
=>
max_amount_to_sell = ( (debt + x) * target_CR - collateral * feed_price )
/ (target_CR * match_price - feed_price)
= ( (debt + x) * tCR / DENOM - collateral * fp_debt_amt / fp_coll_amt )
/ ( (tCR / DENOM) * (mp_debt_amt / mp_coll_amt) - fp_debt_amt / fp_coll_amt )
= ( (debt + x) * tCR * fp_coll_amt * mp_coll_amt - collateral * fp_debt_amt * DENOM * mp_coll_amt)
/ ( tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt )
max_debt_to_cover = max_amount_to_sell * match_price
= max_amount_to_sell * mp_debt_amt / mp_coll_amt
= ( (debt + x) * tCR * fp_coll_amt * mp_debt_amt - collateral * fp_debt_amt * DENOM * mp_debt_amt)
/ (tCR * mp_debt_amt * fp_coll_amt - fp_debt_amt * DENOM * mp_coll_amt)
*/
share_type call_order_object::get_max_debt_to_cover( price match_price,
price feed_price,
const uint16_t maintenance_collateral_ratio )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 )
feed_price = ~feed_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
return 0;
if( !target_collateral_ratio.valid() ) // target cr is not set
return debt;
uint16_t tcr = std::max( *target_collateral_ratio, maintenance_collateral_ratio ); // use mcr if target cr is too small
// 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;
FC_ASSERT( match_price.base.asset_id == call_price.base.asset_id
&& match_price.quote.asset_id == call_price.quote.asset_id );
typedef boost::multiprecision::int256_t i256;
i256 mp_debt_amt = match_price.quote.amount.value;
i256 mp_coll_amt = match_price.base.amount.value;
i256 fp_debt_amt = feed_price.quote.amount.value;
i256 fp_coll_amt = feed_price.base.amount.value;
// firstly we calculate without the fraction (x), the result could be a bit too small
i256 numerator = fp_coll_amt * mp_debt_amt * debt.value * tcr
- fp_debt_amt * mp_debt_amt * collateral.value * GRAPHENE_COLLATERAL_RATIO_DENOM;
if( numerator < 0 ) // feed protected, actually should not be true here, just check to be safe
return 0;
i256 denominator = fp_coll_amt * mp_debt_amt * tcr - fp_debt_amt * mp_coll_amt * GRAPHENE_COLLATERAL_RATIO_DENOM;
if( denominator <= 0 ) // black swan
return debt;
// note: if add 1 here, will result in 1.5x imperfection rate;
// however, due to rounding, the result could still be a bit too big, thus imperfect.
i256 to_cover_i256 = ( numerator / denominator );
if( to_cover_i256 >= debt.value ) // avoid possible overflow
return debt;
share_type to_cover_amt = static_cast< int64_t >( to_cover_i256 );
// stabilize
// note: rounding up-down results in 3x imperfection rate in comparison to down-down-up
asset to_pay = asset( to_cover_amt, debt_type() ) * match_price;
asset to_cover = to_pay * match_price;
to_pay = to_cover.multiply_and_round_up( match_price );
if( to_cover.amount >= debt || to_pay.amount >= collateral ) // to be safe
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 )
return to_cover.amount;
// be here, to_cover is too small due to rounding. deal with the fraction
numerator += fp_coll_amt * mp_debt_amt * tcr; // plus the fraction
to_cover_i256 = ( numerator / denominator ) + 1;
if( to_cover_i256 >= debt.value ) // avoid possible overflow
to_cover_i256 = debt.value;
to_cover_amt = static_cast< int64_t >( to_cover_i256 );
asset max_to_pay = ( ( to_cover_amt == debt.value ) ? get_collateral()
: asset( to_cover_amt, debt_type() ).multiply_and_round_up( match_price ) );
if( max_to_pay.amount > collateral )
max_to_pay.amount = collateral;
asset max_to_cover = ( ( max_to_pay.amount == collateral ) ? get_debt() : ( max_to_pay * match_price ) );
if( max_to_cover.amount >= debt ) // to be safe
{
max_to_pay.amount = collateral;
max_to_cover.amount = debt;
}
if( max_to_pay <= to_pay || max_to_cover <= to_cover ) // strange data. should skip binary search and go on, but doesn't help much
return debt;
FC_ASSERT( max_to_pay > to_pay && max_to_cover > to_cover );
asset min_to_pay = to_pay;
asset min_to_cover = to_cover;
// try with binary search to find a good value
// note: actually binary search can not always provide perfect result here,
// due to rounding, collateral ratio is not always increasing while to_pay or to_cover is increasing
bool max_is_ok = false;
while( true )
{
// get the mean
if( match_price.base.amount < match_price.quote.amount ) // step of collateral is smaller
{
to_pay.amount = ( min_to_pay.amount + max_to_pay.amount + 1 ) / 2; // should not overflow. round up here
if( to_pay.amount == max_to_pay.amount )
to_cover.amount = max_to_cover.amount;
else
{
to_cover = to_pay * match_price;
if( to_cover.amount >= max_to_cover.amount ) // can be true when max_is_ok is false
{
to_pay.amount = max_to_pay.amount;
to_cover.amount = max_to_cover.amount;
}
else
{
to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization, no change or become smaller
FC_ASSERT( to_pay.amount < max_to_pay.amount );
}
}
}
else // step of debt is smaller or equal
{
to_cover.amount = ( min_to_cover.amount + max_to_cover.amount ) / 2; // should not overflow. round down here
if( to_cover.amount == max_to_cover.amount )
to_pay.amount = max_to_pay.amount;
else
{
to_pay = to_cover.multiply_and_round_up( match_price );
if( to_pay.amount >= max_to_pay.amount ) // can be true when max_is_ok is false
{
to_pay.amount = max_to_pay.amount;
to_cover.amount = max_to_cover.amount;
}
else
{
to_cover = to_pay * match_price; // stabilization, to_cover should have increased
if( to_cover.amount >= max_to_cover.amount ) // to be safe
{
to_pay.amount = max_to_pay.amount;
to_cover.amount = max_to_cover.amount;
}
}
}
}
// check again to see if we've moved away from the minimums, if not, use the maximums directly
if( to_pay.amount <= min_to_pay.amount || to_cover.amount <= min_to_cover.amount
|| to_pay.amount > max_to_pay.amount || to_cover.amount > max_to_cover.amount )
{
to_pay.amount = max_to_pay.amount;
to_cover.amount = max_to_cover.amount;
}
// check the mean
if( to_pay.amount == max_to_pay.amount && ( max_is_ok || to_pay.amount == collateral ) )
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
{
if( to_pay.amount == max_to_pay.amount )
return to_cover.amount;
max_to_pay.amount = to_pay.amount;
max_to_cover.amount = to_cover.amount;
max_is_ok = true;
}
else // not good
{
if( to_pay.amount == max_to_pay.amount )
break;
min_to_pay.amount = to_pay.amount;
min_to_cover.amount = to_cover.amount;
}
}
// be here, max_to_cover is too small due to rounding. search forward
for( uint64_t d1 = 0, d2 = 1, d3 = 1; ; d1 = d2, d2 = d3, d3 = d1 + d2 ) // 1,1,2,3,5,8,...
{
if( match_price.base.amount > match_price.quote.amount ) // step of debt is smaller
{
to_pay.amount += d2;
if( to_pay.amount >= collateral )
return debt;
to_cover = to_pay * match_price;
if( to_cover.amount >= debt )
return debt;
to_pay = to_cover.multiply_and_round_up( match_price ); // stabilization
if( to_pay.amount >= collateral )
return debt;
}
else // step of collateral is smaller or equal
{
to_cover.amount += d2;
if( to_cover.amount >= debt )
return debt;
to_pay = to_cover.multiply_and_round_up( match_price );
if( to_pay.amount >= collateral )
return debt;
to_cover = to_pay * match_price; // stabilization
if( to_cover.amount >= debt )
return debt;
}
// 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
return to_cover.amount;
}
} FC_CAPTURE_AND_RETHROW( (*this)(feed_price)(match_price)(maintenance_collateral_ratio) ) }