diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index be65bc5f..a19f91a4 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -247,6 +247,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT(a.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset."); const asset_bitasset_data_object& b = a.bitasset_data(d); + FC_ASSERT( !b.has_settlement(), "Cannot update a bitasset after a settlement has executed" ); if( o.new_options.short_backing_asset != b.options.short_backing_asset ) { FC_ASSERT(a.dynamic_asset_data_id(d).current_supply == 0); @@ -345,21 +346,40 @@ object_id_type asset_settle_evaluator::do_evaluate(const asset_settle_evaluator: const database& d = db(); asset_to_settle = &op.amount.asset_id(d); FC_ASSERT(asset_to_settle->is_market_issued()); - FC_ASSERT(asset_to_settle->can_force_settle()); + const auto& bitasset = asset_to_settle->bitasset_data(d); + FC_ASSERT(asset_to_settle->can_force_settle() || bitasset.has_settlement() ); FC_ASSERT(d.get_balance(d.get(op.account), *asset_to_settle) >= op.amount); return d.get_index_type().get_next_id(); } -object_id_type asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) +operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) { database& d = db(); d.adjust_balance(op.account, -op.amount); - return d.create([&](force_settlement_object& s) { - s.owner = op.account; - s.balance = op.amount; - s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec; - }).id; + + const auto& bitasset = asset_to_settle->bitasset_data(d); + if( bitasset.has_settlement() ) + { + auto settled_amount = op.amount * bitasset.settlement_price; + FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); + + d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + obj.settlement_fund -= settled_amount.amount; + }); + + d.adjust_balance(op.account, settled_amount); + + return settled_amount; + } + else + { + return d.create([&](force_settlement_object& s) { + s.owner = op.account; + s.balance = op.amount; + s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec; + }).id; + } } void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) @@ -371,6 +391,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ FC_ASSERT(base.is_market_issued()); const asset_bitasset_data_object& bitasset = base.bitasset_data(d); + FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" ); FC_ASSERT(o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset); //Verify that the publisher is authoritative to publish a feed if( base.issuer == account_id_type() ) diff --git a/libraries/chain/call_order_evaluator.cpp b/libraries/chain/call_order_evaluator.cpp index 19549e71..8fda9f2b 100644 --- a/libraries/chain/call_order_evaluator.cpp +++ b/libraries/chain/call_order_evaluator.cpp @@ -35,6 +35,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope _bitasset_data = &_debt_asset->bitasset_data(d); + /// if there is a settlement for this asset, then no further margin positions may be taken and + /// all existing margin positions should have been closed va database::globally_settle_asset + FC_ASSERT( !_bitasset_data->has_settlement() ); + FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset ); if( _bitasset_data->is_prediction_market ) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f46e3017..9a7cc0fb 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -28,14 +28,12 @@ namespace graphene { namespace chain { /** - for each short order, fill it at settlement price and place funds received into a total - calculate the USD->CORE price and convert all USD balances to CORE at that price and subtract CORE from total - - any fees accumulated by the issuer in the bitasset are forfeit / not redeemed - - cancel all open orders with bitasset in it - - any bitassets that use this bitasset as collateral are immediately settled at their feed price - - convert all balances in bitasset to CORE and subtract from total - - any prediction markets with usd as the backing get converted to CORE as the backing - any CORE left over due to rounding errors is paid to accumulated fees + * All margin positions are force closed at the swan price + * Collateral received goes into a force-settlement fund + * No new margin positions can be created for this asset + * No more price feed updates + * Force settlement happens without delay at the swan price, deducting from force-settlement fund + * No more asset updates may be issued. */ void database::globally_settle_asset( const asset_object& mia, const price& settlement_price ) { try { @@ -45,6 +43,8 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett edump( (mia.symbol)(settlement_price) ); const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); + FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); + const asset_object& backing_asset = bitasset.options.short_backing_asset(*this); asset collateral_gathered = backing_asset.amount(0); @@ -54,93 +54,30 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); - 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 ) ); - while( call_itr != call_end ) - { - auto pays = call_itr->get_debt() * settlement_price; - wdump( (call_itr->get_debt() ) ); - collateral_gathered += pays; - const auto& order = *call_itr; - ++call_itr; - FC_ASSERT( fill_order( order, pays, order.get_debt() ) ); - } - - const limit_order_index& limit_index = get_index_type(); - const auto& limit_price_index = limit_index.indices().get(); - - auto max_short_squeeze = bitasset.current_feed.max_short_squeeze_price(); - // cancel all orders selling the market issued asset - auto limit_itr = limit_price_index.lower_bound(price::max(mia.id, bitasset.options.short_backing_asset)); - auto limit_end = limit_price_index.upper_bound(~max_short_squeeze); - while( limit_itr != limit_end ) - { - const auto& order = *limit_itr; - ilog( "CANCEL LIMIT ORDER" ); - idump((order)); - ++limit_itr; - cancel_order( order ); - } - - limit_itr = limit_price_index.begin(); - while( limit_itr != limit_end ) - { - if( limit_itr->amount_for_sale().asset_id == mia.id ) - { - const auto& order = *limit_itr; - ilog( "CANCEL_AGAIN" ); - edump((order)); - ++limit_itr; - cancel_order( order ); - } - } - - limit_itr = limit_price_index.begin(); - while( limit_itr != limit_end ) + // 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 ) ); + while( call_itr != call_end ) { - if( limit_itr->amount_for_sale().asset_id == mia.id ) - { - const auto& order = *limit_itr; - edump((order)); - ++limit_itr; - cancel_order( order ); - } + auto pays = call_itr->get_debt() * settlement_price; + wdump( (call_itr->get_debt() ) ); + collateral_gathered += pays; + const auto& order = *call_itr; + ++call_itr; + FC_ASSERT( fill_order( order, pays, order.get_debt() ) ); } - // settle all balances - asset total_mia_settled = mia.amount(0); + modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + obj.settlement_price = settlement_price; + obj.settlement_fund = collateral_gathered.amount; + }); - const auto& index = get_index_type().indices().get(); - auto range = index.equal_range(mia.get_id()); - for( auto itr = range.first; itr != range.second; ++itr ) - { - auto mia_balance = itr->get_balance(); - if( mia_balance.amount > 0 ) - { - adjust_balance(itr->owner, -mia_balance); - auto settled_amount = mia_balance * settlement_price; - idump( (mia_balance)(settled_amount)(settlement_price) ); - adjust_balance(itr->owner, settled_amount); - total_mia_settled += mia_balance; - collateral_gathered -= settled_amount; - } - } + /// TODO: after all margin positions are closed, the current supply will be reported as 0, but + /// that is a lie, the supply didn't change. We need to capture the current supply before + /// filling all call orders and then restore it afterward. Then in the force settlement + /// evaluator reduce the supply - // TODO: convert payments held in escrow - - modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ - total_mia_settled.amount += obj.accumulated_fees; - obj.accumulated_fees = 0; - }); - - wlog( "====================== AFTER SETTLE BLACK SWAN UNCLAIMED SETTLEMENT FUNDS ==============\n" ); - wdump((collateral_gathered)(total_mia_settled)(original_mia_supply)(mia_dyn.current_supply)); - modify( bitasset.options.short_backing_asset(*this).dynamic_asset_data_id(*this), [&]( asset_dynamic_data_object& obj ){ - obj.accumulated_fees += collateral_gathered.amount; - }); - - FC_ASSERT( total_mia_settled.amount == original_mia_supply, "", ("total_settled",total_mia_settled)("original",original_mia_supply) ); - } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } +} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } void database::cancel_order(const force_settlement_object& order, bool create_virtual_op) { diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 212bc9af..786a7e2a 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -114,7 +114,7 @@ namespace graphene { namespace chain { typedef asset_settle_operation operation_type; object_id_type do_evaluate(const operation_type& op); - object_id_type do_apply(const operation_type& op); + operation_result do_apply(const operation_type& op); const asset_object* asset_to_settle = nullptr; }; diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 95d78d4d..cb2a61a9 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -250,6 +250,21 @@ namespace graphene { namespace chain { /// Calculate the maximum force settlement volume per maintenance interval, given the current share supply share_type max_force_settlement_volume(share_type current_supply)const; + /** return true if there has been a black swan, false otherwise */ + bool has_settlement()const { return !settlement_price.is_null(); } + + /** + * In the event of a black swan, the swan price is saved in the settlement price, + * and all margin positions are settled at the same price with the siezed collateral + * being moved into the settlement fund. From this point on no further updates to + * the asset are permitted (no feeds, etc) and forced settlement occurs using + * the settlement price and fund. + */ + ///@{ + price settlement_price; + share_type settlement_fund; + ///@} + time_point_sec feed_expiration_time()const { return current_feed_publication_time + options.feed_lifetime_sec; } bool feed_is_expired(time_point_sec current_time)const @@ -292,6 +307,8 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db:: (options) (force_settled_volume) (is_prediction_market) + (settlement_price) + (settlement_fund) ) FC_REFLECT( graphene::chain::asset_object::asset_options,