Skip to content

Commit

Permalink
Fix blackswan on force settle, Fix #346 and #347
Browse files Browse the repository at this point in the history
- fix detection and handling of black swan events
- fix total core in orders calculation from genesis
  • Loading branch information
bytemaster committed Sep 30, 2015
1 parent 74bbde7 commit 2628854
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 29 deletions.
12 changes: 9 additions & 3 deletions libraries/chain/db_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() )
Expand All @@ -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));
}
*/
}

} }
6 changes: 6 additions & 0 deletions libraries/chain/db_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() }

Expand Down
77 changes: 53 additions & 24 deletions libraries/chain/db_market.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -220,32 +222,43 @@ 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() );

fill_order(call, call_pays, call_receives);
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);
Expand Down Expand Up @@ -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 ){
Expand Down Expand Up @@ -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 );
});
}
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down
84 changes: 83 additions & 1 deletion libraries/chain/db_update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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() )
{
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/include/graphene/chain/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
///@{
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/graphene/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" )
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/market_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 2628854

Please sign in to comment.