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 BSIP87: Force-Settlement Fee #2151

Merged
merged 8 commits into from May 7, 2020
45 changes: 29 additions & 16 deletions libraries/chain/asset_evaluator.cpp
Expand Up @@ -66,7 +66,16 @@ namespace detail {
}
}

void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op)
void check_bitasset_options_hf_bsip87(const fc::time_point_sec& block_time, const bitasset_options& options)
{
// HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:
FC_ASSERT( !options.extensions.value.force_settle_fee_percent.valid()
|| block_time >= HARDFORK_CORE_BSIP87_TIME,
"A BitAsset's FSFP cannot be set before Hardfork BSIP87" );
}

void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time,
const asset_claim_fees_operation& op)
{
// HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:
FC_ASSERT( !op.extensions.value.claim_from_asset_id.valid() ||
Expand All @@ -80,15 +89,20 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o
{ try {

const database& d = db();
// Define now from the current block time
const time_point_sec now = d.head_block_time();

// Hardfork Checks:
detail::check_asset_options_hf_1774(now, op.common_options);
detail::check_asset_options_hf_bsip81(now, op.common_options);
if( op.bitasset_opts ) {
detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts );
detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE
}

const auto& chain_parameters = d.get_global_properties().parameters;
FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );
FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );

detail::check_asset_options_hf_1774( now, op.common_options );

// Check that all authorities do exist
for( auto id : op.common_options.whitelist_authorities )
d.get_object(id);
Expand Down Expand Up @@ -117,7 +131,6 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o

if( op.bitasset_opts )
{
detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts );
const asset_object& backing = op.bitasset_opts->short_backing_asset(d);
if( backing.is_market_issued() )
{
Expand All @@ -133,15 +146,13 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o
FC_ASSERT( op.bitasset_opts->feed_lifetime_sec > chain_parameters.block_interval &&
op.bitasset_opts->force_settlement_delay_sec > chain_parameters.block_interval );
}

if( op.is_prediction_market )
{
FC_ASSERT( op.bitasset_opts );
FC_ASSERT( op.precision == op.bitasset_opts->short_backing_asset(d).precision );
}

// Check the taker fee percent
detail::check_asset_options_hf_bsip81(now, op.common_options);

return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

Expand Down Expand Up @@ -305,6 +316,10 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)
const database& d = db();
const time_point_sec now = d.head_block_time();

// Hardfork Checks:
detail::check_asset_options_hf_1774(now, o.new_options);
detail::check_asset_options_hf_bsip81(now, o.new_options);

const asset_object& a = o.asset_to_update(d);
auto a_copy = a;
a_copy.options = o.new_options;
Expand All @@ -317,8 +332,6 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)
validate_new_issuer( d, a, *o.new_issuer );
}

detail::check_asset_options_hf_1774( now, o.new_options );

if( a.dynamic_asset_data_id(d).current_supply != 0 )
{
// new issuer_permissions must be subset of old issuer permissions
Expand All @@ -344,9 +357,6 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)
for( auto id : o.new_options.blacklist_authorities )
d.get_object(id);

// Check the taker fee percent
detail::check_asset_options_hf_bsip81(now, o.new_options);

return void_result();
} FC_CAPTURE_AND_RETHROW((o)) }

Expand Down Expand Up @@ -425,7 +435,7 @@ void_result asset_update_issuer_evaluator::do_apply(const asset_update_issuer_op
* @param true if after hf 922/931 (if nothing triggers, this and the logic that depends on it
* should be removed).
*/
void check_children_of_bitasset(database& d, const asset_update_bitasset_operation& op,
void check_children_of_bitasset(const database& d, const asset_update_bitasset_operation& op,
const asset_object& new_backing_asset)
{
// no need to do these checks if the new backing asset is CORE
Expand Down Expand Up @@ -454,9 +464,12 @@ void check_children_of_bitasset(database& d, const asset_update_bitasset_operati

void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bitasset_operation& op)
{ try {
database& d = db();
const database& d = db();
const time_point_sec now = d.head_block_time();

detail::check_bitasset_options_hf_bsip77( d.head_block_time(), op.new_options );
// Hardfork Checks:
detail::check_bitasset_options_hf_bsip77( now, op.new_options );
detail::check_bitasset_options_hf_bsip87( now, op.new_options ); // HF_REMOVABLE

const asset_object& asset_obj = op.asset_to_update(d);

Expand Down
72 changes: 68 additions & 4 deletions libraries/chain/db_market.cpp
Expand Up @@ -963,22 +963,55 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
return collateral_freed.valid();
} FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) }

/***
* @brief fullfill a settle order in the specified amounts
*
* @details Called from database::match(), this coordinates exchange of debt asset X held in the
* settle order for collateral asset Y held in a call order, and routes fees. Note that we
* don't touch the call order directly, as match() handles this via a separate call to
* fill_call_order(). We are told exactly how much X and Y to exchange, based on details of
* order matching determined higher up the call chain. Thus it is possible that the settle
* order is not completely satisfied at the conclusion of this function.
*
* @param settle the force_settlement object
* @param pays the quantity of market-issued debt asset X which the settler will yield in this
* round (may be less than the full amount indicated in settle object)
* @param receives the quantity of collateral asset Y which the settler will receive in
* exchange for X
* @param fill_price the price at which the settle order will execute (not used - passed through
* to virtual operation)
* @param is_maker TRUE if the settle order is the maker, FALSE if it is the taker (passed
* through to virtual operation)
* @returns TRUE if the settle order was completely filled, FALSE if only partially filled
*/
bool database::fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives,
const price& fill_price, const bool is_maker )
{ try {
bool filled = false;

const account_object* settle_owner_ptr = nullptr;
// The owner of the settle order pays market fees to the issuer of the collateral asset after HF core-1780
// The owner of the settle order pays market fees to the issuer of the collateral asset.
// After HF core-1780, these fees are shared to the referral program, which is flagged to
// pay_market_fees by setting settle_owner_ptr non-null.
abitmore marked this conversation as resolved.
Show resolved Hide resolved
//
// TODO Check whether the HF check can be removed after the HF.
// Note: even if logically it can be removed, perhaps the removal will lead to a small performance
// loss. Needs testing.
if( head_block_time() >= HARDFORK_CORE_1780_TIME )
settle_owner_ptr = &settle.owner(*this);
// Compute and pay the market fees:
asset market_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives, is_maker );

// Issuer of the settled smartcoin asset lays claim to a force-settlement fee (BSIP87), but
// note that fee is denominated in collateral asset, not the debt asset. Asset object of
// debt asset is passed to the pay function so it knows where to put the fee. Note that
// amount of collateral asset upon which fee is assessed is reduced by market_fees already
// paid to prevent the total fee exceeding total collateral.
asset force_settle_fees = pay_force_settle_fees( get(pays.asset_id), receives - market_fees );
abitmore marked this conversation as resolved.
Show resolved Hide resolved

auto issuer_fees = pay_market_fees( settle_owner_ptr, get(receives.asset_id), receives, is_maker );
auto total_collateral_denominated_fees = market_fees + force_settle_fees;

// If we don't consume entire settle order:
if( pays < settle.balance )
{
modify(settle, [&pays](force_settlement_object& s) {
Expand All @@ -987,15 +1020,18 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a
} else {
filled = true;
}
adjust_balance(settle.owner, receives - issuer_fees);
// Give released collateral not already taken as fees to settle order owner:
adjust_balance(settle.owner, receives - total_collateral_denominated_fees);

assert( pays.asset_id != receives.asset_id );
push_applied_operation( fill_order_operation( settle.id, settle.owner, pays, receives, issuer_fees, fill_price, is_maker ) );
push_applied_operation( fill_order_operation( settle.id, settle.owner, pays, receives,
total_collateral_denominated_fees, fill_price, is_maker ) );

if (filled)
remove(settle);

return filled;

} FC_CAPTURE_AND_RETHROW( (settle)(pays)(receives) ) }

/**
Expand Down Expand Up @@ -1361,4 +1397,32 @@ asset database::pay_market_fees(const account_object* seller, const asset_object
return market_fees;
}

/***
* @brief Calculate force-settlement fee and give it to issuer of the settled asset
* @param collecting_asset the smart asset object which should receive the fee
* @param collat_receives the amount of collateral the settler would expect to receive absent this fee
* (fee is computed as a percentage of this amount)
* @return asset denoting the amount of fee collected
*/
asset database::pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives)
{
FC_ASSERT( collecting_asset.get_id() != collat_receives.asset_id );

const bitasset_options& collecting_bitasset_opts = collecting_asset.bitasset_data(*this).options;

if( !collecting_bitasset_opts.extensions.value.force_settle_fee_percent.valid()
|| *collecting_bitasset_opts.extensions.value.force_settle_fee_percent == 0 )
return asset{ 0, collat_receives.asset_id };

auto value = detail::calculate_percent(collat_receives.amount,
*collecting_bitasset_opts.extensions.value.force_settle_fee_percent);
asset settle_fee = asset{ value, collat_receives.asset_id };

// Deposit fee in asset's dynamic data object:
if( value > 0) {
collecting_asset.accumulate_fee(*this, settle_fee);
}
return settle_fee;
}

} }
4 changes: 4 additions & 0 deletions libraries/chain/hardfork.d/CORE_BSIP87.hf
@@ -0,0 +1,4 @@
// bitshares-core BSIP 87: add force-settlement fee percentage:
#ifndef HARDFORK_CORE_BSIP87_TIME
#define HARDFORK_CORE_BSIP87_TIME (fc::time_point_sec( 1679955066 ) ) // Temporary date until actual hardfork date is set
#endif
1 change: 1 addition & 0 deletions libraries/chain/include/graphene/chain/database.hpp
Expand Up @@ -442,6 +442,7 @@ namespace graphene { namespace chain {
asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount, const bool& is_maker);
asset pay_market_fees(const account_object* seller, const asset_object& recv_asset, const asset& receives,
const bool& is_maker);
asset pay_force_settle_fees(const asset_object& collecting_asset, const asset& collat_receives);
///@}


Expand Down
31 changes: 19 additions & 12 deletions libraries/chain/proposal_evaluator.cpp
Expand Up @@ -30,9 +30,12 @@ namespace graphene { namespace chain {

namespace detail {
void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options);
void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options);
void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options);
void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op);
void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options);
void check_bitasset_options_hf_bsip87(const fc::time_point_sec& block_time,
const bitasset_options& options); // HF_REMOVABLE
void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time,
const asset_claim_fees_operation& op); // HF_REMOVABLE
}

struct proposal_operation_hardfork_visitor
Expand All @@ -49,30 +52,34 @@ struct proposal_operation_hardfork_visitor
void operator()(const T &v) const {}

void operator()(const graphene::chain::asset_create_operation &v) const {
// hf_1774
detail::check_asset_options_hf_1774(block_time, v.common_options);

// HARDFORK_BSIP_77
if( v.bitasset_opts.valid() )
detail::check_asset_options_hf_1774(block_time, v.common_options);
detail::check_asset_options_hf_bsip81(block_time, v.common_options);
if( v.bitasset_opts.valid() ) {
detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts );
detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE
}

// HARDFORK_BSIP_81
detail::check_asset_options_hf_bsip81(block_time, v.common_options);
}

void operator()(const graphene::chain::asset_update_operation &v) const {
// hf_1774
detail::check_asset_options_hf_1774(block_time, v.new_options);

// HARDFORK_BSIP_81
detail::check_asset_options_hf_1774(block_time, v.new_options);
detail::check_asset_options_hf_bsip81(block_time, v.new_options);

}

void operator()(const graphene::chain::asset_update_bitasset_operation &v) const {
// HARDFORK_BSIP_77

detail::check_bitasset_options_hf_bsip77( block_time, v.new_options );
detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE

}

void operator()(const graphene::chain::asset_claim_fees_operation &v) const {

detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE

}

void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const {
Expand Down
4 changes: 4 additions & 0 deletions libraries/protocol/asset_ops.cpp
Expand Up @@ -216,6 +216,10 @@ void bitasset_options::validate() const
FC_ASSERT( *extensions.value.initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );
FC_ASSERT( *extensions.value.initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
}

if( extensions.value.force_settle_fee_percent.valid() )
FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT );

}

void asset_options::validate()const
Expand Down
6 changes: 4 additions & 2 deletions libraries/protocol/include/graphene/protocol/asset_ops.hpp
Expand Up @@ -106,7 +106,8 @@ namespace graphene { namespace protocol {
/// After BSIP77, when creating a new debt position or updating an existing position,
/// the position will be checked against this parameter.
/// Unused for prediction markets, although we allow it to be set for simpler implementation
fc::optional<uint16_t> initial_collateral_ratio;
fc::optional<uint16_t> initial_collateral_ratio; // BSIP-77
fc::optional<uint16_t> force_settle_fee_percent; // BSIP-87
};

/// Time before a price feed expires
Expand Down Expand Up @@ -562,7 +563,7 @@ FC_REFLECT( graphene::protocol::asset_options,
(extensions)
)

FC_REFLECT( graphene::protocol::bitasset_options::ext, (initial_collateral_ratio) )
FC_REFLECT( graphene::protocol::bitasset_options::ext, (initial_collateral_ratio)(force_settle_fee_percent) )

FC_REFLECT( graphene::protocol::bitasset_options,
(feed_lifetime_sec)
Expand All @@ -579,6 +580,7 @@ FC_REFLECT( graphene::protocol::additional_asset_options,

FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type,
(symbol3)(symbol4)(long_symbol)(price_per_kbyte) )

FC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::protocol::asset_settle_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::protocol::asset_settle_cancel_operation::fee_parameters_type, )
Expand Down