Skip to content
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

Closed
wants to merge 32 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e9e0e7e
hardfork protection for bsip74
jmjatlanta Mar 30, 2020
00b2ed1
HF for BSIP74 happens on time, not next maint
jmjatlanta Mar 30, 2020
8d5aad6
calculating call order price
jmjatlanta Apr 1, 2020
fee9fe6
test of normal case complete
jmjatlanta Apr 2, 2020
ca6f361
Implement margin call fee
jmjatlanta Apr 4, 2020
fa7d4d5
do not assert if fee is zero
jmjatlanta Apr 10, 2020
a43e18a
update feature branch with hf changes
jmjatlanta Apr 10, 2020
2b120c9
Resolve compiler warning
jmjatlanta Apr 10, 2020
4fd251c
fix/add comments
jmjatlanta Apr 11, 2020
35d797a
Merge hardfork into jmj_bsip74
jmjatlanta Apr 17, 2020
3ea265a
fix misinterpretation of method params
jmjatlanta Apr 17, 2020
4684e51
Fix wrong comment
jmjatlanta Apr 17, 2020
1aee59c
match on calls with price feed/(mssr-mcfr)
jmjatlanta Apr 23, 2020
750c720
rollback db_market changes
jmjatlanta Apr 23, 2020
9ea7962
marge hardfork to bsip74
jmjatlanta Apr 23, 2020
84c3e04
Set up for margin fee in collateral
jmjatlanta Apr 24, 2020
75374de
test cleanup
jmjatlanta Apr 27, 2020
a394deb
Merge cjs-collat-asset-container-pr into jmj_bsip74
jmjatlanta Apr 30, 2020
d11ecbf
MSSR / MCFR Testing
jmjatlanta Apr 30, 2020
fad10a6
Test limit as taker
jmjatlanta May 1, 2020
0fae92f
limit maker case
jmjatlanta May 3, 2020
3531df9
merge in hf changes to jmj_bsip74
jmjatlanta May 3, 2020
96c2d98
handle when limit order is taker
jmjatlanta May 4, 2020
1424257
Merge hardfork into jmj_bsip74
jmjatlanta May 4, 2020
476e3fd
call order with insufficient collateral
jmjatlanta May 4, 2020
efff548
wrap long lines in tests
jmjatlanta May 4, 2020
d062cd2
Add check to proposal/asset_create for bsip 74
jmjatlanta May 4, 2020
eb9a898
move margin_fee to bitasset struct
jmjatlanta May 11, 2020
4c0ac06
tweak code, clean comments
jmjatlanta May 11, 2020
f3f363c
Merge hardfork into jmj_bsip74
jmjatlanta May 11, 2020
7df91b4
comment fix
jmjatlanta May 11, 2020
5c2210b
charge margin fee to call order
jmjatlanta May 12, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions libraries/chain/asset_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ namespace detail {
}
}

void check_bitasset_options_bsip74( const fc::time_point_sec& block_time, const bitasset_options& options)
{
FC_ASSERT( block_time >= HARDFORK_CORE_BSIP74_TIME
|| !options.extensions.value.margin_call_fee_ratio.valid(),
"A BitAsset's MCFR cannot be set before Hardfork BSIP74" );
}
// TODO review and remove code below and links to it after HARDFORK_BSIP_81_TIME
void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options)
{
Expand Down Expand Up @@ -131,7 +137,10 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o

if( op.bitasset_opts )
{
detail::check_bitasset_options_bsip74(d.head_block_time(), *op.bitasset_opts);

const asset_object& backing = op.bitasset_opts->short_backing_asset(d);

if( backing.is_market_issued() )
{
const asset_bitasset_data_object& backing_bitasset_data = backing.bitasset_data(d);
Expand Down Expand Up @@ -484,6 +493,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita
// hf 922_931 is a consensus/logic change. This hf cannot be removed.
bool after_hf_core_922_931 = ( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_922_931_TIME );

detail::check_bitasset_options_bsip74(d.head_block_time(), op.new_options);

// Are we changing the backing asset?
if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset )
{
Expand Down Expand Up @@ -829,8 +840,8 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::
// performance loss. Needs testing.
if( d.head_block_time() >= HARDFORK_CORE_1780_TIME )
{
const bool is_maker = false; // Settlement orders are takers
auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d), settled_amount , is_maker );
auto issuer_fees = d.pay_market_fees( fee_paying_account, settled_amount.asset_id(d),
settled_amount, false );
settled_amount -= issuer_fees;
}

Expand Down
25 changes: 25 additions & 0 deletions libraries/chain/asset_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
// update data derived from ICR
current_initial_collateralization = price();
}
adjust_mcfr();
return;
}
if( current_feeds.size() == 1 )
Expand All @@ -90,6 +91,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
// update data derived from ICR
refresh_current_initial_collateralization();
}
adjust_mcfr();
return;
}

Expand Down Expand Up @@ -118,6 +120,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
// update data derived from ICR
refresh_current_initial_collateralization();
}
adjust_mcfr();
}

void asset_bitasset_data_object::refresh_current_initial_collateralization()
Expand Down Expand Up @@ -204,6 +207,28 @@ string asset_object::amount_to_string(share_type amount) const
return result;
}

uint16_t asset_bitasset_data_object::adjust_mcfr()
{
if (options.extensions.value.margin_call_fee_ratio.valid())
{
auto mcfr = *options.extensions.value.margin_call_fee_ratio;
// Reduce mcfr if it causes parameters to go out of range
if ( ( 1 > current_feed.maximum_short_squeeze_ratio
|| current_feed.maximum_short_squeeze_ratio >= current_feed.maintenance_collateral_ratio)
|| (1 > current_feed.maximum_short_squeeze_ratio - mcfr
|| current_feed.maximum_short_squeeze_ratio - mcfr >= current_feed.maintenance_collateral_ratio))
mcfr = current_feed.maximum_short_squeeze_ratio - 1;
options.extensions.value.current_margin_call_fee_ratio = mcfr;
return mcfr;
}
else
{
options.extensions.value.current_margin_call_fee_ratio = fc::optional<uint16_t>();
}

return 0;
}

FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (graphene::db::object),
(current_supply)(confidential_supply)(accumulated_fees)(accumulated_collateral_fees)(fee_pool) )

Expand Down
152 changes: 109 additions & 43 deletions libraries/chain/db_market.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Copy link
Member

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 way ratio-GRAPHENE_COLLATERAL_RATIO_DENOM may underflow.


} //detail

/**
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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 );

Expand Down Expand Up @@ -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 );
Expand All @@ -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 );

Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to move this function from database class to price_feed class.

Copy link
Contributor Author

@jmjatlanta jmjatlanta May 12, 2020

Choose a reason for hiding this comment

The 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?

Copy link
Member

Choose a reason for hiding this comment

The 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).
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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):
Expand All @@ -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;

Expand All @@ -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)

Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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)
{
Expand Down
8 changes: 2 additions & 6 deletions libraries/chain/db_update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,8 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s
return false;

price highest = settle_price;
if( maint_time > HARDFORK_CORE_1270_TIME )
// due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here
highest = bitasset.current_feed.max_short_squeeze_price();
else if( maint_time > HARDFORK_CORE_338_TIME )
// due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here
highest = bitasset.current_feed.max_short_squeeze_price_before_hf_1270();
if (maint_time > HARDFORK_CORE_338_TIME)
highest = get_max_short_squeeze_price( maint_time, bitasset.current_feed);

const limit_order_index& limit_index = get_index_type<limit_order_index>();
const auto& limit_price_index = limit_index.indices().get<by_price>();
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/hardfork.d/CORE_BSIP74.hf
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
Loading