-
Notifications
You must be signed in to change notification settings - Fork 648
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement BSIP 74: Margin Call Fee Ratio #2130
Changes from all commits
e9e0e7e
00b2ed1
8d5aad6
fee9fe6
ca6f361
fa7d4d5
a43e18a
2b120c9
4fd251c
35d797a
3ea265a
4684e51
1aee59c
750c720
9ea7962
84c3e04
75374de
a394deb
d11ecbf
fad10a6
0fae92f
3531df9
96c2d98
1424257
476e3fd
efff548
d062cd2
eb9a898
4c0ac06
f3f363c
7df91b4
5c2210b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,14 @@ namespace detail { | |
return static_cast<int64_t>(a); | ||
} | ||
|
||
share_type calculate_ratio( const share_type& value, uint16_t ratio) | ||
{ | ||
fc::uint128_t a(value.value); | ||
a *= (ratio-GRAPHENE_COLLATERAL_RATIO_DENOM); | ||
a /= GRAPHENE_COLLATERAL_RATIO_DENOM; | ||
return static_cast<int64_t>(a); | ||
} | ||
|
||
} //detail | ||
|
||
/** | ||
|
@@ -474,10 +482,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo | |
&& !sell_abd->has_settlement() | ||
&& !sell_abd->current_feed.settlement_price.is_null() ) | ||
{ | ||
if( before_core_hardfork_1270 ) | ||
call_match_price = ~sell_abd->current_feed.max_short_squeeze_price_before_hf_1270(); | ||
else | ||
call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); | ||
call_match_price = ~get_max_short_squeeze_price( maint_time, sell_abd->current_feed ); | ||
if( ~new_order_object.sell_price <= call_match_price ) // new limit order price is good enough to match a call | ||
to_check_call_orders = true; | ||
} | ||
|
@@ -685,8 +690,9 @@ int database::match( const limit_order_object& bid, const call_order_object& ask | |
order_pays = call_receives; | ||
|
||
int result = 0; | ||
result |= fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, false ); // the limit order is taker | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's better to keep the comment. |
||
result |= fill_call_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker | ||
result |= fill_limit_order( bid, order_pays, order_receives, cull_taker, match_price, | ||
false ); // limit order is the taker | ||
result |= fill_call_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker | ||
// result can be 0 when call order has target_collateral_ratio option set. | ||
|
||
return result; | ||
|
@@ -803,9 +809,8 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p | |
FC_ASSERT( pays.asset_id != receives.asset_id ); | ||
|
||
const account_object& seller = order.seller(*this); | ||
const asset_object& recv_asset = receives.asset_id(*this); | ||
|
||
auto issuer_fees = pay_market_fees(&seller, recv_asset, receives, is_maker); | ||
const auto issuer_fees = pay_market_fees(&seller, receives.asset_id(*this), receives, is_maker); | ||
|
||
pay_order( seller, receives - issuer_fees, pays ); | ||
|
||
|
@@ -897,11 +902,19 @@ bool database::fill_limit_order( const limit_order_object& order, const asset& p | |
return maybe_cull_small_order( *this, order ); | ||
return false; | ||
} | ||
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } | ||
|
||
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } | ||
|
||
/*** | ||
* @brief fill a call order in the specified amounts | ||
* @param order the call order | ||
* @param pays What the call order will give to the other party (collateral) | ||
* @param receives what the call order will receive from the other party (debt) | ||
* @param fill_price the price at which the call order will execute | ||
* @param is_maker TRUE if the call order is the maker, FALSE if it is the taker | ||
* @returns TRUE if the call order was completely filled | ||
*/ | ||
bool database::fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, | ||
const price& fill_price, const bool is_maker ) | ||
const price& fill_price, const bool is_maker ) | ||
{ try { | ||
FC_ASSERT( order.debt_type() == receives.asset_id ); | ||
FC_ASSERT( order.collateral_type() == pays.asset_id ); | ||
|
@@ -910,53 +923,60 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay | |
// TODO pass in mia and bitasset_data for better performance | ||
const asset_object& mia = receives.asset_id(*this); | ||
FC_ASSERT( mia.is_market_issued() ); | ||
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); | ||
|
||
// calculate any margin call fees NOTE: Paid in collateral asset | ||
const auto margin_fee = pay_margin_fees(mia, pays); | ||
|
||
optional<asset> collateral_freed; | ||
modify( order, [&]( call_order_object& o ){ | ||
o.debt -= receives.amount; | ||
o.collateral -= pays.amount; | ||
if( o.debt == 0 ) | ||
// adjust the order | ||
modify( order, [&]( call_order_object& o ) { | ||
o.debt -= receives.amount; | ||
o.collateral -= pays.amount + margin_fee.amount; | ||
if( o.debt == 0 ) // is the whole debt paid? | ||
{ | ||
collateral_freed = o.get_collateral(); | ||
o.collateral = 0; | ||
} | ||
else // the debt was not completely paid | ||
{ | ||
auto maint_time = get_dynamic_global_properties().next_maintenance_time; | ||
// update call_price after core-343 hard fork, | ||
// but don't update call_price after core-1270 hard fork | ||
if( maint_time <= HARDFORK_CORE_1270_TIME && maint_time > HARDFORK_CORE_343_TIME ) | ||
{ | ||
collateral_freed = o.get_collateral(); | ||
o.collateral = 0; | ||
} | ||
else | ||
{ | ||
auto maint_time = get_dynamic_global_properties().next_maintenance_time; | ||
// update call_price after core-343 hard fork, | ||
// but don't update call_price after core-1270 hard fork | ||
if( maint_time <= HARDFORK_CORE_1270_TIME && maint_time > HARDFORK_CORE_343_TIME ) | ||
{ | ||
o.call_price = price::call_price( o.get_debt(), o.get_collateral(), | ||
mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); | ||
} | ||
o.call_price = price::call_price( o.get_debt(), o.get_collateral(), | ||
bitasset.current_feed.maintenance_collateral_ratio ); | ||
} | ||
} | ||
}); | ||
|
||
// update current supply | ||
const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); | ||
|
||
modify( mia_ddo, [&receives]( asset_dynamic_data_object& ao ){ | ||
ao.current_supply -= receives.amount; | ||
}); | ||
|
||
// Adjust balance | ||
// If the whole debt is paid, adjust borrower's collateral balance | ||
if( collateral_freed.valid() ) | ||
adjust_balance( order.borrower, *collateral_freed ); | ||
|
||
// Update account statistics. We know that order.collateral_type() == pays.asset_id | ||
if( pays.asset_id == asset_id_type() ) | ||
{ | ||
modify( get_account_stats_by_owner(order.borrower), [&collateral_freed,&pays]( account_statistics_object& b ){ | ||
b.total_core_in_orders -= pays.amount; | ||
modify( get_account_stats_by_owner(order.borrower), | ||
[&collateral_freed,&pays,&margin_fee]( account_statistics_object& b ){ | ||
b.total_core_in_orders -= pays.amount + margin_fee.amount; | ||
if( collateral_freed.valid() ) | ||
b.total_core_in_orders -= collateral_freed->amount; | ||
}); | ||
} | ||
|
||
// virtual operation for account history | ||
push_applied_operation( fill_order_operation( order.id, order.borrower, pays, receives, | ||
asset(0, pays.asset_id), fill_price, is_maker ) ); | ||
margin_fee, fill_price, is_maker ) ); | ||
|
||
// Call order completely filled, remove it | ||
if( collateral_freed.valid() ) | ||
remove( order ); | ||
|
||
|
@@ -1034,6 +1054,20 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a | |
|
||
} FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) } | ||
|
||
/*** | ||
* Get the correct max_short_squeeze_price from the price_feed based on chain time | ||
* (due to hardfork changes in the calculation) | ||
* @param block_time the chain's current block time | ||
* @param feed the debt asset's price feed | ||
* @returns the max short squeeze price | ||
*/ | ||
price database::get_max_short_squeeze_price( const fc::time_point_sec& block_time, const price_feed& feed)const | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to move this function from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about that, but all other HARDFORK code (which is the basis for this function) has been kept away from the protocol code. Should I break that rule? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. True, protocol code doesn't include chain/hardfork.hpp. We usually check the time in chain code and convert the result to another meaningful parameter to pass in. |
||
{ | ||
if ( block_time <= HARDFORK_CORE_1270_TIME ) | ||
return feed.max_short_squeeze_price_before_hf_1270(); | ||
return feed.max_short_squeeze_price(); | ||
} | ||
|
||
/** | ||
* Starting with the least collateralized orders, fill them if their | ||
* call price is above the max(lowest bid,call_limit). | ||
|
@@ -1082,8 +1116,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
// looking for limit orders selling the most USD for the least CORE | ||
auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); | ||
// stop when limit orders are selling too little USD for too much CORE | ||
auto min_price = ( before_core_hardfork_1270 ? bitasset.current_feed.max_short_squeeze_price_before_hf_1270() | ||
: bitasset.current_feed.max_short_squeeze_price() ); | ||
auto min_price = get_max_short_squeeze_price( maint_time, bitasset.current_feed); | ||
|
||
// NOTE limit_price_index is sorted from greatest to least | ||
auto limit_itr = limit_price_index.lower_bound( max_price ); | ||
|
@@ -1181,10 +1214,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
} | ||
|
||
asset usd_for_sale = limit_order.amount_for_sale(); | ||
asset call_pays, call_receives, order_pays, order_receives; | ||
asset call_pays, call_receives, limit_pays, limit_receives; | ||
if( usd_to_buy > usd_for_sale ) | ||
{ // fill order | ||
order_receives = usd_for_sale * match_price; // round down, in favor of call order | ||
limit_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): | ||
|
@@ -1201,7 +1234,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
// 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.multiply_and_round_up( match_price ); | ||
call_receives = limit_receives.multiply_and_round_up( match_price ); | ||
|
||
filled_limit = true; | ||
|
||
|
@@ -1210,10 +1243,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
|
||
if( before_core_hardfork_342 ) | ||
{ | ||
order_receives = usd_to_buy * match_price; // round down, in favor of call order | ||
limit_receives = usd_to_buy * match_price; // round down, in favor of call order | ||
} | ||
else | ||
order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order | ||
limit_receives = usd_to_buy.multiply_and_round_up( 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) | ||
|
||
|
@@ -1227,8 +1260,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
} | ||
} | ||
|
||
call_pays = order_receives; | ||
order_pays = call_receives; | ||
call_pays = limit_receives; | ||
limit_pays = call_receives; | ||
|
||
if( filled_call && before_core_hardfork_343 ) | ||
++call_price_itr; | ||
|
@@ -1241,7 +1274,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa | |
|
||
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_order, order_pays, order_receives, true, match_price, !for_new_limit_order ); | ||
bool really_filled = fill_limit_order( limit_order, limit_pays, limit_receives, true, match_price, !for_new_limit_order ); | ||
if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) | ||
limit_itr = next_limit_itr; | ||
|
||
|
@@ -1295,6 +1328,39 @@ asset database::calculate_market_fee( const asset_object& trade_asset, const ass | |
return percent_fee; | ||
} | ||
|
||
/** | ||
* @brief Calculate the margin fee that is to be taken | ||
* @param debt the indebted asset | ||
* @param collateral the amount of collateral received (before fees) | ||
* @returns the amount of fee that should be collected | ||
*/ | ||
asset database::calculate_margin_fee(const asset_object& debt, const asset& collateral)const | ||
{ | ||
auto ba = debt.bitasset_data(*this); | ||
auto price_feed = ba.current_feed; | ||
if ( price_feed.settlement_price.is_null() | ||
|| price_feed.settlement_price.base.amount == 0 | ||
|| !ba.options.extensions.value.current_margin_call_fee_ratio.valid()) | ||
return asset(0); | ||
auto amount = detail::calculate_ratio( collateral.amount, | ||
*ba.options.extensions.value.current_margin_call_fee_ratio ); | ||
return asset(amount, collateral.asset_id) ; | ||
} | ||
|
||
/**** | ||
* @brief calculate the margin fee and distribute it | ||
* @param debt_asset the indebted asset | ||
* @param collarteral the amount of the collateral | ||
* @returns the amount of the fee that was collected | ||
*/ | ||
asset database::pay_margin_fees(const asset_object& debt_asset, const asset& collateral) | ||
{ | ||
const auto margin_fees = calculate_margin_fee( debt_asset, collateral ); | ||
if (margin_fees.amount.value != 0) | ||
debt_asset.accumulate_fee(*this, margin_fees); | ||
return margin_fees; | ||
} | ||
|
||
asset database::pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives, | ||
const bool& is_maker) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// bitshares-core BSIP 74 add margin call fee | ||
#ifndef HARDFORK_CORE_BSIP74_TIME | ||
#define HARDFORK_CORE_BSIP74_TIME (fc::time_point_sec( 1679955066 ) ) // Temporary date until actual hardfork date is set | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function name is misleading due to the
ratio-GRAPHENE_COLLATERAL_RATIO_DENOM
calculation in the implementation. By the wayratio-GRAPHENE_COLLATERAL_RATIO_DENOM
may underflow.