- fix detection and handling of black swan events - fix total core in orders calculation from genesis
This commit is contained in:
parent
74bbde785b
commit
26288549e6
7 changed files with 158 additions and 29 deletions
|
|
@ -44,12 +44,12 @@ void database::debug_dump()
|
|||
|
||||
for( const account_balance_object& a : balance_index )
|
||||
{
|
||||
idump(("balance")(a));
|
||||
// idump(("balance")(a));
|
||||
total_balances[a.asset_type] += a.balance;
|
||||
}
|
||||
for( const account_statistics_object& s : statistics_index )
|
||||
{
|
||||
idump(("statistics")(s));
|
||||
// idump(("statistics")(s));
|
||||
reported_core_in_orders += s.total_core_in_orders;
|
||||
}
|
||||
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
|
||||
|
|
@ -71,17 +71,23 @@ void database::debug_dump()
|
|||
{
|
||||
total_balances[asset_obj.id] += asset_obj.dynamic_asset_data_id(db).accumulated_fees;
|
||||
total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool;
|
||||
// edump((total_balances[asset_obj.id])(asset_obj.dynamic_asset_data_id(db).current_supply ) );
|
||||
}
|
||||
|
||||
if( total_balances[asset_id_type()].value != core_asset_data.current_supply.value )
|
||||
{
|
||||
edump( (total_balances[asset_id_type()].value)(core_asset_data.current_supply.value ));
|
||||
}
|
||||
|
||||
edump((core_in_orders)(reported_core_in_orders));
|
||||
|
||||
/*
|
||||
const auto& vbidx = db.get_index_type<simple_index<vesting_balance_object>>();
|
||||
for( const auto& s : vbidx )
|
||||
{
|
||||
idump(("vesting_balance")(s));
|
||||
// idump(("vesting_balance")(s));
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -460,6 +460,10 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
|||
cop.active = cop.owner;
|
||||
account_id_type owner_account_id = apply_operation(genesis_eval_state, cop).get<object_id_type>();
|
||||
|
||||
modify( owner_account_id(*this).statistics(*this), [&]( account_statistics_object& o ) {
|
||||
o.total_core_in_orders = collateral_rec.collateral;
|
||||
});
|
||||
|
||||
create<call_order_object>([&](call_order_object& c) {
|
||||
c.borrower = owner_account_id;
|
||||
c.collateral = collateral_rec.collateral;
|
||||
|
|
@ -611,6 +615,8 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
|||
wso.current_shuffled_witnesses.push_back( wid );
|
||||
});
|
||||
|
||||
debug_dump();
|
||||
|
||||
_undo_db.enable();
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua
|
|||
|
||||
modify( order.seller(*this).statistics(*this),[&]( account_statistics_object& obj ){
|
||||
if( refunded.asset_id == asset_id_type() )
|
||||
{
|
||||
obj.total_core_in_orders -= refunded.amount;
|
||||
}
|
||||
});
|
||||
adjust_balance(order.seller, refunded);
|
||||
|
||||
|
|
@ -220,19 +222,30 @@ int database::match( const limit_order_object& bid, const limit_order_object& as
|
|||
}
|
||||
|
||||
|
||||
asset database::match( const call_order_object& call, const force_settlement_object& settle, const price& match_price,
|
||||
asset max_settlement )
|
||||
{
|
||||
assert(call.get_debt().asset_id == settle.balance.asset_id );
|
||||
assert(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);
|
||||
asset database::match( const call_order_object& call,
|
||||
const force_settlement_object& settle,
|
||||
const price& match_price,
|
||||
asset max_settlement )
|
||||
{ try {
|
||||
FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id );
|
||||
FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0);
|
||||
|
||||
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),
|
||||
call_pays = call_receives * match_price,
|
||||
settle_pays = call_receives,
|
||||
settle_receives = call_pays;
|
||||
asset call_receives = std::min(settle_for_sale, call_debt);
|
||||
asset call_pays = call_receives * match_price;
|
||||
asset settle_pays = call_receives;
|
||||
asset settle_receives = call_pays;
|
||||
|
||||
/**
|
||||
* If the least collateralized call position lacks sufficient
|
||||
* collateral to cover at the match price then this indicates a black
|
||||
* swan event according to the price feed, but only the market
|
||||
* can trigger a black swan. So now we must cancel the forced settlement
|
||||
* object.
|
||||
*/
|
||||
GRAPHENE_ASSERT( call_pays < call.get_collateral(), black_swan_exception, "" );
|
||||
|
||||
assert( settle_pays == settle_for_sale || call_receives == call.get_debt() );
|
||||
|
||||
|
|
@ -240,12 +253,12 @@ asset database::match( const call_order_object& call, const force_settlement_obj
|
|||
fill_order(settle, settle_pays, settle_receives);
|
||||
|
||||
return call_receives;
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) }
|
||||
|
||||
bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives )
|
||||
{
|
||||
assert( order.amount_for_sale().asset_id == pays.asset_id );
|
||||
assert( pays.asset_id != receives.asset_id );
|
||||
{ try {
|
||||
FC_ASSERT( order.amount_for_sale().asset_id == pays.asset_id );
|
||||
FC_ASSERT( pays.asset_id != receives.asset_id );
|
||||
|
||||
const account_object& seller = order.seller(*this);
|
||||
const asset_object& recv_asset = receives.asset_id(*this);
|
||||
|
|
@ -279,15 +292,15 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c
|
|||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }
|
||||
|
||||
|
||||
bool database::fill_order( const call_order_object& order, const asset& pays, const asset& receives )
|
||||
{ try {
|
||||
//idump((pays)(receives)(order));
|
||||
assert( order.get_debt().asset_id == receives.asset_id );
|
||||
assert( order.get_collateral().asset_id == pays.asset_id );
|
||||
assert( order.get_collateral() >= pays );
|
||||
FC_ASSERT( order.get_debt().asset_id == receives.asset_id );
|
||||
FC_ASSERT( order.get_collateral().asset_id == pays.asset_id );
|
||||
FC_ASSERT( order.get_collateral() >= pays );
|
||||
|
||||
optional<asset> collateral_freed;
|
||||
modify( order, [&]( call_order_object& o ){
|
||||
|
|
@ -315,11 +328,13 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co
|
|||
const account_statistics_object& borrower_statistics = borrower.statistics(*this);
|
||||
if( collateral_freed )
|
||||
adjust_balance(borrower.get_id(), *collateral_freed);
|
||||
|
||||
modify( borrower_statistics, [&]( account_statistics_object& b ){
|
||||
if( collateral_freed && collateral_freed->amount > 0 )
|
||||
b.total_core_in_orders -= collateral_freed->amount;
|
||||
if( pays.asset_id == asset_id_type() )
|
||||
b.total_core_in_orders -= pays.amount;
|
||||
|
||||
assert( b.total_core_in_orders >= 0 );
|
||||
});
|
||||
}
|
||||
|
|
@ -374,6 +389,10 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
|
|||
bool database::check_call_orders(const asset_object& mia, bool enable_black_swan)
|
||||
{ try {
|
||||
if( !mia.is_market_issued() ) return false;
|
||||
|
||||
if( check_for_blackswan( mia, enable_black_swan ) )
|
||||
return false;
|
||||
|
||||
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
|
||||
if( bitasset.is_prediction_market ) return false;
|
||||
if( bitasset.current_feed.settlement_price.is_null() ) return false;
|
||||
|
|
@ -395,15 +414,23 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
|
|||
auto limit_end = limit_price_index.upper_bound( min_price );
|
||||
|
||||
if( limit_itr == limit_end ) {
|
||||
/*
|
||||
if( head_block_num() > 300000 )
|
||||
ilog( "no orders below between: ${p} and: ${m}",
|
||||
("p", bitasset.current_feed.max_short_squeeze_price())
|
||||
("m", max_price) );
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
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 ) );
|
||||
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_itr = call_price_index.lower_bound( call_min );
|
||||
auto call_end = call_price_index.upper_bound( call_max );
|
||||
|
||||
bool filled_limit = false;
|
||||
|
||||
while( call_itr != call_end )
|
||||
while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end )
|
||||
{
|
||||
bool filled_call = false;
|
||||
price match_price;
|
||||
|
|
@ -419,17 +446,16 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
|
|||
match_price.validate();
|
||||
|
||||
if( match_price > ~call_itr->call_price )
|
||||
{
|
||||
return filled_limit;
|
||||
}
|
||||
|
||||
auto usd_to_buy = call_itr->get_debt();
|
||||
|
||||
if( usd_to_buy * match_price > call_itr->get_collateral() )
|
||||
{
|
||||
elog( "black swan detected" );
|
||||
edump((enable_black_swan));
|
||||
FC_ASSERT( enable_black_swan );
|
||||
//globally_settle_asset(mia, call_itr->get_debt() / call_itr->get_collateral());
|
||||
globally_settle_asset(mia, bitasset.current_feed.settlement_price );// call_itr->get_debt() / call_itr->get_collateral());
|
||||
globally_settle_asset(mia, bitasset.current_feed.settlement_price );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -458,6 +484,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
|
|||
|
||||
auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr;
|
||||
fill_order(*old_limit_itr, order_pays, order_receives);
|
||||
|
||||
} // whlie call_itr != call_end
|
||||
|
||||
return filled_limit;
|
||||
|
|
@ -468,7 +495,9 @@ void database::pay_order( const account_object& receiver, const asset& receives,
|
|||
const auto& balances = receiver.statistics(*this);
|
||||
modify( balances, [&]( account_statistics_object& b ){
|
||||
if( pays.asset_id == asset_id_type() )
|
||||
{
|
||||
b.total_core_in_orders -= pays.amount;
|
||||
}
|
||||
});
|
||||
adjust_balance(receiver.get_id(), receives);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,73 @@ void database::clear_expired_proposals()
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* let HB = the highest bid for the collateral (aka who will pay the most DEBT for the least collateral)
|
||||
* let SP = current median feed's Settlement Price
|
||||
* let LC = the least collateralized call order's swan price (debt/collateral)
|
||||
*
|
||||
* If there is no valid price feed or no bids then there is no black swan.
|
||||
*
|
||||
* A black swan occurs if MAX(HB,SP) <= LC
|
||||
*/
|
||||
bool database::check_for_blackswan( const asset_object& mia, bool enable_black_swan )
|
||||
{
|
||||
if( !mia.is_market_issued() ) return false;
|
||||
|
||||
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
|
||||
if( bitasset.has_settlement() ) return true; // already force settled
|
||||
|
||||
const call_order_index& call_index = get_index_type<call_order_index>();
|
||||
const auto& call_price_index = call_index.indices().get<by_price>();
|
||||
|
||||
const limit_order_index& limit_index = get_index_type<limit_order_index>();
|
||||
const auto& limit_price_index = limit_index.indices().get<by_price>();
|
||||
|
||||
// looking for limit orders selling the most USD for the least CORE
|
||||
auto highest_possible_bid = price::max( mia.id, bitasset.options.short_backing_asset );
|
||||
// stop when limit orders are selling too little USD for too much CORE
|
||||
auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset );
|
||||
|
||||
assert( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id );
|
||||
// NOTE limit_price_index is sorted from greatest to least
|
||||
auto limit_itr = limit_price_index.lower_bound( highest_possible_bid );
|
||||
auto limit_end = limit_price_index.upper_bound( lowest_possible_bid );
|
||||
|
||||
auto call_min = price::min( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_max = price::max( bitasset.options.short_backing_asset, mia.id );
|
||||
auto call_itr = call_price_index.lower_bound( call_min );
|
||||
auto call_end = call_price_index.upper_bound( call_max );
|
||||
|
||||
auto settle_price = bitasset.current_feed.settlement_price;
|
||||
|
||||
if( settle_price.is_null() ) return false; /// no feed;
|
||||
if( call_itr == call_end ) return false; /// no call orders
|
||||
|
||||
price highest = settle_price;
|
||||
if( limit_itr != limit_end ) {
|
||||
assert( settle_price.base.asset_id == limit_itr->sell_price.base.asset_id );
|
||||
highest = std::max( limit_itr->sell_price, settle_price );
|
||||
}
|
||||
|
||||
auto least_collateral = call_itr->collateralization();
|
||||
if( ~least_collateral >= highest )
|
||||
{
|
||||
elog( "Black Swan detected: \n"
|
||||
" Least collateralized call: ${lc} ${~lc}\n"
|
||||
// " Highest Bid: ${hb} ${~hb}\n"
|
||||
" Settle Price: ${sp} ${~sp}\n"
|
||||
" Max: ${h} ${~h}\n",
|
||||
("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()) );
|
||||
FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" );
|
||||
globally_settle_asset(mia, ~least_collateral );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void database::clear_expired_orders()
|
||||
{
|
||||
detail::with_skip_flags( *this,
|
||||
|
|
@ -187,6 +254,13 @@ void database::clear_expired_orders()
|
|||
const asset_object& mia_object = get(current_asset);
|
||||
const asset_bitasset_data_object mia = mia_object.bitasset_data(*this);
|
||||
|
||||
if( mia.has_settlement() )
|
||||
{
|
||||
ilog( "Canceling a force settlement because of black swan" );
|
||||
cancel_order( order );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Has this order not reached its settlement date?
|
||||
if( order.settlement_date > head_block_time() )
|
||||
{
|
||||
|
|
@ -234,7 +308,15 @@ void database::clear_expired_orders()
|
|||
// There should always be a call order, since asset exists!
|
||||
assert(itr != call_index.end() && itr->debt_type() == mia_object.get_id());
|
||||
asset max_settlement = max_settlement_volume - settled;
|
||||
settled += match(*itr, order, settlement_price, max_settlement);
|
||||
|
||||
try {
|
||||
settled += match(*itr, order, settlement_price, max_settlement);
|
||||
}
|
||||
catch ( const black_swan_exception& e ) {
|
||||
wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) );
|
||||
cancel_order( order );
|
||||
break;
|
||||
}
|
||||
}
|
||||
modify(mia, [settled](asset_bitasset_data_object& b) {
|
||||
b.force_settled_volume = settled.amount;
|
||||
|
|
|
|||
|
|
@ -428,6 +428,7 @@ namespace graphene { namespace chain {
|
|||
void update_expired_feeds();
|
||||
void update_maintenance_flag( bool new_maintenance_flag );
|
||||
void update_withdraw_permissions();
|
||||
bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true );
|
||||
|
||||
///Steps performed only at maintenance intervals
|
||||
///@{
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ namespace graphene { namespace chain {
|
|||
FC_DECLARE_DERIVED_EXCEPTION( operation_evaluate_exception, graphene::chain::chain_exception, 3050000, "operation evaluation exception" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( utility_exception, graphene::chain::chain_exception, 3060000, "utility method exception" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( undo_database_exception, graphene::chain::chain_exception, 3070000, "undo database exception" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, graphene::chain::chain_exception, 3080000, "unlinkable block" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( unlinkable_block_exception, graphene::chain::chain_exception, 3080000, "unlinkable block" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( black_swan_exception, graphene::chain::chain_exception, 3090000, "black swan" )
|
||||
|
||||
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_active_auth, graphene::chain::transaction_exception, 3030001, "missing required active authority" )
|
||||
FC_DECLARE_DERIVED_EXCEPTION( tx_missing_owner_auth, graphene::chain::transaction_exception, 3030002, "missing required owner authority" )
|
||||
|
|
|
|||
|
|
@ -183,6 +183,8 @@ 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);
|
||||
|
||||
auto swan_price = call.get_debt()/ call.get_collateral();
|
||||
});
|
||||
}
|
||||
else
|
||||
|
|
@ -194,6 +196,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
|
|||
call.debt += o.delta_debt.amount;
|
||||
if( call.debt > 0 )
|
||||
{
|
||||
auto swan_price = call.get_debt()/ call.get_collateral();
|
||||
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
|
||||
_bitasset_data->current_feed.maintenance_collateral_ratio);
|
||||
}
|
||||
|
|
@ -229,6 +232,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
|
|||
}
|
||||
else
|
||||
{
|
||||
//edump( (~call_obj->call_price) ("<")( _bitasset_data->current_feed.settlement_price) );
|
||||
// We didn't fill any call orders. This may be because we
|
||||
// aren't in margin call territory, or it may be because there
|
||||
// were no matching orders. In the latter case, we throw.
|
||||
|
|
|
|||
Loading…
Reference in a new issue