diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 1f30290c87..0781321eac 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -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() || @@ -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); @@ -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() ) { @@ -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) ) } @@ -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; @@ -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 @@ -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)) } @@ -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 @@ -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); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 76c4ea6932..c3a03fa9f3 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -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. // // 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 ); - 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) { @@ -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) ) } /** @@ -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; +} + } } diff --git a/libraries/chain/hardfork.d/CORE_BSIP87.hf b/libraries/chain/hardfork.d/CORE_BSIP87.hf new file mode 100644 index 0000000000..a4fbc39f34 --- /dev/null +++ b/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 diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index e4c5b880a8..b11cb2e983 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -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); ///@} diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 544e07ef3e..d73e343759 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -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 @@ -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 { diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 7bfd65ef00..812df9b98c 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -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 diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index cc24c17f6e..fd4c53297c 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -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 initial_collateral_ratio; + fc::optional initial_collateral_ratio; // BSIP-77 + fc::optional force_settle_fee_percent; // BSIP-87 }; /// Time before a price feed expires @@ -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) @@ -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, )