Skip to content

Commit

Permalink
Refactor code about individual settlement order
Browse files Browse the repository at this point in the history
Store settled debt amounts and collateral amounts in bitasset objects instead of limit orders.
  • Loading branch information
abitmore committed May 27, 2023
1 parent 4b87ba8 commit 6f410b1
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 120 deletions.
116 changes: 55 additions & 61 deletions libraries/chain/db_market.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -315,13 +315,10 @@ void database::globally_settle_asset_impl( const asset_object& mia,
"Internal error: unable to close margin call ${o}", ("o", order) );
}

// Move the individual settlement order to the GS fund
// Remove the individual settlement order
const limit_order_object* limit_ptr = find_settled_debt_order( bitasset.asset_id );
if( limit_ptr )
{
collateral_gathered.amount += limit_ptr->settled_collateral_amount;
remove( *limit_ptr );
}

// Move individual settlement fund to the GS fund
collateral_gathered.amount += bitasset.individual_settlement_fund;
Expand Down Expand Up @@ -355,14 +352,12 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset,

auto margin_call_fee = order_collateral - fund_receives;

if( bsrm_type::individual_settlement_to_fund == bsrm ) // settle to fund
{
modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){
obj.individual_settlement_debt += order.debt;
obj.individual_settlement_fund += fund_receives.amount;
});
}
else // settle to order
modify( bitasset, [&order,&fund_receives]( asset_bitasset_data_object& obj ){
obj.individual_settlement_debt += order.debt;
obj.individual_settlement_fund += fund_receives.amount;
});

if( bsrm_type::individual_settlement_to_order == bsrm ) // settle to order
{
const auto& head_time = head_block_time();
bool after_core_hardfork_2591 = HARDFORK_CORE_2591_PASSED( head_time ); // Tighter peg (fill debt order at MCOP)
Expand All @@ -371,18 +366,17 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset,
if( limit_ptr )
{
modify( *limit_ptr, [&order,&fund_receives,after_core_hardfork_2591,&bitasset]( limit_order_object& obj ) {
obj.settled_debt_amount += order.debt;
obj.settled_collateral_amount += fund_receives.amount;
// TODO fix duplicate code
bool sell_all = true;
if( after_core_hardfork_2591 )
{
obj.sell_price = ~bitasset.get_margin_call_order_price();
asset settled_debt( obj.settled_debt_amount, obj.receive_asset_id() );
asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );
try
{
obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount; // may overflow
if( obj.for_sale <= obj.settled_collateral_amount ) // "=" for consistency of order matching logic
// Note: the "=" below is for the consistency of order matching logic
if( obj.for_sale <= bitasset.individual_settlement_fund )
sell_all = false;
}
catch( fc::exception& e ) // catch the overflow
Expand All @@ -393,9 +387,8 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset,
}
if( sell_all )
{
obj.for_sale = obj.settled_collateral_amount;
obj.sell_price.base.amount = obj.settled_collateral_amount;
obj.sell_price.quote.amount = obj.settled_debt_amount;
obj.for_sale = bitasset.individual_settlement_fund;
obj.sell_price = ~bitasset.get_individual_settlement_price();
}
} );
}
Expand All @@ -407,8 +400,6 @@ void database::individually_settle( const asset_bitasset_data_object& bitasset,
obj.for_sale = fund_receives.amount;
obj.sell_price = fund_receives / order_debt;
obj.is_settled_debt = true;
obj.settled_debt_amount = order_debt.amount;
obj.settled_collateral_amount = fund_receives.amount;
} );
}
// Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders
Expand Down Expand Up @@ -1115,14 +1106,17 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde
bool cull_taker = false;
bool maker_filled = false;

const auto& mia = maker.receive_asset_id()(*this);
const auto& bitasset = mia.bitasset_data(*this);

auto usd_for_sale = taker.amount_for_sale();
auto usd_to_buy = asset( maker.settled_debt_amount, maker.receive_asset_id() );
auto usd_to_buy = asset( bitasset.individual_settlement_debt, maker.receive_asset_id() );

asset call_receives;
asset order_receives;
if( usd_to_buy > usd_for_sale )
{ // fill taker limit order
order_receives = usd_for_sale * match_price; // round down here, in favor of call order
order_receives = usd_for_sale * match_price; // round down here, in favor of "call 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
Expand Down Expand Up @@ -1151,13 +1145,9 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde

asset call_pays = order_receives;
if( maker_filled ) // Regardless of hf core-2591
call_pays.amount = maker.settled_collateral_amount;
else if( maker.for_sale != maker.settled_collateral_amount ) // implies hf core-2591
{
price settle_price = asset( maker.settled_collateral_amount, maker.sell_asset_id() )
/ asset( maker.settled_debt_amount, maker.receive_asset_id() );
call_pays = call_receives * settle_price; // round down here, in favor of call order
}
call_pays.amount = bitasset.individual_settlement_fund;
else if( maker.for_sale != bitasset.individual_settlement_fund ) // implies hf core-2591
call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of "call order"
if( call_pays < order_receives ) // be defensive, maybe unnecessary
{
wlog( "Unexpected scene: call_pays < order_receives" );
Expand All @@ -1166,7 +1156,7 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde
asset collateral_fee = call_pays - order_receives;

// Reduce current supply, and accumulate collateral fees
const asset_dynamic_data_object& mia_ddo = call_receives.asset_id(*this).dynamic_asset_data_id(*this);
const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);
modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){
ao.current_supply -= call_receives.amount;
ao.accumulated_collateral_fees += collateral_fee.amount;
Expand All @@ -1177,33 +1167,35 @@ database::match_result_type database::match_limit_settled_debt( const limit_orde
push_applied_operation( fill_order_operation( maker.id, maker.seller, call_pays, call_receives,
collateral_fee, match_price, true ) );

// Update bitasset data
modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){
obj.individual_settlement_debt -= call_receives.amount;
obj.individual_settlement_fund -= call_pays.amount;
});

// Update the maker order
// Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders
if( maker_filled )
remove( maker );
else
{
modify( maker, [after_core_hardfork_2591,&call_pays,&call_receives]( limit_order_object& obj ) {
obj.settled_debt_amount -= call_receives.amount;
obj.settled_collateral_amount -= call_pays.amount;
modify( maker, [after_core_hardfork_2591,&call_pays,&call_receives,&bitasset]( limit_order_object& obj ) {
if( after_core_hardfork_2591 )
{
// Note: for simplicity, only update price when necessary
asset settled_debt( obj.settled_debt_amount, obj.receive_asset_id() );
asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );
obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount;
if( obj.for_sale > obj.settled_collateral_amount ) // be defensive, maybe unnecessary
if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary
{
wlog( "Unexpected scene: obj.for_sale > obj.settled_collateral_amount" );
obj.for_sale = obj.settled_collateral_amount;
obj.sell_price.base.amount = obj.settled_collateral_amount;
obj.sell_price.quote.amount = obj.settled_debt_amount;
wlog( "Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund" );
obj.for_sale = bitasset.individual_settlement_fund;
obj.sell_price = ~bitasset.get_individual_settlement_price();
}
}
else
{
obj.for_sale = obj.settled_collateral_amount;
obj.sell_price.base.amount = obj.settled_collateral_amount;
obj.sell_price.quote.amount = obj.settled_debt_amount;
obj.for_sale = bitasset.individual_settlement_fund;
obj.sell_price = ~bitasset.get_individual_settlement_price();
}
});
// Note:
Expand All @@ -1225,8 +1217,11 @@ database::match_result_type database::match_settled_debt_limit( const limit_orde

bool taker_filled = false;

const auto& mia = taker.receive_asset_id()(*this);
const auto& bitasset = mia.bitasset_data(*this);

auto usd_for_sale = maker.amount_for_sale();
auto usd_to_buy = asset( taker.settled_debt_amount, taker.receive_asset_id() );
auto usd_to_buy = asset( bitasset.individual_settlement_debt, taker.receive_asset_id() );

asset call_receives;
asset order_receives;
Expand All @@ -1248,13 +1243,9 @@ database::match_result_type database::match_settled_debt_limit( const limit_orde

asset call_pays = order_receives;
if( taker_filled )
call_pays.amount = taker.settled_collateral_amount;
else if( taker.for_sale != taker.settled_collateral_amount )
{
price settle_price = asset( taker.settled_collateral_amount, taker.sell_asset_id() )
/ asset( taker.settled_debt_amount, taker.receive_asset_id() );
call_pays = call_receives * settle_price; // round down here, in favor of call order
}
call_pays.amount = bitasset.individual_settlement_fund;
else if( taker.for_sale != bitasset.individual_settlement_fund )
call_pays = call_receives * bitasset.get_individual_settlement_price(); // round down, in favor of "call order"
if( call_pays < order_receives ) // be defensive, maybe unnecessary
{
wlog( "Unexpected scene: call_pays < order_receives" );
Expand All @@ -1263,7 +1254,7 @@ database::match_result_type database::match_settled_debt_limit( const limit_orde
asset collateral_fee = call_pays - order_receives;

// Reduce current supply, and accumulate collateral fees
const asset_dynamic_data_object& mia_ddo = call_receives.asset_id(*this).dynamic_asset_data_id(*this);
const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);
modify( mia_ddo, [&call_receives,&collateral_fee]( asset_dynamic_data_object& ao ){
ao.current_supply -= call_receives.amount;
ao.accumulated_collateral_fees += collateral_fee.amount;
Expand All @@ -1274,24 +1265,27 @@ database::match_result_type database::match_settled_debt_limit( const limit_orde
push_applied_operation( fill_order_operation( taker.id, taker.seller, call_pays, call_receives,
collateral_fee, match_price, false ) );

// Update bitasset data
modify( bitasset, [&call_receives,&call_pays]( asset_bitasset_data_object& obj ){
obj.individual_settlement_debt -= call_receives.amount;
obj.individual_settlement_fund -= call_pays.amount;
});

// Update the taker order
// Note: CORE asset in settled debt is not counted in account_stats.total_core_in_orders
if( taker_filled )
remove( taker );
else
{
modify( taker, [&call_pays,&call_receives]( limit_order_object& obj ) {
obj.settled_debt_amount -= call_receives.amount;
obj.settled_collateral_amount -= call_pays.amount;
modify( taker, [&call_pays,&call_receives,&bitasset]( limit_order_object& obj ) {
// Note: for simplicity, only update price when necessary
asset settled_debt( obj.settled_debt_amount, obj.receive_asset_id() );
asset settled_debt( bitasset.individual_settlement_debt, obj.receive_asset_id() );
obj.for_sale = settled_debt.multiply_and_round_up( obj.sell_price ).amount;
if( obj.for_sale > obj.settled_collateral_amount ) // be defensive, maybe unnecessary
if( obj.for_sale > bitasset.individual_settlement_fund ) // be defensive, maybe unnecessary
{
wlog( "Unexpected scene: obj.for_sale > obj.settled_collateral_amount" );
obj.for_sale = obj.settled_collateral_amount;
obj.sell_price.base.amount = obj.settled_collateral_amount;
obj.sell_price.quote.amount = obj.settled_debt_amount;
wlog( "Unexpected scene: obj.for_sale > bitasset.individual_settlement_fund" );
obj.for_sale = bitasset.individual_settlement_fund;
obj.sell_price = ~bitasset.get_individual_settlement_price();
}
});
}
Expand Down
11 changes: 5 additions & 6 deletions libraries/chain/db_update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ static void update_settled_debt_order( database& db, const asset_bitasset_data_o

// Note: bitasset.get_margin_call_order_price() is in debt/collateral
price sell_price = ~bitasset.get_margin_call_order_price();
asset settled_debt( limit_ptr->settled_debt_amount, limit_ptr->receive_asset_id() );
asset settled_debt( bitasset.individual_settlement_debt, limit_ptr->receive_asset_id() );
try
{
for_sale = settled_debt.multiply_and_round_up( sell_price ).amount; // may overflow
if( for_sale <= limit_ptr->settled_collateral_amount ) // "=" is for the consistency of order matching logic
if( for_sale <= bitasset.individual_settlement_fund ) // "=" is for the consistency of order matching logic
sell_all = false;
}
catch( const fc::exception& e ) // catch the overflow
Expand All @@ -252,13 +252,12 @@ static void update_settled_debt_order( database& db, const asset_bitasset_data_o
}

// TODO Potential optimization: to avoid unnecessary database update, check before update
db.modify( *limit_ptr, [sell_all, &sell_price, &for_sale]( limit_order_object& obj )
db.modify( *limit_ptr, [sell_all, &sell_price, &for_sale, &bitasset]( limit_order_object& obj )
{
if( sell_all )
{
obj.for_sale = obj.settled_collateral_amount;
obj.sell_price.base.amount = obj.settled_collateral_amount;
obj.sell_price.quote.amount = obj.settled_debt_amount;
obj.for_sale = bitasset.individual_settlement_fund;
obj.sell_price = ~bitasset.get_individual_settlement_price();
}
else
{
Expand Down
20 changes: 16 additions & 4 deletions libraries/chain/include/graphene/chain/asset_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ namespace graphene { namespace chain {
* In the event of global settlement, all margin positions
* are settled with the siezed collateral being moved into the settlement fund. From this
* point on forced settlement occurs immediately when requested, using the settlement price and fund.
*
* @note After the core-2591 hardfork, forced settlements may be paid at the margin call order price (MCOP)
* when applicable, but are not limited to the settlement price stated here.
*/
///@{
/// Price at which force settlements of a globally settled asset will occur
Expand All @@ -311,17 +314,26 @@ namespace graphene { namespace chain {
///@}

/// The individual settlement pool.
/// In the event of individual settlements to fund, debt and collateral of the margin positions which got
/// settled are moved here.
/// In the event of individual settlements (to fund or to order), debt and collateral of the margin positions
/// which got settled are moved here.
/// * For individual settlement to fund, assets in the pool can only be retrieved through forced settlements.
/// * For individual settlement to order, assets in the pool can only be retrieved through limit orders.
///@{
/// Amount of debt due to individual settlements
share_type individual_settlement_debt;
/// Amount of collateral which is available for force settlement due to individual settlements
share_type individual_settlement_fund;
///@}

/// @return true if the individual settlement pool is not empty, false otherwise
bool is_individually_settled_to_fund()const { return ( individual_settlement_debt != 0 ); }
/// @return true if the individual settlement pool is not empty and the bitasset's black swan response method
/// (BSRM) is @ref bitasset_options::black_swan_response_type::individual_settlement_to_fund,
/// false otherwise
bool is_individually_settled_to_fund()const
{
using bsrm_type = bitasset_options::black_swan_response_type;
return ( ( individual_settlement_debt != 0 ) &&
( bsrm_type::individual_settlement_to_fund == get_black_swan_response_method() ) );
}

/// Get the price of the individual settlement pool
price get_individual_settlement_price() const
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/include/graphene/chain/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

#define GRAPHENE_MAX_NESTED_OBJECTS (200)

const std::string GRAPHENE_CURRENT_DB_VERSION = "20230322";
const std::string GRAPHENE_CURRENT_DB_VERSION = "20230527";

#define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4
#define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3
Expand Down
2 changes: 0 additions & 2 deletions libraries/chain/include/graphene/chain/market_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ class limit_order_object : public abstract_object<limit_order_object, protocol_i
share_type deferred_fee; ///< fee converted to CORE
asset deferred_paid_fee; ///< originally paid fee
bool is_settled_debt = false; ///< Whether this order is an individual settlement fund
share_type settled_debt_amount; ///< Debt amount if this order is an individual settlement fund
share_type settled_collateral_amount; ///< Collateral amount if the order is an individual settlement fund

pair<asset_id_type,asset_id_type> get_market()const
{
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/market_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ share_type call_order_object::get_max_debt_to_cover( price match_price,
FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::limit_order_object,
(graphene::db::object),
(expiration)(seller)(for_sale)(sell_price)(deferred_fee)(deferred_paid_fee)
(is_settled_debt)(settled_debt_amount)(settled_collateral_amount)
(is_settled_debt)
)

FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::call_order_object, (graphene::db::object),
Expand Down
7 changes: 3 additions & 4 deletions tests/common/database_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,10 +579,9 @@ void database_fixture_base::verify_asset_supplies( const database& db )
total_balances[o.deferred_paid_fee.asset_id] += o.deferred_paid_fee.amount;
if( o.is_settled_debt )
{
total_balances[for_sale.asset_id] += o.settled_collateral_amount;
total_debts[o.receive_asset_id()] += o.settled_debt_amount;
BOOST_CHECK_LE( o.for_sale.value, o.settled_collateral_amount.value );
auto settled_debt = asset( o.settled_debt_amount.value, o.receive_asset_id() );
const auto& bitasset = o.receive_asset_id()(db).bitasset_data(db);
BOOST_CHECK_LE( o.for_sale.value, bitasset.individual_settlement_fund.value );
auto settled_debt = asset( bitasset.individual_settlement_debt, o.receive_asset_id() );
BOOST_CHECK_EQUAL( settled_debt.multiply_and_round_up( o.sell_price ).amount.value, o.for_sale.value );
}
else
Expand Down
Loading

0 comments on commit 6f410b1

Please sign in to comment.