fixes bts mid 2018
This commit is contained in:
parent
4d8f3725b0
commit
463c812a33
9 changed files with 588 additions and 173 deletions
|
|
@ -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" );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
4
libraries/chain/hardfork.d/CORE_834.hf
Normal file
4
libraries/chain/hardfork.d/CORE_834.hf
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
261
libraries/chain/market_object.cpp
Normal file
261
libraries/chain/market_object.cpp
Normal 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) ) }
|
||||
Loading…
Reference in a new issue